Compare commits

...

10 Commits

Author SHA1 Message Date
Virgil User
9d9892a4a4 test update 2026-06-08 10:50:01 -07:00
virgil
aedd24ba18 Avoid macOS keychain in Section 0 helper 2026-06-08 10:06:07 -07:00
virgil
ef463785da Add explicit Section 0 auth finish command 2026-06-08 09:54:21 -07:00
virgil
2eeb236214 Make Section 0 auth login polling visible 2026-06-08 09:50:13 -07:00
virgil
75479f6a40 Add Authentik Git login helper 2026-06-08 09:26:37 -07:00
virgil
e1020f0ec7 Separate collaborator docs from operator refresh 2026-06-08 08:57:34 -07:00
virgil
f8b09eade7 Clarify Section 0 setup language 2026-06-07 21:00:57 -07:00
virgil
eddb974aaf Harden Section 0 contributor setup 2026-06-07 19:43:27 -07:00
virgil
f7426cf3a6 Rename projection repo for Section 0 2026-06-07 19:21:24 -07:00
virgil
cf7f4d95fc Improve collaborator setup UX 2026-06-07 17:31:08 -07:00
5 changed files with 451 additions and 136 deletions

115
README.md
View File

@@ -1,17 +1,23 @@
# Mock RTA Knowledge Base # Section 0 Shared Docs -- READ.md
This hosted Git repo is intentionally plain Markdown. It has no required RTA These documents are available in two ways:
sidecars. Collaborators clone, edit, commit, and push it as the canonical
documentation source. The projection operator reads a separate projector 1. Read them in AFFiNE.
checkout and mirrors the Markdown into AFFiNE as read-only docs. AFFiNE is the shared reading and whiteboard space. The Markdown docs from
this repo appear there as read-only copies.
2. Edit them in Git.
The editable files live in this plain Markdown repo. Use your normal editor,
then commit and push changes.
If you want to change one of these docs, edit the Markdown file. Do not edit
the AFFiNE copy; it will be refreshed from Git.
## What To Edit ## What To Edit
Edit Markdown files in this Git repo. Do not edit the projected AFFiNE copies Edit Markdown files in this Git repo.
directly; AFFiNE is the shared reading and canvas surface, not the source of
truth for these files.
Current projected docs: Current docs:
- `README.md` - `README.md`
- `rta/concept.md` - `rta/concept.md`
@@ -19,47 +25,104 @@ Current projected docs:
## First-Time Setup ## First-Time Setup
From any machine that can reach the lab mesh: From any machine that can reach the private lab network:
```sh ```sh
curl -fsSL http://100.64.0.1:30087/virgil-admin/rta-mock-docs/raw/branch/main/scripts/setup-rta-mock-docs.sh | sh curl -fsSL http://100.64.0.1:30087/section0/rta-handbook/raw/branch/main/scripts/setup-section0-docs.sh | sh
``` ```
The script clones this repo into: The script clones this repo into:
```text ```text
~/Developer/Section0/rta-mock-docs ~/Developer/Section0/rta-handbook
```
It also installs a small helper command at:
```text
~/.local/bin/section0-docs
``` ```
Override the destination if you prefer: Override the destination if you prefer:
```sh ```sh
TARGET_DIR=~/work/rta-mock-docs \ TARGET_DIR=~/work/rta-handbook \
curl -fsSL http://100.64.0.1:30087/virgil-admin/rta-mock-docs/raw/branch/main/scripts/setup-rta-mock-docs.sh | sh curl -fsSL http://100.64.0.1:30087/section0/rta-handbook/raw/branch/main/scripts/setup-section0-docs.sh | sh
``` ```
## Daily Flow ## Daily Flow
```sh ```sh
cd ~/Developer/Section0/rta-mock-docs section0-docs auth login
git pull --ff-only section0-docs doctor
section0-docs pull
# Edit Markdown files in your normal editor. # Edit Markdown files in your normal editor.
git status section0-docs status
git add path/to/file.md MESSAGE="Describe the doc change" section0-docs commit
git commit -m "Describe the doc change" section0-docs push
git push git rev-parse --short HEAD
``` ```
After a push, ask the projection operator to pull and project. For now that is After a push, send Virgil the short commit hash. AFFiNE refresh is handled by
manual. The current operator command lives in the lab operator side; collaborators do not need the home-lab repo.
`docs/projection-operations.md`.
## Contributor Setup
Anonymous clone/read works on the private lab network. Pushing requires
Authentik login through the helper.
Run this once per checkout:
```sh
section0-docs auth login
```
The login opens Authentik in your browser, configures the Git author from your
identity, and installs the repo Git credential. After that, normal Git tooling
works too.
Prove write access without changing `main`:
```sh
section0-docs push-test
```
`push-test` does not commit files and does not change `main`. It pushes the
current commit to a temporary scratch branch, then deletes that branch
immediately. If Git prompts for a password, use a Gitea access token rather
than your normal account password. If login is healthy, Git should usually get
the credential from your local credential store.
## End-To-End Smoke Test
Use this when setting up a new machine:
```sh
section0-docs pull
cd "$(section0-docs open)"
printf '\nMBP sync smoke test: %s\n' "$(date -Is)" >> rta/reports/current-status.md
section0-docs status
MESSAGE="MBP sync smoke test" section0-docs commit
section0-docs push
git rev-parse --short HEAD
```
Send the printed commit hash to Virgil. After the AFFiNE copies are refreshed,
the smoke line should appear in:
```text
Agent Workspace / Section 0 / Git Projections / RTA Handbook / RTA Status
```
## Source Models ## Source Models
This repo is the Git-backed model: shared docs are canonical in Gitea. This repo is the shared Git model: shared docs are edited in Git, then copied
into AFFiNE for reading.
Personal vault mounts are different: a person keeps custody of their own local Personal vault mounts are different: a person keeps custody of their own local
Obsidian vault and only projects selected Markdown into AFFiNE. See Obsidian vault and only shares selected Markdown into AFFiNE. See
`docs/source-models.md`. `docs/source-models.md` for that more advanced case.

