Files
xas-test-library/deploy/start.py
T

112 lines
5.9 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
"""Start only this project's CPU-only Rundeck container (run via normal sudo)."""
import argparse
import json
import os
from pathlib import Path
import socket
import subprocess
import sys
CONTAINER = "adamwen-test-console"
IMAGE = "adamwen/test-console:20261013-2"
def validate_runtime(project):
project = project.resolve(strict=True)
runtime = project / "runtime"
candidates = [runtime] + [runtime / name for name in ("auth", "data", "logs", "projects", "results", "proxy")]
for path in candidates:
if path.is_symlink() or not path.resolve().is_relative_to(project):
raise ValueError("Runtime directories must remain inside this project without symlinks")
if path.exists() and not path.is_dir():
raise ValueError("Runtime paths must be directories")
return runtime
def main():
from lan_config import load_settings, proxy_config, PROXY_IMAGE, NETWORK, PROXY
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--project-root", type=Path, required=True)
args = parser.parse_args()
project = args.project_root.resolve(strict=True)
try:
runtime = validate_runtime(project)
except ValueError as exc:
parser.error(str(exc))
settings = load_settings(project / "deploy/lan-settings.json")
for name in (CONTAINER, PROXY):
existing = subprocess.run(["docker", "container", "inspect", name],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
if existing.returncode == 0:
parser.error(f"{name} already exists; inspect it instead of replacing it")
with socket.socket() as probe:
probe.bind((settings['address'], settings['port']))
network = subprocess.run(["docker", "network", "inspect", NETWORK], capture_output=True, text=True)
if network.returncode == 0:
if json.loads(network.stdout)[0].get('Labels', {}).get('cn.aisuan.owner') != CONTAINER:
parser.error('Existing network does not belong to this project')
else:
subprocess.run(['docker','network','create','--label',f'cn.aisuan.owner={CONTAINER}',NETWORK],check=True)
for name in ("data", "logs", "projects", "results", "proxy"):
path = runtime / name
if path.is_symlink():
parser.error("Symlink runtime directories are forbidden")
path.mkdir(parents=True, exist_ok=True, mode=0o700)
os.chown(path, 1000, 0)
os.chmod(path, 0o700)
config = runtime / 'proxy/nginx.conf'
if config.is_symlink():
parser.error('Proxy config must not be a symlink')
config.write_text(proxy_config(settings), encoding='utf-8')
os.chmod(config, 0o644)
url = f"http://{settings['address']}:{settings['port']}"
command = ["docker", "run", "-d", "--name", CONTAINER,
"--label", "cn.aisuan.owner=adamwen-test-console",
"--runtime", "runc", "--user", "1000:0", "--init",
"--cpus", "2", "--memory", "2g", "--memory-swap", "2g",
"--pids-limit", "256", "--cap-drop", "ALL",
"--security-opt", "no-new-privileges:true",
"--restart", "no", "--network", f"container:{PROXY}",
"--log-opt", "max-size=10m", "--log-opt", "max-file=3",
"-e", f"RUNDECK_GRAILS_URL={url}",
"-e", "RUNDECK_SERVER_ADDRESS=127.0.0.1",
"-e", "RUNDECK_SERVER_FORWARDED=true",
"-e", "RUNDECK_PREAUTH_ENABLED=true",
"-e", "RUNDECK_PREAUTH_ATTRIBUTE_NAME=REMOTE_USER_GROUPS",
"-e", "RUNDECK_PREAUTH_USERNAME_HEADER=X-Forwarded-Uuid",
"-e", "RUNDECK_PREAUTH_ROLES_HEADER=X-Forwarded-Roles",
"-e", "RUNDECK_SERVER_SESSION_TIMEOUT=1800",
"-e", "RUNDECK_QUARTZ_THREADPOOL_THREADCOUNT=2",
"-e", "JVM_MAX_RAM_PERCENTAGE=60",
"-e", "NVIDIA_VISIBLE_DEVICES=void",
"-e", "CUDA_VISIBLE_DEVICES="]
mounts = [(project / 'deploy/realm.disabled.properties', "/home/rundeck/secure/realm.properties", "ro"),
(runtime / "data", "/home/rundeck/server/data", "rw"),
(runtime / "logs", "/home/rundeck/var/logs", "rw"),
(runtime / "projects", "/home/rundeck/projects", "rw"),
(runtime / "results", "/home/rundeck/test-results", "rw"),
(project / "deploy" / "operator.aclpolicy", "/home/rundeck/etc/xas-operator.aclpolicy", "ro"),
(project / "deploy" / "jaas-loginmodule.conf.template", "/etc/remco/templates/jaas-loginmodule.conf", "ro")]
for source, destination, access in mounts:
command += ["--mount", f"type=bind,src={source},dst={destination}" + (",readonly" if access == "ro" else "")]
command.append(IMAGE)
proxy_base = ['docker','run','--runtime','runc','--network',NETWORK,'--user','101:101',
'--cpus','0.25','--memory','64m','--pids-limit','32','--cap-drop','ALL',
'--security-opt','no-new-privileges:true','--read-only',
'--tmpfs','/tmp:rw,noexec,nosuid,size=16m',
'--mount',f'type=bind,src={config},dst=/etc/nginx/nginx.conf,readonly',
'--entrypoint','nginx']
subprocess.run(proxy_base + ['--rm',PROXY_IMAGE,'-t'],check=True)
subprocess.run(proxy_base + ['-d','--name',PROXY,'--label',f'cn.aisuan.owner={CONTAINER}',
'--publish',f"{settings['address']}:{settings['port']}:8080",
'--restart','no','--log-opt','max-size=5m','--log-opt','max-file=2',
PROXY_IMAGE,'-g','daemon off;'],check=True)
# Namespace loopback cannot be reached via the container IP from the shared host.
subprocess.run(command, check=True)
print(json.dumps({"container":CONTAINER,"proxy":PROXY,"url":url,"allowed_cidrs":settings['allowed_cidrs'],"gpu":False}))
if __name__ == "__main__":
main()