Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: CI

on:
pull_request:
push:
branches:
- main

permissions:
contents: read

concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
quality:
runs-on: [self-hosted, linux, x64, platform-ci, visible-value]
timeout-minutes: 20
steps:
- name: Check out source
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
fetch-depth: 0

- name: TEST
run: ops/ci/test
49 changes: 49 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
name: Deploy

# DEPLOY and VERIFY run only for the exact SHA that the main-branch CI run just
# tested successfully. workflow_run always executes this file as it exists on the
# default branch, so a pull request cannot change what deploys. The deploy runner
# additionally refuses every job that is not this file on main.
on:
workflow_run:
workflows: [CI]
types: [completed]
branches: [main]

permissions:
contents: read

concurrency:
group: deploy-visible-value
cancel-in-progress: false

jobs:
deploy:
# VISIBLE_VALUE_DEPLOY_ENABLED is the single switch that makes GitHub the release owner.
# Leave it unset while another release path is authoritative.
if: >-
vars.VISIBLE_VALUE_DEPLOY_ENABLED == 'true' &&
github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.event == 'push' &&
github.event.workflow_run.head_branch == 'main'
runs-on: [self-hosted, linux, x64, visible-value-deploy]
timeout-minutes: 30
env:
SHA: ${{ github.event.workflow_run.head_sha }}
steps:
- name: Check out the tested SHA
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
ref: ${{ github.event.workflow_run.head_sha }}

- name: DEPLOY
id: deploy
run: |
status=0
ops/ci/deploy "$SHA" || status=$?
if [[ $status -eq 75 ]]; then echo "superseded=true" >>"$GITHUB_OUTPUT"; exit 0; fi
exit "$status"

- name: VERIFY
if: steps.deploy.outputs.superseded != 'true'
run: ops/ci/verify "$SHA"
20 changes: 20 additions & 0 deletions ops/ci/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# ops/ci — canonical lifecycle for opsle/visible-value

```
AI → PR → TEST → MERGE → TEST merged SHA → DEPLOY → VERIFY
```

| Command | Runs on | Holds |
| --- | --- | --- |
| `ops/ci/test` | `platform-ci,visible-value` runner (`ci.yml`, job `quality`) | no production credentials |
| `ops/ci/deploy SHA` | `visible-value-deploy` runner (`deploy.yml`) | write access (POSIX ACL) to `/opt/opsle-components/visible-value` |
| `ops/ci/verify SHA` | deploy runner, or anywhere | nothing; read-only |

- **Merge enforcement:** `opsle` is on a free GitHub plan (no rulesets, branch protection or auto-merge). Merge only with `gh pr merge --squash --delete-branch` after `quality` passes.
- **Exact SHA:** `deploy.yml` is a `workflow_run` of the `push`-to-`main` CI run and checks out and deploys `workflow_run.head_sha`. `ops/ci/deploy` refuses anything that is not the tip of main (exit 75 = superseded) or a checkout that is not exactly that SHA.
- **Atomic switch:** releases are deployed under `/opt/opsle-components/visible-value/releases/<sha>` and atomically pointed to by `current`.
- **Single release owner:** `deploy.yml` does nothing unless repository variable `VISIBLE_VALUE_DEPLOY_ENABLED` is `true`.

## Operator gates (root; not doable by agents)

Run `bash ops/ci/operator-gate test deploy` in a terminal. It mints the registration tokens and installs the self-hosted runners.
60 changes: 60 additions & 0 deletions ops/ci/deploy
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#!/usr/bin/env bash
# Canonical DEPLOY stage: publish one exact merged SHA as a component release.
# ops/ci/deploy SHA run from a clean checkout of exactly SHA
set -Eeuo pipefail
cd "$(dirname "${BASH_SOURCE[0]}")/../.."

readonly sha="${1:-}"
[[ $sha =~ ^[0-9a-f]{40}$ ]] || { echo "usage: ops/ci/deploy SHA" >&2; exit 64; }
readonly component="visible-value"
readonly release_root="${OPSLE_COMPONENT_ROOT:-/opt/opsle-components}/$component"

fail() { echo "deploy: $1" >&2; exit 1; }

