diff --git a/.forgejo/workflows/deploy.yml b/.forgejo/workflows/deploy.yml new file mode 100644 index 0000000..73bea3d --- /dev/null +++ b/.forgejo/workflows/deploy.yml @@ -0,0 +1,81 @@ +# Deploy Magpie to production via SSH (linuxuser@elektrine.com). +# Joins Docker network elektrine-magpie-shared so Elektrine app/worker/caddy +# resolve magpie:8090. Secrets: DEPLOY_SSH_KEY, DEPLOY_SSH_HOST_KEY +name: Deploy + +on: + push: + branches: [main] + workflow_dispatch: + +concurrency: + group: magpie-deploy + cancel-in-progress: false + +jobs: + deploy: + name: Deploy over SSH + runs-on: ubuntu-latest + needs: [] + # Run after CI if both fire on same push — use a local test job first + steps: + - uses: https://data.forgejo.org/actions/checkout@v4 + + - name: Go test + run: | + set -euo pipefail + case "$GITHUB_WORKSPACE" in + /data/*) HOST_WS="/opt/forgejo-runner/data${GITHUB_WORKSPACE#/data}" ;; + *) HOST_WS="$GITHUB_WORKSPACE" ;; + esac + docker run --rm --network host \ + -v "${HOST_WS}:/src" -w /src -e CGO_ENABLED=0 \ + golang:1.25-bookworm \ + sh -c "go test ./..." + + - name: Configure SSH + env: + DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }} + DEPLOY_SSH_HOST_KEY: ${{ secrets.DEPLOY_SSH_HOST_KEY || '66.42.127.87 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIM5Vx8UDcqvgmBwDs5SlCHd8/oGDoQytGHhbbp4LNK4o' }} + DEPLOY_HOST: ${{ vars.DEPLOY_HOST || 'elektrine.com' }} + DEPLOY_PORT: ${{ vars.DEPLOY_PORT || '22' }} + run: | + set -euo pipefail + install -d -m 0700 ~/.ssh + printf '%s\n' "$DEPLOY_SSH_KEY" | sed 's/\r$//' | sed 's/\\n/\n/g' > ~/.ssh/id_ed25519 + chmod 0600 ~/.ssh/id_ed25519 + : > ~/.ssh/known_hosts + printf '%s\n' "$DEPLOY_SSH_HOST_KEY" | sed 's/\r$//' | sed 's/\\n/\n/g' >> ~/.ssh/known_hosts + ssh-keyscan -p "$DEPLOY_PORT" -T 5 -t ed25519,rsa "$DEPLOY_HOST" 2>/dev/null >> ~/.ssh/known_hosts || true + chmod 0600 ~/.ssh/known_hosts + ssh -p "$DEPLOY_PORT" \ + -i "$HOME/.ssh/id_ed25519" \ + -o BatchMode=yes -o IdentitiesOnly=yes \ + -o StrictHostKeyChecking=yes \ + -o UserKnownHostsFile="$HOME/.ssh/known_hosts" \ + "${{ vars.DEPLOY_USER || 'linuxuser' }}@$DEPLOY_HOST" 'echo ssh_ok; hostname; id' + + - name: Rsync and deploy + env: + DEPLOY_HOST: ${{ vars.DEPLOY_HOST || 'elektrine.com' }} + DEPLOY_USER: ${{ vars.DEPLOY_USER || 'linuxuser' }} + DEPLOY_PORT: ${{ vars.DEPLOY_PORT || '22' }} + DEPLOY_PATH: ${{ vars.DEPLOY_PATH || '/opt/magpie' }} + run: | + set -euo pipefail + RSYNC_RSH="ssh -p ${DEPLOY_PORT} -i ${HOME}/.ssh/id_ed25519 -o BatchMode=yes -o IdentitiesOnly=yes -o StrictHostKeyChecking=yes -o UserKnownHostsFile=${HOME}/.ssh/known_hosts" + # Preserve production secrets and data mounts + rsync -az --delete \ + -e "$RSYNC_RSH" \ + --exclude '.git/' \ + --exclude '.env.production' \ + --exclude 'dist/' \ + --exclude 'magpie' \ + ./ "${DEPLOY_USER}@${DEPLOY_HOST}:${DEPLOY_PATH}/" + ssh -p "$DEPLOY_PORT" \ + -i "$HOME/.ssh/id_ed25519" \ + -o BatchMode=yes -o IdentitiesOnly=yes \ + -o StrictHostKeyChecking=yes \ + -o UserKnownHostsFile="$HOME/.ssh/known_hosts" \ + "${DEPLOY_USER}@${DEPLOY_HOST}" \ + "chmod +x ${DEPLOY_PATH}/scripts/deploy.sh && cd ${DEPLOY_PATH} && ./scripts/deploy.sh" diff --git a/README.md b/README.md index a62d79d..5427b90 100644 --- a/README.md +++ b/README.md @@ -269,3 +269,25 @@ make release ``` Artifacts are written to `dist/`, with checksums in `dist/SHA256SUMS`. + +## Production deploy (same host as Elektrine) + +Magpie runs as a separate Compose project on the Elektrine host and joins the +shared Docker network `elektrine-magpie-shared` so Elektrine services can reach +`http://magpie:8090`. + +```sh +# on the server (linuxuser) +cd /opt/magpie +./scripts/deploy.sh +``` + +Forgejo Actions (push to `main`) rsyncs to `linuxuser@elektrine.com:/opt/magpie` +and runs that script. Elektrine deploy re-attaches Magpie to the shared network +every time via `ensure_magpie_shared_network`. + +Required on the host: + +- `/opt/magpie/.env.production` (not in git) +- Docker (or passwordless `sudo docker`) for `linuxuser` +- Shared network name: `MAGPIE_DOCKER_NETWORK` (default `elektrine-magpie-shared`) diff --git a/scripts/deploy.sh b/scripts/deploy.sh new file mode 100755 index 0000000..f6d0255 --- /dev/null +++ b/scripts/deploy.sh @@ -0,0 +1,61 @@ +#!/bin/bash +# Deploy Magpie on the same host as Elektrine and join the shared Docker network +# so app/worker/caddy can reach http://magpie:8090. +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$ROOT_DIR" + +NETWORK_NAME="${MAGPIE_DOCKER_NETWORK:-elektrine-magpie-shared}" +CONTAINER_NAME="${MAGPIE_CONTAINER_NAME:-magpie}" +COMPOSE_FILES=(-f docker-compose.yml -f docker-compose.network.yml) + +if docker info >/dev/null 2>&1; then + DOCKER=(docker) +elif sudo -n docker info >/dev/null 2>&1; then + DOCKER=(sudo -n docker) +else + echo "No docker access (need docker group or passwordless sudo docker)" >&2 + exit 1 +fi + +if [[ ! -f .env.production ]]; then + echo "Missing $ROOT_DIR/.env.production" >&2 + exit 1 +fi + +export MAGPIE_DOCKER_NETWORK="$NETWORK_NAME" + +echo "Info: ensuring shared network $NETWORK_NAME" >&2 +"${DOCKER[@]}" network inspect "$NETWORK_NAME" >/dev/null 2>&1 \ + || "${DOCKER[@]}" network create "$NETWORK_NAME" >/dev/null + +echo "Info: building and starting Magpie" >&2 +"${DOCKER[@]}" compose "${COMPOSE_FILES[@]}" build +"${DOCKER[@]}" compose "${COMPOSE_FILES[@]}" up -d --force-recreate --remove-orphans + +# Compose should attach via docker-compose.network.yml; re-connect with alias if needed +# (force-recreate can drop a manually-connected endpoint). +attached="$("${DOCKER[@]}" inspect "$CONTAINER_NAME" --format \ + "{{if index .NetworkSettings.Networks \"$NETWORK_NAME\"}}yes{{end}}" 2>/dev/null || true)" +if [[ "$attached" != "yes" ]]; then + echo "Info: connecting $CONTAINER_NAME to $NETWORK_NAME as alias magpie" >&2 + "${DOCKER[@]}" network connect --alias magpie "$NETWORK_NAME" "$CONTAINER_NAME" +else + # Ensure the magpie DNS alias exists (network connect --alias is idempotent only if already set) + echo "Info: $CONTAINER_NAME already on $NETWORK_NAME" >&2 +fi + +echo "Info: Magpie status" >&2 +"${DOCKER[@]}" compose "${COMPOSE_FILES[@]}" ps +"${DOCKER[@]}" inspect "$CONTAINER_NAME" --format \ + 'networks={{range $k,$v := .NetworkSettings.Networks}}{{$k}}({{join $v.Aliases ","}}) {{end}}' + +# Quick health: S3 endpoint listens on loopback +if command -v curl >/dev/null 2>&1; then + if curl -sf -o /dev/null --max-time 3 "http://127.0.0.1:8090/" || true; then + echo "Info: Magpie is listening on 127.0.0.1:8090" >&2 + fi +fi + +echo "DEPLOY_OK"