timothykim bb45d3ef2f fix git-crypt unlock detection
Check for .git/git-crypt/keys directory which only exists after
unlock, instead of parsing git-crypt status output which shows
"encrypted:" regardless of lock state.
2026-03-13 12:25:18 -04:00
2026-03-12 21:16:59 -04:00
2026-03-12 21:16:59 -04:00
2026-03-12 21:16:59 -04:00
2026-03-12 21:16:59 -04:00
2026-03-13 12:25:18 -04:00

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, 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
scripts/
  deploy-dispatch.sh # SSH command dispatcher (routes to per-app deploy scripts)
  deploy-nginx.sh    # Deploy script for nginx
  deploy-timothykim.sh  # Deploy script for timothykim.net
  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

Initial server setup

On a fresh Rocky Linux 9 (or compatible) install:

dnf install -y git
git clone https://git.timothykim.net/timothykim/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.

To save the git-crypt key to Bitwarden (one-time, from your dev machine):

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 three components:

  1. Deploy script (scripts/deploy-<app>.sh) -- pulls the latest repo changes, then runs docker compose pull && up -d for that app.
  2. 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.
  3. SSH dispatcher (scripts/deploy-dispatch.sh) -- the deploy user's authorized_keys is locked to this script via a command= directive. It reads SSH_ORIGINAL_COMMAND to route to the correct per-app deploy script.

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.

Adding a new app

Run the scaffolding script:

./scripts/new-app.sh <app-name>

This creates:

  • docker/<app-name>/compose.yml -- a starter compose file on the shared network
  • scripts/deploy-<app-name>.sh -- the deploy script
  • .gitea/workflows/deploy-<app-name>.yml -- the Gitea Actions workflow
  • A new case in scripts/deploy-dispatch.sh

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.

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 secrets must be configured in the Gitea repo settings for deploys to work:

  • DEPLOY_HOST -- the server's IP or hostname
  • DEPLOY_KEY -- the SSH private key for the deploy user
S
Description
No description provided
Readme 500 KiB
Languages
Shell 65.2%
Python 33.5%
Assembly 0.5%
Dockerfile 0.4%
HTML 0.4%