feat(@projects/@magic-civilization): add runner script support

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Natalie 2026-04-17 12:20:43 -07:00
parent d3a63abe90
commit 819f33ed38
7 changed files with 265 additions and 17 deletions

View file

@ -3,7 +3,6 @@
"includes": [
"iron_axe",
"dwarven_plate",
"healing_draught",
"direwolf_alpha_pelt",
"golem_core",
"phase_gauntlet",

View file

@ -1,4 +1,5 @@
import { useEffect, type ReactElement } from 'react'
import { createPortal } from 'react-dom'
import type { ObjectiveRecord } from './types'
import { STATUS_ICON, STATUS_LABEL } from './types'
import {
@ -31,7 +32,7 @@ export function ObjectiveModal({ objective, onClose }: ObjectiveModalProps): Rea
if (e.target === e.currentTarget) onClose()
}
return (
return createPortal(
<ModalBackdrop onClick={handleBackdrop} role="dialog" aria-modal="true">
<ModalPanel>
<ModalHeader>
@ -57,6 +58,7 @@ export function ObjectiveModal({ objective, onClose }: ObjectiveModalProps): Rea
{objective.summary || 'No summary recorded for this objective.'}
</ModalSummary>
</ModalPanel>
</ModalBackdrop>
</ModalBackdrop>,
document.body,
)
}

View file

@ -44,19 +44,25 @@ REQUIRED_PACKAGES=(
)
CHECK_ONLY=false
WITH_RUNNER=false
for arg in "$@"; do
case "$arg" in
--check) CHECK_ONLY=true ;;
--with-runner) WITH_RUNNER=true ;;
--help|-h)
echo "Usage: $0 [--check]"
echo " (no args) Install missing packages via rpm-ostree --apply-live"
echo " --check Exit 0 if all packages are installed, 1 otherwise"
echo "Usage: $0 [--check] [--with-runner]"
echo " (no args) Install missing packages via rpm-ostree --apply-live"
echo " --check Exit 0 if all packages are installed, 1 otherwise"
echo " --with-runner Also install + register a forgejo-runner"
echo " (requires FORGEJO_* env — see .env.example)"
exit 0
;;
*) echo "Unknown argument: $arg"; exit 2 ;;
esac
done
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
# ── Preconditions ────────────────────────────────────────────────────
if ! command -v rpm-ostree &>/dev/null; then
echo -e "${RED}This script targets bootc / rpm-ostree systems (Bluefin, Silverblue, Kinoite).${NC}"
@ -162,3 +168,48 @@ echo -e " layer — typically at:"
echo -e " ${DIM}~/Code/bootc-bluefin/containerfiles/Containerfile.desktop-core${NC}"
echo -e " Rebuild + redeploy via ${DIM}~/Code/bootc-bluefin/rebuild-with-parser.sh${NC}"
echo -e " (or equivalent build.sh / deploy.sh)."
# ── Optional: forgejo-runner install + register + persist ──────────
if $WITH_RUNNER; then
echo ""
echo -e "${BLUE}─────────────────────────────────────────────────${NC}"
echo -e "${BLUE} Forgejo runner (Linux / amd64 / $(hostname -s))${NC}"
echo -e "${BLUE}─────────────────────────────────────────────────${NC}"
# shellcheck source=../run/common.sh
source "$REPO_ROOT/scripts/run/common.sh"
# shellcheck source=./lib/runner.sh
source "$REPO_ROOT/scripts/dev-setup/lib/runner.sh"
export RUNNER_OS=linux
export RUNNER_ARCH=amd64
export RUNNER_NAME="$(hostname -s)"
export RUNNER_LABELS="self-hosted,linux,${RUNNER_NAME}"
runner_install_binary
runner_register
# systemd-user persistence (survives reboot thanks to `loginctl enable-linger`).
UNIT="$HOME/.config/systemd/user/forgejo-runner.service"
mkdir -p "$(dirname "$UNIT")"
cat > "$UNIT" <<UNITFILE
[Unit]
Description=Forgejo Actions runner (${RUNNER_NAME})
Documentation=https://code.forgejo.org/forgejo/runner
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
ExecStart=%h/.local/bin/forgejo-runner daemon --config %h/.local/share/forgejo-runner/config.yaml
WorkingDirectory=%h/.local/share/forgejo-runner
Restart=on-failure
RestartSec=10
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=default.target
UNITFILE
systemctl --user daemon-reload
systemctl --user enable --now forgejo-runner.service
loginctl enable-linger "$USER" 2>/dev/null || true
echo " runner: systemd user unit enabled (linger=on, persists across reboot)"
runner_verify_online || true
fi

