Deployment and Operations
Deployment-and-Operations.mdDeployment and Operations
Deployment Flow
deployment is GitHub Actions driven.
current chain:
- push to
main code lintworkflow runs- if lint passes,
deploy to fridg3.orgruns from the successful workflow event - repo is rsynced to
/var/www/fridg3.org - the Toast Discord bot is gracefully restarted from the deployed copy
Deploy Workflow
/.github/workflows/deploy.yml
main details:
- triggered by successful
code lintworkflow completion - only deploys pushes to
main - installs
rsyncandopenssh-client - uses
DEPLOY_KEY - deploy target is
deploy@45.76.134.105:/var/www/fridg3.org - after rsync, ssh stops any
toastGNU screen session owned bydeploy, stops anytoastsession owned byhttp, preparesothers/toast-discord-bot/bot/toast-bot.logforhttp, then runs/var/www/fridg3.org/others/toast-discord-bot/bot/start.shashttp - the restart step needs passwordless sudo for
deployto run the Toast bot ashttp; Toast writes DM history and feed notification state under/data, which is owned byhttp:http - because the
httpuser home is not a normal login home, the workflow setsSCREENDIR=/tmp/toast-screen-httpforhttp-owned screen commands and creates that socket directory ashttpwith mode700before start
What Does Not Deploy
deployment uses .rsyncignore, so these are excluded:
/data/**sitemap.xml- repo docs and local config files
.github/**/scripts/**- local editor/codex folders
/others/toast-discord-bot/bot/venv/**
that means production runtime data is expected to already exist on the server.
Server Permissions
from README.md:
- project files should belong to
deploy:http - directories should be
755 - files should be
644 /dataandsitemap.xmlneedhttp:httpownership for webserver writes- Toast runs as
httpin production so it can update/data/etc/toast-dm-history.jsonand/data/etc/toast-feed-notify-state.json; onlytoast-bot.login the bot code directory is made writable for that runtime user
the deploy user needs passwordless sudo for the Toast restart step:
deploy ALL=(http) NOPASSWD: ALL
install that with visudo, preferably as a small file under /etc/sudoers.d/, because typoing sudoers directly is how servers become decorative bricks. the workflow prepares toast-bot.log as deploy with group http and mode 664, so no root sudo is needed for log setup.
Nginx Config Source
the repo-tracked files in .nginx/ are the source for the production nginx config.
.nginx/nginx.confcorresponds to/etc/nginx/nginx.conf.nginx/fridg3.orgcorresponds to/etc/nginx/sites-enabled/fridg3.org- production uses these through symlinks, so edits here are real server config edits, not examples
when adding routes, APIs, uploads, redirects, or private data folders, check .nginx/fridg3.org as part of the feature. a correct PHP route can still fail if nginx redirects POSTs, misses a clean-url rewrite, or accidentally exposes/blocklists the wrong /data path.
Nginx Clean URLs
production nginx needs explicit rewrites for PHP routes that accept path-style ids. without these, nginx falls through to the root /index.php fallback before the route can parse the URL.
POST-only API directory routes also need POST-safe rewrites when called without index.php; otherwise nginx can normalize the directory URL with a redirect and the browser may retry as GET. /api/toast-feed-generate is included in that rewrite list.
the contact route is configured POST-safe at /contact, old /email paths redirect to /contact, and /data/contact/ is blocked from direct web access.
the upload API posts to /tools/upload/?api=*; keep the exact /tools/upload nginx rewrite so stale no-slash requests hit PHP directly instead of losing their POST body to a trailing-slash redirect. cursed but real.
mdpaste share links use /tools/mdpaste/s/{id} and need this block before the generic location / fallback. keep the regexes quoted, because nginx treats unquoted {16} like cursed config syntax.
# mdpaste clean URLs
location ~ "^/tools/mdpaste/s/[a-fA-F0-9]{16}/?$" {
rewrite "^/tools/mdpaste/s/([a-fA-F0-9]{16})/?$" /tools/mdpaste/s/index.php?id=$1 last;
}
location /tools/mdpaste/s/ { try_files $uri $uri/ /tools/mdpaste/s/index.php?$args; }
location /tools/mdpaste/ { try_files $uri $uri/ /tools/mdpaste/index.php?$args; }
Backup Workflow
/.github/workflows/backup-data.yml
what it does:
- ssh to the server
- remove stale temporary backup zips from
/home/deploy - zip
/var/www/fridg3.org/datainto a temporary archive under/home/deploy - download the archive to the runner
- upload it to Google Drive using
rclone - keep only the 10 newest backups
- delete temp archives from runner and server
triggers:
- manual
workflow_dispatch - scheduled daily cron at
0 0 *
required secrets:
DEPLOY_KEYGDRIVE_BACKUP_FOLDER_IDRCLONE_CONFIG
setup notes live in /.github/workflows/backup-data-setup.md.
Sitemap Generation
sitemap.xml is not deployed from git. it is generated by /api/sitemap, which means:
- the file must be writable by the server
- the server copy is the one that matters
Operational Truths
- this repo is source code, not a full backup
/datais operational state- if prod data disappears, git will not magically save you
- if file permissions are wrong, deploys and runtime writes will get weird fast