refactor!: I went too close into the sun

Simlified everything, things were getting out of control
This commit is contained in:
Gustavo "Guz" L. de Mello
2024-04-08 16:37:29 -03:00
parent 0cf8524902
commit 5ce99497d7
87 changed files with 3726 additions and 2415 deletions

View File

@@ -5,7 +5,11 @@ creation_rules:
key_groups:
- age:
- *primary
- path_regex: secrets/homex-secrets.yaml$
- path_regex: secrets/homelab-secrets.yaml$
key_groups:
- age:
- *primary
- path_regex: secrets/homelab-lesser-secrets.json$
key_groups:
- age:
- *primary

View File

@@ -42,37 +42,40 @@
hyprland.url = "github:hyprwm/Hyprland";
xdg-desktop-portal-hyprland.url = "github:hyprwm/xdg-desktop-portal-hyprland";
/*
Note to self:
The last commit with working screen share, as the time of writing this, was
https://github.com/hyprwm/xdg-desktop-portal-hyprland/commit/6a5de92769d5b7038134044053f90e7458f6a197
https://github.com/hyprwm/Hyprland/commit/3c964a9fdc220250a85b1c498e5b6fad9390272f
so if needed, you can always roll-back.
Note to self:
The last commit with working screen share, as the time of writing this, was
https://github.com/hyprwm/xdg-desktop-portal-hyprland/commit/6a5de92769d5b7038134044053f90e7458f6a197
https://github.com/hyprwm/Hyprland/commit/3c964a9fdc220250a85b1c498e5b6fad9390272f
so if needed, you can always roll-back.
Fuck discord.
Fuck discord.
*/
};
outputs = { self, nixpkgs, home-manager, ... }@inputs:
let
create-host = (configs: builtins.listToAttrs (map
outputs = {
self,
nixpkgs,
home-manager,
...
} @ inputs: let
create-host = configs:
builtins.listToAttrs (map
(c: {
name = c;
value = nixpkgs.lib.nixosSystem {
specialArgs = { inherit inputs; };
specialArgs = {inherit inputs;};
modules = [
inputs.home-manager.nixosModules.default
(./. + ("/hosts/" + builtins.replaceStrings [ "@" ] [ "/" ] c) + /configuration.nix)
(./. + ("/hosts/" + builtins.replaceStrings ["@"] ["/"] c) + /configuration.nix)
];
};
})
configs));
in
{
nixosConfigurations = (create-host [
"desktop@default"
"desktop@work"
"homelab"
]);
};
configs);
in {
nixosConfigurations = create-host [
"desktop@default"
"desktop@work"
"homelab"
];
};
}

View File