[[ "$(git rev-parse HEAD)" == "$sha" ]] || fail "the checkout is not exactly $sha"
# Only the current tip of main may deploy; an older merge is simply superseded.
tip="$(git ls-remote origin refs/heads/main | cut -f1)"
if [[ $tip != "$sha" ]]; then
echo "deploy: $sha is not the tip of main ($tip); superseded, nothing deployed" >&2
exit 75
fi
[[ -z "$(git status --porcelain --untracked-files=normal)" ]] || fail "the checkout is not clean"

mkdir -p "$release_root/releases" "$release_root/receipts"
exec 9>"$release_root/.release.lock"
flock -n 9 || fail "another $component release is running"

release_dir="$release_root/releases/$sha"
previous="$(readlink "$release_root/current" || true)"

if [[ ! -d $release_dir ]]; then
candidate="$release_root/releases/.candidate-$sha-$$"
trap 'rm -rf -- "$candidate"' EXIT
mkdir -p "$candidate"
git archive "$sha" | tar -x -C "$candidate"
npm --prefix "$candidate" pack --pack-destination "$candidate" 2>/dev/null || true
chmod -R a+rX "$candidate"
mv "$candidate" "$release_dir"
trap - EXIT
fi

switch() {
ln -sfn "$1" "$release_root/current.next"
mv -Tf "$release_root/current.next" "$release_root/current"
}

switch "$release_dir"
printf '%s\n' "$previous" >"$release_root/previous-release"

receipt="$release_root/receipts/deploy-$sha.json"
cat >"$receipt" <<RECEIPT
{
"component": "$component",
"revision": "$sha",
"previous": "${previous##*/}",
"deployed_at": "$(date -u +%FT%TZ)",
"status": "active"
}
RECEIPT

echo "deploy: released $component $sha at $release_root"
92 changes: 92 additions & 0 deletions ops/ci/install-deploy-runner
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#!/usr/bin/env bash
# One-time OPERATOR gate (run as root on the VPS host): install the deploy
# runner used only by .github/workflows/deploy.yml.
# sudo bash ops/ci/install-deploy-runner < registration-token-file
set -Eeuo pipefail

readonly account=visible-value-deployer
readonly home=/var/lib/visible-value-deployer
readonly runner_dir="$home/runner"
readonly support=/usr/local/lib/visible-value-deploy-runner
readonly release_root=/opt/opsle-components/visible-value
readonly repo=opsle/visible-value
readonly repo_url="https://github.com/$repo"
readonly unit=visible-value-deploy-runner.service
readonly version=2.336.0
readonly archive_sha=04cf0be1aff4c3ec3554466c39124ca250e3effd8873bb7e8d68535aa9505d5d
readonly archive_url="https://github.com/actions/runner/releases/download/v$version/actions-runner-linux-x64-$version.tar.gz"

[[ $EUID -eq 0 ]] || { echo "Run as root." >&2; exit 77; }
command -v setfacl >/dev/null || { echo "setfacl is required." >&2; exit 69; }
IFS= read -r token
[[ $token =~ ^[A-Za-z0-9]{20,64}$ ]] || { echo "Expected one registration token on stdin." >&2; exit 64; }

mkdir -p "$release_root"
id "$account" >/dev/null 2>&1 ||
useradd --system --home-dir "$home" --create-home --shell /usr/sbin/nologin "$account"
install -d -o "$account" -g "$account" -m 0700 "$runner_dir"

setfacl -R -m "u:$account:rwX" "$release_root" 2>/dev/null || true
setfacl -R -d -m "u:$account:rwX" "$release_root" 2>/dev/null || true
setfacl -R -m "u:deploy:rwX" "$release_root" 2>/dev/null || true
setfacl -R -d -m "u:deploy:rwX" "$release_root" 2>/dev/null || true

