Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Keep agentbbs's pinned dependencies current automatically. Dependabot opens
# (review-gated) PRs when a newer version ships — nothing auto-merges, so a bad
# bump can't silently reach production.
#
# Coverage:
# - github-actions : the action pins in .github/workflows/* (checkout, setup-go…)
# - gomod : Go module dependencies (go.mod / go.sum)
# - docker : image tags in the Mailu compose stack and the pod Containerfile
#
# NOT covered (Dependabot can't watch shell-string pins): FORGEJO_VERSION and
# ERGO_VERSION in setup.sh — bump those by hand, or switch to Renovate (which can
# watch them via a custom regex manager). The Mailu *runtime* patch level is kept
# current separately by .github/workflows/mailu-update.yml.
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
groups:
github-actions:
patterns: ["*"]

- package-ecosystem: gomod
directory: /
schedule:
interval: weekly
groups:
go-modules:
patterns: ["*"]

# Mailu compose stack — bumps ghcr.io/mailu/* image tags.
- package-ecosystem: docker
directory: /deploy/mailu
schedule:
interval: weekly

# Pod base image (docker.io/library/ubuntu).
- package-ecosystem: docker
directory: /pods
schedule:
interval: weekly
137 changes: 137 additions & 0 deletions .github/workflows/mailu-update.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
name: mailu-update

# Keep the self-hosted Mailu stack on the latest patch of its pinned series.
#
# The Mailu compose stack (deploy/mailu) pins the FLOATING series tags
# (ghcr.io/mailu/*:2024.06). Upstream ships frequent patch releases within that
# series (2024.06.NN) with security fixes, but a running host only picks them up
# when someone runs `docker compose pull`. Left alone, the box drifts behind.
#
# This workflow SSHes to the droplet on a weekly schedule (and on demand), backs
# up the irreplaceable state (DKIM keys + admin DB), pulls the newest images for
# the pinned series, recreates the containers, and health-checks the result.
# It stays WITHIN the pinned series on purpose: crossing to a future series
# (e.g. a 2025.xx) can carry DB migrations and config changes, so that is a
# deliberate PR that edits the tags in deploy/mailu/*.yml — not an auto-pull.
#
# The agentbbs Go binary already redeploys on every push (deploy.yml) and the
# rootless podman pods rebuild from upstream base images, so this workflow is the
# missing piece: it covers the one long-lived docker-compose stack on the box.
#
# Reuses the same repo secrets as deploy.yml:
# DEPLOY_SSH_KEY private key whose public half is in the droplet admin user's
# authorized_keys
# DEPLOY_HOST bbs.profullstack.com (or the droplet IP)
# DEPLOY_USER admin SSH user (default: root)
# DEPLOY_PORT admin SSH port (default: 2202)

on:
schedule:
# 06:30 UTC every Monday. Off-peak; adjust as you like.
- cron: '30 6 * * 1'
workflow_dispatch:
inputs:
prune:
description: 'Prune dangling images after the update'
type: boolean
default: true

# Share deploy.yml's concurrency group so an image pull can never race a code
# deploy on the same host — whichever starts first runs to completion, the other
# queues behind it.
concurrency:
group: deploy-production
cancel-in-progress: false

permissions:
contents: read

jobs:
update:
runs-on: ubuntu-latest
steps:
- name: Configure SSH
env:
DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
DEPLOY_PORT: ${{ secrets.DEPLOY_PORT || '2202' }}
run: |
test -n "$DEPLOY_SSH_KEY" || { echo "::error::DEPLOY_SSH_KEY secret is not set"; exit 1; }
test -n "$DEPLOY_HOST" || { echo "::error::DEPLOY_HOST secret is not set"; exit 1; }
install -d -m 700 ~/.ssh
printf '%s\n' "$DEPLOY_SSH_KEY" > ~/.ssh/id_deploy
chmod 600 ~/.ssh/id_deploy
ssh-keyscan -p "$DEPLOY_PORT" -H "$DEPLOY_HOST" >> ~/.ssh/known_hosts 2>/dev/null

- name: Pull latest Mailu images, recreate, health-check
env:
DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
DEPLOY_USER: ${{ secrets.DEPLOY_USER || 'root' }}
DEPLOY_PORT: ${{ secrets.DEPLOY_PORT || '2202' }}
PRUNE: ${{ github.event_name == 'schedule' && 'true' || inputs.prune }}
run: |
ssh -i ~/.ssh/id_deploy -p "$DEPLOY_PORT" \
-o BatchMode=yes -o StrictHostKeyChecking=yes \
"${DEPLOY_USER}@${DEPLOY_HOST}" \
"sudo -n env PRUNE=$(printf %q "$PRUNE") bash -s" <<'REMOTE'
set -euo pipefail
MAILU_DIR=/opt/agentbbs/deploy/mailu
cd "$MAILU_DIR"

