# 🔐 PasteVault A secure, self-hosted code and text sharing tool with syntax highlighting, expiring links, and optional password protection. Think Pastebin — but private, expiring, and deployed on Azure. --- ## What the Application Does PasteVault allows users to: - Paste code or text and receive a unique shareable URL - Select a programming language for syntax highlighting (15 languages supported) - Set an expiry time (1 hour, 1 day, 7 days, 30 days, or never) - Optionally password-protect a paste - Share the link — only people with the URL (and password if set) can view it - Copy paste content or the share URL with one click - View metadata: creation time, expiry countdown, view count Pastes are automatically deleted from the database when they expire. --- ## Architecture Overview The application uses a **hybrid cloud architecture**: - **Azure VM** (`Standard_D2s_v3` — 2 vCPU, 8 GB RAM, West Europe) runs all application containers via Docker Compose - **Azure PostgreSQL Flexible Server** (`Standard_B1MS`, North Europe) stores all paste data persistently - **Azure Blob Storage** (North Europe) stores database backup dumps - **Cloudflare** (external, pre-existing) provides DNS, HTTPS/TLS termination, and access logs ### Containers (3 total) | Container | Image | Role | |---|---|---| | `pastevault-frontend` | Custom (Nginx + React) | Serves the UI and proxies `/api/` to backend | | `pastevault-backend` | Custom (Python FastAPI) | REST API — paste CRUD, expiry logic | | `pastevault-redis` | `redis:7-alpine` | Caching paste reads, reducing DB load | ### Communication Flow ``` Browser → Cloudflare (HTTPS) → VM port 80 → Nginx (frontend container) ↓ /api/* proxy FastAPI (backend container) ↓ Redis (cache, 30s TTL) ↓ cache miss Azure PostgreSQL (persistent store) ``` --- ## Cloud Services Used | Service | Purpose | Tier | |---|---|---| | Azure VM `Standard_D2s_v3` | Runs Docker Compose with all 3 containers | Pay-as-you-go | | Azure PostgreSQL Flexible Server | Managed relational database | Burstable B1MS (free tier) | | Azure Blob Storage | Backup storage for `pg_dump` files | Standard LRS | | Cloudflare (pre-existing) | DNS, HTTPS certificate, access logs | Free plan | ### Persistent Storage - **PostgreSQL** stores all paste records (id, title, content, language, expiry, views, password hash) - **Redis Docker volume** (`redis_data`) persists cache data across restarts using `--appendonly yes` - **Azure Blob Storage** holds scheduled database backups ### Auto-restart All three containers use `restart: always` in `docker-compose.yml`. If any container crashes, Docker restarts it automatically without any manual intervention. --- ## File Structure ``` sk1/ ├── backend/ │ ├── main.py # FastAPI app — all API endpoints, DB logic, Redis caching │ ├── requirements.txt # Python dependencies │ └── Dockerfile # Python 3.12 slim image ├── frontend/ │ ├── src/ │ │ ├── main.jsx # React entry point │ │ ├── App.jsx # Router — two routes: / and /paste/:id │ │ ├── CreatePaste.jsx # Paste creation form │ │ ├── ViewPaste.jsx # Paste viewer with syntax highlighting │ │ └── index.css # Dark theme stylesheet │ ├── index.html # HTML shell │ ├── package.json # Node dependencies (React, Vite, highlight.js) │ ├── vite.config.js # Vite build config with API proxy for local dev │ ├── nginx.conf # Nginx config — serves React, proxies /api/ to backend │ └── Dockerfile # Multi-stage: Node build → Nginx serve ├── scripts/ │ ├── prepare-app.sh # Full Azure provisioning + deployment script │ ├── remove-app.sh # Teardown script — stops containers + deletes all Azure resources │ └── backup.sh # pg_dump → Azure Blob Storage ├── docker-compose.yml # Defines frontend, backend, redis services ├── .env.example # Template for environment variables (safe to commit) ├── .gitignore # Excludes .env, node_modules, __pycache__, dist └── README.md # This file ``` --- ## Configuration All configuration is done via environment variables in the `.env` file. This file is **never committed to Git**. | Variable | Description | |---|---| | `DATABASE_URL` | PostgreSQL connection string (without `?ssl=`) | | `REDIS_URL` | Redis connection string (default: `redis://redis:6379`) | | `SECRET_KEY` | Used to salt password hashes | | `DB_PASS` | PostgreSQL admin password (used by backup script) | Copy `.env.example` to `.env` and fill in your values before running any scripts. --- ## How to Run the Application ### Prerequisites - Azure CLI installed and logged in (`az login`) - SSH key at `~/.ssh/id_rsa` - `.env` file filled in ### Deploy ```bash cd sk1/ bash scripts/prepare-app.sh ``` This script automatically: 1. Creates the Azure resource group 2. Provisions the VM (Standard_D2s_v3, West Europe) 3. Creates PostgreSQL Flexible Server (B1MS, North Europe) 4. Creates Azure Blob Storage for backups 5. Installs Docker on the VM 6. Copies app files to the VM 7. Builds and starts all containers ### Teardown ```bash bash scripts/remove-app.sh ``` Stops all containers and deletes the entire Azure resource group (VM, database, storage, IP). ### Conditions to run scripts - `prepare-app.sh`: Azure CLI logged in, `.env` present, SSH key at `~/.ssh/id_rsa`, no existing `pastevault-rg` resource group - `remove-app.sh`: Azure CLI logged in, SSH access to VM still working --- ## Using the Application 1. Open the app URL in a browser (`https://yourdomain.com`) 2. Enter a title, paste your code or text into the editor 3. Select the language for syntax highlighting 4. Choose an expiry time (or never) 5. Optionally set a password 6. Click **"🔒 Create Paste"** 7. You are redirected to the paste URL — copy it with the **"🔗 Share"** button 8. Anyone with the link can view the paste (and password if set) --- ## Backup Run from the project root on your local machine (requires `postgresql-client`): ```bash bash scripts/backup.sh ``` This dumps the entire `pastevault` database with `pg_dump` and uploads it to Azure Blob Storage. **List all backups:** ```bash az storage blob list \ --account-name pastevaultstorage \ --container-name backups \ --output table ``` **Download a specific backup:** ```bash az storage blob download \ --account-name pastevaultstorage \ --container-name backups \ --name pastevault_backup_YYYYMMDD_HHMMSS.sql \ --file ./restore.sql ``` **Restore from backup:** ```bash PGPASSWORD=yourpassword psql \ --host=pastevault-db.postgres.database.azure.com \ --username=pvadmin \ --dbname=pastevault \ --file=./restore.sql \ --sslmode=require ``` --- ## Viewing Access Logs ### From Cloudflare (internet traffic) Cloudflare dashboard → your domain → **Analytics** tab shows: - Total requests, unique visitors - Geographic breakdown - Traffic over time - Blocked threats ### From Nginx (per-request logs) ```bash ssh azureuser@YOUR_VM_IP docker logs pastevault-frontend ``` Each line shows: IP address, timestamp, HTTP method, path, status code, response size. ### From the backend ```bash docker logs pastevault-backend ``` ### Live log stream ```bash docker logs -f pastevault-frontend ``` --- ## Cost Analysis — 1000 Users/Day, 50 GB Data | Resource | Spec | Billing | Monthly | Annual | |---|---|---|---|---| | Azure VM `Standard_D2s_v3` | 2 vCPU, 8 GB, West Europe | Per hour | ~$7.30 | ~$87.60 | | Azure PostgreSQL B1MS | 1 vCPU, 2 GB, 50 GB storage | Per hour + GB | ~$13.80 | ~$165.60 | | Azure Blob Storage | 50 GB backups, LRS | Per GB/month | ~$1.00 | ~$12.00 | | Azure Public IP | Standard static IP | Per hour | ~$3.00 | ~$36.00 | | Cloudflare | DNS + HTTPS + CDN | — | $0 | $0 | | **Total** | | | **~$25.10** | **~$301.20** | At 1000 users/day the PostgreSQL B1MS handles load comfortably. For 10,000+ users/day, scaling to `Standard_D2s_v3` for the DB (~$75/month) would be recommended. The biggest cost saving in this architecture is using Cloudflare as the HTTPS/CDN layer for free instead of an Azure Application Gateway (~$120/month). --- ## Known Issues Encountered During Deployment - **Azure B-series VMs unavailable in West Europe and North Europe** — at time of deployment, `Standard_B1s`, `Standard_B2s`, and `Standard_B2ats_v2` had capacity restrictions across multiple European regions. Resolved by using `Standard_D2s_v3` in West Europe. - **PostgreSQL SSL configuration conflict** — `asyncpg` does not accept `?ssl=require` in the connection URL when also passed as a keyword argument. Resolved by keeping `ssl=True` in code only and removing it from the `DATABASE_URL`. - **Docker Compose `version` field warning** — Docker Compose v2 treats the `version` key as obsolete. Harmless warning, does not affect functionality. --- ## External Resources | Resource | Type | Usage | |---|---|---| | FastAPI documentation | Official docs | API structure, startup events, dependency injection | | asyncpg documentation | Official docs | PostgreSQL async connection pool configuration | | highlight.js | Open source library | Client-side syntax highlighting for 15 languages | | Azure CLI documentation | Official docs | VM, PostgreSQL, and storage provisioning commands | | Docker documentation | Official docs | Multi-stage Dockerfile, Compose configuration | | **Claude (Anthropic)** | Generative AI | Used to generate application code. All output reviewed and tested manually. |