install -d -o root -g root -m 0755 "$support"
cat >"$support/job-started.sh" <<'HOOK'
#!/usr/bin/env bash
set -Eeuo pipefail
[[ ${GITHUB_WORKFLOW_REF:-} == "opsle/visible-value/.github/workflows/deploy.yml@refs/heads/main" ]] &&
[[ ${GITHUB_EVENT_NAME:-} == workflow_run ]] || {
echo "visible-value deploy runner refuses ${GITHUB_WORKFLOW_REF:-unknown} (${GITHUB_EVENT_NAME:-unknown})" >&2
exit 78
}
HOOK
cat >"$support/job-completed.sh" <<'HOOK'
#!/usr/bin/env bash
set -Eeuo pipefail
find /var/lib/visible-value-deployer/runner/_work -xdev -mindepth 1 -delete
HOOK
chmod 0755 "$support/job-started.sh" "$support/job-completed.sh"

archive="$(mktemp)"
trap 'rm -f -- "$archive"' EXIT
curl --fail --silent --show-error --location --output "$archive" "$archive_url"
[[ "$(sha256sum "$archive" | cut -d' ' -f1)" == "$archive_sha" ]] || { echo "Runner archive hash mismatch." >&2; exit 65; }
tar -xzf "$archive" -C "$runner_dir"
chown -R "$account:$account" "$runner_dir"

runuser -u "$account" -- env HOME="$home" "$runner_dir/config.sh" \
--unattended --replace \
--url "$repo_url" --token "$token" \
--name visible-value-deploy-vps --labels visible-value-deploy --work "$runner_dir/_work"

cat >"/etc/systemd/system/$unit" <<UNIT
[Unit]
Description=Visible Value deploy runner (deploy.yml on main only)
After=network-online.target
Wants=network-online.target

[Service]
User=$account
Group=$account
WorkingDirectory=$runner_dir
ExecStart=$runner_dir/bin/Runner.Listener run --once
Restart=always
RestartSec=10s
Environment=HOME=$home
Environment=ACTIONS_RUNNER_HOOK_JOB_STARTED=$support/job-started.sh
Environment=ACTIONS_RUNNER_HOOK_JOB_COMPLETED=$support/job-completed.sh
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=$home /opt/opsle-components/visible-value
InaccessiblePaths=-/home/deploy -/root -/var/run/docker.sock
UMask=0077

[Install]
WantedBy=multi-user.target
UNIT
systemctl daemon-reload
systemctl enable --now "$unit"
echo "Installed $unit (label visible-value-deploy)."
90 changes: 90 additions & 0 deletions ops/ci/install-test-runner
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#!/usr/bin/env bash
# One-time OPERATOR gate (run as root on the VPS host): install the repo-scoped
# TEST runner used by .github/workflows/ci.yml.
# sudo bash ops/ci/install-test-runner < registration-token-file
set -Eeuo pipefail

readonly account=visible-value-ci
readonly home=/var/lib/visible-value-ci
readonly runner_dir="$home/runner"
readonly support=/usr/local/lib/visible-value-ci-runner
readonly repo_url=https://github.com/opsle/visible-value
readonly unit=visible-value-ci-runner.service
readonly version=2.336.0
readonly archive_sha=04cf0be1aff4c3ec3554466c39124ca250e3effd8873bb7e8d68535aa9505d5d
readonly archive_url="https://github.com/actions/runner/releases/download/v$version/actions-runner-linux-x64-$version.tar.gz"

[[ $EUID -eq 0 ]] || { echo "Run as root." >&2; exit 77; }
IFS= read -r token
[[ $token =~ ^[A-Za-z0-9]{20,64}$ ]] || { echo "Expected one registration token on stdin." >&2; exit 64; }
for tool in git node npm; do command -v "$tool" >/dev/null || { echo "$tool is required on the host." >&2; exit 69; }; done

id "$account" >/dev/null 2>&1 ||
useradd --system --home-dir "$home" --create-home --shell /usr/sbin/nologin "$account"
install -d -o "$account" -g "$account" -m 0700 "$runner_dir" "$home/cache"

install -d -o root -g root -m 0755 "$support"
cat >"$support/job-started.sh" <<'HOOK'
#!/usr/bin/env bash
set -Eeuo pipefail
for variable in DATABASE_URL VISIBLE_VALUE_DEPLOY_SSH_KEY OPENAI_API_KEY OPSLE_CODEX_AUTH; do
[[ -z ${!variable:-} ]] || { echo "TEST runner rejected $variable" >&2; exit 78; }
done
for path in /home/deploy/.config/gh/hosts.yml /var/run/docker.sock; do
if [[ -r $path || -w $path ]]; then echo "TEST runner can reach $path" >&2; exit 78; fi
done
HOOK
cat >"$support/job-completed.sh" <<'HOOK'
#!/usr/bin/env bash
set -Eeuo pipefail
find /var/lib/visible-value-ci/runner/_work -xdev -mindepth 1 -delete
HOOK
chmod 0755 "$support/job-started.sh" "$support/job-completed.sh"

