037b78734f
Provision server / provision (push) Successful in 7s
- Move new-app.sh, new-service.sh, remove-app.sh from scripts/ to tools/ - Migrate new-app.sh and remove-app.sh from bw to bws - Replace real domains with example.com in documentation and help text
154 lines
5.6 KiB
Markdown
154 lines
5.6 KiB
Markdown
# Architecture
|
|
|
|
## Servers
|
|
|
|
### Hantim server (hantim)
|
|
|
|
The production application server. Runs Rocky Linux 9 on Vultr.
|
|
|
|
- Hosts all app containers and the nginx reverse proxy via Docker Compose
|
|
- Provisioned by `bootstrap.sh` + `configure.sh` in this repo
|
|
- This repo is cloned to `/opt/hantim`
|
|
- The `deploy` user receives SSH commands from CI to trigger deploys
|
|
- Certbot runs on the host (not in Docker) and manages Let's Encrypt certs
|
|
|
|
### Gitea server (git.timothykim.net)
|
|
|
|
A separate server running the Gitea instance. Provides:
|
|
|
|
- Git hosting for all repos
|
|
- Docker container registry (`git.timothykim.net/hantim/<app>`)
|
|
- Gitea Actions CI/CD (runner registered at site level)
|
|
- Organization-level secrets and variables shared across repos
|
|
|
|
### Bitwarden Secrets Manager (cloud)
|
|
|
|
Secrets are stored in Bitwarden Secrets Manager (free tier, project: `hantim`).
|
|
The `bws` CLI fetches secrets using machine account access tokens.
|
|
|
|
- `hantim-ci-registry-push` -- Gitea API token for registry access
|
|
- `hantim-deploy-ssh-private-key` -- deploy user SSH private key
|
|
- `hantim-deploy-ssh-public-key` -- deploy user SSH public key
|
|
- `hantim-new-app-script` -- Gitea API token for creating repos
|
|
- `hantim-vultr-api-key` -- Vultr API key for DNS management
|
|
|
|
Machine accounts:
|
|
- `hantim-server` -- used by `bootstrap.sh` and `configure.sh` on the server
|
|
(access token at `/etc/bws-token`)
|
|
- `hantim-ci` -- reserved for future CI use
|
|
|
|
### Gitea secrets
|
|
|
|
Workflow secrets stored in the `hantim` Gitea org. These duplicate some
|
|
Bitwarden secrets for direct use in CI workflows without installing `bws`:
|
|
|
|
- `DEPLOY_SSH_KEY` -- deploy SSH private key
|
|
- `CI_REGISTRY_TOKEN` -- Gitea registry token
|
|
- `DEPLOY_HOST` -- server IP (variable, not secret)
|
|
|
|
## Repos
|
|
|
|
All repos live under the `hantim` organization on Gitea.
|
|
|
|
### hantim-server
|
|
|
|
This repo. Contains server provisioning, Docker Compose configs, nginx
|
|
configs, deploy scripts, and Gitea Actions workflows.
|
|
|
|
```
|
|
scripts/bootstrap.sh # First-time server setup (manual, uses Bitwarden)
|
|
scripts/configure.sh # Idempotent server config (CI-safe)
|
|
scripts/deploy.sh # Receives deploy + cert + provision commands via SSH
|
|
tools/new-app.sh # Scaffolds + deploys a new static site (run from dev machine)
|
|
tools/new-service.sh # Scaffolds a new Docker Compose service (run from dev machine)
|
|
tools/remove-app.sh # Removes a static site (run from dev machine)
|
|
docker/nginx/ # nginx reverse proxy
|
|
compose.yml # nginx:alpine container
|
|
nginx.conf # Main nginx config
|
|
conf.d/<domain>.conf # Per-app server blocks
|
|
docker/<domain>/compose.yml # Per-app compose files
|
|
docker/garage/ # Garage S3-compatible object storage
|
|
compose.yml
|
|
garage.toml.template
|
|
.gitea/workflows/deploy-<app>.yml # Per-app deploy workflows
|
|
.gitea/workflows/provision.yml # Triggers configure.sh on setup changes
|
|
```
|
|
|
|
### static-site-template
|
|
|
|
A Gitea template repo used by `new-app.sh` to scaffold new static sites.
|
|
Contains a Dockerfile, nginx config, placeholder HTML, and a `build.yml`
|
|
workflow that builds/pushes a Docker image and triggers a deploy.
|
|
|
|
Uses Gitea template variables (`${REPO_NAME}`) so the image tag and deploy
|
|
command are automatically set to the repo name.
|
|
|
|
### App repos (e.g., example.com)
|
|
|
|
Each app is its own repo created from `static-site-template`. Contains the
|
|
app source code and a `build.yml` workflow.
|
|
|
|
## Network topology
|
|
|
|
```
|
|
Internet
|
|
|
|
|
[Hantim Server]
|
|
|
|
|
nginx (port 80/443)
|
|
| |
|
|
example_com example_org ...
|
|
(shared Docker network)
|
|
```
|
|
|
|
- nginx is the only container with host port bindings (80, 443)
|
|
- All app containers are on the `shared` Docker network
|
|
- nginx proxies `https://www.<domain>` to the app's container by name
|
|
- The `shared` network is created by the nginx compose file and referenced
|
|
as `external: true` by app compose files
|
|
|
|
## Deploy flow
|
|
|
|
### App code change (push to app repo)
|
|
|
|
```
|
|
Developer pushes to app repo (e.g., example.com)
|
|
-> build.yml workflow runs on Gitea runner
|
|
-> docker build + push to git.timothykim.net registry
|
|
-> SSH to deploy@hantim as deploy-<domain>
|
|
-> deploy.sh: git pull, docker compose pull, docker compose up -d
|
|
-> (if image not yet in registry, skips gracefully)
|
|
```
|
|
|
|
### Infrastructure change (push to hantim-server)
|
|
|
|
```
|
|
Developer pushes to hantim-server
|
|
-> deploy-<app>.yml triggers (based on changed paths under docker/<app>/)
|
|
-> SSH to deploy@hantim as deploy-<app>
|
|
-> deploy.sh: git pull, docker compose pull, docker compose up -d
|
|
-> (nginx only): auto-issues certs for new domains, then tests config with nginx -t
|
|
```
|
|
|
|
### Setup change (push to hantim-server)
|
|
|
|
```
|
|
Developer pushes change to scripts/configure.sh
|
|
-> provision.yml triggers
|
|
-> SSH to deploy@hantim as provision
|
|
-> deploy.sh: git pull, configure.sh (firewall, certbot, start services)
|
|
```
|
|
|
|
## Conventions
|
|
|
|
- **App name = domain** (e.g., `example.com`)
|
|
- **Container name** = domain with dots replaced by underscores (e.g.,
|
|
`example_com`)
|
|
- **Cert name** = config filename without `.conf` (stored at `/etc/letsencrypt/live/<name>/`)
|
|
- **One domain per conf file** -- `deploy.sh` derives the cert name from the conf
|
|
filename and collects all `server_name` values for that cert. Putting unrelated
|
|
domains in the same conf file will bundle them into one cert incorrectly.
|
|
- **Image name** = `git.timothykim.net/hantim/<domain>:latest`
|
|
- All HTTP traffic redirects to `https://www.<domain>`
|
|
- Bare domain HTTPS redirects to `https://www.<domain>`
|