Compare commits

..
Author SHA1 Message Date
gitea-actions[bot] a22dc02934 chore: update flake.lock 2026-10-02 20:02:45 -04:00
20 changed files with 728 additions and 103 deletions
@@ -0,0 +1,38 @@
# Package patches
Each package follows the [GnuTLS layout](gnutls/README.md):
- `default.nix` applies the patch through the package overlay.
- A descriptive `.patch` file contains the standalone upstream source change.
- `README.md` explains the problem, scope, reproduction, upstream status,
Nix integration, and recorded validation limits.
- Companion `verify-*` tools live beside the patch when needed; otherwise
the README gives commands for the package's existing tests.
Keep package-specific evidence in its directory. Patch headers explain the
change independently of Nix, and `default.nix` preserves existing patches.
| Package | Repair |
| --- | --- |
| [Abseil](abseil/README.md) | Public BMI2 header in Electron, Deno, and Signal's vendored copies |
| [GnuTLS](gnutls/README.md) | Wait for the UDP server socket before connecting |
| [Prometheus](prometheus/README.md) | Complete parsing before inspecting the test editor state |
| [pytest-xdist](pytest-xdist/README.md) | Check worker replacements and allow startup on loaded builders |
| [SciPy](scipy/README.md) | Account for floating-point rounding in STFT tests |
## Local NixOS integration
The [`x86-64-v3` optional module](../default.nix)
imports this directory's [`default.nix`](default.nix) directly, so the patch
overlay applies only to hosts using that package set. Prometheus patches its
separate assets derivation; Python packages use `pythonPackagesExtensions`.
The [pytest-xdist directory](pytest-xdist/README.md) also owns its outer-worker
limit and remote-worker event timeout. These package overrides add no skipped
tests. Existing nixpkgs exclusions remain separate from these repairs.
The review used Python 3.14.7 and the pinned x86-64-v3 package set. Host-flake
evaluation verified patch wiring, Python install checks, and Prometheus's
reference to the patched assets. No complete NixOS rebuild was performed.
Individual READMEs distinguish package builds, focused tests, and checks that
have not been run.
@@ -0,0 +1,59 @@
# Abseil BMI2 public header
Vendored Abseil includes `bmi2intrin.h` directly when `__BMI2__` is enabled.
Compilers reject that internal header without the umbrella-header setup.
`bmi2-public-header.patch` includes `immintrin.h` instead, allowing builds
that enable BMI2 through `-march=x86-64-v3`.
## Scope and behavior
The patch changes one include in
`third_party/abseil-cpp/absl/container/internal/raw_hash_set.h`.
`default.nix` applies it to Electron 43's unwrapped package, Deno's
`librusty_v8`, and Signal's WebRTC dependency. It also supplies the patched
Electron package to Signal. These overrides apply only to `x86-64-v3`.
The shared file path is relative to each vendoring project's source root,
not the root of a standalone Abseil checkout. No hash-table algorithm or
test exclusion changes.
## Reproduction and focused checks
From this directory, check and apply the patch to each vendored source tree:
```sh
patch --dry-run --fuzz=0 -d /path/to/vendor-source -p1 < bmi2-public-header.patch
patch --fuzz=0 -d /path/to/vendor-source -p1 < bmi2-public-header.patch
```
A small compiler check isolates the header requirement. With GCC or Clang
on x86-64, compile `#include <bmi2intrin.h>` using `-march=x86-64-v3`; the
compiler rejects the direct include. Changing it to `#include <immintrin.h>`
should compile. The full consumer builds below check integration with their
actual toolchains.
## Upstream status
Abseil addressed this issue through
[PR #2071](https://github.com/abseil/abseil-cpp/pull/2071), imported by its
upstream workflow. That change uses `x86gprintrin.h`; this local variant uses
the public `immintrin.h` umbrella header for the vendored toolchains.
Keep the workaround until all three bundled copies include a compatible fix.
This file is a local adaptation, not a verbatim copy of the upstream diff.
## Local NixOS integration and build results
[`../default.nix`](../default.nix) merges this directory's overlay fragment
because it repairs multiple packages. From the repository root, the consumer
build commands are:
```sh
nix build --no-link -L .#nixosConfigurations.jeeves.pkgs.deno
nix build --no-link -L .#nixosConfigurations.jeeves.pkgs.electron_43
nix build --no-link -L .#nixosConfigurations.jeeves.pkgs.signal-desktop
```
The earlier extraction checked the vendored header snapshots and evaluated
all three patch attachments. Those records do not establish successful full
consumer rebuilds. No new compiler or consumer build was run for the layout
change; the patch and override are unchanged.
@@ -1,19 +0,0 @@
Subject: [PATCH] Compare font content in the overlap-removal regression
PFA_SKIP ends with a separator, producing an empty prefix that matches every
line. Remove the empty prefix for this test so glyph differences are checked,
and assert that tx exits successfully before inspecting the output.
--- a/tests/tx_test.py
+++ b/tests/tx_test.py
@@ -1064,6 +1064,8 @@
output_path = get_temp_file_path()
args = [TOOL, '-t1', '+V', '-o', output_path, input_path]
- subprocess.call(args)
- assert differ([expected_path, output_path, '-s', PFA_SKIP[0]])
+ subprocess.check_call(args)
+ # An empty skip prefix matches every line and hides font differences.
+ skip_headers = SPLIT_MARKER.join(filter(None, PFA_SKIP[0].split(SPLIT_MARKER)))
+ assert differ([expected_path, output_path, '-s', skip_headers])
@@ -1,10 +0,0 @@
{ afdko }:
afdko.overridePythonAttrs (old: {
# FMA changes overlap-removal coordinates by 0.01 units on x86-64-v3.
# Separate multiply/add rounding reproduces the reference Type 1 font exactly.
env = (old.env or { }) // {
NIX_CFLAGS_COMPILE = (old.env.NIX_CFLAGS_COMPILE or "") + " -ffp-contract=off";
};
patches = (old.patches or [ ]) ++ [ ./check-overlap-font-content.patch ];
})
@@ -3,12 +3,9 @@ _final: prev:
// {
gnutls = import ./gnutls { inherit (prev) gnutls; };
prometheus = import ./prometheus { inherit (prev) prometheus; };
zopfli = import ./zopfli { inherit (prev) zopfli; };
pythonPackagesExtensions = prev.pythonPackagesExtensions ++ [
(_pythonFinal: pythonPrev: {
afdko = import ./afdko { inherit (pythonPrev) afdko; };
psutil = import ./psutil { inherit (pythonPrev) psutil; };
pytest-xdist = import ./pytest-xdist { inherit (pythonPrev) pytest-xdist; };
scipy = import ./scipy { inherit (pythonPrev) scipy; };
})
@@ -0,0 +1,160 @@
# GnuTLS UDP server readiness
Under load, the test client can start before `gnutls-serv` binds its UDP
socket, and the first handshake fails with `Connection refused`.
`serv-udp.sh` currently waits a fixed four seconds; elapsed time does not
establish server readiness. `udp-server-readiness.patch` replaces that wait
with polling for the local IPv4 UDP endpoint.
## Scope and waiting behavior
The patch changes the existing `wait_udp_server()` and adds a new
`check_if_udp_port_bound()` beside it in `tests/scripts/common.sh`.
`serv-udp.sh` is its only caller in 3.8.13. The TCP helpers `wait_server()`
and `wait_for_port()`, including their existing sleeps, are unchanged.
Both original DTLS handshake checks remain unchanged.
Each iteration checks process liveness and the socket **before sleeping**.
A ready socket returns immediately. An unsuccessful check sleeps two
seconds only if another attempt remains: at most 90 attempts, consistent
with the existing `wait_server()` budget implemented by `wait_for_port()`,
with no sleep after the final check. Server exit fails early; exhausting the
budget fails and terminates the server. No handshake is retried, and no
protocol timeout is changed. Once bound, the kernel can queue datagrams
while the server is scheduled; the probe itself sends no packets.
The existing `have_port_finder()` prefers `ss`, then `netstat`. If neither
exists, it prints `neither ss nor netstat found` and exits **77 (skip)**.
In the normal test flow, port selection calls it before launching a server.
The probe runs in a subshell so that, even if this skip occurs after launch,
the waiting helper can terminate and reap the server before exiting 77.
## Why an IPv4 socket is expected
This is specific to the server used by this test, not a general rule that
IPv6 sockets cannot serve IPv4 clients. The client explicitly uses
`127.0.0.1`. The server's `--udp` path calls `udp_server()`, which calls
`listen_socket(..., SOCK_DGRAM)`. That function iterates the wildcard
addresses returned by `getaddrinfo(NULL, ..., AI_PASSIVE)`:
| Server build / Linux setting | Binding behavior |
| --- | --- |
| IPv6 enabled, `net.ipv6.bindv6only=0` | Requests `IPV6_V6ONLY=1` on the IPv6 socket, binds `[::]:PORT`, and separately binds `0.0.0.0:PORT`. It overrides the system's dual-stack default. |
| IPv6 enabled, `net.ipv6.bindv6only=1` | The same explicit socket option and separate IPv4/IPv6 binds. |
| `HAVE_IPV6` undefined | Skips every address family except `AF_INET`; only the IPv4 wildcard is attempted. |
`udp_server()` uses `wait_for_connection()`, which puts **every listener**
from that list into `select()` and returns a readable socket for `recvfrom()`;
it does not permanently choose one socket based on `getaddrinfo()` order.
The first two cases were traced with the actual GnuTLS 3.8.13 binary in
separate Linux network namespaces: `setsockopt(IPV6_V6ONLY, [1])` and both
UDP binds returned success under each setting. The no-IPv6 case was checked
in source, not by building a second binary. The same bind implementation
was checked directly on GitLab master.
Thus, successful normal startup for this invocation provides an explicit
IPv4 socket; a lone IPv6 wildcard is not the expected success path.
There is one portability caveat: upstream discards the return value of
`setsockopt(IPV6_V6ONLY)`. On a platform where that call fails and the server
ends up with only a dual-stack socket, this helper would time out despite
IPv4 reachability. Such a platform needs additional handling before this
patch can claim support. Blindly accepting every IPv6 wildcard would also
accept IPv6-only sockets before the separate IPv4 bind finishes.
Source: [`src/serv.c`, `listen_socket()`](https://gitlab.com/gnutls/gnutls/-/blob/master/src/serv.c#L937),
[`src/udp-serv.c`](https://gitlab.com/gnutls/gnutls/-/blob/master/src/udp-serv.c),
and [`tests/serv-udp.sh`](https://gitlab.com/gnutls/gnutls/-/blob/master/tests/serv-udp.sh).
## Port matching and ownership limit
Only `-an` is passed to the socket-listing tool: BSD `netstat -u` selects
Unix-domain sockets, whereas Linux `netstat -u` selects UDP. The parser
handles the extra state column in `ss`, Linux colon-separated endpoints,
and BSD dot-separated endpoints, including `*.PORT`. It matches the full
local port and rejects TCP, IPv6 entries, peer ports, and longer numbers.
A live PID plus a bound port does **not** prove that PID owns the socket.
Existing `GETPORT` selection checks for an unused port and uses a test
port-lock directory; `launch_bare_server()` also calls
`wait_for_free_port()` before starting the process. These are advisory:
the launcher does not enforce the latter's result, and another process
can bind between the check and launch. The patch does not close that race
or add nonportable PID parsing. An unrelated process can satisfy the
socket check; the real handshakes remain the functional check and may
fail (or reach the wrong server). This is a startup-order fix, not a
socket-ownership guarantee.
## Reproduction and focused checks
Apply the patch to an unpacked source tree, then run the companion checks
with Python's standard library and a shell:
```sh
patch --fuzz=0 -d /path/to/gnutls -p1 < udp-server-readiness.patch
SHELL=/bin/sh python3 verify-readiness.py /path/to/gnutls/tests/scripts/common.sh -v
```
Set `NETSTAT=/path/to/netstat` to exercise one outside `PATH`. The checks
cover Linux/BSD output samples, false matches, immediate readiness,
missing tools, process exit, timeout cleanup, and real IPv4 UDP sockets
whose bind is delayed six seconds. The missing-tools fixture is skipped
if an absolute fallback `ss` path cannot be hidden with `PATH`. Native
BSD execution remains untested.
To reproduce with GnuTLS itself, run `tests/serv-udp.sh` with `SERV` pointing
to a wrapper that sleeps six seconds, then `exec`s `gnutls-serv` with all
arguments. Set `CLI` to the matching `gnutls-cli`, `srcdir` to the source
`tests` directory, and `abs_top_builddir` to a writable build directory.
With GnuTLS 3.8.13, the original helper failed the first handshake with
`Connection refused`; the patched helper passed both with the same binaries.
## GnuTLS submission
Development and merge requests are on [GitLab](https://gitlab.com/gnutls/gnutls).
[`CONTRIBUTING.md` on master](https://gitlab.com/gnutls/gnutls/-/blob/master/CONTRIBUTING.md)
was read directly for this review. It requires the contributor's DCO
`Signed-off-by`, successful and failure test coverage, consistent coding
style, and adequate documentation; GitLab CI runs for merge requests.
Its commenting guidance asks for comments explaining non-obvious behavior
or protocol expectations. It does not prescribe an additional special
test-suite comment. The patch now explains its IPv4 binding assumption
next to the probe.
The submission will contain the shell patch, without the Python verifier
or a new Python test dependency. The existing `serv-udp.sh` supplies the
functional success check. Running it through the six-second startup
wrapper supplies a reproducible regression case: it fails before the fix
and passes after it. The local verifier was used to validate socket-output
parsing and the helper's success, process-exit, skip-cleanup, and timeout
branches. Those branch checks are local evidence, not new automated
coverage in the upstream suite; the MR must state that distinction.
No dedicated unit-test harness for these shell helpers was found in the
3.8.13 tests inspected. That does not establish that Python cannot be used
upstream; keeping this submission dependency-free is a scope choice. Use
the existing test and before/after reproduction as the submission's
coverage argument, retaining the platform limitations above. Apply the
patch in an upstream checkout and include those results with the
contributor's own sign-off. No MR or sign-off has been created.
## Local NixOS integration and build results
`overlays/default.nix` imports the `overlays/patches` overlay, which loads
`gnutls/default.nix` to apply the patch and keep `serv-udp.sh` enabled.
The patch itself has no Nix dependencies and applies to 3.8.13 and GitLab
master without fuzz.
The final patch was rebuilt with:
```sh
nix build --no-link -L .#nixosConfigurations.jeeves.pkgs.gnutls
```
That x86-64-v3 build passed: 927 tests, 796 passes, 131 existing skips,
zero failures/errors, and `PASS: serv-udp.sh`. The patch bytes in the built
derivation were compared with the repository artifact; both have SHA-256
`59013d47fd446f2dd065012a2259ccc1898fedc8a053a630e13efa0076368760`.
All seven local checks passed, including skip cleanup and exactly 90
probes with 89 sleeps on timeout. The six-second before/after reproduction
was also repeated successfully with the final helper.
@@ -0,0 +1,66 @@
# Prometheus complete test parsing
CodeMirror gives editor-state creation a 20 ms synchronous parsing budget.
The shared `createEditorState()` test helper can therefore return an
incomplete syntax tree when the process is descheduled. The completion and
vector-matching tests immediately inspect that tree.
## Scope and behavior
`complete-test-parsing.patch` changes only
`module/codemirror-promql/src/test/utils-test.ts` inside `web/ui`. It completes
the small test expression with `ensureSyntaxTree(..., Infinity)` and publishes
the completed parse through an empty transaction so `syntaxTree(state)` sees
it. Failure to obtain a tree raises an error.
The original assertions remain enabled, including `autocomplete topk params 2`
and `foo * on(test,blub) bar`. The unlimited budget applies to the test helper;
production editor parsing budgets are unchanged.
## Reproduction and focused checks
Use a disposable Prometheus 3.14.0 checkout. The patch root is `web/ui`, matching
the Nix assets derivation. From this directory:
```sh
patch --fuzz=0 -d /path/to/prometheus/web/ui -p1 < complete-test-parsing.patch
cd /path/to/prometheus/web/ui
pnpm install --frozen-lockfile
pnpm --filter @prometheus-io/lezer-promql build
pnpm --filter @prometheus-io/codemirror-promql test
```
To force the scheduling condition, temporarily append this clock to
`module/codemirror-promql/setupJest.cjs` in the disposable checkout:
```js
let parseClock = 0;
Date.now = () => (parseClock += 25);
```
Each clock read crosses the editor's initial parsing budget. Against the
original helper, the hybrid and vector suites have 186 failures, including
both locally excluded cases. With the patch, all 386 CodeMirror tests pass
under that same clock. Remove the injected clock before normal builds.
## Upstream status
This is a standalone test-helper patch for Prometheus 3.14.0. No upstream
submission was made during this work. Recheck the helper when updating
Prometheus or CodeMirror, including how an ensured parse becomes visible
through the editor state.
## Local NixOS integration and build results
[`../default.nix`](../default.nix) loads `default.nix`, which patches the
separate assets derivation. It updates both `passthru.assets` and the main
Prometheus build's reference to those assets. From the repository root:
```sh
nix build --no-link -L .#nixosConfigurations.jeeves.pkgs.prometheus.assets
```
The full x86-64-v3 assets build passed with the normal clock, including the
CodeMirror and UI suites. Host-flake evaluation confirmed that the main
Prometheus derivation refers to these patched assets. The Go server package
was not rebuilt for this test-helper change.
@@ -1,4 +0,0 @@
{ psutil }:
psutil.overridePythonAttrs (old: {
patches = (old.patches or [ ]) ++ [ ./heap-info-zero-mmap.patch ];
})
@@ -1,17 +0,0 @@
Subject: [PATCH] Allow an empty mmap allocation total in heap_info
The allocator may satisfy all live allocations from the heap. A zero
mmap_used value is valid and depends on the worker allocation history.
Keep the heap and platform checks without requiring an mmap allocation.
--- a/tests/test_system.py
+++ b/tests/test_system.py
@@ -267,6 +267,7 @@
if MACOS:
assert m.mmap_used == 0 # not supported
else:
- assert m.mmap_used > 0
+ # A process can have no live mmap-backed malloc allocations.
+ assert m.mmap_used >= 0
if WINDOWS:
assert m.heap_count >= 0
@@ -0,0 +1,79 @@
# pytest-xdist test fixes
With two workers and a restart limit of three, the fourth worker crash
requests shutdown while another test can still be running. That test may
also crash. The original queued-work test requires exactly four failures,
even though five failures can occur without exceeding the replacement limit.
## Scope and behavior
`concurrent-worker-crashes.patch` changes the assertions in
`TestNodeFailure.test_max_worker_restart_tests_queued` in
`testing/acceptance_test.py`. It requires exactly three replacements, four or
five failed tests, the failed-tests exit status, the limit message, and no
internal error. It retains the two-worker workload and ten queued tests.
`worker-startup-timeout.patch` changes the remote-test helper's event timeout
from 10 to 60 seconds so loaded builders have time to start workers. The
helper returns immediately when an event arrives and still has a bounded wait.
The existing nixpkgs pytest-9 compatibility patches remain in place.
Production scheduling and worker-restart behavior are unchanged.
## Reproduction and focused checks
Use a disposable pytest-xdist 3.8.0 checkout with its test dependencies and
the nixpkgs pytest-9 compatibility patches where required. From this directory:
```sh
patch --fuzz=0 -d /path/to/pytest-xdist -p1 < concurrent-worker-crashes.patch
patch --fuzz=0 -d /path/to/pytest-xdist -p1 < worker-startup-timeout.patch
cd /path/to/pytest-xdist
python -m pytest testing/acceptance_test.py \
-k test_max_worker_restart_tests_queued -q
python -m pytest testing/test_remote.py -q
```
Twenty unmodified runs passed during the review. To force the failing
schedule, modify the generated crashing test in a disposable checkout to
accept `worker_id`: make `gw3` wait for a marker created by `gw4`, and make
`gw4` pause 0.1 seconds after creating the marker. Then both have in-flight
tests when shutdown starts. Bound the marker wait so a reproduction failure
cannot hang the suite. The original assertion fails on five reported
failures; the patched test passes.
## Worker startup and outer concurrency
[`default.nix`](default.nix) runs the outer suite with one worker to limit
nested process pools. This is a Nix test-runner setting; the source timeout
change lives in [`worker-startup-timeout.patch`](worker-startup-timeout.patch).
A separate reproduction inserts an 11-second `pytest_sessionstart` delay
into the child created by `test_basic_collect_and_runtests` in
`testing/test_remote.py`. The original 10-second channel wait fails; the
60-second wait passes. This bounds waits for test worker events, including
startup, rather than changing a product deadline.
## Upstream status
These are standalone test patches for pytest-xdist 3.8.0. No upstream submission
was made during this work. Recheck the allowed in-flight failures, replacement
count, and remote-test wait when updating the scheduler or worker behavior.
## Local NixOS integration and build results
[`../default.nix`](../default.nix) loads `default.nix` through
`pythonPackagesExtensions`. From the repository root:
```sh
nix build --no-link -L .#nixosConfigurations.jeeves.pkgs.python314Packages.pytest-xdist
```
After consolidating the settings in this directory, the full x86-64-v3 package
build passed 185 tests, with 6 existing skips and 10 expected failures. Nix
evaluation confirmed the same outer-worker limit and preserved existing
patches, with the timeout now applied as a source patch.
The earlier forced concurrent-crash and delayed-startup reproductions passed
after their fixes; the focused crash test also passed after formatting its
assertion.
@@ -0,0 +1,70 @@
# SciPy STFT test tolerances
The x86-64-v3 build can produce small floating-point residuals in inverse-STFT
comparisons and scaling round trips. The original bounds reject these results,
including residuals around `4e-17` where a round trip expects zero for a signal
with amplitude 2.
## Scope and behavior
`stft-test-tolerances.patch` changes only the signal tests:
- The inverse-STFT comparison in `_scipy_spectral_test_shim.py` uses
`max(1e-7, 2 * np.finfo(x.dtype).eps)` as its relative tolerance. Float64
keeps the original bound, and the existing i686 override remains.
- Three scaling round trips in `test_spectral.py` gain an absolute tolerance
of one epsilon for the input dtype, allowing small residuals near zero.
The tests remain enabled, and the production STFT implementation is unchanged.
## Reproduction and focused checks
From this directory, apply the patch to a disposable SciPy 1.18.0 checkout:
```sh
patch --fuzz=0 -d /path/to/scipy -p1 < stft-test-tolerances.patch
```
Build and install that tree with SciPy's test dependencies. From outside the
source directory, run the installed tests:
```sh
python -m pytest --pyargs scipy.signal.tests.test_spectral \
-k 'roundtrip_float32 or roundtrip_scaling' -q
```
Use the same compiler flags and numerical libraries for before/after runs.
The earlier reproduction called `TestSTFT.test_roundtrip_float32` and
`TestSTFT.test_roundtrip_scaling` against the x86-64-v3 libraries, then loaded
patched copies of the test modules. Both failed with the original bounds
and passed with the adjusted bounds.
## Upstream status
[SciPy issue #25488](https://github.com/scipy/scipy/issues/25488) records
related test failures with architecture-specific compiler flags. It is
context for the local tolerance repair; this exact patch has not been
submitted upstream during this work.
## Local NixOS integration and build results
[`../default.nix`](../default.nix) loads [`default.nix`](default.nix) through
`pythonPackagesExtensions`, preserving the package's existing patches.
The override also covers SciPy used to test other Python dependencies,
including pgvector in portal's shared Python environment.
From the repository root:
```sh
nix build --no-link -L .#nixosConfigurations.portal-1.pkgs.python314Packages.scipy
```
The original remote build of patched SciPy 1.18.0 passed 87,723 tests, with
8,342 skips, 300 expected failures, and 22 unexpected passes. The patch and
override have been restored byte-for-byte from commit `24cbf74f`; those counts
describe the earlier full build.
Restoration checks confirmed that the patch applies to the pinned source
without fuzz, portal's evaluated SciPy retains its existing patch and install
checks, and pgvector uses the patched SciPy. A full package or system rebuild
was not repeated for this restoration.
@@ -1,4 +0,0 @@
{ zopfli }:
zopfli.overrideAttrs (old: {
patches = (old.patches or [ ]) ++ [ ./unaligned-match-loads.patch ];
})
@@ -1,32 +0,0 @@
Subject: [PATCH] Read unaligned match buffers with memcpy
Byte buffers need not satisfy integer alignment or aliasing requirements.
GCC 16 with x86-64-v3 vectorizes the cast loads using aligned AVX reads,
causing a segmentation fault. Copy into local integers instead.
--- a/src/zopfli/lz77.c
+++ b/src/zopfli/lz77.c
@@ -302,13 +302,20 @@
if (sizeof(size_t) == 8) {
/* 8 checks at once per array bounds check (size_t is 64-bit). */
- while (scan < safe_end && *((size_t*)scan) == *((size_t*)match)) {
+ while (scan < safe_end) {
+ size_t scan_word, match_word;
+ memcpy(&scan_word, scan, sizeof(scan_word));
+ memcpy(&match_word, match, sizeof(match_word));
+ if (scan_word != match_word) break;
scan += 8;
match += 8;
}
} else if (sizeof(unsigned int) == 4) {
/* 4 checks at once per array bounds check (unsigned int is 32-bit). */
- while (scan < safe_end
- && *((unsigned int*)scan) == *((unsigned int*)match)) {
+ while (scan < safe_end) {
+ unsigned int scan_word, match_word;
+ memcpy(&scan_word, scan, sizeof(scan_word));
+ memcpy(&match_word, match, sizeof(match_word));
+ if (scan_word != match_word) break;
scan += 4;
match += 4;
}
Generated
+6 -6
View File
@@ -45,11 +45,11 @@
]
},
"locked": {
"lastModified": 1790989254,
"narHash": "sha256-ZN+riaeFuE+DZ5zfqJiXlM12jLwZzeIXiNkU1YeHMXI=",
"lastModified": 1790972726,
"narHash": "sha256-TVgDIhNGx2bLl1fBOr13YOMJTiMKEoxpxT8leF7hh/8=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "acd21c5a3420a9d5fd0ed06299b10828267ef9ba",
"rev": "47c54aec43c13a728094881551db60b5cc4cc16e",
"type": "github"
},
"original": {
@@ -114,11 +114,11 @@
},
"nixpkgs-master": {
"locked": {
"lastModified": 1791033472,
"narHash": "sha256-IRpPnIfZ0drkkyMtuuvXaada2RjH+G2WYv14Ap1W8q4=",
"lastModified": 1790985011,
"narHash": "sha256-VgqQfUblvgQomOUkyTCy5YE1mjRn8VBS8g984p9Ft4s=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "ad70d223e208533b7ee8cc8e336a34aad134ab19",
"rev": "4379d026b3ff838d5a8655451280c4fdf2e3e96a",
"type": "github"
},
"original": {
-8
View File
@@ -15,14 +15,6 @@
};
};
# trunk 0.21.14 bundles libdeflate 1.23, which uses target attributes that
# GCC 16 removed. libdeflate 1.25 dropped these qualifiers upstream too.
trunk-gcc16 = _final: prev: {
trunk = import ../common/optional/x86-64-v3/patches/trunk {
inherit (prev) trunk jq;
};
};
# Baseline x86-64 (v1) packages for prebuilt applications that should not
# inherit an x86-64-v3 host platform.
x86-v1 = final: _prev: {
+77
View File
@@ -0,0 +1,77 @@
# Ebook Search Docker
Run the EPUB search app against the existing Postgres database on `jeeves`:
```sh
python -m python.ebook_search.docker.containers start --library-path /path/to/epubs --build
```
All ebook-search Docker files live in this directory:
- `Dockerfile` — multi-stage: `test` (runs pytest) and `runtime` (default target, the app image)
- `docker-compose.yml`
- `containers.py` — Typer lifecycle CLI
- `pyproject.toml` / `uv.lock` — the container's uv-locked dependencies
The app listens on `http://localhost:8070`.
Useful lifecycle commands:
```sh
python -m python.ebook_search.docker.containers build
python -m python.ebook_search.docker.containers start --library-path /path/to/epubs
python -m python.ebook_search.docker.containers test
python -m python.ebook_search.docker.containers logs
python -m python.ebook_search.docker.containers ps
python -m python.ebook_search.docker.containers stop
```
Direct compose usage from the repo root:
```sh
docker compose -f python/ebook_search/docker/docker-compose.yml ps
```
## Dependencies
The image builds its environment with uv from `pyproject.toml` + `uv.lock` in this
directory — this is the source of truth for the container's dependencies. To add or
update a dependency, edit `pyproject.toml` here and regenerate the lock (uv is
available in the `ebook-search` dev shell):
```sh
nix develop .#ebook-search -c uv lock --project python/ebook_search/docker
```
## Tests
The main pytest suite excludes `tests/ebook_search` (its dependencies are no longer
in the nix dev shell). The `test ebook search` CI workflow runs them in a uv env
built from the lockfile in this directory — same commands work locally from the
repo root (the `--override-ini` drops the main suite's ignore):
```sh
uv sync --locked --project python/ebook_search/docker
uv run --project python/ebook_search/docker --no-sync pytest tests/ebook_search --override-ini addopts="-n auto -ra"
```
They can also run inside the Docker `test` image, which validates the image itself:
```sh
python -m python.ebook_search.docker.containers test
```
or the raw docker equivalent:
```sh
docker build --file python/ebook_search/docker/Dockerfile --target test --tag ebook-search:test .
docker run --rm ebook-search:test
```
## Configuration
The compose service loads the repo root `.env` into the container via `env_file`.
Mount your EPUB directory by setting `EBOOK_LIBRARY_HOST_PATH` in an env file or on the command line. The container sees it as `/library`, and `EBOOK_SEARCH_LIBRARY_PATHS` is set to `/library` inside the container.
Database connection settings are controlled by `RICHIE_DB`, `RICHIE_HOST`, `RICHIE_PORT`, `RICHIE_USER`, and `RICHIE_PASSWORD`. The default host is `jeeves`.
+54
View File
@@ -0,0 +1,54 @@
# Gems
Gems is a server-rendered, turn-based resource-engine game for one to four human or AI players. It uses FastAPI,
Jinja, HTMX, server-sent events, and SQLite.
The application deliberately contains no playable card deck, patron/governor set, objective set, official artwork,
or copied rulebook text. A room host must upload a content pack they are entitled to use before starting a game.
## Run locally
```shell
uv run gems --host 127.0.0.1 --port 8082
```
The default database and installation key are created under `.gems/`. The following environment variables override
runtime behavior:
- `GEMS_DATABASE_PATH`
- `GEMS_KEY_PATH`
- `GEMS_PUBLIC_ORIGIN`
- `GEMS_SECURE_COOKIES`
- `GEMS_HOST`
- `GEMS_PORT`
## Content packs
The current schema is available from a running server at `/schemas/content-pack-v1.json`. A pack defines exactly
five normal resources, one wild resource, cards, and optional patrons, objectives, and outposts. `patrons` is the
canonical field name; `governors` is accepted as an input alias.
Cards may use only the built-in, bounded effect vocabulary:
- `none`
- `virtual_wild`
- `copy_bonus`
- `copy_and_claim`
- `multi_bonus`
- `claim_free`
- an optional discard-cards alternate cost
Unknown fields, resource references, executable expressions, HTML, artwork URLs, and files larger than 512 KiB are
rejected. The normalized pack is private to its room and becomes immutable when play starts.
## Neutral module mapping
Gems calls the four optional mechanics Objectives, Outposts, Eastern Decks, and Fortifications. Lobby presets combine
these mechanics into the familiar base, objective-race, objective-plus-outpost, eastern-plus-fortification, and
all-module configurations. Component identities and values always come from the uploaded pack.
## Jeeves
The NixOS module runs one Uvicorn worker on `127.0.0.1:8002`, stores state in
`/zfs/media/services/gems`, and publishes it through HAProxy at `https://gems.tmmworkshop.com`. The DNS record must
point to Jeeves before ACME can issue the certificate.
+3
View File
@@ -0,0 +1,3 @@
# docker_networks
docker network create -d bridge web
+33
View File
@@ -0,0 +1,33 @@
# Monitoring
## Vultr API metrics
The `vultr-exporter` service reads its API token from:
```text
/zfs/storage/secrets/services/vultr-exporter
```
Create the file on Jeeves as root with the following contents:
```text
API_KEY=<Vultr API token>
```
The token needs read access to the Vultr Account and Billing APIs. Unrelated
resource collectors are disabled in the packaged exporter.
Restrict the file to root and ensure the public egress IP used by Jeeves is
allowed for the token in the Vultr API settings:
```console
sudo chown root:root /zfs/storage/secrets/services/vultr-exporter
sudo chmod 600 /zfs/storage/secrets/services/vultr-exporter
```
The exporter listens on `127.0.0.1:9188`; it is scraped by the local
`prometheus-main` service every five minutes and is not exposed through the
host firewall.
Portal-1 exposes its node exporter only through `tailscale0` on port `9100`.
Jeeves reaches it using the Portal-1 Tailscale hostname.
+83
View File
@@ -0,0 +1,83 @@
# portal_1
Minimal NixOS target for a Vultr VM, installed with nixos-anywhere. The Nix
flake target is `portal_1`; the machine hostname is `portal-1` because DNS
hostnames cannot contain underscores.
## Before deploying
1. Confirm the VM's system disk is `/dev/vda`. If it is not, update both
references in `disk-config.nix`.
2. Confirm the SSH public key in `default.nix` is the key that should have
administrator access.
3. Boot the VM into a NixOS installer or another nixos-anywhere-compatible
Linux rescue environment with root SSH access. Keep this environment
running while completing the SOPS bootstrap below.
## Bootstrap SOPS
Use the rescue environment's SSH host key as the permanent portal identity.
Replace `VM_IP` below:
```console
ssh root@VM_IP 'cat /etc/ssh/ssh_host_ed25519_key.pub' | \
nix shell nixpkgs#ssh-to-age --command ssh-to-age
```
This prints an `age1...` recipient; it does not copy the private key. Add the
recipient to `.sops.yaml`:
```yaml
- &system_portal_1 age1...
```
Then add `*system_portal_1` to the age recipients for
`users/secrets.yaml`. Re-encrypt the existing file for the new recipient and
add the Tailscale key:
```console
nix shell nixpkgs#sops --command sops updatekeys users/secrets.yaml
nix shell nixpkgs#sops --command sops users/secrets.yaml
```
Add the OAuth client secret from the `Auth Keys: Write` credential in the SOPS
editor and save it:
```yaml
tailscale_auth_key: tskey-client-...
```
## Deploy
From the repository root, replace `VM_IP` with the VM's public IP:
```console
nix run github:nix-community/nixos-anywhere -- \
--copy-host-keys --flake .#portal_1 root@VM_IP
```
This repartitions `/dev/vda`, so anything already on that disk is erased. The
layout reserves 8 GiB for swap and assigns the remaining space to the root
filesystem.
`--copy-host-keys` preserves the same private SSH host key at
`/etc/ssh/ssh_host_ed25519_key` on the installed system. SOPS-Nix converts that
key to an age identity during activation. After the reboot, connect as
`richie` and verify that automatic Tailscale enrollment succeeded:
```console
ssh -p 278 richie@VM_IP
sudo tailscale status
```
The installed OpenSSH service listens on port 278. Port 22 is served by
Endlessh and will not provide an SSH login.
HAProxy uses the same frontend, routing, and rate-limiting configuration as
Jeeves. Portal manages the ACME certificates for the existing public domains;
their DNS records must resolve to Portal for HTTP-01 issuance and renewal.
The application backends still use Jeeves' original `127.0.0.1` addresses.
Replace them with the corresponding Tailscale addresses before directing
application traffic through Portal. Ports 80 and 443 are allowed through the
firewall.