74 lines
3.4 KiB
Python
74 lines
3.4 KiB
Python
#!/usr/bin/env python3
|
|
"""deploy.py <app> [<app> ...] — Part B4: install an app on the Tester 1 box the way the deploy wizard does:
|
|
GET /api/stacks/<app>/deploy-fields, POST /api/stacks/<app>/deploy, then wait for the deploy's own completion mark
|
|
(the pin in app.yaml), as box_walk.deploy does.
|
|
|
|
Values: DOMAIN, the app's default SUBDOMAIN, HDD_PATH = drive A (the drive the household would pick; the drill
|
|
templates bind both drives by plain path and do not read it), and a generated value for each password field — kept
|
|
ONLY in the 0600 file ~/.felhom-tester1/secrets.json (never printed, never in evidence). box_walk.deploy is not used
|
|
because its deploy_values makes a folder on the scratch box's drive path, which does not exist here.
|
|
|
|
Live-probes rule: never send a deploy for an app that is not meant to be installed. This script refuses an app that is
|
|
already installed and installs only the apps named on the command line.
|
|
"""
|
|
import json, os, secrets, sys, time
|
|
sys.path.insert(0, "/mnt/5_hdd/felhom.eu/git/app-catalog-felhom.eu/scripts")
|
|
import box_walk as w
|
|
|
|
HDD = os.environ.get("HDD", "/mnt/felhom-drives/media_a")
|
|
SEC = os.path.join(os.environ.get("SC", os.path.expanduser("~/.felhom-tester1")), "secrets.json")
|
|
|
|
|
|
def load():
|
|
try:
|
|
return json.load(open(SEC))
|
|
except FileNotFoundError:
|
|
return {}
|
|
|
|
|
|
def save(d):
|
|
fd = os.open(SEC, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
|
|
with os.fdopen(fd, "w") as f:
|
|
json.dump(d, f)
|
|
|
|
|
|
w.login()
|
|
for app in sys.argv[1:]:
|
|
st = w.stack(app)
|
|
if st.get("deployed"):
|
|
w.say(f"{app}: already installed — not touched")
|
|
continue
|
|
c, d = w.ctl("GET", f"/api/stacks/{app}/deploy-fields")
|
|
fields = (((d.get("data") or {}).get("metadata") or {}).get("deploy_fields")) or []
|
|
vals, gen = {"DOMAIN": w.DOMAIN}, []
|
|
for f in fields:
|
|
ev, ty = f.get("env_var"), f.get("type")
|
|
if ev == "DOMAIN":
|
|
continue
|
|
if ev == "SUBDOMAIN":
|
|
vals[ev] = f.get("default") or st.get("meta", {}).get("subdomain") or app
|
|
elif ev == "HDD_PATH":
|
|
vals[ev] = HDD
|
|
elif ty == "password":
|
|
vals[ev] = "T1-" + secrets.token_urlsafe(15)
|
|
gen.append(ev)
|
|
elif f.get("required"):
|
|
sys.exit(w.say(f"{app}: required field {ev} ({ty}) has no rule here — STOP, not guessing") or 1)
|
|
s = load(); s[app] = {k: vals[k] for k in gen}; save(s)
|
|
shown = {k: ("<generated, kept in secrets.json>" if k in gen else v) for k, v in vals.items()}
|
|
c, d = w.ctl("POST", f"/api/stacks/{app}/deploy", {"values": vals})
|
|
w.say(f"{app}: POST deploy {json.dumps(shown)} -> {c} {json.dumps(d, ensure_ascii=False)[:200]}")
|
|
if c != "202":
|
|
continue
|
|
for _ in range(120):
|
|
time.sleep(5)
|
|
st = w.stack(app)
|
|
pins = (st.get("app_config") or {}).get("pinned_images")
|
|
if st.get("deployed") and pins and st.get("state") in ("running", "unhealthy", "degraded"):
|
|
w.say(f"{app}: deployed, state={st.get('state')}, pinned={pins}")
|
|
break
|
|
else:
|
|
w.say(f"{app}: never became deployed (state={st.get('state')})")
|
|
print(w.guest(f"docker inspect {app} -f '{{{{.State.Status}}}} {{{{range .Mounts}}}}{{{{.Source}}}}->{{{{.Destination}}}}:{{{{.RW}}}} {{{{end}}}}'; "
|
|
f"docker logs --since 10m felhom-controller 2>&1 | grep -i '{app}' | grep -v DEBUG | grep -iE 'after_install|deploy|error|warn' | tail -8"))
|