2026-10-08 09:36:03 +02:00
#!/usr/bin/env bash
# Send one prepared post (pr-hall-of-helix/posts/<file>.yaml) through a
# short-lived local adapter, then stop it.
#
2026-10-08 09:44:41 +02:00
# The bot token goes from OpenBao straight into the adapter's environment and
# is never printed. The adapter's store is in memory, so idempotency on post_id
# holds only within one run: do not re-run for a post that already went out.
2026-10-08 09:36:03 +02:00
#
2026-10-08 09:44:41 +02:00
# Run it yourself inside the attended OpenBao login. warden fails closed on any
# output, so redirect both streams, and use absolute paths (HOME is replaced):
#
# warden access openbao-platform-admin-login --exec -- bash -c \
# '/home/worsch/fluid-telegram/scripts/send-post.sh \
# /home/worsch/pr-hall-of-helix/posts/<post>.yaml test \
# > /home/worsch/send-post.log 2>&1'
2026-10-08 09:36:03 +02:00
set -euo pipefail
post = $( realpath " $1 " )
channel = ${ 2 :- test }
port = ${ PORT :- 18089 }
2026-10-08 09:44:41 +02:00
cd " $( dirname " $( realpath " $0 " ) " ) /.. "
spec = ${ SPEC :- $( realpath ../pr-hall-of-helix/presence/telegram.yaml) }
# Under `warden access ... --exec`, HOME is a throwaway session directory that
# holds the OpenBao token helper. Keep Go's caches at the real home so the
# build does not start from nothing there.
realhome = $( getent passwd " $( id -u) " | cut -d: -f6)
export GOPATH = ${ GOPATH :- $realhome /go }
export GOMODCACHE = ${ GOMODCACHE :- $GOPATH /pkg/mod }
export GOCACHE = ${ GOCACHE :- $realhome /.cache/go-build }
2026-10-08 09:36:03 +02:00
read -r want reviewed < <( python3 -c '
import sys, yaml
d = yaml.safe_load( open( sys.argv[ 1] ) )
2026-10-08 10:33:31 +02:00
r = str( d.get( "reviewed_by" ) or "" ) .strip( )
print( d[ "channel" ] , r if r and not r.startswith( "<" ) else "-" ) ' " $post " )
[ " $reviewed " != "-" ] || { echo "reviewed_by is not signed; a person reviews and signs before sending" >& 2; exit 2; }
2026-10-08 09:36:03 +02:00
TELEGRAM_CHANNEL_ID = $( go run ./cmd/provision adapter-config --spec " $spec " --channel " $channel " \
| sed -n 's/^TELEGRAM_CHANNEL_ID=//p' )
[ -n " $TELEGRAM_CHANNEL_ID " ] || { echo " no chat id for channel ' $channel ' " >& 2; exit 2; }
tmp = $( mktemp -d)
go build -o " $tmp /adapter " ./cmd/adapter
2026-10-08 10:20:09 +02:00
go build -o " $tmp /compose " ./cmd/compose
# The image, resolved before any credential is touched. A post's own
# request.visual_url wins; otherwise its source entry's rendered portrait;
# an entry with only a visual prompt is held until the portrait is rendered;
# a post with no prompt at all (an outlet post) carries the campaign image.
# Public posts always carry an image.
hall = ${ HALL_DIR :- $( realpath ../hall-of-helix) }
forge = ${ FORGE_RAW :- https : //forgejo.coulomb.social/coulomb }
python3 - " $post " " $channel " " $tmp " " $hall " " $forge " <<'PY' || { rm -rf " $tmp " ; exit 2; }
import json, os, subprocess, sys, yaml
post, channel, tmp, hall, forge = sys.argv[ 1:]
d = yaml.safe_load( open( post) )
req = d[ "request" ]
2026-10-08 10:33:31 +02:00
req[ "reviewed_by" ] = str( d[ "reviewed_by" ] ) .strip( ) # the file's sign-off is the review
2026-10-08 10:20:09 +02:00
if not req.get( "visual_url" ) :
cmd = [ f"{tmp}/compose" , "visual" , "--hall-dir" , hall,
"--hall-raw-base" , f"{forge}/hall-of-helix/raw/branch/main" ,
"--fallback" , f"{forge}/pr-hall-of-helix/raw/branch/main/assets/helix-forge.png" ]
if d.get( "source_entry_path" ) :
cmd += [ "--entry" , os.path.join( hall, d[ "source_entry_path" ] ) ]
v = json.loads( subprocess.run( cmd, check = True, capture_output = True, text = True) .stdout)
if v[ "kind" ] = = "needs-render" :
sys.exit( f"held: the source entry has a visual prompt but no rendered portrait "
f"(expected at {v.get('target') or 'an unnamed visuals/ file'}); render it into the hall first" )
req[ "visual_url" ] = v[ "visual_url" ]
print( f"image: {v['kind']} {v['visual_url']}" )
else :
print( f"image: post {req['visual_url']}" )
if channel = = "public" and not req.get( "visual_url" ) :
sys.exit( "public posts carry an image" )
json.dump( req, open( f"{tmp}/request.json" , "w" ) )
PY
2026-10-08 09:36:03 +02:00
2026-10-08 09:41:50 +02:00
# Read the KV v2 data path directly: `bao kv get` first queries
# sys/internal/ui/mounts, which a workload-scoped token may not be allowed to.
token = $( bao read -format= json \
platform/data/workloads/infotech/fluid-telegram/hall-of-helix/bot-token \
| python3 -c 'import sys, json; print(json.load(sys.stdin)["data"]["data"]["token"])' ) \
|| { rm -rf " $tmp " ; echo "could not read the bot token from OpenBao (logged in? read on platform/data/workloads/infotech/fluid-telegram/hall-of-helix/*?)" >& 2; exit 2; }
2026-10-08 09:36:03 +02:00
2026-10-08 20:55:32 +02:00
# Images are fetched by the adapter and uploaded (FT-WP-0001-T14), so the
# forge need not serve them publicly. FORGE_READ_TOKEN, when set, is a
# read-only forge identity for closed repositories.
2026-10-08 10:20:09 +02:00
TELEGRAM_BOT_TOKEN = $token TELEGRAM_CHANNEL_ID = $TELEGRAM_CHANNEL_ID ATTACH_VISUAL = true \
2026-10-08 20:55:32 +02:00
UPLOAD_VISUALS = true FORGE_READ_TOKEN = ${ FORGE_READ_TOKEN :- } \
2026-10-08 09:36:03 +02:00
ADAPTER_LISTEN_ADDR = 127.0.0.1:$port " $tmp /adapter " &
pid = $!
unset token
trap 'kill $pid 2>/dev/null; rm -rf "$tmp"' EXIT
for _ in $( seq 50) ; do
curl -s -o /dev/null " http://127.0.0.1: $port / " && break
sleep 0.1
done
2026-10-08 10:20:09 +02:00
curl -sS -w '\nHTTP %{http_code}\n' -X POST -H 'Content-Type: application/json' \
--data-binary @" $tmp /request.json " " http://127.0.0.1: $port /v1/channel-posts "