diff --git a/.env.example b/.env.example index 8585401..2058ce7 100644 --- a/.env.example +++ b/.env.example @@ -11,6 +11,11 @@ FRONTEND_HOST=http://localhost:5173 # local, staging, production ENVIRONMENT=local +# Baked into the SPA at build time: the address the interface calls. The +# workspace root's scripts/setup.sh derives it from DOMAIN and ENVIRONMENT — +# plain http for a local target, where nothing terminates TLS. +VITE_API_URL=http://api.localhost + PROJECT_NAME="Fluksio" STACK_NAME=fluksio-app diff --git a/NOTEPAD.md b/NOTEPAD.md index b57dc2c..5462e73 100644 --- a/NOTEPAD.md +++ b/NOTEPAD.md @@ -16,20 +16,12 @@ Deferring because out of scope is fine, but don't mention deferring than. - FEAT/UI: check if PWA (https://whatpwacando.today/) notifications could be used to have a panel sending notifications to the device event bus (or generally using PWA to retrieve e.g. location etc). We could introduce a general concept of having a panel (a device, like a wall panel or a phone where the pwa (dashboard) runs) being effectively a node with various outputs. Then various inputs could trigger actions like authentification (i.e. you get home and get a notification which allows you to authenticate the door unlock), get notified on alarms (native alarm connector) or to query geolocation (check where the user is before turning of all lights) etc - BUG/UI sync the theme state between panels and installations - BUG/UI some dashboard widgets (like the color picker) are scrollable; we should make sure that no widgets (except for text widgets or list-related widgets) are scrollable -- INFRA document `make update` in the docs; this command is intended to run as a fire-and-forget command when updating a local installation - CHORE/UI: the house panels are laid out for 1280x800 — twelve columns, twelve rows. A chart's fixed chrome is now its title line and legend: the range picker moved up onto the title and gave back its row, so the budget is nearer forty pixels than eighty and a third chart may now fit — worth measuring against the panel before adding one. The temperature history was the one dropped; `history.climate_*` still answers, so it is a tile away. -- BUG/INFRA: `compose.yml` builds the frontend with `VITE_API_URL=https://api.${DOMAIN}` - and only `compose.local.yml` overrides it to `http`. Any `up --build` that does not layer - the local file therefore ships a bundle calling `https://api.localhost`, which fails with - `ERR_CERT_AUTHORITY_INVALID` and breaks login entirely — nothing terminates TLS locally. - It recurs every time the stack is brought up without the override, so it is not a stale - image but a default that is wrong for the local target. Either flip the default or make the - local target the one the Makefile always passes. - FEAT/UI: a bar's nested reading is captioned by its port name, which is chosen for the graph rather than for somebody reading it across a room. `Segment` now takes an optional `label`; the house dashboard sets one @@ -51,14 +43,14 @@ Deferring because out of scope is fine, but don't mention deferring than. `Supervisor.cancel_all` was just fixed for. Latent rather than live: a supervised node leaves those handles `None`, so only a node built on its own (a test, a preview) awaits there. -- BUG/INFRA: **475 zombie `git` processes** in the API container after a seeding - session. `FlowStore._git` uses `subprocess.run`, which reaps its own child — these - are the `git gc --auto` daemons `git commit` spawns, reparented to PID 1 when their - parent exits. PID 1 is the FastAPI process, which never reaps orphans. Harmless at - 484 processes against a 34719 limit, but it grows with every save, and a container - that commits per keystroke will get there. Fix: `git -c gc.auto=0 commit`, since a - store that never packs is a separate (and real) concern — 1898 commits had left - 5863 loose objects and no packs. +- BUG/INFRA: **zombie `python` processes in the API container**, twelve of them in + groups of four with matching elapsed times — one group per worker pool. `_Worker.kill()` + waits on the process it kills, so these are workers orphaned when the process holding + their `Popen` went away without stopping the pool, which is what uvicorn `--reload` + does on every source edit. They reparent onto PID 1, and PID 1 in the container is the + reloader, which reaps nothing that is not its own child. `init: true` on the backend + service hands PID 1 to an init that does reap orphans, and would close the whole class + rather than one source of it. - PERF/API: seeding nineteen flows takes two reloads each — one to publish, one to stop — so a full rebuild of every flow in the installation runs about forty times for one seed. It is the slowest thing about standing an installation up, and a @@ -424,12 +416,6 @@ Open on purpose. Each names what should bring it back. with its schemas, so a generator (the way n3xd generates its command catalog) is the obvious fix once the type list stops moving. -- `make update`/`make up` (production overlay) runs `up -d --remove-orphans` under the - same `fluksio-app` project as `make dev`, so it silently deletes the local Traefik - container `fluksio-app-proxy-1`. With `REVERSE_PROXY=external` and no host proxy, - the whole stack then has nothing bound to :80 and `*.localhost` stops resolving. - Either drop `--remove-orphans` or make `update` refuse when `ENVIRONMENT=local`. - - CHORE: `frontend/vite.config.js` and `frontend/vite.config.d.ts` are committed build output of `vite.config.ts` and fail `biome check` as tracked (semicolons, 4-space indent). The pre-commit hook therefore fails for anyone who stages any other file diff --git a/backend/fluksio/flow/store.py b/backend/fluksio/flow/store.py index 3d6cfa2..9e33da7 100644 --- a/backend/fluksio/flow/store.py +++ b/backend/fluksio/flow/store.py @@ -97,6 +97,13 @@ class FlowStore: #: One warning per process when there is no git to commit with. _warned_no_git = False + #: Commits between housekeeping runs. Auto-gc is off (see `_git`), so + #: nothing packs unless it is asked for; a commit counter is the trigger + #: because the process that needs it never restarts — a seeding session + #: commits thousands of times inside one long-lived container, which is how + #: a store reached 1898 commits, 5863 loose objects and no pack at all. + _GC_EVERY = 500 + def __init__(self, root: Path) -> None: self.root = root self.root.mkdir(parents=True, exist_ok=True) @@ -120,7 +127,13 @@ class FlowStore: def _git(self, *args: str) -> subprocess.CompletedProcess[str]: try: return subprocess.run( - ["git", "-C", str(self.root), *args], + # `gc.auto=0` on every invocation, because `git commit` otherwise + # forks a background `gc --auto` and returns without waiting for + # it. `subprocess.run` reaps the commit, not its grandchild, so + # the daemon is reparented onto PID 1 — the FastAPI process, + # which reaps nothing, and the zombies accumulate one per save. + # Packing is asked for explicitly in `_commit` instead. + ["git", "-C", str(self.root), "-c", "gc.auto=0", *args], capture_output=True, text=True, check=False, @@ -158,6 +171,11 @@ class FlowStore: message, ) if result.returncode == 0 or "nothing to commit" in result.stdout: + if self.revision % self._GC_EVERY == 0: + # In the foreground, so this child is waited on rather than + # orphaned. Plain `gc` rather than `gc --auto`, which the flag + # above has just disabled. + self._git("gc", "--quiet") return if result.stderr == "git is not installed": # Already said once, in `_git`. Repeating it per save is noise. diff --git a/docker/compose.yml b/docker/compose.yml index 5662981..e554fbb 100644 --- a/docker/compose.yml +++ b/docker/compose.yml @@ -173,7 +173,15 @@ services: context: .. dockerfile: frontend/Dockerfile args: - - VITE_API_URL=https://api.${DOMAIN?Variable not set} + # The address baked into the bundle. `scripts/setup.sh` already derives + # it from ENVIRONMENT and writes it to .env — http for a local target, + # because nothing terminates TLS there — so take it from the environment + # and keep the https form only as the default for a checkout that + # predates that key. Hardcoding https here made every `up --build` that + # did not also layer compose.local.yml ship a bundle calling + # https://api.localhost, which fails the certificate check and with it + # the whole login. + - VITE_API_URL=${VITE_API_URL:-https://api.${DOMAIN?Variable not set}} - NODE_ENV=production labels: - traefik.enable=true diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index d37d8df..66b757d 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -65,6 +65,11 @@ The dashboard is a static bundle, so this one is a **build** argument of the |---|---|---| | `VITE_API_URL` | `frontend` at build time | the address the interface calls | +The compose stack takes it from `.env`, falling back to `https://api.${DOMAIN}`. +`scripts/setup.sh` writes it there with the scheme `ENVIRONMENT` implies, so a +local build calls `http://` and does not fail a certificate check nothing is +there to satisfy. + Empty is the useful value: the interface then addresses the API relative to whichever origin served the page, so one image answers on a hostname, on a `http://:`, and through an ssh tunnel alike — and no origin has