View File

@@ -1,68 +0,0 @@
# Projection Operations
This repo is projected into AFFiNE manually for now.
Canonical Gitea repo:
```text
http://100.64.0.1:30087/virgil-admin/rta-mock-docs.git
```
Projected AFFiNE path:
```text
Agent Workspace / projected-markdown / mock-rta-docs
```
## What A Collaborator Does
```sh
cd ~/Developer/Section0/rta-mock-docs
git pull --ff-only
# edit Markdown
git add .
git commit -m "Update docs"
git push
```
## What The Projection Operator Does
The operator keeps a separate checkout so projection never depends on a
collaborator's dirty working tree.
```sh
git -C /Users/virgil/Developer/rta/tmp/markdown-projection-gitea/projector-checkout/rta-mock-docs pull --ff-only
```
Then from `home-lab-v7`:
```sh
cd /Users/virgil/Developer/Virgil-Info/home-lab-v7
nix develop --command bash -lc 'scripts/ops/sync-obsidian-affine.rb \
--name rta-mock-docs \
--source /Users/virgil/Developer/rta/tmp/markdown-projection-gitea/projector-checkout/rta-mock-docs \
--username projection-bot \
--authentik-sub rta-projection-bot \
--affine-workspace "Agent Workspace" \
--affine-workspace-id 53ea0a0b-eca7-4887-8e31-f5b2a8ab7744 \
--affine-user-id ce42f50a-5367-4466-920b-7422c4e27de0 \
--affine-namespace projected-markdown/mock-rta-docs \
--include "**/*.md" \
--apply'
```
Expected healthy result:
```text
docs: 3
mark stale: 0
mode: one-way read-only AFFiNE docs
```
## Rule Of Thumb
If the source repo changes, pull the projector checkout and run projection.
If AFFiNE changes, treat it as a comment or sketch. Move durable edits back to
Markdown before projecting again.

View File

@@ -1,6 +1,6 @@
# RTA Status # RTA Status
This is still a demo status, not an exhaustive status report. This is a concise operator status, not an exhaustive status report.
The current mock says RTA has a working Markdown projection slice with hosted Git as the source authority. The current handbook records that RTA has a working Markdown projection slice with hosted Git as the source authority.
Live AFFiNE writing remains the next adapter step. Live AFFiNE writing remains the next adapter step.
The rename demonstrates that Git history can preserve projection identity. The rename demonstrates that Git history can preserve projection identity.

View File