@@ -1,96 +1,69 @@
# your system. Help is available in the configuration.nix(5) man page
# and in the NixOS manual (accessible by running nixos-help).
{ config, ... }:
{
config,
pkgs,
...
}: {
imports = [
../../modules/nixos/config/host.nix
../../modules/nih
../../modules/nixos
./services.nix
./secrets.nix
./hardware-configuration.nix
];
nih = {
enable = true;
name = "homelab";
ip = "192.168.1.10";
type = "server";
programs.nih.enable = true;
programs.nih.flakeDir = "/home/guz/.nix";
programs.nih.host = "homelab";
networking = {
interface = "eno1";
wireless = false;
};
profiles.locale.enable = true;
services.tailscale = {
enable = true;
exitNode = true;
routingFeatures = "both";
};
users.guz = {
username = "guz";
password = "$y$j9T$J7gmdB306rufrjdsY5kJq0$spluDZf8jEkG0VYcZXzBIpnACVIk27C8YTbo2vbNFfA";
profiles.gterminal.enable = true;
};
home-manager-helper.enable = true;
home-manager-helper.users."guz" = {
name = "guz";
shell = pkgs.zsh;
hashedPassword = "$y$j9T$J7gmdB306rufrjdsY5kJq0$spluDZf8jEkG0VYcZXzBIpnACVIk27C8YTbo2vbNFfA";
home = import ./home.nix;
isNormalUser = true;
extraGroups = ["wheel" "networkmanager"];
};
environment.sessionVariables = {
EDITOR = "nvim";
};
/*
server = {
enable = true;
flakeDir = "/home/guz/.nix#homelab";
name = "homelab";
domain = "homelab.local";
environment.systemPackages = with pkgs; [
git
];
ip = "100.66.139.89";
localIp = "192.168.1.10";
programs.zsh.enable = true;
network = {
enable = true;
interface = "eno1";
};
nix.settings.experimental-features = ["nix-command" "flakes"];
nextcloud = {
enable = true;
settings.admin = {
passwordFile = /. + config.sops.secrets."nextcloud/user1/password".path;
};
};
networking = {
networkmanager.enable = true;
hostName = "homelab";
wireless.enable = false;
dhcpcd.enable = true;
defaultGateway = "192.168.1.1";
interfaces."eno1".ipv4.addresses = [
{
address = "192.168.1.10";
prefixLength = 24;
}
];
};
tailscale = {
enable = true;
mode = "both";
exitNode = true;
};
forgejo = {
enable = true;
actions = {
enable = true;
runnerToken = "PYKxHNpeCR2ajtdPgo1C3rvgZHNJqzH4bUXLDwLa";
};
settings.server.url = "https://${config.server.forgejo.settings.server.domain}";
settings.users."user1" = {
name = /. + config.sops.secrets."forgejo/user1/name".path;
email = /. + config.sops.secrets."forgejo/user1/email".path;
password = /. + config.sops.secrets."forgejo/user1/password".path;
admin = true;
};
settings.ui.themes = [ "forgejo-dark" "arc-green" ];
I'm hours trying to make pushing via SSH work, but using the {user}@{domain}:{owner}/{repo}
simply isn't working and returns "does not appear to be a git repository". Probably
is a problem with all the "domain handling" stuff with caddy, adguard, etc. This is
a temporary fix, so I don't end up breaking my actual sanity.
settings.security.allowBypassGiteaEnv = true;
};
jellyfin = {
enable = true;
};
};
*/
services.tailscale.enable = true;
sound.enable = true;
hardware.pulseaudio.enable = false;
security.rtkit.enable = true;
services.pipewire = {
enable = true;
alsa.enable = true;
alsa.support32Bit = true;
pulse.enable = true;
wireplumber.enable = true;
};
# Bootloader.
boot.loader.systemd-boot.enable = true;
@@ -107,5 +80,3 @@
# (e.g. man configuration.nix or on https://nixos.org/nixos/options.html).
system.stateVersion = "23.11"; # Did you read the comment?
}

View File

@@ -1,31 +1,35 @@
# Do not modify this file! It was generated by nixos-generate-config
# and may be overwritten by future invocations. Please make changes
# to /etc/nixos/configuration.nix instead.
{ config, lib, pkgs, modulesPath, ... }:
{
imports =
[ (modulesPath + "/installer/scan/not-detected.nix")
];
config,
lib,
pkgs,
modulesPath,
...
}: {
imports = [
(modulesPath + "/installer/scan/not-detected.nix")
];
boot.initrd.availableKernelModules = [ "ahci" "xhci_pci" "usbhid" "usb_storage" "sd_mod" "sdhci_acpi" ];
boot.initrd.kernelModules = [ ];
boot.kernelModules = [ "kvm-intel" ];
boot.extraModulePackages = [ ];
boot.initrd.availableKernelModules = ["ahci" "xhci_pci" "usbhid" "usb_storage" "sd_mod" "sdhci_acpi"];
boot.initrd.kernelModules = [];
boot.kernelModules = ["kvm-intel"];
boot.extraModulePackages = [];
fileSystems."/" =
{ device = "/dev/disk/by-uuid/26878c61-60d2-4ac9-967f-30faffc0f21f";
fsType = "ext4";
};
fileSystems."/" = {
device = "/dev/disk/by-uuid/26878c61-60d2-4ac9-967f-30faffc0f21f";
fsType = "ext4";
};
fileSystems."/boot" =
{ device = "/dev/disk/by-uuid/ACCB-816C";
fsType = "vfat";
};
fileSystems."/boot" = {
device = "/dev/disk/by-uuid/ACCB-816C";
fsType = "vfat";
};
swapDevices =
[ { device = "/dev/disk/by-uuid/878a189a-e5df-4e1c-92d5-df6f77ff53bb"; }
];
swapDevices = [
{device = "/dev/disk/by-uuid/878a189a-e5df-4e1c-92d5-df6f77ff53bb";}
];
# Enables DHCP on each ethernet and wireless interface. In case of scripted networking
# (the default) this is the recommended approach. When using systemd-networkd it's

8
hosts/homelab/home.nix Normal file
View File

@@ -0,0 +1,8 @@
{...}: {
imports = [
../../modules/home-manager
];
profiles.gterminal.enable = true;
programs.wezterm.enable = false;
}

View File

@@ -1,34 +1,45 @@
{ config, ... }:
{
imports = [ ];
options = { };
config,
inputs,
lib,
pkgs,
...
}: let
lesser-secrets = with builtins;
fromJSON (readFile ../../secrets/homelab-lesser-secrets.decrypted.json);
jsonType = pkgs.formats.json {};
in {
imports = [
inputs.sops-nix.nixosModules.sops
];
options.homelab-secrets = with lib;
with lib.types; {
lesser = mkOption {
type = submodule ({...}: {
freeformType = jsonType.type;
options = {};
});
default = lesser-secrets;
};
};
config = {
environment.systemPackages = with pkgs; [
sops
];
sops.defaultSopsFile = ../../secrets/homelab-secrets.yaml;
sops.defaultSopsFormat = "yaml";
sops.secrets."forgejo/user1/name" = {
owner = config.server.forgejo.user;
owner = config.services.forgejo.user;
};
sops.secrets."forgejo/user1/password" = {
owner = config.server.forgejo.user;
owner = config.services.forgejo.user;
};
sops.secrets."forgejo/user1/email" = {
owner = config.server.forgejo.user;
owner = config.services.forgejo.user;
};
/*
sops.secrets."nextcloud/user1/name" = {
owner = config.homelab.nextcloud.user;
};
sops.secrets."nextcloud/user1/password" = {
owner = config.homelab.nextcloud.user;
};
sops.secrets."nextcloud/user1/email" = {
owner = config.homelab.nextcloud.user;
};
*/
sops.age.keyFile = "/home/guz/.config/sops/age/keys.txt";
};
}

View File

@@ -0,0 +1,63 @@
{
config,
lib,
pkgs,
...
}: let
secrets = config.homelab-secrets.lesser;
deviceIp = config.services.tailscale.deviceIp;
in {
imports = [];
options = {};
config = {
services.adguardhome.enable = true;
services.adguardhome.dns.rewrites = {
"*.${secrets.homelab-domain}" = deviceIp;
"${secrets.homelab-domain}" = deviceIp;
};
services.adguardhome.settings.bind_port = secrets.services.adguard.port;
services.caddy.enable = true;
services.caddy.virtualHosts =
lib.attrsets.mapAttrs'
(name: service: {
name = service.domain;
value = {extraConfig = "reverse_proxy ${deviceIp}:${toString service.port}";};
})
secrets.services;
networking.firewall.allowedTCPPorts = [80 433];
services.forgejo = {
enable = true;
actions = {
enable = true;
token = secrets.services.forgejo.actions-token;
url = "http://${config.services.tailscale.deviceUrl}:${toString secrets.services.forgejo.port}";
};
users = {
user1 = {
name = /. + config.sops.secrets."forgejo/user1/name".path;
password = /. + config.sops.secrets."forgejo/user1/password".path;
email = /. + config.sops.secrets."forgejo/user1/email".path;
admin = true;
};
};
settings = {
server = {
ROOT_URL = "https://${secrets.services.forgejo.domain}";
DOMAIN = "${secrets.services.forgejo.domain}";
};
};
};
services.openssh.enable = true;
services.tailscale = {
enable = true;
useRoutingFeatures = "both";
exitNode = true;
tailnetName = secrets.tailnet-name;
deviceIp = secrets.device-ip;
};
};
}

View File

@@ -1,9 +0,0 @@
{ ... }:
{
imports = [
./guz.nix
];
options = { };
config = { };
}

View File

@@ -1,38 +0,0 @@
{ config, pkgs, inputs, ... }:
{
imports = [ ];
programs.zsh.enable = true;
set-user.users = [{
username = "guz";
shell = pkgs.zsh;
home = {
imports = [
../../../modules/home-manager/programs/lf.nix
../../../modules/home-manager/programs/starship.nix
../../../modules/home-manager/programs/tmux.nix
../../../modules/home-manager/programs/zsh.nix
../../../modules/home-manager/packages/nixi.nix
../../../modules/home-manager/packages/nixx.nix
];
lf.enable = true;
starship.enable = true;
starship.enableZsh = true;
tmux.enable = true;
tmux.shell = "\${pkgs.zsh}/bin/zsh";
zsh.enable = true;
zsh.extraConfig.init = ''
export GPG_TTY=$(tty)
alias tmux="tmux -f /home/guz/.config/tmux/tmux.conf"
'';
};
}];
}

View File

@@ -1,36 +0,0 @@
{ config, lib, pkgs, ... }:
let
cfg = config.client;
in
{
imports = [ ];
options.client = with lib; with lib.types; {
enable = mkEnableOption "";
name = mkOption {
type = str;
default = "client";
};
flakeDir = mkOption {
type = str;
};
domain = mkOption {
type = either str path;
default = "${cfg.name}.local";
};
localIp = mkOption {
type = nullOr str;
default = null;
};
ip = mkOption {
type = nullOr str;
default = cfg.localIp;
};
users = mkOption {
type = attrsOf (submodule { ... }: {
options = { };
});
};
};
config = lib.mkIf cfg.enable { };
}

View File

@@ -0,0 +1,8 @@
{...}: {
imports = [
./programs
./profiles
];
options = {};
config = {};
}

View File

@@ -0,0 +1,7 @@
{...}: {
imports = [
./gterminal.nix
];
options = {};
config = {};
}

View File

@@ -0,0 +1,165 @@
{
config,
lib,
pkgs,
...
}: let
cfg = config.profiles.gterminal;
in {
imports = [
../programs/wezterm.nix
../programs/neovim.nix
];
options.profiles.gterminal = with lib;
with lib.types; {
enable = mkEnableOption "";
};
config = with lib;
mkIf cfg.enable {
home.packages = with pkgs; [
git
lazygit
gcc # Added temporally so my neovim config doesn't break
wget
nixpkgs-fmt
nixpkgs-lint
];
programs = {
direnv.enable = true;
direnv.enableZshIntegration = true;
direnv.nix-direnv.enable = true;
lf.enable = true;
lf.commands = {
dragon-out = ''%${pkgs.xdragon}/bin/xdragon -a -x "$fx"'';
editor-open = ''$$EDITOR $f'';
mkfile = '' ''${{
printf "Dirname: "
read DIR
if [[ $DIR = */ ]]; then
mkdir $DIR
else
touch $DIR
fi
}}'';
};
lf.extraConfig = let
previewer = pkgs.writeShellScriptBin "pv.sh" ''
file=$1
w=$2
h=$3
x=$4
y=$5
if [[ "$(${pkgs.file}/bin/file -Lb --mime-type "$file")" =~ ^image ]]; then
${pkgs.kitty}/bin/kitty +kitten icat --silent --stdin no --transfer-mode file --place "''${w}x''${h}@''${x}x''${y}" "$file" < /dev/null > /dev/tty
exit 1
fi
${pkgs.pistol}/bin/pistol "$file"
'';
cleaner = pkgs.writeShellScriptBin "clean.sh" ''
${pkgs.kitty}/bin/kitty +kitten icat --clear --stdin no --silent --transfer-mode file < /dev/null > /dev/tty
'';
in
mkDefault ''
set cleaner ${cleaner}/bin/clean.sh
set previewer ${previewer}/bin/pv.sh
'';
neovim.enable = true;
starship.enable = true;
starship.enableZshIntegration = true;
tmux.baseIndex = 1;
tmux.enable = true;
tmux.extraConfig = ''
set -sg terminal-overrides ",*:RGB"
set -g renumber-windows on
bind -T prefix / split-window -v -c "#''''{pane_current_path}"
bind -T prefix \\ split-window -h -c "#''''{pane_current_path}"
'';
tmux.keyMode = "vi";
tmux.mouse = true;
tmux.prefix = "C-Space";
tmux.plugins = with pkgs; [
{
plugin = tmuxPlugins.catppuccin.overrideAttrs (_: {
src = fetchFromGitHub {
owner = "guz013";
repo = "frappuccino-tmux";
rev = "4255b0a769cc6f35e12595fe5a33273a247630aa";
sha256 = "0k8yprhx5cd8v1ddpcr0dkssspc17lq2a51qniwafkkzxi3kz3i5";
};
});
extraConfig = ''
set -g @catppuccin_window_left_separator ""
set -g @catppuccin_window_right_separator " "
set -g @catppuccin_window_middle_separator " "
set -g @catppuccin_window_number_position "right"
set -g @catppuccin_window_default_fill "number"
set -g @catppuccin_window_default_text "#W"
set -g @catppuccin_window_current_fill "number"
set -g @catppuccin_window_current_text "#W"
set -g @catppuccin_status_modules_right "application directory session"
set -g @catppuccin_status_left_separator " "
set -g @catppuccin_status_right_separator ""
set -g @catppuccin_status_right_separator_inverse "no"
set -g @catppuccin_status_fill "icon"
set -g @catppuccin_status_connect_separator "no"
set -g @catppuccin_directory_text "#{pane_current_path}"
'';
}
{
plugin = tmuxPlugins.better-mouse-mode;
extraConfig = "set-option -g mouse on";
}
{
plugin = tmuxPlugins.mkTmuxPlugin {
pluginName = "tmux.nvim";
version = "unstable-2024-04-05";
src = fetchFromGitHub {
owner = "aserowy";
repo = "tmux.nvim";
rev = "63e9c5e054099dd30af306bd8ceaa2f1086e1b07";
sha256 = "0ynzljwq6hv7415p7pr0aqx8kycp84p3p3dy4jcx61dxfgdpgc4c";
};
};
extraConfig = '''';
}
];
tmux.shell = "${pkgs.zsh}/bin/zsh";
tmux.terminal = "screen-256color";
wezterm = {
enable = mkDefault true;
config = {
default_prog = ["zsh" "--login"];
enable_wayland = false;
enable_tab_bar = false;
font = "lua wezterm.font(\"Fira Code\")";
font_size = 10;
};
enableZshIntegration = true;
};
zsh.enable = true;
zsh.enableAutosuggestions = true;
zsh.enableCompletion = true;
zsh.initExtra = ''
export GPG_TTY=$(tty)
alias tmux="tmux -f ${config.xdg.configHome}/tmux/tmux.conf";
'';
};
};
}

View File

@@ -1,11 +1,14 @@
{ config, lib, pkgs, ... }:
let
cfg = config.davinci;
in
{
imports = [ ];
options.davinci = with lib; with lib.types; {
config,
lib,
pkgs,
...
}: let
cfg = config.davinci;
in {
imports = [];
options.davinci = with lib;
with lib.types; {
enable = mkEnableOption "";
};
config = lib.mkIf cfg.enable {

View File

@@ -1,6 +1,9 @@
{ lib, config, pkgs, ... }:
let
{
lib,
config,
pkgs,
...
}: let
cfg = config.eww;
ewwDir = "${config.xdg.configHome}/eww";
@@ -9,7 +12,7 @@ let
jq="${pkgs.jq}/bin/jq"
socat="${pkgs.socat}/bin/socat"
$hyprctl monitors -j |
$hyprctl monitors -j |
$jq '.[] | select(.focused) | .activeWorkspace.id'
$socat -u UNIX-CONNECT:/tmp/hypr/$HYPRLAND_INSTANCE_SIGNATURE/.socket2.sock - |
@@ -57,14 +60,13 @@ let
echo $rain
fi
'';
in
{
imports = [ ];
options.eww = with lib; with lib.types; {
in {
imports = [];
options.eww = with lib;
with lib.types; {
enable = mkEnableOption "";
};
config = lib.mkIf cfg.enable {
home.packages = with pkgs; [
eww-wayland
];
@@ -73,7 +75,7 @@ in
home.file."${ewwDir}/eww.scss".source = ./eww.scss;
home.file."${ewwDir}/vars.yuck".text = ''
(deflisten active-workspace :initial "1"
(deflisten active-workspace :initial "1"
"${eww-get-active-workspace}/bin/eww-get-active-workspace")
(defpoll volume :interval "1s"
@@ -111,5 +113,3 @@ in
'';
};
}

View File

@@ -1,14 +1,18 @@
{ config, inputs, lib, pkgs, ... }:
let
cfg = config.hyprland;
in
{
imports = [ ];
options.hyprland = with lib; with lib.types; {
config,
inputs,
lib,
pkgs,
...
}: let
cfg = config.hyprland;
in {
imports = [];
options.hyprland = with lib;
with lib.types; {
enable = mkEnableOption "";
monitors = mkOption {
default = [ ];
default = [];
/*
type = listOf (submodule ({ ... }: {
options = {
@@ -38,15 +42,15 @@ in
*/
};
exec = mkOption {
default = [ ];
default = [];
type = listOf str;
};
env = mkOption {
default = { };
default = {};
type = attrsOf str;
};
windowRules = mkOption {
default = { };
default = {};
description = "window = [ \"rule\" ]";
type = attrsOf (listOf str);
};
@@ -119,13 +123,15 @@ in
};
};
workspaces = mkOption {
default = [ ];
/* type = {
name = str;
monitor = nullOr str;
default = nullOr bool;
extraRules = nullOr str;
}; */
default = [];
/*
type = {
name = str;
monitor = nullOr str;
default = nullOr bool;
extraRules = nullOr str;
};
*/
};
binds = {
mod = mkOption {
@@ -133,11 +139,11 @@ in
type = str;
};
keyboard = mkOption {
default = [ ];
default = [];
type = listOf str;
};
mouse = mkOption {
default = [ ];
default = [];
type = listOf str;
};
};
@@ -151,24 +157,36 @@ in
wayland.windowManager.hyprland.settings = lib.mkMerge [
# Sets monitor variables ("$name" = "id") so it can be used in rules later
(builtins.listToAttrs (map
(m: {
name = "\$${m.name}";
value = "${m.id}";
})
cfg.monitors)
(
builtins.listToAttrs (map
(m: {
name = "\$${m.name}";
value = "${m.id}";
})
cfg.monitors)
)
{
# Construct the "name,resolution@hz,offset,scale" strings
monitor = (map
(m:
"${m.name},${m.resolution}@${
toString (if m?hz then m.hz else 60)
},${
if m?offset then m.offset else "0x0"
},${
toString (if m?scale then m.scale else 1)
}"
monitor = (
map
(
m: "${m.name},${m.resolution}@${
toString (
if m ? hz
then m.hz
else 60
)
},${
if m ? offset
then m.offset
else "0x0"
},${
toString (
if m ? scale
then m.scale
else 1
)
}"
)
cfg.monitors
);
@@ -177,7 +195,8 @@ in
# "Hack" to transform attributes sets to lists (because I didn't know other way to do it)
# Transform { "envName" = "value" } to [ "envName,value" ]
env = builtins.attrValues
env =
builtins.attrValues
(builtins.mapAttrs (n: v: "${n},${v}") (lib.attrsets.mergeAttrsList [
{
"XCURSOR_SIZE" = "24";
@@ -186,44 +205,46 @@ in
cfg.env
]));
windowrulev2 =
let
firefoxPipRules = [
"float"
# "nofullscreenrequest"
"size 480 270"
"fakefullscreen"
"nodim"
"noblur"
];
in
windowrulev2 = let
firefoxPipRules = [
"float"
# "nofullscreenrequest"
"size 480 270"
"fakefullscreen"
"nodim"
"noblur"
];
in
builtins.concatLists
(builtins.attrValues (builtins.mapAttrs
(w: rs:
(map (r: "${r},${w}") rs)
)
(lib.attrsets.mergeAttrsList [
{
"title:^(Picture-in-Picture)$,class:^(firefox)$" = firefoxPipRules;
"title:^(Firefox)$,class:^(firefox)$" = firefoxPipRules;
"title:^(Picture-in-Picture)$" = firefoxPipRules;
"class:^(xwaylandvideobridge)$" = [
"opacity 0.0 override 0.0 override"
"noanim"
"nofocus"
"noinitialfocus"
];
}
cfg.windowRules
])
));
(builtins.attrValues (
builtins.mapAttrs
(
w: rs: (map (r: "${r},${w}") rs)
)
(lib.attrsets.mergeAttrsList [
{
"title:^(Picture-in-Picture)$,class:^(firefox)$" = firefoxPipRules;
"title:^(Firefox)$,class:^(firefox)$" = firefoxPipRules;
"title:^(Picture-in-Picture)$" = firefoxPipRules;
"class:^(xwaylandvideobridge)$" = [
"opacity 0.0 override 0.0 override"
"noanim"
"nofocus"
"noinitialfocus"
];
}
cfg.windowRules
])
));
input = {
kb_layout = cfg.input.keyboard.layout;
kb_variant = cfg.input.keyboard.variant;
follow_mouse = if cfg.input.mouse.follow then "1" else "0";
follow_mouse =
if cfg.input.mouse.follow
then "1"
else "0";
sensitivity = toString cfg.input.mouse.sensitivity;
};
@@ -240,7 +261,10 @@ in
decoration = {
rounding = toString cfg.decoration.rouding;
dim_inactive = if cfg.decoration.dim.inactive then "true" else "false";
dim_inactive =
if cfg.decoration.dim.inactive
then "true"
else "false";
dim_strength = toString cfg.decoration.dim.strength;
dim_around = toString cfg.decoration.dim.around;
@@ -251,7 +275,10 @@ in
};
animations = {
enabled = if cfg.animations.enabled then "yes" else "no";
enabled =
if cfg.animations.enabled
then "yes"
else "no";
bezier = "myBezier, 0.05, 0.9, 0.1, 1.05";
@@ -278,31 +305,29 @@ in
workspace_swipe = "off";
};
workspace =
(map
(w: "${w.name},${
if w?monitor then "monitor:${w.monitor}," else ""
}${
if w?default && w.default then "default:true," else ""
}${
if w?extraRules then "${w.extraRules}" else ""
}")
cfg.workspaces
);
workspace = (
map
(w: "${w.name},${
if w ? monitor
then "monitor:${w.monitor},"
else ""
}${
if w ? default && w.default
then "default:true,"
else ""
}${
if w ? extraRules
then "${w.extraRules}"
else ""
}")
cfg.workspaces
);
"$mod" = cfg.binds.mod;
bind = cfg.binds.keyboard;
bindm = cfg.binds.mouse;
}
];
};
}

View File

@@ -1,15 +1,18 @@
{ config, lib, pkgs, ... }:
let
cfg = config.krita;
in
{
imports = [ ];
options.krita = with lib; with lib.types; {
config,
lib,
pkgs,
...
}: let
cfg = config.krita;
in {
imports = [];
options.krita = with lib;
with lib.types; {
enable = mkEnableOption "";
};
config = lib.mkIf cfg.enable {
home.packages = with pkgs; [ krita ];
home.packages = with pkgs; [krita];
home.file."${config.xdg.configHome}/kritarc".source = ./kritarc;
home.file."${config.xdg.configHome}/kritashortcutsrc".source = ./kritashortcutsrc;

View File

@@ -0,0 +1,94 @@
{
config,
lib,
pkgs,
...
}: let
cfg = config.lf;
in {
imports = [];
options.lf = with lib;
with lib.types; {
enable = mkEnableOption "";
};
config = lib.mkIf cfg.enable {
programs.lf = {
enable = true;
settings = {
preview = true;
hidden = true;
drawbox = true;
icons = true;
ignorecase = true;
};
commands = {
delete-trash = '' ''${{
touch "${config.xdg.dataHome}/Trash/info/$(basename $f).trashinfo"
echo "[Trash Info]" > "${config.xdg.dataHome}/Trash/info/$(basename $f).trashinfo"
echo "Path=$f" >> "${config.xdg.dataHome}/Trash/info/$(basename $f).trashinfo"
echo "DeletionDate=$(date +%Y-%m-%dT%H:%M:%S)" >> "${config.xdg.dataHome}/Trash/info/$(basename $f).trashinfo"
mv $f ${config.xdg.dataHome}/Trash/files
}}'';
dragon-out = ''%${pkgs.xdragon}/bin/xdragon -a -x "$fx"'';
editor-open = ''$$EDITOR $f'';
mkfile = '' ''${{
printf "Dirname: "
read DIR
if [[ $DIR = */ ]]; then
mkdir $DIR
else
touch $DIR
fi
}}'';
mkdir = '' ''${{
printf "Dirname: "
read DIR
mkdir $DIR
}}'';
trash = ''cd ${config.xdg.dataHome}/Trash/files'';
trash-recover = '' ''${{
mv $f "$(cat "${config.xdg.dataHome}/Trash/info/$(basename $f).trashinfo" | sed -n '2 p' | tr "=" "\n" | sed -n '2 p')"
rm -rf "${config.xdg.dataHome}/Trash/info/$(basename $f).trashinfo"
}}'';
};
keybindings = {
"." = "set hidden!";
"<enter>" = "open";
a = "mkfile";
A = "mkdir";
D = "delete";
R = "trash-recover";
ee = "editor-open";
};
extraConfig = let
previewer = pkgs.writeShellScriptBin "pv.sh" ''
file=$1
w=$2
h=$3
x=$4
y=$5
if [[ "$(${pkgs.file}/bin/file -Lb --mime-type "$file")" =~ ^image ]]; then
${pkgs.kitty}/bin/kitty +kitten icat --silent --stdin no --transfer-mode file --place "''${w}x''${h}@''${x}x''${y}" "$file" < /dev/null > /dev/tty
exit 1
fi
${pkgs.pistol}/bin/pistol "$file"
'';
cleaner = pkgs.writeShellScriptBin "clean.sh" ''
${pkgs.kitty}/bin/kitty +kitten icat --clear --stdin no --silent --transfer-mode file < /dev/null > /dev/tty
'';
in ''
set cleaner ${cleaner}/bin/clean.sh
set previewer ${previewer}/bin/pv.sh
'';
};
};
}

View File

@@ -0,0 +1,437 @@
/*
THIS FILE ISN'T LICENSED UNDER THE WTFPL LICENSE.
This file is copied from Home-manager's GitHub and was modified to suit my
(Gustavo "Guz" L. de Mello) personal needs. The original file can be found
here: https://github.com/nix-community/home-manager/blob/master/modules/programs/firefox.nix
Said file is licensed under the MIT License, which a copy is written below:
MIT License
Copyright (c) 2017-2023 Home Manager contributors
Copyright (c) 2023-present Gustavo "Guz" L. de Mello
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
{
config,
lib,
pkgs,
...
}:
with lib; let
cfg = config.librewolf;
jsonFormat = pkgs.formats.json {};
firefoxConfigPath = ".librewolf";
profilesPath = firefoxConfigPath;
# The extensions path shared by all profiles; will not be supported
# by future Firefox versions.
extensionPath = "extensions/{ec8030f7-c20a-464f-9b0e-13a3a9e97384}";
profiles =
flip mapAttrs' cfg.profiles
(_: profile:
nameValuePair "Profile${toString profile.id}" {
Name = profile.name;
Path = profile.path;
IsRelative = 1;
Default =
if profile.isDefault
then 1
else 0;
})
// {
General = {StartWithLastProfile = 1;};
};
profilesIni = generators.toINI {} profiles;
userPrefValue = pref:
builtins.toJSON (
if isBool pref || isInt pref || isString pref
then pref
else builtins.toJSON pref
);
mkUserJs = prefs: extraPrefs: bookmarks: let
prefs' =
lib.optionalAttrs ([] != bookmarks)
{
"browser.bookmarks.file" = toString (firefoxBookmarksFile bookmarks);
"browser.places.importBookmarksHTML" = true;
}
// prefs;
in ''
// Generated by Home Manager.
${concatStrings (mapAttrsToList (name: value: ''
user_pref("${name}", ${userPrefValue value});
'')
prefs')}
${extraPrefs}
'';
mkContainersJson = containers: let
containerToIdentity = _: container: {
userContextId = container.id;
name = container.name;
icon = container.icon;
color = container.color;
public = true;
};
in ''
${builtins.toJSON {
version = 4;
lastUserContextId =
elemAt (mapAttrsToList (_: container: container.id) containers) 0;
identities = mapAttrsToList containerToIdentity containers;
}}
'';
firefoxBookmarksFile = bookmarks: let
indent = level:
lib.concatStringsSep "" (map (lib.const " ") (lib.range 1 level));
bookmarkToHTML = indentLevel: bookmark: ''
${indent indentLevel}<DT><A HREF="${
escapeXML bookmark.url
}" ADD_DATE="1" LAST_MODIFIED="1"${
lib.optionalString (bookmark.keyword != null)
" SHORTCUTURL=\"${escapeXML bookmark.keyword}\""
}${
lib.optionalString (bookmark.tags != [])
" TAGS=\"${escapeXML (concatStringsSep "," bookmark.tags)}\""
}>${escapeXML bookmark.name}</A>'';
directoryToHTML = indentLevel: directory: ''
${indent indentLevel}<DT>${
if directory.toolbar
then ''
<H3 ADD_DATE="1" LAST_MODIFIED="1" PERSONAL_TOOLBAR_FOLDER="true">Bookmarks Toolbar''
else ''<H3 ADD_DATE="1" LAST_MODIFIED="1">${escapeXML directory.name}''
}</H3>
${indent indentLevel}<DL><p>
${allItemsToHTML (indentLevel + 1) directory.bookmarks}
${indent indentLevel}</DL><p>'';
itemToHTMLOrRecurse = indentLevel: item:
if item ? "url"
then bookmarkToHTML indentLevel item
else directoryToHTML indentLevel item;
allItemsToHTML = indentLevel: bookmarks:
lib.concatStringsSep "\n"
(map (itemToHTMLOrRecurse indentLevel) bookmarks);
bookmarkEntries = allItemsToHTML 1 bookmarks;
in
pkgs.writeText "firefox-bookmarks.html" ''
<!DOCTYPE NETSCAPE-Bookmark-file-1>
<!-- This is an automatically generated file.
It will be read and overwritten.
DO NOT EDIT! -->
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">
<TITLE>Bookmarks</TITLE>
<H1>Bookmarks Menu</H1>
<DL><p>
${bookmarkEntries}
</DL>
'';
mkNoDuplicateAssertion = entities: entityKind: (
let
# Return an attribute set with entity IDs as keys and a list of
# entity names with corresponding ID as value. An ID is present in
# the result only if more than one entity has it. The argument
# entities is a list of AttrSet of one id/name pair.
findDuplicateIds = entities:
filterAttrs (_entityId: entityNames: length entityNames != 1)
(zipAttrs entities);
duplicates = findDuplicateIds (mapAttrsToList
(entityName: entity: {"${toString entity.id}" = entityName;})
entities);
mkMsg = entityId: entityNames:
" - ID ${entityId} is used by " + concatStringsSep ", " entityNames;
in {
assertion = duplicates == {};
message =
''
Must not have a Firefox ${entityKind} with an existing ID but
''
+ concatStringsSep "\n" (mapAttrsToList mkMsg duplicates);
}
);
in {
imports = [];
options.librewolf = with lib;
with lib.types; {
enable = mkEnableOption "Enable module";
overrides = mkOption {
default = {};
type = attrsOf (either bool (either int str));
};
profiles = mkOption {
type = attrsOf (submodule ({
config,
name,
...
}: {
options = {
name = mkOption {
type = str;
default = name;
};
id = mkOption {
type = ints.unsigned;
default = 0;
};
settings = mkOption {
type = attrsOf (jsonFormat.type
// {
description = "";
});
default = {};
};
extraConfig = mkOption {
type = lines;
default = "";
};
userChrome = mkOption {
type = lines;
default = "";
};
userContent = mkOption {
type = lines;
default = "";
};
bookmarks =
mkOption
{
type = let
bookmarkSubmodule =
submodule
({
config,
name,
...
}: {
options = {
name = mkOption {
type = str;
default = name;
};
tags = mkOption {
type = listOf str;
default = [];
};
keyword = mkOption {
type = nullOr str;
default = null;
};
url = mkOption {
type = str;
};
};
});
bookmarkType = addCheck bookmarkSubmodule (x: x ? "url");
directoryType = submodule ({
config,
name,
...
}: {
options = {
name = mkOption {
type = str;
default = name;
};
bookmarks = mkOption {
type = listOf nodeType;
default = [];
};
toolbar = mkOption {
type = bool;
default = false;
};
};
});
nodeType = either bookmarkType directoryType;
in
with types;
coercedTo (attrsOf nodeType) attrValues (listOf nodeType);
default = [];
};
path = mkOption {
type = str;
default = name;
};
isDefault = mkOption {
type = bool;
default = config.id == 0;
};
# For some reason Librewolf isn't working with the generated file, so
# a static one is passed as a temp fix.
search.file = mkOption {
type = path;
default = ./search.json.mozlz4;
};
/*
search = {
force = mkOption {
type = bool;
default = false;
};
default = mkOption {
type = nullOr str;
default = null;
};
privateDefault = mkOption {
type = nullOr str;
default = null;
};
order = mkOption {
type = uniq (listOf str);
default = [ ];
};
engines = mkOption {
type = attrsOf (attrsOf jsonFormat.type);
default = { };
};
};
*/
containers = mkOption {
type =
attrsOf
(submodule ({name, ...}: {
options = {
name = mkOption {
type = str;
default = name;
};
id = mkOption {
type = ints.unsigned;
default = 0;
};
color = mkOption {
type = enum [
"blue"
"turquoise"
"green"
"yellow"
"orange"
"red"
"pink"
"purple"
"toolbar"
];
default = "pink";
};
icon = mkOption {
type = enum [
"briefcase"
"cart"
"circle"
"dollar"
"fence"
"fingerprint"
"gift"
"vacation"
"food"
"fruit"
"pet"
"tree"
"chill"
];
default = "fruit";
};
};
}));
default = {};
};
extensions = mkOption {
type = listOf package;
default = [];
};
};
}));
default = {};
};
};
config = lib.mkIf cfg.enable {
programs.librewolf.enable = true;
programs.librewolf.settings = cfg.overrides;
home.file = mkMerge ([
{
"${firefoxConfigPath}/profiles.ini" = mkIf (cfg.profiles != {}) {
text = profilesIni;
force = true;
};
}
]
++ flip mapAttrsToList cfg.profiles (_: profile: {
"${profilesPath}/${profile.path}/.keep".text = "";
"${profilesPath}/${profile.path}/chrome/userChrome.css" =
mkIf (profile.userChrome != "") {text = profile.userChrome;};
"${profilesPath}/${profile.path}/chrome/userContent.css" =
mkIf (profile.userContent != "") {text = profile.userContent;};
"${profilesPath}/${profile.path}/user.js" =
mkIf
(profile.settings
!= {}
|| profile.extraConfig != ""
|| profile.bookmarks != [])
{
text =
mkUserJs profile.settings profile.extraConfig profile.bookmarks;
};
"${profilesPath}/${profile.path}/containers.json" = mkIf (profile.containers != {}) {
text = mkContainersJson profile.containers;
};
"${profilesPath}/${profile.path}/search.json.mozlz4" = {
force = true;
source = ./search.json.mozlz4; # Use static file, because for some reason this isn't working with Librewolf.
};
"${profilesPath}/${profile.path}/extensions" = mkIf (profile.extensions != []) {
source = let
extensionsEnvPkg = pkgs.buildEnv {
name = "hm-firefox-extensions";
paths = profile.extensions;
};
in "${extensionsEnvPkg}/share/mozilla/${extensionPath}";
recursive = true;
force = true;
};
}));
};
}

View File

@@ -1,11 +1,14 @@
{ config, lib, pkgs, ... }:
let
cfg = config.obs;
in
{
imports = [ ];
options.obs = with lib; with lib.types; {
config,
lib,
pkgs,
...
}: let
cfg = config.obs;
in {
imports = [];
options.obs = with lib;
with lib.types; {
enable = mkEnableOption "";
};
config = lib.mkIf cfg.enable {

View File

@@ -1,7 +1,9 @@
{ config, lib, pkgs, ... }:
let
{
config,
lib,
pkgs,
...
}: let
cfg = config.obsidian;
vaultCmd = pkgs.writeShellScriptBin "vault" ''
command="$1";
@@ -18,10 +20,10 @@ let
$git push
fi
'';
in
{
imports = [ ];
options.obsidian = with lib; with lib.types; {
in {
imports = [];
options.obsidian = with lib;
with lib.types; {
enable = mkEnableOption "";
vaultCmd = mkOption {
type = bool;
@@ -37,7 +39,11 @@ in
"md.obsidian.Obsidian"
];
home.packages = [
(if cfg.vaultCmd then vaultCmd else null)
(
if cfg.vaultCmd
then vaultCmd
else null
)
];
};
}

View File

@@ -1,11 +1,13 @@
{ config, lib, ... }:
let
cfg = config.starship;
in
{
imports = [ ];
options.starship = with lib; with lib.types; {
config,
lib,
...
}: let
cfg = config.starship;
in {
imports = [];
options.starship = with lib;
with lib.types; {
enable = mkEnableOption "Enable module";
enableZsh = mkEnableOption "Enable Zsh Integration";
enableBash = mkEnableOption "Enable Bash Integration";

View File

@@ -1,11 +1,15 @@
{ config, inputs, lib, pkgs, ... }:
let
cfg = config.tmux;
in
{
imports = [ ];
options.tmux = with lib; with lib.types; {
config,
inputs,
lib,
pkgs,
...
}: let
cfg = config.tmux;
in {
imports = [];
options.tmux = with lib;
with lib.types; {
enable = mkEnableOption "Enable Tmux module";
baseIndex = mkOption {
type = ints.unsigned;

View File

@@ -0,0 +1,84 @@
{
config,
lib,
...
}: let
cfg = config.wezterm;
in {
imports = [];
options.wezterm = with lib;
with lib.types; {
enable = mkEnableOption "Enable Wezterm";
integration = {
zsh = mkEnableOption "Enable Zsh Integration";
};
colorScheme = mkOption {
type = str;
default = "system";
};
defaultProg = mkOption {
default = [];
};
font = mkOption {
default = "Fira Code";
type = str;
};
fontSize = mkOption {
default = 12;
type = number;
};
};
config = lib.mkIf cfg.enable {
programs.wezterm.enable = true;
programs.wezterm.enableZshIntegration = lib.mkIf (cfg.integration.zsh) true;
programs.wezterm.extraConfig = ''
return {
enable_tab_bar = false;
color_scheme = "${cfg.colorScheme}",
default_prog = { ${lib.concatMapStrings (x: "'" + x + "',") cfg.defaultProg} },
font = wezterm.font("${cfg.font}"),
font_size = ${toString cfg.fontSize},
enable_wayland = false, -- TEMPORALLY FIX (see wez/wezterm#4483)
}
'';
programs.wezterm.colorSchemes = {
system = with config.colorScheme.palette; {
foreground = "#${base05}";
background = "#${base00}";
cursor_fg = "#${base01}";
cursor_bg = "#${config.theme.accent}";
cursor_border = "#${config.theme.accent}";
selection_fg = "#${base04}";
selection_bg = "#${config.theme.accent}";
split = "#${base04}";
ansi = [
"#${base03}"
"#${base08}"
"#${base0B}"
"#${base0A}"
"#${base0D}"
"#${base0E}"
"#${base0C}"
"#${base03}"
];
brights = [
"#${base03}"
"#${base08}"
"#${base0B}"
"#${base0A}"
"#${base0D}"
"#${base0E}"
"#${base0C}"
"#${base03}"
];
};
};
};
}

View File

@@ -1,11 +1,13 @@
{ config, lib, ... }:
let
cfg = config.zsh;
in
{
imports = [ ];
options.zsh = with lib; with lib.types; {
config,
lib,
...
}: let
cfg = config.zsh;
in {
imports = [];
options.zsh = with lib;
with lib.types; {
enable = mkEnableOption "Enable Zsh shell";
plugins = {
suggestions.enable = mkOption {
@@ -41,7 +43,7 @@ in
};
variables = mkOption {
type = attrsOf str;
default = { };
default = {};
};
};
config = lib.mkIf cfg.enable {

View File

@@ -0,0 +1,8 @@
{...}: {
imports = [
./neovim.nix
./wezterm.nix
];
options = {};
config = {};
}

View File

@@ -1,95 +0,0 @@
{ config, lib, pkgs, ... }:
let
cfg = config.lf;
in
{
imports = [ ];
options.lf = with lib; with lib.types; {
enable = mkEnableOption "";
};
config = lib.mkIf cfg.enable {
programs.lf = {
enable = true;
settings = {
preview = true;
hidden = true;
drawbox = true;
icons = true;
ignorecase = true;
};
commands = {
delete-trash = ''''${{
touch "${config.xdg.dataHome}/Trash/info/$(basename $f).trashinfo"
echo "[Trash Info]" > "${config.xdg.dataHome}/Trash/info/$(basename $f).trashinfo"
echo "Path=$f" >> "${config.xdg.dataHome}/Trash/info/$(basename $f).trashinfo"
echo "DeletionDate=$(date +%Y-%m-%dT%H:%M:%S)" >> "${config.xdg.dataHome}/Trash/info/$(basename $f).trashinfo"
mv $f ${config.xdg.dataHome}/Trash/files
}}'';
dragon-out = ''%${pkgs.xdragon}/bin/xdragon -a -x "$fx"'';
editor-open = ''$$EDITOR $f'';
mkfile = ''''${{
printf "Dirname: "
read DIR
if [[ $DIR = */ ]]; then
mkdir $DIR
else
touch $DIR
fi
}}'';
mkdir = ''''${{
printf "Dirname: "
read DIR
mkdir $DIR
}}'';
trash = ''cd ${config.xdg.dataHome}/Trash/files'';
trash-recover = ''''${{
mv $f "$(cat "${config.xdg.dataHome}/Trash/info/$(basename $f).trashinfo" | sed -n '2 p' | tr "=" "\n" | sed -n '2 p')"
rm -rf "${config.xdg.dataHome}/Trash/info/$(basename $f).trashinfo"
}}'';
};
keybindings = {
"." = "set hidden!";
"<enter>" = "open";
a = "mkfile";
A = "mkdir";
D = "delete";
R = "trash-recover";
ee = "editor-open";
};
extraConfig =
let
previewer = pkgs.writeShellScriptBin "pv.sh" ''
file=$1
w=$2
h=$3
x=$4
y=$5
if [[ "$(${pkgs.file}/bin/file -Lb --mime-type "$file")" =~ ^image ]]; then
${pkgs.kitty}/bin/kitty +kitten icat --silent --stdin no --transfer-mode file --place "''${w}x''${h}@''${x}x''${y}" "$file" < /dev/null > /dev/tty
exit 1
fi
${pkgs.pistol}/bin/pistol "$file"
'';
cleaner = pkgs.writeShellScriptBin "clean.sh" ''
${pkgs.kitty}/bin/kitty +kitten icat --clear --stdin no --silent --transfer-mode file < /dev/null > /dev/tty
'';
in
''
set cleaner ${cleaner}/bin/clean.sh
set previewer ${previewer}/bin/pv.sh
'';
};
};
}

View File

@@ -1,416 +0,0 @@
/*
THIS FILE ISN'T LICENSED UNDER THE WTFPL LICENSE.
This file is copied from Home-manager's GitHub and was modified to suit my
(Gustavo "Guz" L. de Mello) personal needs. The original file can be found
here: https://github.com/nix-community/home-manager/blob/master/modules/programs/firefox.nix
Said file is licensed under the MIT License, which a copy is written below:
MIT License
Copyright (c) 2017-2023 Home Manager contributors
Copyright (c) 2023-present Gustavo "Guz" L. de Mello
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.librewolf;
jsonFormat = pkgs.formats.json { };
firefoxConfigPath = ".librewolf";
profilesPath = firefoxConfigPath;
# The extensions path shared by all profiles; will not be supported
# by future Firefox versions.
extensionPath = "extensions/{ec8030f7-c20a-464f-9b0e-13a3a9e97384}";
profiles = flip mapAttrs' cfg.profiles
(_: profile:
nameValuePair "Profile${toString profile.id}" {
Name = profile.name;
Path = profile.path;
IsRelative = 1;
Default = if profile.isDefault then 1 else 0;
}) // {
General = { StartWithLastProfile = 1; };
};
profilesIni = generators.toINI { } profiles;
userPrefValue = pref:
builtins.toJSON (if isBool pref || isInt pref || isString pref then
pref
else
builtins.toJSON pref);
mkUserJs = prefs: extraPrefs: bookmarks:
let
prefs' = lib.optionalAttrs ([ ] != bookmarks)
{
"browser.bookmarks.file" = toString (firefoxBookmarksFile bookmarks);
"browser.places.importBookmarksHTML" = true;
} // prefs;
in
''
// Generated by Home Manager.
${concatStrings (mapAttrsToList (name: value: ''
user_pref("${name}", ${userPrefValue value});
'') prefs')}
${extraPrefs}
'';
mkContainersJson = containers:
let
containerToIdentity = _: container: {
userContextId = container.id;
name = container.name;
icon = container.icon;
color = container.color;
public = true;
};
in
''
${builtins.toJSON {
version = 4;
lastUserContextId =
elemAt (mapAttrsToList (_: container: container.id) containers) 0;
identities = mapAttrsToList containerToIdentity containers;
}}
'';
firefoxBookmarksFile = bookmarks:
let
indent = level:
lib.concatStringsSep "" (map (lib.const " ") (lib.range 1 level));
bookmarkToHTML = indentLevel: bookmark:
''
${indent indentLevel}<DT><A HREF="${
escapeXML bookmark.url
}" ADD_DATE="1" LAST_MODIFIED="1"${
lib.optionalString (bookmark.keyword != null)
" SHORTCUTURL=\"${escapeXML bookmark.keyword}\""
}${
lib.optionalString (bookmark.tags != [ ])
" TAGS=\"${escapeXML (concatStringsSep "," bookmark.tags)}\""
}>${escapeXML bookmark.name}</A>'';
directoryToHTML = indentLevel: directory: ''
${indent indentLevel}<DT>${
if directory.toolbar then
''
<H3 ADD_DATE="1" LAST_MODIFIED="1" PERSONAL_TOOLBAR_FOLDER="true">Bookmarks Toolbar''
else
''<H3 ADD_DATE="1" LAST_MODIFIED="1">${escapeXML directory.name}''
}</H3>
${indent indentLevel}<DL><p>
${allItemsToHTML (indentLevel + 1) directory.bookmarks}
${indent indentLevel}</DL><p>'';
itemToHTMLOrRecurse = indentLevel: item:
if item ? "url" then
bookmarkToHTML indentLevel item
else
directoryToHTML indentLevel item;
allItemsToHTML = indentLevel: bookmarks:
lib.concatStringsSep "\n"
(map (itemToHTMLOrRecurse indentLevel) bookmarks);
bookmarkEntries = allItemsToHTML 1 bookmarks;
in
pkgs.writeText "firefox-bookmarks.html" ''
<!DOCTYPE NETSCAPE-Bookmark-file-1>
<!-- This is an automatically generated file.
It will be read and overwritten.
DO NOT EDIT! -->
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">
<TITLE>Bookmarks</TITLE>
<H1>Bookmarks Menu</H1>
<DL><p>
${bookmarkEntries}
</DL>
'';
mkNoDuplicateAssertion = entities: entityKind:
(
let
# Return an attribute set with entity IDs as keys and a list of
# entity names with corresponding ID as value. An ID is present in
# the result only if more than one entity has it. The argument
# entities is a list of AttrSet of one id/name pair.
findDuplicateIds = entities:
filterAttrs (_entityId: entityNames: length entityNames != 1)
(zipAttrs entities);
duplicates = findDuplicateIds (mapAttrsToList
(entityName: entity: { "${toString entity.id}" = entityName; })
entities);
mkMsg = entityId: entityNames:
" - ID ${entityId} is used by " + concatStringsSep ", " entityNames;
in
{
assertion = duplicates == { };
message = ''
Must not have a Firefox ${entityKind} with an existing ID but
'' + concatStringsSep "\n" (mapAttrsToList mkMsg duplicates);
}
);
in
{
imports = [ ];
options.librewolf = with lib; with lib.types; {
enable = mkEnableOption "Enable module";
overrides = mkOption {
default = { };
type = attrsOf (either bool (either int str));
};
profiles = mkOption {
type = attrsOf (submodule ({ config, name, ... }: {
options = {
name = mkOption {
type = str;
default = name;
};
id = mkOption {
type = ints.unsigned;
default = 0;
};
settings = mkOption {
type = attrsOf (jsonFormat.type // {
description = "";
});
default = { };
};
extraConfig = mkOption {
type = lines;
default = "";
};
userChrome = mkOption {
type = lines;
default = "";
};
userContent = mkOption {
type = lines;
default = "";
};
bookmarks = mkOption
{
type =
let
bookmarkSubmodule = submodule
({ config, name, ... }: {
options = {
name = mkOption {
type = str;
default = name;
};
tags = mkOption {
type = listOf str;
default = [ ];
};
keyword = mkOption {
type = nullOr str;
default = null;
};
url = mkOption {
type = str;
};
};
});
bookmarkType = addCheck bookmarkSubmodule (x: x ? "url");
directoryType = submodule ({ config, name, ... }: {
options = {
name = mkOption {
type = str;
default = name;
};
bookmarks = mkOption {
type = listOf nodeType;
default = [ ];
};
toolbar = mkOption {
type = bool;
default = false;
};
};
});
nodeType = either bookmarkType directoryType;
in
with types;
coercedTo (attrsOf nodeType) attrValues (listOf nodeType);
default = [ ];
};
path = mkOption {
type = str;
default = name;
};
isDefault = mkOption {
type = bool;
default = config.id == 0;
};
# For some reason Librewolf isn't working with the generated file, so
# a static one is passed as a temp fix.
search.file = mkOption {
type = path;
default = ./search.json.mozlz4;
};
/*
search = {
force = mkOption {
type = bool;
default = false;
};
default = mkOption {
type = nullOr str;
default = null;
};
privateDefault = mkOption {
type = nullOr str;
default = null;
};
order = mkOption {
type = uniq (listOf str);
default = [ ];
};
engines = mkOption {
type = attrsOf (attrsOf jsonFormat.type);
default = { };
};
};
*/
containers = mkOption {
type = attrsOf
(submodule ({ name, ... }: {
options = {
name = mkOption {
type = str;
default = name;
};
id = mkOption {
type = ints.unsigned;
default = 0;
};
color = mkOption {
type = enum [
"blue"
"turquoise"
"green"
"yellow"
"orange"
"red"
"pink"
"purple"
"toolbar"
];
default = "pink";
};
icon = mkOption {
type = enum [
"briefcase"
"cart"
"circle"
"dollar"
"fence"
"fingerprint"
"gift"
"vacation"
"food"
"fruit"
"pet"
"tree"
"chill"
];
default = "fruit";
};
};
}));
default = { };
};
extensions = mkOption {
type = listOf package;
default = [ ];
};
};
}));
default = { };
};
};
config = lib.mkIf cfg.enable {
programs.librewolf.enable = true;
programs.librewolf.settings = cfg.overrides;
home.file = mkMerge ([{
"${firefoxConfigPath}/profiles.ini" =
mkIf (cfg.profiles != { }) { text = profilesIni; force = true; };
}] ++ flip mapAttrsToList cfg.profiles (_: profile: {
"${profilesPath}/${profile.path}/.keep".text = "";
"${profilesPath}/${profile.path}/chrome/userChrome.css" =
mkIf (profile.userChrome != "") { text = profile.userChrome; };
"${profilesPath}/${profile.path}/chrome/userContent.css" =
mkIf (profile.userContent != "") { text = profile.userContent; };
"${profilesPath}/${profile.path}/user.js" = mkIf
(profile.settings != { }
|| profile.extraConfig != "" || profile.bookmarks != [ ])
{
text =
mkUserJs profile.settings profile.extraConfig profile.bookmarks;
};
"${profilesPath}/${profile.path}/containers.json" =
mkIf (profile.containers != { }) {
text = mkContainersJson profile.containers;
};
"${profilesPath}/${profile.path}/search.json.mozlz4" = {
force = true;
source = ./search.json.mozlz4; # Use static file, because for some reason this isn't working with Librewolf.
};
"${profilesPath}/${profile.path}/extensions" =
mkIf (profile.extensions != [ ]) {
source =
let
extensionsEnvPkg = pkgs.buildEnv {
name = "hm-firefox-extensions";
paths = profile.extensions;
};
in
"${extensionsEnvPkg}/share/mozilla/${extensionPath}";
recursive = true;
force = true;
};
}));
};
}

View File

@@ -0,0 +1,30 @@
{
config,
lib,
pkgs,
...
}: let
cfg = config.programs.neovim;
in {
imports = [];
config = with lib;
mkIf cfg.enable {
programs.neovim = {
viAlias = true;
vimAlias = true;
withNodeJs = true;
defaultEditor = true;
};
home.sessionVariables = mkIf cfg.defaultEditor {
EDITOR = "nvim";
};
home.packages = with pkgs; [
git
lazygit
gcc
wget
];
};
}

View File

@@ -1,82 +1,73 @@
{ config, lib, ... }:
let
cfg = config.wezterm;
in
{
imports = [ ];
options.wezterm = with lib; with lib.types; {
enable = mkEnableOption "Enable Wezterm";
integration = {
zsh = mkEnableOption "Enable Zsh Integration";
};
colorScheme = mkOption {
type = str;
default = "system";
};
defaultProg = mkOption {
default = [ ];
};
font = mkOption {
default = "Fira Code";
type = str;
};
fontSize = mkOption {
default = 12;
type = number;
config,
lib,
pkgs,
...
}:
with builtins; let
cfg = config.programs.wezterm;
jsonFormat = pkgs.formats.json {};
toLua = with lib.strings;
v:
if isList v
then "{ ${concatMapStringsSep ", " (i: toLua i) v} }"
else if isAttrs v
then "\{ ${concatStringsSep ", " (attrValues (mapAttrs (n: a: "${n} = ${toLua a}") v))} \}"
else if isNull v
then "nil"
else if isBool v
then
if v
then "true"
else "false"
else if isInt v
then toString v
else if isString v && hasPrefix "lua " v
then "${substring 4 (stringLength v) v}"
else "\"${toString v}\"";
configInLua = pkgs.writeText "nih-wezterm-generated-config" ''
local wezterm = require("wezterm");
local nih_generated_config = {};
${concatStringsSep "\n" (attrValues (mapAttrs
(n: v: "nih_generated_config.${n} = ${toLua v};")
cfg.config))}
local function extra_config()
${cfg.extraConfig}
end
for k,v in pairs(extra_config()) do nih_generated_config[k] = v end
return nih_generated_config;
'';
prettyConfig = pkgs.runCommand "nih-wezterm-pretty-config" {config = configInLua;} ''
echo "Nih's Wezterm configuration file builder";
echo "input file: $config";
echo "output file: $out";
echo ""
echo "Formatting config file with Stylua"
cat $config | ${pkgs.stylua}/bin/stylua - > $out
echo ""
echo "Checking erros with luacheck"
${pkgs.luajitPackages.luacheck}/bin/luacheck \
--no-max-line-length \
--no-unused \
"$out";
'';
in {
imports = [];
options.programs.wezterm = with lib;
with lib.types; {
config = mkOption {
type = submodule ({...}: {
freeformType = jsonFormat.type;
});
default = {};
};
};
config = lib.mkIf cfg.enable {
programs.wezterm.enable = true;
programs.wezterm.enableZshIntegration = lib.mkIf (cfg.integration.zsh) true;
programs.wezterm.extraConfig = ''
return {
enable_tab_bar = false;
color_scheme = "${cfg.colorScheme}",
default_prog = { ${lib.concatMapStrings (x: "'" + x + "',") cfg.defaultProg} },
font = wezterm.font("${cfg.font}"),
font_size = ${toString cfg.fontSize},
enable_wayland = false, -- TEMPORALLY FIX (see wez/wezterm#4483)
}
'';
programs.wezterm.colorSchemes = {
system = with config.colorScheme.palette; {
foreground = "#${base05}";
background = "#${base00}";
cursor_fg = "#${base01}";
cursor_bg = "#${config.theme.accent}";
cursor_border = "#${config.theme.accent}";
selection_fg = "#${base04}";
selection_bg = "#${config.theme.accent}";
split = "#${base04}";
ansi = [
"#${base03}"
"#${base08}"
"#${base0B}"
"#${base0A}"
"#${base0D}"
"#${base0E}"
"#${base0C}"
"#${base03}"
];
brights = [
"#${base03}"
"#${base08}"
"#${base0B}"
"#${base0A}"
"#${base0D}"
"#${base0E}"
"#${base0C}"
"#${base03}"
];
};
config = with lib;
mkIf cfg.enable {
xdg.configFile."wezterm/wezterm.lua".source = prettyConfig;
};
};
}

View File

@@ -1,13 +1,17 @@
{ config, lib, inputs, pkgs, ... }:
let
cfg = config.theme;
in
{
config,
lib,
inputs,
pkgs,
...
}: let
cfg = config.theme;
in {
imports = [
inputs.nix-colors.homeManagerModules.default
];
options.theme = with lib; with lib.types; {
options.theme = with lib;
with lib.types; {
accent = mkOption {
type = str;
default = "cdd6f4";
@@ -36,8 +40,8 @@ in
name = "Catppuccin-Mocha-Compact-Mauve-Dark";
package = pkgs.catppuccin-gtk.override {
size = "compact";
tweaks = [ "rimless" "black" ];
accents = [ "mauve" ];
tweaks = ["rimless" "black"];
accents = ["mauve"];
variant = "mocha";
};
};

View File

@@ -1,17 +1,20 @@
{ config, lib, pkgs, ... }:
let
cfg = config.nih;
applyAttrNames = builtins.mapAttrs (name: f: f name);
in
{
config,
lib,
pkgs,
...
}: let
cfg = config.nih;
in {
imports = [
./sound.nix
./users.nix
./domains
./networking
./services
./sound.nix
./users.nix
];
options.nih = with lib; with lib.types; {
options.nih = with lib;
with lib.types; {
domain = mkOption {
type = str;
default = "${cfg.name}.local";
@@ -20,10 +23,6 @@ in
flakeDir = mkOption {
type = either str path;
};
handleDomains = mkOption {
type = bool;
default = true;
};
ip = mkOption {
type = str;
};
@@ -36,64 +35,40 @@ in
default = "nih";
};
type = mkOption {
type = enum [ "laptop" "desktop" "server" ];
type = enum ["laptop" "desktop" "server"];
default = "desktop";
};
_nih = mkOption {
type = attrsOf anything;
default = with builtins; {
servicesNamesList = readDir ./services;
};
};
};
config = with lib; mkIf cfg.enable {
boot = {
loader.systemd-boot.enable = mkDefault true;
loader.efi.canTouchEfiVariables = mkDefault true;
};
systemd.services."nih-setup" = with builtins; {
script = ''
echo ${builtins.toJSON cfg.users}
'';
wantedBy = [ "multi-user.target" ];
after = [ "forgejo.service" ];
serviceConfig = {
Type = "oneshot";
};
};
# Handle domains configuration
networking.firewall.allowedTCPPorts = mkIf cfg.handleDomains [ 80 433 ];
services.openssh.enable = mkDefault (if cfg.type == "server" then true else false);
systemd.services."tailscaled" = mkIf cfg.handleDomains {
serviceConfig = {
Environment = [ "TS_PERMIT_CERT_UID=caddy" ];
};
};
nih.services = mkIf cfg.handleDomains {
adguard = {
enable = true;
settings.dns.rewrites = (if hasPrefix "*." cfg.domain then {
"${cfg.domain}" = cfg.ip;
} else {
"${cfg.domain}" = cfg.ip;
"${"*." + cfg.domain}" = cfg.ip;
});
config = with lib;
mkIf cfg.enable {
boot = {
loader.systemd-boot.enable = mkDefault true;
loader.efi.canTouchEfiVariables = mkDefault true;
};
caddy =
let
nihServices = (filterAttrs (n: v: builtins.isAttrs v && v?domain) cfg.services);
in
mkIf cfg.handleDomains {
enable = true;
virtualHosts = mapAttrs'
(name: value: nameValuePair (value.domain) ({
extraConfig = ''
reverse_proxy ${cfg.localIp}:${toString value.port}
'';
}))
nihServices;
systemd.services."nih-setup" = with builtins; {
script = ''
echo ${builtins.toJSON cfg._nih.servicesNamesList}
'';
wantedBy = ["multi-user.target"];
after = ["forgejo.service"];
serviceConfig = {
Type = "oneshot";
};
};
# Handle domains configuration
services.openssh.enable = mkDefault (
if cfg.type == "server"
then true
else false
);
};
};
}

View File

@@ -0,0 +1,32 @@
{
config,
lib,
pkgs,
...
}: let
cfg = config.nih.domains;
in {
imports = [
./tailscale.nix
./tailscale-caddy.nix
];
options.nih.domains = with lib;
with lib.types; {
enable = mkOption {
type = bool;
default = false;
};
domain = mkOption {
type = str;
default = "${config.nih.name}.local";
};
handler = mkOption {
type = enum ["tailscale" "tailscale-caddy" "adguard" "adguard-caddy"];
default = "tailscale";
};
};
config = with lib;
mkIf cfg.enable {
networking.firewall.allowedTCPPorts = [80 433];
};
}

View File

@@ -0,0 +1,50 @@
{
config,
lib,
pkgs,
...
}:
with lib;
with builtins; let
tailnetName = config.services.tailscale.tailnetName;
ip = config.nih.ip;
domain = config.nih.domains.domain;
listHas = item: list: (lib.lists.count (x: x == item) list) > 0;
servicesList = filterAttrs (n: v: (listHas n ["adguardhome" "caddy" "tailscale"])) config.services;
servicesWithDomain = filterAttrs (n: v: isAttrs v && v ? nihDomain && v ? nihPort) servicesList;
in {
imports = [];
config = with lib;
mkIf (config.nih.domains.enable && config.nih.domains.handler == "tailscale-caddy") {
services.tailscale = {
enable = mkForce true;
useRoutingFeatures = mkForce "both";
};
services.adguardhome = {
enable = mkForce true;
dns.rewrites = {
"*.homelab.local" = "192.168.1.10";
"homelab.local" = "192.168.1.10";
};
};
services.caddy = {
enable = mkForce true;
virtualHosts."homelab.kiko-liberty.ts.net" = {
extraConfig = ''
reverse_proxy 192.168.1.10:4040
'';
};
/*
virtualHosts = mapAttrs'
(n: v: nameValuePair (v.nihDomain) ({
extraConfig = ''
reverse_proxy 100.66.139.89:${toString v.nihPort}
'';
}))
servicesWithDomain;
*/
};
};
}

View File

@@ -0,0 +1,24 @@
{
config,
lib,
pkgs,
...
}: let
cfg = config.nih.domains.handlers.tailscale;
in {
imports = [];
options.nih.domains.handlers.tailscale = with lib;
with lib.types; {
enable = mkOption {
type = bool;
default = config.nih.domains.enable && config.nih.domains.handler == "tailscale";
};
};
config = with lib;
mkIf cfg.enable {
services.tailscale = {
enable = mkDefault true;
useRoutingFeatures = mkDefault "server";
};
};
}

View File

@@ -1,10 +1,12 @@
{ config, lib, ... }:
let
cfg = config.nih.networking;
in
{
options.nih.networking = with lib; with lib.types; {
config,
lib,
...
}: let
cfg = config.nih.networking;
in {
options.nih.networking = with lib;
with lib.types; {
defaultGateway = mkOption {
type = str;
default = "192.168.1.1";
@@ -23,7 +25,7 @@ in
};
nameservers = mkOption {
type = listOf str;
default = [ "1.1.1.1" "8.8.8.8" ];
default = ["1.1.1.1" "8.8.8.8"];
};
networkmanager = mkOption {
type = bool;
@@ -35,22 +37,33 @@ in
};
wireless = mkOption {
type = bool;
default = if config.nih.type == "laptop" then true else false;
default =
if config.nih.type == "laptop"
then true
else false;
};
};
config = with lib; {
boot.kernel.sysctl."net.ipv4.ip_forward" = if cfg.portForwarding then 1 else 0;
boot.kernel.sysctl."net.ipv6.conf.all.forwarding" = if cfg.portForwarding then 1 else 0;
boot.kernel.sysctl."net.ipv4.ip_forward" =
if cfg.portForwarding
then 1
else 0;
boot.kernel.sysctl."net.ipv6.conf.all.forwarding" =
if cfg.portForwarding
then 1
else 0;
networking = {
hostName = cfg.hostName;
defaultGateway = cfg.defaultGateway;
dhcpcd.enable = true;
interfaces = mkIf (cfg.interface != null) {
"${cfg.interface}".ipv4.addresses = [{
address = cfg.localIp;
prefixLength = 28;
}];
"${cfg.interface}".ipv4.addresses = [
{
address = cfg.localIp;
prefixLength = 28;
}
];
};
nameservers = cfg.nameservers;
networkmanager.enable = cfg.networkmanager;

View File

@@ -1,9 +1,11 @@
{ config, lib, pkgs, ... }:
let
cfg = config.programs;
in
{
config,
lib,
pkgs,
...
}: let
cfg = config.programs;
in {
imports = [
./direnv.nix
./hyprland.nix
@@ -13,6 +15,6 @@ in
./wezterm.nix
./zsh.nix
];
options.programs = { };
config = { };
options.programs = {};
config = {};
}

View File

@@ -1,19 +1,22 @@
{ config, lib, pkgs, ... }:
let
cfg = config.programs.direnv;
in
{
imports = [ ];
options.programs.direnv = with lib; with lib.types; { };
config = with lib; mkIf cfg.enable {
programs.direnv = {
enableBashIntegration = mkDefault config.programs.bash.enable;
enableFishIntegration = mkDefault config.programs.fish.enable;
enableNushellIntegration = mkDefault config.programs.nushell.enable;
enableZshIntegration = mkDefault config.programs.zsh.enable;
config,
lib,
pkgs,
...
}: let
cfg = config.programs.direnv;
in {
imports = [];
options.programs.direnv = with lib; with lib.types; {};
config = with lib;
mkIf cfg.enable {
programs.direnv = {
enableBashIntegration = mkDefault config.programs.bash.enable;
enableFishIntegration = mkDefault config.programs.fish.enable;
enableNushellIntegration = mkDefault config.programs.nushell.enable;
enableZshIntegration = mkDefault config.programs.zsh.enable;
nix-direnv.enable = true;
nix-direnv.enable = true;
};
};
};
}

View File

@@ -1,15 +1,19 @@
{ config, inputs, lib, pkgs, ... }:
let
cfg = config.programs.hyprland;
in
{
imports = [ ];
options.programs.hyprland = with lib; with lib.types; {
config,
inputs,
lib,
pkgs,
...
}: let
cfg = config.programs.hyprland;
in {
imports = [];
options.programs.hyprland = with lib;
with lib.types; {
enable = mkEnableOption "";
monitors = mkOption {
default = [ ];
type = listOf (submodule ({ ... }: {
default = [];
type = listOf (submodule ({...}: {
options = {
id = mkOption {
type = str;
@@ -36,15 +40,15 @@ in
}));
};
exec = mkOption {
default = [ ];
default = [];
type = listOf str;
};
env = mkOption {
default = { };
default = {};
type = attrsOf str;
};
windowRules = mkOption {
default = { };
default = {};
description = "window = [ \"rule\" ]";
type = attrsOf (listOf str);
};
@@ -117,9 +121,10 @@ in
};
};
workspaces = mkOption {
default = [ ];
type = listOf
(submodule ({ ... }: {
default = [];
type =
listOf
(submodule ({...}: {
options = {
name = mkOption {
type = str;
@@ -145,11 +150,11 @@ in
type = str;
};
keyboard = mkOption {
default = [ ];
default = [];
type = listOf str;
};
mouse = mkOption {
default = [ ];
default = [];
type = listOf str;
};
};
@@ -163,16 +168,18 @@ in
wayland.windowManager.hyprland.settings = lib.mkMerge [
# Sets monitor variables ("$name" = "id") so it can be used in rules later
(builtins.listToAttrs (map
(m: {
name = "\$${m.name}";
value = "${m.id}";
})
cfg.monitors)
(
builtins.listToAttrs (map
(m: {
name = "\$${m.name}";
value = "${m.id}";
})
cfg.monitors)
)
{
# Construct the "name,resolution@hz,offset,scale" strings
monitor = (map
monitor = (
map
(m: "${m.name},${m.resolution}@${toString m.hz},${m.offset},${toString m.scale}")
cfg.monitors
);
@@ -181,7 +188,8 @@ in
# "Hack" to transform attributes sets to lists (because I didn't know other way to do it)
# Transform { "envName" = "value" } to [ "envName,value" ]
env = builtins.attrValues
env =
builtins.attrValues
(builtins.mapAttrs (n: v: "${n},${v}") (lib.attrsets.mergeAttrsList [
{
"XCURSOR_SIZE" = "24";
@@ -190,44 +198,46 @@ in
cfg.env
]));
windowrulev2 =
let
firefoxPipRules = [
"float"
# "nofullscreenrequest"
"size 480 270"
"fakefullscreen"
"nodim"
"noblur"
];
in
windowrulev2 = let
firefoxPipRules = [
"float"
# "nofullscreenrequest"
"size 480 270"
"fakefullscreen"
"nodim"
"noblur"
];
in
builtins.concatLists
(builtins.attrValues (builtins.mapAttrs
(w: rs:
(map (r: "${r},${w}") rs)
)
(lib.attrsets.mergeAttrsList [
{
"title:^(Picture-in-Picture)$,class:^(firefox)$" = firefoxPipRules;
"title:^(Firefox)$,class:^(firefox)$" = firefoxPipRules;
"title:^(Picture-in-Picture)$" = firefoxPipRules;
"class:^(xwaylandvideobridge)$" = [
"opacity 0.0 override 0.0 override"
"noanim"
"nofocus"
"noinitialfocus"
];
}
cfg.windowRules
])
));
(builtins.attrValues (
builtins.mapAttrs
(
w: rs: (map (r: "${r},${w}") rs)
)
(lib.attrsets.mergeAttrsList [
{
"title:^(Picture-in-Picture)$,class:^(firefox)$" = firefoxPipRules;
"title:^(Firefox)$,class:^(firefox)$" = firefoxPipRules;
"title:^(Picture-in-Picture)$" = firefoxPipRules;
"class:^(xwaylandvideobridge)$" = [
"opacity 0.0 override 0.0 override"
"noanim"
"nofocus"
"noinitialfocus"
];
}
cfg.windowRules
])
));
input = {
kb_layout = cfg.input.keyboard.layout;
kb_variant = cfg.input.keyboard.variant;
follow_mouse = if cfg.input.mouse.follow then "1" else "0";
follow_mouse =
if cfg.input.mouse.follow
then "1"
else "0";
sensitivity = toString cfg.input.mouse.sensitivity;
};
@@ -244,7 +254,10 @@ in
decoration = {
rounding = toString cfg.decoration.rouding;
dim_inactive = if cfg.decoration.dim.inactive then "true" else "false";
dim_inactive =
if cfg.decoration.dim.inactive
then "true"
else "false";
dim_strength = toString cfg.decoration.dim.strength;
dim_around = toString cfg.decoration.dim.around;
@@ -255,7 +268,10 @@ in
};
animations = {
enabled = if cfg.animations.enabled then "yes" else "no";
enabled =
if cfg.animations.enabled
then "yes"
else "no";
bezier = "myBezier, 0.05, 0.9, 0.1, 1.05";
@@ -282,32 +298,26 @@ in
workspace_swipe = "off";
};
workspace =
(map
(w: "${w.name},${
if w.monitor != null then "monitor:${w.monitor}," else ""
}${
if w.default then "default:true," else ""
}${w.extraRules} ")
cfg.workspaces
);
workspace = (
map
(w: "${w.name},${
if w.monitor != null
then "monitor:${w.monitor},"
else ""
}${
if w.default
then "default:true,"
else ""
}${w.extraRules} ")
cfg.workspaces
);
"$
mod " = cfg.binds.mod;
bind = cfg.binds.keyboard;
bindm = cfg.binds.mouse;
}
];
};
}

View File

@@ -1,11 +1,14 @@
{ config, lib, pkgs, ... }:
let
cfg = config.programs.lf;
in
{
imports = [ ];
options.programs.lf = with lib; with lib.types; {
config,
lib,
pkgs,
...
}: let
cfg = config.programs.lf;
in {
imports = [];
options.programs.lf = with lib;
with lib.types; {
cmds = {
mkfile = mkOption {
type = bool;
@@ -29,25 +32,25 @@ in
default = true;
};
};
config = with lib; mkIf cfg.enable {
programs.lf = {
commands = {
dragon-out = mkIf cfg.cmds.dragon-out ''%${pkgs.xdragon}/bin/xdragon -a -x "$fx"'';
editor-open = mkIf cfg.cmds.editor-open ''$$EDITOR $f'';
mkfile = mkIf cfg.cmds.mkfile ''''${{
printf "Dirname: "
read DIR
config = with lib;
mkIf cfg.enable {
programs.lf = {
commands = {
dragon-out = mkIf cfg.cmds.dragon-out ''%${pkgs.xdragon}/bin/xdragon -a -x "$fx"'';
editor-open = mkIf cfg.cmds.editor-open ''$$EDITOR $f'';
mkfile = mkIf cfg.cmds.mkfile '' ''${{
printf "Dirname: "
read DIR
if [[ $DIR = */ ]]; then
mkdir $DIR
else
touch $DIR
fi
}}'';
};
if [[ $DIR = */ ]]; then
mkdir $DIR
else
touch $DIR
fi
}}'';
};
extraConfig =
let
extraConfig = let
previewer = pkgs.writeShellScriptBin "pv.sh" ''
file=$1
w=$2
@@ -66,14 +69,18 @@ in
${pkgs.kitty}/bin/kitty +kitten icat --clear --stdin no --silent --transfer-mode file < /dev/null > /dev/tty
'';
in
mkDefault ''
${if cfg.filePreviewer then ''
set cleaner ${cleaner}/bin/clean.sh
set previewer ${previewer}/bin/pv.sh
'' else ""}
mkDefault ''
${
if cfg.filePreviewer
then ''
set cleaner ${cleaner}/bin/clean.sh
set previewer ${previewer}/bin/pv.sh
''
else ""
}
${cfg.extraCfg}
'';
${cfg.extraCfg}
'';
};
};
};
}

