docs: add server setup requirements for reverse proxy and CI/CD
This commit is contained in:
137
docs/server-setup.md
Normal file
137
docs/server-setup.md
Normal 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 |
|
||||||
Reference in New Issue
Block a user