chore: ROADMAP Phase 8, parallel AI team script, docker and infra updates

- ROADMAP.md: add Phase 8 — Freifunk / Community Mesh Networking with
  F0-F8 checkboxes; F0-F2 marked complete
- scripts/ai_team.py: rewrite to support asyncio.gather parallel agent
  runs; add --sprint flag with predefined work packages (audit,
  phase1-hardening, phase2-tests, phase1-infra, status); add --parallel
  for ad-hoc concurrent agent invocations; output written to
  logs/ai_team/<sprint>_<timestamp>/<agent>.md
- scripts/dev-shell.sh: convenience development shell helper
- docker: update Dockerfiles for quicproquo rename and new server flags
- .gitignore: add qpq-state artifacts (*.bin, *.session, *.pending.ks,
  *.convdb*)
This commit is contained in:
2026-03-03 14:42:21 +01:00
parent d7e530435f
commit b6483dedbc
7 changed files with 1255 additions and 30 deletions

336
scripts/dev-shell.sh Executable file
View File

@@ -0,0 +1,336 @@
#!/usr/bin/env bash
# ── qpq Dev Shell ─────────────────────────────────────────────────────────────
#
# Builds qpq (if needed), starts a local server, registers Alice + Bob, then
# opens a tmux session with two side-by-side REPL panes and a server-log strip.
#
# Layout (window 0 — "chat"):
#
# ┌──────────[ ALICE user=alice pass=alice ]──┬──[ BOB user=bob pass=bob ]──┐
# │ │ │
# │ /dm bob ← start a DM here │ reply here │
# │ /create-group g ← or create a group │ /join ← to accept invite │
# │ │ │
# ├──────────[ SERVER LOG ]────────────────────┴──────────────────────────────┤
# │ live qpq-server stdout / stderr │
# └───────────────────────────────────────────────────────────────────────────┘
#
# Window 1 — "ref": full slash-command cheatsheet (read-only)
#
# Usage:
# ./scripts/dev-shell.sh build if needed, fresh session
# ./scripts/dev-shell.sh --rebuild force cargo build first
# ./scripts/dev-shell.sh --resume reuse existing state files + server data
# ./scripts/dev-shell.sh --help show this message
#
# Stop: Ctrl-C here OR tmux kill-session -t qpq-dev
# Ref: Ctrl-B 1 (inside tmux → cheatsheet window)
# Nav: Ctrl-B ←/→ (switch Alice ↔ Bob pane)
# Ctrl-B z (zoom current pane)
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
BIN_DIR="$PROJECT_ROOT/target/debug"
SESSION="qpq-dev"
SERVER_PORT=7000
SERVER_ADDR="127.0.0.1:$SERVER_PORT"
SERVER_NAME="localhost"
# All runtime state lives in /tmp so the project tree stays clean
RUN_DIR="/tmp/qpq-devshell"
DATA_DIR="$RUN_DIR/server-data" # server stores TLS cert + OPAQUE data here
CA_CERT="$DATA_DIR/server-cert.der"
LOG_FILE="$RUN_DIR/server.log"
QPQ="$BIN_DIR/qpq"
QPQS="$BIN_DIR/qpq-server"
# ── Colours ────────────────────────────────────────────────────────────────────
GRN='\033[0;32m'; CYN='\033[0;36m'; YLW='\033[1;33m'; RED='\033[0;31m'; NC='\033[0m'
step() { printf "\n${GRN}▶ %s${NC}\n" "$*"; }
info() { printf " ${CYN}%s${NC}\n" "$*"; }
warn() { printf " ${YLW}⚠ %s${NC}\n" "$*"; }
die() { printf "${RED}✗ %s${NC}\n" "$*" >&2; exit 1; }
# ── Parse flags ────────────────────────────────────────────────────────────────
REBUILD=false
RESUME=false
for arg in "$@"; do
case "$arg" in
-r|--rebuild) REBUILD=true ;;
--resume) RESUME=true ;;
-h|--help)
sed -n '2,/^[^#]/{ /^#/p }' "$0" | sed 's/^# \?//'
exit 0
;;
*) die "Unknown argument: $arg" ;;
esac
done
# ── Preflight ──────────────────────────────────────────────────────────────────
step "Checking requirements..."
for cmd in cargo tmux; do
command -v "$cmd" &>/dev/null || die "'$cmd' is required but not installed."
done
info "cargo $(cargo --version 2>&1 | head -1)"
info "tmux $(tmux -V)"
# ── Decide whether to clean state ─────────────────────────────────────────────
# By default we start fresh (server is always restarted, state must match).
# Pass --resume to reuse an existing consistent state from a previous run.
if $RESUME; then
info "Resume mode — keeping existing state in $RUN_DIR"
[[ -d "$RUN_DIR" ]] || die "--resume requires a previous dev-shell run (no $RUN_DIR)"
else
step "Cleaning previous run state..."
rm -rf "$RUN_DIR"
info "Cleared $RUN_DIR"
fi
mkdir -p "$RUN_DIR" "$DATA_DIR"
# ── Build ──────────────────────────────────────────────────────────────────────
if $REBUILD || [[ ! -x "$QPQ" ]] || [[ ! -x "$QPQS" ]]; then
step "Building workspace (cargo build)..."
cd "$PROJECT_ROOT"
cargo build
info "Build complete."
else
info "Using cached binaries in $BIN_DIR"
info "(pass --rebuild to recompile)"
fi
[[ -x "$QPQ" ]] || die "Client binary not found: $QPQ"
[[ -x "$QPQS" ]] || die "Server binary not found: $QPQS"
# ── Free the port ──────────────────────────────────────────────────────────────
step "Ensuring port $SERVER_PORT is free..."
SERVER_PID=""
free_port() {
if command -v fuser &>/dev/null; then
fuser -k "${SERVER_PORT}/tcp" 2>/dev/null || true
elif command -v lsof &>/dev/null; then
local pids
pids=$(lsof -ti "tcp:${SERVER_PORT}" 2>/dev/null || true)
[[ -n "$pids" ]] && kill $pids 2>/dev/null || true
fi
}
free_port
sleep 0.3
# ── Cleanup on exit ────────────────────────────────────────────────────────────
cleanup() {
printf "\n"
step "Shutting down..."
tmux kill-session -t "$SESSION" 2>/dev/null || true
if [[ -n "$SERVER_PID" ]] && kill -0 "$SERVER_PID" 2>/dev/null; then
info "Stopping qpq-server (PID $SERVER_PID)..."
kill "$SERVER_PID" 2>/dev/null || true
wait "$SERVER_PID" 2>/dev/null || true
fi
free_port
info "Done."
}
trap cleanup EXIT INT TERM
# ── Start server ───────────────────────────────────────────────────────────────
step "Starting qpq-server on $SERVER_ADDR..."
"$QPQS" \
--listen "$SERVER_ADDR" \
--data-dir "$DATA_DIR" \
--tls-cert "$DATA_DIR/server-cert.der" \
--tls-key "$DATA_DIR/server-key.der" \
--allow-insecure-auth \
>"$LOG_FILE" 2>&1 &
SERVER_PID=$!
info "PID $SERVER_PID log → $LOG_FILE"
# ── Wait for TLS cert (written by server on first boot) ────────────────────────
step "Waiting for server to initialise..."
for i in $(seq 1 20); do
if [[ -f "$CA_CERT" ]]; then
info "Server ready after ${i}s (cert present)."
break
fi
if ! kill -0 "$SERVER_PID" 2>/dev/null; then
warn "Server exited early. Last log output:"
tail -30 "$LOG_FILE" >&2
die "Server failed to start."
fi
sleep 1
if [[ $i -eq 20 ]]; then
tail -30 "$LOG_FILE" >&2
die "Server did not produce TLS cert within 20s."
fi
done
sleep 0.5 # brief pause for the QUIC listener to open
QPQ_GLOBAL=(--ca-cert "$CA_CERT" --server-name "$SERVER_NAME")
# ── Build REPL command strings ─────────────────────────────────────────────────
# Registration is handled automatically by the REPL on first launch:
# 1. load_or_init_state creates state file + identity key (if missing)
# 2. opaque_register sends the identity key → server binds username→identity_key
# 3. opaque_login returns a session token → cached for future runs
# Pre-registering here (without an identity key) would break /dm by preventing
# the identity key from ever being bound on a subsequent REPL launch.
repl_cmd() {
local user="$1" pass="$2"
echo "$QPQ ${QPQ_GLOBAL[*]} repl \
--state $RUN_DIR/${user}.bin \
--server $SERVER_ADDR \
--username $user \
--password $pass"
}
ALICE_CMD=$(repl_cmd alice alice)
BOB_CMD=$(repl_cmd bob bob)
# ── Build tmux session ─────────────────────────────────────────────────────────
step "Creating tmux session '$SESSION'..."
tmux kill-session -t "$SESSION" 2>/dev/null || true
# ─ Window 0 "chat" layout:
# top-left → Alice REPL
# top-right → Bob REPL
# bottom → server log (full width, 30% height)
#
# tmux 3.x renumbers pane indices after each split, so we capture pane IDs
# with -P -F '#{pane_id}' instead of relying on 0.0 / 0.1 / 0.2 arithmetic.
tmux new-session -d -s "$SESSION" -n "chat" -x 220 -y 55
# The initial pane is always %0 / pane 0 — that's Alice.
PANE_ALICE="${SESSION}:0.0"
# Split bottom strip for server log; capture the new pane's stable ID.
PANE_LOG=$(tmux split-window -v -t "$PANE_ALICE" -p 30 -P -F '#{pane_id}')
# Split top-right for Bob from Alice's pane; capture ID.
tmux select-pane -t "$PANE_ALICE"
PANE_BOB=$(tmux split-window -h -t "$PANE_ALICE" -P -F '#{pane_id}')
# Send commands to each pane by stable ID — immune to index renumbering.
tmux send-keys -t "$PANE_LOG" \
"printf '\\033[0;36m[server log]\\033[0m\\n' && tail -F '$LOG_FILE'" \
Enter
tmux send-keys -t "$PANE_BOB" \
"sleep 1.5 && $BOB_CMD" \
Enter
tmux send-keys -t "$PANE_ALICE" \
"sleep 0.8 && $ALICE_CMD" \
Enter
# Pane border labels (tmux ≥ 2.6)
tmux select-pane -t "$PANE_ALICE" -T " ✉ ALICE │ user=alice pass=alice "
tmux select-pane -t "$PANE_LOG" -T " ⚙ SERVER LOG "
tmux select-pane -t "$PANE_BOB" -T " ✉ BOB │ user=bob pass=bob "
tmux set-option -t "$SESSION" pane-border-status top 2>/dev/null || true
tmux set-option -t "$SESSION" \
pane-border-format \
"#{?pane_active,#[bold fg=colour226],#[fg=colour244]} #{pane_title} " \
2>/dev/null || true
# Focus Alice to start
tmux select-pane -t "$PANE_ALICE"
# ─ Window 1 "ref" — slash-command cheatsheet ──────────────────────────────────
tmux new-window -t "${SESSION}:1" -n "ref"
tmux send-keys -t "${SESSION}:1" "clear" Enter
# Heredoc piped through cat so it renders immediately and stays visible
tmux send-keys -t "${SESSION}:1" "cat << 'CHEAT'
╔══════════════════════════════════════════════════════════════════════════╗
║ qpq REPL ─ Slash Command Cheatsheet ║
╠══════════════════════════════════════════════════════════════════════════╣
║ GENERAL ║
║ /help show all commands in the REPL ║
║ /whoami identity key + hybrid key fingerprint ║
║ /quit /q /exit exit the REPL ║
║ ║
║ CONVERSATIONS ║
║ /list /ls list all open conversations ║
║ /switch @username make a DM the active conversation ║
║ /switch #groupname make a group the active conversation ║
║ /history [N] print last N messages (default: 20) ║
║ /members list all members of the current conv. ║
║ ║
║ DIRECT MESSAGES ║
║ /dm <username> open or create an encrypted 1:1 DM ║
║ ║
║ MLS GROUPS ║
║ /create-group <name> create a new MLS group (you are admin) ║
║ /cg <name> alias for /create-group ║
║ /invite <username> invite someone into the current group ║
║ /join accept a pending group Welcome message ║
║ /leave leave the currently active group ║
║ /remove <username> remove (kick) a member from the group ║
║ /kick <username> alias for /remove ║
║ ║
╠══════════════════════════════════════════════════════════════════════════╣
║ QUICK START — 1:1 DM TEST ║
║ ║
║ [Alice] /dm bob creates encrypted DM channel ║
║ [Bob] (Welcome arrives) background poller picks it up auto ║
║ [Alice] Hello Bob! send your first message ║
║ [Bob] Hey Alice! reply ║
║ [Alice] /history verify messages are stored ║
║ [Alice] /whoami check identity + hybrid key status ║
║ ║
║ QUICK START — GROUP CHAT TEST ║
║ ║
║ [Alice] /create-group devtest create an MLS group ║
║ [Alice] /invite bob send a Welcome to Bob ║
║ [Bob] /join accept the Welcome ║
║ [Alice] Hello everyone! send to group ║
║ [Bob] Hi Alice! reply in group ║
║ [Alice] /members verify both Alice + Bob listed ║
║ [Alice] /history 50 dump full message log ║
║ [Alice] /remove bob kick Bob (test admin ops) ║
║ [Bob] (removed from group) ║
║ ║
╠══════════════════════════════════════════════════════════════════════════╣
║ TMUX NAVIGATION ║
║ Ctrl-B 0 window 0 — chat panes (Alice / Bob / log) ║
║ Ctrl-B 1 window 1 — this cheatsheet ║
║ Ctrl-B ← → move between panes in the chat window ║
║ Ctrl-B z zoom current pane to fullscreen (toggle) ║
║ Ctrl-B [ scroll mode — use arrows / PgUp/PgDn (q exits) ║
║ Ctrl-B d detach (session stays alive in background) ║
║ ║
║ EXIT / STOP ║
║ Ctrl-B :kill-session Enter kill tmux + triggers script cleanup ║
║ tmux kill-session -t qpq-dev from any other terminal ║
║ /quit (in Alice or Bob pane) exit that REPL only ║
╚══════════════════════════════════════════════════════════════════════════╝
CHEAT" Enter
# Return focus to the chat window, Alice pane
tmux select-window -t "${SESSION}:0"
tmux select-pane -t "${SESSION}:0.0"
# ── Print startup summary ──────────────────────────────────────────────────────
printf "\n"
printf "${GRN}╔══════════════════════════════════════════════════════╗${NC}\n"
printf "${GRN}${NC} ${GRN}qpq dev shell — ready${NC} ${GRN}${NC}\n"
printf "${GRN}╠══════════════════════════════════════════════════════╣${NC}\n"
printf "${GRN}${NC} Session ${CYN}%s${NC}\n" "$SESSION ${GRN}${NC}"
printf "${GRN}${NC} Server ${CYN}%s${NC}\n" "$SERVER_ADDR (log → $LOG_FILE) ${GRN}${NC}"
printf "${GRN}${NC} Alice user=${CYN}alice${NC} pass=${CYN}alice${NC} ${GRN}${NC}\n"
printf "${GRN}${NC} Bob user=${CYN}bob${NC} pass=${CYN}bob${NC} ${GRN}${NC}\n"
printf "${GRN}${NC} ${GRN}${NC}\n"
printf "${GRN}${NC} Quick DM: ${CYN}[Alice pane]${NC} type: /dm bob ${GRN}${NC}\n"
printf "${GRN}${NC} Cheatsheet: Ctrl-B 1 (inside tmux) ${GRN}${NC}\n"
printf "${GRN}${NC} Exit: Ctrl-B :kill-session Enter ${GRN}${NC}\n"
printf "${GRN}${NC} or from another terminal: ${GRN}${NC}\n"
printf "${GRN}${NC} tmux kill-session -t qpq-dev ${GRN}${NC}\n"
printf "${GRN}╚══════════════════════════════════════════════════════╝${NC}\n"
printf "\n"
# ── Attach ─────────────────────────────────────────────────────────────────────
tmux attach-session -t "$SESSION"
step "Dev shell exited."