270 lines
9.8 KiB
Markdown
270 lines
9.8 KiB
Markdown
# 🔐 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. |
|