6.2 KiB
Use Cases
0. Initial Bitwarden Secrets Manager setup
One-time setup before anything else works. Create the secrets project and machine accounts in Bitwarden Secrets Manager.
- Create a Bitwarden organization (free tier)
- Enable Secrets Manager for the organization
- Create a project named
hantim - Create all required secrets (see README.md Secrets table for the full list)
- Create machine accounts:
hantim-server— grant access to thehantimprojecthantim-ci— reserved for future CI use
- Generate access tokens for each machine account
1. Provision a new server
Set up a fresh Rocky Linux 9 server from scratch.
dnf install -y git
git clone https://git.timothykim.net/hantim/hantim-server.git /opt/hantim
bash /opt/hantim/scripts/bootstrap.sh
What happens:
- Installs all dependencies (jq, certbot, unzip, Docker)
- Installs
bwsCLI, prompts for Bitwarden Secrets Manager access token - Fetches registry token and deploy SSH public key via
bws - Logs into Docker registry
- Creates deploy user with restricted SSH + sudo
- Runs
configure.sh:- Opens firewall ports (HTTP, HTTPS, Garage RPC)
- Sets up certbot renewal
- Deploys all services via
deploy.sh(generates.envfiles, issues certs, starts containers)
Prerequisites:
- DNS for all configured domains must point to the server
- Bitwarden Secrets Manager access token for the
hantim-servermachine account - Docker images must exist in the Gitea registry (or apps will start on next push)
2. Restore from backup
Identical to provisioning a new server. Run the same three commands on a fresh server.
bootstrap.shandconfigure.share idempotent -- safe to re-run- SSL certs are re-issued automatically (Let's Encrypt)
- Docker images are pulled from the Gitea registry
- No data migration needed for stateless static sites
3. Add a new static site
From your dev machine (single command):
./tools/new-app.sh example.com
Prerequisites:
- Bitwarden Secrets Manager must contain
hantim-new-app-script,hantim-vultr-api-key, andhantim-deploy-ssh-private-key - Domain nameservers must be pointed to Vultr (configured on directnic.com)
- Dependencies:
bws,jq,dig BWS_ACCESS_TOKENenv var or token saved at~/.config/hantim/bws-token
What happens (fully automated):
- Resolves server IP from
hantim.net - Fetches Gitea API token, Vultr API key, and deploy SSH key from Bitwarden Secrets Manager
- Creates DNS zone on Vultr (if needed) and A records for bare + www
- Waits for DNS to propagate
- Creates Gitea repo
hantim/example.comfromstatic-site-template - Creates local files:
docker/example.com/compose.yml(container name:example_com).gitea/workflows/deploy-example.com.ymldocker/nginx/conf.d/example.com.conf(HTTP->HTTPS, bare->www, proxy)
- SSHes to server as deploy user, issues SSL cert via webroot (zero downtime)
- Creates Garage media bucket via admin API (create, allow media-key, enable website)
- Commits and pushes hantim-server (triggers deploy)
- Triggers initial build of the app repo via Gitea API
- Polls
https://www.example.comuntil it responds (up to 3 minutes)
After the site is live, clone the app repo and customize:
git clone git@git.timothykim.net:hantim/example.com.git
cd example.com
# edit static/index.html, etc.
git add . && git commit -m "initial content" && git push
4. Update app code
Push a change to the app repo (e.g., example.com):
cd example.com
# make changes
git add . && git commit -m "update content" && git push
What happens:
- Gitea Actions runs
build.yml: builds Docker image, pushes to registry - SSHes into server, runs
deploy-example.com deploy.shpulls latest hantim-server, pulls new image, restarts container
No manual steps needed.
5. Update Docker Compose config
Edit a compose file in this repo and push:
vim docker/example.com/compose.yml
git add docker/example.com/compose.yml
git commit -m "update example.com config"
git push
What happens:
deploy-example.com.ymlworkflow triggers (path match)deploy.shpulls latest code, pulls image, runsdocker compose up -d
6. Update nginx config
Edit an nginx config in this repo and push:
vim docker/nginx/conf.d/example.com.conf
git add docker/nginx/conf.d/example.com.conf
git commit -m "update nginx config"
git push
What happens:
deploy-nginx.ymlworkflow triggers (path match)deploy.shpulls latest code- If any conf files reference certs that don't exist yet, nginx is briefly stopped and certs are issued via standalone certbot (auto-restarts on failure)
- Tests nginx config with
nginx -t-- if it fails, deploy aborts and the running nginx is untouched - Runs
docker compose up -dand reloads nginx
Note: Each conf file must contain server blocks for only one domain. The
cert name is derived from the filename (e.g. example.com.conf -> cert
example.com). Redirect-only domains get their own conf file.
7. Re-run configure on existing server
Safe to do at any time, manually or via CI:
cd /opt/hantim
git pull
bash scripts/configure.sh
Or push a change to scripts/configure.sh — the provision.yml workflow
triggers automatically.
- All steps are idempotent
- Existing certs are skipped
- Brief nginx downtime (seconds) while certbot checks run
- Services are restarted
8. Upload media files
Media files (images, videos) are stored in Garage and served at /media/
on each domain.
# One-time setup
aws configure # key ID + secret from bws, region: garage, output: json
# Then add to ~/.aws/config under [default]:
# endpoint_url = https://s3.hantim.net
# Upload a single file
aws s3 cp photo.jpg s3://example.com/photo.jpg
# Sync a directory
aws s3 sync static/media/ s3://example.com/
What happens:
- File is uploaded to the Garage S3 API via
s3.hantim.net - Garage replicates the file to both nodes (hantim + argento)
- File is accessible at
https://www.example.com/media/photo.jpg
Local development: Store media in static/media/ (gitignored). The app's
nginx serves these locally, matching the /media/ path used in production.
Reference in HTML: Use absolute paths like /media/photo.jpg.