if [ ! -f mailu.env ]; then
echo "::notice::mailu.env not present at ${MAILU_DIR} — stack not deployed, nothing to update"
exit 0
fi

echo "== images before =="
docker compose images

# Back up the irreplaceable bits before touching anything: the DKIM
# signing keys (data/dkim) and the admin database (data/data, sqlite).
# Mailboxes (data/mail) are large and are NOT touched by an image pull,
# so we deliberately skip them here — back those up separately.
ts="$(date -u +%Y%m%dT%H%M%SZ)"
install -d -m 700 backups
tar czf "backups/mailu-state-${ts}.tgz" \
$( [ -d data/dkim ] && echo data/dkim ) \
$( [ -d data/data ] && echo data/data ) 2>/dev/null || true
echo "::notice::backed up DKIM + admin DB to backups/mailu-state-${ts}.tgz"
# Keep only the 10 most recent state backups.
ls -1t backups/mailu-state-*.tgz 2>/dev/null | tail -n +11 | xargs -r rm -f

echo "== pulling latest patch for the pinned series =="
docker compose pull

echo "== recreating containers =="
docker compose up -d

# Give services a moment, then verify. Mailu's front serves HTTP on
# 127.0.0.1:8080 (Caddy fronts it); a reachable webmail means the
# stack came back up.
ok=0
code=""
for i in $(seq 1 30); do
code="$(curl -fsS -o /dev/null -w '%{http_code}' --max-time 5 http://127.0.0.1:8080/ || true)"
case "$code" in
2??|3??) ok=1; break ;;
esac
sleep 3
done

echo "== compose status =="
docker compose ps

if [ "$ok" != "1" ]; then
echo "::error::Mailu front did not answer on 127.0.0.1:8080 after update — check 'docker compose logs' in ${MAILU_DIR}. Restore from backups/mailu-state-${ts}.tgz if needed."
exit 1
fi
echo "::notice::Mailu front is answering (HTTP ${code}) after update"

echo "== images after =="
docker compose images

if [ "${PRUNE:-false}" = "true" ]; then
echo "== pruning dangling images =="
docker image prune -f
fi
REMOTE
10 changes: 10 additions & 0 deletions deploy/mailu/mailu.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,16 @@ BIND_ADDRESS4=127.0.0.1
SUBNET=192.168.203.0/24
MESSAGE_SIZE_LIMIT=52428800 # 50 MB

# --- Addressing --------------------------------------------------------------
# Plus-addressing (subaddressing): deliver mail sent to <name>+<tag>@ into the
# <name>@ mailbox (keeping the +tag in the To: header for filtering) instead of
# bouncing it as an unknown recipient. Required by qaaas.dev's packages/mail,
# which mints throwaway addresses like chovy+run-42@bbs.profullstack.com off the
# single chovy@ mailbox. NOTE: this affects DELIVERY only — it does NOT let
# <name>+<tag>@ be used as a LOGIN (Mailu authenticates the exact address; log
# into webmail as the base <name>@ and all +tagged mail is already there).
RECIPIENT_DELIMITER=+

# --- Gateway (the BBS reads/sends on behalf of members) ----------------------
# A Dovecot master user lets the agentbbs gateway open any member's mailbox with
# one secret (login "<name>*<master>"). Created by deploy/mailu/provision-mailbox.sh.
Expand Down
2 changes: 1 addition & 1 deletion setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ IRC_NETWORK="${IRC_NETWORK:-ProfullstackBBS}" # IRC network name shown to clien
ERGO_DATA="${ERGO_DATA:-/var/lib/ergo}" # Ergo state dir (ircd.db, tls/)
FORGEJO="${FORGEJO:-1}" # set 0 to skip the AgentGit Forgejo backend (git.${DOMAIN#*.})
GIT_DOMAIN="${GIT_DOMAIN:-git.${DOMAIN#*.}}" # AgentGit host (default: git.<root-of-DOMAIN>, e.g. git.profullstack.com)
FORGEJO_VERSION="${FORGEJO_VERSION:-11.0.1}" # Forgejo release to install
FORGEJO_VERSION="${FORGEJO_VERSION:-11.0.15}" # Forgejo release to install (latest 11.0.x LTS patch)
MAIL_STACK="${MAIL_STACK:-1}" # set 0 to skip the co-located Mailu mail stack (mail.${DOMAIN#*.}). NOT named MAIL: that is a reserved env var (the mail-spool path, e.g. /var/mail/root) which PAM sets under sudo, so a CI deploy inherited MAIL=/var/mail/root and silently dropped the mail Caddy route + §9e provisioning.
MAIL_DOMAIN="${MAIL_DOMAIN:-mail.${DOMAIN#*.}}" # mail host (default: mail.<root-of-DOMAIN>, e.g. mail.profullstack.com)
FORGEJO_HTTP_ADDR="${FORGEJO_HTTP_ADDR:-127.0.0.1:3000}" # Forgejo loopback HTTP (Caddy fronts it)
Expand Down
Loading