Implement executing tasks

This commit is contained in:
2026-03-03 16:41:28 +00:00
parent d6bd56dace
commit 9c14e51b43
42 changed files with 3410 additions and 121 deletions

View File

@@ -45,6 +45,27 @@ def main() -> int:
repo_root = Path(__file__).resolve().parents[2]
stack_env_path = abs_from(repo_root, args.stack_env, "stack.env")
env = parse_env(stack_env_path)
stack_id = str(env.get("GIA_STACK_ID") or env.get("STACK_ID") or "").strip()
stack_id = "".join(ch if (ch.isalnum() or ch in "._-") else "-" for ch in stack_id).strip("-")
def with_stack(base: str) -> str:
return f"{base}_{stack_id}" if stack_id else base
unit_prefix = f"gia-{stack_id}" if stack_id else "gia"
pod_ref = f"{unit_prefix}.pod"
target_ref = f"{unit_prefix}.target"
stack_offset_raw = str(env.get("GIA_STACK_PORT_OFFSET") or "").strip()
if stack_offset_raw:
try:
stack_port_offset = max(0, int(stack_offset_raw))
except Exception:
stack_port_offset = 0
elif stack_id:
stack_port_offset = (sum(ord(ch) for ch in stack_id) % 500) + 1
else:
stack_port_offset = 0
app_port = int(env.get("APP_PORT") or (5006 + stack_port_offset))
signal_public_port = int(env.get("SIGNAL_PUBLIC_PORT") or (8080 + stack_port_offset))
repo_dir = abs_from(repo_root, env.get("REPO_DIR", "."), ".")
host_uid = int(os.getuid())
@@ -62,7 +83,7 @@ def main() -> int:
redis_data_dir = abs_from(repo_root, env.get("QUADLET_REDIS_DATA_DIR", "./.podman/gia_redis_data"), "./.podman/gia_redis_data")
whatsapp_data_dir = abs_from(repo_root, env.get("QUADLET_WHATSAPP_DATA_DIR", "./.podman/gia_whatsapp_data"), "./.podman/gia_whatsapp_data")
vrun_dir = Path("/code/vrun")
vrun_dir = Path("/code/vrun") / stack_id if stack_id else Path("/code/vrun")
signal_cli_dir = (repo_dir / "signal-cli-config").resolve()
uwsgi_ini = (repo_dir / "docker" / "uwsgi.ini").resolve()
redis_conf = (repo_dir / "docker" / "redis.conf").resolve()
@@ -80,22 +101,24 @@ def main() -> int:
env_file = stack_env_path
pod_unit = """
pod_unit = f"""
[Unit]
Description=GIA Pod
[Pod]
PodName=gia
PodName={with_stack('gia')}
PublishPort={app_port}:8000
PublishPort={signal_public_port}:8080
[Install]
WantedBy=default.target
"""
target_unit = """
target_unit = f"""
[Unit]
Description=GIA Stack Target
Wants=gia-redis.service gia-signal.service gia-migration.service gia-collectstatic.service gia-app.service gia-asgi.service gia-ur.service gia-scheduling.service
After=gia-redis.service gia-signal.service gia-migration.service gia-collectstatic.service
Wants={unit_prefix}-redis.service {unit_prefix}-signal.service {unit_prefix}-migration.service {unit_prefix}-collectstatic.service {unit_prefix}-app.service {unit_prefix}-asgi.service {unit_prefix}-ur.service {unit_prefix}-scheduling.service {unit_prefix}-codex-worker.service
After={unit_prefix}-redis.service {unit_prefix}-signal.service {unit_prefix}-migration.service {unit_prefix}-collectstatic.service
[Install]
WantedBy=default.target
@@ -104,14 +127,14 @@ WantedBy=default.target
redis_unit = f"""
[Unit]
Description=GIA Redis
PartOf=gia.target
PartOf={target_ref}
After=network-online.target
Wants=network-online.target
[Container]
Image=docker.io/library/redis:latest
ContainerName=redis_gia
Pod=gia.pod
ContainerName={with_stack('redis_gia')}
Pod={pod_ref}
Volume={redis_conf}:/etc/redis.conf:ro
Volume={redis_data_dir}:/data
Volume={vrun_dir}:/var/run
@@ -122,20 +145,20 @@ Restart=always
RestartSec=2
[Install]
WantedBy=gia.target
WantedBy={target_ref}
"""
signal_unit = f"""
[Unit]
Description=GIA Signal API
PartOf=gia.target
PartOf={target_ref}
After=network-online.target
Wants=network-online.target
[Container]
Image=docker.io/bbernhard/signal-cli-rest-api:latest
ContainerName=signal
Pod=gia.pod
ContainerName={with_stack('signal')}
Pod={pod_ref}
Volume={signal_cli_dir}:/home/.local/share/signal-cli
Environment=MODE=json-rpc
@@ -144,21 +167,21 @@ Restart=always
RestartSec=2
[Install]
WantedBy=gia.target
WantedBy={target_ref}
"""
def gia_container(name: str, container_name: str, command: str, include_uwsgi: bool, include_whatsapp: bool, after: str, requires: str, one_shot: bool = False) -> str:
lines = [
"[Unit]",
f"Description={name}",
"PartOf=gia.target",
f"PartOf={target_ref}",
f"After={after}",
f"Requires={requires}",
"",
"[Container]",
"Image=localhost/xf/gia:prod",
f"ContainerName={container_name}",
"Pod=gia.pod",
f"Pod={pod_ref}",
f"User={host_uid}:{host_gid}",
f"EnvironmentFile={env_file}",
"Environment=SIGNAL_HTTP_URL=http://127.0.0.1:8080",
@@ -178,7 +201,7 @@ WantedBy=gia.target
"Type=oneshot",
"RemainAfterExit=yes",
"TimeoutStartSec=0",
"ExecStartPre=/bin/sh -c 'for i in $(seq 1 60); do [ -S /code/vrun/gia-redis.sock ] && exit 0; sleep 1; done; exit 1'",
f"ExecStartPre=/bin/sh -c 'for i in $(seq 1 60); do [ -S {vrun_dir}/gia-redis.sock ] && exit 0; sleep 1; done; exit 1'",
])
else:
lines.extend([
@@ -186,80 +209,92 @@ WantedBy=gia.target
"RestartSec=2",
])
lines.extend(["", "[Install]", "WantedBy=gia.target"])
lines[-1] = f"WantedBy={target_ref}"
return "\n".join(lines)
migration_unit = gia_container(
"GIA Migration",
"migration_gia",
with_stack("migration_gia"),
"sh -c '. /venv/bin/activate && python manage.py migrate --noinput'",
include_uwsgi=False,
include_whatsapp=False,
after="gia-redis.service gia-signal.service",
requires="gia-redis.service gia-signal.service",
after=f"{unit_prefix}-redis.service {unit_prefix}-signal.service",
requires=f"{unit_prefix}-redis.service {unit_prefix}-signal.service",
one_shot=True,
)
collectstatic_unit = gia_container(
"GIA Collectstatic",
"collectstatic_gia",
with_stack("collectstatic_gia"),
"sh -c '. /venv/bin/activate && python manage.py collectstatic --noinput'",
include_uwsgi=False,
include_whatsapp=False,
after="gia-migration.service",
requires="gia-migration.service",
after=f"{unit_prefix}-migration.service",
requires=f"{unit_prefix}-migration.service",
one_shot=True,
)
app_unit = gia_container(
"GIA App",
"gia",
with_stack("gia"),
"sh -c 'if [ \\\"$OPERATION\\\" = \\\"uwsgi\\\" ] ; then . /venv/bin/activate && uwsgi --ini /conf/uwsgi.ini ; else . /venv/bin/activate && exec python manage.py runserver 0.0.0.0:8000; fi'",
include_uwsgi=True,
include_whatsapp=True,
after="gia-collectstatic.service gia-redis.service gia-signal.service",
requires="gia-collectstatic.service gia-redis.service gia-signal.service",
after=f"{unit_prefix}-collectstatic.service {unit_prefix}-redis.service {unit_prefix}-signal.service",
requires=f"{unit_prefix}-collectstatic.service {unit_prefix}-redis.service {unit_prefix}-signal.service",
)
asgi_unit = gia_container(
"GIA ASGI",
"asgi_gia",
with_stack("asgi_gia"),
"sh -c 'rm -f /var/run/asgi-gia.sock && . /venv/bin/activate && python -m pip install --disable-pip-version-check -q uvicorn && python -m uvicorn app.asgi:application --uds /var/run/asgi-gia.sock --workers 1'",
include_uwsgi=False,
include_whatsapp=True,
after="gia-collectstatic.service gia-redis.service gia-signal.service",
requires="gia-collectstatic.service gia-redis.service gia-signal.service",
after=f"{unit_prefix}-collectstatic.service {unit_prefix}-redis.service {unit_prefix}-signal.service",
requires=f"{unit_prefix}-collectstatic.service {unit_prefix}-redis.service {unit_prefix}-signal.service",
)
ur_unit = gia_container(
"GIA Unified Router",
"ur_gia",
with_stack("ur_gia"),
"sh -c '. /venv/bin/activate && python manage.py ur'",
include_uwsgi=True,
include_whatsapp=True,
after="gia-collectstatic.service gia-redis.service gia-signal.service",
requires="gia-collectstatic.service gia-redis.service gia-signal.service",
after=f"{unit_prefix}-collectstatic.service {unit_prefix}-redis.service {unit_prefix}-signal.service",
requires=f"{unit_prefix}-collectstatic.service {unit_prefix}-redis.service {unit_prefix}-signal.service",
)
scheduling_unit = gia_container(
"GIA Scheduling",
"scheduling_gia",
with_stack("scheduling_gia"),
"sh -c '. /venv/bin/activate && python manage.py scheduling'",
include_uwsgi=True,
include_whatsapp=False,
after="gia-collectstatic.service gia-redis.service gia-signal.service",
requires="gia-collectstatic.service gia-redis.service gia-signal.service",
after=f"{unit_prefix}-collectstatic.service {unit_prefix}-redis.service {unit_prefix}-signal.service",
requires=f"{unit_prefix}-collectstatic.service {unit_prefix}-redis.service {unit_prefix}-signal.service",
)
write_unit(out_dir / "gia.pod", pod_unit)
write_unit(out_dir / "gia.target", target_unit)
write_unit(out_dir / "gia-redis.container", redis_unit)
write_unit(out_dir / "gia-signal.container", signal_unit)
write_unit(out_dir / "gia-migration.container", migration_unit)
write_unit(out_dir / "gia-collectstatic.container", collectstatic_unit)
write_unit(out_dir / "gia-app.container", app_unit)
write_unit(out_dir / "gia-asgi.container", asgi_unit)
write_unit(out_dir / "gia-ur.container", ur_unit)
write_unit(out_dir / "gia-scheduling.container", scheduling_unit)
codex_worker_unit = gia_container(
"GIA Codex Worker",
with_stack("codex_worker_gia"),
"sh -c '. /venv/bin/activate && python manage.py codex_worker'",
include_uwsgi=True,
include_whatsapp=False,
after=f"{unit_prefix}-collectstatic.service {unit_prefix}-redis.service {unit_prefix}-signal.service",
requires=f"{unit_prefix}-collectstatic.service {unit_prefix}-redis.service {unit_prefix}-signal.service",
)
write_unit(out_dir / f"{unit_prefix}.pod", pod_unit)
write_unit(out_dir / f"{unit_prefix}.target", target_unit)
write_unit(out_dir / f"{unit_prefix}-redis.container", redis_unit)
write_unit(out_dir / f"{unit_prefix}-signal.container", signal_unit)
write_unit(out_dir / f"{unit_prefix}-migration.container", migration_unit)
write_unit(out_dir / f"{unit_prefix}-collectstatic.container", collectstatic_unit)
write_unit(out_dir / f"{unit_prefix}-app.container", app_unit)
write_unit(out_dir / f"{unit_prefix}-asgi.container", asgi_unit)
write_unit(out_dir / f"{unit_prefix}-ur.container", ur_unit)
write_unit(out_dir / f"{unit_prefix}-scheduling.container", scheduling_unit)
write_unit(out_dir / f"{unit_prefix}-codex-worker.container", codex_worker_unit)
print(f"Wrote Quadlet units to: {out_dir}")
return 0