From 0e132740363431594000e04999b8a7854f40d88e Mon Sep 17 00:00:00 2001 From: luxick Date: Wed, 10 Jun 2026 18:16:20 +0200 Subject: [PATCH] Add deployment scripts --- .gitignore | 1 + deploy/README.md | 46 +++++++++++++++++++++++++++++++++++ deploy/deploy.sh | 31 ++++++++++++++++++++++++ deploy/mediator.nginx.conf | 16 +++++++++++++ deploy/mediator.service | 30 +++++++++++++++++++++++ deploy/setup-server.sh | 49 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 173 insertions(+) create mode 100644 deploy/README.md create mode 100644 deploy/deploy.sh create mode 100644 deploy/mediator.nginx.conf create mode 100644 deploy/mediator.service create mode 100644 deploy/setup-server.sh diff --git a/.gitignore b/.gitignore index b883f1f..38c09a2 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ *.exe +dist/ diff --git a/deploy/README.md b/deploy/README.md new file mode 100644 index 0000000..65ade04 --- /dev/null +++ b/deploy/README.md @@ -0,0 +1,46 @@ +# Deploying mediator + +Target: Ubuntu 22.04 VPS, nginx in front, app in `/opt/mediator`. + +The app runs as an unprivileged `mediator` system user, listens only on +`127.0.0.1:8080`, and nginx proxies the public domain to it. Polls live in +`/opt/mediator/data/polls.json` — that one file is the whole backup. + +## One-time setup (on the server) + +Copy the deploy files over and run the setup script as root: + +```sh +scp deploy/mediator.service deploy/mediator.nginx.conf deploy/setup-server.sh himalia:/tmp/ +ssh himalia + cd /tmp + sudo ./setup-server.sh +``` + +The script creates the `mediator` user, installs the systemd unit and nginx +site, and adds a sudoers rule so your user can `systemctl restart mediator` +without a password (that keeps deploys to a single password prompt). + +Then get a certificate: + +```sh +sudo certbot --nginx -d +``` + +## Every deploy (from your machine) + +```sh +./deploy/deploy.sh # or: ./deploy/deploy.sh otherhost +``` + +Cross-compiles a static linux/amd64 binary, streams it to the server over +one ssh connection (one password prompt), swaps it in atomically, restarts +the service, and prints `active` on success. + +## Useful commands on the server + +```sh +systemctl status mediator +journalctl -u mediator -f +cp /opt/mediator/data/polls.json ~/polls-backup.json # backup +``` diff --git a/deploy/deploy.sh b/deploy/deploy.sh new file mode 100644 index 0000000..7c2c7b7 --- /dev/null +++ b/deploy/deploy.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +# Build mediator for linux/amd64 and push it to the server. +# Run from your machine (Git Bash on Windows works): +# +# ./deploy/deploy.sh [host] default host: himalia +# +# Requires the one-time server setup (setup-server.sh) to have been run. +# Asks for the ssh password once; the binary is streamed over that same +# connection, swapped in atomically, and the service restarted. +set -euo pipefail + +HOST="${1:-himalia}" +APP_DIR=/opt/mediator +cd "$(dirname "$0")/.." + +echo "Building linux/amd64 binary..." +mkdir -p dist +GOOS=linux GOARCH=amd64 CGO_ENABLED=0 \ + go build -trimpath -ldflags "-s -w" -o dist/mediator-linux-amd64 . + +echo "Uploading to $HOST and restarting mediator..." +ssh "$HOST" "set -e + cat > $APP_DIR/mediator.new + chmod 755 $APP_DIR/mediator.new + mv -f $APP_DIR/mediator.new $APP_DIR/mediator + sudo systemctl restart mediator + sleep 1 + systemctl is-active mediator +" < dist/mediator-linux-amd64 + +echo "Deployed $(git rev-parse --short HEAD) to $HOST." diff --git a/deploy/mediator.nginx.conf b/deploy/mediator.nginx.conf new file mode 100644 index 0000000..4d296d6 --- /dev/null +++ b/deploy/mediator.nginx.conf @@ -0,0 +1,16 @@ +# nginx site for mediator. Installed by setup-server.sh to +# /etc/nginx/sites-available/mediator with the real domain substituted. +# For HTTPS run `certbot --nginx -d ` afterwards; certbot rewrites +# this file to add the TLS server block and the HTTP->HTTPS redirect. +server { + listen 80; + listen [::]:80; + server_name mediator.example.com; + + location / { + proxy_pass http://127.0.0.1:8080; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +} diff --git a/deploy/mediator.service b/deploy/mediator.service new file mode 100644 index 0000000..c721fc0 --- /dev/null +++ b/deploy/mediator.service @@ -0,0 +1,30 @@ +[Unit] +Description=mediator - date polls for friend groups +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +User=mediator +Group=mediator +ExecStart=/opt/mediator/mediator -addr 127.0.0.1:8080 -data /opt/mediator/data +Restart=on-failure +RestartSec=2 + +# Sandboxing: the service only needs to read its binary and write its data dir. +NoNewPrivileges=true +ProtectSystem=strict +ReadWritePaths=/opt/mediator/data +ProtectHome=true +PrivateTmp=true +PrivateDevices=true +ProtectKernelTunables=true +ProtectControlGroups=true +RestrictSUIDSGID=true +LockPersonality=true +MemoryDenyWriteExecute=true +RestrictAddressFamilies=AF_INET AF_INET6 +CapabilityBoundingSet= + +[Install] +WantedBy=multi-user.target diff --git a/deploy/setup-server.sh b/deploy/setup-server.sh new file mode 100644 index 0000000..8083c22 --- /dev/null +++ b/deploy/setup-server.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +# One-time server setup for mediator on Ubuntu. Run as root ON THE SERVER, +# from a directory containing mediator.service and mediator.nginx.conf: +# +# sudo ./setup-server.sh +# +# the account you ssh in as; it gets write access to +# /opt/mediator and passwordless `systemctl restart mediator` +# the public hostname nginx should answer on +set -euo pipefail + +DEPLOY_USER="${1:?usage: setup-server.sh }" +DOMAIN="${2:?usage: setup-server.sh }" +APP_DIR=/opt/mediator +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" + +id -u "$DEPLOY_USER" >/dev/null + +# Unprivileged system user the service runs as. +id -u mediator >/dev/null 2>&1 || + useradd --system --home "$APP_DIR" --shell /usr/sbin/nologin mediator + +# Deploy user owns the app dir (to replace the binary); the service user +# owns only the data dir (the single thing it writes). +mkdir -p "$APP_DIR/data" +chown "$DEPLOY_USER" "$APP_DIR" +chmod 755 "$APP_DIR" +chown mediator:mediator "$APP_DIR/data" +chmod 750 "$APP_DIR/data" + +install -m 644 "$SCRIPT_DIR/mediator.service" /etc/systemd/system/mediator.service + +sed "s/mediator\.example\.com/$DOMAIN/" "$SCRIPT_DIR/mediator.nginx.conf" \ + > /etc/nginx/sites-available/mediator +ln -sf /etc/nginx/sites-available/mediator /etc/nginx/sites-enabled/mediator + +# Let the deploy user restart the service without a sudo password, +# so deploy.sh needs exactly one (ssh) password prompt. +printf '%s ALL=(root) NOPASSWD: /usr/bin/systemctl restart mediator\n' "$DEPLOY_USER" \ + > /etc/sudoers.d/mediator-deploy +chmod 440 /etc/sudoers.d/mediator-deploy + +systemctl daemon-reload +systemctl enable mediator +nginx -t +systemctl reload nginx + +echo "Setup done. Now push a binary from your machine: ./deploy/deploy.sh" +echo "For HTTPS: certbot --nginx -d $DOMAIN"