-
Notifications
You must be signed in to change notification settings - Fork 7
162 lines (151 loc) · 7.43 KB
/
Copy pathdeploy.yml
File metadata and controls
162 lines (151 loc) · 7.43 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
name: deploy
# Fully autonomous, idempotent deploy. On every push to main (or manual
# dispatch) this builds the Go binaries ON THE RUNNER (which has plenty of
# RAM), ships them to the bbs.profullstack.com droplet, and re-runs the
# idempotent provisioner (setup.sh) with SKIP_BUILD=1 so the tiny droplet
# never has to compile. setup.sh still pulls origin, refreshes config/assets,
# and restarts the agentbbs service that answers `ssh join@bbs.profullstack.com`.
# Re-running is always safe.
#
# Why build on the runner: the droplet is a ~458MB box also running ergo,
# forgejo, tor, podman and the live agentbbs. The Go linker's peak memory was
# OOM-killing the build — and with it the sshd serving the deploy session,
# surfacing as "Connection closed by remote host" (exit 255). Compiling on the
# 16GB runner removes that failure mode entirely.
#
# Required repo secrets (Settings -> Secrets and variables -> Actions):
# DEPLOY_SSH_KEY private key whose public half is in the droplet admin
# user's ~/.ssh/authorized_keys
# DEPLOY_HOST bbs.profullstack.com (or the droplet's public IP)
# Optional (have sensible defaults below):
# DEPLOY_USER admin SSH user (default: root)
# DEPLOY_PORT admin SSH port (default: 2202 — setup.sh moves OpenSSH here)
#
# The admin user needs passwordless sudo (root already does).
on:
push:
branches: [main, master]
workflow_dispatch:
# Never let two deploys overlap; setup.sh also self-locks, this is belt+braces.
concurrency:
group: deploy-production
cancel-in-progress: false
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- 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: Detect droplet architecture
id: arch
env:
DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
DEPLOY_USER: ${{ secrets.DEPLOY_USER || 'root' }}
DEPLOY_PORT: ${{ secrets.DEPLOY_PORT || '2202' }}
run: |
uname_m="$(ssh -i ~/.ssh/id_deploy -p "$DEPLOY_PORT" \
-o BatchMode=yes -o StrictHostKeyChecking=yes \
"${DEPLOY_USER}@${DEPLOY_HOST}" 'uname -m')"
case "$uname_m" in
x86_64|amd64) goarch=amd64 ;;
aarch64|arm64) goarch=arm64 ;;
*) echo "::error::unsupported droplet arch '$uname_m'"; exit 1 ;;
esac
echo "goarch=$goarch" >> "$GITHUB_OUTPUT"
echo "::notice::droplet arch $uname_m -> GOARCH=$goarch"
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true
- name: Build binaries (on the runner, not the droplet)
env:
GOOS: linux
GOARCH: ${{ steps.arch.outputs.goarch }}
CGO_ENABLED: '0' # pure-Go (modernc sqlite) — static, portable binary
run: |
mkdir -p dist
go build -trimpath -o dist/agentbbs ./cmd/agentbbs
go build -trimpath -o dist/ascii-live ./cmd/ascii-live
file dist/* || true
- name: Ship binaries to the droplet
env:
DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
DEPLOY_USER: ${{ secrets.DEPLOY_USER || 'root' }}
DEPLOY_PORT: ${{ secrets.DEPLOY_PORT || '2202' }}
run: |
# scp can only name one remote target; copy each binary explicitly.
scp -i ~/.ssh/id_deploy -P "$DEPLOY_PORT" \
-o BatchMode=yes -o StrictHostKeyChecking=yes \
dist/agentbbs "${DEPLOY_USER}@${DEPLOY_HOST}:/tmp/agentbbs-deploy-agentbbs"
scp -i ~/.ssh/id_deploy -P "$DEPLOY_PORT" \
-o BatchMode=yes -o StrictHostKeyChecking=yes \
dist/ascii-live "${DEPLOY_USER}@${DEPLOY_HOST}:/tmp/agentbbs-deploy-ascii-live"
- name: Provision / redeploy (idempotent, SKIP_BUILD=1)
env:
DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
DEPLOY_USER: ${{ secrets.DEPLOY_USER || 'root' }}
DEPLOY_PORT: ${{ secrets.DEPLOY_PORT || '2202' }}
# Deploy whichever branch was pushed (main or master), so a rename
# "just works". For workflow_dispatch this is the chosen branch.
DEPLOY_BRANCH: ${{ github.ref_name }}
# App secrets injected into the droplet's env file by setup.sh.
# GitHub masks these in logs; setup.sh upserts them idempotently.
COINPAY_API_KEY: ${{ secrets.COINPAY_API_KEY }}
COINPAY_MERCHANT_ID: ${{ secrets.COINPAY_MERCHANT_ID }}
AGENTBBS_QRYPT_ISSUER_KEY: ${{ secrets.AGENTBBS_QRYPT_ISSUER_KEY }}
run: |
ssh -i ~/.ssh/id_deploy -p "$DEPLOY_PORT" \
-o BatchMode=yes -o StrictHostKeyChecking=yes \
"${DEPLOY_USER}@${DEPLOY_HOST}" \
"sudo -n env BRANCH=$(printf %q "$DEPLOY_BRANCH") \
COINPAY_API_KEY=$(printf %q "$COINPAY_API_KEY") \
COINPAY_MERCHANT_ID=$(printf %q "$COINPAY_MERCHANT_ID") \
AGENTBBS_QRYPT_ISSUER_KEY=$(printf %q "$AGENTBBS_QRYPT_ISSUER_KEY") \
bash -s" <<'REMOTE'
set -euo pipefail
REPO=https://github.com/profullstack/agentbbs.git
BRANCH="${BRANCH:-main}"
SRC=/opt/agentbbs
# Bootstrap on a fresh box, then always sync to origin so we run the
# latest setup.sh (it may have changed in this very push).
if [ ! -d "$SRC/.git" ]; then
git clone --depth 1 -b "$BRANCH" "$REPO" "$SRC"
fi
git -C "$SRC" fetch --depth 1 origin "$BRANCH"
git -C "$SRC" reset --hard "origin/$BRANCH"
# Install the runner-built binaries, then tell setup.sh not to compile.
install -m 0755 /tmp/agentbbs-deploy-agentbbs /usr/local/bin/agentbbs
install -m 0755 /tmp/agentbbs-deploy-ascii-live /usr/local/bin/ascii-live
rm -f /tmp/agentbbs-deploy-agentbbs /tmp/agentbbs-deploy-ascii-live
exec env BRANCH="$BRANCH" SKIP_BUILD=1 \
COINPAY_API_KEY="${COINPAY_API_KEY:-}" \
COINPAY_MERCHANT_ID="${COINPAY_MERCHANT_ID:-}" \
AGENTBBS_QRYPT_ISSUER_KEY="${AGENTBBS_QRYPT_ISSUER_KEY:-}" \
"$SRC/setup.sh"
REMOTE
- name: Smoke-test that agentbbs serves :22
if: success()
env:
DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
run: |
# Confirm an SSH server answers on :22 WITHOUT authenticating — a real
# join@ connection would register the connecting key as a new account,
# so we never complete a handshake here. Admin OpenSSH lives on the
# admin port, so anything serving :22 is agentbbs answering join@/bbs@.
if timeout 15 ssh-keyscan -T 10 -p 22 "$DEPLOY_HOST" 2>/dev/null | grep -q .; then
echo "::notice::agentbbs is serving SSH on ${DEPLOY_HOST}:22 (join@ is reachable)"
else
echo "::error::nothing is serving SSH on ${DEPLOY_HOST}:22 — agentbbs may be down"
exit 1
fi