archive="$(mktemp)"
trap 'rm -f -- "$archive"' EXIT
curl --fail --silent --show-error --location --output "$archive" "$archive_url"
[[ "$(sha256sum "$archive" | cut -d' ' -f1)" == "$archive_sha" ]] || { echo "Runner archive hash mismatch." >&2; exit 65; }
tar -xzf "$archive" -C "$runner_dir"
chown -R "$account:$account" "$runner_dir"

runuser -u "$account" -- env HOME="$home" "$runner_dir/config.sh" \
--unattended --replace \
--url "$repo_url" --token "$token" \
--name platform-ci-visible-value-vps --labels platform-ci,visible-value --work "$runner_dir/_work"

cat >"/etc/systemd/system/$unit" <<UNIT
[Unit]
Description=Visible Value TEST runner (ci.yml)
After=network-online.target
Wants=network-online.target

[Service]
User=$account
Group=$account
WorkingDirectory=$runner_dir
ExecStart=$runner_dir/bin/Runner.Listener run --once
Restart=always
RestartSec=10s
Environment=HOME=$home
Environment=CI=true
Environment=RUNNER_TOOL_CACHE=$home/cache/tool
Environment=ACTIONS_RUNNER_HOOK_JOB_STARTED=$support/job-started.sh
Environment=ACTIONS_RUNNER_HOOK_JOB_COMPLETED=$support/job-completed.sh
NoNewPrivileges=true
PrivateTmp=true
PrivateDevices=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=$home
InaccessiblePaths=-/home/deploy -/root -/var/run/docker.sock
CapabilityBoundingSet=
RestrictSUIDSGID=true
UMask=0077

[Install]
WantedBy=multi-user.target
UNIT
systemctl daemon-reload
systemctl enable --now "$unit"
echo "Installed $unit (labels platform-ci,visible-value)."
33 changes: 33 additions & 0 deletions ops/ci/operator-gate
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/usr/bin/env bash
# Operator helper for the root-only gates for opsle/visible-value.
# bash ops/ci/operator-gate test install the TEST runner
# bash ops/ci/operator-gate deploy install the deploy runner
set -Eeuo pipefail
cd "$(dirname "${BASH_SOURCE[0]}")/../.."

readonly repo=opsle/visible-value

token() { gh api -X POST "repos/$repo/actions/runners/registration-token" --jq .token; }
online() {
local _ found
for _ in {1..30}; do
found="$(gh api "repos/$repo/actions/runners" --jq ".runners[] | select(.name==\"$1\" and .status==\"online\") | .name")"
if [[ -n $found ]]; then echo "runner $1 is online"; return 0; fi
sleep 2
done
echo "runner $1 did not come online" >&2; return 1
}

[[ $# -gt 0 ]] || { echo "usage: ops/ci/operator-gate test|deploy ..." >&2; exit 64; }
sudo -v
for step in "$@"; do
case "$step" in
test)
token | sudo bash ops/ci/install-test-runner
online platform-ci-visible-value-vps ;;
deploy)
token | sudo bash ops/ci/install-deploy-runner
online visible-value-deploy-vps ;;
*) echo "unknown step: $step" >&2; exit 64 ;;
esac
done
13 changes: 13 additions & 0 deletions ops/ci/test
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/usr/bin/env bash
# Canonical TEST stage for opsle/visible-value. GitHub Actions and agents run this same command.
# ops/ci/test
set -Eeuo pipefail
cd "$(dirname "${BASH_SOURCE[0]}")/../.."

step() { printf '\n==> %s\n' "$*"; }

step "Run test suite"
npm test

step "Test CLI entry point"
node bin/visible-value.js summarize examples/run.json >/dev/null
Loading
Loading