Wire CoinPay merchant id + deploy secret injection

- payments: AGENTBBS_COINPAY_MERCHANT_ID -> --business-id on the premium
  create/pay commands (coinpay CLI reads COINPAY_API_KEY from env for auth).
- deploy.yml: forward COINPAY_API_KEY + COINPAY_MERCHANT_ID (GitHub secrets,
  masked in logs) to the remote setup.sh.
- setup.sh: idempotent upsert_env writes those secrets into agentbbs.env
  (COINPAY_API_KEY, AGENTBBS_COINPAY_MERCHANT_ID, COINPAY_BUSINESS_ID),
  preserving the rest. No secret values are committed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Anthony Ettinger 2026-06-14 08:19:48 +00:00
parent 3dd6fe863c
commit 5c0feda8d4
3 changed files with 57 additions and 9 deletions

View file

@ -51,11 +51,18 @@ jobs:
# 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 }}
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") bash -s" <<'REMOTE'
"sudo -n env BRANCH=$(printf %q "$DEPLOY_BRANCH") \
COINPAY_API_KEY=$(printf %q "$COINPAY_API_KEY") \
COINPAY_MERCHANT_ID=$(printf %q "$COINPAY_MERCHANT_ID") \
bash -s" <<'REMOTE'
set -euo pipefail
REPO=https://github.com/profullstack/agentbbs.git
BRANCH="${BRANCH:-main}"
@ -67,7 +74,10 @@ jobs:
fi
git -C "$SRC" fetch --depth 1 origin "$BRANCH"
git -C "$SRC" reset --hard "origin/$BRANCH"
exec env BRANCH="$BRANCH" "$SRC/setup.sh"
exec env BRANCH="$BRANCH" \
COINPAY_API_KEY="${COINPAY_API_KEY:-}" \
COINPAY_MERCHANT_ID="${COINPAY_MERCHANT_ID:-}" \
"$SRC/setup.sh"
REMOTE
- name: Smoke-test that agentbbs serves :22

View file

@ -40,6 +40,24 @@ func PremiumAmount() string { return envOr("AGENTBBS_PREMIUM_AMOUNT", "10")
func PremiumCurrency() string { return envOr("AGENTBBS_PREMIUM_CURRENCY", "USD") }
func PremiumBlockchain() string { return envOr("AGENTBBS_PREMIUM_BLOCKCHAIN", "eth") }
// MerchantID is the CoinPay merchant/business id payments are created under
// (AGENTBBS_COINPAY_MERCHANT_ID). When set it is passed as --business-id.
func MerchantID() string { return os.Getenv("AGENTBBS_COINPAY_MERCHANT_ID") }
// premiumCreateCmd builds the default `coinpay payment create` command, adding
// --business-id when a merchant id is configured. extra is appended verbatim
// (e.g. " --json --metadata %s"). The coinpay CLI reads COINPAY_API_KEY from
// the environment for auth.
func premiumCreateCmd(extra string) string {
cmd := "coinpay payment create --amount " + PremiumAmount() +
" --currency " + PremiumCurrency() +
" --blockchain " + PremiumBlockchain()
if m := MerchantID(); m != "" {
cmd += " --business-id " + m
}
return cmd + extra
}
func envOr(k, def string) string {
if v := os.Getenv(k); v != "" {
return v
@ -74,9 +92,7 @@ func PremiumReference(pubkeyFP string) string { return Reference("premium", pubk
func CreatePremiumCharge(ref string) (Charge, bool, error) {
tmpl := os.Getenv("AGENTBBS_COINPAY_PREMIUM_CREATE_CMD")
if tmpl == "" {
tmpl = "coinpay payment create --amount " + PremiumAmount() +
" --currency " + PremiumCurrency() +
" --blockchain " + PremiumBlockchain() + " --json --metadata %s"
tmpl = premiumCreateCmd(" --json --metadata %s")
}
line := tmpl
if strings.Contains(tmpl, "%s") {
@ -120,9 +136,7 @@ func CreatePremiumCharge(ref string) (Charge, bool, error) {
func PremiumPayCommand(ref string) string {
tmpl := os.Getenv("AGENTBBS_COINPAY_PREMIUM_PAY_TMPL")
if tmpl == "" {
tmpl = "coinpay payment create --amount " + PremiumAmount() +
" --currency " + PremiumCurrency() +
" --blockchain " + PremiumBlockchain() + " --metadata %s"
tmpl = premiumCreateCmd(" --metadata %s")
}
if strings.Contains(tmpl, "%s") {
return fmt.Sprintf(tmpl, ref)

View file

@ -188,7 +188,11 @@ AGENTBBS_HTTP_ADDR=${HTTP_ADDR}
# Premium payment via the coinpay CLI: join@ mints a charge and shows the amount
# + deposit address; the status command verifies a later settlement. %s is the
# per-account payment reference.
# per-account payment reference. COINPAY_API_KEY + the merchant id are injected
# by the CI deploy from GitHub secrets (COINPAY_API_KEY / COINPAY_MERCHANT_ID);
# set them here directly when provisioning by hand:
# COINPAY_API_KEY=cp_live_xxx
# AGENTBBS_COINPAY_MERCHANT_ID=<merchant/business uuid> # -> --business-id
# AGENTBBS_PREMIUM_AMOUNT=10
# AGENTBBS_PREMIUM_CURRENCY=USD
# AGENTBBS_PREMIUM_BLOCKCHAIN=eth
@ -213,6 +217,26 @@ ENV
chmod 0640 "$ENV_DIR/agentbbs.env"
fi
# Idempotently upsert secrets passed in the environment (e.g. by the CI deploy
# from GitHub Actions secrets) into agentbbs.env, preserving everything else.
# Secrets are never committed — they live only here and in encrypted CI storage.
upsert_env() { # KEY VALUE — skips when VALUE is empty
local key="$1" val="$2" file="$ENV_DIR/agentbbs.env"
[ -n "$val" ] || return 0
touch "$file"
if grep -qE "^${key}=" "$file"; then
# Replace in place (| delimiter avoids clashes with / or & in the value).
sed -i "s|^${key}=.*|${key}=${val}|" "$file"
else
printf '%s=%s\n' "$key" "$val" >> "$file"
fi
chmod 0640 "$file"
}
# CoinPay: API key (read by the coinpay CLI) + merchant/business id.
upsert_env COINPAY_API_KEY "${COINPAY_API_KEY:-}"
upsert_env AGENTBBS_COINPAY_MERCHANT_ID "${COINPAY_MERCHANT_ID:-${AGENTBBS_COINPAY_MERCHANT_ID:-}}"
upsert_env COINPAY_BUSINESS_ID "${COINPAY_MERCHANT_ID:-${AGENTBBS_COINPAY_MERCHANT_ID:-}}"
# ---- 7. systemd unit (runs as $SVC_USER, binds :22 via ambient capability) --
log "installing agentbbs.service"
cat > /etc/systemd/system/agentbbs.service <<UNIT