112 lines
5.9 KiB
Python
112 lines
5.9 KiB
Python
#!/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()
|