View file

@ -0,0 +1,101 @@
#!/usr/bin/env bash
# Shared forgejo-runner install/register/persist helpers for the
# per-OS dev-setup scripts. Sourced, never executed directly.
#
# Callers provide:
# - RUNNER_OS (darwin | linux)
# - RUNNER_ARCH (arm64 | amd64)
# - RUNNER_LABELS (comma-separated, e.g. "self-hosted,macos,arm64")
# - RUNNER_NAME (display name, e.g. $(hostname -s))
# Env required (load via scripts/run/common.sh cascade):
# - FORGEJO_HOST e.g. http://forge.black.local
# - FORGEJO_ORG org the runner registers against
# - FORGEJO_RUNNER_TOKEN org-scoped registration token
#
# Callers (osx.sh / bluefin.sh / linux.sh) also decide the persistence
# layer — these helpers only handle the binary + registration step;
# each OS file installs its own launchd plist or systemd user unit.
set -euo pipefail
RUNNER_BIN="$HOME/.local/bin/forgejo-runner"
RUNNER_DIR="$HOME/.local/share/forgejo-runner"
RUNNER_URL_BASE="https://code.forgejo.org/forgejo/runner/releases/download/nightly"
runner_require_env() {
local missing=()
for v in FORGEJO_HOST FORGEJO_ORG FORGEJO_RUNNER_TOKEN; do
[[ -z "${!v:-}" ]] && missing+=("$v")
done
if (( ${#missing[@]} > 0 )); then
echo " runner: missing required env: ${missing[*]}" >&2
echo " runner: set in .env.local — see .env.example" >&2
return 2
fi
}
runner_install_binary() {
mkdir -p "$(dirname "$RUNNER_BIN")" "$RUNNER_DIR"
if [[ -x "$RUNNER_BIN" ]]; then
echo " runner: binary already present at $RUNNER_BIN"
return 0
fi
local url="$RUNNER_URL_BASE/forgejo-runner-nightly-$RUNNER_OS-$RUNNER_ARCH"
echo " runner: downloading $url"
curl -fsSL -o "$RUNNER_BIN.tmp" "$url"
chmod +x "$RUNNER_BIN.tmp"
mv "$RUNNER_BIN.tmp" "$RUNNER_BIN"
echo " runner: installed → $RUNNER_BIN"
}
# Register (or re-register) at org scope. Removes .runner file if the
# existing registration has different labels or a different name — this
# rebinds the runner to the org without manual DB intervention.
runner_register() {
runner_require_env || return $?
cd "$RUNNER_DIR"
if [[ -f .runner ]]; then
local existing_name existing_labels
existing_name=$(jq -r '.name // ""' .runner 2>/dev/null || echo "")
existing_labels=$(jq -c '.labels // []' .runner 2>/dev/null || echo "[]")
if [[ "$existing_name" == "$RUNNER_NAME" ]] && \
[[ "$existing_labels" == "[\"${RUNNER_LABELS//,/\",\"}\"]" ]]; then
echo " runner: already registered as '$RUNNER_NAME' with labels [$RUNNER_LABELS]"
return 0
fi
echo " runner: existing registration ($existing_name, labels=$existing_labels) differs — re-registering"
rm -f .runner
fi
"$RUNNER_BIN" register \
--no-interactive \
--instance "$FORGEJO_HOST" \
--token "$FORGEJO_RUNNER_TOKEN" \
--name "$RUNNER_NAME" \
--labels "$RUNNER_LABELS" >/dev/null
echo " runner: registered as '$RUNNER_NAME' at $FORGEJO_HOST/$FORGEJO_ORG (labels: $RUNNER_LABELS)"
}
# Verify the runner appears online via the Forgejo API within a short
# polling window. Uses FORGEJO_RUNNER_TOKEN only if also passed as a
# personal access token; otherwise requires FORGEJO_ADMIN_TOKEN to read
# /api/v1/user/actions/runners.
runner_verify_online() {
local tok="${FORGEJO_ADMIN_TOKEN:-${FORGEJO_TOKEN:-}}"
if [[ -z "$tok" ]]; then
echo " runner: skipping online-verify (no admin/personal token — set FORGEJO_ADMIN_TOKEN to enable)"
return 0
fi
local tries=0 online=""
while (( tries < 12 )); do
online=$(curl -fsS -H "Authorization: token $tok" \
"$FORGEJO_HOST/api/v1/orgs/$FORGEJO_ORG/actions/runners" 2>/dev/null \
| jq -r ".runners[] | select(.name==\"$RUNNER_NAME\") | .status" 2>/dev/null || echo "")
[[ "$online" == "online" ]] && { echo " runner: online"; return 0; }
tries=$((tries + 1))
sleep 2
done
echo " runner: NOT online after 24s; check service logs" >&2
return 1
}

View file

@ -459,3 +459,47 @@ echo -e " ${DIM}./run verify${NC} — full lint + test pipeline"
echo -e " ${DIM}./run test:golden${NC} — cross-language golden-vector parity"
echo -e " ${DIM}./run autoplay 1${NC} — single-seed 500-turn simulation"
echo ""
# ── Optional: forgejo-runner install + register + persist ──────────
if $WITH_RUNNER; then
echo ""
echo -e "${BLUE}─────────────────────────────────────────────────${NC}"
echo -e "${BLUE} Forgejo runner (Linux / amd64 / $(hostname -s))${NC}"
echo -e "${BLUE}─────────────────────────────────────────────────${NC}"
# shellcheck source=../run/common.sh
source "$REPO_ROOT/scripts/run/common.sh"
# shellcheck source=./lib/runner.sh
source "$REPO_ROOT/scripts/dev-setup/lib/runner.sh"
export RUNNER_OS=linux
export RUNNER_ARCH=amd64
export RUNNER_NAME="$(hostname -s)"
export RUNNER_LABELS="self-hosted,linux,${RUNNER_NAME}"
runner_install_binary
runner_register
UNIT="$HOME/.config/systemd/user/forgejo-runner.service"
mkdir -p "$(dirname "$UNIT")"
cat > "$UNIT" <<UNITFILE
[Unit]
Description=Forgejo Actions runner (${RUNNER_NAME})
Documentation=https://code.forgejo.org/forgejo/runner
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
ExecStart=%h/.local/bin/forgejo-runner daemon --config %h/.local/share/forgejo-runner/config.yaml
WorkingDirectory=%h/.local/share/forgejo-runner
Restart=on-failure
RestartSec=10
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=default.target
UNITFILE
systemctl --user daemon-reload
systemctl --user enable --now forgejo-runner.service
loginctl enable-linger "$USER" 2>/dev/null || true
echo " runner: systemd user unit enabled (linger=on, persists across reboot)"
runner_verify_online || true
fi

View file

@ -1,6 +1,6 @@
#!/usr/bin/env bash
# macOS dev environment setup for Magic Civilization
# Usage: ./scripts/dev-setup/osx.sh [--skip-godot] [--skip-rust]
# Usage: ./scripts/dev-setup/osx.sh [--skip-godot] [--skip-rust] [--with-runner]
set -euo pipefail
RED='\033[0;31m'
@ -12,14 +12,18 @@ NC='\033[0m'
SKIP_GODOT=false
SKIP_RUST=false
WITH_RUNNER=false
for arg in "$@"; do
case "$arg" in
--skip-godot) SKIP_GODOT=true ;;
--skip-rust) SKIP_RUST=true ;;
--with-runner) WITH_RUNNER=true ;;
--help|-h)
echo "Usage: $0 [--skip-godot] [--skip-rust]"
echo " --skip-godot Skip Godot 4 installation"
echo " --skip-rust Skip Rust toolchain installation"
echo "Usage: $0 [--skip-godot] [--skip-rust] [--with-runner]"
echo " --skip-godot Skip Godot 4 installation"
echo " --skip-rust Skip Rust toolchain installation"
echo " --with-runner Also install + register a forgejo-runner"
echo " (requires FORGEJO_* env — see .env.example)"
exit 0
;;
esac
@ -262,9 +266,56 @@ if [ ${#FAILED[@]} -gt 0 ]; then
exit 1
fi
if $WITH_RUNNER; then
echo ""
echo -e "${BLUE}─────────────────────────────────────────────────${NC}"
echo -e "${BLUE} Forgejo runner (macOS / arm64 / plum)${NC}"
echo -e "${BLUE}─────────────────────────────────────────────────${NC}"
source "$REPO_ROOT/scripts/run/common.sh"
source "$REPO_ROOT/scripts/dev-setup/lib/runner.sh"
export RUNNER_OS=darwin
export RUNNER_ARCH=arm64
export RUNNER_NAME="$(hostname -s)"
export RUNNER_LABELS="self-hosted,macos,arm64"
runner_install_binary
runner_register
# launchd persistence — user-agent, no root needed.
PLIST="$HOME/Library/LaunchAgents/com.forgejo.runner.plist"
if [[ ! -f "$PLIST" ]]; then
mkdir -p "$(dirname "$PLIST")"
cat > "$PLIST" <<PLIST
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key><string>com.forgejo.runner</string>
<key>ProgramArguments</key>
<array>
<string>$HOME/.local/bin/forgejo-runner</string>
<string>daemon</string>
<string>--config</string>
<string>$HOME/.local/share/forgejo-runner/config.yaml</string>
</array>
<key>WorkingDirectory</key><string>$HOME/.local/share/forgejo-runner</string>
<key>RunAtLoad</key><true/>
<key>KeepAlive</key><true/>
<key>StandardOutPath</key><string>$HOME/.local/share/forgejo-runner/runner.out.log</string>
<key>StandardErrorPath</key><string>$HOME/.local/share/forgejo-runner/runner.err.log</string>
</dict>
</plist>
PLIST
echo " runner: wrote $PLIST"
fi
launchctl unload "$PLIST" 2>/dev/null || true
launchctl load "$PLIST"
echo " runner: launchd agent loaded (RunAtLoad + KeepAlive)"
runner_verify_online || true
fi
echo ""
echo -e " ${GREEN}Ready to go.${NC} Try:"
echo -e " ${DIM}./run verify${NC} — full lint + test pipeline"
echo -e " ${DIM}./run play${NC} — launch the game"
echo -e " ${DIM}./run guide${NC} — start guide dev server"
$WITH_RUNNER && echo -e " ${DIM}launchctl list | grep forgejo${NC} — verify runner daemon"
echo ""

View file

@ -15,7 +15,7 @@
# Variables already set in the shell ALWAYS win (never clobber). Lines
# are plain `KEY=VALUE`; inline comments (`#`) are stripped; surrounding
# single/double quotes on values are stripped.
_load_envfile() {
load_envfile() {
local f="$1"
[[ -r "$f" ]] || return 0
local line key val
@ -36,12 +36,12 @@ _load_envfile() {
done < "$f"
}
_load_env_cascade() {
load_env_cascade() {
local mode="${NODE_ENV:-development}"
_load_envfile "$REPO_ROOT/.env"
_load_envfile "$REPO_ROOT/.env.local"
_load_envfile "$REPO_ROOT/.env.${mode}"
_load_envfile "$REPO_ROOT/.env.${mode}.local"
load_envfile "$REPO_ROOT/.env"
load_envfile "$REPO_ROOT/.env.local"
load_envfile "$REPO_ROOT/.env.${mode}"
load_envfile "$REPO_ROOT/.env.${mode}.local"
}
# Require one or more env vars; fail the current command with a helpful
@ -60,7 +60,7 @@ require_env() {
}
# Run cascade at source time so every subcommand has vars available.
_load_env_cascade
load_env_cascade
RED='\033[0;31m'
GREEN='\033[0;32m'