@@ -1,40 +0,0 @@
#!/usr/bin/env sh
set -eu
REPO_URL="${REPO_URL:-http://100.64.0.1:30087/virgil-admin/rta-mock-docs.git}"
TARGET_DIR="${TARGET_DIR:-$HOME/Developer/Section0/rta-mock-docs}"
if ! command -v git >/dev/null 2>&1; then
echo "git is required but was not found on PATH" >&2
exit 1
fi
if [ -d "$TARGET_DIR/.git" ]; then
echo "Repo already exists: $TARGET_DIR"
git -C "$TARGET_DIR" remote set-url origin "$REPO_URL"
git -C "$TARGET_DIR" fetch origin
git -C "$TARGET_DIR" checkout main
git -C "$TARGET_DIR" pull --ff-only
else
mkdir -p "$(dirname "$TARGET_DIR")"
git clone "$REPO_URL" "$TARGET_DIR"
fi
git -C "$TARGET_DIR" config pull.ff only
cat <<EOF
Ready.
Repo: $TARGET_DIR
Remote: $REPO_URL
Daily flow:
cd "$TARGET_DIR"
git pull --ff-only
# edit Markdown
git add .
git commit -m "Update docs"
git push
AFFiNE updates after the projection operator pulls and runs projection.
EOF

360
scripts/setup-section0-docs.sh Executable file
View File

