The first run failed at configure-pages because the repository had no Pages
site yet. The deploy job already holds pages: write, so it can create one
instead of the setting having to be flipped by hand.
The real frontend, built against an in-memory backend, so the UI can be tried
without a Linux host, NET_ADMIN or a running firewall. A visitor can create,
edit, start, stop and delete services across all five modules and watch the UI
react as it would against a real instance; a reload restores the seed.
- frontend/src/js/demo.ts holds the mock. genericapi() and the socket.io client
are the frontend's only two ways out to the network, so routing both through
it was enough - no component knows the difference. It also emits the same
"update" events the backend does, and ticks the packet counters of running
services so the page looks alive.
- The demo lives behind IS_DEMO, which vite inlines as a literal, so a normal
`bun run build` tree-shakes the module and its seed data away. Verified: no
demo string survives in the production bundle.
- The workflow rebuilds on every push touching frontend/ or docs/ and deploys
to demo.firegex.pwnzer0tt1.it, copying index.html to 404.html so deep links
resolve on Pages.
IPFilterMiddleware matches the raw TCP peer address, which is 127.0.0.1 only
when firegex shares the host's network namespace. Behind a published docker
port the peer is the bridge gateway instead, so the scenario allowing
127.0.0.1/32,::1/128 got a 403 and the suite failed everywhere except
standalone mode.
Allow loopback plus the private ranges a docker bridge can occupy. The check
keeps its meaning: the deny scenario uses 203.0.113.0/24 (TEST-NET-3), which no
real peer can fall into, so the allow/deny pair still exercises the middleware.
- CI is release.yml and runs only on `release: published`, gating the publish
jobs. The documented docker-image.yml no longer exists, and nothing validates
a push or a pull request.
- nginx_test.py is gone; document ipfilter_test.py instead, including that it
drives run.py itself and is not part of run_tests.sh.
- `restart` now force-recreates the container, so it re-applies compose env
vars; note that, and that most start/restart flags persist to the config file.
- Describe the authentication surface: everything hangs off the `api` router
dependency plus the socket.io connect handler, with three routes deliberately
declared on `app`. Cover --unsafe-disable-auth and the fail-closed IP filter.
Rename the flag to --unsafe-disable-auth so the risk is visible at the call
site, and close the ways the mode could outlive itself:
- Keep an explicit -P/--startup-psw instead of dropping it. Without a password
in the database, --no-unsafe-disable-auth brought firegex up in its
initial-setup state, where anyone able to reach it could choose the password.
- Refuse /api/login, /api/set-password and /api/change-password while
authentication is disabled: an anonymous caller could otherwise plant a
credential that kept working once authentication was turned back on.
run.py config --password still works, writing to the database from the host.
- Print a banner on every start/restart with authentication disabled. The flag
is persisted in the config file, so a later bare `run.py start` inherits it.
- Report the change in `run.py config --unsafe-disable-auth`, which claimed
"No configuration changes specified" while persisting it, and stop that
command from nulling the stored port and host on the way.
- Expose the state as auth_disabled in /api/status and hide the logout button
and the change-password entry, which were silent no-ops in this mode.
Tests cover both directions of the toggle, the socket.io handler, the password
endpoints and the -P handling.
Objects archived in the history deques no longer carry their own
_history snapshot, breaking the recursive chain where each historical
entry held copies of all prior entries. The .history property on
archived objects now returns an empty HttpHistory([], []) instead.
- Fix parameter order sensitivity in HttpHistory._fetch_packet when history is first argument
- Delegate total_size and should_upgrade to _message instead of live _parser
- Sanitize FGEX_MAX_HISTORY_SIZE with max(0, int(val)) and dynamically sync deque maxlen