Compare commits
2 commits
master
...
build-serv
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f89c119181 | ||
|
|
0a10ca5e2a |
5 changed files with 194 additions and 8 deletions
126
infra/build-server/forgejo.sh
Normal file
126
infra/build-server/forgejo.sh
Normal file
|
|
@ -0,0 +1,126 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# The git host (main, 7 October 2026 20:29 BST; GitHub suspended): Forgejo 9 under docker on build-1, data under /srv/git (config
|
||||||
|
# app.ini written once with generated secrets, kept on the box only), ssh on 2222 public, http 3000 on loopback behind Caddy at
|
||||||
|
# git.igneum.network (ACME), deSEC A record by hand (dns.sh pattern). Then, by hand through the API as the owner user igneum-labs
|
||||||
|
# (credentials in ~/.config/igneum/forgejo-admin on the Mac): org igneum-network, private repos igneum and spec, the Mac key on the
|
||||||
|
# owner; registration off, anonymous read off, push-to-create off. Nightly dump to build-2 by /usr/local/bin/forgejo-backup.sh
|
||||||
|
# (forgejo-backup.timer 03:30 UTC). Runs as root on the box: scp it over and run it. Idempotent.
|
||||||
|
set -euo pipefail
|
||||||
|
mkdir -p /srv/git/data /srv/git/config
|
||||||
|
chown -R 1000:1000 /srv/git
|
||||||
|
if [ ! -s /srv/git/config/app.ini ]; then
|
||||||
|
SECRET=$(head -c 48 /dev/urandom | base64 | tr -d '/+=' | head -c 48)
|
||||||
|
JWT=$(head -c 48 /dev/urandom | base64 | tr -d '/+=' | head -c 48)
|
||||||
|
LFSJWT=$(head -c 48 /dev/urandom | base64 | tr -d '/+=' | head -c 48)
|
||||||
|
cat > /srv/git/config/app.ini <<INI
|
||||||
|
APP_NAME = Igneum
|
||||||
|
RUN_MODE = prod
|
||||||
|
RUN_USER = git
|
||||||
|
WORK_PATH = /data/gitea
|
||||||
|
|
||||||
|
[server]
|
||||||
|
PROTOCOL = http
|
||||||
|
DOMAIN = git.igneum.network
|
||||||
|
ROOT_URL = https://git.igneum.network/
|
||||||
|
HTTP_ADDR = 0.0.0.0
|
||||||
|
HTTP_PORT = 3000
|
||||||
|
SSH_DOMAIN = git.igneum.network
|
||||||
|
SSH_PORT = 2222
|
||||||
|
SSH_LISTEN_PORT = 22
|
||||||
|
START_SSH_SERVER = false
|
||||||
|
DISABLE_SSH = false
|
||||||
|
LFS_START_SERVER = true
|
||||||
|
LFS_JWT_SECRET = $LFSJWT
|
||||||
|
OFFLINE_MODE = true
|
||||||
|
LANDING_PAGE = login
|
||||||
|
|
||||||
|
[database]
|
||||||
|
DB_TYPE = sqlite3
|
||||||
|
PATH = /data/gitea/forgejo.db
|
||||||
|
|
||||||
|
[repository]
|
||||||
|
ROOT = /data/git/repositories
|
||||||
|
DEFAULT_PRIVATE = private
|
||||||
|
FORCE_PRIVATE = true
|
||||||
|
ENABLE_PUSH_CREATE_USER = false
|
||||||
|
ENABLE_PUSH_CREATE_ORG = false
|
||||||
|
DEFAULT_BRANCH = master
|
||||||
|
|
||||||
|
[service]
|
||||||
|
DISABLE_REGISTRATION = true
|
||||||
|
REQUIRE_SIGNIN_VIEW = true
|
||||||
|
ENABLE_NOTIFY_MAIL = false
|
||||||
|
DEFAULT_KEEP_EMAIL_PRIVATE = true
|
||||||
|
DEFAULT_ALLOW_CREATE_ORGANIZATION = false
|
||||||
|
|
||||||
|
[security]
|
||||||
|
INSTALL_LOCK = true
|
||||||
|
SECRET_KEY = $SECRET
|
||||||
|
INTERNAL_TOKEN = $JWT
|
||||||
|
PASSWORD_HASH_ALGO = argon2
|
||||||
|
MIN_PASSWORD_LENGTH = 16
|
||||||
|
|
||||||
|
[oauth2]
|
||||||
|
ENABLED = false
|
||||||
|
|
||||||
|
[openid]
|
||||||
|
ENABLE_OPENID_SIGNIN = false
|
||||||
|
ENABLE_OPENID_SIGNUP = false
|
||||||
|
|
||||||
|
[mailer]
|
||||||
|
ENABLED = false
|
||||||
|
|
||||||
|
[session]
|
||||||
|
PROVIDER = file
|
||||||
|
COOKIE_SECURE = true
|
||||||
|
|
||||||
|
[log]
|
||||||
|
MODE = file
|
||||||
|
LEVEL = Info
|
||||||
|
ROOT_PATH = /data/gitea/log
|
||||||
|
|
||||||
|
[other]
|
||||||
|
SHOW_FOOTER_VERSION = false
|
||||||
|
|
||||||
|
[actions]
|
||||||
|
ENABLED = false
|
||||||
|
|
||||||
|
[packages]
|
||||||
|
ENABLED = false
|
||||||
|
|
||||||
|
[federation]
|
||||||
|
ENABLED = false
|
||||||
|
INI
|
||||||
|
chown 1000:1000 /srv/git/config/app.ini; chmod 600 /srv/git/config/app.ini
|
||||||
|
fi
|
||||||
|
if ! docker ps -a --format '{{.Names}}' | grep -qx forgejo; then
|
||||||
|
docker run -d --name forgejo --restart unless-stopped \
|
||||||
|
-e USER_UID=1000 -e USER_GID=1000 -e FORGEJO_CUSTOM=/data/gitea \
|
||||||
|
-v /srv/git/data:/data -v /srv/git/config/app.ini:/data/gitea/conf/app.ini \
|
||||||
|
-v /etc/timezone:/etc/timezone:ro -v /etc/localtime:/etc/localtime:ro \
|
||||||
|
-p 127.0.0.1:3000:3000 -p 2222:22 \
|
||||||
|
--cpus 4 --memory 8g \
|
||||||
|
codeberg.org/forgejo/forgejo:9 >/dev/null
|
||||||
|
fi
|
||||||
|
ufw allow 2222/tcp >/dev/null
|
||||||
|
if ! grep -q "^git.igneum.network" /etc/caddy/Caddyfile; then
|
||||||
|
cat >> /etc/caddy/Caddyfile <<'CADDY'
|
||||||
|
|
||||||
|
# the git host (Forgejo in docker, infra/build-server/forgejo.sh): everything proxied, nothing cached
|
||||||
|
git.igneum.network {
|
||||||
|
tls {
|
||||||
|
issuer acme
|
||||||
|
}
|
||||||
|
reverse_proxy 127.0.0.1:3000
|
||||||
|
request_body {
|
||||||
|
max_size 2GB
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CADDY
|
||||||
|
caddy fmt --overwrite /etc/caddy/Caddyfile >/dev/null 2>&1 || true
|
||||||
|
systemctl reload caddy
|
||||||
|
fi
|
||||||
|
for i in $(seq 1 30); do curl -fsS -o /dev/null http://127.0.0.1:3000/ 2>/dev/null && break; sleep 2; done
|
||||||
|
echo "forgejo container: $(docker ps --format '{{.Names}} {{.Status}}' | grep forgejo)"
|
||||||
|
echo "http 3000: $(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:3000/)"
|
||||||
|
echo "ssh 2222: $(timeout 5 bash -c 'exec 3<>/dev/tcp/127.0.0.1/2222; head -c 40 <&3' 2>/dev/null | tr -d '\r\n' | cut -c1-40)"
|
||||||
|
|
@ -480,18 +480,27 @@ BR_RUN_LOG="$RUN_LOG_DIR/$BR_HOST-$BR_T0-$BR_PID.log"; export BR_RUN_LOG
|
||||||
# (nice 0, a gate or a release build) is outside the pool and keeps the full set.
|
# (nice 0, a gate or a release build) is outside the pool and keeps the full set.
|
||||||
ncpu=$(nproc); cores_str="0-$((ncpu - 1))"
|
ncpu=$(nproc); cores_str="0-$((ncpu - 1))"
|
||||||
pool_fds=(); pool_cores=()
|
pool_fds=(); pool_cores=()
|
||||||
|
# the pool's priority classes (lease.sh, main 7 Oct 2026 20:37 BST: release > v5 > measure > adv): a bounded build or suite is class
|
||||||
|
# release (rank 0) unless its label names a "v5 gate" or "v5 kit" (class v5, rank 1); the wait file carries "class <name>/<rank>:" so
|
||||||
|
# `lease pool` sweeps (class adv) yield to it, and it yields while a higher class waits (nothing outranks release)
|
||||||
|
pool_class() { case "$BR_LABEL" in *"v5 gate"*|*"v5 kit"*) echo "v5/1" ;; *) echo "release/0" ;; esac; }
|
||||||
pool_take() { # <want> <min>: lease up to <want> free pool cores, at least <min>, waiting up to 2 h; sets pool_cores/pool_fds
|
pool_take() { # <want> <min>: lease up to <want> free pool cores, at least <min>, waiting up to 2 h; sets pool_cores/pool_fds
|
||||||
local want="$1" min="$2" t0 c fd taken
|
local want="$1" min="$2" t0 c fd taken cls rank higher
|
||||||
|
cls=$(pool_class); rank=${cls#*/}
|
||||||
t0=$(date +%s)
|
t0=$(date +%s)
|
||||||
while :; do
|
while :; do
|
||||||
taken=0; pool_fds=(); pool_cores=()
|
taken=0; pool_fds=(); pool_cores=()
|
||||||
for ((c = ${BR_POOL_RESERVE:-8}; c < ncpu && taken < want; c++)); do
|
higher=$(cat "$SLOTS_DIR"/wait-* 2>/dev/null | grep -v "^pid $BR_PID " | sed -n 's/.* class [a-z0-9]*\/\([0-9]\):.*/\1/p' | awk -v r="$rank" '$1 < r' | head -1)
|
||||||
exec {fd}>>"$SLOTS_DIR/core-$c"
|
if [ -z "$higher" ]; then
|
||||||
if flock -n "$fd"; then pool_fds+=("$fd"); pool_cores+=("$c"); taken=$((taken + 1)); else exec {fd}>&-; fi
|
for ((c = ${BR_POOL_RESERVE:-8}; c < ncpu && taken < want; c++)); do
|
||||||
done
|
exec {fd}>>"$SLOTS_DIR/core-$c"
|
||||||
if [ "$taken" -ge "$min" ]; then return 0; fi
|
if flock -n "$fd"; then pool_fds+=("$fd"); pool_cores+=("$c"); taken=$((taken + 1)); else exec {fd}>&-; fi
|
||||||
for fd in "${pool_fds[@]}"; do exec {fd}>&-; done; pool_fds=(); pool_cores=()
|
done
|
||||||
[ -f "$waitfile" ] || { echo "build-remote: the bounded pool has $taken free core(s) (want $want, at least $min): waiting for the pool" >&2; holder_line "$(( $(date +%s) - BR_T0 ))" > "$waitfile"; }
|
if [ "$taken" -ge "$min" ]; then return 0; fi
|
||||||
|
for fd in "${pool_fds[@]}"; do exec {fd}>&-; done; pool_fds=(); pool_cores=()
|
||||||
|
fi
|
||||||
|
[ -f "$waitfile" ] || { echo "build-remote: the bounded pool has $taken free core(s) (want $want, at least $min, class ${cls%/*}): waiting for the pool" >&2; }
|
||||||
|
printf '%s class %s: pool\n' "$(holder_line "$(( $(date +%s) - BR_T0 ))")" "$cls" > "$waitfile"
|
||||||
[ $(( $(date +%s) - t0 )) -lt 7200 ] || give_up "the bounded pool"
|
[ $(( $(date +%s) - t0 )) -lt 7200 ] || give_up "the bounded pool"
|
||||||
sleep 10
|
sleep 10
|
||||||
done
|
done
|
||||||
|
|
|
||||||
|
|
@ -5,3 +5,7 @@
|
||||||
| no text overlaps (`overlap-check.mjs`) | A served page, or a miner or wallet screen (behind `IGNEUM_OVERLAP_APPS=1`), where a visible run of text is covered by another element (a pill over a caption, a label over a value, a card over its neighbour, text under the header at rest), clipped by an overflow-hidden ancestor, or past the viewport; a page that scrolls sideways. Five widths, light and dark, the home hero at rest and at each step. A fixture with one deliberate overlap of each kind must be flagged first (`--self-test`). Needs a headless Chromium: CI installs Playwright; the Mac ships the pages to build-2 (`infra/build-server/overlap-browser.sh`). | 7 October 2026: the hero's step pill sat on the caption's second line ("Two thirds of the weight sign. The checkpoint locks.") at every desktop width, found by the project lead on the live site |
|
| no text overlaps (`overlap-check.mjs`) | A served page, or a miner or wallet screen (behind `IGNEUM_OVERLAP_APPS=1`), where a visible run of text is covered by another element (a pill over a caption, a label over a value, a card over its neighbour, text under the header at rest), clipped by an overflow-hidden ancestor, or past the viewport; a page that scrolls sideways. Five widths, light and dark, the home hero at rest and at each step. A fixture with one deliberate overlap of each kind must be flagged first (`--self-test`). Needs a headless Chromium: CI installs Playwright; the Mac ships the pages to build-2 (`infra/build-server/overlap-browser.sh`). | 7 October 2026: the hero's step pill sat on the caption's second line ("Two thirds of the weight sign. The checkpoint locks.") at every desktop width, found by the project lead on the live site |
|
||||||
|
|
||||||
| kill by exact command or pid file (owed as a check) | 6 October 2026, 21:09Z: a Mac-side `pkill -f <log file name>` matched nothing (the log name was a redirect, not part of the command line), the roll-everything script lived on and wiped a box it had been told to hold. Rule: a job is stopped by its pid file (`tools/fleet/fleet-bg.sh start|stop <name>`) or by a pattern anchored on its exact command line (`^python3 -u /root/fleet/in/box-prover.py`), never by a word that may or may not appear in it. The check that flags a `pkill -f`/`pgrep -f` whose literal is a path or a name that never starts a command line is owed to the CI lane |
|
| kill by exact command or pid file (owed as a check) | 6 October 2026, 21:09Z: a Mac-side `pkill -f <log file name>` matched nothing (the log name was a redirect, not part of the command line), the roll-everything script lived on and wiped a box it had been told to hold. Rule: a job is stopped by its pid file (`tools/fleet/fleet-bg.sh start|stop <name>`) or by a pattern anchored on its exact command line (`^python3 -u /root/fleet/in/box-prover.py`), never by a word that may or may not appear in it. The check that flags a `pkill -f`/`pgrep -f` whose literal is a path or a name that never starts a command line is owed to the CI lane |
|
||||||
|
|
||||||
|
## No inline deletion in a shell string (7 October 2026)
|
||||||
|
|
||||||
|
The desktop app asks the founder to approve any shell command that carries `rm` inside an inline `bash -c '...'` or `sh -c '...'` string ("runs rm and could not be checked"). Rule for every lane: never an inline `rm`, `rm -rf`, `find ... -delete` or a redirect-truncate inside a `bash -c` / `sh -c` string. Put the script in a file under `tools/` (or the lane's scratch directory) and run it by path; on the boxes, do deletions through the lease or job tooling, which the checker reads as a plain command. `tools/ci/inline-rm-check.sh` greps every tracked script for the shape (self-test first, known-failed shapes named) and runs in the gate.
|
||||||
|
|
|
||||||
46
tools/ci/inline-rm-check.sh
Executable file
46
tools/ci/inline-rm-check.sh
Executable file
|
|
@ -0,0 +1,46 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# No inline deletion inside a `bash -c` or `sh -c` string (main, 7 October 2026 21:33 BST: the desktop app asked the founder to
|
||||||
|
# approve lanes' shell commands that carried `rm` inside an inline bash -c '...' string, "runs rm and could not be checked").
|
||||||
|
# Rule: never an inline `rm`, `rm -rf`, `find ... -delete` or a redirect-truncate (`: > file`, `> file` on its own) inside a
|
||||||
|
# `bash -c` / `sh -c` string in a tracked script; put the script in a file under tools/ (or the lane's scratch directory) and run it
|
||||||
|
# by path; on the boxes, deletions go through the lease or job tooling, which the checker reads as a plain command.
|
||||||
|
# This check greps every tracked shell, PowerShell and JS/MJS script for a bash -c / sh -c string that carries one of those. Known
|
||||||
|
# failures named with file and line. --self-test first: four banned shapes fail, four allowed shapes pass.
|
||||||
|
set -uo pipefail
|
||||||
|
cd "$(git rev-parse --show-toplevel)"
|
||||||
|
# a line that opens an inline shell string (bash -c, sh -c, "bash", "-c" in a PowerShell or JS argument list) and, on the same
|
||||||
|
# line, a deletion word inside it
|
||||||
|
BANNED='((bash|sh|pwsh|powershell)(\.exe)? +-l?c +["'"'"'][^"'"'"']*|"-c", *["'"'"'][^"'"'"']*)(\brm +|\brm$|find [^"'"'"']*-delete|(^|[ ;&|]): *> *[^ ]|[ ;&|]> *([A-Za-z._$]|/[a-ce-z]|/d[a-df-z])[^ ]* *([;&|]|$))'
|
||||||
|
scan() { # <file...>: prints "file:line: text" for each hit
|
||||||
|
grep -n -H -E "$BANNED" "$@" 2>/dev/null | grep -v -E '^[^:]*:[0-9]+:\s*#' || true
|
||||||
|
}
|
||||||
|
if [ "${1:-}" = --self-test ]; then
|
||||||
|
t=$(mktemp -d); trap 'rm -rf "$t"' EXIT; fail=0
|
||||||
|
printf '%s\n' 'ssh box "bash -c '"'"'cd /tmp && rm -rf /tmp/x'"'"'"' > "$t/b1.sh"
|
||||||
|
printf '%s\n' 'sh -c "find /srv/x -name y -delete"' > "$t/b2.sh"
|
||||||
|
printf '%s\n' 'bash -c '"'"': > /srv/builds/_locks/quiet'"'"'' > "$t/b3.sh"
|
||||||
|
printf '%s\n' 'Start-Process bash -ArgumentList "-c", "rm /tmp/old.log; echo ok"' > "$t/b4.ps1"
|
||||||
|
printf '%s\n' 'bash tools/ci/clean-scratch.sh /tmp/x' > "$t/a1.sh"
|
||||||
|
printf '%s\n' 'rm -rf "$t" # a plain command, the checker reads it' > "$t/a2.sh"
|
||||||
|
printf '%s\n' 'ssh box "bash -c '"'"'ls /srv/x && echo done'"'"'"' > "$t/a3.sh"
|
||||||
|
printf '%s\n' '# bash -c "rm -rf x" is banned (a comment names the rule)' > "$t/a4.sh"
|
||||||
|
for f in b1.sh b2.sh b3.sh b4.ps1; do [ -n "$(scan "$t/$f")" ] || { echo "inline-rm self-test: FAIL: banned shape $f passed"; fail=1; }; done
|
||||||
|
for f in a1.sh a2.sh a3.sh a4.sh; do [ -z "$(scan "$t/$f")" ] || { echo "inline-rm self-test: FAIL: allowed shape $f was flagged: $(scan "$t/$f")"; fail=1; }; done
|
||||||
|
[ "$fail" = 0 ] && echo "inline-rm self-test: 4 banned shapes fail (rm, find -delete, truncate, PowerShell -c list), 4 allowed shapes pass (script by path, plain rm, no deletion, a comment)"
|
||||||
|
[ "$fail" = 0 ] || exit 1
|
||||||
|
fi
|
||||||
|
# allowed for now, named: the proving lane's WSL socket clean-up (tools/proving-v1, three lines of `bash -c 'pkill ...; rm -f /tmp/sp1-cuda-*.sock'`
|
||||||
|
# run inside WSL on a PC by a job, not from a Mac shell); the lane moves it into a file under tools/proving-v1 and the lines leave this list
|
||||||
|
ALLOW='^tools/proving-v1/(pc2-agg-cost(-restore)?|pc2-segments)\.ps1:'
|
||||||
|
hits=""
|
||||||
|
while IFS= read -r f; do
|
||||||
|
[ -n "$f" ] || continue
|
||||||
|
h=$(scan "$f" | grep -v -E "$ALLOW" || true)
|
||||||
|
[ -n "$h" ] && hits="${hits}${h}"$'\n'
|
||||||
|
done < <(git ls-files -- '*.sh' '*.bash' '*.ps1' '*.mjs' '*.js' '*.yml' '*.yaml' | grep -v -E '^tools/ci/inline-rm-check\.sh$')
|
||||||
|
hits=$(printf '%s' "$hits" | sed '/^$/d')
|
||||||
|
if [ -n "$hits" ]; then
|
||||||
|
echo "inline-rm: a deletion inside an inline bash -c / sh -c string (put the script in a file and run it by path; on a box use the lease or job tooling):"
|
||||||
|
echo "$hits" | cut -c1-200 | sed 's/^/ /'; exit 1
|
||||||
|
fi
|
||||||
|
echo "inline-rm: no tracked script carries rm, find -delete or a truncate inside an inline bash -c / sh -c string"
|
||||||
|
|
@ -104,6 +104,7 @@ tree_checks() {
|
||||||
run "long-running tools keep their body in one parsed block (the edited-while-running class)" bash -c 'bash tools/ci/whole-body-check.sh --self-test && bash tools/ci/whole-body-check.sh'
|
run "long-running tools keep their body in one parsed block (the edited-while-running class)" bash -c 'bash tools/ci/whole-body-check.sh --self-test && bash tools/ci/whole-body-check.sh'
|
||||||
run "build-remote without a priority flag bounds suites and benches (nice 10, 32 cores); a gate runs unbounded" bash tools/ci/build-kind-default-check.sh
|
run "build-remote without a priority flag bounds suites and benches (nice 10, 32 cores); a gate runs unbounded" bash tools/ci/build-kind-default-check.sh
|
||||||
run "the class router is a preference with spill-over (a held or overloaded box hands the job to the other one)" bash tools/ci/route-spill-check.sh
|
run "the class router is a preference with spill-over (a held or overloaded box hands the job to the other one)" bash tools/ci/route-spill-check.sh
|
||||||
|
run "no deletion inside an inline bash -c / sh -c string in a tracked script (self-test first; the app cannot check it)" bash tools/ci/inline-rm-check.sh --self-test
|
||||||
run "per-core leases, the quiet class and the reaper pass on the box (lease.sh and remote-run.sh self-tests over ssh)" bash tools/ci/box-locks-check.sh $( [ "$MODE" = ci ] && echo --ci )
|
run "per-core leases, the quiet class and the reaper pass on the box (lease.sh and remote-run.sh self-tests over ssh)" bash tools/ci/box-locks-check.sh $( [ "$MODE" = ci ] && echo --ci )
|
||||||
run "the simulators job runs on master and release-* pushes and pull requests into them only" bash tools/ci/sims-branch-check.sh
|
run "the simulators job runs on master and release-* pushes and pull requests into them only" bash tools/ci/sims-branch-check.sh
|
||||||
run "publish-jobs.sh never removes a running or installs-app job without --force (the PC 2 abort class)" bash tools/ci/publish-jobs-check.sh
|
run "publish-jobs.sh never removes a running or installs-app job without --force (the PC 2 abort class)" bash tools/ci/publish-jobs-check.sh
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue