Add NixOS module and vm demo

This commit is contained in:
Tim Van Baak 2025-02-12 13:58:37 -08:00
parent 71978dbae4
commit fc68d313b1
9 changed files with 328 additions and 4 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
.direnv
tmp/
nixos.qcow2

View File

@ -1,4 +1,4 @@
.PHONY: help serve test-data
.PHONY: help serve test-data demo
help: ## display this help
@awk 'BEGIN{FS = ":.*##"; printf "\033[1m\nUsage\n \033[1;92m make\033[0;36m <target>\033[0m\n"} /^[a-zA-Z0-9_-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } ' $(MAKEFILE_LIST)
@ -7,4 +7,7 @@ serve: ## Run "intake serve" with live reload
@air -build.cmd "go build -o tmp/intake" -build.bin tmp/intake -build.args_bin serve,--data-dir,tmp -build.include_ext "go,html,css"
test-data: ## Recreate test data in tmp/
@test/test_items.sh
@test/test_items.sh
demo: ## Run the demo vm
@nix run ".#nixosConfigurations.demo.config.system.build.nixos-shell"

View File

@ -4,6 +4,8 @@ Intake is an arbitrary feed aggregator that generalizes the concept of a feed.
Rather than being restricted to parsing items out of an RSS feed, Intake provides a middle layer of executing arbitrary commands that conform to a JSON-based specification.
An Intake source can parse an RSS feed, but it can also scrape a website without a feed, provide additional logic to filter or annotate feed items, or integrate with an API.
A demo running in a NixOS VM is available via `make demo` or using `nix run` on the `nixosConfigurations.demo.config.system.build.nixos-shell` flake attribute.
## Overview
In Intake, a _source_ represents a single content feed of discrete _items_, such as a blog and its posts or a website and its pages.
@ -104,8 +106,8 @@ Instead, the web interface can be locked behind a password set via `intake passw
Parity features
* [ ] source batching
* [ ] NixOS module
* [ ] NixOS vm demo
* [x] NixOS module
* [x] NixOS vm demo
Future features

25
demo/alice.sh Normal file
View File

@ -0,0 +1,25 @@
#!/bin/sh
if [ -f /home/alice/.intake-setup-done ]; then
echo "already done"
exit 0
fi
# intake service gets a crontab wrapper, cheat here
export PATH="/run/wrappers/bin:$PATH"
mkdir -p $INTAKE_DATA_DIR
intake source add -s echo
intake action add -s echo -a fetch -- jq -cn '{id: env.ID, title: env.MESSAGE}'
intake source env -s echo --set "ID=hello"
intake source env -s echo --set "MESSAGE=Hello, world!"
intake channel add -s echo -c home
intake source add -s currenttime
intake action add -s currenttime -a fetch -- sh -c "date +%Y-%m-%d-%H-%M | jq -cR '{id: .}'"
intake source env -s currenttime --set "INTAKE_CRON=* * * * *"
intake channel add -s currenttime -c home
touch /home/alice/.intake-setup-done
echo "done"

20
demo/bob.sh Normal file
View File

@ -0,0 +1,20 @@
#!/bin/sh
if [ -f /home/bob/.intake-setup-done ]; then
echo "already done"
exit 0
fi
# intake service gets a crontab wrapper, cheat here
export PATH="/run/wrappers/bin:$PATH"
mkdir -p $INTAKE_DATA_DIR
intake source add -s echo
intake action add -s echo -a fetch -- jq -cn '{id: env.ID, title: env.MESSAGE}'
intake source env -s echo --set "ID=goodbye"
intake source env -s echo --set "MESSAGE=Goodbye, world!"
intake channel add -s echo -c home
touch /home/bob/.intake-setup-done
echo "done"

106
demo/default.nix Normal file
View File

@ -0,0 +1,106 @@
{ pkgs, lib, ... }:
{
system.stateVersion = "25.05";
environment.systemPackages = with pkgs; [
jq
];
# Set up two users to demonstrate the user separation
users.users.alice = {
isNormalUser = true;
password = "a";
uid = 1000;
};
users.users.bob = {
isNormalUser = true;
password = "b";
uid = 1001;
};
# Set up intake for both users
services.intake.extraPackages = with pkgs; [
jq
];
services.intake.users = {
alice = {
enable = true;
listen.addr = "0.0.0.0";
listen.port = 6001;
};
bob = {
enable = true;
listen.addr = "0.0.0.0";
listen.port = 6002;
};
};
# Forward both ports
virtualisation.forwardPorts = [
{
from = "host";
host.port = 6001;
guest.port = 6001;
}
{
from = "host";
host.port = 6002;
guest.port = 6002;
}
];
# Disable nixos-shell autologin
services.getty.autologinUser = lib.mkForce null;
# Disable default mounts
nixos-shell.mounts = {
mountHome = false;
mountNixProfile = false;
cache = "none";
};
# Define a setup service to create some demo content
systemd.services =
let
setupFor = userName: script: {
description = "Intake demo setup for ${userName}";
serviceConfig = {
User = userName;
Type = "oneshot";
RemainAfterExit = true;
};
path = [ pkgs.intake ];
environment.INTAKE_DATA_DIR = "/home/${userName}/.local/share/intake";
wantedBy = [ "intake-${userName}.service" ];
before = [ "intake-${userName}.service" ];
after = [ "network.target" ];
script = builtins.readFile ./${userName}.sh;
};
in
{
intake-alice-setup = setupFor "alice";
intake-bob-setup = setupFor "bob";
};
# Include some demo instructions
environment.etc.issue.text = ''
###
# Welcome to the intake demo! Log in as `alice` with password `a` to begin.
#
# Exit the VM with ctrl+a x, or switch to the qemu console with ctrl+a c and `quit`.
###
'';
users.motd = ''
###
# The web interfaces are exposed at http://localhost:6001 and http://localhost:6002
#
# Within this demo VM, you can run `intake` CLI commands.
###
'';
}

21
flake.lock generated
View File

@ -1,5 +1,25 @@
{
"nodes": {
"nixos-shell": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1732727306,
"narHash": "sha256-4R+OVEmJ8yR7/gsxMQtC39b9f61SvELYQwKeXGAyFfo=",
"owner": "Mic92",
"repo": "nixos-shell",
"rev": "c61dce7cf5dc263d237ba8a7fc175b09642f96eb",
"type": "github"
},
"original": {
"owner": "Mic92",
"repo": "nixos-shell",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1736798957,
@ -18,6 +38,7 @@
},
"root": {
"inputs": {
"nixos-shell": "nixos-shell",
"nixpkgs": "nixpkgs"
}
}

View File

@ -3,14 +3,18 @@
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
nixos-shell.url = "github:Mic92/nixos-shell";
nixos-shell.inputs.nixpkgs.follows = "nixpkgs";
};
outputs =
{
self,
nixpkgs,
nixos-shell,
}:
let
inherit (nixpkgs.lib) nixosSystem;
system = "x86_64-linux";
pkgs = nixpkgs.legacyPackages.${system};
in
@ -39,11 +43,23 @@
pkgs.gotools
pkgs.cobra-cli
pkgs.air
pkgs.nixos-shell
];
};
overlays.default = final: prev: {
intake = final.callPackage ./package.nix { };
};
nixosModules.default = import ./module.nix self;
nixosConfigurations."demo" = nixosSystem {
inherit system;
modules = [
nixos-shell.nixosModules.nixos-shell
self.nixosModules.default
./demo
];
};
};
}

130
module.nix Normal file
View File

@ -0,0 +1,130 @@
flake:
{
config,
lib,
pkgs,
...
}:
let
inherit (lib)
filterAttrs
foldl
imap1
mapAttrsToList
mkEnableOption
mkIf
mkMerge
mkOption
mkPackageOption
types
;
in
{
options = {
services.intake = {
package = mkPackageOption pkgs "intake" { };
extraPackages = mkOption {
type = types.listOf types.package;
default = [ ];
description = "Extra packages available to all enabled users and their intake services.";
};
users = mkOption {
description = "User intake service definitions.";
default = { };
type = types.attrsOf (
types.submodule {
options = {
enable = mkEnableOption "intake, a universal and extensible feed aggregator.";
dataDir = mkOption {
type = types.str;
default = "/home/$USER/.local/share/intake";
description = "The data directory for this user's intake service.";
};
listen.addr = mkOption {
type = types.str;
default = "127.0.0.1";
description = "The listen address for this user's intake service.";
};
listen.port = mkOption {
type = types.port;
default = 80;
description = "The listen port for this user's intake service.";
};
extraPackages = mkOption {
type = types.listOf types.package;
default = [ ];
description = "Extra packages available to this user and their intake service.";
};
};
}
);
};
};
};
config =
let
intakeCfg = config.services.intake;
enabledUsers = filterAttrs (userName: userCfg: userCfg.enable) intakeCfg.users;
enabledUserNames = mapAttrsToList (userName: userCfg: userName) enabledUsers;
userPackages = userName: [ intakeCfg.package ] ++ intakeCfg.extraPackages ++ intakeCfg.users.${userName}.extraPackages;
crontabWrapper = pkgs.writeShellScriptBin "crontab" ''
exec ${config.security.wrapperDir}/crontab "$@"
'';
in
{
# Apply the overlay so intake is included in pkgs.
nixpkgs.overlays = [ flake.overlays.default ];
# Give every intake user the shared packages and their user-specific packages.
users.users =
let
addPackagesToUser = userName: {
${userName}.packages = userPackages userName;
};
in
mkMerge (map addPackagesToUser enabledUserNames);
# Enable cron
services.cron.enable = true;
# Define a user service for each configured user
systemd.services =
let
runScript =
userName:
pkgs.writeShellScript "intake-run.sh" ''
mkdir -p $INTAKE_DATA_DIR
# Add the setuid wrapper directory so `crontab` is accessible
export PATH="${config.security.wrapperDir}:$PATH"
${intakeCfg.package}/bin/intake serve --addr ${enabledUsers.${userName}.listen.addr} --port ${toString enabledUsers.${userName}.listen.port}
'';
# systemd service definition for a single user, given `services.intake.users.userName` = `userCfg`
userServiceConfig = userName: userCfg: {
"intake-${userName}" = {
description = "Intake service for user ${userName}";
script = "${runScript userName}";
path = [ crontabWrapper ] ++ intakeCfg.extraPackages ++ userCfg.extraPackages;
environment = {
INTAKE_DATA_DIR = "/home/${userName}/.local/share/intake";
};
serviceConfig = {
User = userName;
Type = "simple";
};
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
enable = userCfg.enable;
};
};
in
mkMerge (mapAttrsToList userServiceConfig enabledUsers);
};
}