|
| 1 | +#!/usr/bin/env bash |
| 2 | +# Install continuous goal work; preserve configuration. |
| 3 | +set -euo pipefail |
| 4 | +REV="${1:?Pass the pinned 40-character commit}" |
| 5 | +[[ "$REV" =~ ^[0-9a-f]{40}$ ]] || { echo 'Invalid revision' >&2; exit 1; } |
| 6 | +source "$HOME/ai-v100/env.sh" |
| 7 | +PY="$AI_V100_ROOT/venvs/v100-continual/bin/python" |
| 8 | +export SYNTA_GOAL_WORK_REV="$REV" |
| 9 | +export PATH="$HOME/.local/bin:$PATH" |
| 10 | +UV="$(command -v uv || true)" |
| 11 | +if [[ -z "$UV" || ! -x "$UV" ]]; then |
| 12 | + echo 'uv executable unavailable; preflight failed before any service change.' >&2 |
| 13 | + exit 127 |
| 14 | +fi |
| 15 | +"$UV" --version |
| 16 | +for COMMAND in git curl systemctl; do |
| 17 | + command -v "$COMMAND" >/dev/null || { echo "Required executable unavailable: $COMMAND; no service changed." >&2; exit 127; } |
| 18 | +done |
| 19 | +[[ -x "$PY" ]] || { echo 'Existing Synta Python unavailable; no service changed.' >&2; exit 127; } |
| 20 | +RESTORE_MARKER="$(mktemp "$AI_V100_ROOT/research/.update-v82-restore.XXXXXX")" |
| 21 | +export SYNTA_RESTORE_MARKER="$RESTORE_MARKER" |
| 22 | +restore_after_failure() { |
| 23 | + local failure=$? |
| 24 | + trap - ERR |
| 25 | + echo "Update failed with exit $failure. Checking preserved mission recovery." >&2 |
| 26 | + if [[ -s "$RESTORE_MARKER" ]]; then |
| 27 | + if ! "$PY" <<'RECOVER' |
| 28 | +import json |
| 29 | +import os |
| 30 | +from pathlib import Path |
| 31 | +from rlm.v100.mission import status |
| 32 | +from rlm.v100.supervisor import install |
| 33 | +root = Path(os.environ['AI_V100_ROOT']).resolve() |
| 34 | +profile = Path(Path(os.environ['SYNTA_RESTORE_MARKER']).read_text().strip()).resolve() |
| 35 | +if not profile.is_relative_to(root / 'research') or not profile.is_file(): |
| 36 | + raise SystemExit('Preserved recovery profile unavailable; inspect update journal') |
| 37 | +if status(root).get('running'): |
| 38 | + print('Mission is already running; recovery did not restart it', flush=True) |
| 39 | +else: |
| 40 | + print(json.dumps(install(root, profile), indent=2), flush=True) |
| 41 | + print('Preserved profile submitted to supervisor. Verify mission-status.', flush=True) |
| 42 | +RECOVER |
| 43 | + then |
| 44 | + echo 'Automatic mission recovery failed; inspect journal. Package rollback was not claimed.' >&2 |
| 45 | + fi |
| 46 | + fi |
| 47 | + rm -f "$RESTORE_MARKER" |
| 48 | + exit "$failure" |
| 49 | +} |
| 50 | +trap restore_after_failure ERR |
| 51 | + |
| 52 | +"$PY" <<'PY' |
| 53 | +import json |
| 54 | +import os |
| 55 | +import subprocess |
| 56 | +import time |
| 57 | +from importlib.metadata import version |
| 58 | +from pathlib import Path |
| 59 | +from rlm.v100.common import atomic_json, load_profile |
| 60 | +from rlm.v100.mission import status, stop |
| 61 | +
|
| 62 | +installed = version('rlms') |
| 63 | +if installed not in tuple(f'0.1.3+v100.{number}' for number in range(78, 83)): |
| 64 | + raise SystemExit(f'Targeted goal work update requires v78-v82; installed: {installed}') |
| 65 | +service = subprocess.run(['systemctl', '--user', 'is-active', '--quiet', 'v100-mission.service']) |
| 66 | +root = Path(os.environ['AI_V100_ROOT']) |
| 67 | +record = status(root) |
| 68 | +if not record.get('run'): |
| 69 | + raise SystemExit('No mission profile to preserve; no service changed') |
| 70 | +for line in subprocess.check_output(['ps', '-eo', 'pid=,args='], text=True).splitlines(): |
| 71 | + if 'rlm.v100.cli' in line and any(' '+mode+' ' in line+' ' for mode in |
| 72 | + ('train', 'mission-prepare', 'optimize-mtp', 'calibrate-training', 'test-mtp')): |
| 73 | + raise SystemExit('Exclusive experiment still runs; no service changed. '+line) |
| 74 | +if service.returncode == 0 and record.get('running'): |
| 75 | + source = Path(record['learning']['live_profile']) if record.get('learning', {}).get('live_profile') else Path(record['run']) / 'input-profile.json' |
| 76 | +else: |
| 77 | + checkpoint = root / 'research/availability-recovery-update/current.json' |
| 78 | + if not checkpoint.is_file(): |
| 79 | + raise SystemExit('Stopped mission has no preserved updater profile; no service changed') |
| 80 | + source = Path(json.loads(checkpoint.read_text())['profile']).resolve() |
| 81 | + if not source.is_relative_to(root.resolve() / 'research/availability-recovery-update'): |
| 82 | + raise SystemExit('Preserved profile escaped updater folder; no service changed') |
| 83 | + print('Recovering previously stopped update from:', source, flush=True) |
| 84 | +profile = load_profile(source, root) |
| 85 | +folder = root / 'research/availability-recovery-update' / str(time.time_ns()) |
| 86 | +folder.mkdir(parents=True) |
| 87 | +atomic_json(folder / 'before.json', record) |
| 88 | +atomic_json(folder / 'profile.json', profile) |
| 89 | +atomic_json(root / 'research/availability-recovery-update/current.json', {'profile': str(folder / 'profile.json')}) |
| 90 | +print('Preserved profile:', folder / 'profile.json', flush=True) |
| 91 | +Path(os.environ['SYNTA_RESTORE_MARKER']).write_text(str(folder / 'profile.json')) |
| 92 | +print(stop(root), flush=True) |
| 93 | +subprocess.run(['systemctl', '--user', 'stop', 'v100-mission.service'], check=True, timeout=90) |
| 94 | +if status(root)['running']: |
| 95 | + raise SystemExit('Mission still running; package not changed') |
| 96 | +PY |
| 97 | + |
| 98 | +export UV_HTTP_TIMEOUT=600 UV_HTTP_RETRIES=5 |
| 99 | +"$UV" --no-config pip install --python "$PY" --no-deps --reinstall-package rlms \ |
| 100 | + "rlms @ git+https://github.com/Mantiee/rlm.git@$REV" |
| 101 | +"$UV" --no-config pip check --python "$PY" |
| 102 | +"$PY" <<'PY' |
| 103 | +import json |
| 104 | +import os |
| 105 | +from importlib.metadata import version |
| 106 | +from pathlib import Path |
| 107 | +from rlm.v100.supervisor import install |
| 108 | +from rlm.v100.mission import status |
| 109 | +import time |
| 110 | +
|
| 111 | +root = Path(os.environ['AI_V100_ROOT']) |
| 112 | +if version('rlms') != '0.1.3+v100.82': |
| 113 | + raise SystemExit('Pinned revision is not the v82 updater recovery update; mission remains stopped') |
| 114 | +path = Path(json.loads((root / 'research/availability-recovery-update/current.json').read_text())['profile']) |
| 115 | +print(json.dumps(install(root, path), indent=2), flush=True) |
| 116 | +deadline = time.monotonic() + 30 |
| 117 | +while time.monotonic() < deadline: |
| 118 | + record = status(root) |
| 119 | + if record.get('running'): |
| 120 | + print(json.dumps(record, indent=2), flush=True) |
| 121 | + break |
| 122 | + time.sleep(0.5) |
| 123 | +else: |
| 124 | + raise SystemExit('Supervisor installed, but mission did not start within 30 seconds; inspect v100-mission.service journal') |
| 125 | +PY |
| 126 | +curl -fL --retry 3 "https://raw.githubusercontent.com/Mantiee/rlm/$REV/tools/serve-v100-dashboard.py" -o "$AI_V100_ROOT/bin/serve-v100-dashboard.py.new" |
| 127 | +"$PY" -m py_compile "$AI_V100_ROOT/bin/serve-v100-dashboard.py.new" |
| 128 | +mv "$AI_V100_ROOT/bin/serve-v100-dashboard.py.new" "$AI_V100_ROOT/bin/serve-v100-dashboard.py" |
| 129 | +systemctl --user restart v100-dashboard.service |
| 130 | +systemctl --user try-restart synta-remote.service |
| 131 | +if ! "$PY" <<'PY' |
| 132 | +import json |
| 133 | +from rlm.v100.dashboard_health import wait |
| 134 | +import os |
| 135 | +from pathlib import Path |
| 136 | +print(json.dumps(wait(60, root=Path(os.environ['AI_V100_ROOT'])), indent=2)) |
| 137 | +PY |
| 138 | +then |
| 139 | + systemctl --user status v100-dashboard.service --no-pager -l || true |
| 140 | + journalctl --user -u v100-dashboard.service -n 30 --no-pager || true |
| 141 | + exit 1 |
| 142 | +fi |
| 143 | +echo 'v82 installed: mission restarted with its preserved profile. Goals, weights, policy, balances and archives retained. Reload dashboard once.' |
| 144 | + |
| 145 | +curl -fL --retry 3 "https://raw.githubusercontent.com/Mantiee/rlm/$REV/tools/qualify-synta-models-debian.sh" -o "$AI_V100_ROOT/qualify-synta-models-debian.sh" |
| 146 | + |
| 147 | +trap - ERR |
| 148 | +rm -f "$RESTORE_MARKER" |
0 commit comments