thread-master/deploy/prod/README.md

111 lines
3.3 KiB
Markdown

# Harbor Desk Production Deployment
This deployment targets Ubuntu amd64 at `daniel@10.0.0.33` and serves
`threads-tool-dev.30cm.net`.
## Layout
- Host Nginx serves immutable static web releases.
- Gateway uses blue/green systemd slots on `127.0.0.1:8888` and `:8889`.
- Worker uses matching systemd slots and guarded Mongo/Redis leases.
- Docker Compose runs MongoDB, Redis, MinIO, Prometheus, Grafana, Loki,
Alertmanager, Alloy, node-exporter, cAdvisor, and blackbox-exporter.
- Runtime secrets stay in `/etc/harbor/harbor.env` with mode `0600`.
- Releases stay under `/opt/harbor/releases`; `/opt/harbor/current` changes
atomically.
## First Install
The dedicated SSH key is expected at `~/.ssh/harbor_deploy`.
```bash
./deploy/prod/bootstrap-server.sh
./deploy/prod/release.sh
```
Bootstrap asks for remote sudo once. It then grants `daniel` passwordless sudo
only for the validated activate, rollback, and status scripts.
Edit external provider credentials after bootstrap:
```bash
ssh -i ~/.ssh/harbor_deploy daniel@10.0.0.33
sudoedit /etc/harbor/harbor.env
```
Restart the active services after changing runtime env values.
## Normal Release
```bash
./deploy/prod/release.sh
```
The command runs backend tests/vet, frontend tests, builds Linux amd64 binaries
and static assets, uploads a checksummed archive, migrates forward, starts the
inactive gateway, checks health, reloads Nginx, then hands work to the new
worker. The old gateway remains available for Nginx connection draining.
If an earlier inactive gateway still has established connections, activation
refuses to replace it. Retry after those requests finish.
To deploy an already built artifact:
```bash
./deploy/prod/release.sh deploy/prod/artifacts/harbor-*.tar.gz
```
## Rollback
```bash
./deploy/prod/rollback.sh
```
Rollback switches application code only. Never automatically run a down
migration; production migrations must remain backward compatible.
## TLS
The public hostname is Cloudflare-proxied while the origin has a private IP.
Use a restricted Cloudflare DNS API token to obtain a DNS-01 certificate:
```bash
sudo install -m 0600 /dev/null /etc/letsencrypt/cloudflare.ini
sudoedit /etc/letsencrypt/cloudflare.ini
# dns_cloudflare_api_token = REPLACE_WITH_RESTRICTED_TOKEN
sudo certbot certonly --dns-cloudflare \
--dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini \
-d threads-tool-dev.30cm.net
sudo /opt/harbor/deploy/remote/enable-tls.sh
```
Ensure Cloudflare Tunnel, router forwarding, or split DNS sends origin traffic
to `10.0.0.33`. A public Cloudflare DNS record alone cannot reach a private IP.
## Monitoring
All monitoring ports bind to localhost. Open Grafana through an SSH tunnel:
```bash
ssh -i ~/.ssh/harbor_deploy -L 3000:127.0.0.1:3000 daniel@10.0.0.33
```
Then browse `http://127.0.0.1:3000`. The generated Grafana password is in
`/etc/harbor/harbor.env` and should be read with sudo.
Useful commands:
```bash
ssh -i ~/.ssh/harbor_deploy daniel@10.0.0.33 \
sudo /opt/harbor/deploy/remote/status.sh
ssh -i ~/.ssh/harbor_deploy daniel@10.0.0.33 \
journalctl -u 'harbor-gateway@*' -f
```
## Backups
`harbor-backup.timer` creates daily Mongo and MinIO backups under
`/var/backups/harbor` and retains 14 days. This protects against accidental
deletion but not loss of the machine or disk. Add an off-host copy before using
the service for irreplaceable production data.