hantim-server
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, nginx configs, and Gitea Actions workflows for each app.
Repo structure
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 # Server provisioning script (idempotent)
.gitea/workflows/ # Per-app deploy workflows triggered by path changes
Provisioning a new server
On a fresh Rocky Linux 9 install:
dnf install -y git
git clone https://git.timothykim.net/hantim/hantim-server.git /opt/hantim
bash /opt/hantim/setup.sh
setup.sh is fully automated and idempotent. It:
- Installs system dependencies (git-crypt, jq, certbot, Node.js 20, Docker)
- Installs and authenticates the Bitwarden CLI
- Unlocks git-crypt using a key stored in Bitwarden
- Fetches the Docker registry token and deploy SSH public key from Bitwarden
- Logs into the Gitea Docker registry
- Creates the
deployuser with restricted SSH access and sudo - Issues SSL certificates via certbot for all configured domains
- Starts all services (nginx first, then all apps)
Prerequisites: DNS for all configured domains must point to the server before running setup.
Adding a new app
./scripts/new-app.sh <domain>
Example: ./scripts/new-app.sh hcsuzuki.net
This creates:
- A Gitea repo (
hantim/<domain>) from thestatic-site-template docker/<domain>/compose.yml-- app compose file.gitea/workflows/deploy-<domain>.yml-- deploy workflowdocker/nginx/conf.d/<domain>.conf-- nginx proxy config (HTTP->HTTPS, bare domain->www)
After running the script:
- Issue the SSL cert on the server (before pushing):
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 - Commit and push this repo to deploy
- 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.ymlbuilds 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.shpulls the latest code and runsdocker 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
The following are stored in Bitwarden and fetched automatically by setup.sh:
| 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 |
The following are configured in the hantim Gitea org settings:
| 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 |