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