incus-pet: hello-web example flake — minimal contract demonstration

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.<sys>.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/
  <!doctype html>
  <h1>Hello from incus-pet</h1>

Idempotent re-deploy verified (no flags needed — host-port + listen
read back from container metadata; container name auto-detected from
incus.container.hostname).
This commit is contained in:
Sridhar Ratnakumar 2026-05-23 14:47:54 -04:00
parent caea54f185
commit 293e53071c
3 changed files with 182 additions and 0 deletions

View file

@ -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.<sys>.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.

View file

@ -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
}

View file

@ -0,0 +1,115 @@
# hello-web — minimal incus-pet example.
#
# Self-contained reference flake that ships the three outputs incus-pet
# consumes:
#
# - packages.<sys>.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 <path-to-this-flake> --port 8081 --listen <ip>
{
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" ''
<!doctype html>
<meta charset="utf-8">
<title>hello-web</title>
<h1>Hello from incus-pet</h1>
<p>Served by darkhttpd inside an incus container.</p>
'';
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);
};
}