From 293e53071c1c03da637520d1a8cc4a56b4a16cf2 Mon Sep 17 00:00:00 2001 From: Sridhar Ratnakumar Date: Sat, 23 May 2026 14:47:54 -0400 Subject: [PATCH] =?UTF-8?q?incus-pet:=20hello-web=20example=20flake=20?= =?UTF-8?q?=E2=80=94=20minimal=20contract=20demonstration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Self-contained reference under modules/nixos/linux/incus/incus-pet/example/hello-web/ that satisfies the incus-pet contract in the smallest possible form: - packages..default a darkhttpd wrapper that serves a one-page index.html, reading HOST/PORT from the environment - nixosModules.default services.hello-web.{enable, package, host, port} + a DynamicUser=true systemd unit - nixosModules.incus the deploy contract: services.hello-web bound to 8080, hostname "hello-web" Useful as a copy-paste template for new apps. Less moving parts than the anywhen reference (no bun, no SQLite, no state dir, no e2e tests to keep green) — just three flake outputs and a static HTML response. Live-deployed end-to-end on pureintent during this PR's bring-up: $ incus-pet deploy path:./.../example/hello-web hello-web \ --port 8081 --listen 100.122.32.106 $ curl http://100.122.32.106:8081/

Hello from incus-pet

Idempotent re-deploy verified (no flags needed — host-port + listen read back from container metadata; container name auto-detected from incus.container.hostname). --- .../incus-pet/example/hello-web/README.md | 40 ++++++ .../incus-pet/example/hello-web/flake.lock | 27 ++++ .../incus-pet/example/hello-web/flake.nix | 115 ++++++++++++++++++ 3 files changed, 182 insertions(+) create mode 100644 modules/nixos/linux/incus/incus-pet/example/hello-web/README.md create mode 100644 modules/nixos/linux/incus/incus-pet/example/hello-web/flake.lock create mode 100644 modules/nixos/linux/incus/incus-pet/example/hello-web/flake.nix diff --git a/modules/nixos/linux/incus/incus-pet/example/hello-web/README.md b/modules/nixos/linux/incus/incus-pet/example/hello-web/README.md new file mode 100644 index 0000000..af57845 --- /dev/null +++ b/modules/nixos/linux/incus/incus-pet/example/hello-web/README.md @@ -0,0 +1,40 @@ +# hello-web — incus-pet reference example + +Smallest possible flake that satisfies the [incus-pet contract](../../SKILL.md). +A static HTTP server (`darkhttpd` wrapping a one-page `index.html`) packaged +through the full stack: + +- `packages..default` — the binary +- `nixosModules.default` — `services.hello-web.{enable, package, host, port}` + a systemd unit +- `nixosModules.incus` — the deploy contract (binds 8080 inside a `hello-web` container) + +## Build it standalone + +```sh +nix build path:./modules/nixos/linux/incus/incus-pet/example/hello-web + +# Then run it locally — listens on 0.0.0.0:8080. +./result/bin/hello-web & +curl http://127.0.0.1:8080 +``` + +## Deploy it as an incus-pet container + +From a host that has the incus daemon and the `incus-pet` CLI on PATH: + +```sh +nix run path:.#incus-pet -- deploy \ + path:./modules/nixos/linux/incus/incus-pet/example/hello-web \ + hello-web --port 8081 --listen 100.122.32.106 + +curl http://100.122.32.106:8081 +``` + +`incus-pet list` will show the running container; `incus-pet rm hello-web` +tears it down. + +## What to read next + +The flake here is meant to be a working template. Compare the three +outputs (`packages.default`, `nixosModules.default`, `nixosModules.incus`) +side-by-side with what srid/anywhen ships — same shape, scaled down. diff --git a/modules/nixos/linux/incus/incus-pet/example/hello-web/flake.lock b/modules/nixos/linux/incus/incus-pet/example/hello-web/flake.lock new file mode 100644 index 0000000..5966c45 --- /dev/null +++ b/modules/nixos/linux/incus/incus-pet/example/hello-web/flake.lock @@ -0,0 +1,27 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1779357205, + "narHash": "sha256-cCO8aTqss5x9Ky8GWkpY0Hy5fyTZEbtifSUV8QjSzic=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "f83fc3c307e74bc5fd5adb7eb6b8b13ffd2a36e1", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/modules/nixos/linux/incus/incus-pet/example/hello-web/flake.nix b/modules/nixos/linux/incus/incus-pet/example/hello-web/flake.nix new file mode 100644 index 0000000..66380c6 --- /dev/null +++ b/modules/nixos/linux/incus/incus-pet/example/hello-web/flake.nix @@ -0,0 +1,115 @@ +# hello-web — minimal incus-pet example. +# +# Self-contained reference flake that ships the three outputs incus-pet +# consumes: +# +# - packages..default a tiny static-file server (darkhttpd +# wrapped to read HOST/PORT from env) +# - nixosModules.default services.hello-web.{enable, package, +# host, port} + a systemd unit +# - nixosModules.incus the deploy contract — wires the +# service to bind 8080 inside an incus +# container at hostname "hello-web" +# +# Deploy with (from a host that has the incus daemon + incus-pet CLI): +# +# nix run github:srid/nixos-config#incus-pet -- \ +# deploy --port 8081 --listen +{ + inputs.nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; + + outputs = { self, nixpkgs, ... }: + let + systems = [ "x86_64-linux" "aarch64-linux" ]; + eachSystem = f: nixpkgs.lib.genAttrs systems + (system: f nixpkgs.legacyPackages.${system}); + in + { + packages = eachSystem (pkgs: + let + www = pkgs.writeTextDir "index.html" '' + + + hello-web +

