Apply nixfmt

This commit is contained in:
Tim Van Baak 2024-09-21 12:51:24 -07:00
parent 75c13a21dc
commit a5754e7023
5 changed files with 257 additions and 203 deletions

View File

@ -1,10 +1,9 @@
(import (import (
( let
let lock = builtins.fromJSON (builtins.readFile ./flake.lock); in lock = builtins.fromJSON (builtins.readFile ./flake.lock);
fetchTarball { in
url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz"; fetchTarball {
sha256 = lock.nodes.flake-compat.locked.narHash; url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz";
} sha256 = lock.nodes.flake-compat.locked.narHash;
) }
{ src = ./.; } ) { src = ./.; }).defaultNix
).defaultNix

View File

@ -14,7 +14,7 @@
users.users.bob = { users.users.bob = {
isNormalUser = true; isNormalUser = true;
password = "beta"; password = "beta";
uid = 1001; uid = 1001;
packages = [ pkgs.intake ]; packages = [ pkgs.intake ];
}; };
@ -26,11 +26,13 @@
}; };
# Expose the vm's intake revproxy at host port 5234 # Expose the vm's intake revproxy at host port 5234
virtualisation.forwardPorts = [{ virtualisation.forwardPorts = [
from = "host"; {
host.port = 5234; from = "host";
guest.port = 8080; host.port = 5234;
}]; guest.port = 8080;
}
];
# Mount the demo content for both users # Mount the demo content for both users
nixos-shell.mounts = { nixos-shell.mounts = {
@ -47,20 +49,20 @@
# Create an activation script that copies and chowns the demo content # Create an activation script that copies and chowns the demo content
# chmod 777 because the users may not exist when the activation script runs # chmod 777 because the users may not exist when the activation script runs
system.activationScripts = system.activationScripts =
let let
userSetup = name: uid: '' userSetup = name: uid: ''
${pkgs.coreutils}/bin/mkdir -p /home/${name}/.local/share/intake ${pkgs.coreutils}/bin/mkdir -p /home/${name}/.local/share/intake
${pkgs.coreutils}/bin/cp -r /mnt/${name}/* /home/${name}/.local/share/intake/ ${pkgs.coreutils}/bin/cp -r /mnt/${name}/* /home/${name}/.local/share/intake/
${pkgs.coreutils}/bin/chown -R ${uid} /home/${name} ${pkgs.coreutils}/bin/chown -R ${uid} /home/${name}
${pkgs.findutils}/bin/find /home/${name} -type d -exec ${pkgs.coreutils}/bin/chmod 755 {} \; ${pkgs.findutils}/bin/find /home/${name} -type d -exec ${pkgs.coreutils}/bin/chmod 755 {} \;
${pkgs.findutils}/bin/find /home/${name} -type f -exec ${pkgs.coreutils}/bin/chmod 644 {} \; ${pkgs.findutils}/bin/find /home/${name} -type f -exec ${pkgs.coreutils}/bin/chmod 644 {} \;
''; '';
in in
{ {
aliceSetup = userSetup "alice" "1000"; aliceSetup = userSetup "alice" "1000";
bobSetup = userSetup "bob" "1001"; bobSetup = userSetup "bob" "1001";
}; };
# Put the demo sources on the global PATH # Put the demo sources on the global PATH
environment.variables.PATH = "/mnt/sources"; environment.variables.PATH = "/mnt/sources";

136
flake.nix
View File

@ -13,63 +13,91 @@
nixos-shell.inputs.nixpkgs.follows = "nixpkgs"; nixos-shell.inputs.nixpkgs.follows = "nixpkgs";
}; };
outputs = { self, nixpkgs, flake-compat, nixos-shell }: outputs =
let {
inherit (nixpkgs.lib) makeOverridable nixosSystem; self,
system = "x86_64-linux"; nixpkgs,
in { flake-compat,
formatter.${system} = nixpkgs.legacyPackages.${system}.nixfmt-rfc-style; nixos-shell,
}:
let
inherit (nixpkgs.lib) makeOverridable nixosSystem;
system = "x86_64-linux";
in
{
formatter.${system} = nixpkgs.legacyPackages.${system}.nixfmt-rfc-style;
packages.${system} = let packages.${system} =
pkgs = (import nixpkgs { let
pkgs = (
import nixpkgs {
inherit system;
overlays = [ self.overlays.default ];
}
);
in
{
default = self.packages.${system}.intake;
inherit (pkgs) intake;
};
devShells.${system} = {
default =
let
pkgs = nixpkgs.legacyPackages.${system};
pythonEnv = pkgs.python3.withPackages (
pypkgs: with pypkgs; [
flask
black
pytest
]
);
in
pkgs.mkShell {
packages = [
pythonEnv
pkgs.nixos-shell
# We only take this dependency for htpasswd, which is a little unfortunate
pkgs.apacheHttpd
];
shellHook = ''
PS1="(develop) $PS1"
'';
};
};
overlays.default = final: prev: {
intake = final.python3Packages.buildPythonPackage {
name = "intake";
src = builtins.path {
path = ./.;
name = "intake";
};
format = "pyproject";
propagatedBuildInputs = with final.python3Packages; [
flask
setuptools
];
};
};
templates.source = {
path = builtins.path {
path = ./template;
name = "source";
};
description = "A basic intake source config";
};
nixosModules.default = import ./module.nix self;
nixosConfigurations."demo" = makeOverridable nixosSystem {
inherit system; inherit system;
overlays = [ self.overlays.default ]; modules = [
}); nixos-shell.nixosModules.nixos-shell
in { self.nixosModules.default
default = self.packages.${system}.intake; ./demo
inherit (pkgs) intake;
};
devShells.${system} = {
default = let
pkgs = nixpkgs.legacyPackages.${system};
pythonEnv = pkgs.python3.withPackages (pypkgs: with pypkgs; [ flask black pytest ]);
in pkgs.mkShell {
packages = [
pythonEnv
pkgs.nixos-shell
# We only take this dependency for htpasswd, which is a little unfortunate
pkgs.apacheHttpd
]; ];
shellHook = ''
PS1="(develop) $PS1"
'';
}; };
}; };
overlays.default = final: prev: {
intake = final.python3Packages.buildPythonPackage {
name = "intake";
src = builtins.path { path = ./.; name = "intake"; };
format = "pyproject";
propagatedBuildInputs = with final.python3Packages; [ flask setuptools ];
};
};
templates.source = {
path = builtins.path { path = ./template; name = "source"; };
description = "A basic intake source config";
};
nixosModules.default = import ./module.nix self;
nixosConfigurations."demo" = makeOverridable nixosSystem {
inherit system;
modules = [
nixos-shell.nixosModules.nixos-shell
self.nixosModules.default
./demo
];
};
};
} }

View File

@ -1,153 +1,179 @@
flake: { config, lib, pkgs, ... }: flake:
{
config,
lib,
pkgs,
...
}:
let let
inherit (lib) filterAttrs foldl imap1 mapAttrsToList mkEnableOption mkIf mkMerge mkOption mkPackageOption types; inherit (lib)
filterAttrs
foldl
imap1
mapAttrsToList
mkEnableOption
mkIf
mkMerge
mkOption
mkPackageOption
types
;
intakeCfg = config.services.intake; intakeCfg = config.services.intake;
in { in
{
options = { options = {
services.intake = { services.intake = {
listen.addr = mkOption { listen.addr = mkOption {
type = types.str; type = types.str;
default = "0.0.0.0"; default = "0.0.0.0";
description = "The listen address for the entry point to intake services. This endpoint will redirect to a " description = "The listen address for the entry point to intake services. This endpoint will redirect to a local port based on the request's HTTP Basic Auth credentials.";
"local port based on the request's HTTP Basic Auth credentials.";
}; };
listen.port = mkOption { listen.port = mkOption {
type = types.port; type = types.port;
default = 80; default = 80;
description = "The listen port for the entry point to intake services. This endpoint will redirect to a local " description = "The listen port for the entry point to intake services. This endpoint will redirect to a local port based on the request's HTTP Basic Auth credentials.";
"port based on the request's HTTP Basic Auth credentials.";
}; };
package = mkPackageOption pkgs "intake" {}; package = mkPackageOption pkgs "intake" { };
internalPortStart = mkOption { internalPortStart = mkOption {
type = types.port; type = types.port;
default = 24130; default = 24130;
description = "The first port to use for internal service endpoints. A number of ports will be continguously " description = "The first port to use for internal service endpoints. A number of ports will be continguously allocated equal to the number of users with enabled intake services.";
"allocated equal to the number of users with enabled intake services.";
}; };
extraPackages = mkOption { extraPackages = mkOption {
type = types.listOf types.package; type = types.listOf types.package;
default = []; default = [ ];
description = "Extra packages available to all enabled users and their intake services."; description = "Extra packages available to all enabled users and their intake services.";
}; };
users = mkOption { users = mkOption {
description = "User intake service definitions."; description = "User intake service definitions.";
default = {}; default = { };
type = types.attrsOf (types.submodule { type = types.attrsOf (
options = { types.submodule {
enable = mkEnableOption "intake, a personal feed aggregator."; options = {
enable = mkEnableOption "intake, a personal feed aggregator.";
extraPackages = mkOption { extraPackages = mkOption {
type = types.listOf types.package; type = types.listOf types.package;
default = []; default = [ ];
description = "Extra packages available to this user and their intake service."; description = "Extra packages available to this user and their intake service.";
};
}; };
}; }
}); );
}; };
}; };
}; };
config = config =
let
# Define the intake package and a python environment to run it from
intake = intakeCfg.package;
pythonEnv = pkgs.python3.withPackages (pypkgs: [ intake ]);
# Assign each user an internal port for their personal intake instance
enabledUsers = filterAttrs (userName: userCfg: userCfg.enable) intakeCfg.users;
enabledUserNames = mapAttrsToList (userName: userCfg: userName) enabledUsers;
userPortList = imap1 (i: userName: { ${userName} = i + intakeCfg.internalPortStart; }) enabledUserNames;
userPort = foldl (acc: val: acc // val) {} userPortList;
# To avoid polluting PATH with httpd programs, define an htpasswd wrapper
htpasswdWrapper = pkgs.writeShellScriptBin "htpasswd" ''
${pkgs.apacheHttpd}/bin/htpasswd $@
'';
# File locations
intakeDir = "/etc/intake";
intakePwd = "${intakeDir}/htpasswd";
in {
# Apply the overlay so intake is included in pkgs.
nixpkgs.overlays = [ flake.overlays.default ];
# Define a user group for access to the htpasswd file. nginx needs to be able to read it.
users.groups.intake.members = mkIf (enabledUsers != {}) (enabledUserNames ++ [ "nginx" ]);
# Define an activation script that ensures that the htpasswd file exists.
system.activationScripts.etc-intake = ''
if [ ! -e ${intakeDir} ]; then
${pkgs.coreutils}/bin/mkdir -p ${intakeDir};
fi
${pkgs.coreutils}/bin/chown root:root ${intakeDir}
${pkgs.coreutils}/bin/chmod 755 ${intakeDir}
if [ ! -e ${intakePwd} ]; then
${pkgs.coreutils}/bin/touch ${intakePwd}
fi
${pkgs.coreutils}/bin/chown root:intake ${intakePwd}
${pkgs.coreutils}/bin/chmod 660 ${intakePwd}
'';
# Give every intake user the htpasswd wrapper, the shared packages, and the user-specific packages.
users.users =
let let
addPackagesToUser = userName: { # Define the intake package and a python environment to run it from
${userName}.packages = intake = intakeCfg.package;
[ htpasswdWrapper intake ] pythonEnv = pkgs.python3.withPackages (pypkgs: [ intake ]);
++ intakeCfg.extraPackages
++ intakeCfg.users.${userName}.extraPackages;
};
in mkMerge (map addPackagesToUser enabledUserNames);
# Enable cron # Assign each user an internal port for their personal intake instance
services.cron.enable = true; enabledUsers = filterAttrs (userName: userCfg: userCfg.enable) intakeCfg.users;
enabledUserNames = mapAttrsToList (userName: userCfg: userName) enabledUsers;
userPortList = imap1 (i: userName: {
${userName} = i + intakeCfg.internalPortStart;
}) enabledUserNames;
userPort = foldl (acc: val: acc // val) { } userPortList;
# Define a user service for each configured user # To avoid polluting PATH with httpd programs, define an htpasswd wrapper
systemd.services = htpasswdWrapper = pkgs.writeShellScriptBin "htpasswd" ''
let ${pkgs.apacheHttpd}/bin/htpasswd $@
runScript = userName: pkgs.writeShellScript "intake-run.sh" ''
# Add the setuid wrapper directory so `crontab` is accessible
export PATH="${config.security.wrapperDir}:$PATH"
${pythonEnv}/bin/intake run -d /home/${userName}/.local/share/intake --port ${toString userPort.${userName}}
''; '';
# 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 = intakeCfg.extraPackages ++ userCfg.extraPackages;
serviceConfig = {
User = userName;
Type = "simple";
};
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
enable = userCfg.enable;
};
};
in mkMerge (mapAttrsToList userServiceConfig intakeCfg.users);
# Define an nginx reverse proxy to request auth # File locations
services.nginx = mkIf (enabledUsers != {}) { intakeDir = "/etc/intake";
enable = true; intakePwd = "${intakeDir}/htpasswd";
virtualHosts."intake" = mkIf (enabledUsers != {}) { in
listen = [ intakeCfg.listen ]; {
locations."/" = { # Apply the overlay so intake is included in pkgs.
proxyPass = "http://127.0.0.1:$target_port"; nixpkgs.overlays = [ flake.overlays.default ];
basicAuthFile = intakePwd;
# Define a user group for access to the htpasswd file. nginx needs to be able to read it.
users.groups.intake.members = mkIf (enabledUsers != { }) (enabledUserNames ++ [ "nginx" ]);
# Define an activation script that ensures that the htpasswd file exists.
system.activationScripts.etc-intake = ''
if [ ! -e ${intakeDir} ]; then
${pkgs.coreutils}/bin/mkdir -p ${intakeDir};
fi
${pkgs.coreutils}/bin/chown root:root ${intakeDir}
${pkgs.coreutils}/bin/chmod 755 ${intakeDir}
if [ ! -e ${intakePwd} ]; then
${pkgs.coreutils}/bin/touch ${intakePwd}
fi
${pkgs.coreutils}/bin/chown root:intake ${intakePwd}
${pkgs.coreutils}/bin/chmod 660 ${intakePwd}
'';
# Give every intake user the htpasswd wrapper, the shared packages, and the user-specific packages.
users.users =
let
addPackagesToUser = userName: {
${userName}.packages = [
htpasswdWrapper
intake
] ++ intakeCfg.extraPackages ++ intakeCfg.users.${userName}.extraPackages;
};
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" ''
# Add the setuid wrapper directory so `crontab` is accessible
export PATH="${config.security.wrapperDir}:$PATH"
${pythonEnv}/bin/intake run -d /home/${userName}/.local/share/intake --port ${toString userPort.${userName}}
'';
# 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 = intakeCfg.extraPackages ++ userCfg.extraPackages;
serviceConfig = {
User = userName;
Type = "simple";
};
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
enable = userCfg.enable;
};
};
in
mkMerge (mapAttrsToList userServiceConfig intakeCfg.users);
# Define an nginx reverse proxy to request auth
services.nginx = mkIf (enabledUsers != { }) {
enable = true;
virtualHosts."intake" = mkIf (enabledUsers != { }) {
listen = [ intakeCfg.listen ];
locations."/" = {
proxyPass = "http://127.0.0.1:$target_port";
basicAuthFile = intakePwd;
};
extraConfig = foldl (acc: val: acc + val) "" (
mapAttrsToList (userName: port: ''
if ($remote_user = "${userName}") {
set $target_port ${toString port};
}
'') userPort
);
}; };
extraConfig = foldl (acc: val: acc + val) "" (mapAttrsToList (userName: port: ''
if ($remote_user = "${userName}") {
set $target_port ${toString port};
}
'') userPort);
}; };
}; };
};
} }

View File

@ -1,10 +1,9 @@
(import (import (
( let
let lock = builtins.fromJSON (builtins.readFile ./flake.lock); in lock = builtins.fromJSON (builtins.readFile ./flake.lock);
fetchTarball { in
url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz"; fetchTarball {
sha256 = lock.nodes.flake-compat.locked.narHash; url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz";
} sha256 = lock.nodes.flake-compat.locked.narHash;
) }
{ src = ./.; } ) { src = ./.; }).shellNix
).shellNix