Skip to content

Deploy mirror: f450e27c1b47efb4048c011333770bad69905769 #4

Deploy mirror: f450e27c1b47efb4048c011333770bad69905769

Deploy mirror: f450e27c1b47efb4048c011333770bad69905769 #4

Workflow file for this run

name: Deploy Mirror
run-name: "Deploy mirror: ${{ github.sha }}"
concurrency:
group: deploy-mirror
cancel-in-progress: true
on:
workflow_dispatch:
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
permissions:
contents: read
packages: write
jobs:
deploy:
name: Deploy Mirror
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 22
- name: Install and typecheck
run: |
npm ci
npx astro sync
npm run typecheck
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
target: production
push: true
provenance: false
sbom: false
cache-from: type=gha
cache-to: type=gha,mode=max
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:mirror-${{ github.sha }}
${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:mirror-latest
labels: ${{ steps.meta.outputs.labels }}
- name: Start SSH agent
uses: webfactory/ssh-agent@v0.9.0
with:
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
- name: Add known hosts
run: |
mkdir -p ~/.ssh
ssh-keyscan -H "${{ secrets.SSH_HOST_DEV }}" >> ~/.ssh/known_hosts
- name: Deploy over SSH
env:
SSH_USER: ${{ secrets.SSH_USER }}
SSH_HOST: ${{ secrets.SSH_HOST_DEV }}
REPO: ${{ github.repository }}
BRANCH: ${{ github.ref_name }}
GHCR_USER: ${{ secrets.GHCR_USER }}
GHCR_TOKEN: ${{ secrets.GHCR_TOKEN }}
IMAGE_TAG: mirror-${{ github.sha }}
run: |
ssh "${SSH_USER}@${SSH_HOST}" \
"env GHCR_USER='${GHCR_USER}' GHCR_TOKEN='${GHCR_TOKEN}' IMAGE_TAG='${IMAGE_TAG}' bash -s -- '${REPO}' '${BRANCH}'" <<'EOS'
set -euo pipefail
REPO="${1:?missing repo}"
BRANCH="${2:-main}"
: "${IMAGE_TAG:?missing IMAGE_TAG}"
: "${GHCR_USER:?missing GHCR_USER}"
: "${GHCR_TOKEN:?missing GHCR_TOKEN}"
STACK_NAME="underlay-mirror"
REPO_NAME="${REPO##*/}"
APP_DIR="/srv/${REPO_NAME}-mirror"
PROD_DIR="/srv/${REPO_NAME}"
REPO_SSH="git@github.com:${REPO}.git"
ssh-keyscan -H github.com >> ~/.ssh/known_hosts 2>/dev/null
chmod 600 ~/.ssh/known_hosts
if [[ ! -d "${APP_DIR}/.git" ]]; then
sudo mkdir -p "${APP_DIR}"
sudo chown -R "$USER:$USER" "${APP_DIR}"
git clone --branch "${BRANCH}" "${REPO_SSH}" "${APP_DIR}"
fi
cd "${APP_DIR}"
git fetch --prune --tags origin
git checkout "${BRANCH}"
git pull origin "${BRANCH}"
# Load secrets from prod's .env (already decrypted on the server)
# This gives us S3_*, UNDERLAY_UPSTREAM_API_KEY, etc.
if [[ -f "${PROD_DIR}/.env" ]]; then
set -a
source <(grep -v '^#' "${PROD_DIR}/.env" | grep -v '^$')
set +a
else
echo "ERROR: ${PROD_DIR}/.env not found — run a prod deploy first"
exit 1
fi
# Init swarm if not already active
if ! sudo docker info --format '{{.Swarm.LocalNodeState}}' | grep -qx active; then
sudo docker swarm init
fi
echo "$GHCR_TOKEN" | sudo docker login ghcr.io -u "$GHCR_USER" --password-stdin
sudo docker pull "ghcr.io/${REPO}:${IMAGE_TAG}"
# Deploy using docker-compose.mirror.yml — Postgres is self-contained.
# S3 creds and API key are sourced from prod .env above.
sudo env \
IMAGE="ghcr.io/${REPO}" IMAGE_TAG="$IMAGE_TAG" \
S3_ENDPOINT="${S3_ENDPOINT:-}" S3_REGION="${S3_REGION:-auto}" \
S3_ACCESS_KEY="${S3_ACCESS_KEY:-}" S3_SECRET_KEY="${S3_SECRET_KEY:-}" \
S3_BUCKET="${S3_BUCKET:-underlay}" \
UNDERLAY_UPSTREAM_API_KEY="${UNDERLAY_UPSTREAM_API_KEY:-}" \
SESSION_SECRET="$(openssl rand -hex 32)" \
docker stack deploy -c docker-compose.mirror.yml \
--with-registry-auth --resolve-image always --prune "${STACK_NAME}"
sudo docker stack services "${STACK_NAME}"
echo "Mirror deployed as ${STACK_NAME} (image: ${IMAGE_TAG})"
EOS