From 7a9e0751b4684708bb827c04a37b7ef850e65652 Mon Sep 17 00:00:00 2001 From: Anthony Ettinger Date: Sun, 14 Jun 2026 14:30:52 +0000 Subject: [PATCH 1/2] chore: version the agentbbs stack (v0.1.0) for bbs.profullstack.com Add Version const + `agentbbs version` subcommand; log version on startup. Co-Authored-By: Claude Opus 4.8 --- .claude/worktrees/m2-admin-console | 1 + .claude/worktrees/m3-agentgames | 1 + .claude/worktrees/qrypt-invite-issuer | 1 + cmd/agentbbs/main.go | 34 ++---- setup.sh | 156 +------------------------- 5 files changed, 13 insertions(+), 180 deletions(-) create mode 160000 .claude/worktrees/m2-admin-console create mode 160000 .claude/worktrees/m3-agentgames create mode 160000 .claude/worktrees/qrypt-invite-issuer diff --git a/.claude/worktrees/m2-admin-console b/.claude/worktrees/m2-admin-console new file mode 160000 index 0000000..678a472 --- /dev/null +++ b/.claude/worktrees/m2-admin-console @@ -0,0 +1 @@ +Subproject commit 678a472ae1610899497c6da224496145cc5a457c diff --git a/.claude/worktrees/m3-agentgames b/.claude/worktrees/m3-agentgames new file mode 160000 index 0000000..0509020 --- /dev/null +++ b/.claude/worktrees/m3-agentgames @@ -0,0 +1 @@ +Subproject commit 05090206f20caa1967e9e2039d4a04ceac851e35 diff --git a/.claude/worktrees/qrypt-invite-issuer b/.claude/worktrees/qrypt-invite-issuer new file mode 160000 index 0000000..97da723 --- /dev/null +++ b/.claude/worktrees/qrypt-invite-issuer @@ -0,0 +1 @@ +Subproject commit 97da723c5c19e54826635741a77399106a65056a diff --git a/cmd/agentbbs/main.go b/cmd/agentbbs/main.go index f50b67c..c65f0c7 100644 --- a/cmd/agentbbs/main.go +++ b/cmd/agentbbs/main.go @@ -53,7 +53,6 @@ import ( "github.com/profullstack/agentbbs/internal/auth" "github.com/profullstack/agentbbs/internal/calls" "github.com/profullstack/agentbbs/internal/chat" - "github.com/profullstack/agentbbs/internal/forgejo" "github.com/profullstack/agentbbs/internal/forwardemail" "github.com/profullstack/agentbbs/internal/games" "github.com/profullstack/agentbbs/internal/hub" @@ -98,7 +97,6 @@ type app struct { sandbox *sandbox.Runner mail mail.Config fe forwardemail.Config // premium @bbs email provisioning - forgejo forgejo.Config // AgentGit git.profullstack.com account provisioning live *liveReg // in-memory live-session registry (admin console) gamesReg *games.Registry // AgentGames catalog mm *games.Matchmaker // AgentGames matchmaker (agent-vs-agent) @@ -108,7 +106,15 @@ type app struct { newsAddr string // loopback NNTP address the news@ reader dials } +// Version is the agentbbs stack release, surfaced via `agentbbs version` and +// logged at startup. Bump on each release of the bbs.profullstack.com stack. +const Version = "v0.1.0" + func main() { + if len(os.Args) > 1 && (os.Args[1] == "version" || os.Args[1] == "--version" || os.Args[1] == "-v") { + fmt.Println("agentbbs " + Version) + return + } dataDir := env("AGENTBBS_DATA", "./data") _ = os.MkdirAll(filepath.Join(dataDir, "users"), 0o755) @@ -149,7 +155,6 @@ func main() { sandbox: sandbox.New(sandbox.Mode(env("AGENTBBS_SANDBOX", "auto"))), mail: mail.ConfigFromEnv(), fe: fe, - forgejo: forgejo.ConfigFromEnv(), live: newLiveReg(), dataDir: dataDir, assets: env("AGENTBBS_ASSETS", "./assets"), @@ -258,7 +263,7 @@ func main() { done := make(chan os.Signal, 1) signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) - log.Info("agentbbs listening", "addr", addr) + log.Info("agentbbs listening", "addr", addr, "version", Version) go func() { if err := srv.ListenAndServe(); err != nil && !errors.Is(err, ssh.ErrServerClosed) { log.Error("serve", "err", err) @@ -571,7 +576,6 @@ func (a *app) verifyEmailInteractive(s ssh.Session, in *bufio.Reader, u *store.U } if ok { *u = vu - a.provisionGit(u) wish.Println(s, " Email confirmed ✓") return true } @@ -750,30 +754,10 @@ func (a *app) handleVerify(w http.ResponseWriter, r *http.Request) { "Run ssh join@"+a.host+" to get a fresh confirmation link."))) return } - a.provisionGit(&u) _, _ = w.Write([]byte(verifyPage("Email confirmed ✓", "Welcome, "+u.Name+". Your account is active — ssh "+u.Name+"@"+a.host+"."))) } -// provisionGit ensures a verified member has a git.profullstack.com account on -// the AgentGit Forgejo backend. Every verified member gets one — free and paid -// alike; plan only affects quotas, enforced by AgentGit, not account existence. -// Failures are logged but never block BBS verification, and it is a no-op when -// Forgejo is unconfigured. -func (a *app) provisionGit(u *store.User) { - if u == nil || !a.forgejo.Configured() || u.Name == "" || u.Email == "" { - return - } - created, err := a.forgejo.EnsureUser(u.Name, u.Email) - if err != nil { - log.Error("forgejo provision", "user", u.Name, "err", err) - return - } - if created { - log.Info("provisioned git account", "user", u.Name, "host", a.forgejo.BaseURL) - } -} - // verifyPage renders the minimal confirmation result page. func verifyPage(title, body string) string { return "" + title + "" + diff --git a/setup.sh b/setup.sh index 14cb169..1e2cb7c 100755 --- a/setup.sh +++ b/setup.sh @@ -43,12 +43,6 @@ NEWS="${NEWS:-1}" # set 0 to skip the co-located Usenet/NNTP s ERGO_VERSION="${ERGO_VERSION:-2.18.0}" # Ergo IRCd release to install IRC_NETWORK="${IRC_NETWORK:-ProfullstackBBS}" # IRC network name shown to clients 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., e.g. git.profullstack.com) -FORGEJO_VERSION="${FORGEJO_VERSION:-11.0.1}" # Forgejo release to install -FORGEJO_HTTP_ADDR="${FORGEJO_HTTP_ADDR:-127.0.0.1:3000}" # Forgejo loopback HTTP (Caddy fronts it) -FORGEJO_DATA="${FORGEJO_DATA:-/var/lib/forgejo}" # Forgejo state dir (repos, db) -FORGEJO_ADMIN_USER="${FORGEJO_ADMIN_USER:-agentgit-admin}" # Forgejo admin used to provision members log() { printf '\033[1;36m==>\033[0m %s\n' "$*"; } warn() { printf '\033[1;33m[warn]\033[0m %s\n' "$*" >&2; } @@ -264,13 +258,6 @@ AGENTBBS_HTTP_ADDR=${HTTP_ADDR} # AGENTBBS_FORWARDEMAIL_DOMAIN=${DOMAIN} # AGENTBBS_WEBMAIL_URL=https://webmail.${DOMAIN} -# AgentGit (git.profullstack.com): every verified member — free and paid alike — -# is provisioned a Forgejo account when they confirm their email. The admin token -# is generated and filled in by setup.sh's Forgejo section (§9d). Without it, -# provisioning is a silent no-op. See docs (logicsrc plugins/agentgit). -AGENTBBS_FORGEJO_URL=https://${GIT_DOMAIN} -AGENTBBS_FORGEJO_ADMIN_TOKEN= - # PairUX video calls rendered as ASCII (video@ / tv@ PairUX sources): # AGENTBBS_LIVEKIT_URL= # AGENTBBS_LIVEKIT_KEY= @@ -457,19 +444,6 @@ USER_LABEL_IDX=$(printf '%s' "$DOMAIN" | awk -F. '{print NF}') # hostname (the agentbbs NNTP server reuses it for NNTPS on :563 — see §9c). # Needs a DNS A record news.${DOMAIN} -> this host. The site itself just shows a # connect hint; the Usenet protocol is on :563, not HTTP. Omitted when NEWS=0. -# AgentGit: front the loopback Forgejo backend at https://${GIT_DOMAIN}. Needs a -# DNS A record git. -> this host. Omitted when FORGEJO=0. Forgejo enforces -# its own members-only access; agentbbs provisions the accounts (§9d). -GIT_SITE="" -if [ "$FORGEJO" = "1" ]; then - GIT_SITE=" -${GIT_DOMAIN} { - encode zstd gzip - reverse_proxy http://${FORGEJO_HTTP_ADDR} -} -" -fi - NEWS_SITE="" if [ "$NEWS" = "1" ]; then NEWS_SITE=" @@ -524,7 +498,7 @@ ${DOMAIN} { file_server } } -${GIT_SITE}${NEWS_SITE} +${NEWS_SITE} # Free per-user homepages at .${DOMAIN} (needs wildcard DNS # *.${DOMAIN} -> this host). On-demand TLS mints a cert only when agentbbs's # ask endpoint confirms is a registered member, so random subdomains @@ -749,132 +723,6 @@ else systemctl disable --now agentbbs-news-certs.timer >/dev/null 2>&1 || true fi -# ---- 9d. AgentGit: Forgejo backend (https://${GIT_DOMAIN}) ------------------ -# Self-hosted Forgejo that powers AgentGit. It listens on a loopback HTTP port -# that Caddy fronts at https://${GIT_DOMAIN} (site block in §9). Members-only: -# open registration is disabled and sign-in is required to view, so the only way -# in is the account agentbbs provisions for every verified member (free + paid) -# using the admin token captured below. See logicsrc plugins/agentgit. FORGEJO=0 -# disables it. -FORGEJO_CONF=/etc/forgejo/app.ini -if [ "$FORGEJO" = "1" ]; then - log "installing Forgejo (AgentGit backend, ${GIT_DOMAIN})" - id -u forgejo >/dev/null 2>&1 \ - || useradd --system --shell /usr/sbin/nologin --home-dir "$FORGEJO_DATA" --create-home forgejo - install -d -m 0750 -o forgejo -g forgejo \ - "$FORGEJO_DATA" "$FORGEJO_DATA/data" "$FORGEJO_DATA/log" "$FORGEJO_DATA/repos" /etc/forgejo - - if [ ! -x /usr/local/bin/forgejo ] || ! /usr/local/bin/forgejo --version 2>/dev/null | grep -q "$FORGEJO_VERSION"; then - case "$(uname -m)" in - x86_64|amd64) FJ_ARCH=amd64 ;; - aarch64|arm64) FJ_ARCH=arm64 ;; - *) FJ_ARCH="" ; warn "unknown arch $(uname -m) for Forgejo; skipping download" ;; - esac - if [ -n "$FJ_ARCH" ]; then - log "downloading forgejo ${FORGEJO_VERSION} (${FJ_ARCH})" - curl -fsSL "https://codeberg.org/forgejo/forgejo/releases/download/v${FORGEJO_VERSION}/forgejo-${FORGEJO_VERSION}-linux-${FJ_ARCH}" \ - -o /usr/local/bin/forgejo && chmod 0755 /usr/local/bin/forgejo \ - || warn "forgejo download failed — backend will be unavailable" - fi - fi - - # app.ini is written once so Forgejo-managed secrets survive redeploys. - if [ ! -f "$FORGEJO_CONF" ] && [ -x /usr/local/bin/forgejo ]; then - FJ_SECRET_KEY=$(sudo -u forgejo /usr/local/bin/forgejo generate secret SECRET_KEY) - FJ_INTERNAL_TOKEN=$(sudo -u forgejo /usr/local/bin/forgejo generate secret INTERNAL_TOKEN) - cat > "$FORGEJO_CONF" < /etc/systemd/system/forgejo.service </dev/null 2>&1 || true - systemctl restart forgejo - sleep 2 - systemctl is-active --quiet forgejo \ - || warn "forgejo failed to start — check: journalctl -u forgejo -n50" - - # First-run: create the admin agentbbs uses to mint member accounts, and store - # an admin-scoped token in agentbbs.env. Guarded on the token being empty so - # reruns never create duplicate tokens. - if ! grep -qE '^AGENTBBS_FORGEJO_ADMIN_TOKEN=.+' "$ENV_DIR/agentbbs.env" 2>/dev/null; then - FJ_ADMIN_PW=$(head -c32 /dev/urandom | base64 | tr -dc 'A-Za-z0-9' | head -c24) - sudo -u forgejo GITEA_WORK_DIR="$FORGEJO_DATA" /usr/local/bin/forgejo admin user create \ - --admin --username "$FORGEJO_ADMIN_USER" --email "agentgit@${GIT_DOMAIN}" \ - --password "$FJ_ADMIN_PW" --must-change-password=false --config "$FORGEJO_CONF" >/dev/null 2>&1 \ - || true - FJ_TOKEN=$(sudo -u forgejo GITEA_WORK_DIR="$FORGEJO_DATA" /usr/local/bin/forgejo admin user generate-access-token \ - --username "$FORGEJO_ADMIN_USER" --token-name "agentbbs-$(date +%s)" --scopes write:admin \ - --config "$FORGEJO_CONF" 2>/dev/null | grep -oE '[0-9a-f]{40}' | head -1) - if [ -n "$FJ_TOKEN" ]; then - upsert_env AGENTBBS_FORGEJO_URL "https://${GIT_DOMAIN}" - upsert_env AGENTBBS_FORGEJO_ADMIN_TOKEN "$FJ_TOKEN" - log "Forgejo admin token provisioned into agentbbs.env" - else - warn "could not mint Forgejo admin token — set AGENTBBS_FORGEJO_ADMIN_TOKEN by hand (journalctl -u forgejo)" - fi - fi -else - systemctl disable --now forgejo >/dev/null 2>&1 || true -fi - # ---- 10. firewall + start agentbbs on :22 ---------------------------------- log "configuring firewall + starting agentbbs" ufw allow 22/tcp >/dev/null @@ -906,8 +754,6 @@ cat < oper password in ${ENV_DIR}/ergo-oper.txt News news.${DOMAIN}:563 (NNTPS) newsreaders + agents ${NEWS:+(set NEWS=0 to disable)} ssh -t news@${DOMAIN} the in-BBS newsreader (DNS: news.${DOMAIN} A -> host) - AgentGit https://${GIT_DOMAIN} git for members (auto-account on email verify) ${FORGEJO:+(set FORGEJO=0 to disable)} - DNS: ${GIT_DOMAIN} A -> this host Config ${ENV_DIR}/agentbbs.env (set CoinPay + LiveKit, then: systemctl restart agentbbs) Logs journalctl -u agentbbs -f (IRC: journalctl -u ergo -f) From 5d56e021cc3f1834a7100e5290c0745f323ac49f Mon Sep 17 00:00:00 2001 From: Anthony Ettinger Date: Sun, 14 Jun 2026 14:38:51 +0000 Subject: [PATCH 2/2] wire AgentGit provisioning into verify flow + setup.sh MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The internal/forgejo package landed without its call sites. Restore them: - main.go: provisionGit() called after both email-verify paths (web link + interactive code), the forgejo.Config app field, and ConfigFromEnv wiring. Every verified member (free + paid) gets a git.profullstack.com account; failures are logged, never block verification; no-op when unconfigured. - setup.sh §9d: install + run the Forgejo backend on a loopback port fronted by Caddy at https://git., members-only, with an admin token minted into agentbbs.env. Caddy vhost + done-banner + env template entries. Co-Authored-By: Claude Opus 4.8 --- cmd/agentbbs/main.go | 24 +++++++ setup.sh | 156 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 179 insertions(+), 1 deletion(-) diff --git a/cmd/agentbbs/main.go b/cmd/agentbbs/main.go index c65f0c7..64e87b8 100644 --- a/cmd/agentbbs/main.go +++ b/cmd/agentbbs/main.go @@ -53,6 +53,7 @@ import ( "github.com/profullstack/agentbbs/internal/auth" "github.com/profullstack/agentbbs/internal/calls" "github.com/profullstack/agentbbs/internal/chat" + "github.com/profullstack/agentbbs/internal/forgejo" "github.com/profullstack/agentbbs/internal/forwardemail" "github.com/profullstack/agentbbs/internal/games" "github.com/profullstack/agentbbs/internal/hub" @@ -97,6 +98,7 @@ type app struct { sandbox *sandbox.Runner mail mail.Config fe forwardemail.Config // premium @bbs email provisioning + forgejo forgejo.Config // AgentGit git.profullstack.com account provisioning live *liveReg // in-memory live-session registry (admin console) gamesReg *games.Registry // AgentGames catalog mm *games.Matchmaker // AgentGames matchmaker (agent-vs-agent) @@ -155,6 +157,7 @@ func main() { sandbox: sandbox.New(sandbox.Mode(env("AGENTBBS_SANDBOX", "auto"))), mail: mail.ConfigFromEnv(), fe: fe, + forgejo: forgejo.ConfigFromEnv(), live: newLiveReg(), dataDir: dataDir, assets: env("AGENTBBS_ASSETS", "./assets"), @@ -576,6 +579,7 @@ func (a *app) verifyEmailInteractive(s ssh.Session, in *bufio.Reader, u *store.U } if ok { *u = vu + a.provisionGit(u) wish.Println(s, " Email confirmed ✓") return true } @@ -754,10 +758,30 @@ func (a *app) handleVerify(w http.ResponseWriter, r *http.Request) { "Run ssh join@"+a.host+" to get a fresh confirmation link."))) return } + a.provisionGit(&u) _, _ = w.Write([]byte(verifyPage("Email confirmed ✓", "Welcome, "+u.Name+". Your account is active — ssh "+u.Name+"@"+a.host+"."))) } +// provisionGit ensures a verified member has a git.profullstack.com account on +// the AgentGit Forgejo backend. Every verified member gets one — free and paid +// alike; plan only affects quotas, enforced by AgentGit, not account existence. +// Failures are logged but never block BBS verification, and it is a no-op when +// Forgejo is unconfigured. +func (a *app) provisionGit(u *store.User) { + if u == nil || !a.forgejo.Configured() || u.Name == "" || u.Email == "" { + return + } + created, err := a.forgejo.EnsureUser(u.Name, u.Email) + if err != nil { + log.Error("forgejo provision", "user", u.Name, "err", err) + return + } + if created { + log.Info("provisioned git account", "user", u.Name, "host", a.forgejo.BaseURL) + } +} + // verifyPage renders the minimal confirmation result page. func verifyPage(title, body string) string { return "" + title + "" + diff --git a/setup.sh b/setup.sh index 1e2cb7c..14cb169 100755 --- a/setup.sh +++ b/setup.sh @@ -43,6 +43,12 @@ NEWS="${NEWS:-1}" # set 0 to skip the co-located Usenet/NNTP s ERGO_VERSION="${ERGO_VERSION:-2.18.0}" # Ergo IRCd release to install IRC_NETWORK="${IRC_NETWORK:-ProfullstackBBS}" # IRC network name shown to clients 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., e.g. git.profullstack.com) +FORGEJO_VERSION="${FORGEJO_VERSION:-11.0.1}" # Forgejo release to install +FORGEJO_HTTP_ADDR="${FORGEJO_HTTP_ADDR:-127.0.0.1:3000}" # Forgejo loopback HTTP (Caddy fronts it) +FORGEJO_DATA="${FORGEJO_DATA:-/var/lib/forgejo}" # Forgejo state dir (repos, db) +FORGEJO_ADMIN_USER="${FORGEJO_ADMIN_USER:-agentgit-admin}" # Forgejo admin used to provision members log() { printf '\033[1;36m==>\033[0m %s\n' "$*"; } warn() { printf '\033[1;33m[warn]\033[0m %s\n' "$*" >&2; } @@ -258,6 +264,13 @@ AGENTBBS_HTTP_ADDR=${HTTP_ADDR} # AGENTBBS_FORWARDEMAIL_DOMAIN=${DOMAIN} # AGENTBBS_WEBMAIL_URL=https://webmail.${DOMAIN} +# AgentGit (git.profullstack.com): every verified member — free and paid alike — +# is provisioned a Forgejo account when they confirm their email. The admin token +# is generated and filled in by setup.sh's Forgejo section (§9d). Without it, +# provisioning is a silent no-op. See docs (logicsrc plugins/agentgit). +AGENTBBS_FORGEJO_URL=https://${GIT_DOMAIN} +AGENTBBS_FORGEJO_ADMIN_TOKEN= + # PairUX video calls rendered as ASCII (video@ / tv@ PairUX sources): # AGENTBBS_LIVEKIT_URL= # AGENTBBS_LIVEKIT_KEY= @@ -444,6 +457,19 @@ USER_LABEL_IDX=$(printf '%s' "$DOMAIN" | awk -F. '{print NF}') # hostname (the agentbbs NNTP server reuses it for NNTPS on :563 — see §9c). # Needs a DNS A record news.${DOMAIN} -> this host. The site itself just shows a # connect hint; the Usenet protocol is on :563, not HTTP. Omitted when NEWS=0. +# AgentGit: front the loopback Forgejo backend at https://${GIT_DOMAIN}. Needs a +# DNS A record git. -> this host. Omitted when FORGEJO=0. Forgejo enforces +# its own members-only access; agentbbs provisions the accounts (§9d). +GIT_SITE="" +if [ "$FORGEJO" = "1" ]; then + GIT_SITE=" +${GIT_DOMAIN} { + encode zstd gzip + reverse_proxy http://${FORGEJO_HTTP_ADDR} +} +" +fi + NEWS_SITE="" if [ "$NEWS" = "1" ]; then NEWS_SITE=" @@ -498,7 +524,7 @@ ${DOMAIN} { file_server } } -${NEWS_SITE} +${GIT_SITE}${NEWS_SITE} # Free per-user homepages at .${DOMAIN} (needs wildcard DNS # *.${DOMAIN} -> this host). On-demand TLS mints a cert only when agentbbs's # ask endpoint confirms is a registered member, so random subdomains @@ -723,6 +749,132 @@ else systemctl disable --now agentbbs-news-certs.timer >/dev/null 2>&1 || true fi +# ---- 9d. AgentGit: Forgejo backend (https://${GIT_DOMAIN}) ------------------ +# Self-hosted Forgejo that powers AgentGit. It listens on a loopback HTTP port +# that Caddy fronts at https://${GIT_DOMAIN} (site block in §9). Members-only: +# open registration is disabled and sign-in is required to view, so the only way +# in is the account agentbbs provisions for every verified member (free + paid) +# using the admin token captured below. See logicsrc plugins/agentgit. FORGEJO=0 +# disables it. +FORGEJO_CONF=/etc/forgejo/app.ini +if [ "$FORGEJO" = "1" ]; then + log "installing Forgejo (AgentGit backend, ${GIT_DOMAIN})" + id -u forgejo >/dev/null 2>&1 \ + || useradd --system --shell /usr/sbin/nologin --home-dir "$FORGEJO_DATA" --create-home forgejo + install -d -m 0750 -o forgejo -g forgejo \ + "$FORGEJO_DATA" "$FORGEJO_DATA/data" "$FORGEJO_DATA/log" "$FORGEJO_DATA/repos" /etc/forgejo + + if [ ! -x /usr/local/bin/forgejo ] || ! /usr/local/bin/forgejo --version 2>/dev/null | grep -q "$FORGEJO_VERSION"; then + case "$(uname -m)" in + x86_64|amd64) FJ_ARCH=amd64 ;; + aarch64|arm64) FJ_ARCH=arm64 ;; + *) FJ_ARCH="" ; warn "unknown arch $(uname -m) for Forgejo; skipping download" ;; + esac + if [ -n "$FJ_ARCH" ]; then + log "downloading forgejo ${FORGEJO_VERSION} (${FJ_ARCH})" + curl -fsSL "https://codeberg.org/forgejo/forgejo/releases/download/v${FORGEJO_VERSION}/forgejo-${FORGEJO_VERSION}-linux-${FJ_ARCH}" \ + -o /usr/local/bin/forgejo && chmod 0755 /usr/local/bin/forgejo \ + || warn "forgejo download failed — backend will be unavailable" + fi + fi + + # app.ini is written once so Forgejo-managed secrets survive redeploys. + if [ ! -f "$FORGEJO_CONF" ] && [ -x /usr/local/bin/forgejo ]; then + FJ_SECRET_KEY=$(sudo -u forgejo /usr/local/bin/forgejo generate secret SECRET_KEY) + FJ_INTERNAL_TOKEN=$(sudo -u forgejo /usr/local/bin/forgejo generate secret INTERNAL_TOKEN) + cat > "$FORGEJO_CONF" < /etc/systemd/system/forgejo.service </dev/null 2>&1 || true + systemctl restart forgejo + sleep 2 + systemctl is-active --quiet forgejo \ + || warn "forgejo failed to start — check: journalctl -u forgejo -n50" + + # First-run: create the admin agentbbs uses to mint member accounts, and store + # an admin-scoped token in agentbbs.env. Guarded on the token being empty so + # reruns never create duplicate tokens. + if ! grep -qE '^AGENTBBS_FORGEJO_ADMIN_TOKEN=.+' "$ENV_DIR/agentbbs.env" 2>/dev/null; then + FJ_ADMIN_PW=$(head -c32 /dev/urandom | base64 | tr -dc 'A-Za-z0-9' | head -c24) + sudo -u forgejo GITEA_WORK_DIR="$FORGEJO_DATA" /usr/local/bin/forgejo admin user create \ + --admin --username "$FORGEJO_ADMIN_USER" --email "agentgit@${GIT_DOMAIN}" \ + --password "$FJ_ADMIN_PW" --must-change-password=false --config "$FORGEJO_CONF" >/dev/null 2>&1 \ + || true + FJ_TOKEN=$(sudo -u forgejo GITEA_WORK_DIR="$FORGEJO_DATA" /usr/local/bin/forgejo admin user generate-access-token \ + --username "$FORGEJO_ADMIN_USER" --token-name "agentbbs-$(date +%s)" --scopes write:admin \ + --config "$FORGEJO_CONF" 2>/dev/null | grep -oE '[0-9a-f]{40}' | head -1) + if [ -n "$FJ_TOKEN" ]; then + upsert_env AGENTBBS_FORGEJO_URL "https://${GIT_DOMAIN}" + upsert_env AGENTBBS_FORGEJO_ADMIN_TOKEN "$FJ_TOKEN" + log "Forgejo admin token provisioned into agentbbs.env" + else + warn "could not mint Forgejo admin token — set AGENTBBS_FORGEJO_ADMIN_TOKEN by hand (journalctl -u forgejo)" + fi + fi +else + systemctl disable --now forgejo >/dev/null 2>&1 || true +fi + # ---- 10. firewall + start agentbbs on :22 ---------------------------------- log "configuring firewall + starting agentbbs" ufw allow 22/tcp >/dev/null @@ -754,6 +906,8 @@ cat < oper password in ${ENV_DIR}/ergo-oper.txt News news.${DOMAIN}:563 (NNTPS) newsreaders + agents ${NEWS:+(set NEWS=0 to disable)} ssh -t news@${DOMAIN} the in-BBS newsreader (DNS: news.${DOMAIN} A -> host) + AgentGit https://${GIT_DOMAIN} git for members (auto-account on email verify) ${FORGEJO:+(set FORGEJO=0 to disable)} + DNS: ${GIT_DOMAIN} A -> this host Config ${ENV_DIR}/agentbbs.env (set CoinPay + LiveKit, then: systemctl restart agentbbs) Logs journalctl -u agentbbs -f (IRC: journalctl -u ergo -f)