docs: add server setup requirements for reverse proxy and CI/CD

This commit is contained in:
Kushal Gaywala
2026-02-28 19:24:12 +01:00
parent e0e0cc65f1
commit 74f72914f5

137
docs/server-setup.md Normal file
View File

@@ -0,0 +1,137 @@
# Server Setup
This document describes the requirements for hosting the BudgetWise monorepo deployments. It is intentionally tool-agnostic — any reverse proxy that meets the requirements below will work.
---
## 1. SSH Deploy Access
CI pipelines connect to the server over SSH to transfer built files. A dedicated deploy user is required.
**Requirements:**
- A non-root system user (e.g. `deploy`) with shell access
- The CI deploy SSH public key added to that user's `~/.ssh/authorized_keys`
- SSH key must correspond to the `ssh_private_key` Woodpecker secret
**Deploy directory:**
- The deploy user must have read/write access to the base deploy path (the value of the `deploy_path` Woodpecker secret, e.g. `/var/www/budget-app`)
- The directory must exist before the first pipeline run
- Subdirectories `production/` and `staging/` must also exist and be writable
- PR preview directories (`mr-{number}/`) are created and deleted automatically by the CI — no pre-creation needed
```
/var/www/budget-app/ ← deploy_path (must exist, writable by deploy user)
production/ ← must exist
staging/ ← must exist
mr-42/ ← created by CI on PR open, deleted on PR close
mr-91/
...
```
---
## 2. DNS
Three types of hostnames are used:
| Hostname pattern | Environment | Example |
|---|---|---|
| `{domain}` | Production | `budget.kushalgaywala.com` |
| `staging.{domain}` | Staging | `staging.budget.kushalgaywala.com` |
| `{pr-number}.{domain}` | PR preview | `42.budget.kushalgaywala.com` |
**Requirements:**
- An A (or AAAA) record pointing `{domain}` to the server IP
- A wildcard A record `*.{domain}` pointing to the same server IP — this covers staging and all PR preview subdomains with a single record
---
## 3. TLS
**Requirements:**
- TLS certificates for `{domain}` and `*.{domain}` (wildcard)
- A wildcard certificate covers staging and all PR preview subdomains automatically, so no certificate management is needed per PR
- The wildcard certificate must be issued via a DNS-01 ACME challenge (HTTP-01 cannot validate wildcard domains)
---
## 4. Reverse Proxy
The reverse proxy sits in front of the deploy directories and routes incoming HTTPS requests to the correct static files.
### 4.1 Static file serving
Each environment maps a hostname to a directory of static HTML/JS/CSS files built by Vite.
| Hostname | Directory |
|---|---|
| `{domain}` | `{deploy_path}/production/` |
| `staging.{domain}` | `{deploy_path}/staging/` |
| `{pr-number}.{domain}` | `{deploy_path}/mr-{pr-number}/` |
### 4.2 SPA fallback routing
The app is a single-page application (React Router). Any request for a path that does not match a physical file must fall back to serving `index.html` from that environment's directory.
**Example:** A request for `/buckets/42` has no corresponding file on disk. The proxy must serve `index.html` instead of returning a 404, so React Router can handle the route client-side.
**Requirement:** For every environment, configure: "if the requested file does not exist, serve `index.html` from the same root directory."
### 4.3 Wildcard PR preview routing
The proxy must extract the PR number from the subdomain and map it to the correct directory dynamically — without needing a config change per PR.
**Requirement:** Match hostnames of the form `{number}.{domain}` and serve files from `{deploy_path}/mr-{number}/`, with the SPA fallback above applied.
**Note:** If a PR preview directory does not exist (e.g. the PR was closed and cleaned up), the proxy should return a 404 or a friendly error page rather than serving files from another environment.
### 4.4 HTTP → HTTPS redirect
**Requirement:** All plain HTTP requests must be redirected to HTTPS (301 or 308).
### 4.5 Headers (recommended)
| Header | Value | Reason |
|---|---|---|
| `X-Frame-Options` | `DENY` | Prevent clickjacking |
| `X-Content-Type-Options` | `nosniff` | Prevent MIME sniffing |
| `Referrer-Policy` | `strict-origin-when-cross-origin` | Limit referrer leakage |
| `Cache-Control` for `index.html` | `no-cache` | Ensure users get the latest app shell |
| `Cache-Control` for `assets/` | `public, max-age=31536000, immutable` | Vite hashes asset filenames, safe to cache forever |
---
## 5. Woodpecker CI
Woodpecker must be connected to the Gitea repo and have the following secrets configured under **Repository → Settings → Secrets**:
| Secret name | Description |
|---|---|
| `ssh_private_key` | Private SSH key for the deploy user |
| `deploy_host` | Server hostname or IP |
| `deploy_user` | SSH username on the server |
| `deploy_path` | Absolute base path, e.g. `/var/www/budget-app` |
| `prod_url` | Full production URL, e.g. `https://budget.kushalgaywala.com` |
| `prod_appwrite_endpoint` | Production Appwrite API endpoint |
| `prod_appwrite_project_id` | Production Appwrite project ID |
| `prod_appwrite_api_key` | Production Appwrite API key (`databases.write` + `collections.write` scopes) |
| `staging_url` | Full staging URL, e.g. `https://staging.budget.kushalgaywala.com` |
| `staging_appwrite_endpoint` | Staging Appwrite API endpoint |
| `staging_appwrite_project_id` | Staging Appwrite project ID |
| `staging_appwrite_api_key` | Staging Appwrite API key (same scopes) |
| `base_domain` | Base domain for PR previews, e.g. `budget.kushalgaywala.com` |
---
## 6. What CI Handles Automatically
Once the above is in place, no further manual server changes are needed for day-to-day use:
| Event | What happens automatically |
|---|---|
| Push to `main` | Web app built with production credentials and rsynced to `production/` |
| Push to `staging` | Web app built with staging credentials and rsynced to `staging/` |
| PR opened / updated | Web app built and deployed to `mr-{number}/` |
| PR closed | `mr-{number}/` directory removed from server |
| `apps/appwrite/**` changed on `main` | Appwrite schema deployed to production Appwrite project |
| `apps/appwrite/**` changed on `staging` | Appwrite schema deployed to staging Appwrite project |