Every crate to its latest compatible release, rcgen 0.13 -> 0.14 for
the tests (only the features they use). Clippy: async fn in trait
impls, try_update over the deprecated fetch_update, no-op casts, a
duplicated attribute, Database::is_empty.
cibuildwheel ran wheel_smoke.py from fgex-lib/, so Python put that
directory first on sys.path and imported the unbuilt source package:
no _llhttp, every wheel job failed. python -I leaves it off.
A backup taken within a second of a block missed it. The backup test
also snapshotted while NFQUEUE blocks were still being reported, which
failed the 5.0.0 release run; it now waits until two reads agree.
As root on Ubuntu, pip into the system Python cannot replace apt's
typing_extensions, so pytest was never installed and the 5.0.0
release gate failed. run_tests.sh now installs into tests/.venv and
stops on a failed install.
The queue watchdog and packet-exception handling from main were already
carried by dev (services/ managers, utils.cpp, nfqueue.cpp); the tree is
kept as dev has it.
The stubbed `GET .../code` answered with `{ code }` while the real endpoint
is a PlainTextResponse returning the source, which is what crashed the demo
service page, and the save route was registered as POST where the real API
and the frontend both use PUT.
The seeded sample also carried a `strip_debug_header` filter that mutated
`HttpRequest.headers`. That silently does nothing: `headers` is a read-only
property over the parsed message, and nothing re-serialises it onto the wire
(a mangle forwards `current_pkt.raw_packet`, which the dict never touches).
Dropped it, leaving the one filter that behaves as shown, and brought the
filter counts and the packet counters back in line with it.
`genericapi` ran every response body through `JSON.parse`, falling back to
the raw text only when that threw. The nfproxy filter source is served as a
PlainTextResponse, so a filter file that happens to be valid JSON came back
as a number/array/object. Mantine's CodeHighlight then calls `code.trim()`
on it, which throws and takes the whole page down with it. `42`, `"abc"`,
`[1,2]` and `{"a":1}` are all both valid JSON and valid Python, so they
pass the upload validation and leave the service page unreachable.
Responses declaring text/plain are no longer parsed, and the render site
type-checks the code before handing it to CodeHighlight.
The Card carried Mantine's `w="100%"` prop, whose style props land in the
style attribute after anything passed via `style`, so it overrode the pixel
width dnd measures at lift time. On a `position: fixed` element `100%`
resolves against the viewport rather than the list, so grabbing a rule
snapped the card from 734px to 798px and pushed it past the right edge.
Moving the width into `style`, ahead of the dnd spread, leaves normal flow
untouched and lets the measured width win while dragging.
Three separate defects made the rule list impossible to reorder:
- The Card's own `style` was placed after `{...provided.draggableProps}`,
clobbering the `position`/`transform` dnd sets while dragging. The drag
did start, but nothing moved on screen. Styles are now merged with dnd's
winning, and the dragged card is highlighted.
- `Draggable index` used the position in the full `state` array and the
rules of the other table were filtered out afterwards, leaving gaps.
dnd requires contiguous indexes from 0 inside a Droppable, so a single
rule in the other table broke the visible tab entirely. Visible rules
are now re-indexed and mapped back to their `state` position on drop.
- `destination?.index || 0` reordered to the top instead of doing nothing
when a rule was dropped outside the list.
GITHUB_TOKEN cannot create a Pages site - configure-pages reported "Resource
not accessible by integration" even with pages: write on the job. Pages is now
enabled on the repository with source "GitHub Actions", so the step only has
to read the existing configuration.
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.