← Files LogForgeARCHIVED FILE
scripts/logforge_setup.py
20.1 KB · Nov 3, 2026 · 00:14 UTC
"""Provision an owned, loopback-only LogForge appliance; never adopt or delete user resources."""
from __future__ import annotations
import argparse
from contextlib import contextmanager
import hashlib
import http.cookiejar
import json
import os
from pathlib import Path
import platform
import secrets
import shutil
import socket
import ssl
import subprocess
import sys
import tempfile
import time
import urllib.error
import urllib.parse
import urllib.request
import uuid
import logforge_mcp as bridge
APPLIANCE_IMAGE = 'logforge/unicron@sha256:80ce7b65e1950d026f16789f1feb117c3d20894de83a3d5cb0729ffbc3c7a93a'
AGENT_IMAGE = 'logforge/unicron-agent@sha256:328adfef3bb1efc02a26390591b2c68d282d4c0b6c1b5034ffda1d5aa3fea197'
LABEL = 'dev.logforge.codex.owner'
INSTALL_DOCS = {
'Windows': 'https://docs.docker.com/desktop/setup/install/windows-install/',
'Darwin': 'https://docs.docker.com/desktop/setup/install/mac-install/',
'Linux': 'https://docs.docker.com/engine/install/',
}
class SetupError(Exception):
pass
def home():
return Path(os.environ.get('LOGFORGE_SETUP_HOME', str(bridge.config_path().parent / 'managed'))).expanduser().resolve()
def run(args, *, check=True, timeout=120):
try:
result = subprocess.run(args, capture_output=True, text=True, encoding='utf-8', timeout=timeout)
except (OSError, subprocess.TimeoutExpired):
raise SetupError('Docker could not complete the operation; check installation and engine availability.') from None
if check and result.returncode:
# Docker error output can contain an environment value; do not export it.
raise SetupError('Docker operation failed: ' + ' '.join(args[:2]) + '. Existing data is preserved; run status and inspect Docker locally.')
return result
def docker_ready():
if not shutil.which('docker'):
raise SetupError('Docker is not installed. Install it using ' + INSTALL_DOCS.get(platform.system(), INSTALL_DOCS['Linux']) + '. Local LogForge.out diagnostics remain available without Docker.')
result = run(['docker', 'info', '--format', '{{.OSType}}'], check=False, timeout=20)
if result.returncode:
raise SetupError('Docker is installed but its engine is unavailable. Start Docker Desktop or the Docker Engine service, then retry.')
if result.stdout.strip() != 'linux':
raise SetupError('The LogForge appliance requires Docker Linux containers. Switch Docker to its Linux engine.')
explicit_context = os.environ.get('DOCKER_CONTEXT')
endpoint = None if explicit_context else os.environ.get('DOCKER_HOST')
if not endpoint:
context = explicit_context or run(['docker', 'context', 'show']).stdout.strip()
endpoint = run(['docker', 'context', 'inspect', context, '--format', '{{.Endpoints.docker.Host}}']).stdout.strip()
if not endpoint.startswith(('unix://', 'npipe://')):
raise SetupError('Automatic provisioning requires a local Docker socket/context. A remote Docker context was detected; use an explicitly reviewed remote deployment instead.')
def private_write(path, value):
path.parent.mkdir(parents=True, exist_ok=True)
if os.name != 'nt':
path.parent.chmod(0o700)
temporary = path.with_name(path.name + '.tmp')
fd = os.open(temporary, os.O_CREAT | os.O_TRUNC | os.O_WRONLY, 0o600)
with os.fdopen(fd, 'w', encoding='utf-8') as handle:
json.dump(value, handle, indent=2)
temporary.replace(path)
if os.name != 'nt':
path.chmod(0o600)
@contextmanager
def setup_lock():
home().mkdir(parents=True, exist_ok=True)
if os.name != 'nt':
home().chmod(0o700)
with open(home() / 'setup.lock', 'a+b') as handle:
handle.seek(0, 2)
if handle.tell() == 0:
handle.write(b'0'); handle.flush()
handle.seek(0)
try:
if os.name == 'nt':
import msvcrt
msvcrt.locking(handle.fileno(), msvcrt.LK_NBLCK, 1)
else:
import fcntl
fcntl.flock(handle.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
except OSError:
raise SetupError('Another LogForge setup operation is running.') from None
try:
yield
finally:
handle.seek(0)
if os.name == 'nt':
msvcrt.locking(handle.fileno(), msvcrt.LK_UNLCK, 1)
else:
fcntl.flock(handle.fileno(), fcntl.LOCK_UN)
def read_state():
path = home() / 'state.json'
if not path.exists():
return None
state = json.loads(path.read_text(encoding='utf-8'))
uuid.UUID(state['owner'])
if state.get('schema_version') != 1:
raise SetupError('Unsupported managed-instance state. No Docker resources were changed.')
return state
def names(state):
base = 'logforge-codex-' + uuid.UUID(state['owner']).hex[:12]
return {'central': base, 'network': base, 'volume': base + '-data', 'agent': base + '-agent', 'agent_volume': base + '-agent-data'}
def owned_object(kind, name, state):
result = run(['docker', kind, 'inspect', name], check=False)
if result.returncode:
return None
info = json.loads(result.stdout)[0]
labels = info.get('Config', {}).get('Labels') if kind == 'container' else info.get('Labels')
if (labels or {}).get(LABEL) != state['owner']:
raise SetupError('A resource with the expected name belongs to another owner: ' + name + '. It was not modified.')
return info
def free_ports():
sockets = []
try:
for _ in range(3):
sock = socket.socket(); sock.bind(('127.0.0.1', 0)); sockets.append(sock)
return dict(zip(('http', 'https', 'mtls'), [s.getsockname()[1] for s in sockets]))
finally:
for sock in sockets:
sock.close()
def environment(state):
if 'environment_dpapi' in state:
return json.loads(bridge.protect_secret(state['environment_dpapi'], decrypt=True))
return state['environment']
def new_state():
env = {key: secrets.token_hex(32) for key in ('POSTGRES_PASSWORD', 'CENTRAL_AUTH_SECRET', 'INTERNAL_API_SECRET',
'DEPLOYMENT_ENCRYPTION_KEY', 'CSRF_COOKIE_SECRET', 'CSRF_SECRET', 'STEP_CA_PASSWORD', 'STEP_CA_PROVISIONER_PASSWORD', 'STEP_CA_RA_PASSWORD')}
env.update(UNICRON_CENTRAL_FQDN='localhost', CENTRAL_ADMIN_USERNAME='admin',
CENTRAL_ADMIN_PASSWORD='Lf!9aA' + secrets.token_urlsafe(32), CENTRAL_ADMIN_RECOVERY_OVERRIDE='false', REMOTE_AGENT_IMAGE=AGENT_IMAGE)
state = {'schema_version': 1, 'owner': str(uuid.uuid4()), 'ports': free_ports()}
if os.name == 'nt':
state['environment_dpapi'] = bridge.protect_secret(json.dumps(env))
else:
state['environment'] = env
return state
@contextmanager
def env_file(env):
fd, filename = tempfile.mkstemp(prefix='launch-', suffix='.env', dir=home())
try:
with os.fdopen(fd, 'w', encoding='utf-8', newline='\n') as handle:
for key, value in env.items():
if '\n' in str(value) or '\r' in str(value):
raise SetupError('Invalid environment value')
handle.write(key + '=' + str(value) + '\n')
yield filename
finally:
Path(filename).unlink(missing_ok=True)
def create_storage(kind, name, state):
if owned_object(kind, name, state) is None:
run(['docker', kind, 'create', '--label', LABEL + '=' + state['owner'], name])
def ensure_central(state):
resources = names(state)
info = owned_object('container', resources['central'], state)
if info:
if not info['State']['Running']:
run(['docker', 'start', resources['central']])
return
create_storage('network', resources['network'], state)
create_storage('volume', resources['volume'], state)
state['ports'] = free_ports()
private_write(home() / 'state.json', state)
args = ['docker', 'run', '-d', '--name', resources['central'], '--label', LABEL + '=' + state['owner'],
'--restart', 'unless-stopped', '--read-only', '--tmpfs', '/tmp:rw,nosuid,nodev,mode=1777,size=256m',
'--tmpfs', '/run:rw,nosuid,nodev,mode=755,size=64m', '--tmpfs', '/run/pyinstaller:rw,nosuid,nodev,exec,mode=1777,size=256m',
'--cap-drop', 'ALL', '--security-opt', 'no-new-privileges:true', '--network', resources['network'], '--network-alias', 'unicron.central']
for capability in ('CHOWN', 'DAC_OVERRIDE', 'FOWNER', 'KILL', 'SETGID', 'SETUID', 'NET_BIND_SERVICE'):
args += ['--cap-add', capability]
for host in ('unicron-stepca', 'unicron-stepca-ra', 'unicron.central', 'localhost'):
args += ['--add-host', host + ':127.0.0.1']
for role, internal in (('http', 80), ('https', 443), ('mtls', 8443)):
args += ['-p', f'127.0.0.1:{state["ports"][role]}:{internal}']
env = environment(state)
env.update(UNICRON_PUBLIC_CENTRAL_PORT=str(state['ports']['https']), UNICRON_PUBLIC_CENTRAL_MTLS_PORT=str(state['ports']['mtls']),
UNICRON_APPLIANCE_CONTAINER_NAME=resources['central'], LOCAL_AGENT_DOCKER_NETWORK=resources['network'],
LOCAL_AGENT_CENTRAL_URL='https://unicron.central/unicron', UNICRON_SELF_UPDATE_ENABLED='false', TMPDIR='/run/pyinstaller')
with env_file(env) as filename:
run(args + ['--env-file', filename, '-v', resources['volume'] + ':/var/lib/unicron', APPLIANCE_IMAGE], timeout=600)
def wait_ready(state, timeout):
deadline = time.monotonic() + timeout
while time.monotonic() < deadline:
info = owned_object('container', names(state)['central'], state)
if info and info['State'].get('Health', {}).get('Status') == 'healthy':
return
if info and info['State'].get('Status') in ('exited', 'dead'):
raise SetupError('The managed LogForge container stopped during startup. Its data is preserved; inspect its Docker logs locally.')
time.sleep(2)
raise SetupError('LogForge is still starting or unhealthy. Retry start after checking Docker; no volumes were deleted.')
def admin_client(state):
ca = home() / 'root_ca.crt'
owned_object('container', names(state)['central'], state)
run(['docker', 'cp', names(state)['central'] + ':/var/lib/unicron/pki/trust/root_ca.crt', str(ca)])
base = f'https://localhost:{state["ports"]["https"]}'
jar = http.cookiejar.CookieJar()
opener = urllib.request.build_opener(bridge.NoRedirect(), urllib.request.HTTPCookieProcessor(jar),
urllib.request.HTTPSHandler(context=ssl.create_default_context(cafile=str(ca))))
def request(path, data=None):
req = urllib.request.Request(base + path, data=json.dumps(data).encode() if data is not None else None,
headers={'Content-Type': 'application/json', 'Origin': base})
try:
with opener.open(req, timeout=30) as response:
return json.load(response)
except urllib.error.HTTPError as error:
raise SetupError('Managed LogForge setup API ' + path + ' returned HTTP ' + str(error.code) + '. No credentials or response body were printed.') from None
except (urllib.error.URLError, ValueError, TimeoutError):
raise SetupError('Managed LogForge setup API is unavailable with trusted TLS. Retry when the appliance is healthy.') from None
env = environment(state)
request('/unicron/auth/api/auth/sign-in/username', {'username': env['CENTRAL_ADMIN_USERNAME'], 'password': env['CENTRAL_ADMIN_PASSWORD']})
return request, base, ca
def ensure_agent(state, request, ca):
resources = names(state)
info = owned_object('container', resources['agent'], state)
if info:
if not info['State']['Running']:
run(['docker', 'start', resources['agent']])
return
create_storage('volume', resources['agent_volume'], state)
result = request('/unicron/api/agent/enroll', {'agent_name': resources['agent'], 'install_target': 'local', 'queue_mode': 'memory', 'memory_queue_mb': 32})
if result.get('ok') is not True or not isinstance(result.get('token'), str):
raise SetupError('Managed agent enrollment was not accepted.')
fingerprint = hashlib.sha256(ssl.PEM_cert_to_DER_cert(ca.read_text())).hexdigest()
env = {'AGENT_NAME': resources['agent'], 'HOST_ID': resources['agent'], 'HERALD_NAME': resources['agent'], 'HERALD_ID': resources['agent'],
'CENTRAL_URL': 'https://unicron.central/unicron', 'CENTRAL_MTLS_URL': 'https://unicron.central:8443',
'CENTRAL_WS_URL': 'wss://unicron.central:8443/unicron/api/agent/ws', 'ENROLL_TOKEN': result['token'], 'CA_FINGERPRINT': fingerprint,
'HERALD_CA_ROOT': '/agent-data/certs/root_ca.crt', 'HERALD_CERT': '/agent-data/certs/agent.crt', 'HERALD_KEY': '/agent-data/certs/agent.key',
'TELEMETRY_MODE': 'hybrid', 'TELEMETRY_QUEUE_MODE': 'memory', 'TELEMETRY_MEMORY_QUEUE_MB': '32', 'FLB_MEM_BUF_LIMIT': '32MB',
'FLB_TAIL_DB_PATH': '/dev/shm/flb_monitored.db', 'ENVIRONMENT': 'development'}
# Construct the command ourselves. Never execute the server's shell command or its reset/deletion preamble.
with env_file(env) as filename:
run(['docker', 'run', '-d', '--name', resources['agent'], '--label', LABEL + '=' + state['owner'], '--restart', 'unless-stopped',
'--network', resources['network'], '--env-file', filename, '-v', '/var/run/docker.sock:/var/run/docker.sock',
'-v', '/var/lib/docker/containers:/var/lib/docker/containers:ro', '-v', '/:/host/root:ro',
'-v', resources['agent_volume'] + ':/agent-data', AGENT_IMAGE], timeout=600)
def wait_agent(state, request, timeout):
deadline = time.monotonic() + timeout
while time.monotonic() < deadline:
agents = request('/unicron/api/agent/list').get('agents', [])
for agent in agents:
if agent.get('agent_name') == names(state)['agent'] and agent.get('status') == 'online' and agent.get('container_count', 0) > 0:
return {'agent': agent['agent_name'], 'status': 'online', 'inventory_received': True}
time.sleep(2)
raise SetupError('The managed Docker agent has not connected and delivered inventory. Check its logs locally; a running container alone is not verified monitoring.')
def status():
result = {'local_diagnostics': 'available without Docker or a configured appliance', 'platform': platform.system(),
'docker_install_docs': INSTALL_DOCS.get(platform.system(), INSTALL_DOCS['Linux']),
'configuration_present': bridge.config_path().exists() or bool(os.getenv('LOGFORGE_MCP_URL') and os.getenv('LOGFORGE_MCP_API_KEY'))}
try:
docker_ready(); result['docker'] = 'ready'
except SetupError as error:
result.update(docker='unavailable', next_step=str(error)); return result
state = read_state()
if not state:
result['managed_instance'] = 'not provisioned'; return result
resources = names(state)
info = owned_object('container', resources['central'], state)
agent = owned_object('container', resources['agent'], state)
result.update(managed_instance=info['State']['Status'] if info else 'missing', container=resources['central'],
url=f'https://localhost:{state["ports"]["https"]}/unicron', docker_agent_container=agent['State']['Status'] if agent else 'not enabled')
return result
def start(monitor_docker=False, timeout=240):
docker_ready()
if os.getenv('LOGFORGE_MCP_URL') or os.getenv('LOGFORGE_MCP_API_KEY'):
raise SetupError('An explicit environment connection is configured. Preserve it or select a separate setup profile before provisioning.')
with setup_lock():
state = read_state()
if bridge.config_path().exists():
expected = f'https://localhost:{state["ports"]["https"]}/unicron/api/mcp' if state else None
if bridge.load_config()['url'] != expected:
raise SetupError('An existing LogForge connection is configured; it was not replaced. Use that connection or explicitly select a separate profile.')
if state is None:
state = new_state(); private_write(home() / 'state.json', state)
ensure_central(state); wait_ready(state, timeout)
request, base, ca = admin_client(state)
if not bridge.config_path().exists():
created = request('/unicron/auth/api/v1/api-keys', {'name': 'Codex managed read-only', 'scopes': ['read']})
config = {'url': base + '/unicron/api/mcp', 'api_key': created['data']['key'], 'ca_file': str(ca)}
bridge.doctor(config); bridge.save_config(config)
state['key_id'] = created['data']['apiKey']['id']; private_write(home() / 'state.json', state)
monitoring = None
if monitor_docker or owned_object('container', names(state)['agent'], state) is not None:
ensure_agent(state, request, ca)
monitoring = wait_agent(state, request, timeout)
checked = bridge.doctor(bridge.load_config())
return {**status(), 'connection': checked, 'monitoring_verification': monitoring, 'reconnect_mcp': True, 'publisher_usage': 'aggregate reporting defaults on; saved opt-out and environment disable are preserved'}
def monitor(container_key, enabled=True):
docker_ready()
with setup_lock():
state = read_state()
if not state or not container_key.startswith(names(state)['agent'] + ':'):
raise SetupError('Select an inventory key belonging to this managed agent; other hosts were not modified.')
config = bridge.load_config()
if config['url'] != f'https://localhost:{state["ports"]["https"]}/unicron/api/mcp':
raise SetupError('The active connection does not belong to this managed appliance; it was not modified.')
gateway = bridge.Gateway(config)
response = gateway.call({'jsonrpc': '2.0', 'id': 1, 'method': 'tools/call',
'params': {'name': 'logforge_inventory_search', 'arguments': {'container_key': container_key, 'limit': 1}}})
result = response.get('result', {})
data = result.get('structuredContent') or json.loads(result['content'][0]['text'])
matches = [item for item in data.get('containers', []) if item.get('container_key') == container_key and item.get('herald_id') == names(state)['agent']]
if result.get('isError') or not matches:
raise SetupError('The selected container was not found in this managed agent inventory.')
request, _, _ = admin_client(state)
return request('/unicron/api/containers/' + urllib.parse.quote(container_key, safe='') + '/monitoring?host_id=' + urllib.parse.quote(names(state)['agent'], safe=''), {'enabled': enabled})
def stop():
docker_ready()
with setup_lock():
state = read_state()
if not state:
return {'stopped': [], 'volumes': 'preserved'}
stopped = []
for role in ('agent', 'central'):
name = names(state)[role]
info = owned_object('container', name, state)
if info and info['State']['Running']:
run(['docker', 'stop', name]); stopped.append(name)
return {'stopped': stopped, 'volumes': 'preserved'}
def main():
parser = argparse.ArgumentParser(description=__doc__)
sub = parser.add_subparsers(dest='command', required=True)
sub.add_parser('status'); sub.add_parser('stop')
monitor_parser = sub.add_parser('monitor', help='Enable or disable monitoring for one explicitly selected workload on the managed agent.')
monitor_parser.add_argument('--container-key', required=True)
monitor_parser.add_argument('--disable', action='store_true')
start_parser = sub.add_parser('start')
start_parser.add_argument('--monitor-docker', action='store_true', help='Enroll the managed agent with local Docker socket access; requires authorization to monitor this Docker host.')
start_parser.add_argument('--timeout', type=int, default=240)
args = parser.parse_args()
try:
if args.command == 'status':
result = status()
elif args.command == 'stop':
result = stop()
elif args.command == 'monitor':
result = monitor(args.container_key, not args.disable)
else:
result = start(args.monitor_docker, args.timeout)
print(json.dumps(result, indent=2)); return 0
except (SetupError, bridge.BridgeError, OSError, ValueError, KeyError) as error:
print(json.dumps({'ok': False, 'error': str(error)})); return 1
if __name__ == '__main__':
raise SystemExit(main())
SHA-256: a26309b635e4c02b733cba87746105246fce8da5a2827f7e0ca3e42d75b658a8