sx-gitea deploy: live serving for sx.sx-web.org
lib/gitea/serve.sx: durable live forge on the kernel persist store (SX_PERSIST_DIR) with idempotent seeding (instance id, admin user + rotating token, welcome repo), blocking in the native http-listen loop via host/native-handler — the same wiring that serves blog.rose-ash.com. lib/gitea/serve.sh: full-stack launcher (every substrate the eight phases compose, in dependency order, + dream/session for the cookie bridge) — container entrypoint and local launcher in one. docker-compose.dev-sx-gitea.yml: sx_docs image, bind-mounted worktree + binary, /root/sx-gitea-persist for durable state, externalnet so Caddy can proxy sx.sx-web.org. Serving JIT off until validated for this path. Smoke-tested locally: pages, authed API, markdown-rendered issues, pkt-line ref advertisement, 401 gating, and full state survival across a restart against the same persist dir. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
44
docker-compose.dev-sx-gitea.yml
Normal file
44
docker-compose.dev-sx-gitea.yml
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# sx-gitea live service — the federated git forge (lib/gitea) served by the
|
||||||
|
# native http-listen server via lib/gitea/serve.sh. Joins externalnet so Caddy
|
||||||
|
# can reverse_proxy sx.sx-web.org to it. Durable state on a host dir.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# sudo mkdir -p /root/sx-gitea-persist && sudo chown 10001:10001 /root/sx-gitea-persist
|
||||||
|
# docker compose -p sx-gitea -f docker-compose.dev-sx-gitea.yml up -d
|
||||||
|
# docker compose -p sx-gitea -f docker-compose.dev-sx-gitea.yml logs -f
|
||||||
|
# docker compose -p sx-gitea -f docker-compose.dev-sx-gitea.yml down
|
||||||
|
|
||||||
|
services:
|
||||||
|
sx_gitea:
|
||||||
|
image: registry.rose-ash.com:5000/sx_docs:latest
|
||||||
|
container_name: sx-gitea-1
|
||||||
|
entrypoint: ["bash", "/app/lib/gitea/serve.sh"]
|
||||||
|
working_dir: /app
|
||||||
|
environment:
|
||||||
|
SX_PROJECT_DIR: /app
|
||||||
|
SX_SERVER: /app/bin/sx_server
|
||||||
|
HOST_PORT: "8000"
|
||||||
|
# Bind all interfaces so Caddy (on externalnet) can reach it.
|
||||||
|
SX_HTTP_HOST: "0.0.0.0"
|
||||||
|
# Durable persist store root — repos/issues/PRs/tokens survive restarts.
|
||||||
|
SX_PERSIST_DIR: /data/persist
|
||||||
|
# Forge identity + admin. The token gates every mutation (repo create,
|
||||||
|
# push, issues, PRs) — rotate by editing here and recreating.
|
||||||
|
SX_INSTANCE: "sx.sx-web.org"
|
||||||
|
SX_GITEA_ADMIN: "giles"
|
||||||
|
SX_GITEA_TOKEN: "sxg-9f2e6c81a4d35b07"
|
||||||
|
OCAMLRUNPARAM: "b"
|
||||||
|
# Serving JIT stays OFF for the forge until validated under it.
|
||||||
|
volumes:
|
||||||
|
- ./spec:/app/spec:ro
|
||||||
|
- ./lib:/app/lib:ro
|
||||||
|
- ./hosts/ocaml/_build/default/bin/sx_server.exe:/app/bin/sx_server:ro
|
||||||
|
- /root/sx-gitea-persist:/data/persist
|
||||||
|
networks:
|
||||||
|
- externalnet
|
||||||
|
- default
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
networks:
|
||||||
|
externalnet:
|
||||||
|
external: true
|
||||||
177
lib/gitea/serve.sh
Normal file
177
lib/gitea/serve.sh
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# sx-gitea live server launcher. Loads the FULL forge stack (every substrate
|
||||||
|
# the eight phases compose: git, datalog/acl, dream, smalltalk/content,
|
||||||
|
# relations, scheme/flow, apl/feed, events, haskell/search, fed) into one
|
||||||
|
# sx_server process and calls (gitea/serve! PORT ...) — the native
|
||||||
|
# http-listen loop serving the dream app. Runs in the FOREGROUND, so this
|
||||||
|
# doubles as a container entrypoint and a local launcher.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# bash lib/gitea/serve.sh # serve on $HOST_PORT (default 8940)
|
||||||
|
# HOST_PORT=8941 bash lib/gitea/serve.sh
|
||||||
|
#
|
||||||
|
# Env:
|
||||||
|
# SX_INSTANCE instance id baked into AP actor ids (default sx.sx-web.org)
|
||||||
|
# SX_GITEA_ADMIN admin username (default giles)
|
||||||
|
# SX_GITEA_TOKEN admin bearer token (REQUIRED in production)
|
||||||
|
# SX_PERSIST_DIR durable store root (kernel persist ops)
|
||||||
|
# SX_HTTP_HOST bind address (0.0.0.0 in containers)
|
||||||
|
|
||||||
|
set -uo pipefail
|
||||||
|
cd "${SX_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || echo .)}"
|
||||||
|
|
||||||
|
SX_SERVER="${SX_SERVER:-hosts/ocaml/_build/default/bin/sx_server.exe}"
|
||||||
|
if [ ! -x "$SX_SERVER" ]; then
|
||||||
|
SX_SERVER="/root/rose-ash/hosts/ocaml/_build/default/bin/sx_server.exe"
|
||||||
|
fi
|
||||||
|
if [ ! -x "$SX_SERVER" ]; then
|
||||||
|
echo "ERROR: sx_server.exe not found." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
PORT="${HOST_PORT:-8940}"
|
||||||
|
INSTANCE="${SX_INSTANCE:-sx.sx-web.org}"
|
||||||
|
ADMIN="${SX_GITEA_ADMIN:-giles}"
|
||||||
|
TOKEN="${SX_GITEA_TOKEN:-dev-token-change-me}"
|
||||||
|
|
||||||
|
# The full stack, in dependency order — the same lists the conformance
|
||||||
|
# runner uses (base MODULES + every suite's extras), plus the host
|
||||||
|
# native-handler bridge and the serve boot module.
|
||||||
|
MODULES=(
|
||||||
|
# kernel + parser (sx-parse for the wire protocol)
|
||||||
|
"spec/stdlib.sx"
|
||||||
|
"spec/parser.sx"
|
||||||
|
"lib/r7rs.sx"
|
||||||
|
# persist (durable backend rides kernel persist/* ops)
|
||||||
|
"lib/persist/event.sx"
|
||||||
|
"lib/persist/backend.sx"
|
||||||
|
"lib/persist/log.sx"
|
||||||
|
"lib/persist/kv.sx"
|
||||||
|
"lib/persist/durable.sx"
|
||||||
|
# canonical form + sx-git
|
||||||
|
"lib/artdag/dag.sx"
|
||||||
|
"lib/git/object.sx"
|
||||||
|
"lib/git/ref.sx"
|
||||||
|
"lib/git/dag.sx"
|
||||||
|
"lib/git/worktree.sx"
|
||||||
|
"lib/git/diff.sx"
|
||||||
|
"lib/git/merge.sx"
|
||||||
|
"lib/git/porcelain.sx"
|
||||||
|
# datalog + acl (access control)
|
||||||
|
"lib/datalog/tokenizer.sx"
|
||||||
|
"lib/datalog/parser.sx"
|
||||||
|
"lib/datalog/unify.sx"
|
||||||
|
"lib/datalog/db.sx"
|
||||||
|
"lib/datalog/builtins.sx"
|
||||||
|
"lib/datalog/aggregates.sx"
|
||||||
|
"lib/datalog/strata.sx"
|
||||||
|
"lib/datalog/eval.sx"
|
||||||
|
"lib/datalog/api.sx"
|
||||||
|
"lib/datalog/magic.sx"
|
||||||
|
"lib/acl/schema.sx"
|
||||||
|
"lib/acl/facts.sx"
|
||||||
|
"lib/acl/engine.sx"
|
||||||
|
# dream (web)
|
||||||
|
"lib/dream/types.sx"
|
||||||
|
"lib/dream/router.sx"
|
||||||
|
"lib/dream/middleware.sx"
|
||||||
|
"lib/dream/error.sx"
|
||||||
|
"lib/dream/html.sx"
|
||||||
|
"lib/dream/json.sx"
|
||||||
|
"lib/dream/auth.sx"
|
||||||
|
"lib/dream/session.sx"
|
||||||
|
"lib/dream/api.sx"
|
||||||
|
# relations (issue graph)
|
||||||
|
"lib/relations/schema.sx"
|
||||||
|
"lib/relations/engine.sx"
|
||||||
|
"lib/relations/api.sx"
|
||||||
|
# smalltalk + content (markdown issue/PR bodies)
|
||||||
|
"lib/smalltalk/tokenizer.sx"
|
||||||
|
"lib/smalltalk/parser.sx"
|
||||||
|
"lib/guest/reflective/class-chain.sx"
|
||||||
|
"lib/smalltalk/runtime.sx"
|
||||||
|
"lib/guest/reflective/env.sx"
|
||||||
|
"lib/smalltalk/eval.sx"
|
||||||
|
"lib/content/block.sx"
|
||||||
|
"lib/content/doc.sx"
|
||||||
|
"lib/content/render.sx"
|
||||||
|
"lib/content/api.sx"
|
||||||
|
"lib/content/meta.sx"
|
||||||
|
"lib/content/text.sx"
|
||||||
|
"lib/content/section.sx"
|
||||||
|
"lib/content/table.sx"
|
||||||
|
"lib/content/markdown.sx"
|
||||||
|
"lib/content/md-import.sx"
|
||||||
|
# scheme + flow (PR lifecycle, notification delivery)
|
||||||
|
"lib/guest/lex.sx"
|
||||||
|
"lib/guest/reflective/quoting.sx"
|
||||||
|
"lib/scheme/parser.sx"
|
||||||
|
"lib/scheme/eval.sx"
|
||||||
|
"lib/scheme/runtime.sx"
|
||||||
|
"lib/flow/spec.sx"
|
||||||
|
"lib/flow/store.sx"
|
||||||
|
"lib/flow/remote.sx"
|
||||||
|
"lib/flow/host.sx"
|
||||||
|
"lib/flow/api.sx"
|
||||||
|
# apl + feed (timelines) + events notify
|
||||||
|
"lib/apl/runtime.sx"
|
||||||
|
"lib/feed/normalize.sx"
|
||||||
|
"lib/feed/stream.sx"
|
||||||
|
"lib/feed/api.sx"
|
||||||
|
"lib/feed/fanout.sx"
|
||||||
|
"lib/feed/dedupe.sx"
|
||||||
|
"lib/feed/aggregate.sx"
|
||||||
|
"lib/feed/rank.sx"
|
||||||
|
"lib/feed/acl.sx"
|
||||||
|
"lib/feed/mute.sx"
|
||||||
|
"lib/feed/page.sx"
|
||||||
|
"lib/feed/notify.sx"
|
||||||
|
"lib/feed/home.sx"
|
||||||
|
"lib/feed/fed.sx"
|
||||||
|
"lib/events/notify.sx"
|
||||||
|
# haskell + search
|
||||||
|
"lib/haskell/tokenizer.sx"
|
||||||
|
"lib/haskell/layout.sx"
|
||||||
|
"lib/haskell/parser.sx"
|
||||||
|
"lib/haskell/desugar.sx"
|
||||||
|
"lib/haskell/runtime.sx"
|
||||||
|
"lib/haskell/match.sx"
|
||||||
|
"lib/haskell/eval.sx"
|
||||||
|
"lib/haskell/map.sx"
|
||||||
|
"lib/haskell/set.sx"
|
||||||
|
"lib/haskell/testlib.sx"
|
||||||
|
"lib/search/tokenize.sx"
|
||||||
|
"lib/search/index.sx"
|
||||||
|
"lib/search/query.sx"
|
||||||
|
"lib/search/parse.sx"
|
||||||
|
"lib/search/rank.sx"
|
||||||
|
"lib/search/rankq.sx"
|
||||||
|
"lib/search/testlib.sx"
|
||||||
|
# the forge
|
||||||
|
"lib/gitea/repo.sx"
|
||||||
|
"lib/gitea/access.sx"
|
||||||
|
"lib/gitea/web.sx"
|
||||||
|
"lib/gitea/wire.sx"
|
||||||
|
"lib/gitea/issues.sx"
|
||||||
|
"lib/gitea/pr.sx"
|
||||||
|
"lib/gitea/activity.sx"
|
||||||
|
"lib/gitea/search.sx"
|
||||||
|
"lib/gitea/fed.sx"
|
||||||
|
# native http bridge + serve boot
|
||||||
|
"lib/host/server.sx"
|
||||||
|
"lib/gitea/serve.sx"
|
||||||
|
)
|
||||||
|
|
||||||
|
EPOCH=1
|
||||||
|
{
|
||||||
|
for M in "${MODULES[@]}"; do
|
||||||
|
echo "(epoch $EPOCH)"; echo "(load \"$M\")"; EPOCH=$((EPOCH+1))
|
||||||
|
done
|
||||||
|
# content classes must exist before the first issue/PR page renders
|
||||||
|
echo "(epoch $EPOCH)"
|
||||||
|
echo "(eval \"(begin (st-bootstrap-classes!) (content/bootstrap!) (content-bootstrap-markdown!) (content-bootstrap-table!))\")"
|
||||||
|
EPOCH=$((EPOCH+1))
|
||||||
|
# boot the durable forge + block in the native server loop
|
||||||
|
echo "(epoch $EPOCH)"
|
||||||
|
echo "(eval \"(gitea/serve! $PORT \\\"$INSTANCE\\\" \\\"$ADMIN\\\" \\\"$TOKEN\\\")\")"
|
||||||
|
} | exec "$SX_SERVER"
|
||||||
61
lib/gitea/serve.sx
Normal file
61
lib/gitea/serve.sx
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
; lib/gitea/serve.sx — live-serving boot for sx-gitea.
|
||||||
|
;
|
||||||
|
; The live forge runs on the kernel's DURABLE persist backend (perform ->
|
||||||
|
; sx_persist_store under $SX_PERSIST_DIR), so repos, issues, PRs, tokens
|
||||||
|
; and activity all survive restarts. Seeding is idempotent: the instance
|
||||||
|
; id and admin token are (re)written each boot (rotating the env rotates
|
||||||
|
; the token), the admin user and welcome repo create-only.
|
||||||
|
;
|
||||||
|
; gitea/serve! blocks inside the kernel http-listen loop, bridging the
|
||||||
|
; dream app through host/native-handler — the exact wiring that serves
|
||||||
|
; blog.rose-ash.com.
|
||||||
|
;
|
||||||
|
; Requires: the full gitea stack (see lib/gitea/serve.sh) plus
|
||||||
|
; lib/host/server.sx (host/native-handler).
|
||||||
|
|
||||||
|
(define gitea/live-forge-cache false)
|
||||||
|
|
||||||
|
(define
|
||||||
|
gitea/live-forge
|
||||||
|
(fn
|
||||||
|
()
|
||||||
|
(begin
|
||||||
|
(if
|
||||||
|
gitea/live-forge-cache
|
||||||
|
nil
|
||||||
|
(set! gitea/live-forge-cache (gitea/forge (persist/durable-backend))))
|
||||||
|
gitea/live-forge-cache)))
|
||||||
|
|
||||||
|
(define
|
||||||
|
gitea/welcome-md
|
||||||
|
"# sx-gitea\n\nA federated git forge written entirely in SX — repos, issues, pull\nrequests, activity, search and ForgeFed federation composed from the\nx-on-sx subsystems (sx-git, acl, content, relations, flow, feed,\nevents, search) on the OCaml kernel. Zero third-party dependencies.\n\nBrowse: `/<owner>/<repo>`, `/issues`, `/pulls`, `/activity`,\n`/<owner>/<repo>/search?q=...`\n\nClone over the native smart-HTTP wire: `GET /<owner>/<repo>/info/refs`.\n")
|
||||||
|
|
||||||
|
(define
|
||||||
|
gitea/seed!
|
||||||
|
(fn
|
||||||
|
(forge instance admin token)
|
||||||
|
(begin
|
||||||
|
(gitea/instance! forge instance)
|
||||||
|
(gitea/user-create! forge admin)
|
||||||
|
(gitea/token-create! forge admin token)
|
||||||
|
(let
|
||||||
|
((res (gitea/repo-create! forge admin "welcome" {:description "Welcome to sx-gitea — a federated git forge in plain SX"})))
|
||||||
|
(if
|
||||||
|
(get res :conflict)
|
||||||
|
nil
|
||||||
|
(let
|
||||||
|
((grepo (gitea/repo-git forge admin "welcome")))
|
||||||
|
(begin
|
||||||
|
(git/add! grepo "README.md" gitea/welcome-md)
|
||||||
|
(git/commit! grepo {:message "welcome" :time 0 :author admin})
|
||||||
|
nil))))
|
||||||
|
forge)))
|
||||||
|
|
||||||
|
; blocks: the kernel http-listen loop serves the forge
|
||||||
|
(define
|
||||||
|
gitea/serve!
|
||||||
|
(fn
|
||||||
|
(port instance admin token)
|
||||||
|
(let
|
||||||
|
((forge (gitea/seed! (gitea/live-forge) instance admin token)))
|
||||||
|
(http-listen port (host/native-handler (gitea/app forge))))))
|
||||||
Reference in New Issue
Block a user