momo: NixOS 23.11, fix build (treefmt), move to flake-parts #262

Merged
teutat3s merged 12 commits from momo/nixos-23.11 into momo/main 2024-01-08 22:50:39 +00:00
20 changed files with 820 additions and 784 deletions
Showing only changes of commit 0212b85efc - Show all commits

View file

@ -36,8 +36,8 @@
erpnext.inputs.agenix.follows = "agenix";
};
outputs = inputs@{ self, ...}:
inputs.flake-parts.lib.mkFlake { inherit inputs; } {
outputs = inputs @ {self, ...}:
inputs.flake-parts.lib.mkFlake {inherit inputs;} {
debug = true;
systems = [
"x86_64-linux"
@ -52,7 +52,12 @@
./overlays
];
perSystem = args@{ system, pkgs, config, ... }: {
perSystem = args @ {
system,
pkgs,
config,
...
}: {
_module.args = {
inherit inputs;
pkgs = import inputs.nixpkgs {
@ -61,7 +66,7 @@
inputs.agenix.overlays.default
];
};
unstable = import inputs.unstable { inherit system; };
unstable = import inputs.unstable {inherit system;};
};
devShells.default = pkgs.mkShell {

View file

@ -1,5 +1,8 @@
{ self, inputs, ...}:
{
self,
inputs,
...
}: {
flake = {
nixosConfigurations = {
pioneer-momo-koeln = self.nixos-flake.lib.mkLinuxSystem {

View file

@ -1,4 +1,4 @@
{ ... }: {
{...}: {
imports = [
./configuration.nix
./hardware-configuration.nix

View file

@ -1,5 +1,4 @@
{ lib }:
hostnames: {
{lib}: hostnames: {
"127.0.0.1" = hostnames;
"::1" = hostnames;
}

View file

@ -1,4 +1,8 @@
{ lib, inputs, ... }: {
{
lib,
inputs,
...
}: {
# Configuration common to all Linux systems
flake = {
lib = let
@ -10,9 +14,8 @@
#foo = callLibs ./foo.nix;
## In configs, they can be used under "lib.our"
deploy = import ./deploy.nix { inherit inputs lib; };
deploy = import ./deploy.nix {inherit inputs lib;};
addLocalHostname = callLibs ./add-local-hostname.nix;
recursiveMerge = callLibs ./recursive-merge.nix;
};
};
}

View file

@ -1,11 +1,13 @@
/*
* The contents of this file are adapted from digga
* https://github.com/divnix/digga
*
* Licensed under the MIT license
*/
{ lib, inputs }: let
* The contents of this file are adapted from digga
* https://github.com/divnix/digga
*
* Licensed under the MIT license
*/
{
lib,
inputs,
}: let
getFqdn = c: let
net = c.config.networking;
fqdn =
@ -17,35 +19,35 @@
in {
mkDeployNodes = systemConfigurations: extraConfig:
/*
*
Synopsis: mkNodes _systemConfigurations_ _extraConfig_
*
Synopsis: mkNodes _systemConfigurations_ _extraConfig_
Generate the `nodes` attribute expected by deploy-rs
where _systemConfigurations_ are `nodes`.
Generate the `nodes` attribute expected by deploy-rs
where _systemConfigurations_ are `nodes`.
_systemConfigurations_ should take the form of a flake's
_nixosConfigurations_. Note that deploy-rs does not currently support
deploying to darwin hosts.
_systemConfigurations_ should take the form of a flake's
_nixosConfigurations_. Note that deploy-rs does not currently support
deploying to darwin hosts.
_extraConfig_, if specified, will be merged into each of the
nodes' configurations.
_extraConfig_, if specified, will be merged into each of the
nodes' configurations.
Example _systemConfigurations_ input:
Example _systemConfigurations_ input:
```
{
hostname-1 = {
fastConnection = true;
sshOpts = [ "-p" "25" ];
};
hostname-2 = {
sshOpts = [ "-p" "19999" ];
sshUser = "root";
};
}
```
*
*/
```
{
hostname-1 = {
fastConnection = true;
sshOpts = [ "-p" "25" ];
};
hostname-2 = {
sshOpts = [ "-p" "19999" ];
sshUser = "root";
};
}
```
*
*/
lib.recursiveUpdate
(lib.mapAttrs
(

View file

@ -1,16 +0,0 @@
{ lib }:
attrList:
let
f = attrPath:
zipAttrsWith (
n: values:
if tail values == []
then head values
else if all isList values
then unique (concatLists values)
else if all isAttrs values
then f (attrPath ++ [n]) values
else last values
);
in
f [] attrList;

View file

@ -3,16 +3,24 @@
pkgs,
lib,
...
}:
let
}: let
cfg = config.services.ddclient;
boolToStr = bool: if bool then "yes" else "no";
boolToStr = bool:
if bool
then "yes"
else "no";
dataDir = "/var/lib/ddclient";
StateDirectory = builtins.baseNameOf dataDir;
RuntimeDirectory = StateDirectory;
usev4 = if cfg.usev4 != "" then "usev4=${cfg.usev4}" else "";
usev6 = if cfg.usev6 != "" then "usev6=${cfg.usev6}" else "";
usev4 =
if cfg.usev4 != ""
then "usev4=${cfg.usev4}"
else "";
usev6 =
if cfg.usev6 != ""
then "usev6=${cfg.usev6}"
else "";
configFile' = pkgs.writeText "ddclient.conf" ''
# This file can be used as a template for configFile or is automatically generated by Nix options.
@ -22,11 +30,15 @@ let
cache=${dataDir}/ddclient.cache
foreground=yes
login=${cfg.username}
password=${if cfg.protocol == "nsupdate" then "/run/${RuntimeDirectory}/ddclient.key" else "@password_placeholder@"}
password=${
if cfg.protocol == "nsupdate"
then "/run/${RuntimeDirectory}/ddclient.key"
else "@password_placeholder@"
}
protocol=${cfg.protocol}
${lib.optionalString (cfg.script != "") "script=${cfg.script}"}
${lib.optionalString (cfg.server != "") "server=${cfg.server}"}
${lib.optionalString (cfg.zone != "") "zone=${cfg.zone}"}
${lib.optionalString (cfg.zone != "") "zone=${cfg.zone}"}
ssl=${boolToStr cfg.ssl}
wildcard=yes
quiet=${boolToStr cfg.quiet}
@ -34,212 +46,224 @@ let
${cfg.extraConfig}
${lib.concatStringsSep "," cfg.domains}
'';
configFile = if (cfg.configFile != null) then cfg.configFile else configFile';
configFile =
if (cfg.configFile != null)
then cfg.configFile
else configFile';
preStart = ''
install --mode=600 --owner=$USER ${configFile} /run/${RuntimeDirectory}/ddclient.conf
${lib.optionalString (cfg.configFile == null) (if (cfg.protocol == "nsupdate") then ''
install --mode=600 --owner=$USER ${cfg.passwordFile} /run/${RuntimeDirectory}/ddclient.key
'' else if (cfg.passwordFile != null) then ''
"${pkgs.replace-secret}/bin/replace-secret" "@password_placeholder@" "${cfg.passwordFile}" "/run/${RuntimeDirectory}/ddclient.conf"
'' else ''
sed -i '/^password=@password_placeholder@$/d' /run/${RuntimeDirectory}/ddclient.conf
'')}
${lib.optionalString (cfg.configFile == null) (
if (cfg.protocol == "nsupdate")
then ''
install --mode=600 --owner=$USER ${cfg.passwordFile} /run/${RuntimeDirectory}/ddclient.key
''
else if (cfg.passwordFile != null)
then ''
"${pkgs.replace-secret}/bin/replace-secret" "@password_placeholder@" "${cfg.passwordFile}" "/run/${RuntimeDirectory}/ddclient.conf"
''
else ''
sed -i '/^password=@password_placeholder@$/d' /run/${RuntimeDirectory}/ddclient.conf
''
)}
'';
in with lib; {
disabledModules = [
"services/networking/ddclient.nix"
];
in
with lib; {
disabledModules = [
"services/networking/ddclient.nix"
];
imports = [
(mkChangedOptionModule [ "services" "ddclient" "domain" ] [ "services" "ddclient" "domains" ]
(config:
let value = getAttrFromPath [ "services" "ddclient" "domain" ] config;
in if value != "" then [ value ] else []))
(mkRemovedOptionModule [ "services" "ddclient" "homeDir" ] "")
(mkRemovedOptionModule [ "services" "ddclient" "password" ] "Use services.ddclient.passwordFile instead.")
];
imports = [
(mkChangedOptionModule ["services" "ddclient" "domain"] ["services" "ddclient" "domains"]
(config: let
value = getAttrFromPath ["services" "ddclient" "domain"] config;
in
if value != ""
then [value]
else []))
(mkRemovedOptionModule ["services" "ddclient" "homeDir"] "")
(mkRemovedOptionModule ["services" "ddclient" "password"] "Use services.ddclient.passwordFile instead.")
];
###### interface
###### interface
options = {
services.ddclient = with lib.types; {
enable = mkOption {
default = false;
type = bool;
description = lib.mdDoc ''
Whether to synchronise your machine's IP address with a dynamic DNS provider (e.g. dyndns.org).
'';
};
options = {
services.ddclient = with lib.types; {
enable = mkOption {
default = false;
type = bool;
description = lib.mdDoc ''
Whether to synchronise your machine's IP address with a dynamic DNS provider (e.g. dyndns.org).
'';
};
package = mkOption {
type = package;
default = pkgs.ddclient;
defaultText = lib.literalExpression "pkgs.ddclient";
description = lib.mdDoc ''
The ddclient executable package run by the service.
'';
};
package = mkOption {
type = package;
default = pkgs.ddclient;
defaultText = lib.literalExpression "pkgs.ddclient";
description = lib.mdDoc ''
The ddclient executable package run by the service.
'';
};
domains = mkOption {
default = [ "" ];
type = listOf str;
description = lib.mdDoc ''
Domain name(s) to synchronize.
'';
};
domains = mkOption {
default = [""];
type = listOf str;
description = lib.mdDoc ''
Domain name(s) to synchronize.
'';
};
username = mkOption {
# For `nsupdate` username contains the path to the nsupdate executable
default = lib.optionalString (config.services.ddclient.protocol == "nsupdate") "${pkgs.bind.dnsutils}/bin/nsupdate";
defaultText = "";
type = str;
description = lib.mdDoc ''
User name.
'';
};
username = mkOption {
# For `nsupdate` username contains the path to the nsupdate executable
default = lib.optionalString (config.services.ddclient.protocol == "nsupdate") "${pkgs.bind.dnsutils}/bin/nsupdate";
defaultText = "";
type = str;
description = lib.mdDoc ''
User name.
'';
};
passwordFile = mkOption {
default = null;
type = nullOr str;
description = lib.mdDoc ''
A file containing the password or a TSIG key in named format when using the nsupdate protocol.
'';
};
passwordFile = mkOption {
default = null;
type = nullOr str;
description = lib.mdDoc ''
A file containing the password or a TSIG key in named format when using the nsupdate protocol.
'';
};
interval = mkOption {
default = "10min";
type = str;
description = lib.mdDoc ''
The interval at which to run the check and update.
See {command}`man 7 systemd.time` for the format.
'';
};
interval = mkOption {
default = "10min";
type = str;
description = lib.mdDoc ''
The interval at which to run the check and update.
See {command}`man 7 systemd.time` for the format.
'';
};
configFile = mkOption {
default = null;
type = nullOr path;
description = lib.mdDoc ''
Path to configuration file.
When set this overrides the generated configuration from module options.
'';
example = "/root/nixos/secrets/ddclient.conf";
};
configFile = mkOption {
default = null;
type = nullOr path;
description = lib.mdDoc ''
Path to configuration file.
When set this overrides the generated configuration from module options.
'';
example = "/root/nixos/secrets/ddclient.conf";
};
protocol = mkOption {
default = "dyndns2";
type = str;
description = lib.mdDoc ''
Protocol to use with dynamic DNS provider (see https://sourceforge.net/p/ddclient/wiki/protocols).
'';
};
protocol = mkOption {
default = "dyndns2";
type = str;
description = lib.mdDoc ''
Protocol to use with dynamic DNS provider (see https://sourceforge.net/p/ddclient/wiki/protocols).
'';
};
server = mkOption {
default = "";
type = str;
description = lib.mdDoc ''
Server address.
'';
};
server = mkOption {
default = "";
type = str;
description = lib.mdDoc ''
Server address.
'';
};
ssl = mkOption {
default = true;
type = bool;
description = lib.mdDoc ''
Whether to use SSL/TLS to connect to dynamic DNS provider.
'';
};
ssl = mkOption {
default = true;
type = bool;
description = lib.mdDoc ''
Whether to use SSL/TLS to connect to dynamic DNS provider.
'';
};
quiet = mkOption {
default = false;
type = bool;
description = lib.mdDoc ''
Print no messages for unnecessary updates.
'';
};
quiet = mkOption {
default = false;
type = bool;
description = lib.mdDoc ''
Print no messages for unnecessary updates.
'';
};
script = mkOption {
default = "";
type = str;
description = lib.mdDoc ''
script as required by some providers.
'';
};
script = mkOption {
default = "";
type = str;
description = lib.mdDoc ''
script as required by some providers.
'';
};
usev4 = mkOption {
default = "webv4, webv4=checkip.dyndns.com/, webv4-skip='Current IP Address: '";
type = str;
description = lib.mdDoc ''
Method to determine the IP address to send to the dynamic DNS provider.
'';
};
usev4 = mkOption {
default = "webv4, webv4=checkip.dyndns.com/, webv4-skip='Current IP Address: '";
type = str;
description = lib.mdDoc ''
Method to determine the IP address to send to the dynamic DNS provider.
'';
};
usev6 = mkOption {
default = "";
type = str;
description = lib.mdDoc ''
Method to determine the IP address to send to the dynamic DNS provider.
'';
};
usev6 = mkOption {
default = "";
type = str;
description = lib.mdDoc ''
Method to determine the IP address to send to the dynamic DNS provider.
'';
};
verbose = mkOption {
default = false;
type = bool;
description = lib.mdDoc ''
Print verbose information.
'';
};
verbose = mkOption {
default = false;
type = bool;
description = lib.mdDoc ''
Print verbose information.
'';
};
zone = mkOption {
default = "";
type = str;
description = lib.mdDoc ''
zone as required by some providers.
'';
};
zone = mkOption {
default = "";
type = str;
description = lib.mdDoc ''
zone as required by some providers.
'';
};
extraConfig = mkOption {
default = "";
type = lines;
description = lib.mdDoc ''
Extra configuration. Contents will be added verbatim to the configuration file.
extraConfig = mkOption {
default = "";
type = lines;
description = lib.mdDoc ''
Extra configuration. Contents will be added verbatim to the configuration file.
::: {.note}
`daemon` should not be added here because it does not work great with the systemd-timer approach the service uses.
:::
'';
};
};
};
###### implementation
config = mkIf config.services.ddclient.enable {
systemd.services.ddclient = {
description = "Dynamic DNS Client";
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
restartTriggers = optional (cfg.configFile != null) cfg.configFile;
serviceConfig = {
DynamicUser = true;
RuntimeDirectoryMode = "0700";
inherit RuntimeDirectory;
inherit StateDirectory;
Type = "oneshot";
ExecStartPre = "!${pkgs.writeShellScript "ddclient-prestart" preStart}";
ExecStart = "${lib.getBin cfg.package}/bin/ddclient -file /run/${RuntimeDirectory}/ddclient.conf";
::: {.note}
`daemon` should not be added here because it does not work great with the systemd-timer approach the service uses.
:::
'';
};
};
};
systemd.timers.ddclient = {
description = "Run ddclient";
wantedBy = [ "timers.target" ];
timerConfig = {
OnBootSec = cfg.interval;
OnUnitInactiveSec = cfg.interval;
###### implementation
config = mkIf config.services.ddclient.enable {
systemd.services.ddclient = {
description = "Dynamic DNS Client";
wantedBy = ["multi-user.target"];
after = ["network.target"];
restartTriggers = optional (cfg.configFile != null) cfg.configFile;
serviceConfig = {
DynamicUser = true;
RuntimeDirectoryMode = "0700";
inherit RuntimeDirectory;
inherit StateDirectory;
Type = "oneshot";
ExecStartPre = "!${pkgs.writeShellScript "ddclient-prestart" preStart}";
ExecStart = "${lib.getBin cfg.package}/bin/ddclient -file /run/${RuntimeDirectory}/ddclient.conf";
};
};
systemd.timers.ddclient = {
description = "Run ddclient";
wantedBy = ["timers.target"];
timerConfig = {
OnBootSec = cfg.interval;
OnUnitInactiveSec = cfg.interval;
};
};
};
};
}
}

View file

@ -29,13 +29,13 @@
else ""
)
+ ''
bindsym l exec ${pkgs.swaylock-bg}/bin/swaylock-bg, mode "default"
bindsym s exec systemctl suspend, mode "default"
bindsym r exec systemctl reboot, mode "default"
bindsym Shift+s exec systemctl poweroff, mode "default"
bindsym l exec ${pkgs.swaylock-bg}/bin/swaylock-bg, mode "default"
bindsym s exec systemctl suspend, mode "default"
bindsym r exec systemctl reboot, mode "default"
bindsym Shift+s exec systemctl poweroff, mode "default"
# exit system mode: "Enter" or "Escape"
bindsym Return mode "default"
bindsym Escape mode "default"
}
# exit system mode: "Enter" or "Escape"
bindsym Return mode "default"
bindsym Escape mode "default"
}
''

View file

@ -24,9 +24,10 @@ in {
'';
};
environment.systemPackages = with pkgs; mkIf psCfg.graphical.v4l2loopback.enable [
linuxPackages.v4l2loopback
];
environment.systemPackages = with pkgs;
mkIf psCfg.graphical.v4l2loopback.enable [
linuxPackages.v4l2loopback
];
programs.sway.enable = true;

View file

@ -1,215 +1,221 @@
{ config, pkgs, lib, ... }:
with lib;
let
{
config,
pkgs,
lib,
...
}:
with lib; let
cfg = config.services.invoiceplane;
eachSite = cfg.sites;
user = "invoiceplane";
webserver = config.services.${cfg.webserver};
invoiceplane-config = hostName: cfg: pkgs.writeText "ipconfig.php" ''
IP_URL=http://${hostName}
ENABLE_DEBUG=false
DISABLE_SETUP=false
REMOVE_INDEXPHP=false
DB_HOSTNAME=${cfg.database.host}
DB_USERNAME=${cfg.database.user}
# NOTE: file_get_contents adds newline at the end of returned string
DB_PASSWORD=${if cfg.database.passwordFile == null then "" else "trim(file_get_contents('${cfg.database.passwordFile}'),\"\\r\\n\")"}
DB_DATABASE=${cfg.database.name}
DB_PORT=${toString cfg.database.port}
SESS_EXPIRATION=864000
ENABLE_INVOICE_DELETION=false
DISABLE_READ_ONLY=false
ENCRYPTION_KEY=
ENCRYPTION_CIPHER=AES-256
SETUP_COMPLETED=false
REMOVE_INDEXPHP=true
'';
extraConfig = hostName: cfg: pkgs.writeText "extraConfig.php" ''
${toString cfg.extraConfig}
'';
pkg = hostName: cfg: pkgs.stdenv.mkDerivation rec {
pname = "invoiceplane-${hostName}";
version = src.version;
src = pkgs.invoiceplane;
postPhase = ''
# Patch index.php file to load additional config file
substituteInPlace index.php \
--replace "require('vendor/autoload.php');" "require('vendor/autoload.php'); \$dotenv = Dotenv\Dotenv::createImmutable(__DIR__, 'extraConfig.php'); \$dotenv->load();";
invoiceplane-config = hostName: cfg:
pkgs.writeText "ipconfig.php" ''
IP_URL=http://${hostName}
ENABLE_DEBUG=false
DISABLE_SETUP=false
REMOVE_INDEXPHP=false
DB_HOSTNAME=${cfg.database.host}
DB_USERNAME=${cfg.database.user}
# NOTE: file_get_contents adds newline at the end of returned string
DB_PASSWORD=${
if cfg.database.passwordFile == null
then ""
else "trim(file_get_contents('${cfg.database.passwordFile}'),\"\\r\\n\")"
}
DB_DATABASE=${cfg.database.name}
DB_PORT=${toString cfg.database.port}
SESS_EXPIRATION=864000
ENABLE_INVOICE_DELETION=false
DISABLE_READ_ONLY=false
ENCRYPTION_KEY=
ENCRYPTION_CIPHER=AES-256
SETUP_COMPLETED=false
REMOVE_INDEXPHP=true
'';
installPhase = ''
mkdir -p $out
cp -r * $out/
# symlink uploads and log directories
rm -r $out/uploads $out/application/logs $out/vendor/mpdf/mpdf/tmp
ln -sf ${cfg.stateDir}/uploads $out/
ln -sf ${cfg.stateDir}/logs $out/application/
ln -sf ${cfg.stateDir}/tmp $out/vendor/mpdf/mpdf/
# symlink the InvoicePlane config
ln -s ${cfg.stateDir}/ipconfig.php $out/ipconfig.php
# symlink the extraConfig file
ln -s ${extraConfig hostName cfg} $out/extraConfig.php
# symlink additional templates
${concatMapStringsSep "\n" (template: "cp -r ${template}/. $out/application/views/invoice_templates/pdf/") cfg.invoiceTemplates}
extraConfig = hostName: cfg:
pkgs.writeText "extraConfig.php" ''
${toString cfg.extraConfig}
'';
};
siteOpts = { lib, name, ... }:
{
options = {
pkg = hostName: cfg:
pkgs.stdenv.mkDerivation rec {
pname = "invoiceplane-${hostName}";
version = src.version;
src = pkgs.invoiceplane;
enable = mkEnableOption (lib.mdDoc "InvoicePlane web application");
postPhase = ''
# Patch index.php file to load additional config file
substituteInPlace index.php \
--replace "require('vendor/autoload.php');" "require('vendor/autoload.php'); \$dotenv = Dotenv\Dotenv::createImmutable(__DIR__, 'extraConfig.php'); \$dotenv->load();";
'';
stateDir = mkOption {
type = types.path;
default = "/var/lib/invoiceplane/${name}";
description = lib.mdDoc ''
This directory is used for uploads of attachments and cache.
The directory passed here is automatically created and permissions
adjusted as required.
'';
};
installPhase = ''
mkdir -p $out
cp -r * $out/
database = {
host = mkOption {
type = types.str;
default = "localhost";
description = lib.mdDoc "Database host address.";
};
# symlink uploads and log directories
rm -r $out/uploads $out/application/logs $out/vendor/mpdf/mpdf/tmp
ln -sf ${cfg.stateDir}/uploads $out/
ln -sf ${cfg.stateDir}/logs $out/application/
ln -sf ${cfg.stateDir}/tmp $out/vendor/mpdf/mpdf/
port = mkOption {
type = types.port;
default = 3306;
description = lib.mdDoc "Database host port.";
};
# symlink the InvoicePlane config
ln -s ${cfg.stateDir}/ipconfig.php $out/ipconfig.php
name = mkOption {
type = types.str;
default = "invoiceplane";
description = lib.mdDoc "Database name.";
};
# symlink the extraConfig file
ln -s ${extraConfig hostName cfg} $out/extraConfig.php
user = mkOption {
type = types.str;
default = "invoiceplane";
description = lib.mdDoc "Database user.";
};
# symlink additional templates
${concatMapStringsSep "\n" (template: "cp -r ${template}/. $out/application/views/invoice_templates/pdf/") cfg.invoiceTemplates}
'';
};
passwordFile = mkOption {
type = types.nullOr types.path;
default = null;
example = "/run/keys/invoiceplane-dbpassword";
description = lib.mdDoc ''
A file containing the password corresponding to
{option}`database.user`.
'';
};
createLocally = mkOption {
type = types.bool;
default = true;
description = lib.mdDoc "Create the database and database user locally.";
};
};
invoiceTemplates = mkOption {
type = types.listOf types.path;
default = [];
description = lib.mdDoc ''
List of path(s) to respective template(s) which are copied from the 'invoice_templates/pdf' directory.
::: {.note}
These templates need to be packaged before use, see example.
:::
'';
example = literalExpression ''
let
# Let's package an example template
template-vtdirektmarketing = pkgs.stdenv.mkDerivation {
name = "vtdirektmarketing";
# Download the template from a public repository
src = pkgs.fetchgit {
url = "https://git.project-insanity.org/onny/invoiceplane-vtdirektmarketing.git";
sha256 = "1hh0q7wzsh8v8x03i82p6qrgbxr4v5fb05xylyrpp975l8axyg2z";
};
sourceRoot = ".";
# Installing simply means copying template php file to the output directory
installPhase = ""
mkdir -p $out
cp invoiceplane-vtdirektmarketing/vtdirektmarketing.php $out/
"";
};
# And then pass this package to the template list like this:
in [ template-vtdirektmarketing ]
'';
};
poolConfig = mkOption {
type = with types; attrsOf (oneOf [ str int bool ]);
default = {
"pm" = "dynamic";
"pm.max_children" = 32;
"pm.start_servers" = 2;
"pm.min_spare_servers" = 2;
"pm.max_spare_servers" = 4;
"pm.max_requests" = 500;
};
description = lib.mdDoc ''
Options for the InvoicePlane PHP pool. See the documentation on `php-fpm.conf`
for details on configuration directives.
'';
};
extraConfig = mkOption {
type = types.nullOr types.lines;
default = null;
example = ''
SETUP_COMPLETED=true
DISABLE_SETUP=true
IP_URL=https://invoice.example.com
'';
description = lib.mdDoc ''
InvoicePlane configuration. Refer to
<https://github.com/InvoicePlane/InvoicePlane/blob/master/ipconfig.php.example>
for details on supported values.
'';
};
cron = {
enable = mkOption {
type = types.bool;
default = false;
description = lib.mdDoc ''
Enable cron service which periodically runs Invoiceplane tasks.
Requires key taken from the administration page. Refer to
<https://wiki.invoiceplane.com/en/1.0/modules/recurring-invoices>
on how to configure it.
'';
};
key = mkOption {
type = types.str;
description = lib.mdDoc "Cron key taken from the administration page.";
};
};
siteOpts = {
lib,
name,
...
}: {
options = {
enable = mkEnableOption (lib.mdDoc "InvoicePlane web application");
stateDir = mkOption {
type = types.path;
default = "/var/lib/invoiceplane/${name}";
description = lib.mdDoc ''
This directory is used for uploads of attachments and cache.
The directory passed here is automatically created and permissions
adjusted as required.
'';
};
database = {
host = mkOption {
type = types.str;
default = "localhost";
description = lib.mdDoc "Database host address.";
};
port = mkOption {
type = types.port;
default = 3306;
description = lib.mdDoc "Database host port.";
};
name = mkOption {
type = types.str;
default = "invoiceplane";
description = lib.mdDoc "Database name.";
};
user = mkOption {
type = types.str;
default = "invoiceplane";
description = lib.mdDoc "Database user.";
};
passwordFile = mkOption {
type = types.nullOr types.path;
default = null;
example = "/run/keys/invoiceplane-dbpassword";
description = lib.mdDoc ''
A file containing the password corresponding to
{option}`database.user`.
'';
};
createLocally = mkOption {
type = types.bool;
default = true;
description = lib.mdDoc "Create the database and database user locally.";
};
};
invoiceTemplates = mkOption {
type = types.listOf types.path;
default = [];
description = lib.mdDoc ''
List of path(s) to respective template(s) which are copied from the 'invoice_templates/pdf' directory.
::: {.note}
These templates need to be packaged before use, see example.
:::
'';
example = literalExpression ''
let
# Let's package an example template
template-vtdirektmarketing = pkgs.stdenv.mkDerivation {
name = "vtdirektmarketing";
# Download the template from a public repository
src = pkgs.fetchgit {
url = "https://git.project-insanity.org/onny/invoiceplane-vtdirektmarketing.git";
sha256 = "1hh0q7wzsh8v8x03i82p6qrgbxr4v5fb05xylyrpp975l8axyg2z";
};
sourceRoot = ".";
# Installing simply means copying template php file to the output directory
installPhase = ""
mkdir -p $out
cp invoiceplane-vtdirektmarketing/vtdirektmarketing.php $out/
"";
};
# And then pass this package to the template list like this:
in [ template-vtdirektmarketing ]
'';
};
poolConfig = mkOption {
type = with types; attrsOf (oneOf [str int bool]);
default = {
"pm" = "dynamic";
"pm.max_children" = 32;
"pm.start_servers" = 2;
"pm.min_spare_servers" = 2;
"pm.max_spare_servers" = 4;
"pm.max_requests" = 500;
};
description = lib.mdDoc ''
Options for the InvoicePlane PHP pool. See the documentation on `php-fpm.conf`
for details on configuration directives.
'';
};
extraConfig = mkOption {
type = types.nullOr types.lines;
default = null;
example = ''
SETUP_COMPLETED=true
DISABLE_SETUP=true
IP_URL=https://invoice.example.com
'';
description = lib.mdDoc ''
InvoicePlane configuration. Refer to
<https://github.com/InvoicePlane/InvoicePlane/blob/master/ipconfig.php.example>
for details on supported values.
'';
};
cron = {
enable = mkOption {
type = types.bool;
default = false;
description = lib.mdDoc ''
Enable cron service which periodically runs Invoiceplane tasks.
Requires key taken from the administration page. Refer to
<https://wiki.invoiceplane.com/en/1.0/modules/recurring-invoices>
on how to configure it.
'';
};
key = mkOption {
type = types.str;
description = lib.mdDoc "Cron key taken from the administration page.";
};
};
};
in
{
};
in {
disabledModules = [
"services/web-apps/invoiceplane.nix"
];
@ -218,7 +224,6 @@ in
options = {
services.invoiceplane = mkOption {
type = types.submodule {
options.sites = mkOption {
type = types.attrsOf (types.submodule siteOpts);
default = {};
@ -226,7 +231,7 @@ in
};
options.webserver = mkOption {
type = types.enum [ "caddy" ];
type = types.enum ["caddy"];
default = "caddy";
description = lib.mdDoc ''
Which webserver to use for virtual host management. Currently only
@ -237,126 +242,136 @@ in
default = {};
description = lib.mdDoc "InvoicePlane configuration.";
};
};
# implementation
config = mkIf (eachSite != {}) (mkMerge [{
config = mkIf (eachSite != {}) (mkMerge [
{
assertions = flatten (mapAttrsToList (hostName: cfg: [
{
assertion = cfg.database.createLocally -> cfg.database.user == user;
message = ''services.invoiceplane.sites."${hostName}".database.user must be ${user} if the database is to be automatically provisioned'';
}
{
assertion = cfg.database.createLocally -> cfg.database.passwordFile == null;
message = ''services.invoiceplane.sites."${hostName}".database.passwordFile cannot be specified if services.invoiceplane.sites."${hostName}".database.createLocally is set to true.'';
}
{
assertion = cfg.cron.enable -> cfg.cron.key != null;
message = ''services.invoiceplane.sites."${hostName}".cron.key must be set in order to use cron service.'';
}
])
eachSite);
assertions = flatten (mapAttrsToList (hostName: cfg:
[{ assertion = cfg.database.createLocally -> cfg.database.user == user;
message = ''services.invoiceplane.sites."${hostName}".database.user must be ${user} if the database is to be automatically provisioned'';
}
{ assertion = cfg.database.createLocally -> cfg.database.passwordFile == null;
message = ''services.invoiceplane.sites."${hostName}".database.passwordFile cannot be specified if services.invoiceplane.sites."${hostName}".database.createLocally is set to true.'';
}
{ assertion = cfg.cron.enable -> cfg.cron.key != null;
message = ''services.invoiceplane.sites."${hostName}".cron.key must be set in order to use cron service.'';
}
]) eachSite);
services.mysql = mkIf (any (v: v.database.createLocally) (attrValues eachSite)) {
enable = true;
package = mkDefault pkgs.mariadb;
ensureDatabases = mapAttrsToList (hostName: cfg: cfg.database.name) eachSite;
ensureUsers =
mapAttrsToList (
hostName: cfg: {
name = cfg.database.user;
ensurePermissions = {"${cfg.database.name}.*" = "ALL PRIVILEGES";};
}
)
eachSite;
};
services.mysql = mkIf (any (v: v.database.createLocally) (attrValues eachSite)) {
enable = true;
package = mkDefault pkgs.mariadb;
ensureDatabases = mapAttrsToList (hostName: cfg: cfg.database.name) eachSite;
ensureUsers = mapAttrsToList (hostName: cfg:
{ name = cfg.database.user;
ensurePermissions = { "${cfg.database.name}.*" = "ALL PRIVILEGES"; };
}
) eachSite;
};
services.phpfpm = {
phpPackage = pkgs.php81;
pools =
mapAttrs' (hostName: cfg: (
nameValuePair "invoiceplane-${hostName}" {
inherit user;
group = webserver.group;
settings =
{
"listen.owner" = webserver.user;
"listen.group" = webserver.group;
}
// cfg.poolConfig;
}
))
eachSite;
};
}
services.phpfpm = {
phpPackage = pkgs.php81;
pools = mapAttrs' (hostName: cfg: (
nameValuePair "invoiceplane-${hostName}" {
inherit user;
group = webserver.group;
settings = {
"listen.owner" = webserver.user;
"listen.group" = webserver.group;
} // cfg.poolConfig;
}
)) eachSite;
};
{
systemd.tmpfiles.rules = flatten (mapAttrsToList (hostName: cfg: [
"d ${cfg.stateDir} 0750 ${user} ${webserver.group} - -"
"f ${cfg.stateDir}/ipconfig.php 0750 ${user} ${webserver.group} - -"
"d ${cfg.stateDir}/logs 0750 ${user} ${webserver.group} - -"
"d ${cfg.stateDir}/uploads 0750 ${user} ${webserver.group} - -"
"d ${cfg.stateDir}/uploads/archive 0750 ${user} ${webserver.group} - -"
"d ${cfg.stateDir}/uploads/customer_files 0750 ${user} ${webserver.group} - -"
"d ${cfg.stateDir}/uploads/temp 0750 ${user} ${webserver.group} - -"
"d ${cfg.stateDir}/uploads/temp/mpdf 0750 ${user} ${webserver.group} - -"
"d ${cfg.stateDir}/tmp 0750 ${user} ${webserver.group} - -"
])
eachSite);
}
systemd.services.invoiceplane-config = {
serviceConfig.Type = "oneshot";
script = concatStrings (mapAttrsToList (hostName: cfg: ''
mkdir -p ${cfg.stateDir}/logs \
${cfg.stateDir}/uploads
if ! grep -q IP_URL "${cfg.stateDir}/ipconfig.php"; then
cp "${invoiceplane-config hostName cfg}" "${cfg.stateDir}/ipconfig.php"
fi
'')
eachSite);
wantedBy = ["multi-user.target"];
};
{
users.users.${user} = {
group = webserver.group;
isSystemUser = true;
};
}
{
# Cron service implementation
systemd.tmpfiles.rules = flatten (mapAttrsToList (hostName: cfg: [
"d ${cfg.stateDir} 0750 ${user} ${webserver.group} - -"
"f ${cfg.stateDir}/ipconfig.php 0750 ${user} ${webserver.group} - -"
"d ${cfg.stateDir}/logs 0750 ${user} ${webserver.group} - -"
"d ${cfg.stateDir}/uploads 0750 ${user} ${webserver.group} - -"
"d ${cfg.stateDir}/uploads/archive 0750 ${user} ${webserver.group} - -"
"d ${cfg.stateDir}/uploads/customer_files 0750 ${user} ${webserver.group} - -"
"d ${cfg.stateDir}/uploads/temp 0750 ${user} ${webserver.group} - -"
"d ${cfg.stateDir}/uploads/temp/mpdf 0750 ${user} ${webserver.group} - -"
"d ${cfg.stateDir}/tmp 0750 ${user} ${webserver.group} - -"
]) eachSite);
systemd.timers =
mapAttrs' (hostName: cfg: (
nameValuePair "invoiceplane-cron-${hostName}" (mkIf cfg.cron.enable {
wantedBy = ["timers.target"];
timerConfig = {
OnBootSec = "5m";
OnUnitActiveSec = "5m";
Unit = "invoiceplane-cron-${hostName}.service";
};
})
))
eachSite;
systemd.services.invoiceplane-config = {
serviceConfig.Type = "oneshot";
script = concatStrings (mapAttrsToList (hostName: cfg:
''
mkdir -p ${cfg.stateDir}/logs \
${cfg.stateDir}/uploads
if ! grep -q IP_URL "${cfg.stateDir}/ipconfig.php"; then
cp "${invoiceplane-config hostName cfg}" "${cfg.stateDir}/ipconfig.php"
fi
'') eachSite);
wantedBy = [ "multi-user.target" ];
};
users.users.${user} = {
group = webserver.group;
isSystemUser = true;
};
}
{
# Cron service implementation
systemd.timers = mapAttrs' (hostName: cfg: (
nameValuePair "invoiceplane-cron-${hostName}" (mkIf cfg.cron.enable {
wantedBy = [ "timers.target" ];
timerConfig = {
OnBootSec = "5m";
OnUnitActiveSec = "5m";
Unit = "invoiceplane-cron-${hostName}.service";
};
})
)) eachSite;
systemd.services =
mapAttrs' (hostName: cfg: (
nameValuePair "invoiceplane-cron-${hostName}" (mkIf cfg.cron.enable {
serviceConfig = {
Type = "oneshot";
User = user;
ExecStart = "${pkgs.curl}/bin/curl --header 'Host: ${hostName}' http://localhost/invoices/cron/recur/${cfg.cron.key}";
};
})
)) eachSite;
}
(mkIf (cfg.webserver == "caddy") {
services.caddy = {
enable = true;
virtualHosts = mapAttrs' (hostName: cfg: (
nameValuePair "http://${hostName}" {
extraConfig = ''
root * ${pkg hostName cfg}
file_server
php_fastcgi unix/${config.services.phpfpm.pools."invoiceplane-${hostName}".socket}
'';
}
)) eachSite;
};
})
systemd.services =
mapAttrs' (hostName: cfg: (
nameValuePair "invoiceplane-cron-${hostName}" (mkIf cfg.cron.enable {
serviceConfig = {
Type = "oneshot";
User = user;
ExecStart = "${pkgs.curl}/bin/curl --header 'Host: ${hostName}' http://localhost/invoices/cron/recur/${cfg.cron.key}";
};
})
))
eachSite;
}
(mkIf (cfg.webserver == "caddy") {
services.caddy = {
enable = true;
virtualHosts =
mapAttrs' (hostName: cfg: (
nameValuePair "http://${hostName}" {
extraConfig = ''
root * ${pkg hostName cfg}
file_server
php_fastcgi unix/${config.services.phpfpm.pools."invoiceplane-${hostName}".socket}
'';
}
))
eachSite;
};
})
]);
}

View file

@ -5,15 +5,16 @@
flake,
...
}: {
nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [
"1password"
"1password-cli"
"cups-brother-hl3140cw"
"facetimehd-firmware"
"slack"
"veracrypt"
"zoom"
];
nixpkgs.config.allowUnfreePredicate = pkg:
builtins.elem (lib.getName pkg) [
"1password"
"1password-cli"
"cups-brother-hl3140cw"
"facetimehd-firmware"
"slack"
"veracrypt"
"zoom"
];
nix = {
# Use default version alias for nix package

View file

@ -16,11 +16,15 @@
services.printing.listenAddresses = ["localhost:631"];
services.printing.defaultShared = lib.mkDefault false;
services.printing.drivers = [
pkgs.gutenprint
] ++ (if (pkgs.system == "x86_64-linux")
then [ pkgs.cups-brother-hl3140cw ]
else []);
services.printing.drivers =
[
pkgs.gutenprint
]
++ (
if (pkgs.system == "x86_64-linux")
then [pkgs.cups-brother-hl3140cw]
else []
);
networking.hosts = flake.self.lib.addLocalHostname ["cups.local"];

View file

@ -1,5 +1,4 @@
{ ... }:
{
{...}: {
enable = true;
nix-direnv = {
enable = true;

View file

@ -1,5 +1,4 @@
{ ... }:
{
{...}: {
enable = true;
extraConfig = {

View file

@ -44,135 +44,136 @@ in {
universal-ctags
];
plugins = with pkgs.vimPlugins; lib.mkIf cfg.full [
(pkgs.vimPlugins.nvim-treesitter.withPlugins (p: [
p.ini
p.json
p.json5
p.markdown
p.nix
p.toml
p.yaml
plugins = with pkgs.vimPlugins;
lib.mkIf cfg.full [
(pkgs.vimPlugins.nvim-treesitter.withPlugins (p: [
p.ini
p.json
p.json5
p.markdown
p.nix
p.toml
p.yaml
p.css
p.graphql
p.html
p.javascript
p.scss
p.tsx
p.typescript
p.vue
p.css
p.graphql
p.html
p.javascript
p.scss
p.tsx
p.typescript
p.vue
p.c
p.cpp
p.go
p.gomod
p.gosum
p.haskell
p.lua
p.php
p.python
p.ruby
p.rust
p.c
p.cpp
p.go
p.gomod
p.gosum
p.haskell
p.lua
p.php
p.python
p.ruby
p.rust
p.vim
p.vimdoc
p.vim
p.vimdoc
p.passwd
p.sql
p.passwd
p.sql
p.diff
p.gitcommit
p.gitignore
p.git_config
p.gitattributes
p.git_rebase
p.diff
p.gitcommit
p.gitignore
p.git_config
p.gitattributes
p.git_rebase
p.bash
p.dockerfile
p.make
p.ninja
p.terraform
]))
p.bash
p.dockerfile
p.make
p.ninja
p.terraform
]))
# Dependencies for nvim-lspconfig
nvim-cmp
cmp-nvim-lsp
cmp_luasnip
luasnip
# Dependencies for nvim-lspconfig
nvim-cmp
cmp-nvim-lsp
cmp_luasnip
luasnip
# Quickstart configs for neovim LSP
lsp_extensions-nvim
nvim-lspconfig
# Quickstart configs for neovim LSP
lsp_extensions-nvim
nvim-lspconfig
# Collaborative editing in Neovim using built-in capabilities
instant-nvim-nvfetcher
# Collaborative editing in Neovim using built-in capabilities
instant-nvim-nvfetcher
# Search functionality behind :Ack
ack-vim
# Search functionality behind :Ack
ack-vim
# The status bar in the bottom of the screen with the mode indication and file location
vim-airline
# The status bar in the bottom of the screen with the mode indication and file location
vim-airline
# Automatically load editorconfig files in repos to configure nvim settings
editorconfig-vim
# Automatically load editorconfig files in repos to configure nvim settings
editorconfig-vim
# File browser. Use <leader>n to access
nnn-vim
# File browser. Use <leader>n to access
nnn-vim
# Highlight characters when using f, F, t, and T
quick-scope
# Highlight characters when using f, F, t, and T
quick-scope
# Get sudo in vim; :SudaWrite <optional filename>
suda-vim
# Get sudo in vim; :SudaWrite <optional filename>
suda-vim
# Undo history etc. per project
vim-workspace-nvfetcher
# Undo history etc. per project
vim-workspace-nvfetcher
# JSON schemas
SchemaStore-nvim
# JSON schemas
SchemaStore-nvim
# Work with tags files
vim-gutentags
# Work with tags files
vim-gutentags
# Neovim colorschemes / themes
sonokai
vim-hybrid-material
vim-airline-themes
vim-apprentice-nvfetcher
# Neovim colorschemes / themes
sonokai
vim-hybrid-material
vim-airline-themes
vim-apprentice-nvfetcher
# Git integrations
# A Git wrapper so awesome, it should be illegal
fugitive
# Shows git diff markers in the sign column
vim-gitgutter
# GitHub extension for fugitive
vim-rhubarb
# Ease your git workflow within Vim
vimagit-nvfetcher
# Git integrations
# A Git wrapper so awesome, it should be illegal
fugitive
# Shows git diff markers in the sign column
vim-gitgutter
# GitHub extension for fugitive
vim-rhubarb
# Ease your git workflow within Vim
vimagit-nvfetcher
# FZF fuzzy finder
fzf-vim
fzfWrapper
# Make the yanked region apparent
vim-highlightedyank
# FZF fuzzy finder
fzf-vim
fzfWrapper
# Make the yanked region apparent
vim-highlightedyank
# :Beautify Code beautifier
vim-beautify-nvfetcher
# :Beautify Code beautifier
vim-beautify-nvfetcher
# Unload, delete or wipe a buffer without closing the window
vim-bufkill
# Defaults everyone can agree on
vim-sensible
# Unload, delete or wipe a buffer without closing the window
vim-bufkill
# Defaults everyone can agree on
vim-sensible
# emmet for vim: http://emmet.io/
emmet-vim
# Caddyfile syntax support for Vim
vim-caddyfile-nvfetcher
# emmet for vim: http://emmet.io/
emmet-vim
# Caddyfile syntax support for Vim
vim-caddyfile-nvfetcher
# Fix TOFU hashes when writing nix derivations without leaving neovim
vim-nixhash
];
# Fix TOFU hashes when writing nix derivations without leaving neovim
vim-nixhash
];
extraConfig = builtins.concatStringsSep "\n" [
''

View file

@ -6,96 +6,96 @@
}: let
psCfg = config.pub-solar;
in
with lib; {
imports = [
./home.nix
];
options.pub-solar = {
user = {
name = mkOption {
description = "User login name";
type = types.nullOr types.str;
default = "nixos";
};
description = mkOption {
description = "User description";
type = types.nullOr types.str;
default = "The main PubSolarOS user";
};
password = mkOption {
description = "User password";
type = types.nullOr types.str;
default = null;
};
passwordlessSudo = mkOption {
description = "Whether this user can use sudo without entering a password";
type = types.bool;
default = false;
};
publicKeys = mkOption {
description = "User SSH public keys";
type = types.listOf types.str;
default = [];
};
fullName = mkOption {
description = "User full name";
type = types.nullOr types.str;
default = null;
};
email = mkOption {
description = "User email address";
type = types.nullOr types.str;
default = null;
};
gpgKeyId = mkOption {
description = "GPG Key ID";
type = types.nullOr types.str;
default = null;
};
};
};
config = {
users = {
mutableUsers = false;
users."${psCfg.user.name}" = {
# Indicates whether this is an account for a “real” user.
# This automatically sets group to users, createHome to true,
# home to /home/username, useDefaultShell to true, and isSystemUser to false.
isNormalUser = true;
description = psCfg.user.description;
extraGroups = [
"input"
"lp"
"networkmanager"
"scanner"
"video"
"wheel"
];
shell = pkgs.bash;
initialHashedPassword =
if psCfg.user.password != null
then psCfg.user.password
else "";
openssh.authorizedKeys.keys =
if psCfg.user.publicKeys != null
then psCfg.user.publicKeys
else [];
};
};
security.sudo.extraRules = mkIf psCfg.user.passwordlessSudo [
{
users = ["${psCfg.user.name}"];
commands = [
{
command = "ALL";
options = ["NOPASSWD"];
}
];
}
with lib; {
imports = [
./home.nix
];
};
}
options.pub-solar = {
user = {
name = mkOption {
description = "User login name";
type = types.nullOr types.str;
default = "nixos";
};
description = mkOption {
description = "User description";
type = types.nullOr types.str;
default = "The main PubSolarOS user";
};
password = mkOption {
description = "User password";
type = types.nullOr types.str;
default = null;
};
passwordlessSudo = mkOption {
description = "Whether this user can use sudo without entering a password";
type = types.bool;
default = false;
};
publicKeys = mkOption {
description = "User SSH public keys";
type = types.listOf types.str;
default = [];
};
fullName = mkOption {
description = "User full name";
type = types.nullOr types.str;
default = null;
};
email = mkOption {
description = "User email address";
type = types.nullOr types.str;
default = null;
};
gpgKeyId = mkOption {
description = "GPG Key ID";
type = types.nullOr types.str;
default = null;
};
};
};
config = {
users = {
mutableUsers = false;
users."${psCfg.user.name}" = {
# Indicates whether this is an account for a “real” user.
# This automatically sets group to users, createHome to true,
# home to /home/username, useDefaultShell to true, and isSystemUser to false.
isNormalUser = true;
description = psCfg.user.description;
extraGroups = [
"input"
"lp"
"networkmanager"
"scanner"
"video"
"wheel"
];
shell = pkgs.bash;
initialHashedPassword =
if psCfg.user.password != null
then psCfg.user.password
else "";
openssh.authorizedKeys.keys =
if psCfg.user.publicKeys != null
then psCfg.user.publicKeys
else [];
};
};
security.sudo.extraRules = mkIf psCfg.user.passwordlessSudo [
{
users = ["${psCfg.user.name}"];
commands = [
{
command = "ALL";
options = ["NOPASSWD"];
}
];
}
];
};
}

View file

@ -6,18 +6,16 @@
}: {
flake = {
nixosModules = rec {
overlays = ({ ... }: {
overlays = {...}: {
nixpkgs.overlays = [
(final: prev:
let
(final: prev: let
unstable = import inputs.unstable {
system = prev.system;
};
master = import inputs.master {
system = prev.system;
};
in
{
in {
direnv = unstable.direnv;
nix-direnv = unstable.nix-direnv;
#vimPlugins = prev.vimPlugins // {inherit (unstable.vimPlugins) nvim-lspconfig;};
@ -31,7 +29,7 @@
(import ./nix-index.nix)
(import ./neovim-plugins.nix)
];
});
};
};
};
}

View file

@ -1,5 +1,4 @@
{ self, ... }:
{
{self, ...}: {
flake = {
nixosModules = rec {
root = import ./root;

View file

@ -1,4 +1,3 @@
{...}:
{
{...}: {
users.users.root.hashedPassword = "";
}