Hello from incus-pet

+

Served by darkhttpd inside an incus container.

+ ''; + hello-web = pkgs.writeShellApplication { + name = "hello-web"; + runtimeInputs = [ pkgs.darkhttpd ]; + text = '' + HOST="''${HOST:-0.0.0.0}" + PORT="''${PORT:-8080}" + exec darkhttpd ${www} --addr "$HOST" --port "$PORT" + ''; + meta.mainProgram = "hello-web"; + }; + in + { + default = hello-web; + hello-web = hello-web; + }); + + nixosModules.default = { config, lib, pkgs, ... }: + let + cfg = config.services.hello-web; + in + { + options.services.hello-web = { + enable = lib.mkEnableOption "hello-web example server"; + package = lib.mkOption { + type = lib.types.package; + description = "The hello-web package to run (must expose bin/hello-web via meta.mainProgram)."; + }; + host = lib.mkOption { + type = lib.types.str; + default = "0.0.0.0"; + description = "Address the HTTP server binds to."; + }; + port = lib.mkOption { + type = lib.types.port; + default = 8080; + description = "Port the HTTP server listens on."; + }; + }; + + config = lib.mkIf cfg.enable { + systemd.services.hello-web = { + description = "hello-web example HTTP server"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + environment = { + HOST = cfg.host; + PORT = toString cfg.port; + }; + serviceConfig = { + ExecStart = lib.getExe cfg.package; + Restart = "on-failure"; + RestartSec = 2; + DynamicUser = true; + }; + }; + }; + }; + + # incus-pet contract — see SKILL.md in the incus-pet tree. + nixosModules.incus = { config, lib, pkgs, ... }: { + imports = [ self.nixosModules.default ]; + + incus.container = { + enable = true; + hostname = lib.mkDefault "hello-web"; + }; + system.stateVersion = "25.05"; + + services.hello-web = { + enable = true; + package = lib.mkDefault self.packages.${pkgs.stdenv.hostPlatform.system}.default; + host = lib.mkDefault "0.0.0.0"; + port = 8080; # fixed by the incus-pet contract + }; + }; + + formatter = eachSystem (pkgs: pkgs.nixpkgs-fmt); + }; +}