forgedock 1.32.0 → 1.33.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/engine/outcome-codes.mjs +7 -3
- package/bin/engine/phases.mjs +29 -8
- package/bin/engine-cli.mjs +84 -24
- package/bin/forgedock.mjs +21 -1
- package/bin/lean/basesync.mjs +98 -0
- package/bin/lean/basesync.test.mjs +151 -0
- package/bin/lean/batch.mjs +9 -3
- package/bin/lean/batch.test.mjs +134 -3
- package/bin/lean/budget.mjs +7 -1
- package/bin/lean/budget.test.mjs +9 -1
- package/bin/lean/conformance.mjs +20 -7
- package/bin/lean/conformance.test.mjs +29 -2
- package/bin/lean/depcache.mjs +23 -3
- package/bin/lean/depcache.test.mjs +42 -1
- package/bin/lean/effects.mjs +29 -3
- package/bin/lean/execution.mjs +106 -14
- package/bin/lean/execution.test.mjs +101 -1
- package/bin/lean/hostkill.mjs +8 -0
- package/bin/lean/lean.mjs +65 -24
- package/bin/lean/lean.test.mjs +221 -10
- package/bin/lean/lightlane.mjs +38 -13
- package/bin/lean/lightlane.test.mjs +35 -1
- package/bin/lean/orchestrate.mjs +93 -26
- package/bin/lean/prompts.mjs +17 -7
- package/bin/lean/recovery.test.mjs +26 -0
- package/bin/lean/retry.mjs +15 -1
- package/bin/lean/sandbox-provider.contract.test.mjs +74 -1
- package/bin/lean/sandbox-remote.mjs +40 -7
- package/bin/lean/schedd.mjs +50 -14
- package/bin/lean/schedd.test.mjs +133 -21
- package/bin/lean/seqlease.mjs +49 -2
- package/bin/lean/seqlease.test.mjs +14 -0
- package/bin/lean/sequence.mjs +54 -6
- package/bin/lean/sequence.test.mjs +83 -1
- package/bin/pool/agent.mjs +435 -0
- package/bin/pool/cli.mjs +110 -0
- package/bin/pool/docker.mjs +56 -0
- package/bin/pool/http.mjs +55 -0
- package/bin/pool/router.mjs +299 -0
- package/bin/pool/store.mjs +25 -0
- package/bin/pool/version.mjs +44 -0
- package/bin/runner.mjs +1 -0
- package/bin/stats/aggregate.mjs +6 -0
- package/bin/stats/index.mjs +14 -2
- package/bin/stats/local-source.mjs +1 -1
- package/bin/stats/pool.mjs +51 -0
- package/bin/stats/pool.test.mjs +39 -0
- package/bin/stats/query.mjs +2 -2
- package/bin/stats/stats.test.mjs +15 -1
- package/commands/orchestrate.md +3 -3
- package/docs/CONFIG.md +23 -9
- package/docs/OUTCOME-CODES.md +5 -3
- package/docs/STATS.md +7 -0
- package/package.json +2 -1
- package/pool/README.md +13 -0
- package/pool/image/Dockerfile +46 -0
- package/pool/image/claude-shim +8 -0
- package/pool/image/cs-cred +12 -0
- package/pool/image/entrypoint.sh +61 -0
- package/pool/image/gh-shim +6 -0
- package/pool/install/install-agent-linux.sh +53 -0
- package/pool/install/install-agent.ps1 +225 -0
- package/pool/install/install-agent.sh +101 -0
- package/pool/install/install-router.sh +67 -0
- package/pool/install/keepalive.ps1 +36 -0
- package/pool/install/uninstall-agent.ps1 +20 -0
- package/scripts/hotspots.sh +33 -21
- package/scripts/hotspots.test.sh +22 -3
- package/scripts/refresh-bot-token.test.sh +56 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios
|
|
3
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
4
|
+
# Prepares the checkout ForgeDock expects, then execs the lane command (`forgedock run-issue ...`).
|
|
5
|
+
# All output goes to /csout/lane.log, which the agent streams back to ForgeDock.
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
mkdir -p /csout /result /cache
|
|
8
|
+
exec > >(tee -a /csout/lane.log) 2>&1
|
|
9
|
+
|
|
10
|
+
export HOME=/root
|
|
11
|
+
export UV_CACHE_DIR=/cache/uv npm_config_cache=/cache/npm POETRY_CACHE_DIR=/cache/poetry PIP_CACHE_DIR=/cache/pip
|
|
12
|
+
export TMPDIR=/tmp
|
|
13
|
+
# ForgeDock's dependency cache (its pnpm store and snapshots) and pnpm's own store live in the persistent per-repo
|
|
14
|
+
# cache volume, so a lane installs from a warm store instead of downloading every package (a cold monorepo install
|
|
15
|
+
# took ~15 min and hit ForgeDock's install timeout). /cache and /work are different volumes, so pnpm imports by copy.
|
|
16
|
+
export FORGE_DEPCACHE_ROOT=/cache/forge-depcache npm_config_store_dir=/cache/pnpm-store
|
|
17
|
+
# Testcontainers' Ryuk reaper never logs "Started" on the sandbox's private dockerd, so every DB test setup failed.
|
|
18
|
+
# The sandbox and its dockerd are discarded after the lane, so the reaper has nothing to add.
|
|
19
|
+
export TESTCONTAINERS_RYUK_DISABLED=true
|
|
20
|
+
|
|
21
|
+
echo "[computeshare] sandbox ${CS_SANDBOX_ID:-?} repo ${CS_REPO_SLUG:-?} ref ${CS_REPO_REF:-default} on $(hostname)"
|
|
22
|
+
|
|
23
|
+
# GitHub auth: the token is fetched on demand through the agent (installation tokens expire hourly), so a long lane
|
|
24
|
+
# never runs on a stale token. `gh` is a shim that exports a fresh GH_TOKEN; git uses the same source as a helper.
|
|
25
|
+
if [ -n "${CS_CRED_BASE:-}" ]; then
|
|
26
|
+
# Scoped to github.com: the installation token is never offered to another HTTPS git host (#4565).
|
|
27
|
+
git config --global credential.https://github.com.helper '!f() { echo username=x-access-token; echo "password=$(cs-cred github)"; }; f'
|
|
28
|
+
elif [ -n "${GH_TOKEN:-}" ]; then
|
|
29
|
+
gh auth setup-git >/dev/null 2>&1 || true
|
|
30
|
+
fi
|
|
31
|
+
git config --global user.name "${GIT_AUTHOR_NAME:-forgedock}"
|
|
32
|
+
git config --global user.email "${GIT_AUTHOR_EMAIL:-forgedock@localhost}"
|
|
33
|
+
|
|
34
|
+
if [ -n "${CS_REPO_URL:-}" ]; then
|
|
35
|
+
# Shared bare mirror in the cache volume: later lanes fetch a delta instead of a full clone.
|
|
36
|
+
mirror="/cache/git/$(echo "${CS_REPO_SLUG:-repo}" | tr '/' '_').git"
|
|
37
|
+
if [ -d "$mirror" ]; then
|
|
38
|
+
git -C "$mirror" fetch --prune --quiet origin '+refs/heads/*:refs/heads/*' || true
|
|
39
|
+
else
|
|
40
|
+
mkdir -p /cache/git
|
|
41
|
+
git clone --mirror --quiet "$CS_REPO_URL" "$mirror"
|
|
42
|
+
fi
|
|
43
|
+
rm -rf /work/repo
|
|
44
|
+
git clone --quiet --reference "$mirror" ${CS_REPO_REF:+--branch "$CS_REPO_REF"} "$CS_REPO_URL" /work/repo
|
|
45
|
+
cd /work/repo
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
if [ -n "${CS_FORGE_YAML_B64:-}" ]; then
|
|
49
|
+
echo "$CS_FORGE_YAML_B64" | base64 -d > forge.yaml
|
|
50
|
+
unset CS_FORGE_YAML_B64
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
if [ -n "${DOCKER_HOST:-}" ]; then
|
|
54
|
+
# Docker-enabled sandbox: wait for the private daemon sidecar before the lane starts its stack.
|
|
55
|
+
for i in $(seq 1 60); do docker info >/dev/null 2>&1 && break; sleep 1; done
|
|
56
|
+
docker info >/dev/null 2>&1 && echo "[computeshare] private docker daemon ready ($(docker version --format '{{.Server.Version}}'))" \
|
|
57
|
+
|| echo "[computeshare] WARNING: private docker daemon not reachable at $DOCKER_HOST"
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
echo "[computeshare] running: $*"
|
|
61
|
+
exec "$@"
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios
|
|
3
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
4
|
+
# gh with a GitHub token that is always current (see cs-cred).
|
|
5
|
+
if [ -n "${CS_CRED_BASE:-}" ]; then GH_TOKEN="$(cs-cred github)" || exit 1; export GH_TOKEN; fi
|
|
6
|
+
exec /usr/bin/gh "$@"
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios
|
|
3
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
4
|
+
# Native Linux (systemd) agent install: the Windows installer without the VM layer. Resolves host settings, then runs install-agent.sh,
|
|
5
|
+
# which installs docker, node, enrolls the agent, builds the lane image and installs the computeshare-agent systemd unit.
|
|
6
|
+
# Usage: install-agent-linux.sh --router URL --token CODE [--name N] [--port P] [--cpus N] [--mem GB] [--bundle FILE] [--dry-run]
|
|
7
|
+
# Env equivalents: ROUTER_URL ENROLL_TOKEN AGENT_NAME AGENT_PORT ALLOW_CPUS ALLOW_MEM_GB BUNDLE AGENT_URL
|
|
8
|
+
set -euo pipefail
|
|
9
|
+
DRY=0
|
|
10
|
+
while [ $# -gt 0 ]; do
|
|
11
|
+
case "$1" in
|
|
12
|
+
--router) ROUTER_URL="${2:?}"; shift 2 ;;
|
|
13
|
+
--token) ENROLL_TOKEN="${2:?}"; shift 2 ;;
|
|
14
|
+
--name) AGENT_NAME="${2:?}"; shift 2 ;;
|
|
15
|
+
--port) AGENT_PORT="${2:?}"; shift 2 ;;
|
|
16
|
+
--cpus) ALLOW_CPUS="${2:?}"; shift 2 ;;
|
|
17
|
+
--mem) ALLOW_MEM_GB="${2:?}"; shift 2 ;;
|
|
18
|
+
--bundle) BUNDLE="${2:?}"; shift 2 ;;
|
|
19
|
+
--dry-run) DRY=1; shift ;;
|
|
20
|
+
*) echo "unknown argument: $1" >&2; exit 2 ;;
|
|
21
|
+
esac
|
|
22
|
+
done
|
|
23
|
+
: "${ROUTER_URL:?--router or ROUTER_URL required}" "${ENROLL_TOKEN:?--token or ENROLL_TOKEN required}"
|
|
24
|
+
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
25
|
+
PORT="${AGENT_PORT:-7711}"
|
|
26
|
+
AGENT_NAME="${AGENT_NAME:-$(hostname | tr 'A-Z' 'a-z')}"
|
|
27
|
+
# Same defaults as install-agent.ps1: logical CPUs minus 4 (min 2); half of physical RAM (min 4 GB).
|
|
28
|
+
CPUS="${ALLOW_CPUS:-0}"
|
|
29
|
+
if [ "$CPUS" -le 0 ]; then CPUS=$(( $(nproc) - 4 )); [ "$CPUS" -ge 2 ] || CPUS=2; fi
|
|
30
|
+
MEM="${ALLOW_MEM_GB:-0}"
|
|
31
|
+
if [ "$MEM" -le 0 ]; then MEM=$(( $(awk '/MemTotal/ {print int($2/1024/1024)}' /proc/meminfo) / 2 )); [ "$MEM" -ge 4 ] || MEM=4; fi
|
|
32
|
+
if [ -z "${AGENT_URL:-}" ]; then
|
|
33
|
+
if [ "$DRY" = 1 ] && ! command -v tailscale >/dev/null; then TS_IP="<tailscale-ip>"; else
|
|
34
|
+
TS_IP="$(tailscale ip -4 | head -1)"
|
|
35
|
+
[[ "$TS_IP" == 100.* ]] || { echo "no Tailscale IPv4 found (got '$TS_IP')" >&2; exit 1; }
|
|
36
|
+
fi
|
|
37
|
+
AGENT_URL="http://${TS_IP}:${PORT}"
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
if [ "$DRY" = 1 ]; then
|
|
41
|
+
echo "router=$ROUTER_URL name=$AGENT_NAME url=$AGENT_URL port=$PORT cpus=$CPUS mem_gb=$MEM bundle=${BUNDLE:-<download>} token=<redacted>"
|
|
42
|
+
exit 0
|
|
43
|
+
fi
|
|
44
|
+
[ "$(id -u)" = 0 ] || { echo "run as root" >&2; exit 1; }
|
|
45
|
+
|
|
46
|
+
if [ -z "${BUNDLE:-}" ]; then
|
|
47
|
+
BUNDLE="$(mktemp /tmp/computeshare-bundle.XXXXXX.tar.gz)"
|
|
48
|
+
trap 'rm -f "$BUNDLE"' EXIT
|
|
49
|
+
curl -fsS "${ROUTER_URL}/bootstrap/bundle.tar.gz?token=${ENROLL_TOKEN}" -o "$BUNDLE"
|
|
50
|
+
fi
|
|
51
|
+
export ROUTER_URL ENROLL_TOKEN AGENT_NAME AGENT_URL BUNDLE
|
|
52
|
+
export AGENT_PORT="$PORT" ALLOW_CPUS="$CPUS" ALLOW_MEM_GB="$MEM"
|
|
53
|
+
bash "$HERE/install-agent.sh"
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios
|
|
2
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
3
|
+
<#
|
|
4
|
+
.SYNOPSIS
|
|
5
|
+
Installs a computeshare agent on a Windows host: a dedicated WSL2 distro ("computeshare") running Docker and the agent,
|
|
6
|
+
reachable on this machine's Tailscale IP, with a CPU/RAM ceiling enforced by the WSL VM itself.
|
|
7
|
+
.NOTES
|
|
8
|
+
Run in an elevated PowerShell. Normally started by the router's bootstrap one-liner (computeshare install-command).
|
|
9
|
+
#>
|
|
10
|
+
[CmdletBinding()]
|
|
11
|
+
param(
|
|
12
|
+
[Parameter(Mandatory)] [string] $RouterUrl,
|
|
13
|
+
[Parameter(Mandatory)] [string] $EnrollToken,
|
|
14
|
+
[Parameter(Mandatory)] [string] $BundlePath,
|
|
15
|
+
[string] $AgentName = $env:COMPUTERNAME.ToLower(),
|
|
16
|
+
[int] $Port = 7711,
|
|
17
|
+
[int] $AllowCpus = 0, # 0 = logical CPUs minus 4 (min 2)
|
|
18
|
+
[int] $AllowMemGb = 0, # 0 = half of physical RAM (min 4)
|
|
19
|
+
[string] $Distro = 'computeshare',
|
|
20
|
+
[switch] $Force # kept for compatibility; the install never prompts
|
|
21
|
+
)
|
|
22
|
+
$ErrorActionPreference = 'Stop'
|
|
23
|
+
$env:WSL_UTF8 = '1'
|
|
24
|
+
function Say($m) { Write-Host "[computeshare] $m" -ForegroundColor Cyan }
|
|
25
|
+
function Fail($m) { Write-Host "[computeshare] $m" -ForegroundColor Red; exit 1 }
|
|
26
|
+
|
|
27
|
+
$principal = [Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()
|
|
28
|
+
if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) { Fail 'Run this from an elevated (Administrator) PowerShell.' }
|
|
29
|
+
|
|
30
|
+
$installDir = Join-Path $env:ProgramData 'computeshare'
|
|
31
|
+
New-Item -ItemType Directory -Force -Path $installDir | Out-Null
|
|
32
|
+
|
|
33
|
+
# --- Tailscale ---------------------------------------------------------------------------------------------------
|
|
34
|
+
$ts = (Get-Command tailscale.exe -ErrorAction SilentlyContinue).Source
|
|
35
|
+
if (-not $ts) { $ts = Join-Path $env:ProgramFiles 'Tailscale\tailscale.exe' }
|
|
36
|
+
if (-not (Test-Path $ts)) { Fail 'Tailscale is not installed.' }
|
|
37
|
+
$tsIp = (& $ts ip -4 | Select-Object -First 1).Trim()
|
|
38
|
+
if ($tsIp -notmatch '^100\.') { Fail "Could not read this machine's Tailscale IPv4 (got '$tsIp')." }
|
|
39
|
+
Say "Tailscale IP $tsIp"
|
|
40
|
+
|
|
41
|
+
# --- Resource ceiling --------------------------------------------------------------------------------------------
|
|
42
|
+
$threads = [Environment]::ProcessorCount
|
|
43
|
+
Add-Type -AssemblyName Microsoft.VisualBasic
|
|
44
|
+
$ramGb = [math]::Floor((New-Object Microsoft.VisualBasic.Devices.ComputerInfo).TotalPhysicalMemory / 1GB)
|
|
45
|
+
if ($AllowCpus -le 0) { $AllowCpus = [math]::Max(2, $threads - 4) }
|
|
46
|
+
if ($AllowMemGb -le 0) { $AllowMemGb = [math]::Max(4, [math]::Floor($ramGb / 2)) }
|
|
47
|
+
$vmMemGb = $AllowMemGb + 2 # docker + agent overhead inside the VM
|
|
48
|
+
Say "Host: $threads threads, $ramGb GB RAM. Lanes get $AllowCpus CPUs / $AllowMemGb GB (WSL VM capped at $AllowCpus CPUs / $vmMemGb GB)."
|
|
49
|
+
|
|
50
|
+
# --- WSL ---------------------------------------------------------------------------------------------------------
|
|
51
|
+
# wsl.exe can hang when the WSL service is wedged. Every call runs in a job with a timeout; on a hang the service is
|
|
52
|
+
# restarted once and the call retried, so the install never needs a manual fix.
|
|
53
|
+
function Reset-WslService {
|
|
54
|
+
Say 'WSL service is not responding: restarting it'
|
|
55
|
+
Get-Process wsl, wslhost, wslrelay, wslservice -ErrorAction SilentlyContinue | Stop-Process -Force -ErrorAction SilentlyContinue
|
|
56
|
+
Start-Sleep -Seconds 2
|
|
57
|
+
Start-Service WSLService -ErrorAction SilentlyContinue
|
|
58
|
+
Start-Sleep -Seconds 3
|
|
59
|
+
}
|
|
60
|
+
function Invoke-Wsl([string[]] $WslArgs, [int] $TimeoutSec = 120, [switch] $Optional) {
|
|
61
|
+
for ($try = 1; $try -le 2; $try++) {
|
|
62
|
+
$job = Start-Job -ScriptBlock { param($a) $env:WSL_UTF8 = '1'; $o = & wsl.exe @a 2>&1 | Out-String; [pscustomobject]@{ rc = $LASTEXITCODE; out = $o } } -ArgumentList (, $WslArgs)
|
|
63
|
+
if (Wait-Job $job -Timeout $TimeoutSec) { $r = Receive-Job $job; Remove-Job $job -Force; return $r }
|
|
64
|
+
Stop-Job $job -ErrorAction SilentlyContinue; Remove-Job $job -Force -ErrorAction SilentlyContinue
|
|
65
|
+
if ($Optional) { return $null }
|
|
66
|
+
if ($try -eq 1) { Reset-WslService }
|
|
67
|
+
}
|
|
68
|
+
Fail "wsl $($WslArgs -join ' ') still hangs after restarting the WSL service. Reboot this PC, then run a fresh install command."
|
|
69
|
+
}
|
|
70
|
+
Say 'Checking WSL'
|
|
71
|
+
$v = Invoke-Wsl @('--version') 60
|
|
72
|
+
if ($v.rc -ne 0) {
|
|
73
|
+
Say 'WSL is missing or the old inbox version: installing it. Reboot when it finishes, then run a fresh install command.'
|
|
74
|
+
$r = Invoke-Wsl @('--install', '--no-distribution', '--web-download') 1200
|
|
75
|
+
Write-Host $r.out
|
|
76
|
+
exit 3010
|
|
77
|
+
}
|
|
78
|
+
$wslVer = [version]'0.0'
|
|
79
|
+
if ($v.out -match 'WSL[^:\r\n]*:\s*([0-9]+(\.[0-9]+)+)') { $wslVer = [version]$Matches[1] }
|
|
80
|
+
Say "WSL $wslVer"
|
|
81
|
+
if ($wslVer -lt [version]'2.0') {
|
|
82
|
+
# mirrored networking and the current --import behaviour need WSL 2.0+; only then is an update worth the wait
|
|
83
|
+
Say 'Updating WSL (up to 10 min; skipped if it stalls)'
|
|
84
|
+
$u = Invoke-Wsl @('--update', '--web-download') 600 -Optional
|
|
85
|
+
if ($null -eq $u) { Say 'wsl --update stalled; continuing with the installed version.' }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
# WSL 2 needs the Virtual Machine Platform feature and CPU virtualisation enabled in firmware (wsl.exe alone is not enough).
|
|
89
|
+
$cpu = Get-CimInstance Win32_Processor -ErrorAction SilentlyContinue | Select-Object -First 1
|
|
90
|
+
$hvPresent = [bool](Get-CimInstance Win32_ComputerSystem -ErrorAction SilentlyContinue).HypervisorPresent
|
|
91
|
+
if (-not $hvPresent -and $cpu -and $cpu.VirtualizationFirmwareEnabled -eq $false) {
|
|
92
|
+
Fail 'CPU virtualisation is disabled in the BIOS/UEFI (Intel VT-x / AMD SVM). Enable it in the firmware setup, boot Windows, then run a fresh install command.'
|
|
93
|
+
}
|
|
94
|
+
$needReboot = $false
|
|
95
|
+
foreach ($f in 'VirtualMachinePlatform', 'Microsoft-Windows-Subsystem-Linux') {
|
|
96
|
+
$st = (Get-WindowsOptionalFeature -Online -FeatureName $f -ErrorAction SilentlyContinue).State
|
|
97
|
+
if ($st -and $st -ne 'Enabled') {
|
|
98
|
+
Say "Enabling Windows feature $f"
|
|
99
|
+
$r = Enable-WindowsOptionalFeature -Online -FeatureName $f -All -NoRestart -ErrorAction Stop
|
|
100
|
+
if ($r.RestartNeeded) { $needReboot = $true }
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if ($needReboot) {
|
|
104
|
+
Say 'Windows features for WSL 2 were enabled. Reboot this PC, then run a fresh install command (computeshare install-command).'
|
|
105
|
+
exit 3010
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
$build = [int](Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion').CurrentBuildNumber
|
|
109
|
+
$mirrored = $build -ge 22621
|
|
110
|
+
Say ("Networking: " + ($(if ($mirrored) { 'mirrored (agent listens on the Tailscale IP directly)' } else { 'NAT + portproxy (Windows 10 / older 11)' })))
|
|
111
|
+
|
|
112
|
+
# .wslconfig is per Windows user and applies to every distro: merge our keys, keep everything else.
|
|
113
|
+
$wslConfig = Join-Path $env:USERPROFILE '.wslconfig'
|
|
114
|
+
$lines = @(); if (Test-Path $wslConfig) { $lines = Get-Content $wslConfig }
|
|
115
|
+
$want = [ordered]@{ processors = "$AllowCpus"; memory = "${vmMemGb}GB"; vmIdleTimeout = '-1' }
|
|
116
|
+
if ($mirrored) { $want['networkingMode'] = 'mirrored' }
|
|
117
|
+
$out = New-Object System.Collections.Generic.List[string]; $inWsl2 = $false; $seen = @{}
|
|
118
|
+
foreach ($l in $lines) {
|
|
119
|
+
if ($l -match '^\s*\[(.+)\]\s*$') {
|
|
120
|
+
if ($inWsl2) { foreach ($k in $want.Keys) { if (-not $seen[$k]) { $out.Add("$k=$($want[$k])"); $seen[$k] = $true } } }
|
|
121
|
+
$inWsl2 = ($Matches[1].Trim().ToLower() -eq 'wsl2')
|
|
122
|
+
$out.Add($l); continue
|
|
123
|
+
}
|
|
124
|
+
if ($inWsl2 -and $l -match '^\s*([A-Za-z]+)\s*=') {
|
|
125
|
+
$k = $want.Keys | Where-Object { $_ -ieq $Matches[1] } | Select-Object -First 1
|
|
126
|
+
if ($k) { $out.Add("$k=$($want[$k])"); $seen[$k] = $true; continue }
|
|
127
|
+
}
|
|
128
|
+
$out.Add($l)
|
|
129
|
+
}
|
|
130
|
+
if ($inWsl2) { foreach ($k in $want.Keys) { if (-not $seen[$k]) { $out.Add("$k=$($want[$k])"); $seen[$k] = $true } } }
|
|
131
|
+
if (-not ($out | Where-Object { $_ -match '^\s*\[wsl2\]\s*$' })) { $out.Add('[wsl2]'); foreach ($k in $want.Keys) { $out.Add("$k=$($want[$k])") } }
|
|
132
|
+
$changed = (($lines -join "`n") -ne ($out -join "`n"))
|
|
133
|
+
if ($changed) {
|
|
134
|
+
if (Test-Path $wslConfig) { Copy-Item $wslConfig "$wslConfig.computeshare-backup" -Force }
|
|
135
|
+
Set-Content -Path $wslConfig -Value $out -Encoding ascii
|
|
136
|
+
Say "Updated $wslConfig (backup: .wslconfig.computeshare-backup). NOTE: these limits apply to all your WSL distros."
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
# --- Dedicated distro -------------------------------------------------------------------------------------------
|
|
140
|
+
$distros = (Invoke-Wsl @('-l', '-q') 60).out -split "`r?`n" | ForEach-Object { $_.Trim() } | Where-Object { $_ }
|
|
141
|
+
if ($distros -notcontains $Distro) {
|
|
142
|
+
$base = 'https://cloud-images.ubuntu.com/wsl/releases/24.04/current'
|
|
143
|
+
$file = 'ubuntu-noble-wsl-amd64-wsl.rootfs.tar.gz'
|
|
144
|
+
$tar = Join-Path $installDir $file
|
|
145
|
+
# SHA256SUMS is served without a content type, so PowerShell 5.1 would hand back bytes: save it and read it as text.
|
|
146
|
+
$sumsFile = Join-Path $installDir 'SHA256SUMS'
|
|
147
|
+
Invoke-WebRequest -UseBasicParsing "$base/SHA256SUMS" -OutFile $sumsFile
|
|
148
|
+
$expected = ((Get-Content $sumsFile | Where-Object { $_ -match [regex]::Escape($file) } | Select-Object -First 1) -split '\s+')[0]
|
|
149
|
+
if (-not $expected) { Fail "no checksum for $file in SHA256SUMS" }
|
|
150
|
+
$actual = if (Test-Path $tar) { (Get-FileHash -Algorithm SHA256 $tar).Hash.ToLower() } else { '' }
|
|
151
|
+
if ($actual -ne $expected.ToLower()) {
|
|
152
|
+
Say "Downloading Ubuntu 24.04 WSL rootfs"
|
|
153
|
+
Invoke-WebRequest -UseBasicParsing "$base/$file" -OutFile $tar
|
|
154
|
+
$actual = (Get-FileHash -Algorithm SHA256 $tar).Hash.ToLower()
|
|
155
|
+
if ($actual -ne $expected.ToLower()) { Remove-Item $tar -Force; Fail "rootfs checksum mismatch ($actual vs $expected)" }
|
|
156
|
+
} else { Say 'Reusing the already downloaded rootfs (checksum verified)' }
|
|
157
|
+
Say "Importing WSL distro '$Distro'"
|
|
158
|
+
$r = Invoke-Wsl @('--import', $Distro, (Join-Path $installDir 'wsl'), $tar, '--version', '2') 900
|
|
159
|
+
if ($r.rc -ne 0) {
|
|
160
|
+
if ($r.out -match 'HCS_E_HYPERV_NOT_INSTALLED|Virtual Machine Platform') { Fail "WSL 2 cannot start a VM here: enable virtualisation (Intel VT-x / AMD SVM) in the BIOS/UEFI, reboot, and run a fresh install command. Details: $($r.out.Trim())" }
|
|
161
|
+
Fail "wsl --import failed: $($r.out)"
|
|
162
|
+
}
|
|
163
|
+
Remove-Item $tar -Force
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
# systemd for docker + the agent; no Windows PATH or Windows binaries inside the lane distro.
|
|
167
|
+
$wslConf = "[boot]\nsystemd=true\n\n[network]\nhostname=$AgentName-cs\n\n[interop]\nappendWindowsPath=false\n"
|
|
168
|
+
$before = (Invoke-Wsl @('-d', $Distro, '-u', 'root', '--', 'cat', '/etc/wsl.conf') 120).out
|
|
169
|
+
[void](Invoke-Wsl @('-d', $Distro, '-u', 'root', '--', 'sh', '-c', "printf '$wslConf' > /etc/wsl.conf") 120)
|
|
170
|
+
$after = (Invoke-Wsl @('-d', $Distro, '-u', 'root', '--', 'cat', '/etc/wsl.conf') 120).out
|
|
171
|
+
$confChanged = ($before -ne $after)
|
|
172
|
+
|
|
173
|
+
if ($changed -or $confChanged -or -not ((Invoke-Wsl @('-d', $Distro, '-u', 'root', '--', 'ps', '-p', '1', '-o', 'comm=') 120).out -match 'systemd')) {
|
|
174
|
+
Say 'Restarting WSL to apply the settings (this briefly stops every running WSL distro)'
|
|
175
|
+
[void](Invoke-Wsl @('--shutdown') 120)
|
|
176
|
+
Start-Sleep -Seconds 3
|
|
177
|
+
$p1 = (Invoke-Wsl @('-d', $Distro, '-u', 'root', '--', 'ps', '-p', '1', '-o', 'comm=') 180).out
|
|
178
|
+
if ($p1 -notmatch 'systemd') { Fail "systemd did not start in the '$Distro' distro (PID 1: $($p1.Trim()))." }
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
# --- Firewall ------------------------------------------------------------------------------------------------------
|
|
182
|
+
# Idempotent: re-runs replace the rules (a leftover rule must never stop the install).
|
|
183
|
+
Remove-NetFirewallRule -Name 'computeshare-agent' -ErrorAction SilentlyContinue
|
|
184
|
+
New-NetFirewallRule -Name 'computeshare-agent' -DisplayName 'computeshare agent (Tailscale only)' -Direction Inbound -Protocol TCP -LocalPort $Port -InterfaceAlias 'Tailscale' -Action Allow | Out-Null
|
|
185
|
+
if ($mirrored -and (Get-Command New-NetFirewallHyperVRule -ErrorAction SilentlyContinue)) {
|
|
186
|
+
$wslCreator = '{40E0AC32-46A5-438A-A0B2-2B479E8F2E90}'
|
|
187
|
+
# Hyper-V rules are not always addressable by the -Name they were created with: match ours by display name or port.
|
|
188
|
+
Get-NetFirewallHyperVRule -ErrorAction SilentlyContinue |
|
|
189
|
+
Where-Object { $_.DisplayName -like 'computeshare*' -or $_.Name -like '*computeshare*' -or ("$($_.LocalPorts)" -eq "$Port" -and "$($_.VMCreatorId)" -eq $wslCreator) } |
|
|
190
|
+
ForEach-Object { Remove-NetFirewallHyperVRule -Name $_.Name -ErrorAction SilentlyContinue }
|
|
191
|
+
try {
|
|
192
|
+
New-NetFirewallHyperVRule -Name "computeshare-agent-$Port" -DisplayName 'computeshare agent' -Direction Inbound -VMCreatorId $wslCreator -Protocol TCP -LocalPorts $Port -Action Allow -ErrorAction Stop | Out-Null
|
|
193
|
+
} catch {
|
|
194
|
+
Say "Hyper-V firewall rule not created ($($_.Exception.Message.Trim())); continuing, reachability is checked at the end."
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
# --- Agent inside the distro --------------------------------------------------------------------------------------
|
|
199
|
+
$bundleWsl = (Invoke-Wsl @('-d', $Distro, '-u', 'root', '--', 'wslpath', '-a', ($BundlePath -replace '\\', '/')) 60).out.Trim()
|
|
200
|
+
$r = Invoke-Wsl @('-d', $Distro, '-u', 'root', '--', 'sh', '-c', "tar -xzf '$bundleWsl' -C /tmp pool/install/install-agent.sh && cp '$bundleWsl' /tmp/computeshare-bundle.tar.gz") 120
|
|
201
|
+
if ($r.rc -ne 0) { Fail "could not stage the bundle in the distro: $($r.out)" }
|
|
202
|
+
$envs = "ROUTER_URL='$RouterUrl' ENROLL_TOKEN='$EnrollToken' AGENT_NAME='$AgentName' AGENT_URL='http://${tsIp}:$Port' AGENT_PORT='$Port' ALLOW_CPUS='$AllowCpus' ALLOW_MEM_GB='$AllowMemGb' BUNDLE=/tmp/computeshare-bundle.tar.gz"
|
|
203
|
+
Say 'Installing Docker, Node and the agent inside the distro (first run builds the lane image; this takes a while)'
|
|
204
|
+
& wsl.exe -d $Distro -u root -- bash -c "$envs bash /tmp/pool/install/install-agent.sh"
|
|
205
|
+
if ($LASTEXITCODE -ne 0) { Fail 'Linux-side install failed (see output above).' }
|
|
206
|
+
|
|
207
|
+
# --- Keep-alive task: keeps the distro up, holds off sleep while lanes run, refreshes portproxy on NAT -----------------
|
|
208
|
+
$keep = Join-Path $installDir 'keepalive.ps1'
|
|
209
|
+
Copy-Item (Join-Path $PSScriptRoot 'keepalive.ps1') $keep -Force
|
|
210
|
+
$taskArgs = "-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File `"$keep`" -Distro $Distro -Port $Port -TailscaleIp $tsIp" + $(if ($mirrored) { ' -Mirrored' } else { '' })
|
|
211
|
+
$action = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument $taskArgs
|
|
212
|
+
$trigger = New-ScheduledTaskTrigger -AtLogOn -User "$env:USERDOMAIN\$env:USERNAME"
|
|
213
|
+
$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -ExecutionTimeLimit ([TimeSpan]::Zero) -RestartCount 999 -RestartInterval (New-TimeSpan -Minutes 1)
|
|
214
|
+
Register-ScheduledTask -TaskName 'computeshare-keepalive' -Action $action -Trigger $trigger -Settings $settings -Description 'computeshare agent keep-alive' -Force | Out-Null
|
|
215
|
+
Start-ScheduledTask -TaskName 'computeshare-keepalive'
|
|
216
|
+
|
|
217
|
+
Start-Sleep -Seconds 5
|
|
218
|
+
try {
|
|
219
|
+
# Windows cannot reach its own Tailscale IP through the mirrored WSL network (no hairpin), so check via loopback;
|
|
220
|
+
# the router reaches the agent on the Tailscale IP.
|
|
221
|
+
$h = Invoke-RestMethod -Uri "http://127.0.0.1:$Port/health" -TimeoutSec 10
|
|
222
|
+
Say "Agent '$AgentName' is up on http://${tsIp}:$Port : $($h.cpus_total) CPUs / $($h.mem_total_gb) GB reserved for lanes."
|
|
223
|
+
} catch {
|
|
224
|
+
Say "Installed, but http://${tsIp}:$Port/health is not answering yet. Check: wsl -d $Distro -u root -- journalctl -u computeshare-agent -n 50"
|
|
225
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios
|
|
3
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
4
|
+
# Linux side of the agent install. Runs as root inside the dedicated `computeshare` WSL distro (or any systemd Linux host).
|
|
5
|
+
# Required env: ROUTER_URL ENROLL_TOKEN AGENT_NAME AGENT_URL ALLOW_CPUS ALLOW_MEM_GB BUNDLE
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
: "${ROUTER_URL:?}" "${ENROLL_TOKEN:?}" "${AGENT_NAME:?}" "${AGENT_URL:?}" "${ALLOW_CPUS:?}" "${ALLOW_MEM_GB:?}" "${BUNDLE:?}"
|
|
8
|
+
# Never resolve tools through the Windows PATH that WSL appends (e.g. Docker Desktop's docker CLI).
|
|
9
|
+
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
|
10
|
+
NODE_VERSION=22.12.0
|
|
11
|
+
PORT="${AGENT_PORT:-7711}"
|
|
12
|
+
log() { echo "[computeshare] $*"; }
|
|
13
|
+
|
|
14
|
+
[ "$(id -u)" = 0 ] || { echo "run as root" >&2; exit 1; }
|
|
15
|
+
pidof systemd >/dev/null || [ "$(ps -p 1 -o comm=)" = systemd ] || { echo "systemd is not PID 1; enable it in /etc/wsl.conf and restart the distro" >&2; exit 1; }
|
|
16
|
+
|
|
17
|
+
log "base packages"
|
|
18
|
+
export DEBIAN_FRONTEND=noninteractive
|
|
19
|
+
apt-get update -qq
|
|
20
|
+
apt-get install -y -qq ca-certificates curl git jq xz-utils >/dev/null
|
|
21
|
+
|
|
22
|
+
if [ ! -x /usr/bin/dockerd ]; then
|
|
23
|
+
log "docker engine"
|
|
24
|
+
install -d -m 0755 /etc/apt/keyrings
|
|
25
|
+
. /etc/os-release
|
|
26
|
+
DOCKER_DISTRO=ubuntu; [ "${ID:-}" = debian ] && DOCKER_DISTRO=debian
|
|
27
|
+
curl -fsSL "https://download.docker.com/linux/${DOCKER_DISTRO}/gpg" -o /etc/apt/keyrings/docker.asc
|
|
28
|
+
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/${DOCKER_DISTRO} ${VERSION_CODENAME} stable" > /etc/apt/sources.list.d/docker.list
|
|
29
|
+
apt-get update -qq
|
|
30
|
+
apt-get install -y -qq docker-ce docker-ce-cli containerd.io >/dev/null
|
|
31
|
+
fi
|
|
32
|
+
# WSL mirrored networking usually has no IPv6 route; dockerd would still dial registries over IPv6 and fail.
|
|
33
|
+
if grep -qi microsoft /proc/sys/kernel/osrelease && [ -z "$(ip -6 route show default 2>/dev/null)" ]; then
|
|
34
|
+
printf 'net.ipv6.conf.all.disable_ipv6=1\nnet.ipv6.conf.default.disable_ipv6=1\n' > /etc/sysctl.d/90-computeshare-noipv6.conf
|
|
35
|
+
sysctl -q -p /etc/sysctl.d/90-computeshare-noipv6.conf
|
|
36
|
+
systemctl restart docker 2>/dev/null || true
|
|
37
|
+
fi
|
|
38
|
+
systemctl enable --now docker >/dev/null
|
|
39
|
+
|
|
40
|
+
if [ ! -x /opt/node/bin/node ] || [ "$(/opt/node/bin/node --version)" != "v${NODE_VERSION}" ]; then
|
|
41
|
+
log "node ${NODE_VERSION}"
|
|
42
|
+
rm -rf /opt/node
|
|
43
|
+
case "$(uname -m)" in aarch64|arm64) NODE_ARCH=arm64 ;; *) NODE_ARCH=x64 ;; esac
|
|
44
|
+
curl -fsSL "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-${NODE_ARCH}.tar.xz" | tar -xJ -C /opt
|
|
45
|
+
mv "/opt/node-v${NODE_VERSION}-linux-${NODE_ARCH}" /opt/node
|
|
46
|
+
fi
|
|
47
|
+
|
|
48
|
+
log "agent code"
|
|
49
|
+
rm -rf /opt/computeshare.new && mkdir -p /opt/computeshare.new
|
|
50
|
+
tar -xzf "$BUNDLE" -C /opt/computeshare.new
|
|
51
|
+
rm -rf /opt/computeshare && mv /opt/computeshare.new /opt/computeshare
|
|
52
|
+
|
|
53
|
+
install -d -m 0700 /etc/computeshare /var/lib/computeshare
|
|
54
|
+
if [ ! -s /etc/computeshare/agent.token ]; then
|
|
55
|
+
log "enrolling ${AGENT_NAME} (${AGENT_URL}) with ${ROUTER_URL}"
|
|
56
|
+
resp=$(curl -fsS -X POST "${ROUTER_URL}/enroll" -H 'content-type: application/json' \
|
|
57
|
+
-d "$(jq -n --arg t "$ENROLL_TOKEN" --arg n "$AGENT_NAME" --arg u "$AGENT_URL" '{token:$t,name:$n,url:$u}')")
|
|
58
|
+
umask 077
|
|
59
|
+
echo "$resp" | jq -r .agent_token > /etc/computeshare/agent.token
|
|
60
|
+
[ -s /etc/computeshare/agent.token ] && [ "$(cat /etc/computeshare/agent.token)" != null ] || { echo "enrollment failed: $resp" >&2; rm -f /etc/computeshare/agent.token; exit 1; }
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
jq -n --arg name "$AGENT_NAME" --arg router "$ROUTER_URL" --argjson port "$PORT" --argjson cpus "$ALLOW_CPUS" --argjson mem "$ALLOW_MEM_GB" '{
|
|
64
|
+
name: $name, listen: {host: "0.0.0.0", port: $port}, token_file: "/etc/computeshare/agent.token",
|
|
65
|
+
router_url: $router, code_root: "/opt/computeshare",
|
|
66
|
+
state_dir: "/var/lib/computeshare",
|
|
67
|
+
allow_cpus: $cpus, allow_mem_gb: $mem, lease_ttl_s: 600, max_lifetime_s: 21600, keep_exited_s: 7200, reap_interval_s: 30
|
|
68
|
+
}' > /etc/computeshare/agent.json
|
|
69
|
+
chmod 0600 /etc/computeshare/agent.json
|
|
70
|
+
|
|
71
|
+
log "building lane image (first run takes several minutes)"
|
|
72
|
+
FD_VERSION="$(jq -r .version /opt/computeshare/package.json)"
|
|
73
|
+
docker build -q -t "forgedock-lane:${FD_VERSION}" --build-arg "FORGEDOCK_VERSION=${FD_VERSION}" /opt/computeshare/pool/image >/dev/null
|
|
74
|
+
|
|
75
|
+
cat > /etc/systemd/system/computeshare-agent.service <<EOF
|
|
76
|
+
[Unit]
|
|
77
|
+
Description=computeshare agent
|
|
78
|
+
After=docker.service network-online.target
|
|
79
|
+
Requires=docker.service
|
|
80
|
+
|
|
81
|
+
[Service]
|
|
82
|
+
ExecStart=/opt/node/bin/node /opt/computeshare/bin/pool/cli.mjs agent --config /etc/computeshare/agent.json
|
|
83
|
+
Restart=always
|
|
84
|
+
RestartSec=5
|
|
85
|
+
|
|
86
|
+
[Install]
|
|
87
|
+
WantedBy=multi-user.target
|
|
88
|
+
EOF
|
|
89
|
+
systemctl daemon-reload
|
|
90
|
+
systemctl enable computeshare-agent >/dev/null
|
|
91
|
+
systemctl restart computeshare-agent
|
|
92
|
+
|
|
93
|
+
for i in $(seq 1 20); do
|
|
94
|
+
if curl -fsS "http://127.0.0.1:${PORT}/health" >/dev/null 2>&1; then
|
|
95
|
+
log "agent healthy: $(curl -fsS "http://127.0.0.1:${PORT}/health")"
|
|
96
|
+
exit 0
|
|
97
|
+
fi
|
|
98
|
+
sleep 1
|
|
99
|
+
done
|
|
100
|
+
echo "agent did not become healthy; see: journalctl -u computeshare-agent" >&2
|
|
101
|
+
exit 1
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios
|
|
3
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
4
|
+
# Installs the computeshare router as a systemd --user service on the host that runs ForgeDock.
|
|
5
|
+
# It listens only on this machine's Tailscale IP. Re-running keeps existing tokens and agent enrollments.
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
8
|
+
CONF_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/computeshare"
|
|
9
|
+
PORT="${ROUTER_PORT:-7710}"
|
|
10
|
+
TS_IP="$(tailscale ip -4 | head -1)"
|
|
11
|
+
[[ "$TS_IP" == 100.* ]] || { echo "no Tailscale IPv4 found" >&2; exit 1; }
|
|
12
|
+
NODE="$(command -v node)"
|
|
13
|
+
|
|
14
|
+
mkdir -p "$CONF_DIR" && chmod 700 "$CONF_DIR"
|
|
15
|
+
umask 077
|
|
16
|
+
for t in forgedock enroll; do
|
|
17
|
+
[ -s "$CONF_DIR/$t.token" ] || openssl rand -hex 32 > "$CONF_DIR/$t.token"
|
|
18
|
+
done
|
|
19
|
+
|
|
20
|
+
# repos: slug -> gitignored forge.yaml that lanes need (passed to the sandbox, never committed). Extend as needed.
|
|
21
|
+
# An existing router.json is kept (re-running the installer must not drop repos or settings added later).
|
|
22
|
+
[ -s "$CONF_DIR/router.json" ] || cat > "$CONF_DIR/router.json" <<EOF
|
|
23
|
+
{
|
|
24
|
+
"listen": { "host": "$TS_IP", "port": $PORT },
|
|
25
|
+
"public_url": "http://$TS_IP:$PORT",
|
|
26
|
+
"state_dir": "$CONF_DIR/state",
|
|
27
|
+
"lane": { "cpus": ${LANE_CPUS:-2}, "mem_gb": ${LANE_MEM_GB:-3} },
|
|
28
|
+
"lease_interval_s": 60,
|
|
29
|
+
"abandon_after_s": 1800,
|
|
30
|
+
"retry_after_s": 60,
|
|
31
|
+
"repos": {}
|
|
32
|
+
}
|
|
33
|
+
EOF
|
|
34
|
+
|
|
35
|
+
mkdir -p "$HOME/.config/systemd/user"
|
|
36
|
+
cat > "$HOME/.config/systemd/user/computeshare-router.service" <<EOF
|
|
37
|
+
[Unit]
|
|
38
|
+
Description=computeshare router
|
|
39
|
+
After=network-online.target tailscaled.service
|
|
40
|
+
|
|
41
|
+
[Service]
|
|
42
|
+
ExecStart=$NODE $REPO_ROOT/bin/pool/cli.mjs router --config $CONF_DIR/router.json
|
|
43
|
+
Restart=always
|
|
44
|
+
RestartSec=5
|
|
45
|
+
|
|
46
|
+
[Install]
|
|
47
|
+
WantedBy=default.target
|
|
48
|
+
EOF
|
|
49
|
+
systemctl --user daemon-reload
|
|
50
|
+
systemctl --user enable --now computeshare-router >/dev/null
|
|
51
|
+
systemctl --user restart computeshare-router
|
|
52
|
+
loginctl enable-linger "$USER" 2>/dev/null || true
|
|
53
|
+
|
|
54
|
+
# ForgeDock reads execution.token_env from the env, then the OS keychain (service forgedock, account = the name).
|
|
55
|
+
if command -v secret-tool >/dev/null; then
|
|
56
|
+
secret-tool store --label "forgedock COMPUTESHARE_TOKEN" service forgedock account COMPUTESHARE_TOKEN < "$CONF_DIR/forgedock.token" \
|
|
57
|
+
&& echo "[computeshare] stored the ForgeDock token in the keychain as COMPUTESHARE_TOKEN"
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
for i in $(seq 1 15); do
|
|
61
|
+
curl -fsS "http://$TS_IP:$PORT/health" >/dev/null 2>&1 && break
|
|
62
|
+
sleep 1
|
|
63
|
+
done
|
|
64
|
+
echo "[computeshare] router: $(curl -fsS "http://$TS_IP:$PORT/health")"
|
|
65
|
+
echo
|
|
66
|
+
echo "Install an agent: run this in an elevated PowerShell on the Windows host:"
|
|
67
|
+
"$NODE" "$REPO_ROOT/bin/pool/cli.mjs" install-command --config "$CONF_DIR/router.json"
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios
|
|
2
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
3
|
+
# Runs at logon (scheduled task). Keeps the computeshare distro running, keeps Windows awake while lanes are active,
|
|
4
|
+
# and on NAT networking keeps a portproxy from the Tailscale IP to the distro's current address.
|
|
5
|
+
param([string] $Distro = 'computeshare', [int] $Port = 7711, [string] $TailscaleIp, [switch] $Mirrored)
|
|
6
|
+
$ErrorActionPreference = 'Continue'
|
|
7
|
+
$env:WSL_UTF8 = '1'
|
|
8
|
+
|
|
9
|
+
Add-Type -Namespace CS -Name Power -MemberDefinition '[DllImport("kernel32.dll")] public static extern uint SetThreadExecutionState(uint flags);'
|
|
10
|
+
$ES_CONTINUOUS = [uint32]'0x80000000'; $ES_SYSTEM_REQUIRED = [uint32]'0x00000001'
|
|
11
|
+
|
|
12
|
+
$holder = $null
|
|
13
|
+
while ($true) {
|
|
14
|
+
if (-not $holder -or $holder.HasExited) {
|
|
15
|
+
# A long-lived process keeps the distro (and its systemd services) from being stopped by WSL.
|
|
16
|
+
$holder = Start-Process -FilePath 'wsl.exe' -ArgumentList @('-d', $Distro, '-u', 'root', '--', 'sleep', 'infinity') -WindowStyle Hidden -PassThru
|
|
17
|
+
Start-Sleep -Seconds 5
|
|
18
|
+
}
|
|
19
|
+
if (-not $Mirrored -and $TailscaleIp) {
|
|
20
|
+
$wslIp = ((& wsl.exe -d $Distro -u root -- hostname -I) -split '\s+' | Where-Object { $_ -match '^\d+\.' } | Select-Object -First 1)
|
|
21
|
+
if ($wslIp) {
|
|
22
|
+
$cur = (netsh interface portproxy show v4tov4) -match [regex]::Escape("$TailscaleIp") -match "$Port"
|
|
23
|
+
if (-not ($cur -match [regex]::Escape($wslIp))) {
|
|
24
|
+
netsh interface portproxy delete v4tov4 listenaddress=$TailscaleIp listenport=$Port | Out-Null
|
|
25
|
+
netsh interface portproxy add v4tov4 listenaddress=$TailscaleIp listenport=$Port connectaddress=$wslIp connectport=$Port | Out-Null
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
$active = 0
|
|
30
|
+
try { $active = [int](Invoke-RestMethod -Uri "http://127.0.0.1:$Port/health" -TimeoutSec 5).active } catch {
|
|
31
|
+
if ($TailscaleIp) { try { $active = [int](Invoke-RestMethod -Uri "http://${TailscaleIp}:$Port/health" -TimeoutSec 5).active } catch {} }
|
|
32
|
+
}
|
|
33
|
+
if ($active -gt 0) { [void][CS.Power]::SetThreadExecutionState($ES_CONTINUOUS -bor $ES_SYSTEM_REQUIRED) }
|
|
34
|
+
else { [void][CS.Power]::SetThreadExecutionState($ES_CONTINUOUS) }
|
|
35
|
+
Start-Sleep -Seconds 60
|
|
36
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright (c) RapierCraft Studios
|
|
2
|
+
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
3
|
+
<#
|
|
4
|
+
.SYNOPSIS
|
|
5
|
+
Removes the computeshare agent from this Windows host: keep-alive task, firewall rules, portproxy, the dedicated WSL
|
|
6
|
+
distro (with every container, image and cache in it) and the .wslconfig changes (restored from the backup).
|
|
7
|
+
#>
|
|
8
|
+
param([string] $Distro = 'computeshare', [int] $Port = 7711)
|
|
9
|
+
$ErrorActionPreference = 'Continue'
|
|
10
|
+
$env:WSL_UTF8 = '1'
|
|
11
|
+
Unregister-ScheduledTask -TaskName 'computeshare-keepalive' -Confirm:$false -ErrorAction SilentlyContinue
|
|
12
|
+
Get-Process powershell -ErrorAction SilentlyContinue | Where-Object { $_.CommandLine -match 'computeshare\\keepalive.ps1' } | Stop-Process -Force
|
|
13
|
+
Get-NetFirewallRule -Name 'computeshare-agent' -ErrorAction SilentlyContinue | Remove-NetFirewallRule
|
|
14
|
+
if (Get-Command Get-NetFirewallHyperVRule -ErrorAction SilentlyContinue) { Get-NetFirewallHyperVRule -ErrorAction SilentlyContinue | Where-Object { $_.DisplayName -like 'computeshare*' -or $_.Name -like '*computeshare*' } | ForEach-Object { Remove-NetFirewallHyperVRule -Name $_.Name -ErrorAction SilentlyContinue } }
|
|
15
|
+
(netsh interface portproxy show v4tov4) -match " $Port " | ForEach-Object { $a = ($_ -split '\s+')[0]; if ($a) { netsh interface portproxy delete v4tov4 listenaddress=$a listenport=$Port | Out-Null } }
|
|
16
|
+
& wsl.exe --unregister $Distro
|
|
17
|
+
$cfg = Join-Path $env:USERPROFILE '.wslconfig'
|
|
18
|
+
if (Test-Path "$cfg.computeshare-backup") { Move-Item "$cfg.computeshare-backup" $cfg -Force; Write-Host 'Restored .wslconfig from backup (takes effect after wsl --shutdown).' }
|
|
19
|
+
Remove-Item -Recurse -Force (Join-Path $env:ProgramData 'computeshare') -ErrorAction SilentlyContinue
|
|
20
|
+
Write-Host '[computeshare] agent removed. Remove it from the router with: computeshare status (then delete it from router-state.json).'
|