feat: bake a deploy-time server pin into the client via presetServer

This commit is contained in:
randogoth 2026-09-26 20:00:12 +03:00
parent db83ec1289
commit 4075afbb11
5 changed files with 93 additions and 6 deletions

View file

@ -2,11 +2,23 @@
# shebang (`uv run --script`) is only for `devbox run serve`; here it runs
# under a plain interpreter with aiohttp from nixpkgs, so no network access
# or uv is needed at build or run time — required for the Nix sandbox anyway.
{ lib, stdenvNoCC, makeWrapper, python3, nix-gitignore }:
{ lib, stdenvNoCC, makeWrapper, python3, nix-gitignore
# A server this deployment pre-pins for every first-time visitor (the NixOS
# module's `presetServer`). Both or neither — see web/js/config.js. Baking a
# config value into the served JS is a build-time concern, not something
# bridge.py — "one file, no keys" — should gain runtime logic to do.
, serverHost ? null
, serverPublicKey ? null
}:
let
pythonEnv = python3.withPackages (ps: [ ps.aiohttp ]);
hasPreset = serverHost != null && serverPublicKey != null;
presetLine = "export const PRESET_SERVER = "
+ builtins.toJSON { host = serverHost; publicKey = serverPublicKey; } + ";";
in
assert lib.assertMsg ((serverHost == null) == (serverPublicKey == null))
"gsmol-bridge: serverHost and serverPublicKey must be set together, or not at all";
stdenvNoCC.mkDerivation {
pname = "gsmol-bridge";
version = "0.1.0";
@ -30,6 +42,10 @@ stdenvNoCC.mkDerivation {
cp bridge.py $out/share/gsmol/bridge.py
cp -r web $out/share/gsmol/web
${lib.optionalString hasPreset ''
printf '%s\n' ${lib.escapeShellArg presetLine} > $out/share/gsmol/web/js/config.js
''}
makeWrapper ${pythonEnv}/bin/python3 $out/bin/gsmol-bridge \
--add-flags "$out/share/gsmol/bridge.py" \
--add-flags "--dir $out/share/gsmol/web"

View file

@ -13,9 +13,56 @@ in
package = lib.mkOption {
type = lib.types.package;
default = self.packages.${pkgs.system}.default;
defaultText = lib.literalExpression "gsmol.packages.\${system}.default";
description = "The gsmol-bridge package to run.";
default =
if cfg.presetServer == null
then self.packages.${pkgs.system}.default
else self.packages.${pkgs.system}.default.override {
serverHost = cfg.presetServer.host;
serverPublicKey = cfg.presetServer.publicKey;
};
defaultText = lib.literalExpression
"gsmol.packages.\${system}.default, built with presetServer baked in when set";
description = ''
The gsmol-bridge package to run. Overriding this yourself bypasses
`presetServer` — bake it in via the same `.override` if you need both.
'';
};
presetServer = lib.mkOption {
type = lib.types.nullOr (lib.types.submodule {
options = {
host = lib.mkOption {
type = lib.types.str;
example = "example.org";
description = "Hostname of the smolmaild server to pre-pin.";
};
publicKey = lib.mkOption {
type = lib.types.str;
example = "mfrggzdfmztwq2lknnwg23tpobxxk4tznb2xg5btmvwgy3zmn5xg4===";
description = ''
Its static key, base32-encoded exactly as gsmol's own settings
dialog and `smolmaild --keygen` display it.
'';
};
};
});
default = null;
description = ''
Bake a server key into the pages this deployment serves, so a
first-time visitor is pre-pinned rather than asked to copy a base32
key into settings by hand (SPEC.md §4).
This only makes sense when whoever runs this deployment and whoever
runs that smolmaild are the same trusted party: the value reaches the
browser over this deployment's own TLS, which stands in for the "get
it from the operator through a trusted channel" step. Do not set this
for a key you do not operate or otherwise vouch for — it pins it for
every visitor, unasked.
It only seeds the first run: the browser stores it as an ordinary
pin from then on, which the user can still replace or remove in
settings like any other.
'';
};
host = lib.mkOption {