Compare commits

...
8 Commits
Author SHA1 Message Date
finn.markwitzandClaude Sonnet 5 84ada68069 fix(mibook): disable hermes-agent's built-in self-update GitHub polling
Its self-updater was independently polling GitHub for the same repo the
flake input tracks, and stacked with a runaway crash-restart loop this
exhausted the GitHub rate limit, causing unrelated flake fetches to 429.
The Nix flake input is already the source of truth for hermes's version.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F9ge2kVMD55sLJExx3v2G3
2026-09-29 17:36:18 +02:00
finn.markwitz 07a1d8ee62 feat(mibook): open port 47823 for hermes ad-hoc webpage hosting
Distinct from the existing 9119 dashboard port — this lets the agent
serve one-off pages to devices on the local network.
2026-09-26 08:15:58 +02:00
finn.markwitz c1fc8e2a05 Merge pull request 'fix(homepage): merge service groups by name to stop manual entries being dropped' (#14) from fix/homepage-duplicate-service-groups into main
Reviewed-on: #14
2026-09-21 15:29:59 +02:00
finn.markwitzandClaude Sonnet 5 202eca2d03 fix(homepage): merge service groups by name to stop manual entries being dropped
homepage-dashboard resolves services.yaml groups by name using first-match
lookup, so the module-contributed "Services" group and manual-services.nix's
"Services" group (two separate top-level YAML list entries) caused the
second one to be silently discarded. Merge by group name before handing the
list to services.homepage-dashboard.services.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JbLevsLozTCcCWe1SoUGe1
2026-09-21 15:27:07 +02:00
finn.markwitz 2ff3042cd9 Merge pull request 'feat(mibook): add swapfile and nodejs dev tooling' (#13) from feat/mibook-swapfile into main
Reviewed-on: #13
2026-09-21 15:21:22 +02:00
finn.markwitzandClaude Sonnet 5 d219f2b61c feat(mibook): add swapfile and nodejs dev tooling
Add an 8G swapfile for headroom during nix builds (7.7G RAM, no prior
swap) and bring nodejs (bundles npm) into the development profile.
Also picks up latest flake.lock input updates.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C5fRUDLRzDhoKhEqa69cyR
2026-09-21 15:13:03 +02:00
finn.markwitz 5a2a649244 feat: Sops for Jupiter Setup 2026-09-19 18:48:53 +02:00
finn.markwitz 4691eb1aa4 Merge pull request 'Feat/sops nix' (#12) from feat/sops-nix into main
Reviewed-on: #12
2026-09-14 13:06:45 +02:00
7 changed files with 133 additions and 105 deletions
+7 -5
View File
@@ -1,7 +1,7 @@
keys: keys:
- &admin_finn age1a0v442ct2qfjcdhyktn25spw02yq4fl08plrrvuradwwxzsle4mq8thln4 - &admin_finn age1a0v442ct2qfjcdhyktn25spw02yq4fl08plrrvuradwwxzsle4mq8thln4
- &host_mibook age1n3uqtee5ymykrls6k04dznq50cwmj99hw7ny83yxjndt4y2ncf5qhjwa2p - &host_mibook age1n3uqtee5ymykrls6k04dznq50cwmj99hw7ny83yxjndt4y2ncf5qhjwa2p
# - &host_jupiter age1... # TODO: Phase 9, see docs/secrets.md - &host_jupiter age15uxuaxhn2hr4ypmgll36fwnkdkvv6gynvlxuc2p8ek0enmxmr5rsvlq0r2
creation_rules: creation_rules:
- path_regex: ^secrets/mibook\.yaml$ - path_regex: ^secrets/mibook\.yaml$
@@ -9,9 +9,11 @@ creation_rules:
- age: - age:
- *admin_finn - *admin_finn
- *host_mibook - *host_mibook
# - path_regex: ^secrets/jupiter\.yaml$ # TODO: Phase 9 - path_regex: ^secrets/jupiter\.yaml$
# key_groups: key_groups:
# - age: [ *admin_finn, *host_jupiter ] - age:
# - path_regex: ^secrets/common\.yaml$ # TODO: Phase 9 - *admin_finn
- *host_jupiter
# - path_regex: ^secrets/common\.yaml$ # TODO: only if/when a secret needs to be shared across both hosts
# key_groups: # key_groups:
# - age: [ *admin_finn, *host_mibook, *host_jupiter ] # - age: [ *admin_finn, *host_mibook, *host_jupiter ]
Generated
+36 -36
View File
@@ -21,11 +21,11 @@
"nixpkgs-lib": "nixpkgs-lib" "nixpkgs-lib": "nixpkgs-lib"
}, },
"locked": { "locked": {
"lastModified": 1785627969, "lastModified": 1788450739,
"narHash": "sha256-4dtXQk/NMePegK/nWp5NSeuZKLATItOq61lpEvmXqGw=", "narHash": "sha256-glZLQlzIn1fXH6PazR2iUmTo7kzzyYSshrWhLS9TqCU=",
"owner": "hercules-ci", "owner": "hercules-ci",
"repo": "flake-parts", "repo": "flake-parts",
"rev": "427bf4bd9435fdf21321c8cc628c24efc14c0f7a", "rev": "31729ca8cbdb4fa927b34e5f4353e6a83f39e993",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -63,11 +63,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1782949081, "lastModified": 1785627969,
"narHash": "sha256-vp6Y/Grm98ESt6ceOkWiHWyZRDV3J1RID4w+6NWK9yA=", "narHash": "sha256-4dtXQk/NMePegK/nWp5NSeuZKLATItOq61lpEvmXqGw=",
"owner": "hercules-ci", "owner": "hercules-ci",
"repo": "flake-parts", "repo": "flake-parts",
"rev": "17c9d6cdfc60c64f4ee8d306f9bc0b4ccb51481e", "rev": "427bf4bd9435fdf21321c8cc628c24efc14c0f7a",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -87,11 +87,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1783008725, "lastModified": 1784288435,
"narHash": "sha256-jGiy6+sxjNWXSjp25uoJuNfyH9zBK1PEDY0lVoL4ibQ=", "narHash": "sha256-ReRHaLgr/uVqdD8afFSn+myXIfpHeOhP0yYe0TJqAA8=",
"owner": "cachix", "owner": "cachix",
"repo": "git-hooks.nix", "repo": "git-hooks.nix",
"rev": "bca82caa46d5ec0f5d422c61fb1e30bc51313cbe", "rev": "43b3c1ab9d40fb1dbb008f451988a91e375825e9",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -113,11 +113,11 @@
"uv2nix": "uv2nix" "uv2nix": "uv2nix"
}, },
"locked": { "locked": {
"lastModified": 1788877844, "lastModified": 1789361774,
"narHash": "sha256-9Ik9egIxYhW/IDw1mz/dYN0IqLTP3bEe+LdEFtP9FWM=", "narHash": "sha256-ashkMxyQelHq7T4AcJ0Zwuzm9/jMR3hTCWW+2FPwlDg=",
"owner": "NousResearch", "owner": "NousResearch",
"repo": "hermes-agent", "repo": "hermes-agent",
"rev": "c8aa5608c24e3636e77c267650c0f1f52e44adb0", "rev": "5eb99eb2844b22ebb723711b8e6a0bbb80bb5f04",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -157,11 +157,11 @@
"nixpkgs-regression": "nixpkgs-regression" "nixpkgs-regression": "nixpkgs-regression"
}, },
"locked": { "locked": {
"lastModified": 1787274306, "lastModified": 1789352761,
"narHash": "sha256-Qg9f9td5iphUWSQS6zmvyZWO1F+D7j8Z3U6dGyUTg08=", "narHash": "sha256-jAcDj4iXkbxa5AlooMRZj7H3E81Cz/q7yN/bX7GPFSU=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nix", "repo": "nix",
"rev": "649e823fb24ed118d72e613be35fa8ea1b64afe7", "rev": "920f6956361496578b8a97ae04ec5f400d729ab4",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -211,11 +211,11 @@
"nixpkgs": "nixpkgs_2" "nixpkgs": "nixpkgs_2"
}, },
"locked": { "locked": {
"lastModified": 1787144466, "lastModified": 1789127271,
"narHash": "sha256-HHfv2/HkNSKbbSyU9iD/g8lbP6r4tl33sSw1W4rXCk0=", "narHash": "sha256-vL/Fpo7Zxa7wvO8V5FAToUBvP+K3XsMB1wUglTtd/J0=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixos-hardware", "repo": "nixos-hardware",
"rev": "0471accf8d0a8210b31d947497d179ecc99e0021", "rev": "24cfdc1f9344b90a1eee329a3906e2f39f4d0f1e",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -226,11 +226,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1783148766, "lastModified": 1788921488,
"narHash": "sha256-H9+N+GFtsbVC8ZniHliChM7ndizxtqVZs6bnGOLM3WQ=", "narHash": "sha256-JLopN/XSodSsJIZ7v7tEjpPZ+QXc883s3HoUIxHD8Io=",
"rev": "a50de1b7d8a586adc18d2395c19de7d6058e6030", "rev": "6aefcda9401be8acc2b74244fb3b37520ea1f0a8",
"type": "tarball", "type": "tarball",
"url": "https://releases.nixos.org/nixos/26.05/nixos-26.05.4193.a50de1b7d8a5/nixexprs.tar.xz" "url": "https://releases.nixos.org/nixos/26.05/nixos-26.05.9440.6aefcda9401b/nixexprs.tar.xz"
}, },
"original": { "original": {
"type": "tarball", "type": "tarball",
@@ -255,11 +255,11 @@
}, },
"nixpkgs-lib": { "nixpkgs-lib": {
"locked": { "locked": {
"lastModified": 1785031560, "lastModified": 1788057806,
"narHash": "sha256-OmshNvn2vupOFpYinLUu+1Dnpu4n7Q5N3ggGVNHpkUI=", "narHash": "sha256-DTQSMxzDWmT0zhguthvegnVkn7CFqGCv4IHCzk5ZUpM=",
"owner": "nix-community", "owner": "nix-community",
"repo": "nixpkgs.lib", "repo": "nixpkgs.lib",
"rev": "0e79af5e3d4dcfcd676ab5ba3f95d2e3352e078c", "rev": "596e2e3940e09b2abbeb03f75fa1828c57fcd72c",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -286,11 +286,11 @@
}, },
"nixpkgs-unstable": { "nixpkgs-unstable": {
"locked": { "locked": {
"lastModified": 1787135253, "lastModified": 1789286504,
"narHash": "sha256-M5/r2v++FjVhdsxXYMb4BDJ5YLAdCWFt3aZotcshocA=", "narHash": "sha256-3i80JnZ9pZ7tWReb/Sqpc32cfzffCu8TWLnbnH0hN9c=",
"rev": "ffb3c9b700e759be2ef13237c9d8f953b32a1e46", "rev": "ef34387ddd751e1ab8857adf4676492d32eb24ec",
"type": "tarball", "type": "tarball",
"url": "https://releases.nixos.org/nixos/unstable/nixos-26.11pre1058091.ffb3c9b700e7/nixexprs.tar.xz" "url": "https://releases.nixos.org/nixos/unstable/nixos-26.11pre1073009.ef34387ddd75/nixexprs.tar.xz"
}, },
"original": { "original": {
"id": "nixpkgs", "id": "nixpkgs",
@@ -313,11 +313,11 @@
}, },
"nixpkgs_3": { "nixpkgs_3": {
"locked": { "locked": {
"lastModified": 1787101114, "lastModified": 1789114715,
"narHash": "sha256-BA7sSNjLDuPGSOYBGpr6WQjke1MQ8AZpJ8GlYZM/mOc=", "narHash": "sha256-eeH+/1GxEjw8NCObjaHVIy3YvCQ4Qckrx4LZj5xBaU4=",
"rev": "b18a4b905f8d028dc4476412e6d6891728695379", "rev": "21a67dc470149f337cecafbe965d8d252a390518",
"type": "tarball", "type": "tarball",
"url": "https://releases.nixos.org/nixos/26.05/nixos-26.05.8045.b18a4b905f8d/nixexprs.tar.xz" "url": "https://releases.nixos.org/nixos/26.05/nixos-26.05.9592.21a67dc47014/nixexprs.tar.xz"
}, },
"original": { "original": {
"id": "nixpkgs", "id": "nixpkgs",
@@ -415,11 +415,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1788337237, "lastModified": 1788914643,
"narHash": "sha256-gkSH8VUtCo6hnysNmb9DbTuDepH2t5pv+QWjP75xKAk=", "narHash": "sha256-4GuMPW90JSxXWDPUB9M+1m7fYbe3H0apOd86/zBQ2Kw=",
"owner": "Mic92", "owner": "Mic92",
"repo": "sops-nix", "repo": "sops-nix",
"rev": "fbf759290e0cb0a98dfc813a4eb7d53ad1dacb57", "rev": "13616fff713a9f94055c66f15687ebdc17a335df",
"type": "github" "type": "github"
}, },
"original": { "original": {
+7
View File
@@ -16,6 +16,13 @@
]; ];
networking.hostName = "mibook"; networking.hostName = "mibook";
swapDevices = [
{
device = "/var/lib/swapfile";
size = 8 * 1024; # 8G, headroom for nix builds (7.7G RAM, no prior swap)
}
];
boot.loader.grub = { boot.loader.grub = {
enable = true; enable = true;
device = "/dev/nvme0n1"; device = "/dev/nvme0n1";
+1
View File
@@ -4,5 +4,6 @@ _: {
# firewall otherwise blocks inbound connections from other devices. # firewall otherwise blocks inbound connections from other devices.
networking.firewall.allowedTCPPorts = [ networking.firewall.allowedTCPPorts = [
9119 # athena hermes dashboard 9119 # athena hermes dashboard
47823 # hermes agent ad-hoc webpage server
]; ];
} }
@@ -21,6 +21,7 @@ in
neovim neovim
# jetbrains.idea-ultimate # jetbrains.idea-ultimate
go go
nodejs # bundles npm
(python3.withPackages ( (python3.withPackages (
ps: with ps; [ ps: with ps; [
jupyter # notebooks jupyter # notebooks
@@ -20,6 +20,9 @@ in
enable = true; enable = true;
# Deliberate pin, matches the approved design — bump on purpose, not by drift. # Deliberate pin, matches the approved design — bump on purpose, not by drift.
settings.model.default = "anthropic/claude-sonnet-4-5"; settings.model.default = "anthropic/claude-sonnet-4-5";
# The Nix flake input is the source of truth for hermes's version here; its
# built-in self-updater polling GitHub independently just burns API quota.
settings.updates.check = false;
environmentFiles = [ config.sops.secrets."hermes-env".path ]; environmentFiles = [ config.sops.secrets."hermes-env".path ];
addToSystemPackages = true; addToSystemPackages = true;
+78 -64
View File
@@ -12,79 +12,93 @@ let
manualServices = import ./manual-services.nix; manualServices = import ./manual-services.nix;
manualWidgets = import ./manual-widgets.nix; manualWidgets = import ./manual-widgets.nix;
groupedServices = groupedServices = lib.foldl' (
lib.foldl' acc: entry:
( acc
acc: entry: // {
acc ${entry.group} = (acc.${entry.group} or [ ]) ++ [ entry ];
// {
${entry.group} = (acc.${entry.group} or [ ]) ++ [ entry ];
}
)
{ }
config.my.homepage.services;
homepageServices = lib.mapAttrsToList (
group: entries:
{
${group} = map (
entry:
let
service = builtins.removeAttrs entry [
"group"
"name"
];
in
{
${entry.name} = lib.filterAttrs (_: value: value != null) service;
}
) entries;
} }
) groupedServices; ) { } config.my.homepage.services;
homepageServices = lib.mapAttrsToList (group: entries: {
${group} = map (
entry:
let
service = builtins.removeAttrs entry [
"group"
"name"
];
in
{
${entry.name} = lib.filterAttrs (_: value: value != null) service;
}
) entries;
}) groupedServices;
# homepage-dashboard keeps only the first services.yaml list entry with a
# given group name (first match wins), so module-contributed and manual
# groups sharing a name (e.g. "Services") must be merged here rather than
# simply concatenated, or the later ones are silently dropped.
mergeGroups =
lists:
let
merged = lib.foldl' (
acc: groupAttrs:
let
groupName = builtins.head (builtins.attrNames groupAttrs);
in
acc // { ${groupName} = (acc.${groupName} or [ ]) ++ groupAttrs.${groupName}; }
) { } lists;
in
lib.mapAttrsToList (name: entries: { ${name} = entries; }) merged;
in in
{ {
options.my.homepage.services = with lib; mkOption { options.my.homepage.services =
type = types.listOf ( with lib;
types.submodule { mkOption {
options = { type = types.listOf (
group = mkOption { types.submodule {
type = types.str; options = {
description = "Homepage service group"; group = mkOption {
}; type = types.str;
description = "Homepage service group";
};
name = mkOption { name = mkOption {
type = types.str; type = types.str;
description = "Homepage service name"; description = "Homepage service name";
}; };
description = mkOption { description = mkOption {
type = types.str; type = types.str;
description = "Homepage service description"; description = "Homepage service description";
}; };
href = mkOption { href = mkOption {
type = types.str; type = types.str;
description = "Homepage service URL"; description = "Homepage service URL";
}; };
icon = mkOption { icon = mkOption {
type = types.nullOr types.str; type = types.nullOr types.str;
default = null; default = null;
description = "Optional Homepage service icon"; description = "Optional Homepage service icon";
};
}; };
}; }
} );
); default = [ ];
default = [ ]; description = "Merged homepage service metadata contributed by repo modules.";
description = "Merged homepage service metadata contributed by repo modules."; };
};
options.my.homepage.widgets = with lib; mkOption { options.my.homepage.widgets =
type = types.listOf types.attrs; with lib;
default = [ ]; mkOption {
description = "Widget definitions passed directly to services.homepage-dashboard.widgets. Each entry is an attrset like { resources = { cpu = true; memory = true; }; }."; type = types.listOf types.attrs;
}; default = [ ];
description = "Widget definitions passed directly to services.homepage-dashboard.widgets. Each entry is an attrset like { resources = { cpu = true; memory = true; }; }.";
};
options.my.profiles.homepage = with lib; { options.my.profiles.homepage = with lib; {
enable = mkEnableOption "getHomepage.dev Dashboard"; enable = mkEnableOption "getHomepage.dev Dashboard";
@@ -98,7 +112,7 @@ in
listenPort = dashboardPort; listenPort = dashboardPort;
allowedHosts = "${dashboardHost}:${toString dashboardPort},192.168.178.65:${toString dashboardPort},localhost:${toString dashboardPort},127.0.0.1:${toString dashboardPort},jupiter:${toString dashboardPort},jupiter.solar.internal:${toString dashboardPort}"; allowedHosts = "${dashboardHost}:${toString dashboardPort},192.168.178.65:${toString dashboardPort},localhost:${toString dashboardPort},127.0.0.1:${toString dashboardPort},jupiter:${toString dashboardPort},jupiter.solar.internal:${toString dashboardPort}";
bookmarks = import ./bookmarks.nix; bookmarks = import ./bookmarks.nix;
services = homepageServices ++ manualServices; services = mergeGroups (homepageServices ++ manualServices);
widgets = config.my.homepage.widgets ++ manualWidgets; widgets = config.my.homepage.widgets ++ manualWidgets;
}; };