View File

@@ -1,17 +1,20 @@
{ config, lib, pkgs, ... }:
let
cfg = config.programs.starship;
in
{
imports = [ ];
options.programs.starship = with lib; with lib.types; { };
config = with lib; mkIf cfg.enable {
programs.starship = {
enableFishIntegration = mkDefault config.programs.fish.enable;
enableIonIntegration = mkDefault config.programs.ion.enable;
enableNushellIntegration = mkDefault config.programs.nushell.enable;
enableZshIntegration = mkDefault config.programs.zsh.enable;
config,
lib,
pkgs,
...
}: let
cfg = config.programs.starship;
in {
imports = [];
options.programs.starship = with lib; with lib.types; {};
config = with lib;
mkIf cfg.enable {
programs.starship = {
enableFishIntegration = mkDefault config.programs.fish.enable;
enableIonIntegration = mkDefault config.programs.ion.enable;
enableNushellIntegration = mkDefault config.programs.nushell.enable;
enableZshIntegration = mkDefault config.programs.zsh.enable;
};
};
};
}

View File

@@ -1,11 +1,12 @@
{ config, lib, pkgs, ... }:
let
cfg = config.programs.tmux;
in
{
imports = [ ];
options.programs.tmux = with lib; with lib.types; { };
config = with lib; mkIf cfg.enable { };
config,
lib,
pkgs,
...
}: let
cfg = config.programs.tmux;
in {
imports = [];
options.programs.tmux = with lib; with lib.types; {};
config = with lib; mkIf cfg.enable {};
}

View File

@@ -1,42 +1,47 @@
{ config, lib, pkgs, ... }:
with builtins;
let
{
config,
lib,
pkgs,
...
}:
with builtins; let
cfg = config.programs.wezterm;
jsonFormat = pkgs.formats.json { };
toLua = with lib.strings; v:
if isList v then
"{ ${concatMapStringsSep ", " (i: toLua i) v} }"
else if isAttrs v then
"\{ ${concatStringsSep ", " (attrValues (mapAttrs (n: a: "${n} = ${toLua a}") v))} \}"
else if isNull v then
"nil"
else if isBool v then
if v then "true" else "false"
else if isInt v then
toString v
else if isString v && hasPrefix "lua " v then
"${substring 4 (stringLength v) v}"
else
"\"${toString v}\"";
configInLua =
pkgs.writeText "nih-wezterm-generated-config" ''
local wezterm = require("wezterm");
jsonFormat = pkgs.formats.json {};
toLua = with lib.strings;
v:
if isList v
then "{ ${concatMapStringsSep ", " (i: toLua i) v} }"
else if isAttrs v
then "\{ ${concatStringsSep ", " (attrValues (mapAttrs (n: a: "${n} = ${toLua a}") v))} \}"
else if isNull v
then "nil"
else if isBool v
then
if v
then "true"
else "false"
else if isInt v
then toString v
else if isString v && hasPrefix "lua " v
then "${substring 4 (stringLength v) v}"
else "\"${toString v}\"";
configInLua = pkgs.writeText "nih-wezterm-generated-config" ''
local wezterm = require("wezterm");
local nih_generated_config = {};
${concatStringsSep "\n" (attrValues (mapAttrs
(n: v: "nih_generated_config.${n} = ${toLua v};")
cfg.config))}
local nih_generated_config = {};
${concatStringsSep "\n" (attrValues (mapAttrs
(n: v: "nih_generated_config.${n} = ${toLua v};")
cfg.config))}
local function extra_config()
${cfg.extraConfig}
end
local function extra_config()
${cfg.extraConfig}
end
for k,v in pairs(extra_config()) do nih_generated_config[k] = v end
for k,v in pairs(extra_config()) do nih_generated_config[k] = v end
return nih_generated_config;
'';
prettyConfig = pkgs.runCommand "nih-wezterm-pretty-config" { config = configInLua; } ''
return nih_generated_config;
'';
prettyConfig = pkgs.runCommand "nih-wezterm-pretty-config" {config = configInLua;} ''
echo "Nih's Wezterm configuration file builder";
echo "input file: $config";
echo "output file: $out";
@@ -50,27 +55,24 @@ let
--no-unused \
"$out";
'';
in
{
imports = [ ];
options.programs.wezterm = with lib; with lib.types; {
in {
imports = [];
options.programs.wezterm = with lib;
with lib.types; {
config = mkOption {
type = submodule ({ ... }: {
type = submodule ({...}: {
freeformType = jsonFormat.type;
});
default = { };
default = {};
};
};
config = with lib; mkIf cfg.enable {
programs.wezterm = {
enableBashIntegration = mkDefault config.programs.bash.enable;
enableZshIntegration = mkDefault config.programs.zsh.enable;
};
config = with lib;
mkIf cfg.enable {
programs.wezterm = {
enableBashIntegration = mkDefault config.programs.bash.enable;
enableZshIntegration = mkDefault config.programs.zsh.enable;
};
xdg.configFile."wezterm/wezterm.lua".source = prettyConfig;
};
xdg.configFile."wezterm/wezterm.lua".source = prettyConfig;
};
}

View File

@@ -1,10 +1,12 @@
{ config, lib, pkgs, ... }:
let
cfg = config.programs.zsh;
in
{
imports = [ ];
options.programs.zsh = with lib; with lib.types; { };
config = with lib; mkIf cfg.enable { };
config,
lib,
pkgs,
...
}: let
cfg = config.programs.zsh;
in {
imports = [];
options.programs.zsh = with lib; with lib.types; {};
config = with lib; mkIf cfg.enable {};
}

View File

@@ -1,90 +0,0 @@
{ config, lib, ... }:
let
cfg = config.nih.services.adguard;
in
{
imports = [ ];
options.nih.services.adguard = with lib; with lib.types; {
enable = mkEnableOption "";
extraArgs = mkOption {
type = listOf str;
default = [ ];
};
domain = mkOption {
type = str;
default = "adguard." + config.nih.domain;
};
port = mkOption {
type = port;
default = 3010;
};
settings = {
server.domain = mkOption {
type = str;
default = cfg.domain;
};
server.port = mkOption {
type = port;
default = cfg.port;
};
server.address = mkOption {
type = str;
default = "0.0.0.0";
};
dns.rewrites = mkOption {
type = attrsOf str;
default = { };
};
dns.filters = mkOption {
type = attrsOf (submodule ({ lib, ... }: {
options = {
name = mkOption {
type = nullOr str;
default = null;
};
url = mkOption {
type = str;
};
enabled = {
type = bool;
default = true;
};
};
}));
default = { };
};
};
};
config = lib.mkIf cfg.enable {
networking.firewall = {
allowedTCPPorts = [ 53 ];
allowedUDPPorts = [ 53 51820 ];
};
services.adguardhome = with builtins; {
enable = true;
settings = {
bind_port = cfg.settings.server.port;
bind_host = cfg.settings.server.address;
http = {
address = "${cfg.settings.server.address}:${toString cfg.settings.server.port}";
};
dns.rewrites = (builtins.attrValues (builtins.mapAttrs
(from: to: {
domain = from;
answer = to;
})
cfg.settings.dns.rewrites));
filters = (attrValues (mapAttrs
(id: list: {
name = if isNull list.name then id else list.name;
ID = id;
url = list.url;
enabled = list.enabled;
})
cfg.settings.dns.filters));
};
};
};
}

View File

@@ -0,0 +1,75 @@
{
config,
lib,
...
}: let
cfg = config.services.adguardhome;
in {
imports = [];
options.services.adguardhome = with lib;
with lib.types; {
nihDomain = mkOption {
type = str;
default = "adguard.${config.nih.domains.domain}";
};
nihPort = mkOption {
type = port;
default = 3053;
};
dns.filters = mkOption {
type = attrsOf (submodule ({lib, ...}: {
options = {
name = mkOption {
type = nullOr str;
default = null;
};
url = mkOption {
type = str;
};
enabled = {
type = bool;
default = true;
};
};
}));
default = {};
};
dns.rewrites = mkOption {
type = attrsOf str;
default = {};
};
};
config = with lib;
mkIf cfg.enable {
networking.firewall = {
allowedTCPPorts = [53];
allowedUDPPorts = [53 51820];
};
services.adguardhome = {
settings = {
bind_port = mkForce cfg.nihPort;
http = {
address = "${cfg.settings.bind_host}:${toString cfg.settings.bind_port}";
};
dns.rewrites = builtins.attrValues (builtins.mapAttrs
(from: to: {
domain = from;
answer = to;
})
cfg.dns.rewrites);
filters = attrValues (mapAttrs
(id: list: {
name =
if isNull list.name
then id
else list.name;
ID = id;
url = list.url;
enabled = list.enabled;
})
cfg.dns.filters);
};
};
};
}

View File

@@ -1,28 +1,11 @@
{ config, lib, pkgs, ... }:
let
cfg = config.nih.services.caddy;
in
{
imports = [ ];
options.nih.services.caddy = with lib; with lib.types; {
enable = mkEnableOption "";
virtualHosts = mkOption {
type = attrsOf (submodule ({ config, lib, ... }: {
options = {
extraConfig = mkOption {
type = lines;
default = "";
};
};
}));
default = { };
};
};
config = with lib; mkIf cfg.enable {
services.caddy = {
enable = true;
virtualHosts = cfg.virtualHosts;
};
};
config,
lib,
...
}: let
cfg = config.server.caddy;
in {
imports = [];
options.server.caddy = with lib; with lib.types; {};
config = lib.mkIf cfg.enable {};
}

View File

@@ -1,12 +1,16 @@
{ config, lib, pkgs, ... }:
{
config,
lib,
pkgs,
...
}: let
cfg = config.services;
in {
imports = [
./adguard.nix
./adguardhome.nix
./caddy.nix
./forgejo.nix
./tailscale.nix
];
options.nih.services = { };
config = { };
options.services = {};
config = {};
}

View File

@@ -1,301 +0,0 @@
{ config, lib, pkgs, ... }:
let
cfg = config.nih.services.forgejo;
users = (builtins.attrValues (builtins.mapAttrs
(username: info: {
name = if isNull info.name then username else info.name;
email = info.email;
password = info.password;
admin = info.admin;
})
cfg.settings.users));
in
{
imports = [ ];
options.nih.services.forgejo = with lib; with lib.types; {
enable = mkEnableOption "";
user = mkOption {
type = str;
default = "git";
};
package = mkOption {
type = package;
default = pkgs.forgejo;
};
cliAlias = mkOption {
type = bool;
default = true;
};
domain = mkOption {
type = str;
default = "forgejo." + config.nih.domain;
};
port = mkOption {
type = port;
default = 3020;
};
data = {
root = mkOption {
type = path;
default = config.server.storage + /forgejo;
};
};
handleUndeclaredUsers = mkOption {
type = bool;
default = false;
};
settings = {
users = mkOption {
type = attrsOf (submodule ({ config, lib, ... }: with lib; with lib.types; {
options = {
name = mkOption {
type = nullOr (either str path);
default = null;
};
password = mkOption {
type = either str path;
};
email = mkOption {
type = either str path;
};
admin = mkOption {
type = bool;
default = false;
};
};
}));
default = { };
};
name = mkOption {
type = str;
default = "Forgejo: Beyond coding. We forge";
};
prod = mkOption {
type = bool;
default = true;
};
repo.defaultUnits = mkOption {
type = listOf (enum [
"repo.code"
"repo.releases"
"repo.issues"
"repo.pulls"
"repo.wiki"
"repo.projects"
"repo.packages"
"repo.actions"
]);
default = [
"repo.code"
"repo.issues"
"repo.pulls"
];
};
repo.disabledUnits = mkOption {
type = listOf (enum [
"repo.issues"
"repo.ext_issues"
"repo.pulls"
"repo.wiki"
"repo.ext_wiki"
"repo.projects"
"repo.packages"
"repo.actions"
]);
default = [ ];
};
repo.pushCreate = mkOption {
type = bool;
default = false;
};
cors.enable = mkOption {
type = bool;
default = false;
};
cors.domains = mkOption {
type = listOf str;
default = [ ];
};
cors.methods = mkOption {
type = listOf str;
default = [ ];
};
ui.defaultTheme = mkOption {
type = str;
default = "forgejo-auto";
};
ui.themes = mkOption {
type = listOf str;
default = [
"forgejo-auto"
"forgejo-light"
"forgejo-dark"
"auto"
"gitea"
"arc-green"
];
};
server.protocol = mkOption {
type = enum [ "http" "https" "fcgi" "http+unix" "fcgi+unix" ];
default = "http";
};
server.domain = mkOption {
type = str;
default = cfg.domain;
};
server.port = mkOption {
type = port;
default = cfg.port;
};
server.address = mkOption {
type = either str path;
default = if hasSuffix "+unix" cfg.settings.server.protocol then "/run/forgejo/forgejo.sock" else "0.0.0.0";
};
server.url = mkOption {
type = str;
default = "http://${cfg.settings.server.domain}:${toString cfg.settings.server.port}";
};
server.offline = mkOption {
type = bool;
default = false;
};
server.compression = mkOption {
type = bool;
default = false;
};
server.landingPage = mkOption {
type = enum [ "home" "explore" "organizations" "login" str ];
default = "home";
};
service.registration = mkOption {
type = bool;
default = false;
};
security.allowBypassGiteaEnv = mkOption {
type = bool;
default = false;
};
};
};
config = lib.mkIf cfg.enable {
users.users."${cfg.user}" = {
home = cfg.data.root;
useDefaultShell = true;
group = cfg.user;
isSystemUser = true;
initialPassword = "1313";
};
users.groups."${cfg.user}" = { };
services.forgejo = {
enable = true;
package = cfg.package;
user = cfg.user;
group = cfg.user;
stateDir = toString cfg.data.root;
useWizard = false;
database = {
user = cfg.user;
type = "sqlite3";
};
settings = with builtins; {
DEFAULT = {
APP_NAME = cfg.settings.name;
RUN_MODE = if cfg.settings.prod then "prod" else "dev";
};
repository = {
DISABLED_REPO_UNITS = concatStringsSep "," cfg.settings.repo.disabledUnits;
DEFAULT_REPO_UNITS = concatStringsSep "," cfg.settings.repo.defaultUnits;
ENABLE_PUSH_CREATE_USER = cfg.settings.repo.pushCreate;
ENABLE_PUSH_CREATE_ORG = cfg.settings.repo.pushCreate;
};
cors = {
ENABLED = cfg.settings.cors.enable;
ALLOW_DOMAIN = concatStringsSep "," cfg.settings.cors.domains;
METHODS = concatStringsSep "," cfg.settings.cors.methods;
};
ui = {
DEFAULT_THEME = cfg.settings.ui.defaultTheme;
THEMES = concatStringsSep "," cfg.settings.ui.themes;
};
server = {
PROTOCOL = cfg.settings.server.protocol;
DOMAIN = cfg.settings.server.domain;
ROOT_URL = cfg.settings.server.url;
HTTP_ADDR = cfg.settings.server.address;
HTTP_PORT = cfg.settings.server.port;
OFFLINE_MODE = cfg.settings.server.offline;
ENABLE_GZIP = cfg.settings.server.compression;
LANDING_PAGE = cfg.settings.server.landingPage;
};
security = {
ONLY_ALLOW_PUSH_IF_GITEA_ENVIRONMENT_SET = if cfg.settings.security.allowBypassGiteaEnv then false else true;
};
service = {
DISABLE_REGISTRATION = if cfg.settings.service.registration then false else true;
};
};
};
systemd.services."homelab-forgejo-setup" = with builtins; {
script = ''
configFile="${toString cfg.data.root}/custom/conf/app.ini";
touch $configFile
gum="${pkgs.gum}/bin/gum"
forgejo="${cfg.package}/bin/gitea --config $configFile"
user="$forgejo admin user"
awk="${pkgs.gawk}/bin/awk"
declaredUsers=(${toString (map (user: "${if isPath user.name then "$(cat ${toString user.name})" else user.name}") users)});
$gum log --structured --time timeonly --level info "HANDLING UNDECLARED USERS"
$user list | $awk '{print $2}' | tail -n +2 | while read username; do
if printf '%s\0' "''${declaredUsers[@]}" | grep -Fxqz -- "$username"; then
$gum log --structured --time timeonly --level warn "Declared user already exists, ignoring" username $username;
else
if [[ "$($user list | tail -n +2 | $awk '{print $2 " " $5}' | grep "$username " | $awk '{print $2}')" == "true" ]]; then
$gum log --structured --time timeonly --level warn "Undeclared user is a ADMIN, ignoring" username $username;
else
${if cfg.handleUndeclaredUsers then ''
$gum log --structured --time timeonly --level warn "DELETING undeclared user" username $username;
$user delete -u "$username";
'' else ''
$gum log --structured --time timeonly --level warn "UNDECLARED user, please declare it in the config so it's reproducible" username "$username";
''}
fi
fi
done
${toString (map (user: ''
username="${if isPath user.name then "\"$(cat ${toString user.name})\"" else user.name}";
email="${if isPath user.email then "\"$(cat ${toString user.email})\"" else user.email}";
password="${if isPath user.password then "\"$(cat ${toString user.password})\"" else user.password}";
if [[ "$($user list | grep "$username" | $awk '{print $2}')" ]]; then
$gum log --structured --time timeonly --level warn "User with username already exists" username $username;
elif [[ "$($user list | grep "$email" | $awk '{print $3}')" ]]; then
$gum log --structured --time timeonly --level warn "User with email already exists" email $email;
else
$gum log --structured --time timeonly --level info ${if user.admin then "Creating ADMIN user" else "Creating user"} username $username email $email password $password;
$user create --username $username --email $email --password $password ${if user.admin then "--admin" else ""};
fi
'') users)}
'';
wantedBy = [ "multi-user.target" ];
after = [ "forgejo.service" ];
serviceConfig = {
Type = "oneshot";
User = cfg.user;
Group = cfg.user;
};
};
};
}

View File

@@ -1,50 +1,54 @@
{ config, lib, ... }:
let
cfg = config.nih.services.tailscale;
in
{
imports = [ ];
options.nih.services.tailscale = with lib; with lib.types; {
enable = mkEnableOption "";
config,
lib,
...
}: let
cfg = config.services.tailscale;
in {
imports = [];
options.services.tailscale = with lib;
with lib.types; {
exitNode = mkOption {
type = bool;
default = false;
};
port = mkOption {
type = port;
default = 41641;
};
routingFeatures = mkOption {
type = enum [ "none" "client" "server" "both" ];
default = "client";
};
tailnetName = mkOption {
type = nullOr str;
default = null;
apply = v:
if cfg.enable && config.nih.handleDomains && v == null then
throw "The option ${tailnetName} a is used when Tailscale and Nih's domain handling is enabled, but it is not defined."
else null;
};
upFlags = mkOption {
type = listOf str;
default = [ ];
type = str;
};
};
config = with lib; {
services.tailscale = {
enable = true;
extraUpFlags = cfg.upFlags ++ [
(if cfg.exitNode then "--advertise-exit-node" else null)
];
port = cfg.port;
useRoutingFeatures = cfg.routingFeatures;
};
config = with lib;
mkIf cfg.enable {
services.tailscale = {
extraUpFlags = [
(
if cfg.exitNode
then "--advertise-exit-node"
else null
)
(
if cfg.exitNode
then "--exit-node"
else null
)
];
useRoutingFeatures = mkDefault (
if config.nih.type == "server" || cfg.exitNode
then "server"
else "client"
);
};
nih.networking = mkIf cfg.exitNode {
portForwarding = mkDefault true;
nameservers = [ "100.100.100.100" ];
networking.firewall.allowedTCPPorts = [80 433];
systemd.services."tailscaled" = mkIf config.services.caddy.enable {
serviceConfig = {
Environment = ["TS_PERMIT_CERT_UID=caddy"];
};
};
nih.networking = mkIf cfg.exitNode {
portForwarding = mkDefault true;
nameservers = ["100.100.100.100"];
};
};
};
}

View File

@@ -1,10 +1,14 @@
{ config, lib, pkgs, ... }:
let
cfg = config.nih.sound;
in
{
imports = [ ];
options.nih.sound = with lib; with lib.types; {
config,
lib,
pkgs,
...
}: let
cfg = config.nih.sound;
in {
imports = [];
options.nih.sound = with lib;
with lib.types; {
enable = mkOption {
type = bool;
default = true;
@@ -18,16 +22,17 @@ in
default = !cfg.pipewire.enable;
};
};
config = with lib; mkIf cfg.enable {
sound.enable = true;
hardware.pulseaudio.enable = cfg.pulseaudio.enable;
security.rtkit.enable = true;
services.pipewire = mkIf cfg.pipewire.enable {
enable = true;
alsa.enable = mkDefault true;
alsa.support32Bit = mkDefault true;
pulse.enable = mkDefault true;
wireplumber.enable = mkDefault true;
config = with lib;
mkIf cfg.enable {
sound.enable = true;
hardware.pulseaudio.enable = cfg.pulseaudio.enable;
security.rtkit.enable = true;
services.pipewire = mkIf cfg.pipewire.enable {
enable = true;
alsa.enable = mkDefault true;
alsa.support32Bit = mkDefault true;
pulse.enable = mkDefault true;
wireplumber.enable = mkDefault true;
};
};
};
}

View File

@@ -1,12 +1,14 @@
{ config, lib, pkgs, ... }:
let
cfg = config.profiles;
in
{
config,
lib,
pkgs,
...
}: let
cfg = config.profiles;
in {
imports = [
./gterminal.nix
];
options.profiles = { };
config = { };
options.profiles = {};
config = {};
}

View File

@@ -1,104 +1,123 @@
{ config, lib, pkgs, ... }:
let
cfg = config.profiles.gterminal;
in
{
imports = [ ];
options.profiles.gterminal = with lib; with lib.types; {
config,
lib,
pkgs,
...
}: let
cfg = config.profiles.gterminal;
in {
imports = [];
options.profiles.gterminal = with lib;
with lib.types; {
enable = mkEnableOption "";
};
config = with lib; mkIf cfg.enable {
programs = {
direnv.enable = true;
lf.enable = true;
starship.enable = true;
tmux.baseIndex = 1;
tmux.enable = true;
tmux.extraConfig = ''
set -sg terminal-overrides ",*:RGB"
set -g renumber-windows on
bind -T prefix / split-window -v -c "#''''{pane_current_path}"
bind -T prefix \\ split-window -h -c "#''''{pane_current_path}"
'';
tmux.keyMode = "vi";
tmux.mouse = true;
tmux.prefix = "C-Space";
tmux.plugins = with pkgs; [
{
plugin = (tmuxPlugins.catppuccin.overrideAttrs (_: {
src = fetchFromGitHub {
owner = "guz013";
repo = "frappuccino-tmux";
rev = "4255b0a769cc6f35e12595fe5a33273a247630aa";
sha256 = "0k8yprhx5cd8v1ddpcr0dkssspc17lq2a51qniwafkkzxi3kz3i5";
};
}));
extraConfig = ''
set -g @catppuccin_window_left_separator ""
set -g @catppuccin_window_right_separator " "
set -g @catppuccin_window_middle_separator " "
set -g @catppuccin_window_number_position "right"
set -g @catppuccin_window_default_fill "number"
set -g @catppuccin_window_default_text "#W"
set -g @catppuccin_window_current_fill "number"
set -g @catppuccin_window_current_text "#W"
set -g @catppuccin_status_modules_right "application directory session"
set -g @catppuccin_status_left_separator " "
set -g @catppuccin_status_right_separator ""
set -g @catppuccin_status_right_separator_inverse "no"
set -g @catppuccin_status_fill "icon"
set -g @catppuccin_status_connect_separator "no"
set -g @catppuccin_directory_text "#{pane_current_path}"
'';
}
{ plugin = tmuxPlugins.better-mouse-mode; extraConfig = "set-option -g mouse on"; }
{
plugin = (tmuxPlugins.mkTmuxPlugin {
pluginName = "tmux.nvim";
version = "unstable-2024-04-05";
src = fetchFromGitHub {
owner = "aserowy";
repo = "tmux.nvim";
rev = "63e9c5e054099dd30af306bd8ceaa2f1086e1b07";
sha256 = "0ynzljwq6hv7415p7pr0aqx8kycp84p3p3dy4jcx61dxfgdpgc4c";
};
});
extraConfig = '''';
}
config = with lib;
mkIf cfg.enable {
home.packages = with pkgs; [
neovim
gcc
wget
git
tmux
lazygit
nixpkgs-fmt
nixpkgs-lint
];
programs = {
direnv.enable = true;
tmux.shell = "${pkgs.zsh}/bin/zsh";
tmux.terminal = "screen-256color";
lf.enable = true;
wezterm = mkIf (config._nih.type != "server") {
enable = true;
config = {
default_prog = [ "zsh" "--login" ];
enable_wayland = false;
enable_tab_bar = false;
font = "lua wezterm.font(\"Fira Code\")";
font_size = 10;
neovim.enable = true;
starship.enable = true;
tmux.baseIndex = 1;
tmux.enable = true;
tmux.extraConfig = ''
set -sg terminal-overrides ",*:RGB"
set -g renumber-windows on
bind -T prefix / split-window -v -c "#''''{pane_current_path}"
bind -T prefix \\ split-window -h -c "#''''{pane_current_path}"
'';
tmux.keyMode = "vi";
tmux.mouse = true;
tmux.prefix = "C-Space";
tmux.plugins = with pkgs; [
{
plugin = tmuxPlugins.catppuccin.overrideAttrs (_: {
src = fetchFromGitHub {
owner = "guz013";
repo = "frappuccino-tmux";
rev = "4255b0a769cc6f35e12595fe5a33273a247630aa";
sha256 = "0k8yprhx5cd8v1ddpcr0dkssspc17lq2a51qniwafkkzxi3kz3i5";
};
});
extraConfig = ''
set -g @catppuccin_window_left_separator ""
set -g @catppuccin_window_right_separator " "
set -g @catppuccin_window_middle_separator " "
set -g @catppuccin_window_number_position "right"
set -g @catppuccin_window_default_fill "number"
set -g @catppuccin_window_default_text "#W"
set -g @catppuccin_window_current_fill "number"
set -g @catppuccin_window_current_text "#W"
set -g @catppuccin_status_modules_right "application directory session"
set -g @catppuccin_status_left_separator " "
set -g @catppuccin_status_right_separator ""
set -g @catppuccin_status_right_separator_inverse "no"
set -g @catppuccin_status_fill "icon"
set -g @catppuccin_status_connect_separator "no"
set -g @catppuccin_directory_text "#{pane_current_path}"
'';
}
{
plugin = tmuxPlugins.better-mouse-mode;
extraConfig = "set-option -g mouse on";
}
{
plugin = tmuxPlugins.mkTmuxPlugin {
pluginName = "tmux.nvim";
version = "unstable-2024-04-05";
src = fetchFromGitHub {
owner = "aserowy";
repo = "tmux.nvim";
rev = "63e9c5e054099dd30af306bd8ceaa2f1086e1b07";
sha256 = "0ynzljwq6hv7415p7pr0aqx8kycp84p3p3dy4jcx61dxfgdpgc4c";
};
};
extraConfig = '''';
}
];
tmux.shell = "${pkgs.zsh}/bin/zsh";
tmux.terminal = "screen-256color";
wezterm = mkIf (config._nih.type != "server") {
enable = true;
config = {
default_prog = ["zsh" "--login"];
enable_wayland = false;
enable_tab_bar = false;
font = "lua wezterm.font(\"Fira Code\")";
font_size = 10;
};
};
zsh.enable = true;
zsh.enableAutosuggestions = true;
zsh.enableCompletion = true;
zsh.initExtra = ''
export GPG_TTY=$(tty)
alias tmux="tmux -f ${config.xdg.configHome}/tmux/tmux.conf";
'';
};
zsh.enable = true;
zsh.enableAutosuggestions = true;
zsh.enableCompletion = true;
zsh.initExtra = ''
export GPG_TTY=$(tty)
alias tmux="tmux -f ${config.xdg.configHome}/tmux/tmux.conf";
'';
};
};
}

View File

@@ -1,6 +1,10 @@
{ config, inputs, lib, pkgs, ... }:
let
{
config,
inputs,
lib,
pkgs,
...
}: let
cfg = config.nih;
hmModule = lib.types.submoduleWith {
description = "Home Manager module";
@@ -8,29 +12,32 @@ let
lib = lib;
osConfig = config;
};
modules = [
({ name, ... }: {
config = {
submoduleSupport.enable = true;
submoduleSupport.externalPackageInstall = cfg.useUserPackages;
modules =
[
({name, ...}: {
config = {
submoduleSupport.enable = true;
submoduleSupport.externalPackageInstall = cfg.useUserPackages;
home.username = config.users.users.${name}.name;
home.homeDirectory = config.users.users.${name}.home;
home.username = config.users.users.${name}.name;
home.homeDirectory = config.users.users.${name}.home;
# Make activation script use same version of Nix as system as a whole.
# This avoids problems with Nix not being in PATH.
nix.package = config.nix.package;
};
})
] ++ config.home-manager.sharedModules;
# Make activation script use same version of Nix as system as a whole.
# This avoids problems with Nix not being in PATH.
nix.package = config.nix.package;
};
})
]
++ config.home-manager.sharedModules;
};
in
{
imports = [ ];
options.nih = with lib; with lib.types; {
in {
imports = [];
options.nih = with lib;
with lib.types; {
users = mkOption {
type = attrsOf
(submodule ({ ... }: {
type =
attrsOf
(submodule ({...}: {
options = {
description = mkOption {
type = nullOr str;
@@ -38,11 +45,11 @@ in
};
extraGroups = mkOption {
type = listOf str;
default = [ "networkmanager" "wheel" ];
default = ["networkmanager" "wheel"];
};
home = mkOption {
type = attrsOf anything;
default = { };
default = {};
};
normalUser = mkOption {
type = bool;
@@ -50,7 +57,7 @@ in
};
packages = mkOption {
type = listOf package;
default = [ ];
default = [];
};
password = mkOption {
type = nullOr (passwdEntry str);
@@ -58,15 +65,15 @@ in
};
profiles = mkOption {
type = attrsOf anything;
default = { };
default = {};
};
programs = mkOption {
type = attrsOf anything;
default = { };
default = {};
};
services = mkOption {
type = attrsOf anything;
default = { };
default = {};
};
shell = mkOption {
type = package;
@@ -74,8 +81,11 @@ in
};
username = mkOption {
type = passwdEntry str;
apply = x: assert (builtins.stringLength
x < 32 || abort "Username '${x}' is longer than 31 characters"); x;
apply = x:
assert (builtins.stringLength
x
< 32
|| abort "Username '${x}' is longer than 31 characters"); x;
};
};
}));
@@ -83,23 +93,27 @@ in
};
config = with lib; {
users.users =
(builtins.mapAttrs
(name: value: {
name = value.username;
hashedPassword = value.password;
description = if value.description != null then value.description else value.username;
isNormalUser = value.normalUser;
shell = value.shell;
extraGroups = value.extraGroups ++ [ "wheel" ];
})
cfg.users);
builtins.mapAttrs
(name: value: {
name = value.username;
hashedPassword = value.password;
description =
if value.description != null
then value.description
else value.username;
isNormalUser = value.normalUser;
shell = value.shell;
extraGroups = value.extraGroups ++ ["wheel"];
})
cfg.users;
users.mutableUsers = true;
home-manager.extraSpecialArgs = { inherit inputs; };
home-manager.extraSpecialArgs = {inherit inputs;};
home-manager.users =
(builtins.mapAttrs
(name: value: mkMerge [
builtins.mapAttrs
(name: value:
mkMerge [
{
imports = [
inputs.nix-index-database.hmModules.nix-index
@@ -107,36 +121,38 @@ in
./programs
./user-profiles
];
options = with lib; with lib.types; {
options = with lib;
with lib.types; {
_nih = mkOption {
type = attrsOf anything;
default = { };
default = {};
};
};
config = {
_nih = {
type = config.nih.type;
};
profiles = mkMerge [ value.profiles ];
profiles = mkMerge [value.profiles];
programs = mkMerge [
{ home-manager.enable = true; }
{home-manager.enable = true;}
value.programs
];
services = mkMerge [
{ flatpak.enable = mkDefault true; }
{flatpak.enable = mkDefault true;}
value.services
];
home = mkMerge [
{
username = value.username;
homeDirectory = mkDefault
homeDirectory =
mkDefault
"/home/${value.username}";
stateVersion = mkDefault
stateVersion =
mkDefault
"23.11"; # Do not change
}
value.home
@@ -144,6 +160,6 @@ in
};
}
])
cfg.users);
cfg.users;
};
}

View File

@@ -1,16 +1,20 @@
# Config shared between all host's configuration.nix (NixOS config)
{ config, pkgs, inputs, lib, ... }:
let
cfg = config.host;
in
{
config,
pkgs,
inputs,
lib,
...
}: let
cfg = config.host;
in {
imports = [
inputs.home-manager.nixosModules.default
inputs.sops-nix.nixosModules.sops
../systems/localization.nix
];
options.host = with lib; with lib.types; {
options.host = with lib;
with lib.types; {
networking = {
hostName = mkOption {
default = "nixos";
@@ -31,8 +35,8 @@ in
};
};
config = {
# Nix configuration
nix.settings.experimental-features = [ "nix-command" "flakes" ];
# Nix configuration
nix.settings.experimental-features = ["nix-command" "flakes"];
boot = {
loader.systemd-boot.enable = true;
@@ -86,6 +90,5 @@ in
# Before changing this value read the documentation for this option
# (e.g. man configuration.nix or on https://nixos.org/nixos/options.html).
system.stateVersion = "23.11"; # Did you read the comment?
};
}

10
modules/nixos/default.nix Normal file
View File

@@ -0,0 +1,10 @@
{...}: {
imports = [
./profiles
./services
./home-manager-helper.nix
./programs
];
options = {};
config = {};
}

View File

@@ -0,0 +1,248 @@
{
config,
inputs,
lib,
pkgs,
...
}:
with lib;
with lib.types; let
cfg = config.home-manager-helper;
subordinateUidRange = {
options = {
startUid = mkOption {
type = int;
};
count = mkOption {
type = int;
default = 1;
};
};
};
subordinateGidRange = {
options = {
startGid = mkOption {
type = int;
};
count = mkOption {
type = int;
default = 1;
};
};
};
in {
imports = [
inputs.home-manager.nixosModules.default
];
options.home-manager-helper = with lib;
with lib.types; {
enable = mkEnableOption "";
users = mkOption {
type =
attrsOf
(submodule
({
config,
name,
...
}: {
options = {
autoSubUidGidRange = mkOption {
type = bool;
default = false;
};
createHome = mkOption {
type = bool;
default = cfg.users.${name}.homeManager;
};
cryptHomeLuks = mkOption {
type = nullOr str;
default = null;
};
description = mkOption {
type = passwdEntry str;
default = "";
};
extraGroups = mkOption {
type = listOf str;
default = [];
};
group = mkOption {
type = str;
default = name;
};
hashedPassword = mkOption {
type = nullOr (passwdEntry str);
default = null;
};
hashedPasswordFile = mkOption {
type = nullOr str;
default = null;
};
home = mkOption {
type = anything;
default = {};
};
homeDirectory = mkOption {
type = passwdEntry path;
default =
if cfg.users.${name}.homeManager
then "/home/${name}"
else "/var/empty";
};
homeManager = mkOption {
type = bool;
default =
if cfg.users.${name}.isNormalUser
then true
else false;
};
homeMode = mkOption {
type = strMatching "[0-7]{1,5}";
default = "700";
};
ignoreShellProgramCheck = mkOption {
type = bool;
default = false;
};
initialHashedPassword = mkOption {
type = nullOr (passwdEntry str);
default = null;
};
initialPassword = mkOption {
type = nullOr (passwdEntry str);
default = null;
};
isNormalUser = mkOption {
type = bool;
default = false;
};
isSystemUser = mkOption {
type = bool;
default = false;
};
linger = mkOption {
type = bool;
default = false;
};
name = mkOption {
type = passwdEntry str;
};
packages = mkOption {
type = listOf package;
default = [];
};
pamMount = mkOption {
type = attrsOf str;
default = {};
};
shell = mkOption {
type = nullOr (either shellPackage (passwdEntry path));
default = pkgs.shadow;
};
subGidRanges = mkOption {
type = listOf (submodule subordinateGidRange);
default = [];
};
subUidRanges = mkOption {
type = listOf (submodule subordinateUidRange);
default = [];
};
uid = mkOption {
type = nullOr int;
default = null;
};
useDefaultShell = mkOption {
type = bool;
default = false;
};
};
}));
default = {};
};
};
config = with lib;
with builtins;
mkIf cfg.enable {
users.users =
mapAttrs
(name: value: {
inherit
(value)
autoSubUidGidRange
createHome
cryptHomeLuks
description
extraGroups
group
hashedPassword
hashedPasswordFile
homeMode
ignoreShellProgramCheck
initialHashedPassword
initialPassword
isNormalUser
isSystemUser
linger
name
pamMount
shell
subGidRanges
subUidRanges
uid
useDefaultShell
;
home = value.homeDirectory;
packages =
if value.homeManager
then []
else value.packages;
})
cfg.users;
users.mutableUsers = true;
users.groups =
mapAttrs'
(name: value: {
name = name;
value =
mkDefault
{
name = name;
members = ["${name}"];
};
})
cfg.users;
home-manager.extraSpecialArgs = {inherit inputs;};
home-manager.users =
mapAttrs
(name: value: (mkMerge [
{
imports = [
inputs.nix-index-database.hmModules.nix-index
inputs.flatpaks.homeManagerModules.nix-flatpak
];
home.username = value.name;
home.homeDirectory = value.homeDirectory;
home.packages =
value.packages
++ (
if value ? home ? packages
then value.home.packages
else []
);
programs.home-manager.enable = true;
home.stateVersion = "23.11"; # DO NOT CHANGE
}
value.home
]))
(filterAttrs (n: v: v.homeManager) cfg.users);
};
}

View File

@@ -0,0 +1,7 @@
{...}: {
imports = [
./locale.nix
];
options = {};
config = {};
}

View File

@@ -0,0 +1,64 @@
{
config,
lib,
...
}: let
cfg = config.profiles.locale;
in {
imports = [];
options.profiles.locale = with lib;
with lib.types; {
enable = mkEnableOption "";
locale = mkOption {
type = str;
default = "en_US.UTF-8";
};
extraLocales = mkOption {
type = attrsOf str;
default = rec {
LC_ADDRESS = "pt_BR.UTF-8";
LC_IDENTIFICATION = LC_ADDRESS;
LC_MEASUREMENT = LC_ADDRESS;
LC_MONETARY = LC_ADDRESS;
LC_NAME = LC_ADDRESS;
LC_NUMERIC = LC_ADDRESS;
LC_PAPER = LC_ADDRESS;
LC_TELEPHONE = LC_ADDRESS;
LC_TIME = LC_ADDRESS;
};
};
keymap.layout = mkOption {
type = str;
default = "br";
};
keymap.variant = mkOption {
type = str;
default = "";
};
keymap.console = mkOption {
type = str;
default = "br-abnt2";
};
timeZone = mkOption {
type = str;
default = "America/Sao_Paulo";
};
};
config = {
i18n = {
defaultLocale = cfg.locale;
extraLocaleSettings = cfg.extraLocales;
};
services.xserver = {
xkb.layout = cfg.keymap.layout;
xkb.variant = cfg.keymap.variant;
};
console.keyMap = cfg.keymap.console;
time = {
timeZone = cfg.timeZone;
};
};
}

View File

@@ -0,0 +1,7 @@
{...}: {
imports = [
./nih
];
options = {};
config = {};
}

View File

@@ -0,0 +1,40 @@
{
config,
lib,
pkgs,
...
}: let
cfg = config.programs.nih;
cli = pkgs.writeShellScriptBin "nih" ''
# Since alias= doesn't work in bash scripts
function alejandra() { ${pkgs.alejandra}/bin/alejandra "$@"; }
function git() { ${pkgs.git}/bin/git "$@"; }
function gum() { ${pkgs.gum}/bin/gum "$@"; }
function lazygit() { ${pkgs.lazygit}/bin/lazygit "$@"; }
function notify-send() {
(${pkgs.libnotify}/bin/notify-send "$@" &>/dev/null || echo "")
}
function mktemp() { ${pkgs.mktemp}/bin/mktemp "$@"; }
flake_dir="${toString cfg.flakeDir}";
host="${toString cfg.host}";
${builtins.readFile ./cli.sh}
'';
in {
imports = [];
options.nih = with lib;
with lib.types; {
cli = mkOption {
type = bool;
default = cfg.enable;
};
};
config = with lib;
mkIf cfg.enable {
environment.systemPackages = [
cli
];
};
}

View File

@@ -0,0 +1,124 @@
function nih-edit() {
local flake_dir="$1"
local host="$2"
# Exit if a command exits with a non-zero value
set -e
# Push directory to history
pushd $flake_dir
# Edit file
$EDITOR "$(gum file "$flakedir")"
# Skip if there's no changes
if git diff --quiet "*.*"; then
gum log --structured \
--prefix 'nih edit' \
--level warn \
'No files changed'
popd
exit 0
fi
# Autoformat nix files
gum log --structured --prefix 'nih edit' --level debug 'Formatting files'
alejandra . &>/dev/null \
|| (alejandra . ; \
gum log --structured \
--prefix 'nih edit' \
--level error 'Failed to format files' \
&& exit 1)
# Show modifications
gum log --structured --prefix 'nih edit' --level debug 'Creatting diff files'
temp_file="$(mktemp /tmp/nih-diff-XXXXX)"
git diff -U0 '*.nix' > $temp_file
echo "$(gum format -l diff -t code < $temp_file)" > $temp_file
gum pager < $temp_file
rm $temp_file
# Build NixOS
gum log --structured --prefix 'nih edit' --level debug 'Building NixOS'
sudo nixos-rebuild switch --flake "$flake_dir#$host" \
|| (gum log --structured --prefix 'nih edit' --level error 'Error building new config' && exit 1)
gum log --structured \
--prefix 'nih edit' \
--level info 'NixOS finished building, please commit the changes'
notify-send -e "NixOS finished building, please commit the changes" \
--icon=software-update-available \
--urgency=normal
case "$(gum choose --limit 1 'Commit' 'Open lazygit' 'No commit')" in
"Commit")
metadata="$(nixos-rebuild list-generations | grep current)"
commit_msg="$(gum write --value "$metadata" --placeholder 'Commit message')"
git commit -am "$commit_msg"
gum confirm 'Push changes to remote?' \
&& git push \
|| echo "";
;;
"Open lazygit")
lazygit
gum confirm 'Push changes to remote?' \
&& git push \
|| echo "";
;;
*)
gum log --structured \
--prefix 'nih edit' \
--level info 'Not commiting'
;;
esac
gum log --structured --prefix 'nih edit' --level info 'NixOS rebuilt!'
notify-send -e "NixOS Rebuilt!" \
--icon=software-update-available \
--urgency=low
# Pop back to previous directory
popd
}
function nih-build () {
gum log --structured --prefix 'nih switch' --level debug 'Building NixOS'
sudo nixos-rebuild switch --flake "$flake_dir#$host" \
|| (gum log --structured --prefix 'nih edit' --level error 'Error building new config' && exit 1)
gum log --structured --prefix 'nih switch' --level info 'NixOS rebuilt!'
notify-send -e "NixOS Rebuilt!" \
--icon=software-update-available \
--urgency=low
}
function nih-install() {
local pkgs=()
local index=0
for arg in "$@"; do
if [[ "$arg" == "--" ]]; then
break
fi
pkgs+=("nixpkgs#$arg")
index=$(($index + 1))
done
shift $(($index + 1))
nix shell "${pkgs[@]}" "$@"
}
function nih-execute() {
local pkg="$1"
shift 1
nix run "nixpkgs#$pkg" "$@"
}
case "$1" in
"edit") nih-edit $flake_dir $host ;;
"switch" | "build") nih-build $flake_dir $host ;;
"install" | "i" ) shift 1; nih-install "$@" ;;
"exec" | "x" ) shift 1; nih-execute "$@" ;;
*) gum log --structured --prefix 'nih' --level error "Command $1 does not exist" ;;
esac

View File

@@ -0,0 +1,23 @@
{
config,
lib,
pkgs,
...
}: let
cfg = config.programs.nih;
in {
imports = [
./cli.nix
];
options.programs.nih = with lib;
with lib.types; {
enable = mkEnableOption "";
host = mkOption {
type = str;
};
flakeDir = mkOption {
type = str;
};
};
config = with lib; mkIf cfg.enable {};
}

View File

@@ -0,0 +1,62 @@
{
config,
lib,
...
}: let
cfg = config.services.adguardhome;
in {
imports = [];
options.services.adguardhome = with lib;
with lib.types; {
dns.filters = mkOption {
type = attrsOf (submodule ({lib, ...}: {
options = {
name = mkOption {
type = nullOr str;
default = null;
};
url = mkOption {
type = str;
};
enabled = {
type = bool;
default = true;
};
};
}));
default = {};
};
dns.rewrites = mkOption {
type = attrsOf str;
default = {};
};
};
config = with lib;
mkIf cfg.enable {
networking.firewall.allowedTCPPorts = [53];
networking.firewall.allowedUDPPorts = [53 51820];
services.adguardhome = {
settings = {
http.address = "${cfg.settings.bind_host}:${toString cfg.settings.bind_port}";
dns.rewrites = builtins.attrValues (builtins.mapAttrs
(from: to: {
domain = from;
answer = to;
})
cfg.dns.rewrites);
filters = attrValues (mapAttrs
(id: list: {
name =
if isNull list.name
then id
else list.name;
ID = id;
url = list.url;
enabled = list.enabled;
})
cfg.dns.filters);
};
};
};
}

View File

@@ -0,0 +1,9 @@
{...}: {
imports = [
./adguardhome.nix
./forgejo.nix
./tailscale.nix
];
options = {};
config = {};
}

View File

@@ -0,0 +1,249 @@
{
config,
lib,
pkgs,
utils,
...
}: let
cfg = config.services.forgejo;
yamlFormat = pkgs.formats.yaml {};
users = builtins.attrValues (builtins.mapAttrs
(username: info: {
name =
if isNull info.name
then username
else info.name;
email = info.email;
password = info.password;
admin = info.admin;
})
cfg.users);
initList = l: lib.strings.concatStringsSep "," l;
in {
imports = [];
options.services.forgejo = with lib;
with lib.types; {
handleUndeclaredUsers = mkOption {
type = bool;
default = false;
};
users = mkOption {
type = attrsOf (submodule ({
config,
lib,
...
}:
with lib;
with lib.types; {
options = {
name = mkOption {
type = nullOr (either str path);
default = null;
};
password = mkOption {
type = either str path;
};
email = mkOption {
type = either str path;
};
admin = mkOption {
type = bool;
default = false;
};
};
}));
default = {};
};
actions = {
enable = mkEnableOption "";
hostPackages = mkOption {
type = listOf package;
default = with pkgs; [
bash
coreutils
curl
gawk
gitMinimal
gnused
nodejs
wget
];
};
labels = mkOption {
type = listOf str;
default = [
"host:host"
"shell:host://-self-hosted"
"debian-latest:docker://node:18-bullseye"
"ubuntu-latest:docker://node:18-bullseye"
"debian-slim:docker://node:18-bullseye-slim"
"ubuntu-slim:docker://node:18-bullseye-slim"
"alpine-latest:docker://alpine:latest"
];
};
name = mkOption {
type = str;
default = "Forgejo ${toString cfg.settings.server.HTTP_PORT} - Actions Runner";
};
package = mkOption {
type = package;
default = pkgs.forgejo-actions-runner;
};
settings = mkOption {
type = yamlFormat.type;
default = {};
};
token = mkOption {
type = nullOr str;
default = null;
};
tokenFile = mkOption {
type = nullOr (either path str);
default = null;
};
url = mkOption {
type = str;
default = cfg.settings.server.ROOT_URL;
};
};
};
config = with lib;
mkIf cfg.enable {
# this opens the port for the internal actions be able to clone and interact with the instance
networking.firewall.allowedTCPPorts =
mkIf cfg.actions.enable [cfg.settings.server.HTTP_PORT];
services.forgejo.settings = {
actions = {
ENABLED = mkDefault cfg.actions.enable;
DEFAULT_ACTIONS_URL = mkDefault cfg.settings.server.ROOT_URL;
};
repository = {
DEFAULT_REPO_UNITS = mkDefault (initList [
"repo.code"
"repo.issues"
"repo.pulls"
]);
DISABLED_REPO_UNITS = mkIf (!cfg.actions.enable) (mkDefault "repo.actions");
};
security = {
ONLY_ALLOW_PUSH_IF_GITEA_ENVIRONMENT_SET = mkDefault true;
};
server = {
HTTP_PORT = mkDefault 3617;
DOMAIN =
mkIf config.services.tailscale.enable
(mkDefault "${config.services.tailscale.deviceUrl}");
};
service = {
DISABLE_REGISTRATION = mkDefault true;
};
};
virtualisation.docker.enable = mkIf cfg.settings.actions.ENABLED (mkDefault true);
services.gitea-actions-runner.instances."generatedForgejo${toString cfg.settings.server.HTTP_PORT}" = mkIf cfg.actions.enable {
enable = mkDefault true;
hostPackages = mkDefault cfg.actions.hostPackages;
labels = mkDefault cfg.actions.labels;
name = mkDefault cfg.actions.name;
settings = mkDefault cfg.actions.settings;
url = mkDefault cfg.actions.url;
token = cfg.actions.token;
};
systemd.services = {
"${utils.escapeSystemdPath "generatedForgejo${toString cfg.settings.server.HTTP_PORT}"}" = {
serviceConfig.User = mkIf cfg.actions.enable (mkDefault cfg.user);
};
};
systemd.services."homelab-forgejo-setup" = with builtins; {
script = ''
configFile="${toString cfg.stateDir}/custom/conf/app.ini";
touch $configFile
gum="${pkgs.gum}/bin/gum"
forgejo="${cfg.package}/bin/gitea --config $configFile"
user="$forgejo admin user"
awk="${pkgs.gawk}/bin/awk"
declaredUsers=(${toString (map (user: "${
if isPath user.name
then "$(cat ${toString user.name})"
else user.name
}")
users)});
$gum log --structured --time timeonly --level info "HANDLING UNDECLARED USERS"
$user list | $awk '{print $2}' | tail -n +2 | while read username; do
if printf '%s\0' "''${declaredUsers[@]}" | grep -Fxqz -- "$username"; then
$gum log --structured --time timeonly --level warn "Declared user already exists, ignoring" username $username;
else
if [[ "$($user list | tail -n +2 | $awk '{print $2 " " $5}' | grep "$username " | $awk '{print $2}')" == "true" ]]; then
$gum log --structured --time timeonly --level warn "Undeclared user is a ADMIN, ignoring" username $username;
else
${
if cfg.handleUndeclaredUsers
then ''
$gum log --structured --time timeonly --level warn "DELETING undeclared user" username $username;
$user delete -u "$username";
''
else ''
$gum log --structured --time timeonly --level warn "UNDECLARED user, please declare it in the config so it's reproducible" username "$username";
''
}
fi
fi
done
${toString (map (user: ''
username="${
if isPath user.name
then "\"$(cat ${toString user.name})\""
else user.name
}";
email="${
if isPath user.email
then "\"$(cat ${toString user.email})\""
else user.email
}";
password="${
if isPath user.password
then "\"$(cat ${toString user.password})\""
else user.password
}";
if [[ "$($user list | grep "$username" | $awk '{print $2}')" ]]; then
$gum log --structured --time timeonly --level warn "User with username already exists" username $username;
elif [[ "$($user list | grep "$email" | $awk '{print $3}')" ]]; then
$gum log --structured --time timeonly --level warn "User with email already exists" email $email;
else
$gum log --structured --time timeonly --level info ${
if user.admin
then "Creating ADMIN user"
else "Creating user"
} username $username email $email password $password;
$user create --username $username --email $email --password $password ${
if user.admin
then "--admin"
else ""
};
fi
'')
users)}
'';
wantedBy = ["multi-user.target"];
after = ["forgejo.service"];
serviceConfig = {
Type = "oneshot";
User = cfg.user;
Group = cfg.group;
};
};
};
}

View File

@@ -0,0 +1,61 @@
{
config,
lib,
...
}: let
cfg = config.services.tailscale;
in {
imports = [];
options.services.tailscale = with lib;
with lib.types; {
exitNode = mkOption {
type = bool;
default = false;
};
tailnetName = mkOption {
type = str;
};
tailnetUrl = mkOption {
type = str;
default = "${config.services.tailscale.tailnetName}.ts.net";
};
deviceUrl = mkOption {
type = str;
default = "${config.networking.hostName}.${config.services.tailscale.tailnetUrl}";
};
deviceIp = mkOption {
type = str;
};
};
config = with lib;
mkIf cfg.enable {
services.tailscale = {
extraUpFlags = [
(
if cfg.exitNode
then "--advertise-exit-node"
else null
)
(
if cfg.exitNode
then "--exit-node"
else null
)
];
useRoutingFeatures = mkDefault (
if cfg.exitNode
then "server"
else "client"
);
};
systemd.services."tailscaled" = mkIf config.services.caddy.enable (mkDefault {
serviceConfig = {
Environment = ["TS_PERMIT_CERT_UID=caddy"];
};
});
boot.kernel.sysctl."net.ipv4.ip_forward" = mkIf cfg.exitNode (mkDefault 1);
boot.kernel.sysctl."net.ipv6.conf.all.forwarding" = mkIf cfg.exitNode (mkDefault 1);
};
}

View File

@@ -1,15 +1,17 @@
{ config, lib, ... }:
let
cfg = config.my-fonts;
in
{
imports = [ ];
options.my-fonts = with lib; with lib.types; {
config,
lib,
...
}: let
cfg = config.my-fonts;
in {
imports = [];
options.my-fonts = with lib;
with lib.types; {
enable = mkEnableOption "";
fonts = mkOption {
type = listOf package;
default = [ ];
default = [];
};
user = mkOption {
type = str;
@@ -35,7 +37,7 @@ in
ln -sf /run/current-system/sw/share/X11/fonts /home/${cfg.user}/.fonts;
fi
'';
wantedBy = [ "multi-user.target" ];
wantedBy = ["multi-user.target"];
serviceConfig = {
Type = "oneshot";
User = cfg.user;

View File

@@ -1,10 +1,14 @@
{ config, pkgs, inputs, lib, ... }:
let
cfg = config.localization;
in
{
options.localization = with lib; with lib.types; {
config,
pkgs,
inputs,
lib,
...
}: let
cfg = config.localization;
in {
options.localization = with lib;
with lib.types; {
locale = mkOption {
default = "en_US.UTF-8";
type = str;

View File

@@ -1,13 +1,17 @@
{ config, pkgs, inputs, lib, ... }:
let
cfg = config.set-user;
in
{
options.set-user = with lib; with lib.types; {
config,
pkgs,
inputs,
lib,
...
}: let
cfg = config.set-user;
in {
options.set-user = with lib;
with lib.types; {
users = mkOption {
default = [ ];
type = listOf (submodule ({ ... }: {
default = [];
type = listOf (submodule ({...}: {
options = {
username = mkOption {
type = str;
@@ -26,11 +30,11 @@ in
};
packages = mkOption {
type = listOf package;
default = [ ];
default = [];
};
extraGroups = mkOption {
type = listOf str;
default = [ "networkmanager" "wheel" ];
default = ["networkmanager" "wheel"];
};
home = mkOption {
type = anything;
@@ -43,52 +47,56 @@ in
}));
};
};
config =
let
home-default = user: {
imports = [
(
if user?flatpak && !user.flatpak
then null
else inputs.flatpaks.homeManagerModules.nix-flatpak
)
inputs.nix-index-database.hmModules.nix-index
];
programs.home-manager.enable = true;
home.username = user.username;
home.homeDirectory = "/home/${user.username}";
home.stateVersion = "23.11"; # Do not change
};
in
{
users.users = (builtins.listToAttrs
(map
(u: {
name = u.username;
value = {
description =
if u.description != null then u.description else u.username;
isNormalUser = u.normalUser;
shell = u.shell;
packages = u.packages;
extraGroups = u.extraGroups ++ [ "wheel" ];
};
})
cfg.users
config = let
home-default = user: {
imports = [
(
if user ? flatpak && !user.flatpak
then null
else inputs.flatpaks.homeManagerModules.nix-flatpak
)
);
home-manager.extraSpecialArgs = { inherit inputs; };
home-manager.users = (builtins.listToAttrs
(map
(u: {
name = u.username;
value =
if u?home then lib.mkMerge [ (home-default u) u.home ]
else (home-default u);
})
cfg.users
)
);
inputs.nix-index-database.hmModules.nix-index
];
programs.home-manager.enable = true;
home.username = user.username;
home.homeDirectory = "/home/${user.username}";
home.stateVersion = "23.11"; # Do not change
};
in {
users.users = (
builtins.listToAttrs
(
map
(u: {
name = u.username;
value = {
description =
if u.description != null
then u.description
else u.username;
isNormalUser = u.normalUser;
shell = u.shell;
packages = u.packages;
extraGroups = u.extraGroups ++ ["wheel"];
};
})
cfg.users
)
);
home-manager.extraSpecialArgs = {inherit inputs;};
home-manager.users = (
builtins.listToAttrs
(
map
(u: {
name = u.username;
value =
if u ? home
then lib.mkMerge [(home-default u) u.home]
else (home-default u);
})
cfg.users
)
);
};
}

View File

@@ -1,15 +1,17 @@
{ config, lib, ... }:
let
cfg = config.server.adguard;
in
{
imports = [ ];
options.server.adguard = with lib; with lib.types; {
config,
lib,
...
}: let
cfg = config.server.adguard;
in {
imports = [];
options.server.adguard = with lib;
with lib.types; {
enable = mkEnableOption "";
extraArgs = mkOption {
type = listOf str;
default = [ ];
default = [];
};
domain = mkOption {
type = str;
@@ -34,10 +36,10 @@ in
};
dns.rewrites = mkOption {
type = attrsOf str;
default = { };
default = {};
};
dns.filters = mkOption {
type = attrsOf (submodule ({ lib, ... }: {
type = attrsOf (submodule ({lib, ...}: {
options = {
name = mkOption {
type = nullOr str;
@@ -52,14 +54,14 @@ in
};
};
}));
default = { };
default = {};
};
};
};
config = lib.mkIf cfg.enable {
networking.firewall = {
allowedTCPPorts = [ 53 ];
allowedUDPPorts = [ 53 51820 ];
allowedTCPPorts = [53];
allowedUDPPorts = [53 51820];
};
services.adguardhome = with builtins; {
enable = true;
@@ -69,22 +71,24 @@ in
http = {
address = "${cfg.settings.server.address}:${toString cfg.settings.server.port}";
};
dns.rewrites = (builtins.attrValues (builtins.mapAttrs
dns.rewrites = builtins.attrValues (builtins.mapAttrs
(from: to: {
domain = from;
answer = to;
})
cfg.settings.dns.rewrites));
filters = (attrValues (mapAttrs
cfg.settings.dns.rewrites);
filters = attrValues (mapAttrs
(id: list: {
name = if isNull list.name then id else list.name;
name =
if isNull list.name
then id
else list.name;
ID = id;
url = list.url;
enabled = list.enabled;
})
cfg.settings.dns.filters));
cfg.settings.dns.filters);
};
};
};
}

View File

@@ -1,15 +1,21 @@
{ config, lib, ... }:
let
cfg = config.server.caddy;
in
{
imports = [ ];
options.server.caddy = with lib; with lib.types; {
config,
lib,
...
}: let
cfg = config.server.caddy;
in {
imports = [];
options.server.caddy = with lib;
with lib.types; {
enable = mkEnableOption "";
settings = {
virtualHosts = mkOption {
type = attrsOf (submodule ({ config, lib, ... }: {
type = attrsOf (submodule ({
config,
lib,
...
}: {
options = {
extraConfig = mkOption {
type = lines;
@@ -17,7 +23,7 @@ in
};
};
}));
default = { };
default = {};
};
};
};

View File

@@ -1,6 +1,9 @@
{ config, lib, pkgs, ... }:
let
{
config,
lib,
pkgs,
...
}: let
cfg = config.server;
server = pkgs.writeShellScriptBin "server" ''
gum="${pkgs.gum}/bin/gum";
@@ -13,15 +16,23 @@ let
sudo nixos-rebuild switch --flake "$flakeDir" "$@"
fi
${if cfg.forgejo.cliAlias then ''
if [[ "$command" == "forgejo" ]]; then
shift 1;
sudo --user=${cfg.forgejo.user} ${cfg.forgejo.package}/bin/gitea --work-path ${cfg.forgejo.data.root} "$@"
fi
'' else ""}
${
if cfg.forgejo.cliAlias
then ''
if [[ "$command" == "forgejo" ]]; then
shift 1;
sudo --user=${cfg.forgejo.user} ${cfg.forgejo.package}/bin/gitea --work-path ${cfg.forgejo.data.root} "$@"
fi
if [[ "$command" == "forgejo-act" ]]; then
shift 1;
sudo --user=${cfg.forgejo.user} ${cfg.forgejo.actions.package}/bin/act_runner --config /var/lib/gitea-runner/${cfg.forgejo.actions.instanceName} "$@"
fi
''
else ""
}
'';
in
{
in {
imports = [
./adguard.nix
./caddy.nix
@@ -33,7 +44,8 @@ in
./photoprism.nix
./tailscale.nix
];
options.server = with lib; with lib.types; {
options.server = with lib;
with lib.types; {
enable = mkEnableOption "";
name = mkOption {
type = str;
@@ -68,42 +80,45 @@ in
server
];
networking.firewall.allowedTCPPorts = lib.mkIf cfg.handleDomains [ 80 433 ];
networking.firewall.allowedTCPPorts = lib.mkIf cfg.handleDomains [80 433];
systemd.services."tailscaled" = lib.mkIf cfg.handleDomains {
serviceConfig = {
Environment = [ "TS_PERMIT_CERT_UID=caddy" ];
Environment = ["TS_PERMIT_CERT_UID=caddy"];
};
};
server = with lib; mkIf cfg.handleDomains {
adguard = {
enable = true;
settings.dns.rewrites = (if hasPrefix "*." cfg.domain then {
"${cfg.domain}" = cfg.ip;
} else {
"${cfg.domain}" = cfg.ip;
"${"*." + cfg.domain}" = cfg.ip;
});
};
caddy =
let
homelabServices = (lib.filterAttrs (n: v: builtins.isAttrs v && v?domain) cfg);
in
with lib;
mkIf cfg.handleDomains {
server = with lib;
mkIf cfg.handleDomains {
adguard = {
enable = true;
settings.virtualHosts = mapAttrs'
(name: value: nameValuePair (value.domain) ({
extraConfig = ''
reverse_proxy ${cfg.localIp}:${toString value.port}
'';
}))
homelabServices;
settings.dns.rewrites =
if hasPrefix "*." cfg.domain
then {
"${cfg.domain}" = cfg.ip;
}
else {
"${cfg.domain}" = cfg.ip;
"${"*." + cfg.domain}" = cfg.ip;
};
};
};
caddy = let
homelabServices = lib.filterAttrs (n: v: builtins.isAttrs v && v ? domain) cfg;
in
with lib;
mkIf cfg.handleDomains {
enable = true;
settings.virtualHosts =
mapAttrs'
(name: value:
nameValuePair (value.domain) {
extraConfig = ''
reverse_proxy ${cfg.localIp}:${toString value.port}
'';
})
homelabServices;
};
};
};
}

View File

@@ -1,24 +1,67 @@
{ config, lib, pkgs, ... }:
let
{
config,
lib,
pkgs,
utils,
...
}: let
cfg = config.server.forgejo;
users = (builtins.attrValues (builtins.mapAttrs
users = builtins.attrValues (builtins.mapAttrs
(username: info: {
name = if isNull info.name then username else info.name;
name =
if isNull info.name
then username
else info.name;
email = info.email;
password = info.password;
admin = info.admin;
})
cfg.settings.users));
in
{
imports = [ ];
options.server.forgejo = with lib; with lib.types; {
cfg.settings.users);
settingsFormat = pkgs.formats.yaml {};
in {
imports = [];
options.server.forgejo = with lib;
with lib.types; {
enable = mkEnableOption "";
user = mkOption {
type = str;
default = "git";
};
actions.enable = mkOption {
type = bool;
default = false;
};
actions.runnerToken = mkOption {
type = str;
};
actions.package = mkOption {
type = package;
default = pkgs.forgejo-actions-runner;
};
actions.instanceUrl = mkOption {
type = str;
default = "https://${cfg.domain}";
};
actions.url = mkOption {
type = str;
default = "http://localhost:${toString cfg.port}";
};
actions.instanceName = mkOption {
type = str;
default = "${cfg.user}Forgejo${toString cfg.port}";
};
actions.settings = mkOption {
type = submodule {
freeformType = settingsFormat.type;
options = {
runner.insecure = mkOption {
type = bool;
default = true;
};
};
};
default = {};
};
package = mkOption {
type = package;
default = pkgs.forgejo;
@@ -47,25 +90,31 @@ in
};
settings = {
users = mkOption {
type = attrsOf (submodule ({ config, lib, ... }: with lib; with lib.types; {
options = {
name = mkOption {
type = nullOr (either str path);
default = null;
type = attrsOf (submodule ({
config,
lib,
...
}:
with lib;
with lib.types; {
options = {
name = mkOption {
type = nullOr (either str path);
default = null;
};
password = mkOption {
type = either str path;
};
email = mkOption {
type = either str path;
};
admin = mkOption {
type = bool;
default = false;
};
};
password = mkOption {
type = either str path;
};
email = mkOption {
type = either str path;
};
admin = mkOption {
type = bool;
default = false;
};
};
}));
default = { };
}));
default = {};
};
name = mkOption {
type = str;
@@ -103,7 +152,7 @@ in
"repo.packages"
"repo.actions"
]);
default = [ ];
default = [];
};
repo.pushCreate = mkOption {
type = bool;
@@ -115,11 +164,11 @@ in
};
cors.domains = mkOption {
type = listOf str;
default = [ ];
default = [];
};
cors.methods = mkOption {
type = listOf str;
default = [ ];
default = [];
};
ui.defaultTheme = mkOption {
type = str;
@@ -137,7 +186,7 @@ in
];
};
server.protocol = mkOption {
type = enum [ "http" "https" "fcgi" "http+unix" "fcgi+unix" ];
type = enum ["http" "https" "fcgi" "http+unix" "fcgi+unix"];
default = "http";
};
server.domain = mkOption {
@@ -150,7 +199,10 @@ in
};
server.address = mkOption {
type = either str path;
default = if hasSuffix "+unix" cfg.settings.server.protocol then "/run/forgejo/forgejo.sock" else "0.0.0.0";
default =
if hasSuffix "+unix" cfg.settings.server.protocol
then "/run/forgejo/forgejo.sock"
else "0.0.0.0";
};
server.url = mkOption {
type = str;
@@ -165,7 +217,7 @@ in
default = false;
};
server.landingPage = mkOption {
type = enum [ "home" "explore" "organizations" "login" str ];
type = enum ["home" "explore" "organizations" "login" str];
default = "home";
};
service.registration = mkOption {
@@ -178,134 +230,190 @@ in
};
};
};
config = lib.mkIf cfg.enable {
users.users."${cfg.user}" = {
home = cfg.data.root;
useDefaultShell = true;
group = cfg.user;
isSystemUser = true;
initialPassword = "1313";
};
users.groups."${cfg.user}" = { };
config = let
inherit (utils) escapeSystemdPath;
in
with lib;
mkIf cfg.enable {
users.users."${cfg.user}" = {
home = cfg.data.root;
useDefaultShell = true;
group = cfg.user;
isSystemUser = true;
initialPassword = "1313";
};
users.groups."${cfg.user}" = {};
services.forgejo = {
enable = true;
package = cfg.package;
user = cfg.user;
group = cfg.user;
stateDir = toString cfg.data.root;
useWizard = false;
database = {
user = cfg.user;
type = "sqlite3";
};
settings = with builtins; {
DEFAULT = {
APP_NAME = cfg.settings.name;
RUN_MODE = if cfg.settings.prod then "prod" else "dev";
virtualisation.docker.enable = cfg.actions.enable;
services.gitea-actions-runner.instances."${cfg.actions.instanceName}" = mkIf cfg.actions.enable {
enable = true;
name = cfg.actions.instanceName;
url = cfg.actions.instanceUrl;
token = cfg.actions.runnerToken;
settings = cfg.actions.settings;
labels = [
"host:host"
"shell:host://-self-hosted"
"debian-latest:docker://node:18-bullseye"
"ubuntu-latest:docker://node:18-bullseye"
"debian-slim:docker://node:18-bullseye-slim"
"ubuntu-slim:docker://node:18-bullseye-slim"
"alpine-latest:docker://alpine:latest"
];
};
repository = {
DISABLED_REPO_UNITS = concatStringsSep "," cfg.settings.repo.disabledUnits;
DEFAULT_REPO_UNITS = concatStringsSep "," cfg.settings.repo.defaultUnits;
ENABLE_PUSH_CREATE_USER = cfg.settings.repo.pushCreate;
ENABLE_PUSH_CREATE_ORG = cfg.settings.repo.pushCreate;
};
cors = {
ENABLED = cfg.settings.cors.enable;
ALLOW_DOMAIN = concatStringsSep "," cfg.settings.cors.domains;
METHODS = concatStringsSep "," cfg.settings.cors.methods;
};
ui = {
DEFAULT_THEME = cfg.settings.ui.defaultTheme;
THEMES = concatStringsSep "," cfg.settings.ui.themes;
};
server = {
PROTOCOL = cfg.settings.server.protocol;
DOMAIN = cfg.settings.server.domain;
ROOT_URL = cfg.settings.server.url;
HTTP_ADDR = cfg.settings.server.address;
HTTP_PORT = cfg.settings.server.port;
OFFLINE_MODE = cfg.settings.server.offline;
ENABLE_GZIP = cfg.settings.server.compression;
LANDING_PAGE = cfg.settings.server.landingPage;
};
security = {
ONLY_ALLOW_PUSH_IF_GITEA_ENVIRONMENT_SET = if cfg.settings.security.allowBypassGiteaEnv then false else true;
};
service = {
DISABLE_REGISTRATION = if cfg.settings.service.registration then false else true;
};
};
};
systemd.services."homelab-forgejo-setup" = with builtins; {
script = ''
configFile="${toString cfg.data.root}/custom/conf/app.ini";
touch $configFile
services.gitea-actions-runner.package = cfg.actions.package;
systemd.services."${escapeSystemdPath cfg.actions.instanceName}".serviceConfig.User = cfg.user;
gum="${pkgs.gum}/bin/gum"
forgejo="${cfg.package}/bin/gitea --config $configFile"
user="$forgejo admin user"
awk="${pkgs.gawk}/bin/awk"
services.forgejo = {
enable = true;
package = cfg.package;
user = cfg.user;
group = cfg.user;
stateDir = toString cfg.data.root;
useWizard = false;
database = {
user = cfg.user;
type = "sqlite3";
};
settings = with builtins; {
actions = mkIf cfg.actions.enable {
ENABLED = true;
DEFAULT_ACTIONS_URL = cfg.actions.url;
};
DEFAULT = {
APP_NAME = cfg.settings.name;
RUN_MODE =
if cfg.settings.prod
then "prod"
else "dev";
};
repository = {
DISABLED_REPO_UNITS = concatStringsSep "," cfg.settings.repo.disabledUnits;
DEFAULT_REPO_UNITS = concatStringsSep "," cfg.settings.repo.defaultUnits;
ENABLE_PUSH_CREATE_USER = cfg.settings.repo.pushCreate;
ENABLE_PUSH_CREATE_ORG = cfg.settings.repo.pushCreate;
};
cors = {
ENABLED = cfg.settings.cors.enable;
ALLOW_DOMAIN = concatStringsSep "," cfg.settings.cors.domains;
METHODS = concatStringsSep "," cfg.settings.cors.methods;
};
ui = {
DEFAULT_THEME = cfg.settings.ui.defaultTheme;
THEMES = concatStringsSep "," cfg.settings.ui.themes;
};
server = {
PROTOCOL = cfg.settings.server.protocol;
DOMAIN = cfg.settings.server.domain;
ROOT_URL = cfg.settings.server.url;
HTTP_ADDR = cfg.settings.server.address;
HTTP_PORT = cfg.settings.server.port;
OFFLINE_MODE = cfg.settings.server.offline;
ENABLE_GZIP = cfg.settings.server.compression;
LANDING_PAGE = cfg.settings.server.landingPage;
};
security = {
ONLY_ALLOW_PUSH_IF_GITEA_ENVIRONMENT_SET =
if cfg.settings.security.allowBypassGiteaEnv
then false
else true;
};
service = {
DISABLE_REGISTRATION =
if cfg.settings.service.registration
then false
else true;
};
};
};
systemd.services."homelab-forgejo-setup" = with builtins; {
script = ''
declaredUsers=(${toString (map (user: "${if isPath user.name then "$(cat ${toString user.name})" else user.name}") users)});
configFile="${toString cfg.data.root}/custom/conf/app.ini";
touch $configFile
$gum log --structured --time timeonly --level info "HANDLING UNDECLARED USERS"
$user list | $awk '{print $2}' | tail -n +2 | while read username; do
if printf '%s\0' "''${declaredUsers[@]}" | grep -Fxqz -- "$username"; then
$gum log --structured --time timeonly --level warn "Declared user already exists, ignoring" username $username;
else
if [[ "$($user list | tail -n +2 | $awk '{print $2 " " $5}' | grep "$username " | $awk '{print $2}')" == "true" ]]; then
$gum log --structured --time timeonly --level warn "Undeclared user is a ADMIN, ignoring" username $username;
else
${if cfg.handleUndeclaredUsers then ''
gum="${pkgs.gum}/bin/gum"
forgejo="${cfg.package}/bin/gitea --config $configFile"
user="$forgejo admin user"
awk="${pkgs.gawk}/bin/awk"
declaredUsers=(${toString (map (user: "${
if isPath user.name
then "$(cat ${toString user.name})"
else user.name
}")
users)});
$gum log --structured --time timeonly --level info "HANDLING UNDECLARED USERS"
$user list | $awk '{print $2}' | tail -n +2 | while read username; do
if printf '%s\0' "''${declaredUsers[@]}" | grep -Fxqz -- "$username"; then
$gum log --structured --time timeonly --level warn "Declared user already exists, ignoring" username $username;
else
if [[ "$($user list | tail -n +2 | $awk '{print $2 " " $5}' | grep "$username " | $awk '{print $2}')" == "true" ]]; then
$gum log --structured --time timeonly --level warn "Undeclared user is a ADMIN, ignoring" username $username;
else
${
if cfg.handleUndeclaredUsers
then ''
$gum log --structured --time timeonly --level warn "DELETING undeclared user" username $username;
$user delete -u "$username";
'' else ''
''
else ''
$gum log --structured --time timeonly --level warn "UNDECLARED user, please declare it in the config so it's reproducible" username "$username";
''}
fi
fi
done
''
}
fi
fi
done
${toString (map (user: ''
username="${if isPath user.name then "\"$(cat ${toString user.name})\"" else user.name}";
email="${if isPath user.email then "\"$(cat ${toString user.email})\"" else user.email}";
password="${if isPath user.password then "\"$(cat ${toString user.password})\"" else user.password}";
${toString (map (user: ''
username="${
if isPath user.name
then "\"$(cat ${toString user.name})\""
else user.name
}";
email="${
if isPath user.email
then "\"$(cat ${toString user.email})\""
else user.email
}";
password="${
if isPath user.password
then "\"$(cat ${toString user.password})\""
else user.password
}";
if [[ "$($user list | grep "$username" | $awk '{print $2}')" ]]; then
$gum log --structured --time timeonly --level warn "User with username already exists" username $username;
if [[ "$($user list | grep "$username" | $awk '{print $2}')" ]]; then
$gum log --structured --time timeonly --level warn "User with username already exists" username $username;
elif [[ "$($user list | grep "$email" | $awk '{print $3}')" ]]; then
$gum log --structured --time timeonly --level warn "User with email already exists" email $email;
elif [[ "$($user list | grep "$email" | $awk '{print $3}')" ]]; then
$gum log --structured --time timeonly --level warn "User with email already exists" email $email;
else
$gum log --structured --time timeonly --level info ${if user.admin then "Creating ADMIN user" else "Creating user"} username $username email $email password $password;
$user create --username $username --email $email --password $password ${if user.admin then "--admin" else ""};
else
$gum log --structured --time timeonly --level info ${
if user.admin
then "Creating ADMIN user"
else "Creating user"
} username $username email $email password $password;
$user create --username $username --email $email --password $password ${
if user.admin
then "--admin"
else ""
};
fi
'') users)}
'';
wantedBy = [ "multi-user.target" ];
after = [ "forgejo.service" ];
serviceConfig = {
Type = "oneshot";
User = cfg.user;
Group = cfg.user;
fi
'')
users)}
'';
wantedBy = ["multi-user.target"];
after = ["forgejo.service"];
serviceConfig = {
Type = "oneshot";
User = cfg.user;
Group = cfg.user;
};
};
};
};
};
}

View File

@@ -1,6 +1,9 @@
{ config, lib, pkgs, ... }:
let
{
config,
lib,
pkgs,
...
}: let
cfg = config.server.jellyfin;
networkConfig = pkgs.writeTextFile {
name = "network.json";
@@ -14,12 +17,12 @@ let
name = "encoding.json";
text = builtins.toJSON cfg.settings.system;
};
in
{
in {
imports = [
./jellyseerr.nix
];
options.server.jellyfin = with lib; with lib.types; {
options.server.jellyfin = with lib;
with lib.types; {
enable = mkEnableOption "";
user = mkOption {
type = str;
@@ -49,8 +52,8 @@ in
};
settings = {
network = mkOption {
type = (submodule {
freeformType = (pkgs.formats.json { }).type;
type = submodule {
freeformType = (pkgs.formats.json {}).type;
options = {
AutoDiscovery = mkOption {
type = bool;
@@ -185,149 +188,148 @@ in
default = "vEthernet*";
};
};
});
default = { };
};
default = {};
};
encoding = mkOption {
type =
(submodule {
freeformType = (pkgs.formats.json { }).type;
options = {
AllowOnDemandMetadataBasedKeyframeExtractionForExtensions.string = mkOption {
type = listOf str;
default = [ "mkv" ];
};
DeinterlaceDoubleRate = mkOption {
type = bool;
default = false;
};
DeinterlaceMethod = mkOption {
type = str;
default = "yadif";
};
DownMixAudioBoost = mkOption {
type = int;
default = 2;
};
EnableDecodingColorDepth10Hevc = mkOption {
type = bool;
default = true;
};
EnableDecodingColorDepth10Vp9 = mkOption {
type = bool;
default = true;
};
EnableEnhancedNvdecDecoder = mkOption {
type = bool;
default = true;
};
EnableFallbackFont = mkOption {
type = bool;
default = false;
};
EnableHardwareEncoding = mkOption {
type = bool;
default = true;
};
EnableIntelLowPowerH264HwEncoder = mkOption {
type = bool;
default = false;
};
EnableIntelLowPowerHevcHwEncoder = mkOption {
type = bool;
default = false;
};
EnableSubtitleExtraction = mkOption {
type = bool;
default = true;
};
EnableThrottling = mkOption {
type = bool;
default = false;
};
EnableTonemapping = mkOption {
type = bool;
default = false;
};
EnableVppTonemapping = mkOption {
type = bool;
default = false;
};
EncoderAppPathDisplay = mkOption {
type = either path str;
default = "${pkgs.jellyfin-ffmpeg}/bin/ffmpeg";
};
EncodingThreadCount = mkOption {
type = int;
default = -1;
};
H264Crf = mkOption {
type = int;
default = 23;
};
H265Crf = mkOption {
type = int;
default = 28;
};
HardwareDecodingCodecs.string = mkOption {
type = listOf str;
default = [ "h254" "vc1" ];
};
MaxMuxingQueueSize = mkOption {
type = int;
default = 2048;
};
PreferSystemNativeHwDecoder = mkOption {
type = bool;
default = true;
};
ThrottleDelaySeconds = mkOption {
type = int;
default = 180;
};
TonemappingAlgorithm = mkOption {
type = str;
default = "bt2390";
};
TonemappingDesat = mkOption {
type = int;
default = 0;
};
TonemappingMode = mkOption {
type = str;
default = "auto";
};
TonemappingParam = mkOption {
type = int;
default = 0;
};
TonemappingPeak = mkOption {
type = int;
default = 100;
};
TonemappingRange = mkOption {
type = str;
default = "auto";
};
VaapiDevice = mkOption {
type = either path str;
default = "/dev/dri/renderD128";
};
VppTonemappingBrightness = mkOption {
type = int;
default = 16;
};
VppTonemappingContrast = mkOption {
type = int;
default = 1;
};
type = submodule {
freeformType = (pkgs.formats.json {}).type;
options = {
AllowOnDemandMetadataBasedKeyframeExtractionForExtensions.string = mkOption {
type = listOf str;
default = ["mkv"];
};
});
default = { };
DeinterlaceDoubleRate = mkOption {
type = bool;
default = false;
};
DeinterlaceMethod = mkOption {
type = str;
default = "yadif";
};
DownMixAudioBoost = mkOption {
type = int;
default = 2;
};
EnableDecodingColorDepth10Hevc = mkOption {
type = bool;
default = true;
};
EnableDecodingColorDepth10Vp9 = mkOption {
type = bool;
default = true;
};
EnableEnhancedNvdecDecoder = mkOption {
type = bool;
default = true;
};
EnableFallbackFont = mkOption {
type = bool;
default = false;
};
EnableHardwareEncoding = mkOption {
type = bool;
default = true;
};
EnableIntelLowPowerH264HwEncoder = mkOption {
type = bool;
default = false;
};
EnableIntelLowPowerHevcHwEncoder = mkOption {
type = bool;
default = false;
};
EnableSubtitleExtraction = mkOption {
type = bool;
default = true;
};
EnableThrottling = mkOption {
type = bool;
default = false;
};
EnableTonemapping = mkOption {
type = bool;
default = false;
};
EnableVppTonemapping = mkOption {
type = bool;
default = false;
};
EncoderAppPathDisplay = mkOption {
type = either path str;
default = "${pkgs.jellyfin-ffmpeg}/bin/ffmpeg";
};
EncodingThreadCount = mkOption {
type = int;
default = -1;
};
H264Crf = mkOption {
type = int;
default = 23;
};
H265Crf = mkOption {
type = int;
default = 28;
};
HardwareDecodingCodecs.string = mkOption {
type = listOf str;
default = ["h254" "vc1"];
};
MaxMuxingQueueSize = mkOption {
type = int;
default = 2048;
};
PreferSystemNativeHwDecoder = mkOption {
type = bool;
default = true;
};
ThrottleDelaySeconds = mkOption {
type = int;
default = 180;
};
TonemappingAlgorithm = mkOption {
type = str;
default = "bt2390";
};
TonemappingDesat = mkOption {
type = int;
default = 0;
};
TonemappingMode = mkOption {
type = str;
default = "auto";
};
TonemappingParam = mkOption {
type = int;
default = 0;
};
TonemappingPeak = mkOption {
type = int;
default = 100;
};
TonemappingRange = mkOption {
type = str;
default = "auto";
};
VaapiDevice = mkOption {
type = either path str;
default = "/dev/dri/renderD128";
};
VppTonemappingBrightness = mkOption {
type = int;
default = 16;
};
VppTonemappingContrast = mkOption {
type = int;
default = 1;
};
};
};
default = {};
};
system = mkOption {
type = (submodule {
freeformType = (pkgs.formats.json { }).type;
type = submodule {
freeformType = (pkgs.formats.json {}).type;
options = {
ActivityLogRetentionDays = mkOption {
type = int;
@@ -347,7 +349,7 @@ in
};
CorsHost.string = mkOption {
type = listOf str;
default = [ "*" ];
default = ["*"];
};
DisableLiveTvChannelUserDataName = mkOption {
type = bool;
@@ -439,19 +441,19 @@ in
}
{
ItemType = "MusicVideo";
DisabledMetadataFetchers.string = [ "The Open Movie Database" ];
DisabledImageFetchers.string = [ "The Open Movie Database" ];
DisabledMetadataFetchers.string = ["The Open Movie Database"];
DisabledImageFetchers.string = ["The Open Movie Database"];
}
{
ItemType = "Series";
}
{
ItemType = "MusicAlbum";
DisabledMetadataFetchers.string = [ "TheAudioDB" ];
DisabledMetadataFetchers.string = ["TheAudioDB"];
}
{
ItemType = "MusicArtist";
DisabledMetadataFetchers.string = [ "TheAudioDB" ];
DisabledMetadataFetchers.string = ["TheAudioDB"];
}
{
ItemType = "BoxSet";
@@ -470,27 +472,27 @@ in
type = str;
};
DisabledMetadataSavers.string = mkOption {
default = [ ];
default = [];
type = listOf str;
};
LocalMetadataReaderOrder.string = mkOption {
default = [ ];
default = [];
type = listOf str;
};
DisabledMetadataFetchers.string = mkOption {
default = [ ];
default = [];
type = listOf str;
};
MetadataFetcherOrder.string = mkOption {
default = [ ];
default = [];
type = listOf str;
};
DisabledImageFetchers.string = mkOption {
default = [ ];
default = [];
type = listOf str;
};
ImageFetcherOrder.string = mkOption {
default = [ ];
default = [];
type = listOf str;
};
};
@@ -517,11 +519,13 @@ in
default = "";
};
PluginRepositories.RepositoryInfo = mkOption {
default = [{
Name = "Jellyfin Stable";
Url = "https://repo.jellyfin.org/releases/plugin/manifest-stable.json";
Enabled = true;
}];
default = [
{
Name = "Jellyfin Stable";
Url = "https://repo.jellyfin.org/releases/plugin/manifest-stable.json";
Enabled = true;
}
];
type = listOf (submodule {
options = {
Name = mkOption {
@@ -571,23 +575,23 @@ in
};
SortRemoveCharacters.string = mkOption {
type = listOf str;
default = [ "," "&" "-" "{" "}" "'" ];
default = ["," "&" "-" "{" "}" "'"];
};
SortRemoveWords.string = mkOption {
type = listOf str;
default = [ "the" "a" "an" ];
default = ["the" "a" "an"];
};
SortReplaceCharacters.string = mkOption {
type = listOf str;
default = [ "." "+" "%" ];
default = ["." "+" "%"];
};
UICulture = mkOption {
type = str;
default = "en-US";
};
};
});
default = { };
};
default = {};
};
};
};
@@ -636,12 +640,11 @@ in
touch "$jellyfin_dir/config/system.xml";
echo "$(system_file)" > "$jellyfin_dir/config/system.xml";
'';
wantedBy = [ "multi-user.target" ];
after = [ "jellyfin.service" ];
wantedBy = ["multi-user.target"];
after = ["jellyfin.service"];
serviceConfig = {
Type = "oneshot";
};
};
};
}

View File

@@ -1,11 +1,13 @@
{ config, lib, ... }:
let
cfg = config.server.jellyseerr;
in
{
imports = [ ];
options.server.jellyseerr = with lib; with lib.types; {
config,
lib,
...
}: let
cfg = config.server.jellyseerr;
in {
imports = [];
options.server.jellyseerr = with lib;
with lib.types; {
enable = mkEnableOption "";
domain = mkOption {
type = str;

View File

@@ -1,11 +1,13 @@
{ config, lib, ... }:
let
cfg = config.server.network;
in
{
imports = [ ];
options.server.network = with lib; with lib.types; {
config,
lib,
...
}: let
cfg = config.server.network;
in {
imports = [];
options.server.network = with lib;
with lib.types; {
enable = mkOption {
type = bool;
default = true;
@@ -27,7 +29,7 @@ in
};
nameservers = mkOption {
type = listOf str;
default = [ "1.1.1.1" "8.8.8.8" ];
default = ["1.1.1.1" "8.8.8.8"];
};
portForwarding = mkOption {
type = bool;
@@ -37,25 +39,39 @@ in
type = bool;
default = true;
};
settings = { };
settings = {};
};
config = lib.mkIf cfg.enable {
host.networking.hostName = cfg.hostName;
networking = {
dhcpcd.enable = true;
interfaces."${cfg.interface}".ipv4.addresses = [{
address = cfg.localIp;
prefixLength = 28;
}];
interfaces."${cfg.interface}".ipv4.addresses = [
{
address = cfg.localIp;
prefixLength = 28;
}
];
defaultGateway = cfg.defaultGateway;
nameservers = [
(if config.server.tailscale.enable then "100.100.100.100" else null)
] ++ cfg.nameservers;
nameservers =
[
(
if config.server.tailscale.enable
then "100.100.100.100"
else null
)
]
++ cfg.nameservers;
};
boot.kernel.sysctl."net.ipv4.ip_forward" = if cfg.portForwarding then 1 else 0;
boot.kernel.sysctl."net.ipv6.conf.all.forwarding" = if cfg.portForwarding then 1 else 0;
boot.kernel.sysctl."net.ipv4.ip_forward" =
if cfg.portForwarding
then 1
else 0;
boot.kernel.sysctl."net.ipv6.conf.all.forwarding" =
if cfg.portForwarding
then 1
else 0;
services.openssh.enable = cfg.openssh;
};

View File

@@ -1,11 +1,14 @@
{ config, lib, pkgs, ... }:
let
cfg = config.server.nextcloud;
in
{
imports = [ ];
options.server.nextcloud = with lib; with lib.types; {
config,
lib,
pkgs,
...
}: let
cfg = config.server.nextcloud;
in {
imports = [];
options.server.nextcloud = with lib;
with lib.types; {
enable = mkEnableOption "";
user = mkOption {
type = str;

View File

@@ -1,11 +1,13 @@
{ config, lib, ... }:
let
cfg = config.server.photoprism;
in
{
imports = [ ];
options.server.photoprism = with lib; with lib.types; {
config,
lib,
...
}: let
cfg = config.server.photoprism;
in {
imports = [];
options.server.photoprism = with lib;
with lib.types; {
enable = mkEnableOption "";
user = mkOption {
type = str;

View File

@@ -1,13 +1,15 @@
{ config, lib, ... }:
let
cfg = config.server.tailscale;
in
{
config,
lib,
...
}: let
cfg = config.server.tailscale;
in {
imports = [
./network.nix
];
options.server.tailscale = with lib; with lib.types; {
options.server.tailscale = with lib;
with lib.types; {
enable = mkEnableOption "";
mode = mkOption {
type = enum [
@@ -21,7 +23,7 @@ in
type = bool;
default = false;
};
settings = { };
settings = {};
};
config = lib.mkIf cfg.enable {
services.tailscale = {
@@ -29,7 +31,6 @@ in
useRoutingFeatures = cfg.mode;
};
server.network = lib.mkIf cfg.exitNode { portForwarding = lib.mkDefault true; };
server.network = lib.mkIf cfg.exitNode {portForwarding = lib.mkDefault true;};
};
}

10
secrets/decrypt.sh Executable file
View File

@@ -0,0 +1,10 @@
#!/usr/bin/env bash
export SOPS_AGE_KEY_FILE=/home/guz/.config/sops/age/keys.txt
secrets_dir="/home/guz/.nix/secrets"
sops --output $secrets_dir/homelab-lesser-secrets.decrypted.json \
-d $secrets_dir/homelab-lesser-secrets.json

View File

@@ -0,0 +1,33 @@
{
"tailnet-name": "ENC[AES256_GCM,data:f9T+/IRApqThgMlE,iv:LufRlHxdon5mahAi1+jwbhTqcOZh2bdnUubfEL6QFg0=,tag:KmJ4E0EggzQh8ZCm2fLeGw==,type:str]",
"device-ip": "ENC[AES256_GCM,data:Ed6hS/9F52UGVLpAyw==,iv:sg9iVEmZxA2lNJoc0xwLRyDzoF1Cy48wp9CQf3zOOzQ=,tag:77RYGvjgw0QdZUsPcqVTvA==,type:str]",
"homelab-domain": "ENC[AES256_GCM,data:IQAkzgxJL4WsOqJ0RA==,iv:COJSPyCP33ZJJXz1blr4CTH3DfZ9oH/Em72VWlGWtO0=,tag:sLj91nAgVtvDiRcVzqgIsg==,type:str]",
"services": {
"forgejo": {
"domain": "ENC[AES256_GCM,data:DJDExE7VVmAk4ZLhOkTfD2wBY5i1,iv:tnOgrKCpglvDyk75mnmeoiz2trmD3r3wCL2etHmALC4=,tag:rAiEK9U48cR1q+W7Zbkhvg==,type:str]",
"port": "ENC[AES256_GCM,data:ydSACw==,iv:0RWRLLCU8YyYmOmTawns2Iy+ABiBFbBqgQ10+buZNt0=,tag:3QW0NzbKeUkcfYh/5my3fA==,type:float]",
"actions-token": "ENC[AES256_GCM,data:j9hpNPmbWC2z862ca8RtmB1A62P3F3L6hS1WdpCXiCEunDvHOo9qcg==,iv:DSWebuZqdwx8R9MaJgSvIVRfUOJO7OpR3ORjgp6amzw=,tag:/bPMMWsfeoKbY47JQgC38Q==,type:str]"
},
"adguard": {
"domain": "ENC[AES256_GCM,data:QquWEbgpXY13UMV9BTXplQ5LhSgv,iv:0AstA5oaS8714QME3QK0/aiv9Khqk1bLCcFdCEPn+IA=,tag:XXIWjIjHQ0gZFSFBHU49Ag==,type:str]",
"port": "ENC[AES256_GCM,data:4JC+Dg==,iv:jACiG3MB0u8mKFhghBN3VzLBGkUYeCC58fGOuLePJ+M=,tag:Lz4Q8U24aOfjIA4tK6yZwg==,type:float]"
}
},
"sops": {
"kms": null,
"gcp_kms": null,
"azure_kv": null,
"hc_vault": null,
"age": [
{
"recipient": "age1sseqwwa7fc0ftry8njyuagdg28fkmtdwmj6m7p3etjsj83suee3shfzjyz",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBnL3dCY1VLMmoxRFRmS0Ir\nV0ppTnI0RE5ZMjcvRGNPWkNxWFdJYTBDTG00ClRGQkh1UStGTmc0RE5aNy9nL3FI\nbHJIa3hLR0ZkTjd6WkFzOFkzeFdMNUEKLS0tIDBidk93Qy9LenFlSGZ2aEpuTUFt\nWVM2eS9UdXAvbzE4eEdKMjVEM3RLdm8KKeIhk+YOKVL9Y19lLyb6/Pxv8rbewK2e\nLm96jx+LOMOCFcQGxuFKWqQbTB4br/cPvRKSY5jFmFWqVg7pCPTAzQ==\n-----END AGE ENCRYPTED FILE-----\n"
}
],
"lastmodified": "2024-04-07T21:39:36Z",
"mac": "ENC[AES256_GCM,data:29gkzrSMR1O6PRVMyBuNxRwvpAHwvY+VZxzfkzukxRdlQ8GKaskVf/xjIzTlojsRZDLRBIP7ZGfCRsmYskwfkoqEZRfQ+/LwEXdol/Lhyd+OM9aP7h+GP9+qjf4KN7JvJSzj3HNdUVJNULDmAFdRaKAkIkRl5lCrVUGZJTgfj3s=,iv:GVGchkF1YllSUePk+cIz2op50OLaeuTpizbuOOF7x0E=,tag:/ZoZllvCtKdaikA5TzsdGA==,type:str]",
"pgp": null,
"unencrypted_suffix": "_unencrypted",
"version": "3.8.1"
}
}