@@ -0,0 +1,360 @@
#!/usr/bin/env sh
set -eu
REPO_URL="${REPO_URL:-http://100.64.0.1:30087/section0/rta-handbook.git}"
TARGET_DIR="${TARGET_DIR:-$HOME/Developer/Section0/rta-handbook}"
COMMAND_DIR="${COMMAND_DIR:-$HOME/.local/bin}"
COMMAND_NAME="${COMMAND_NAME:-section0-docs}"
COMMAND_PATH="$COMMAND_DIR/$COMMAND_NAME"
SERVER_URL="${SECTION0_SERVER_URL:-https://ops.virgil.info/md-to-section0-api}"
step() {
printf "\n==> %s\n" "$*"
}
if ! command -v git >/dev/null 2>&1; then
echo "git is required but was not found on PATH" >&2
exit 1
fi
step "Preparing local Markdown workspace"
if [ -d "$TARGET_DIR/.git" ]; then
echo "Repo already exists: $TARGET_DIR"
git -C "$TARGET_DIR" remote set-url origin "$REPO_URL"
git -C "$TARGET_DIR" fetch origin
git -C "$TARGET_DIR" checkout main
git -C "$TARGET_DIR" pull --ff-only
else
mkdir -p "$(dirname "$TARGET_DIR")"
git clone "$REPO_URL" "$TARGET_DIR"
fi
git -C "$TARGET_DIR" config pull.ff only
step "Installing helper command"
mkdir -p "$COMMAND_DIR"
cat > "$COMMAND_PATH" <<EOF
#!/usr/bin/env sh
set -eu
REPO_DIR="$TARGET_DIR"
SERVER_URL="$SERVER_URL"
SESSION_DIR="\${SECTION0_SESSION_DIR:-\$HOME/.config/section0-docs}"
SESSION_PATH="\$SESSION_DIR/session.json"
usage() {
cat <<USAGE
section0-docs - helper for Section 0 shared Markdown docs
Commands:
auth login open Authentik and install Git credentials
auth finish finish login from a printed device code
auth status show current saved login
configure set Git author name/email for this repo
doctor check clone, author, remote, and read access
open print the repo path
pull pull latest Markdown with --ff-only
status show Git status
commit commit all current changes with MESSAGE
push push current branch
push-test prove write access with a temporary remote branch
help show this help
Examples:
section0-docs auth login
section0-docs auth finish DEVICE_CODE
section0-docs auth status
section0-docs doctor
section0-docs configure
section0-docs pull
section0-docs status
section0-docs push-test
MESSAGE="Update concept notes" section0-docs commit
section0-docs push
AFFiNE is read-only for these docs. Make lasting changes in Markdown; the
AFFiNE copies are refreshed from Git.
USAGE
}
need_python() {
command -v python3 >/dev/null 2>&1 || {
echo "python3 is required for Authentik login JSON handling" >&2
exit 1
}
}
json_get() {
need_python
python3 -c 'import json,sys; data=json.load(sys.stdin); cur=data
for part in sys.argv[1].split("."):
cur = cur.get(part, "") if isinstance(cur, dict) else ""
print(cur if cur is not None else "")' "\$1"
}
open_url() {
if command -v open >/dev/null 2>&1; then
open "\$1" >/dev/null 2>&1 || true
elif command -v xdg-open >/dev/null 2>&1; then
xdg-open "\$1" >/dev/null 2>&1 || true
fi
}
credential_host() {
need_python
python3 -c 'from urllib.parse import urlparse; import sys
url=urlparse(sys.argv[1])
print(url.netloc)' "\$1"
}
http_json() {
curl --connect-timeout 5 --max-time 15 -fsS "\$@"
}
install_git_credential() {
remote="\$1"
username="\$2"
password="\$3"
host="\$(credential_host "\$remote")"
protocol="\$(printf "%s" "\$remote" | sed -n "s#^\\([^:/]*\\)://.*#\\1#p")"
[ -n "\$protocol" ] || protocol="http"
mkdir -p "\$SESSION_DIR"
credential_path="\$SESSION_DIR/git-credentials"
touch "\$credential_path"
chmod 600 "\$credential_path"
git -C "\$REPO_DIR" config --local --replace-all credential.helper ""
git -C "\$REPO_DIR" config --local --add credential.helper "store --file=\$credential_path"
printf "protocol=%s\nhost=%s\nusername=%s\npassword=%s\n\n" "\$protocol" "\$host" "\$username" "\$password" \
| git -C "\$REPO_DIR" credential approve
chmod 600 "\$credential_path"
}
auth_login() {
need_python
mkdir -p "\$SESSION_DIR"
device_json="\$(curl -fsS -X POST "\$SERVER_URL/device")"
code="\$(printf "%s" "\$device_json" | json_get code)"
auth_url="\$(printf "%s" "\$device_json" | json_get authUrl)"
[ -n "\$code" ] && [ -n "\$auth_url" ] || {
echo "login server did not return a device code" >&2
exit 1
}
echo "Opening Authentik login:"
echo " \$auth_url"
echo "Device code:"
echo " \$code"
echo "Token check:"
echo " \$SERVER_URL/device/\$code/token"
open_url "\$auth_url"
if [ "\${SECTION0_AUTH_POLL:-}" != "1" ]; then
echo "When the browser says login succeeded, return here and press Enter."
printf "Press Enter to finish login: "
IFS= read -r _section0_continue
auth_finish "\$code"
return
fi
echo "Waiting for login..."
token_json=""
i=0
while [ "\$i" -lt 90 ]; do
token_json="\$(http_json "\$SERVER_URL/device/\$code/token" 2>/dev/null || true)"
access_token="\$(printf "%s" "\${token_json:-{}}" | json_get accessToken 2>/dev/null || true)"
[ -n "\$access_token" ] && break
status="\$(printf "%s" "\${token_json:-{}}" | json_get status 2>/dev/null || true)"
if [ \$((i % 5)) -eq 0 ]; then
printf "\n still waiting%s\n" "\${status:+ (\$status)}"
else
printf "."
fi
i=\$((i + 1))
sleep 2
done
printf "\n"
[ -n "\${access_token:-}" ] || {
echo "timed out waiting for Authentik login" >&2
echo "If the browser says login succeeded, run:" >&2
echo " section0-docs auth finish \$code" >&2
exit 1
}
complete_login "\$token_json" "\$access_token"
}
auth_finish() {
need_python
mkdir -p "\$SESSION_DIR"
code="\${1:-}"
[ -n "\$code" ] || {
echo "usage: section0-docs auth finish DEVICE_CODE" >&2
exit 1
}
token_json="\$(http_json "\$SERVER_URL/device/\$code/token")"
access_token="\$(printf "%s" "\$token_json" | json_get accessToken 2>/dev/null || true)"
[ -n "\$access_token" ] || {
echo "No completed login token for code: \$code" >&2
printf "%s\n" "\$token_json" >&2
exit 1
}
complete_login "\$token_json" "\$access_token"
}
complete_login() {
token_json="\$1"
access_token="\$2"
echo "Login accepted; requesting Section 0 Git access..."
access_json="\$(http_json -X POST -H "Authorization: Bearer \$access_token" "\$SERVER_URL/section0/git/access")"
ok="\$(printf "%s" "\$access_json" | json_get ok)"
[ "\$ok" = "True" ] || [ "\$ok" = "true" ] || {
echo "Git access broker did not return credentials:" >&2
printf "%s\n" "\$access_json" >&2
exit 1
}
remote="\$(printf "%s" "\$access_json" | json_get remote)"
git_username="\$(printf "%s" "\$access_json" | json_get git.username)"
git_password="\$(printf "%s" "\$access_json" | json_get git.password)"
author_name="\$(printf "%s" "\$access_json" | json_get author.name)"
author_email="\$(printf "%s" "\$access_json" | json_get author.email)"
[ -n "\$remote" ] && [ -n "\$git_username" ] && [ -n "\$git_password" ] || {
echo "Git access broker response was incomplete" >&2
exit 1
}
git -C "\$REPO_DIR" remote set-url origin "\$remote"
git -C "\$REPO_DIR" config user.name "\$author_name"
git -C "\$REPO_DIR" config user.email "\$author_email"
echo "Storing Git credential..."
install_git_credential "\$remote" "\$git_username" "\$git_password"
python3 -c 'import json,sys
token=json.loads(sys.argv[1])
access=json.loads(sys.argv[2])
print(json.dumps({
"authenticated": True,
"serverUrl": sys.argv[3],
"authentik": token.get("authentik", {}),
"author": access.get("author", {}),
"remote": access.get("remote", ""),
"credentialUser": access.get("git", {}).get("username", "")
}, indent=2))' "\$token_json" "\$access_json" "\$SERVER_URL" > "\$SESSION_PATH"
chmod 600 "\$SESSION_PATH"
echo "Authenticated: \$author_email"
echo "Git remote: \$remote"
echo "Git user: \$git_username"
echo "Session: \$SESSION_PATH"
}
case "\${1:-help}" in
auth)
case "\${2:-}" in
login)
auth_login
;;
finish)
auth_finish "\${3:-}"
;;
status)
if [ -f "\$SESSION_PATH" ]; then
cat "\$SESSION_PATH"
else
echo "not authenticated; run: section0-docs auth login" >&2
exit 1
fi
;;
*)
echo "usage: section0-docs auth <login|status>" >&2
exit 1
;;
esac
;;
configure)
current_name="\$(git -C "\$REPO_DIR" config user.name || true)"
current_email="\$(git -C "\$REPO_DIR" config user.email || true)"
printf "Git author name [%s]: " "\$current_name"
IFS= read -r author_name
printf "Git author email [%s]: " "\$current_email"
IFS= read -r author_email
if [ -n "\$author_name" ]; then
git -C "\$REPO_DIR" config user.name "\$author_name"
elif [ -z "\$current_name" ]; then
echo "Git author name is required" >&2
exit 1
fi
if [ -n "\$author_email" ]; then
git -C "\$REPO_DIR" config user.email "\$author_email"
elif [ -z "\$current_email" ]; then
echo "Git author email is required" >&2
exit 1
fi
;;
doctor)
remote="\$(git -C "\$REPO_DIR" remote get-url origin)"
branch="\$(git -C "\$REPO_DIR" branch --show-current)"
author_name="\$(git -C "\$REPO_DIR" config user.name || true)"
author_email="\$(git -C "\$REPO_DIR" config user.email || true)"
echo "Repo: \$REPO_DIR"
echo "Remote: \$remote"
echo "Branch: \$branch"
if [ -n "\$author_name" ] && [ -n "\$author_email" ]; then
echo "Git author: \$author_name <\$author_email>"
else
echo "Git author: missing; run section0-docs configure"
fi
git -C "\$REPO_DIR" ls-remote --heads origin main >/dev/null
echo "Read access: ok"
;;
open)
printf "%s\n" "\$REPO_DIR"
;;
pull)
git -C "\$REPO_DIR" pull --ff-only
;;
status)
git -C "\$REPO_DIR" status --short --branch
;;
commit)
: "\${MESSAGE:?Set MESSAGE before running commit}"
git -C "\$REPO_DIR" add .
git -C "\$REPO_DIR" commit -m "\$MESSAGE"
;;
push)
git -C "\$REPO_DIR" push
;;
push-test)
branch="section0-smoke-\${USER:-user}-\$(date +%Y%m%d%H%M%S)"
git -C "\$REPO_DIR" push origin HEAD:refs/heads/"\$branch"
git -C "\$REPO_DIR" push origin :refs/heads/"\$branch"
echo "Write access: ok"
;;
help|--help|-h)
usage
;;
*)
usage >&2
exit 1
;;
esac
EOF
chmod +x "$COMMAND_PATH"
cat <<EOF
Ready.
Repo: $TARGET_DIR
Remote: $REPO_URL
Helper: $COMMAND_PATH
Daily flow:
$COMMAND_NAME auth login
$COMMAND_NAME doctor
$COMMAND_NAME pull
# edit Markdown
$COMMAND_NAME status
MESSAGE="Update docs" $COMMAND_NAME commit
$COMMAND_NAME push
Contributor setup:
$COMMAND_NAME configure
$COMMAND_NAME push-test
AFFiNE updates after the Markdown docs are refreshed from Git.
If $COMMAND_DIR is not on PATH, add this to your shell profile:
export PATH="$COMMAND_DIR:\$PATH"
EOF