Files
apex.ermnvldmr.com/ci/scripts/init-node/provisioning/repository.py
T
deytenit 6463bad5ee feat(engine): de-slop commons, harden security, add targeted compose and env bridge
- Purge misplaced RFCs, scratch tokens, and node-proprietary assets
- Decouple remote provisioning to ci/scripts/init-node.py
- Add targeted composition orchestration in actions/compose.py
- Add host-side Xray stale socket cleanup in /dev/shm
- Add actions/env.py for shell exports and .env synchronization
- Harden UFW, backup retention, host tempfiles, and notify dispatcher
- Add pyproject.toml and Makefile with 78 passing unit tests
2026-10-26 00:03:33 +03:00

122 lines
6.5 KiB
Python

"""Pinned, non-destructive checkout enrollment as the node administrator."""
from __future__ import annotations
import os
from pathlib import Path
import pwd
import subprocess
from engine.identity import read_env
from . import ENROLLMENT_INTERFACE
from .state import ProvisionError, safe_path
def as_adam(args, cwd=None):
environment = dict(os.environ, GIT_TERMINAL_PROMPT='0',
GIT_SSH_COMMAND='ssh -o BatchMode=yes -o StrictHostKeyChecking=yes',
LC_ALL='C')
result = subprocess.run(['runuser', '-u', 'adam', '--', *args], cwd=cwd,
env=environment, text=True, capture_output=True, timeout=300)
if result.returncode:
# Git/hook stderr may contain credential files or remote URL credentials.
raise ProvisionError('repository operation failed; check administrator Git access and the pinned checkout')
return result.stdout.strip()
def verify_checkout_setup(destination, account):
if as_adam(['git', 'config', '--get', 'core.hooksPath'], destination) != 'commons/githooks':
raise ProvisionError('checkout hook configuration is missing or incompatible')
home = safe_path(account.pw_dir)
bashrc = safe_path(home / '.bashrc')
entry = 'export PATH="' + str(destination / 'commons') + ':$PATH"'
if not bashrc.is_file() or bashrc.read_text().splitlines().count(entry) != 1:
raise ProvisionError('checkout PATH setup is missing or duplicated; reconcile init.sh before retrying')
vim = home / '.vimrc'
if not vim.is_symlink() or vim.resolve() != (destination / 'commons/etc/.vimrc').resolve():
raise ProvisionError('checkout editor link is missing or incompatible')
def reconcile_repository(spec, state, save, srv=Path('/srv')):
repository = spec.repository
destination = safe_path(Path(srv) / repository.name)
staging = safe_path(Path(srv) / ('.apex-clone-' + repository.name))
expected = {'name': repository.name, 'url': repository.url, 'ref': repository.ref}
account = pwd.getpwnam('adam')
record = state.setdefault('repository', {})
if record and record.get('request') != expected:
raise ProvisionError('repository enrollment conflicts with its recorded request')
if not record:
if destination.exists() or staging.exists():
raise ProvisionError('unrecorded repository path exists; explicit adoption is required')
record.update(request=expected, clone_intent=True)
save()
if not destination.exists():
if staging.exists():
# An interrupted clone may be incomplete: never erase or guess its state.
raise ProvisionError('an interrupted clone remains; inspect the staging checkout before retrying')
staging.mkdir(mode=0o755)
os.chown(staging, account.pw_uid, account.pw_gid)
as_adam(['git', 'clone', '--no-checkout', '--', repository.url, str(staging)])
os.rename(staging, destination)
safe_path(destination / '.git')
origin = as_adam(['git', 'remote', 'get-url', 'origin'], destination)
if origin != repository.url:
raise ProvisionError('existing checkout has a different origin; it was preserved')
if 'commit' not in record:
reference = repository.ref or 'HEAD'
if repository.ref:
as_adam(['git', 'fetch', '--no-tags', 'origin', reference], destination)
reference = 'FETCH_HEAD'
commit = as_adam(['git', 'rev-parse', '--verify', reference + '^{commit}'], destination)
record['commit'] = commit
record['checkout_intent'] = True
save()
commit = record['commit']
if record.get('checkout_intent'):
# --no-checkout index intentionally looks dirty before the first checkout.
if any(p.name != '.git' for p in destination.iterdir()):
head = as_adam(['git', 'rev-parse', 'HEAD'], destination)
dirty = as_adam(['git', 'status', '--porcelain'], destination)
if head != commit or dirty:
raise ProvisionError('interrupted checkout has unexpected contents; it was preserved')
else:
as_adam(['git', 'checkout', '--detach', commit], destination)
record.pop('checkout_intent')
save()
if as_adam(['git', 'rev-parse', 'HEAD'], destination) != commit:
raise ProvisionError('checkout revision changed; initialization will not reset it')
if as_adam(['git', 'status', '--porcelain'], destination):
raise ProvisionError('checkout contains local changes; initialization will not reset it')
node_env = safe_path(destination / 'node.env')
node_identity = read_env(str(node_env))
if node_identity.get('APEX_NODE_FQDN') != spec.hostname:
raise ProvisionError('repository node.env does not declare the requested hostname')
if node_identity.get('APEX_ENROLLMENT_INTERFACE') != str(ENROLLMENT_INTERFACE):
raise ProvisionError('node checkout enrollment interface is missing or unsupported; update its init.sh and node.env together')
as_adam(['git', 'submodule', 'update', '--init', '--recursive'], destination)
commons = safe_path(destination / 'commons')
if not commons.is_dir():
raise ProvisionError('node checkout does not contain its pinned commons submodule')
commons_commit = as_adam(['git', 'rev-parse', 'HEAD'], commons)
gitlink = as_adam(['git', 'ls-tree', 'HEAD', '--', 'commons'], destination).split()
if gitlink != ['160000', 'commit', commons_commit, 'commons']:
raise ProvisionError('commons is not the exact pinned Git submodule')
if Path(as_adam(['git', 'rev-parse', '--show-toplevel'], commons)).resolve() != commons.resolve():
raise ProvisionError('commons does not have its own Git worktree')
if record.get('commons_commit', commons_commit) != commons_commit:
raise ProvisionError('pinned commons revision changed unexpectedly')
hook = safe_path(destination / 'init.sh')
if not hook.is_file():
raise ProvisionError('node repository is missing its checkout initializer')
if not record.get('hook_complete'):
as_adam(['bash', './init.sh'], destination)
verify_checkout_setup(destination, account)
record['hook_complete'] = True
else:
verify_checkout_setup(destination, account)
if as_adam(['git', 'status', '--porcelain'], destination):
raise ProvisionError('checkout initializer changed tracked files; inspect before resuming')
record['commons_commit'] = commons_commit
save()
return {'path': str(destination), 'commit': commit, 'commons_commit': commons_commit}