replace NPM with vanilla nginx + certbot
- nginx:alpine replaces nginx-proxy-manager - certbot on host with standalone issuance and auto-renewal - per-site nginx configs in conf.d/ (HTTP->HTTPS, bare->www) - container names use underscores (timothykim_net) - setup.sh: automated cert issuance, dynamic app startup - deploy.sh: nginx -t guard, graceful image-not-found - new-app.sh: Gitea API repo creation, nginx conf generation - add ARCHITECTURE.md, USECASES.md, CLAUDE.md - remove old NPM data/certs from tracking
This commit is contained in:
@@ -2,24 +2,28 @@
|
||||
|
||||
Server provisioning and app management for the hantim webserver. This repo
|
||||
lives at `/opt/hantim` on the server and contains Docker Compose configs, deploy
|
||||
scripts, and Gitea Actions workflows for each app.
|
||||
scripts, nginx configs, and Gitea Actions workflows for each app.
|
||||
|
||||
## Repo structure
|
||||
|
||||
```
|
||||
docker/ # One directory per app, each with a compose.yml
|
||||
nginx/ # Nginx Proxy Manager (reverse proxy + TLS)
|
||||
timothykim.net/ # timothykim.net static site
|
||||
docker/
|
||||
nginx/ # Reverse proxy (nginx:alpine) + SSL config
|
||||
nginx.conf # Main nginx config
|
||||
conf.d/ # Per-app server blocks (e.g. timothykim.net.conf)
|
||||
compose.yml
|
||||
timothykim.net/ # timothykim.net static site
|
||||
compose.yml
|
||||
scripts/
|
||||
deploy.sh # Deploy script (validates command, git pull, docker compose up)
|
||||
new-app.sh # Scaffolding script to add a new app
|
||||
setup.sh # One-time server provisioning script
|
||||
.gitea/workflows/ # Per-app deploy workflows triggered by path changes
|
||||
deploy.sh # Deploy script (validates command, git pull, docker compose up)
|
||||
new-app.sh # Scaffolding script to add a new app
|
||||
setup.sh # Server provisioning script (idempotent)
|
||||
.gitea/workflows/ # Per-app deploy workflows triggered by path changes
|
||||
```
|
||||
|
||||
## Initial server setup
|
||||
## Provisioning a new server
|
||||
|
||||
On a fresh Rocky Linux 9 (or compatible) install:
|
||||
On a fresh Rocky Linux 9 install:
|
||||
|
||||
```bash
|
||||
dnf install -y git
|
||||
@@ -27,69 +31,87 @@ git clone https://git.timothykim.net/hantim/hantim-server.git /opt/hantim
|
||||
bash /opt/hantim/setup.sh
|
||||
```
|
||||
|
||||
`setup.sh` handles everything else: installs git-crypt and the Bitwarden CLI,
|
||||
fetches the git-crypt key from your Bitwarden vault (stored as a base64-encoded
|
||||
secure note named `hantim-git-crypt-key`), unlocks the repo, installs Docker,
|
||||
creates the `deploy` user, and starts all services.
|
||||
`setup.sh` is fully automated and idempotent. It:
|
||||
|
||||
To save the git-crypt key to Bitwarden (one-time, from your dev machine):
|
||||
1. Installs system dependencies (git-crypt, jq, certbot, Node.js 20, Docker)
|
||||
2. Installs and authenticates the Bitwarden CLI
|
||||
3. Unlocks git-crypt using a key stored in Bitwarden
|
||||
4. Fetches the Docker registry token and deploy SSH public key from Bitwarden
|
||||
5. Logs into the Gitea Docker registry
|
||||
6. Creates the `deploy` user with restricted SSH access and sudo
|
||||
7. Issues SSL certificates via certbot for all configured domains
|
||||
8. Starts all services (nginx first, then all apps)
|
||||
|
||||
```bash
|
||||
base64 /path/to/git-crypt-key
|
||||
# Save the output as a Bitwarden secure note named "hantim-git-crypt-key"
|
||||
```
|
||||
|
||||
## How deploys work
|
||||
|
||||
Each app has two components:
|
||||
|
||||
1. **Gitea Actions workflow** (`.gitea/workflows/deploy-<app>.yml`) -- triggers
|
||||
on pushes to `main` when files under `docker/<app>/` change. SSHes into the
|
||||
server as the `deploy` user to trigger the deploy.
|
||||
2. **Deploy script** (`scripts/deploy.sh`) -- the `deploy` user's
|
||||
`authorized_keys` is locked to this script via a `command=` directive. It
|
||||
validates the command, pulls the latest repo changes, and runs
|
||||
`docker compose pull && up -d` for that app.
|
||||
|
||||
This means:
|
||||
|
||||
- Pushing a change to `docker/nginx/compose.yml` only deploys nginx.
|
||||
- Pushing a change to `scripts/` or `setup.sh` does not trigger any deploy.
|
||||
- Each app deploys independently.
|
||||
**Prerequisites**: DNS for all configured domains must point to the server
|
||||
before running setup.
|
||||
|
||||
## Adding a new app
|
||||
|
||||
Run the scaffolding script:
|
||||
|
||||
```bash
|
||||
./scripts/new-app.sh <app-name>
|
||||
./scripts/new-app.sh <domain>
|
||||
```
|
||||
|
||||
Example: `./scripts/new-app.sh hcsuzuki.net`
|
||||
|
||||
This creates:
|
||||
|
||||
- `docker/<app-name>/compose.yml` -- a starter compose file on the `shared`
|
||||
network
|
||||
- `.gitea/workflows/deploy-<app-name>.yml` -- the Gitea Actions workflow
|
||||
- A Gitea repo (`hantim/<domain>`) from the `static-site-template`
|
||||
- `docker/<domain>/compose.yml` -- app compose file
|
||||
- `.gitea/workflows/deploy-<domain>.yml` -- deploy workflow
|
||||
- `docker/nginx/conf.d/<domain>.conf` -- nginx proxy config (HTTP->HTTPS,
|
||||
bare domain->www)
|
||||
|
||||
After running the script:
|
||||
|
||||
1. Edit `docker/<app-name>/compose.yml` to fit your app (image, ports,
|
||||
volumes, environment variables, etc.).
|
||||
2. Commit and push to `main`.
|
||||
3. Configure the proxy host in Nginx Proxy Manager to route traffic to the new
|
||||
service.
|
||||
1. Issue the SSL cert on the server (**before** pushing):
|
||||
```bash
|
||||
docker stop nginx
|
||||
certbot certonly --standalone --non-interactive --agree-tos \
|
||||
--register-unsafely-without-email --cert-name <domain> \
|
||||
-d <domain> -d www.<domain>
|
||||
docker start nginx
|
||||
```
|
||||
2. Commit and push this repo to deploy
|
||||
3. Clone the new app repo, customize it, and push to build/deploy
|
||||
|
||||
## Deploying changes
|
||||
|
||||
**App code change** (push to an app repo like `timothykim.net`):
|
||||
- The app's `build.yml` builds the Docker image, pushes to the registry, and
|
||||
SSHes into the server to trigger a deploy
|
||||
|
||||
**Compose or nginx config change** (push to this repo):
|
||||
- Gitea Actions workflows trigger based on which `docker/<app>/` paths changed
|
||||
- `deploy.sh` pulls the latest code and runs `docker compose up -d`
|
||||
- For nginx deploys, the config is tested before applying to prevent downtime
|
||||
|
||||
## Restoring from backup
|
||||
|
||||
Run the same three commands as provisioning. `setup.sh` is idempotent:
|
||||
- Existing packages are skipped
|
||||
- Existing certs are skipped (or re-issued if the server is new)
|
||||
- All services are started
|
||||
|
||||
## Secrets
|
||||
|
||||
Nginx Proxy Manager certs and Let's Encrypt account keys are encrypted with
|
||||
git-crypt (see `.gitattributes`). You need the git-crypt key to unlock them.
|
||||
The following are stored in Bitwarden and fetched automatically by `setup.sh`:
|
||||
|
||||
The following must be configured in the `hantim` Gitea org settings and are
|
||||
shared across all repos:
|
||||
| Bitwarden item | Type | Purpose |
|
||||
|---|---|---|
|
||||
| `hantim-git-crypt-key` | Secure Note | git-crypt symmetric key (base64) |
|
||||
| `hantim-ci-registry-push` | Secure Note | Gitea token for Docker registry |
|
||||
| `hantim-server-deploy` | SSH Key | Deploy user's SSH key |
|
||||
|
||||
Variables (Settings > Actions > Variables):
|
||||
- `DEPLOY_HOST` -- the server's IP or hostname
|
||||
The following are configured in the `hantim` Gitea org settings:
|
||||
|
||||
Secrets (Settings > Actions > Secrets):
|
||||
- `DEPLOY_SSH_KEY` -- the SSH private key for the `deploy` user
|
||||
- `CI_REGISTRY_TOKEN` -- Gitea personal access token for Docker registry login
|
||||
| Name | Type | Purpose |
|
||||
|---|---|---|
|
||||
| `DEPLOY_HOST` | Variable | Server IP or hostname |
|
||||
| `DEPLOY_SSH_KEY` | Secret | SSH private key for the deploy user |
|
||||
| `CI_REGISTRY_TOKEN` | Secret | Gitea token for Docker registry login |
|
||||
|
||||
The following is stored in Bitwarden and used by `new-app.sh`:
|
||||
|
||||
| Bitwarden item | Type | Purpose |
|
||||
|---|---|---|
|
||||
| `hantim-new-app-script` | Secure Note | Gitea API token for creating repos |
|
||||
|
||||
Reference in New Issue
Block a user