Compare commits

..

1 Commits

Author SHA1 Message Date
Alexander Heldt ba3309c268 pinwheel/manatee: Share org via syncthing 2025-05-18 17:14:13 +02:00
107 changed files with 514 additions and 4697 deletions
+1 -1
View File
@@ -9,7 +9,7 @@ let
flakePath = config.config-manager.flakePath; flakePath = config.config-manager.flakePath;
nixosConfiguration = config.config-manager.nixosConfiguration; nixosConfiguration = config.config-manager.nixosConfiguration;
nh = inputs.nh.packages."${pkgs.stdenv.hostPlatform.system}".default; nh = inputs.nh.packages."${pkgs.system}".default;
config-manager = config-manager =
if flakePath == "" then if flakePath == "" then
Generated
+40 -863
View File
File diff suppressed because it is too large Load Diff
+3 -56
View File
@@ -12,7 +12,8 @@
}; };
nh = { nh = {
url = "github:viperML/nh/b00a24b39944efd4ec7944f02e0bd9113d991767"; url = "github:viperML/nh";
inputs.nixpkgs.follows = "nixpkgs";
}; };
nix-gc-env.url = "github:Julow/nix-gc-env"; nix-gc-env.url = "github:Julow/nix-gc-env";
@@ -32,26 +33,11 @@
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
}; };
hyprland = {
url = "github:hyprwm/Hyprland";
inputs.nixpkgs.follows = "nixpkgs";
};
hyprland-contrib = { hyprland-contrib = {
url = "github:hyprwm/contrib"; url = "github:hyprwm/contrib";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
}; };
nix-jetbrains-plugins = {
url = "github:nix-community/nix-jetbrains-plugins";
inputs.nixpkgs.follows = "nixpkgs";
};
naviterm = {
url = "gitlab:detoxify92/naviterm";
inputs.nixpkgs.follows = "nixpkgs";
};
pppdotpm-site = { pppdotpm-site = {
url = "git+ssh://gitea@git.ppp.pm:1122/alex/ppp.pm-site.git?ref=main"; url = "git+ssh://gitea@git.ppp.pm:1122/alex/ppp.pm-site.git?ref=main";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
@@ -59,43 +45,6 @@
whib-backend = { whib-backend = {
url = "git+ssh://gitea@git.ppp.pm:1122/alex/whib.git?ref=master"; url = "git+ssh://gitea@git.ppp.pm:1122/alex/whib.git?ref=master";
# url = "path:/home/alex/code/own/whib";
inputs.nixpkgs.follows = "nixpkgs";
};
whib-frontend = {
url = "git+ssh://gitea@git.ppp.pm:1122/alex/whib-react.git?ref=master";
# url = "path:/home/alex/code/own/whib-react";
inputs.nixpkgs.follows = "nixpkgs";
};
komga-comictracker = {
url = "git+ssh://gitea@git.ppp.pm:1122/alex/komga-comictracker.git?ref=main";
# url = "path:/home/alex/code/own/komga-comictracker";
inputs.nixpkgs.follows = "nixpkgs";
};
komga-bookmanager = {
url = "git+ssh://gitea@git.ppp.pm:1122/alex/komga-bookmanager.git?ref=main";
# url = "path:/home/alex/code/own/komga-bookmanager";
inputs.nixpkgs.follows = "nixpkgs";
};
komga-reading-stats = {
url = "git+ssh://gitea@git.ppp.pm:1122/alex/komga-reading-stats.git?ref=main";
# url = "path:/home/alex/code/own/komga-reading-stats";
inputs.nixpkgs.follows = "nixpkgs";
};
puppy-tracker = {
url = "git+ssh://gitea@git.ppp.pm:1122/alex/puppy-tracker.git?ref=main";
# url = "path:/home/alex/code/puppy-tracker";
inputs.nixpkgs.follows = "nixpkgs";
};
solo-referee = {
url = "git+ssh://gitea@git.ppp.pm:1122/alex/solo-referee.git?ref=main";
# url = "path:/home/alex/code/solo-referee";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
}; };
}; };
@@ -151,7 +100,6 @@
./hosts/tadpole/configuration.nix ./hosts/tadpole/configuration.nix
./hosts/tadpole/home.nix ./hosts/tadpole/home.nix
inputs.whib-backend.nixosModules.${system}.default inputs.whib-backend.nixosModules.${system}.default
inputs.whib-frontend.nixosModules.${system}.default
]; ];
}; };
@@ -167,7 +115,6 @@
modules = [ modules = [
./hosts/test-vm/configuration.nix ./hosts/test-vm/configuration.nix
inputs.whib-backend.nixosModules.${system}.default inputs.whib-backend.nixosModules.${system}.default
inputs.whib-frontend.nixosModules.${system}.default
]; ];
}; };
}; };
@@ -179,7 +126,7 @@
in in
{ {
${system}.default = pkgs.mkShell { ${system}.default = pkgs.mkShell {
packages = [ pkgs.nixfmt ]; packages = [ pkgs.nixfmt-rfc-style ];
}; };
}; };
}; };
-7
View File
@@ -22,13 +22,6 @@
alsa.enable = true; alsa.enable = true;
alsa.support32Bit = true; alsa.support32Bit = true;
pulse.enable = true; pulse.enable = true;
extraConfig.pipewire."90-hdmi-fix" = {
"context.properties" = {
"default.clock.rate" = 48000;
"default.clock.allowed-rates" = [ 48000 ];
};
};
}; };
hardware = { hardware = {
+1 -1
View File
@@ -8,7 +8,7 @@
}; };
environment.systemPackages = [ environment.systemPackages = [
inputs.agenix.packages."${pkgs.stdenv.hostPlatform.system}".default inputs.agenix.packages."${pkgs.system}".default
]; ];
}; };
} }
-5
View File
@@ -38,11 +38,6 @@ in
efi.canTouchEfiVariables = true; efi.canTouchEfiVariables = true;
}; };
extraModprobeConfig = ''
options snd-intel-dspcfg dsp_driver=1
options snd_hda_intel power_save=0 power_save_controller=N
'';
}; };
}; };
} }
@@ -0,0 +1,32 @@
{ lib, config, ... }:
let
enabled = config.mod.calibre-web.enable;
in
{
options = {
mod.calibre-web = {
enable = lib.mkEnableOption "add calibre-web module";
};
};
config = lib.mkIf enabled {
services = {
calibre-web = {
enable = true;
user = "alex";
group = "users";
listen = {
ip = "0.0.0.0";
port = 8083;
};
options = {
calibreLibrary = "/home/alex/sync/reading-material/books";
enableBookUploading = true;
};
};
};
};
}
+1
View File
@@ -15,6 +15,7 @@ in
nginx.enable = true; nginx.enable = true;
syncthing.enable = true; syncthing.enable = true;
restic.enable = true; restic.enable = true;
calibre-web.enable = true;
}; };
}; };
} }
@@ -2,8 +2,6 @@
{ {
home-manager.users.alex = { home-manager.users.alex = {
home.packages = [ home.packages = [
pkgs.nethack
pkgs.moonlight-qt pkgs.moonlight-qt
pkgs.pcsx2 pkgs.pcsx2
+3
View File
@@ -4,3 +4,6 @@
[url "git@github.com:"] [url "git@github.com:"]
insteadOf = https://github.com/ insteadOf = https://github.com/
[url "git@codeberg.org:"]
insteadOf = https://codeberg.org/
+8 -8
View File
@@ -8,14 +8,6 @@
}; };
}; };
desktopManager = {
gnome.enable = true;
};
displayManager = {
gdm.enable = true;
};
xserver = { xserver = {
enable = true; enable = true;
@@ -23,6 +15,14 @@
layout = "se"; layout = "se";
variant = ""; variant = "";
}; };
desktopManager = {
gnome.enable = true;
};
displayManager = {
gdm.enable = true;
};
}; };
}; };
+22 -29
View File
@@ -3,40 +3,33 @@
networking = { networking = {
hostName = "backwards"; hostName = "backwards";
wireless.enable = false; networkmanager.enable = false;
networkmanager = { #wireless.networks are defined in the secret `wpa_supplicant.conf`
wireless = {
enable = true; enable = true;
wifi.backend = "iwd"; secretsFile = config.age.secrets.wireless-network-secrets.path;
ensureProfiles = { networks = {
environmentFiles = [ "w1-f1_5G" = {
config.age.secrets.wireless-network-secrets.path pskRaw = "ext:w1-f1_psk";
]; };
};
};
profiles = { defaultGateway = "192.168.50.1";
w1-f1_5G = { nameservers = [ "1.1.1.1" ];
connection = { interfaces = {
id = "w1-f1_5G"; wlp1s0 = {
type = "wifi"; useDHCP = false;
interface-name = "wlp1s0"; ipv4 = {
}; addresses = [
wifi = { {
ssid = "w1-f1_5G"; address = "192.168.50.202";
mode = "infrastructure"; prefixLength = 24;
}; }
wifi-security = { ];
key-mgmt = "wpa-psk";
psk = "$w1_f1_psk";
};
ipv4 = {
method = "manual";
addresses = "192.168.50.202/24";
gateway = "192.168.50.1";
dns = "1.1.1.1";
};
};
}; };
}; };
}; };
+16 -13
View File
@@ -35,22 +35,12 @@ in
identityFile = "/home/alex/.ssh/alex.backwards-git.ppp.pm"; identityFile = "/home/alex/.ssh/alex.backwards-git.ppp.pm";
}; };
"*" = { "codeberg.org" = {
forwardAgent = false; hostname = "codeberg.org";
addKeysToAgent = "no"; identityFile = "/home/alex/.ssh/alex.backwards-codeberg.org";
compression = false;
serverAliveInterval = 0;
serverAliveCountMax = 3;
hashKnownHosts = false;
userKnownHostsFile = "~/.ssh/known_hosts";
controlMaster = "no";
controlPath = "~/.ssh/master-%r@%n:%p";
controlPersist = "no";
}; };
}; };
}; };
home.packages = [ pkgs.sshfs ];
}; };
environment.etc."ssh/authorized_keys_command" = { environment.etc."ssh/authorized_keys_command" = {
@@ -131,6 +121,19 @@ in
owner = "alex"; owner = "alex";
group = "users"; group = "users";
}; };
"alex.backwards-codeberg.org" = {
file = ../../../../secrets/backwards/alex.backwards-codeberg.org.age;
path = "/home/alex/.ssh/alex.backwards-codeberg.org";
owner = "alex";
group = "users";
};
"alex.backwards-codeberg.org.pub" = {
file = ../../../../secrets/backwards/alex.backwards-codeberg.org.pub.age;
path = "/home/alex/.ssh/alex.backwards-codeberg.org.pub";
owner = "alex";
group = "users";
};
}; };
}; };
} }
@@ -85,6 +85,21 @@ in
}; };
}; };
ttrpg = {
path = "/home/alex/sync/reading-material/ttrpg";
devices = [
"phone"
"tablet"
"pinwheel"
];
versioning = {
type = "staggered";
params = {
maxAge = "2592000"; # 30 days
};
};
};
"phone-gps" = { "phone-gps" = {
path = "/home/alex/sync/phone-gps"; path = "/home/alex/sync/phone-gps";
devices = [ "phone" ]; devices = [ "phone" ];
-3
View File
@@ -2,7 +2,6 @@
{ {
imports = [ imports = [
../../config-manager/default.nix ../../config-manager/default.nix
../../shared-modules/syncthing.nix
./hardware-configuration.nix ./hardware-configuration.nix
./disk-config.nix ./disk-config.nix
./modules ./modules
@@ -23,8 +22,6 @@
]; ];
}; };
environment.variables.EDITOR = "vim";
environment.systemPackages = with pkgs; [ environment.systemPackages = with pkgs; [
vim vim
git git
-45
View File
@@ -79,16 +79,11 @@
{ device = config.disko.devices.disk.root.device; } { device = config.disko.devices.disk.root.device; }
{ device = config.disko.devices.disk.disk1.device; } { device = config.disko.devices.disk.disk1.device; }
{ device = config.disko.devices.disk.disk2.device; } { device = config.disko.devices.disk.disk2.device; }
{ device = config.disko.devices.disk.disk3.device; }
{ device = config.disko.devices.disk.disk4.device; }
]; ];
}; };
services.zfs.autoScrub.enable = true; services.zfs.autoScrub.enable = true;
# Don't force-import the pool if it appears in use elsewhere; safer default in 26.11+.
boot.zfs.forceImportRoot = false;
networking.hostId = "0a9474e7"; # Required by ZFS networking.hostId = "0a9474e7"; # Required by ZFS
disko.devices = { disko.devices = {
disk = { disk = {
@@ -154,39 +149,6 @@
}; };
}; };
}; };
disk3 = {
type = "disk";
device = "/dev/disk/by-id/ata-TOSHIBA_MG10ACA20TE_85K2A0UCF4MJ";
content = {
type = "gpt";
partitions = {
zfs = {
size = "100%";
content = {
type = "zfs";
pool = "storage";
};
};
};
};
};
disk4 = {
type = "disk";
device = "/dev/disk/by-id/ata-TOSHIBA_MG10ACA20TE_85K2A0V6F4MJ";
content = {
type = "gpt";
partitions = {
zfs = {
size = "100%";
content = {
type = "zfs";
pool = "storage";
};
};
};
};
};
}; };
zpool = { zpool = {
@@ -204,13 +166,6 @@
"disk2" "disk2"
]; ];
} }
{
mode = "mirror";
members = [
"disk3"
"disk4"
];
}
]; ];
}; };
}; };
-7
View File
@@ -26,13 +26,6 @@
boot.kernelModules = [ "kvm-intel" ]; boot.kernelModules = [ "kvm-intel" ];
boot.extraModulePackages = [ ]; boot.extraModulePackages = [ ];
swapDevices = [
{
device = "/swapfile";
size = 32 * 1024; # 32GB
}
];
# Enables DHCP on each ethernet and wireless interface. In case of scripted networking # 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 # (the default) this is the recommended approach. When using systemd-networkd it's
# still possible to use this option, but it's recommended to use it in conjunction # still possible to use this option, but it's recommended to use it in conjunction
+2 -6
View File
@@ -1,4 +1,4 @@
{ inputs, pkgs, ... }: { inputs, ... }:
{ {
imports = [ inputs.home-manager.nixosModules.home-manager ]; imports = [ inputs.home-manager.nixosModules.home-manager ];
@@ -13,11 +13,7 @@
home.username = "alex"; home.username = "alex";
home.homeDirectory = "/home/alex"; home.homeDirectory = "/home/alex";
home.packages = [ home.packages = [ ];
pkgs.streamrip
pkgs.claude-code
pkgs.wl-clipboard
];
home.stateVersion = "24.11"; home.stateVersion = "24.11";
}; };
+1 -1
View File
@@ -8,7 +8,7 @@
}; };
environment.systemPackages = [ environment.systemPackages = [
inputs.agenix.packages."${pkgs.stdenv.hostPlatform.system}".default inputs.agenix.packages."${pkgs.system}".default
]; ];
}; };
} }
@@ -10,14 +10,6 @@ in
}; };
config = lib.mkIf enabled { config = lib.mkIf enabled {
mod.homepage.services = [
{
name = "Audiobookshelf";
port = 8000;
description = "Audiobooks & podcasts";
}
];
users.users.audiobookshelf = { users.users.audiobookshelf = {
isSystemUser = true; isSystemUser = true;
description = "audiobookshelf"; description = "audiobookshelf";
-70
View File
@@ -1,70 +0,0 @@
{ config, ... }:
{
security.acme = {
acceptTerms = true;
defaults = {
email = "acme@ppp.pm";
};
certs = {
"ha.ppp.pm" = {
dnsProvider = "hetzner";
environmentFile = config.age.secrets.hetzner-dns.path;
group = "nginx";
extraLegoFlags = [
"--dns.resolvers=1.1.1.1:53,8.8.8.8:53"
"--dns.propagation-wait=60s" # Wait for 60 seconds for DNS propagation
"--dns-timeout=60"
"--http-timeout=60"
];
};
"komga.ppp.pm" = {
dnsProvider = "hetzner";
environmentFile = config.age.secrets.hetzner-dns.path;
group = "nginx";
extraLegoFlags = [
"--dns.resolvers=1.1.1.1:53,8.8.8.8:53"
"--dns.propagation-wait=60s"
"--dns-timeout=60"
"--http-timeout=60"
];
};
"romm.ppp.pm" = {
dnsProvider = "hetzner";
environmentFile = config.age.secrets.hetzner-dns.path;
group = "nginx";
extraLegoFlags = [
"--dns.resolvers=1.1.1.1:53,8.8.8.8:53"
"--dns.propagation-wait=60s"
"--dns-timeout=60"
"--http-timeout=60"
];
};
"puppy.ppp.pm" = {
dnsProvider = "hetzner";
environmentFile = config.age.secrets.hetzner-dns.path;
group = "nginx";
extraLegoFlags = [
"--dns.resolvers=1.1.1.1:53,8.8.8.8:53"
"--dns.propagation-wait=60s"
"--dns-timeout=60"
"--http-timeout=60"
];
};
};
};
age = {
secrets = {
"hetzner-dns".file = ../../../../secrets/manatee/hetzner-dns.age;
};
};
}
+1 -9
View File
@@ -12,21 +12,13 @@ in
ssh.enable = true; ssh.enable = true;
git.enable = true; git.enable = true;
tmux.enable = true;
nginx.enable = true; nginx.enable = true;
syncthing.enable = true; syncthing.enable = true;
transmission.enable = true; transmission.enable = true;
audiobookshelf.enable = true; audiobookshelf.enable = true;
jellyfin.enable = true; jellyfin.enable = true;
immich.enable = false; immich.enable = true;
navidrome.enable = true;
komga.enable = true;
romm.enable = true;
homepage.enable = true;
disk-smart.enable = true;
puppy-tracker.enable = true;
solo-referee.enable = true;
}; };
}; };
} }
@@ -1,159 +0,0 @@
{
pkgs,
lib,
config,
...
}:
let
enabled = config.mod.disk-smart.enable;
disks = [
{ path = "/dev/disk/by-id/ata-ST8000VN004-3CP101_WWZ8QCG4"; name = "seagate_8tb_1"; label = "Seagate 8TB #1"; }
{ path = "/dev/disk/by-id/ata-ST8000VN004-3CP101_WWZ8QDJ5"; name = "seagate_8tb_2"; label = "Seagate 8TB #2"; }
{ path = "/dev/disk/by-id/ata-TOSHIBA_MG10ACA20TE_85K2A0UCF4MJ"; name = "toshiba_20tb_1"; label = "Toshiba 20TB #1"; }
{ path = "/dev/disk/by-id/ata-TOSHIBA_MG10ACA20TE_85K2A0V6F4MJ"; name = "toshiba_20tb_2"; label = "Toshiba 20TB #2"; }
];
outputDir = "/var/lib/disk-smart";
collectScript = pkgs.writeShellScript "disk-smart-collect" ''
set -euo pipefail
export PATH="${lib.makeBinPath [ pkgs.smartmontools pkgs.jq pkgs.coreutils ]}"
mkdir -p ${outputDir}
result="{"
${lib.concatMapStringsSep "\n" (disk: ''
raw=$(smartctl -j -A -H ${disk.path} 2>/dev/null || true)
temp=$(echo "$raw" | jq -r '.temperature.current // empty')
power_on=$(echo "$raw" | jq -r '.power_on_time.hours // empty')
smart_status=$(echo "$raw" | jq -r '.smart_status.passed // empty')
reallocated=$(echo "$raw" | jq -r '[.ata_smart_attributes.table[] | select(.name == "Reallocated_Sector_Ct")][0].raw.value // empty')
pending=$(echo "$raw" | jq -r '[.ata_smart_attributes.table[] | select(.name == "Current_Pending_Sector")][0].raw.value // empty')
result="$result\"${disk.name}\":{\"temperature\":$temp,\"power_on_hours\":$power_on,\"smart_passed\":$smart_status,\"reallocated_sectors\":$reallocated,\"pending_sectors\":$pending},"
'') disks}
# Remove trailing comma, close object
result="''${result%,}}"
echo "$result" | jq . > ${outputDir}/smart.json.tmp
mv ${outputDir}/smart.json.tmp ${outputDir}/smart.json
'';
indent = prefix: s:
lib.concatMapStringsSep "\n"
(line: if line == "" then line else prefix + line)
(lib.splitString "\n" s);
mkSensor = disk: ''
- name: "${disk.label} Temperature"
value_template: "{{ value_json.${disk.name}.temperature }}"
unit_of_measurement: "°C"
device_class: temperature
state_class: measurement
- name: "${disk.label} Power On Hours"
value_template: "{{ value_json.${disk.name}.power_on_hours }}"
unit_of_measurement: "h"
state_class: total_increasing
- name: "${disk.label} SMART Passed"
value_template: "{{ value_json.${disk.name}.smart_passed }}"
- name: "${disk.label} Reallocated Sectors"
value_template: "{{ value_json.${disk.name}.reallocated_sectors }}"
state_class: measurement
- name: "${disk.label} Pending Sectors"
value_template: "{{ value_json.${disk.name}.pending_sectors }}"
state_class: measurement
'';
sensorYaml = indent " " (lib.concatMapStrings mkSensor disks);
sectorEntities = lib.concatMap (disk: [
"sensor.${disk.name}_reallocated_sectors"
"sensor.${disk.name}_pending_sectors"
]) disks;
sectorEntitiesYaml = lib.concatMapStringsSep "\n"
(id: " - ${id}") sectorEntities;
smartPassedEntities = map (disk: "sensor.${disk.name}_smart_passed") disks;
smartPassedEntitiesYaml = lib.concatMapStringsSep "\n"
(id: " - ${id}") smartPassedEntities;
in
{
options = {
mod.disk-smart = {
enable = lib.mkEnableOption "Enable disk SMART monitoring module";
};
};
config = lib.mkIf enabled {
mod.home-assistant.extraConfig = ''
rest:
- resource: http://127.0.0.1:9633/smart.json
scan_interval: 60
sensor:
${sensorYaml}
automation disk_smart:
- alias: "Disk sector count increased"
trigger:
- platform: state
entity_id:
${sectorEntitiesYaml}
condition:
- condition: template
value_template: "{{ trigger.from_state.state | int(-1) >= 0 and trigger.to_state.state | int(0) > trigger.from_state.state | int(0) }}"
action:
- service: notify.mobile_app_pixel_9_pro
data:
title: "Disk SMART warning"
message: "{{ trigger.to_state.attributes.friendly_name }} increased from {{ trigger.from_state.state }} to {{ trigger.to_state.state }}"
- alias: "Disk SMART check failed"
trigger:
- platform: state
entity_id:
${smartPassedEntitiesYaml}
condition:
- condition: template
value_template: "{{ trigger.to_state.state | lower == 'false' }}"
action:
- service: notify.mobile_app_pixel_9_pro
data:
title: "Disk SMART FAILURE"
message: "{{ trigger.to_state.attributes.friendly_name }} reports SMART failure drive is likely failing"
'';
systemd.services.disk-smart-collect = {
description = "Collect disk SMART data";
serviceConfig = {
Type = "oneshot";
ExecStart = collectScript;
};
};
systemd.timers.disk-smart-collect = {
description = "Periodically collect disk SMART data";
wantedBy = [ "timers.target" ];
timerConfig = {
OnBootSec = "1min";
OnUnitActiveSec = "1min";
};
};
services.nginx.virtualHosts."127.0.0.1" = {
listen = [
{ addr = "127.0.0.1"; port = 9633; }
];
locations."= /smart.json" = {
alias = "${outputDir}/smart.json";
extraConfig = ''
default_type application/json;
'';
};
};
};
}
+1 -1
View File
@@ -23,7 +23,7 @@ in
{ path = ./gitconfig; } { path = ./gitconfig; }
]; ];
settings = { extraConfig = {
rerere.enable = true; rerere.enable = true;
}; };
}; };
@@ -1,300 +0,0 @@
{
pkgs,
lib,
config,
...
}:
let
nginxEnabled = config.mod.nginx.enable;
cfg = config.mod.home-assistant;
configFile = pkgs.writeText "ha-configuration.yaml" ''
# Loads default set of integrations. Do not remove.
default_config:
http:
use_x_forwarded_for: true
trusted_proxies:
- 127.0.0.1
# Load frontend themes from the themes folder
frontend:
themes: !include_dir_merge_named themes
automation: !include automations.yaml
script: !include scripts.yaml
scene: !include scenes.yaml
recorder:
purge_keep_days: 365
alert:
fridge_door:
name: Fridge is open
done_message: Fride is closed
entity_id: binary_sensor.kyldorr
state: "on"
repeat: 2
skip_first: true
notifiers:
- mobile_app_pixel_9_pro
${cfg.extraConfig}'';
btResetScript = pkgs.writeShellScript "bt-reset" ''
set -euo pipefail
export PATH="${
lib.makeBinPath [
pkgs.bluez
pkgs.util-linux
pkgs.kmod
pkgs.gnugrep
pkgs.coreutils
]
}"
logger -t bt-reset "Starting Bluetooth adapter reset..."
# Exit early if the adapter is already present and running
if hciconfig hci0 2>/dev/null | grep -q "UP RUNNING"; then
logger -t bt-reset "hci0 is already UP RUNNING nothing to do"
exit 0
fi
# If hci0 exists but isn't UP, try bringing it up
if hciconfig hci0 2>/dev/null; then
logger -t bt-reset "hci0 exists but not running bringing it up"
hciconfig hci0 up || true
sleep 2
if hciconfig hci0 2>/dev/null | grep -q "UP RUNNING"; then
logger -t bt-reset "hci0 is UP now"
systemctl restart bluetooth.service
logger -t bt-reset "bluetooth.service restarted done"
exit 0
fi
fi
# Hard reset: reload the btusb kernel module (works for USB adapters)
logger -t bt-reset "hci0 missing reloading btusb module..."
modprobe -r btusb 2>/dev/null || true
sleep 3
modprobe btusb
sleep 3
if hciconfig hci0 2>/dev/null; then
hciconfig hci0 up
logger -t bt-reset "hci0 restored after module reload"
else
logger -t bt-reset "ERROR: hci0 not found after module reload"
exit 1
fi
# Restart the bluetooth systemd service so bluetoothd picks up the adapter
systemctl restart bluetooth.service
logger -t bt-reset "bluetooth.service restarted done"
'';
in
{
options = {
mod.home-assistant = {
extraConfig = lib.mkOption {
type = lib.types.lines;
default = "";
description = "Extra YAML to append to Home Assistant's configuration.yaml";
};
};
};
config = {
mod.homepage.services = [
{
name = "Home Assistant";
port = 8123;
description = "Home automation";
}
];
hardware.bluetooth.enable = true;
# Give up and enter a `failed` state (visible in `systemctl --failed`) if the
# container restarts more than 5 times in 5 minutes, instead of crash-looping
# forever. The window is deliberately long: a slow crash-loop (~5s per attempt)
# stays under systemd's default 5-starts-per-10s limit and would otherwise
# retry indefinitely.
systemd.services.podman-homeassistant = {
startLimitIntervalSec = 300;
startLimitBurst = 5;
};
virtualisation.oci-containers = {
backend = "podman";
containers.homeassistant = {
image = "ghcr.io/home-assistant/home-assistant:stable";
volumes = [
"/home/alex/.config/home-assistant:/config"
"${configFile}:/config/configuration.yaml:ro"
# Pass in bluetooth
"/run/dbus:/run/dbus:ro"
];
environment.TZ = "Europe/Stockholm";
extraOptions = [
"--network=host"
# Allows HA to perform low-level network operations (scan/reset adapter)
"--cap-add=NET_ADMIN"
"--cap-add=NET_RAW"
# Pass in Zigbee antenna
"--device=/dev/serial/by-id/usb-Nabu_Casa_ZBT-2_9C139EAAD464-if00:/dev/ttyACM0"
];
};
};
services = {
blueman.enable = true;
nginx = lib.mkIf nginxEnabled {
recommendedProxySettings = true;
virtualHosts."ha.ppp.pm" = {
forceSSL = true;
useACMEHost = "ha.ppp.pm";
extraConfig = ''
proxy_buffering off;
'';
locations."/" = {
proxyPass = "http://127.0.0.1:8123";
proxyWebsockets = true;
};
};
};
# Trigger reset via udev when hci0 disappears
udev.extraRules = ''
ACTION=="remove", SUBSYSTEM=="bluetooth", KERNEL=="hci0", \
TAG+="systemd", ENV{SYSTEMD_WANTS}+="bt-reset.service"
'';
};
systemd = {
services = {
# Trigger reset on bluetoothd failure
bluetooth = {
unitConfig.OnFailure = [ "bt-reset.service" ];
};
bt-reset = {
description = "Reset Bluetooth adapter";
after = [ "bluetooth.service" ];
serviceConfig = {
Type = "oneshot";
ExecStart = btResetScript;
Restart = "on-failure";
RestartSec = "10s";
StartLimitIntervalSec = "120";
StartLimitBurst = 3;
};
};
};
timers.bt-reset = {
description = "Periodically reset Bluetooth adapter";
wantedBy = [ "timers.target" ];
timerConfig = {
OnBootSec = "5min"; # first run 5 min after boot
OnUnitActiveSec = "4h"; # then every 4 hours
RandomizedDelaySec = "5min";
};
};
user = {
timers = {
"update-hetzner-dns" = {
unitConfig = {
Description = "updates Hetzner DNS records";
};
timerConfig = {
Unit = "update-hetzner-dns.service";
OnCalendar = "*-*-* *:00/30:00";
Persistent = true;
};
wantedBy = [ "timers.target" ];
};
};
services = {
"update-hetzner-dns" = {
unitConfig = {
Description = "updates Hetzner DNS records";
};
serviceConfig = {
Type = "exec";
EnvironmentFile = config.age.secrets.hetzner-dns.path;
};
path = [
pkgs.curl
pkgs.coreutils
pkgs.jq
];
script = ''
SUBDOMAINS="ha komga romm puppy"
INTERFACE="enp3s0"
CURRENT_IP=$(curl -s --fail --interface "$INTERFACE" ifconfig.me)
for SUBDOMAIN in $SUBDOMAINS; do
LAST_IP_FILE="/tmp/hetzner-dns-''${SUBDOMAIN}-ip"
LAST_IP=""
if [[ -f "$LAST_IP_FILE" ]]; then
LAST_IP=$(cat "$LAST_IP_FILE")
fi
if [[ "$CURRENT_IP" == "$LAST_IP" ]]; then
echo "$SUBDOMAIN: IP unchanged, NOOP update."
else
echo "$SUBDOMAIN: Updating IP"
JSON_BODY=$(jq -n --arg ip "$CURRENT_IP" '{records: [{value: $ip}]}')
curl \
--fail \
-X POST \
-H "Authorization: Bearer $HETZNER_API_TOKEN" \
-H "Content-Type: application/json" \
-d "$JSON_BODY" \
"https://api.hetzner.cloud/v1/zones/ppp.pm/rrsets/''${SUBDOMAIN}/A/actions/set_records" \
&& echo $CURRENT_IP > $LAST_IP_FILE
fi
done
'';
};
};
};
};
age = {
secrets = {
"hetzner-dns" = {
file = ../../../../secrets/manatee/hetzner-dns.age;
owner = "alex";
group = "users";
};
};
};
};
}
-121
View File
@@ -1,121 +0,0 @@
{
pkgs,
lib,
config,
...
}:
let
enabled = config.mod.homepage.enable;
nginxEnabled = config.mod.nginx.enable;
services = config.mod.homepage.services;
serviceToCard =
svc:
let
href = if svc.url != null then svc.url else "http://manatee:${toString svc.port}";
in
''
<a class="card" href="${href}">
<div class="name">${svc.name}</div>
<div class="desc">${svc.description}</div>
<div class="port">:${toString svc.port}</div>
</a>
'';
page = pkgs.writeTextDir "index.html" ''
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>manatee</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
background: #1a1b26;
color: #c0caf5;
min-height: 100vh;
padding: 3rem 1.5rem;
}
h1 {
text-align: center;
font-size: 1.5rem;
font-weight: 400;
color: #7aa2f7;
margin-bottom: 2rem;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 1rem;
max-width: 900px;
margin: 0 auto;
}
.card {
display: block;
background: #24283b;
border: 1px solid #414868;
border-radius: 8px;
padding: 1.25rem;
text-decoration: none;
color: inherit;
transition: border-color 0.15s;
}
.card:hover { border-color: #7aa2f7; }
.name { font-size: 1.1rem; font-weight: 600; color: #c0caf5; }
.desc { font-size: 0.85rem; color: #565f89; margin-top: 0.35rem; }
.port { font-size: 0.8rem; color: #414868; margin-top: 0.5rem; font-family: monospace; }
</style>
</head>
<body>
<h1>manatee</h1>
<div class="grid">
${lib.concatMapStrings serviceToCard services}
</div>
</body>
</html>
'';
in
{
options = {
mod.homepage = {
enable = lib.mkEnableOption "Enable homepage module";
services = lib.mkOption {
type = lib.types.listOf (
lib.types.submodule {
options = {
name = lib.mkOption { type = lib.types.str; };
port = lib.mkOption { type = lib.types.port; };
description = lib.mkOption { type = lib.types.str; };
url = lib.mkOption {
type = lib.types.nullOr lib.types.str;
default = null;
description = "Link target for the card; defaults to http://manatee:<port>.";
};
};
}
);
default = [ ];
description = "Services to display on the homepage";
};
};
};
config = lib.mkIf (enabled && nginxEnabled) {
services.nginx.virtualHosts."homepage" = {
listen = [
{
addr = "0.0.0.0";
port = 9999;
}
];
root = page;
locations."/" = {
index = "index.html";
};
};
networking.firewall.allowedTCPPorts = [ 9999 ];
};
}
-8
View File
@@ -10,14 +10,6 @@ in
}; };
config = lib.mkIf enabled { config = lib.mkIf enabled {
mod.homepage.services = [
{
name = "Immich";
port = 2283;
description = "Photo library";
}
];
users.users.immich = { users.users.immich = {
isSystemUser = true; isSystemUser = true;
group = "storage"; group = "storage";
@@ -31,7 +31,6 @@ in
extraPackages = [ extraPackages = [
pkgs.intel-media-driver # Modern Intel VA-API driver (needed for N305) pkgs.intel-media-driver # Modern Intel VA-API driver (needed for N305)
pkgs.libvdpau-va-gl # VDPAU backend for VA-API GLX interop pkgs.libvdpau-va-gl # VDPAU backend for VA-API GLX interop
pkgs.intel-compute-runtime # OpenCL support
]; ];
}; };
@@ -47,18 +46,6 @@ in
}; };
}; };
mod.homepage.services = [
{
name = "Jellyfin";
port = 8096;
description = "Media streaming";
}
];
networking = {
firewall.allowedTCPPorts = [ 8096 ];
};
environment.systemPackages = [ environment.systemPackages = [
pkgs.jellyfin pkgs.jellyfin
pkgs.jellyfin-web pkgs.jellyfin-web
-153
View File
@@ -1,153 +0,0 @@
{
inputs,
pkgs,
lib,
config,
...
}:
let
enabled = config.mod.komga.enable;
nginxEnabled = config.mod.nginx.enable;
in
{
options = {
mod.komga = {
enable = lib.mkEnableOption "Enable komga module";
};
};
imports = [
inputs.komga-comictracker.nixosModules.default
inputs.komga-bookmanager.nixosModules.default
inputs.komga-reading-stats.nixosModules.default
];
config = lib.mkIf enabled {
mod.homepage.services = [
{
name = "Komga";
port = 8002;
description = "Comic library";
}
{
name = "Komga Reader";
port = 8888;
description = "Comic reader";
}
{
name = "Komga Book Manager";
port = 8686;
description = "Book manager";
}
{
name = "Komga Reading Stats";
port = 8787;
description = "Reading stats";
}
];
users.users.komga = {
isSystemUser = true;
group = "storage";
};
services.komga = {
enable = true;
user = "komga";
group = "storage";
settings = {
server.port = 8002;
komga."cors.allowed-origins" = [
"http://manatee:8888"
"https://komga.ppp.pm"
];
};
openFirewall = true;
};
services.nginx = lib.mkIf nginxEnabled {
virtualHosts."komga-reader" = {
listen = [
{
addr = "0.0.0.0";
port = 8888;
}
];
root = (pkgs.writeTextDir "komga-reader.html" (builtins.readFile ./komga-reader.html));
locations."/" = {
index = "komga-reader.html";
tryFiles = "$uri $uri/ /komga-reader.html";
};
};
virtualHosts."komga.ppp.pm" = {
forceSSL = true;
useACMEHost = "komga.ppp.pm";
locations."/" = {
proxyPass = "http://127.0.0.1:8002";
proxyWebsockets = true;
};
};
};
networking.firewall.allowedTCPPorts = [ 8888 ];
programs.comictracker = {
enable = true;
komgaUrl = "http://127.0.0.1:8002";
komgaLibraryId = "0NVZH5AK3RPE1";
secretsFile = config.age.secrets.komga-comicbooktracker-credentials.path;
};
services.komga-book-manager = {
enable = true;
port = 8686;
group = "storage";
komgaUrl = "http://127.0.0.1:8002";
credentialsFile = config.age.secrets.komga-bookmanager-credentials.path;
libraryRoot = "/mnt/media/public/books";
libraryId = "0PNE1NEPY6995";
};
services.komga-reading-stats = {
enable = true;
host = "0.0.0.0";
port = 8787;
origin = "http://manatee:8787";
anthropicApiKeyFile = config.age.secrets.komga-reading-stats-claude-api-key.path;
claudeModel = "claude-sonnet-4-6";
komga = {
url = "http://127.0.0.1:8002";
apiKeyFile = config.age.secrets.komga-reading-stats-komga-api-key.path;
};
};
age.secrets = {
"komga-comicbooktracker-credentials" = {
file = ../../../../secrets/manatee/komga-comicbooktracker-credentials.age;
owner = "alex";
group = "users";
};
"komga-bookmanager-credentials" = {
file = ../../../../secrets/manatee/komga-bookmanager-credentials.age;
owner = "alex";
group = "users";
};
"komga-reading-stats-claude-api-key" = {
file = ../../../../secrets/manatee/komga-reading-stats-claude-api-key.age;
};
"komga-reading-stats-komga-api-key" = {
file = ../../../../secrets/manatee/komga-reading-stats-komga-api-key.age;
};
};
};
}
File diff suppressed because it is too large Load Diff
@@ -1,41 +0,0 @@
{
lib,
pkgs,
config,
...
}:
let
navidromeEnabled = config.mod.navidrome.enable;
in
{
options = {
mod.navidrome = {
enable = lib.mkEnableOption "Enable navidrome module";
};
};
config = {
mod.homepage.services = lib.mkIf navidromeEnabled [
{
name = "Navidrome";
port = 4533;
description = "Music streaming";
}
];
services = lib.mkIf navidromeEnabled {
navidrome = {
enable = true;
openFirewall = true;
user = "navidrome";
group = "storage";
settings = {
Port = 4533;
Address = "0.0.0.0";
MusicFolder = "/mnt/media/public/music";
};
};
};
};
}
+1 -29
View File
@@ -1,15 +1,8 @@
{ ... }: { ... }:
let
hostAddress = "192.168.50.203";
in
{ {
networking = { networking = {
hostName = "manatee"; hostName = "manatee";
# Required for asymmetric routing (sending replies out a different interface
# than the default route). Without this, the kernel drops the return traffic.
firewall.checkReversePath = "loose";
defaultGateway = "192.168.50.1"; defaultGateway = "192.168.50.1";
nameservers = [ "1.1.1.1" ]; nameservers = [ "1.1.1.1" ];
interfaces = { interfaces = {
@@ -18,33 +11,12 @@ in
ipv4 = { ipv4 = {
addresses = [ addresses = [
{ {
address = hostAddress; address = "192.168.50.203";
prefixLength = 24; prefixLength = 24;
} }
]; ];
}; };
ipv4.routes = [
{
address = "0.0.0.0";
prefixLength = 0;
via = "192.168.50.1"; # Router
options = {
table = "100";
};
}
];
}; };
}; };
localCommands = ''
# Ensure local LAN traffic uses the main table, e.g. responds to the local machine
ip rule list | grep -q "192.168.50.0/24 lookup main" || \
ip rule add to 192.168.50.0/24 lookup main priority 4999
# All other traffic from this IP uses Table 100 (e.g. responds to router and back out)
ip rule list | grep -q "from ${hostAddress} lookup 100" || \
ip rule add from ${hostAddress} lookup 100 priority 5000
'';
}; };
} }
-6
View File
@@ -18,11 +18,5 @@ in
recommendedTlsSettings = true; recommendedTlsSettings = true;
}; };
}; };
networking = {
firewall = {
allowedTCPPorts = [ 443 ];
};
};
}; };
} }
@@ -1,63 +0,0 @@
{
inputs,
lib,
config,
...
}:
let
enabled = config.mod.puppy-tracker.enable;
nginxEnabled = config.mod.nginx.enable;
port = 8089;
in
{
options = {
mod.puppy-tracker = {
enable = lib.mkEnableOption "Enable puppy-tracker module";
};
};
imports = [
inputs.puppy-tracker.nixosModules.default
];
config = lib.mkIf enabled {
mod.homepage.services = [
{
name = "Puppy Tracker";
port = port;
description = "Sleep, meals, pees, poos";
# Login needs HTTPS (Secure cookies), so link to the public vhost.
url = "https://puppy.ppp.pm";
}
];
services.puppy-tracker = {
enable = true;
inherit port;
openFirewall = true;
# Served publicly over HTTPS via the nginx vhost below.
secureCookies = true;
# Shared registration secret; the file holds `PUPPY_INVITE_CODE=...`.
inviteCodeFile = config.age.secrets."puppy-tracker-invite-code".path;
};
services.nginx = lib.mkIf nginxEnabled {
virtualHosts."puppy.ppp.pm" = {
forceSSL = true;
useACMEHost = "puppy.ppp.pm";
# Photo uploads are up to 15 MB; give nginx headroom over its 1 MB default.
extraConfig = ''
client_max_body_size 20m;
'';
locations."/" = {
proxyPass = "http://127.0.0.1:${toString port}";
};
};
};
age.secrets."puppy-tracker-invite-code".file =
../../../../secrets/manatee/puppy-tracker-invite-code.age;
};
}
-230
View File
@@ -1,230 +0,0 @@
{
pkgs,
lib,
config,
...
}:
let
enabled = config.mod.romm.enable;
nginxEnabled = config.mod.nginx.enable;
configFile = pkgs.writeText "romm-config.yml" ''
filesystem:
skip_hash_calculation: false
exclude:
roms:
single_file:
extensions:
- xml
- txt
- nfo
- dat
- jpg
- png
names:
- '._*'
- 'Thumbs.db'
- '.DS_Store'
'';
# Give up and enter a `failed` state (visible in `systemctl --failed`) if a
# container restarts more than 5 times in 5 minutes, instead of crash-looping
# forever. The window is deliberately long: a slow crash-loop (~5s per attempt)
# stays under systemd's default 5-starts-per-10s limit and would otherwise
# retry indefinitely.
crashLoopGuard = {
startLimitIntervalSec = 300;
startLimitBurst = 5;
};
in
{
options = {
mod.romm = {
enable = lib.mkEnableOption "Enable romm module";
};
};
config = lib.mkIf enabled {
mod.homepage.services = [
{
name = "RomM";
port = 8085;
description = "ROM library manager";
}
];
systemd.tmpfiles.rules = [
"d /var/lib/romm 0755 root root -"
"d /var/lib/romm/db 0755 root root -"
"d /var/lib/romm/redis 0755 999 1000 -"
"d /var/lib/romm/resources 0755 root root -"
"d /var/lib/romm/assets 0755 root root -"
];
systemd.services.podman-romm = crashLoopGuard;
systemd.services.podman-romm-db = crashLoopGuard;
systemd.services.podman-romm-redis = crashLoopGuard;
systemd.services.romm-net = {
description = "Create Podman network for RomM";
after = [ "podman.service" ];
requires = [ "podman.service" ];
before = [
"podman-romm.service"
"podman-romm-db.service"
"podman-romm-redis.service"
];
requiredBy = [
"podman-romm.service"
"podman-romm-db.service"
"podman-romm-redis.service"
];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
ExecStart = pkgs.writeShellScript "romm-net-create" ''
${pkgs.podman}/bin/podman network exists romm-net \
|| ${pkgs.podman}/bin/podman network create romm-net
'';
ExecStop = "${pkgs.podman}/bin/podman network rm -f romm-net";
};
};
virtualisation.oci-containers = {
backend = "podman";
containers.romm-db = {
image = "mariadb:latest";
environment = {
MARIADB_DATABASE = "romm";
MARIADB_USER = "romm";
};
environmentFiles = [
config.age.secrets.romm-db-password.path
];
volumes = [
"/var/lib/romm/db:/var/lib/mysql"
];
extraOptions = [
"--network=romm-net"
];
};
containers.romm-redis = {
image = "redis:alpine";
volumes = [
"/var/lib/romm/redis:/data"
];
extraOptions = [
"--network=romm-net"
"--user=root"
];
};
containers.romm = {
image = "rommapp/romm:latest";
dependsOn = [
"romm-db"
"romm-redis"
];
environment = {
DB_HOST = "romm-db";
DB_PORT = "3306";
DB_NAME = "romm";
DB_USER = "romm";
REDIS_HOST = "romm-redis";
REDIS_PORT = "6379";
ROMM_AUTH_ENABLED = "true";
};
environmentFiles = [
config.age.secrets.romm-auth-secret-key.path
config.age.secrets.romm-db-password.path
config.age.secrets.romm-metadata-api-keys.path
];
ports = [
"127.0.0.1:8086:8080"
];
volumes = [
"${configFile}:/romm/config/config.yml:ro"
"/mnt/media/public/games:/romm/library"
"/var/lib/romm/resources:/romm/resources"
"/var/lib/romm/assets:/romm/assets"
];
extraOptions = [
"--network=romm-net"
];
};
};
services.nginx = lib.mkIf nginxEnabled {
virtualHosts."romm-local" = {
listen = [
{
addr = "0.0.0.0";
port = 8085;
}
];
extraConfig = ''
client_max_body_size 0;
'';
locations."/" = {
proxyPass = "http://127.0.0.1:8086";
proxyWebsockets = true;
};
};
virtualHosts."romm.ppp.pm" = {
forceSSL = true;
useACMEHost = "romm.ppp.pm";
extraConfig = ''
client_max_body_size 0;
'';
locations."/" = {
proxyPass = "http://127.0.0.1:8086";
proxyWebsockets = true;
};
};
};
boot.kernel.sysctl."net.ipv4.ip_forward" = 1;
networking.firewall.allowedTCPPorts = [ 8085 ];
age.secrets = {
"romm-auth-secret-key" = {
file = ../../../../secrets/manatee/romm-auth-secret-key.age;
owner = "root";
group = "root";
};
"romm-db-password" = {
file = ../../../../secrets/manatee/romm-db-password.age;
owner = "root";
group = "root";
};
"romm-metadata-api-keys" = {
file = ../../../../secrets/manatee/romm-metadata-api-keys.age;
owner = "root";
group = "root";
};
};
};
}
@@ -1,46 +0,0 @@
{
inputs,
lib,
config,
...
}:
let
enabled = config.mod.solo-referee.enable;
port = 8090;
in
{
options = {
mod.solo-referee = {
enable = lib.mkEnableOption "Enable solo-referee module";
};
};
imports = [
inputs.solo-referee.nixosModules.default
];
config = lib.mkIf enabled {
mod.homepage.services = [
{
name = "Solo Referee";
port = port;
description = "Solo-RPG tool with Claude as GM";
# url omitted → homepage links to http://manatee:${port}.
}
];
services.solo-referee = {
enable = true;
inherit port;
# Reachable on the LAN / Tailscale at manatee:${port}; no public vhost.
address = "0.0.0.0";
openFirewall = true;
# Anthropic API key kept out of the store; the file holds
# `ANTHROPIC_API_KEY=...`. Without it the offline stub GM runs.
apiKeyFile = config.age.secrets."solo-referee-api-key".path;
};
age.secrets."solo-referee-api-key".file =
../../../../secrets/manatee/solo-referee-api-key.age;
};
}
+1 -15
View File
@@ -21,26 +21,12 @@ in
home-manager.users.alex = { home-manager.users.alex = {
programs.ssh = { programs.ssh = {
enable = true; enable = true;
enableDefaultConfig = false;
settings = { matchBlocks = {
"git.ppp.pm" = { "git.ppp.pm" = {
hostname = "git.ppp.pm"; hostname = "git.ppp.pm";
identityFile = "/home/alex/.ssh/alex.manatee-git.ppp.pm"; identityFile = "/home/alex/.ssh/alex.manatee-git.ppp.pm";
}; };
"*" = {
forwardAgent = false;
addKeysToAgent = "no";
compression = false;
serverAliveInterval = 0;
serverAliveCountMax = 3;
hashKnownHosts = false;
userKnownHostsFile = "~/.ssh/known_hosts";
controlMaster = "no";
controlPath = "~/.ssh/master-%r@%n:%p";
controlPersist = "no";
};
}; };
}; };
}; };
@@ -10,14 +10,6 @@ in
}; };
config = lib.mkIf enabled { config = lib.mkIf enabled {
mod.homepage.services = [
{
name = "Syncthing";
port = 8384;
description = "File sync";
}
];
services.syncthing = { services.syncthing = {
enable = true; enable = true;
-71
View File
@@ -1,71 +0,0 @@
{
pkgs,
lib,
config,
...
}:
let
enabled = config.mod.tmux.enable;
in
{
options = {
mod.tmux = {
enable = lib.mkEnableOption "enable tmux module";
};
};
config = lib.mkIf enabled {
home-manager.users.alex = {
programs.tmux = {
enable = true;
baseIndex = 1;
keyMode = "vi";
# Allow vi mode to be enabled instantly
escapeTime = 0;
plugins = [ pkgs.tmuxPlugins.sensible ];
extraConfig = ''
set -g renumber-windows on
# https://old.reddit.com/r/tmux/comments/mesrci/tmux_2_doesnt_seem_to_use_256_colors/
set -g default-terminal "xterm-256color"
set -ga terminal-overrides ",*256col*:Tc"
set -ga terminal-overrides ',*:Ss=\E[%p1%d q:Se=\E[ q'
set-environment -g COLORTERM "truecolor"
set-option -g allow-rename off
# Remove date/time etc. on the right side
set -g status-right ""
bind r source-file ~/.config/tmux/tmux.conf \; display "Config reloaded"
# Remove accidental `suspend-client` triggers
unbind C-z
bind | split-window -h -c "#{pane_current_path}"
bind - split-window -v -c "#{pane_current_path}"
# Move panes shortcuts
bind h select-pane -L
bind j select-pane -D
bind k select-pane -U
bind l select-pane -R
# Resize panes
bind -r H resize-pane -L 5
bind -r J resize-pane -D 5
bind -r K resize-pane -U 5
bind -r L resize-pane -R 5
# Move windows
bind -r Left swap-window -t -1 \; select-window -t -1
bind -r Right swap-window -t +1 \; select-window -t +1
'';
};
};
};
}
@@ -15,14 +15,6 @@ in
}; };
config = lib.mkIf enabled { config = lib.mkIf enabled {
mod.homepage.services = [
{
name = "Transmission";
port = 9091;
description = "Torrent client";
}
];
services = { services = {
transmission = { transmission = {
enable = true; enable = true;
+1 -4
View File
@@ -17,10 +17,7 @@
users.users.alex = { users.users.alex = {
isNormalUser = true; isNormalUser = true;
description = "alex"; description = "alex";
extraGroups = [ extraGroups = [ "wheel" ];
"wheel"
"networkmanager"
];
}; };
environment.systemPackages = with pkgs; [ environment.systemPackages = with pkgs; [
+4 -6
View File
@@ -14,19 +14,17 @@
home.homeDirectory = "/home/alex"; home.homeDirectory = "/home/alex";
home.packages = [ home.packages = [
inputs.whib-backend.packages.${pkgs.stdenv.hostPlatform.system}.whib-import inputs.whib-backend.packages.${pkgs.system}.whib-import
# pkgs.beekeeper-studio pkgs.beekeeper-studio
pkgs.bitwarden-desktop
pkgs.gimp pkgs.gimp
pkgs.zip pkgs.zip
pkgs.unzip
pkgs.unar pkgs.unar
pkgs.jq pkgs.jq
pkgs.dbeaver-bin pkgs.dbeaver-bin
pkgs.htop pkgs.htop
pkgs.onlyoffice-desktopeditors pkgs.onlyoffice-bin
pkgs.wdisplays pkgs.wdisplays
pkgs.vlc
pkgs.opencode
]; ];
home.stateVersion = "23.05"; home.stateVersion = "23.05";
+1 -1
View File
@@ -11,7 +11,7 @@
}; };
environment.systemPackages = [ environment.systemPackages = [
inputs.agenix.packages."${pkgs.stdenv.hostPlatform.system}".default inputs.agenix.packages."${pkgs.system}".default
]; ];
}; };
} }
-29
View File
@@ -1,29 +0,0 @@
{
pkgs,
lib,
config,
...
}:
let
enabled = config.mod.ai.enable;
in
{
options = {
mod.ai = {
enable = lib.mkEnableOption "enable ai module";
};
};
config = lib.mkIf enabled {
home-manager.users.alex = {
home.packages = [
pkgs.claude-code
];
programs.zsh.shellAliases = {
wclaude = "CLAUDE_CONFIG_DIR=$HOME/.claude-personal claude";
pclaude = "CLAUDE_CONFIG_DIR=$HOME/.claude-work claude";
};
};
};
}
@@ -30,6 +30,12 @@ in
# Low battery notification for bluetooth devices # Low battery notification for bluetooth devices
systemd.user = systemd.user =
let let
trackpad = {
id = "battery_hid_a8o91o3doe5ofeo38_battery";
name = "trackpad";
threshold = "20";
};
headphones = { headphones = {
id = "headset_dev_38_18_4C_18_A4_6E"; id = "headset_dev_38_18_4C_18_A4_6E";
name = "headphones"; name = "headphones";
@@ -59,6 +65,7 @@ in
in in
builtins.listToAttrs ( builtins.listToAttrs (
builtins.map mkTimer [ builtins.map mkTimer [
trackpad
headphones headphones
] ]
); );
@@ -112,6 +119,7 @@ in
in in
builtins.listToAttrs ( builtins.listToAttrs (
builtins.map mkService [ builtins.map mkService [
trackpad
headphones headphones
] ]
); );
+1 -4
View File
@@ -14,7 +14,7 @@ in
nix-index.enable = false; nix-index.enable = false;
greetd.enable = true; greetd.enable = true;
hyprland.enable = true; hyprland.enable = true;
hyprlock.enable = true; swaylock.enable = true;
physlock.enable = false; physlock.enable = false;
power.enable = true; power.enable = true;
@@ -33,7 +33,6 @@ in
rust.enable = true; rust.enable = true;
scala.enable = true; scala.enable = true;
python.enable = true; python.enable = true;
gleam.enable = true;
keyboard.enable = true; keyboard.enable = true;
containers = { containers = {
@@ -42,8 +41,6 @@ in
}; };
vm.enable = true; vm.enable = true;
scripts.enable = true; scripts.enable = true;
ai.enable = true;
}; };
}; };
} }
+12 -51
View File
@@ -58,7 +58,7 @@
#+END_SRC #+END_SRC
*** This makes emacsclient startup faster in TUI-mode *** This makes emacsclient startup faster in TUI-mode
#+BEGIN_SRC emacs-lisp #+BEGIN_SRC emacs-lisp
(setq-default xterm-query-timeout 0.1) (setq-default xterm-query-timeout nil)
#+END_SRC #+END_SRC
*** Disable startup messages *** Disable startup messages
#+BEGIN_SRC emacs-lisp #+BEGIN_SRC emacs-lisp
@@ -479,34 +479,7 @@ Setup prefix for keybindings.
* Flycheck * Flycheck
#+BEGIN_SRC emacs-lisp #+BEGIN_SRC emacs-lisp
(use-package flycheck (use-package flycheck)
:preface
(defun mp-flycheck-eldoc (callback &rest _ignored)
"Print flycheck messages at point by calling CALLBACK."
(when-let ((flycheck-errors (and flycheck-mode (flycheck-overlay-errors-at (point)))))
(mapc
(lambda (err)
(funcall callback
(format "%s: %s"
(let ((level (flycheck-error-level err)))
(pcase level
('info (propertize "I" 'face 'flycheck-error-list-info))
('error (propertize "E" 'face 'flycheck-error-list-error))
('warning (propertize "W" 'face 'flycheck-error-list-warning))
(_ level)))
(flycheck-error-message err))
:thing (or (flycheck-error-id err)
(flycheck-error-group err))
:face 'font-lock-doc-face))
flycheck-errors)))
(defun mp-flycheck-prefer-eldoc ()
(add-hook 'eldoc-documentation-functions #'mp-flycheck-eldoc nil t)
(setq eldoc-documentation-strategy 'eldoc-documentation-compose-eagerly)
(setq flycheck-display-errors-function nil)
(setq flycheck-help-echo-function nil))
:hook ((flycheck-mode . mp-flycheck-prefer-eldoc)))
(use-package flycheck-eglot (use-package flycheck-eglot
:after (flycheck eglot) :after (flycheck eglot)
@@ -530,12 +503,6 @@ Setup prefix for keybindings.
(add-hook 'before-save-hook #'eglot-format-buffer -10 t)))) (add-hook 'before-save-hook #'eglot-format-buffer -10 t))))
(use-package eglot (use-package eglot
:preface
(defun mp-eglot-eldoc ()
(setq eldoc-echo-area-use-multiline-p nil)
(setq eldoc-documentation-strategy
'eldoc-documentation-compose-eagerly))
:config :config
(add-to-list 'eglot-server-programs (add-to-list 'eglot-server-programs
'(scala-mode . '(scala-mode .
@@ -544,9 +511,6 @@ Setup prefix for keybindings.
(add-to-list 'eglot-server-programs (add-to-list 'eglot-server-programs
'(nix-mode . ("nixd"))) '(nix-mode . ("nixd")))
(add-to-list 'eglot-server-programs
'(gleam-ts-mode . ("gleam" "lsp")))
(setq-default eglot-workspace-configuration (setq-default eglot-workspace-configuration
'( '(
:metals ( :metals (
@@ -555,11 +519,7 @@ Setup prefix for keybindings.
) )
) )
:custom
(eglot-code-action-indications nil)
:hook ( :hook (
(eglot-managed-mode . mp-eglot-eldoc)
(go-mode . eglot-ensure) (go-mode . eglot-ensure)
(go-mode . alex/organize-imports-on-save) (go-mode . alex/organize-imports-on-save)
(go-mode . alex/format-on-save) (go-mode . alex/format-on-save)
@@ -569,9 +529,6 @@ Setup prefix for keybindings.
(nix-mode . eglot-ensure) (nix-mode . eglot-ensure)
(nix-mode . alex/format-on-save) (nix-mode . alex/format-on-save)
(gleam-ts-mode . eglot-ensure)
(gleam-ts-mode . alex/format-on-save)
(python-mode . eglot-ensure) (python-mode . eglot-ensure)
(javascript-mode . eglot-ensure) (javascript-mode . eglot-ensure)
(js-mode . eglot-ensure) (js-mode . eglot-ensure)
@@ -588,6 +545,16 @@ Setup prefix for keybindings.
) )
) )
(use-package eglot-booster
:after eglot
:config (eglot-booster-mode))
#+END_SRC
** Eldoc-box
#+BEGIN_SRC emacs-lisp
(use-package eldoc-box
:after eglot
:bind (:map eglot-mode-map
("M-h" . eldoc-box-help-at-point)))
#+END_SRC #+END_SRC
** Go ** Go
#+BEGIN_SRC emacs-lisp #+BEGIN_SRC emacs-lisp
@@ -606,12 +573,6 @@ Setup prefix for keybindings.
) )
) )
#+END_SRC #+END_SRC
** Gleam
#+BEGIN_SRC emacs-lisp
(use-package gleam-ts-mode
:mode "\\.gleam\\'"
)
#+END_SRC
** YAML ** YAML
#+BEGIN_SRC emacs-lisp #+BEGIN_SRC emacs-lisp
(use-package yaml-mode (use-package yaml-mode
+13
View File
@@ -12,6 +12,18 @@ let
epkgs.flymake-go-staticcheck epkgs.flymake-go-staticcheck
epkgs.tree-sitter-langs epkgs.tree-sitter-langs
epkgs.treesit-grammars.with-all-grammars epkgs.treesit-grammars.with-all-grammars
(epkgs.trivialBuild {
pname = "eglot-booster";
version = "main-2024-04-11";
src = pkgs.fetchFromGitHub {
owner = "jdtsmith";
repo = "eglot-booster";
rev = "e19dd7ea81bada84c66e8bdd121408d9c0761fe6";
hash = "sha256-vF34ZoUUj8RENyH9OeKGSPk34G6KXZhEZozQKEcRNhs=";
};
})
]; ];
}; };
@@ -38,6 +50,7 @@ in
ec ec
emacs emacs
pkgs.wl-clipboard pkgs.wl-clipboard
pkgs.emacs-lsp-booster
pkgs.nixd pkgs.nixd
]; ];
}; };
+1 -1
View File
@@ -3,7 +3,7 @@
fonts.packages = [ fonts.packages = [
pkgs.noto-fonts pkgs.noto-fonts
pkgs.noto-fonts-cjk-sans pkgs.noto-fonts-cjk-sans
pkgs.noto-fonts-color-emoji pkgs.noto-fonts-emoji
pkgs.nerd-fonts.jetbrains-mono pkgs.nerd-fonts.jetbrains-mono
pkgs.liberation_ttf pkgs.liberation_ttf
]; ];
+1 -1
View File
@@ -1,4 +1,4 @@
{ ... }: { pkgs, ... }:
{ {
home-manager.users.alex = { home-manager.users.alex = {
programs.fzf = { programs.fzf = {
+2 -69
View File
@@ -6,38 +6,6 @@
}: }:
let let
enabled = config.mod.git.enable; enabled = config.mod.git.enable;
# Wrapper around `ssh-keygen` used as git's SSH signing program. Before a
# signing operation it ensures the passphrase-protected signing key is loaded
# into the agent — `ssh-keygen -Y sign` reads the key from disk and re-prompts
# every commit otherwise, since `AddKeysToAgent` only ever caches auth keys.
# Loading it once (through the GUI askpass) lets later commits reuse the
# cached key from the agent. Verification and every other op pass straight
# through to the real ssh-keygen untouched.
sshSignWrapper = pkgs.writeShellApplication {
name = "git-ssh-sign";
runtimeInputs = [
pkgs.openssh
pkgs.gawk
pkgs.gnugrep
];
text = ''
key="${config.age.secrets."alex.pinwheel-github.com-signing".path}"
case " $* " in
*" -Y sign "*)
fp=""
fp="$(ssh-keygen -lf "$key.pub" 2>/dev/null | awk '{print $2}')" || true
if [ -n "$fp" ] && ! ssh-add -l 2>/dev/null | grep -qF "$fp"; then
# </dev/null detaches stdin so ssh-add uses SSH_ASKPASS (the GUI).
ssh-add "$key" </dev/null || true
fi
;;
esac
exec ssh-keygen "$@"
'';
};
in in
{ {
options = { options = {
@@ -47,7 +15,7 @@ in
}; };
config = lib.mkIf enabled { config = lib.mkIf enabled {
home-manager.users.alex = { lib, ... }: { home-manager.users.alex = {
programs.git = { programs.git = {
enable = true; enable = true;
@@ -55,31 +23,11 @@ in
{ path = ./gitconfig; } { path = ./gitconfig; }
]; ];
signing = { extraConfig = {
key = config.age.secrets."alex.pinwheel-github.com-signing.pub".path;
signByDefault = true;
};
settings = {
rerere.enable = true; rerere.enable = true;
# Tells Git to use SSH instead of the default GPG
gpg.format = "ssh";
# Sign via a wrapper that loads the signing key into the agent on
# first use, so subsequent signed commits reuse the cached key
# instead of re-prompting for the passphrase every time.
gpg.ssh.program = "${sshSignWrapper}/bin/git-ssh-sign";
}; };
}; };
home.file.".ssh/config".target = ".ssh/config_source";
home.activation.sshConfig = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
run cat ~/.ssh/config_source > ~/.ssh/config
run chmod 600 ~/.ssh/config
'';
home.packages = [ pkgs.tig ]; home.packages = [ pkgs.tig ];
home.file.".tigrc".text = '' home.file.".tigrc".text = ''
@@ -87,20 +35,5 @@ in
set main-view-line-number-interval = 1 set main-view-line-number-interval = 1
''; '';
}; };
age.secrets = {
"alex.pinwheel-github.com-signing" = {
file = ../../../../secrets/pinwheel/alex.pinwheel-github.com-signing.age;
path = "/home/alex/.ssh/alex.pinwheel-github.com-signing";
owner = "alex";
group = "users";
};
"alex.pinwheel-github.com-signing.pub" = {
file = ../../../../secrets/pinwheel/alex.pinwheel-github.com-signing.pub.age;
path = "/home/alex/.ssh/alex.pinwheel-github.com-signing.pub";
owner = "alex";
group = "users";
};
};
}; };
} }
+3
View File
@@ -5,5 +5,8 @@
[url "git@github.com:"] [url "git@github.com:"]
insteadOf = https://github.com/ insteadOf = https://github.com/
[url "git@codeberg.org:"]
insteadOf = https://codeberg.org/
[url "gitea@git.ppp.pm:"] [url "gitea@git.ppp.pm:"]
insteadOf = https://git.ppp.pm/ insteadOf = https://git.ppp.pm/
-25
View File
@@ -1,25 +0,0 @@
{
pkgs,
lib,
config,
...
}:
let
enabled = config.mod.gleam.enable;
in
{
options = {
mod.gleam = {
enable = lib.mkEnableOption "enable gleam module";
};
};
config = lib.mkIf enabled {
home-manager.users.alex = {
home.packages = [
pkgs.gleam
pkgs.erlang
];
};
};
}
+1 -3
View File
@@ -20,9 +20,7 @@ in
enable = true; enable = true;
package = pkgs.go; package = pkgs.go;
env = { goPath = "code/go";
GOPATH = "/home/alex/code/go";
};
}; };
home.packages = [ home.packages = [
+1 -1
View File
@@ -22,7 +22,7 @@ in
let let
session = { session = {
user = "alex"; user = "alex";
command = "uwsm start hyprland-uwsm.desktop"; command = "${pkgs.hyprland}/bin/Hyprland";
}; };
in in
{ {
+96 -123
View File
@@ -1,5 +1,4 @@
{ {
inputs,
pkgs, pkgs,
lib, lib,
config, config,
@@ -7,71 +6,6 @@
}: }:
let let
enabled = config.mod.hyprland.enable; enabled = config.mod.hyprland.enable;
monitorScript = pkgs.writeShellScript "hyprland-monitor-handler" ''
INTERNAL="eDP-1"
EXTERNAL_MONITORS="HDMI-A-1 DP-3"
HYPRCTL="${pkgs.hyprland}/bin/hyprctl"
JQ="${pkgs.jq}/bin/jq"
get_active_external() {
# Return the first connected external monitor
for mon in $EXTERNAL_MONITORS; do
if $HYPRCTL monitors -j | $JQ -e ".[] | select(.name == \"$mon\")" > /dev/null 2>&1; then
echo "$mon"
return 0
fi
done
return 1
}
bind_workspaces() {
local external batch=""
if external=$(get_active_external); then
# External monitor connected: move workspaces 1-5 to external, 6-10 to internal
for ws in 1 2 3 4 5; do
batch="$batch dispatch moveworkspacetomonitor $ws $external;"
done
for ws in 6 7 8 9 10; do
batch="$batch dispatch moveworkspacetomonitor $ws $INTERNAL;"
done
else
# No external monitor: move all workspaces to internal
for ws in 1 2 3 4 5 6 7 8 9 10; do
batch="$batch dispatch moveworkspacetomonitor $ws $INTERNAL;"
done
fi
$HYPRCTL --batch "$batch"
}
handle_event() {
case $1 in
monitoradded*|monitorremoved*)
sleep 0.5
bind_workspaces
;;
esac
}
# Start the event listener first so monitoradded events emitted during
# session startup are not lost in the gap before we begin reading them.
${pkgs.socat}/bin/socat -U - UNIX-CONNECT:"$XDG_RUNTIME_DIR/hypr/$HYPRLAND_INSTANCE_SIGNATURE/.socket2.sock" | while read -r line; do
handle_event "$line"
done &
LISTENER_PID=$!
# Give socat a moment to actually connect before the initial bind.
sleep 0.2
bind_workspaces
# Re-bind once more after the DRM subsystem has had time to enumerate
# external connectors, in case they were not yet present at session start.
(sleep 3; bind_workspaces) &
wait $LISTENER_PID
'';
in in
{ {
options = { options = {
@@ -81,50 +15,28 @@ in
}; };
config = lib.mkIf enabled { config = lib.mkIf enabled {
programs.hyprland = {
enable = true;
withUWSM = true;
package = inputs.hyprland.packages.${pkgs.stdenv.hostPlatform.system}.hyprland;
portalPackage =
inputs.hyprland.packages.${pkgs.stdenv.hostPlatform.system}.xdg-desktop-portal-hyprland;
xwayland = {
enable = true;
};
};
home-manager.users.alex = { home-manager.users.alex = {
wayland.windowManager.hyprland = { wayland.windowManager.hyprland = {
enable = true; enable = true;
systemd.enable = false;
configType = "hyprlang"; xwayland = {
enable = true;
};
extraConfig = '' extraConfig = ''
exec-once = uwsm app -- waybar exec-once=waybar
exec-once = uwsm app -- hyprctl setcursor Adwaita 24
env = GDK_DPI_SCALE,1.5 env = GDK_DPI_SCALE,1.5
env = HYPRCURSOR_THEME,Adwaita env = XCURSOR_SIZE,64
env = HYPRCURSOR_SIZE,24
monitor=eDP-1, 1920x1200, auto-center-down, 1 monitor=eDP-1, 1920x1200, 0x0, 1
monitor=HDMI-A-1, 2560x1440@100, auto-center-up, 1
monitor=DP-3, 2560x1440@60, auto-center-up, 1
# Workspaces 1-5 on external monitors (HDMI-A-1 or DP-3) workspace = 1, monitor:HDMI-A-1
workspace = 1, monitor:HDMI-A-1, default:true
workspace = 2, monitor:HDMI-A-1 workspace = 2, monitor:HDMI-A-1
workspace = 3, monitor:HDMI-A-1 workspace = 3, monitor:HDMI-A-1
workspace = 4, monitor:HDMI-A-1 workspace = 4, monitor:HDMI-A-1
workspace = 5, monitor:HDMI-A-1 workspace = 5, monitor:HDMI-A-1
workspace = 1, monitor:DP-3, default:true workspace = 6, monitor:eDP-1
workspace = 2, monitor:DP-3
workspace = 3, monitor:DP-3
workspace = 4, monitor:DP-3
workspace = 5, monitor:DP-3
# Workspaces 6-10 on internal monitor
workspace = 6, monitor:eDP-1, default:true
workspace = 7, monitor:eDP-1 workspace = 7, monitor:eDP-1
workspace = 8, monitor:eDP-1 workspace = 8, monitor:eDP-1
workspace = 9, monitor:eDP-1 workspace = 9, monitor:eDP-1
@@ -132,14 +44,12 @@ in
workspace = w[tv1], gapsout:0, gapsin:0 workspace = w[tv1], gapsout:0, gapsin:0
workspace = f[1], gapsout:0, gapsin:0 workspace = f[1], gapsout:0, gapsin:0
windowrule = border_size 0, match:float 0, match:workspace w[tv1] windowrulev2 = bordersize 0, floating:0, onworkspace:w[tv1]
windowrule = rounding 0, match:float 0, match:workspace w[tv1] windowrulev2 = rounding 0, floating:0, onworkspace:w[tv1]
windowrule = border_size 0, match:float 0, match:workspace f[1] windowrulev2 = bordersize 0, floating:0, onworkspace:f[1]
windowrule = rounding 0, match:float 0, match:workspace f[1] windowrulev2 = rounding 0, floating:0, onworkspace:f[1]
# https://wiki.archlinux.org/title/Hyprland#Jetbrains_apps_focus_issues
windowrule = match:xwayland true, no_initial_focus on
exec-once=dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP
''; '';
settings = { settings = {
@@ -158,7 +68,6 @@ in
follow_mouse = 2; follow_mouse = 2;
sensitivity = 0.3; sensitivity = 0.3;
accel_profile = "flat";
touchpad = { touchpad = {
natural_scroll = false; natural_scroll = false;
tap-and-drag = false; tap-and-drag = false;
@@ -251,23 +160,6 @@ in
pkgs.wdisplays pkgs.wdisplays
pkgs.bc pkgs.bc
]; ];
systemd.user.services.hyprland-monitors = {
Unit = {
Description = "Hyprland monitor hotplug handler";
PartOf = [ "graphical-session.target" ];
After = [ "graphical-session.target" ];
};
Service = {
Type = "simple";
ExecStart = "${monitorScript}";
Restart = "on-failure";
RestartSec = 5;
};
Install = {
WantedBy = [ "graphical-session.target" ];
};
};
}; };
# To start electron apps like `chromium` with wayland support # To start electron apps like `chromium` with wayland support
@@ -286,6 +178,87 @@ in
# openGL is needed for wayland/hyprland # openGL is needed for wayland/hyprland
hardware.graphics.enable = true; hardware.graphics.enable = true;
boot.kernelParams = [ "i915.enable_psr=0" ]; systemd.user.services.hyprland-monitors = {
# systemctl --user restart hyprland-monitors.service
# journalctl --user -u hyprland-monitors.service -e -f
unitConfig = {
Description = "handles hyprland monitor connect/disconnect";
};
wantedBy = [ "graphical-session.target" ];
requires = [ "graphical-session.target" ];
after = [ "graphical-session.target" ];
path = [
pkgs.coreutils # to include `cat`
pkgs.waybar
pkgs.hyprland
pkgs.socat
pkgs.jq
pkgs.bc
pkgs.libnotify
];
script =
let
moveWSToMonitor =
monitor: first: last:
if last < first then
throw "'first' has to be less than or equal to 'last'"
else
builtins.genList (
n: "dispatch moveworkspacetomonitor ${builtins.toString (first + n)} ${monitor}"
) (last - first + 1);
external = moveWSToMonitor "HDMI-A-1" 1 5;
internal = moveWSToMonitor "eDPI-1" 6 10;
onlyInternal = moveWSToMonitor "eDPI-1" 1 10;
in
''
update() {
HDMI_STATUS=$(cat /sys/class/drm/card1-HDMI-A-1/status)
INTERNAL_WIDTH=1920
INTERNAL_HEIGHT=1200
if [ $HDMI_STATUS = "connected" ]; then
notify-send "Using external and laptop monitor"
hyprctl keyword monitor HDMI-A-1,preferred,0x0,1
HDMI=$(hyprctl monitors -j | jq '.[] | select(.name=="HDMI-A-1")')
HDMI_WIDTH=$(echo $HDMI | jq .width)
HDMI_HEIGHT=$(echo $HDMI | jq .height)
INTERNAL_POS_X=$(echo "($HDMI_WIDTH - $INTERNAL_WIDTH) / 2" | bc)
if (( $(echo "$INTERNAL_POS_X < 0" | bc) )); then INTERNAL_POS_X=0; fi
INTERNAL_POS_Y=$HDMI_HEIGHT
hyprctl keyword monitor eDP-1,$INTERNAL_WIDTH"x"$INTERNAL_HEIGHT,$INTERNAL_POS_X"x"$INTERNAL_POS_Y,1
hyprctl --batch "${lib.strings.concatStringsSep ";" (external ++ internal)}"
else
notify-send "Using only laptop monitor"
hyprctl --batch "keyword monitor HDMI-A,disable; keyword monitor eDP-1,$INTERNAL_WIDTH"x"$INTERNAL_HEIGHT,0x0,1"
hyprctl --batch "${lib.strings.concatStringsSep ";" onlyInternal}"
fi
}
handle() {
case $1 in
monitoradded\>\>*|monitorremoved\>\>*)
echo "handling event: \"$1\""
update ;;
esac
}
echo "Starting service with instance \"$HYPRLAND_INSTANCE_SIGNATURE\""
# Do initial configuration
update
socat -U - UNIX-CONNECT:$XDG_RUNTIME_DIR/hypr/$HYPRLAND_INSTANCE_SIGNATURE/.socket2.sock | while read -r line; do handle "$line"; done
'';
};
}; };
} }
@@ -1,6 +1,6 @@
{ pkgs, ... }: { pkgs, ... }:
{ {
home-manager.users.alex = { home-manager.users.alex = {
home.packages = [ pkgs.typescript-language-server ]; home.packages = [ pkgs.nodePackages.typescript-language-server ];
}; };
} }
+3 -2
View File
@@ -9,13 +9,14 @@ let
in in
{ {
users.users.alex.extraGroups = [ "video" ]; users.users.alex.extraGroups = [ "video" ];
programs.light.enable = true;
home-manager.users.alex = { home-manager.users.alex = {
wayland.windowManager.hyprland = lib.mkIf hyprlandEnabled { wayland.windowManager.hyprland = lib.mkIf hyprlandEnabled {
settings = { settings = {
bind = [ bind = [
", XF86MonBrightnessUp, exec, ${pkgs.brightnessctl}/bin/brightnessctl set +5%" ", XF86MonBrightnessUp, exec, ${pkgs.light}/bin/light -A 5"
", XF86MonBrightnessDown, exec, ${pkgs.brightnessctl}/bin/brightnessctl set 5%-" ", XF86MonBrightnessDown, exec, ${pkgs.light}/bin/light -U 5"
]; ];
}; };
}; };
+14 -15
View File
@@ -1,22 +1,21 @@
{ pkgs, ... }:
{ {
home-manager = { services.connman = {
users.alex = { enable = true;
home.packages = [ pkgs.networkmanager ];
};
};
networking = { wifi = {
wireless.enable = false; # Wireless is managed by networkmanager backend = "iwd";
networkmanager = {
enable = true;
wifi = {
backend = "iwd";
};
}; };
networkInterfaceBlacklist = [
"vmnet"
"vboxnet"
"virbr"
"ifb"
"ve"
"docker"
"br-"
"wg-"
];
}; };
networking = { networking = {
+1 -1
View File
@@ -2,7 +2,7 @@
{ {
home-manager.users.alex = { home-manager.users.alex = {
home.packages = [ home.packages = [
pkgs.nixfmt pkgs.nixfmt-rfc-style
pkgs.nix-tree pkgs.nix-tree
]; ];
}; };
+11 -9
View File
@@ -18,19 +18,21 @@ in
home-manager.users.alex = { home-manager.users.alex = {
home.packages = [ home.packages = [
pkgs.openvpn pkgs.openvpn
pkgs.update-systemd-resolved
]; ];
}; };
services.resolved = { services.resolved = {
enable = true; enable = false;
settings = { dnssec = "true";
Resolve = { domains = [ "~." ];
Domains = [ "~." ]; fallbackDns = [
DNSSEC = false; "1.1.1.1#one.one.one.one"
DNSOverTLS = true; "1.0.0.1#one.one.one.one"
}; ];
}; extraConfig = ''
DNSOverTLS=yes
'';
}; };
}; };
} }
+3 -3
View File
@@ -80,12 +80,12 @@ in
path = [ path = [
pkgs.coreutils # For `cat` pkgs.coreutils # For `cat`
pkgs.libnotify pkgs.libnotify
pkgs.hyprlock pkgs.swaylock
]; ];
script = script =
let let
pause-music = "${pkgs.playerctl}/bin/playerctl -a pause"; pause-music = "${pkgs.playerctl}/bin/playerctl -p spotify pause";
in in
'' ''
BATTERY_CAPACITY=$(cat /sys/class/power_supply/${lowbat.battery}/capacity) BATTERY_CAPACITY=$(cat /sys/class/power_supply/${lowbat.battery}/capacity)
@@ -103,7 +103,7 @@ in
BATTERY_STATUS=$(cat /sys/class/power_supply/${lowbat.battery}/status) BATTERY_STATUS=$(cat /sys/class/power_supply/${lowbat.battery}/status)
if [[ $BATTERY_STATUS = "Discharging" ]]; then if [[ $BATTERY_STATUS = "Discharging" ]]; then
${pause-music}; ${pkgs.hyprlock}/bin/hyprlock & sleep 0.5; systemctl suspend ${pause-music}; ${pkgs.swaylock}/bin/swaylock -f; systemctl suspend
fi fi
fi fi
''; '';
@@ -8,7 +8,7 @@
let let
hyprlandEnabled = config.mod.hyprland.enable; hyprlandEnabled = config.mod.hyprland.enable;
grimblast = inputs.hyprland-contrib.packages.${pkgs.stdenv.hostPlatform.system}.grimblast; grimblast = inputs.hyprland-contrib.packages.${pkgs.system}.grimblast;
area = "${pkgs.libnotify}/bin/notify-send 'ps: selected area' && ${grimblast}/bin/grimblast copy area"; area = "${pkgs.libnotify}/bin/notify-send 'ps: selected area' && ${grimblast}/bin/grimblast copy area";
screen = "${pkgs.libnotify}/bin/notify-send 'ps: selected screen' &&${grimblast}/bin/grimblast copy output"; screen = "${pkgs.libnotify}/bin/notify-send 'ps: selected screen' &&${grimblast}/bin/grimblast copy output";
in in
@@ -1,5 +1,4 @@
{ {
inputs,
pkgs, pkgs,
lib, lib,
config, config,
@@ -14,18 +13,18 @@ in
settings = { settings = {
bind = bind =
let let
prev = "${pkgs.playerctl}/bin/playerctl -p naviterm,spotify previous"; prev = "${pkgs.playerctl}/bin/playerctl -p spotify previous";
next = "${pkgs.playerctl}/bin/playerctl -p naviterm,spotify next"; next = "${pkgs.playerctl}/bin/playerctl -p spotify next";
in in
[ [
", XF86AudioPrev, exec, ${prev}" ", XF86AudioPrev, exec, ${prev}"
", XF86AudioNext, exec, ${next}" ", XF86AudioNext, exec, ${next}"
", XF86AudioPlay, exec, ${pkgs.playerctl}/bin/playerctl -p naviterm,spotify play-pause" ", XF86AudioPlay, exec, ${pkgs.playerctl}/bin/playerctl -p spotify play-pause"
", XF86AudioPause, exec, ${pkgs.playerctl}/bin/playerctl -p naviterm,spoitfy play-pause" ", XF86AudioPause, exec, ${pkgs.playerctl}/bin/playerctl -p spoitfy play-pause"
"$mod ALT, LEFT, exec, ${prev}" "$mod ALT, LEFT, exec, ${prev}"
"$mod ALT, RIGHT, exec, ${next}" "$mod ALT, RIGHT, exec, ${next}"
"$mod ALT, DOWN, exec, ${pkgs.playerctl}/bin/playerctl -p naviterm,spotify play-pause" "$mod ALT, DOWN, exec, ${pkgs.playerctl}/bin/playerctl -p spotify play-pause"
]; ];
}; };
}; };
@@ -33,7 +32,6 @@ in
home.packages = [ home.packages = [
pkgs.playerctl pkgs.playerctl
pkgs.spotify pkgs.spotify
inputs.naviterm.packages.${pkgs.stdenv.hostPlatform.system}.default
]; ];
}; };
+38 -44
View File
@@ -1,35 +1,8 @@
{ pkgs, ... }: { pkgs, ... }:
{ {
# Enable gnome-keyring at system level for PAM integration
services.gnome.gnome-keyring.enable = true;
# Use openssh's own ssh-agent — gcr's ssh-agent stalls signing RSA keys.
services.gnome.gcr-ssh-agent.enable = false;
programs.ssh.startAgent = true;
home-manager.users.alex = { home-manager.users.alex = {
services.gnome-keyring = {
enable = true;
components = [ "secrets" ];
};
home.sessionVariables = {
# gnome-keyring's PAM hooks export SSH_AUTH_SOCK pointing at a dead gcr
# socket (gcr-ssh-agent is disabled above), which shadows openssh's own
# agent and silently breaks passphrase caching. Force it back to the
# openssh agent started by `programs.ssh.startAgent`.
SSH_AUTH_SOCK = "$XDG_RUNTIME_DIR/ssh-agent";
# Route passphrase prompts through seahorse's GUI askpass instead of the
# terminal. `prefer` uses the GUI even when a tty is attached (ssh only
# falls back to askpass with no controlling terminal otherwise).
SSH_ASKPASS = "${pkgs.seahorse}/libexec/seahorse/ssh-askpass";
SSH_ASKPASS_REQUIRE = "prefer";
};
programs.ssh = { programs.ssh = {
enable = true; enable = true;
enableDefaultConfig = false;
matchBlocks = { matchBlocks = {
"manatee" = { "manatee" = {
@@ -46,6 +19,12 @@
port = 1122; port = 1122;
}; };
"andromeda" = {
hostname = "andromeda.a2x.se";
user = "alex";
identityFile = "/home/alex/.ssh/alex.pinwheel-andromeda";
};
"tadpole" = { "tadpole" = {
hostname = "65.21.106.222"; hostname = "65.21.106.222";
user = "alex"; user = "alex";
@@ -58,30 +37,19 @@
identityFile = "/home/alex/.ssh/alex.pinwheel-github.com"; identityFile = "/home/alex/.ssh/alex.pinwheel-github.com";
}; };
"codeberg.org" = {
hostname = "codeberg.org";
identityFile = "/home/alex/.ssh/alex.pinwheel-codeberg.org";
};
"git.ppp.pm" = { "git.ppp.pm" = {
hostname = "git.ppp.pm"; hostname = "git.ppp.pm";
identityFile = "/home/alex/.ssh/alex.pinwheel-git.ppp.pm"; identityFile = "/home/alex/.ssh/alex.pinwheel-git.ppp.pm";
}; };
"*" = {
forwardAgent = false;
addKeysToAgent = "yes";
compression = false;
serverAliveInterval = 0;
serverAliveCountMax = 3;
hashKnownHosts = false;
userKnownHostsFile = "~/.ssh/known_hosts";
controlMaster = "no";
controlPath = "~/.ssh/master-%r@%n:%p";
controlPersist = "no";
};
}; };
}; };
home.packages = [ home.packages = [ pkgs.sshfs ];
pkgs.sshfs
pkgs.seahorse # GUI for managing gnome-keyring
];
}; };
age.secrets = { age.secrets = {
@@ -124,6 +92,19 @@
group = "users"; group = "users";
}; };
"alex.pinwheel-codeberg.org" = {
file = ../../../../secrets/pinwheel/alex.pinwheel-codeberg.org.age;
path = "/home/alex/.ssh/alex.pinwheel-codeberg.org";
owner = "alex";
group = "users";
};
"alex.pinwheel-codeberg.org.pub" = {
file = ../../../../secrets/pinwheel/alex.pinwheel-codeberg.org.pub.age;
path = "/home/alex/.ssh/alex.pinwheel-codeberg.org.pub";
owner = "alex";
group = "users";
};
"alex.pinwheel-git.ppp.pm" = { "alex.pinwheel-git.ppp.pm" = {
file = ../../../../secrets/pinwheel/alex.pinwheel-git.ppp.pm.age; file = ../../../../secrets/pinwheel/alex.pinwheel-git.ppp.pm.age;
path = "/home/alex/.ssh/alex.pinwheel-git.ppp.pm"; path = "/home/alex/.ssh/alex.pinwheel-git.ppp.pm";
@@ -137,6 +118,19 @@
group = "users"; group = "users";
}; };
"alex.pinwheel-andromeda" = {
file = ../../../../secrets/pinwheel/alex.pinwheel-andromeda.age;
path = "/home/alex/.ssh/alex.pinwheel-andromeda";
owner = "alex";
group = "users";
};
"alex.pinwheel-andromeda.pub" = {
file = ../../../../secrets/pinwheel/alex.pinwheel-andromeda.pub.age;
path = "/home/alex/.ssh/alex.pinwheel-andromeda.pub";
owner = "alex";
group = "users";
};
"alex.pinwheel-tadpole" = { "alex.pinwheel-tadpole" = {
file = ../../../../secrets/pinwheel/alex.pinwheel-tadpole.age; file = ../../../../secrets/pinwheel/alex.pinwheel-tadpole.age;
path = "/home/alex/.ssh/alex.pinwheel-tadpole"; path = "/home/alex/.ssh/alex.pinwheel-tadpole";
@@ -5,13 +5,13 @@
... ...
}: }:
let let
enabled = config.mod.hyprlock.enable; enabled = config.mod.swaylock.enable;
hyprlandEnabled = config.mod.hyprland.enable; hyprlandEnabled = config.mod.hyprland.enable;
in in
{ {
options = { options = {
mod.hyprlock = { mod.swaylock = {
enable = lib.mkEnableOption "enable hyprlock module"; enable = lib.mkEnableOption "enable swaylock module";
dpmsTimeout = lib.mkOption { dpmsTimeout = lib.mkOption {
description = "timeout in seconds before DPMS is turned on"; description = "timeout in seconds before DPMS is turned on";
@@ -23,32 +23,13 @@ in
config = lib.mkIf enabled { config = lib.mkIf enabled {
home-manager.users.alex = { home-manager.users.alex = {
programs.hyprlock = { programs.swaylock = {
enable = true; enable = true;
settings = { settings = {
general = { color = "000000";
hide_cursor = true; indicator-idle-visible = false;
}; show-failed-attempts = true;
background = [
{
color = "rgb(000000)";
}
];
input-field = [
{
size = "250, 50";
position = "0, 0";
halign = "center";
valign = "center";
outline_thickness = 2;
dots_center = true;
fade_on_empty = false;
placeholder_text = "";
}
];
}; };
}; };
@@ -56,20 +37,20 @@ in
settings = { settings = {
bind = bind =
let let
pause-music = "${pkgs.playerctl}/bin/playerctl -a pause"; pause-music = "${pkgs.playerctl}/bin/playerctl -p spotify pause";
dpmsTimeout = config.mod.hyprlock.dpmsTimeout; dpmsTimeout = config.mod.swaylock.dpmsTimeout;
dpms-lock = pkgs.writeShellScript "dpms-lock" '' dpms-lock = pkgs.writeShellScript "dpms-lock" ''
${pkgs.swayidle}/bin/swayidle \ ${pkgs.swayidle}/bin/swayidle \
timeout ${dpmsTimeout} "${pkgs.hyprland}/bin/hyprctl dispatch dpms off" \ timeout ${dpmsTimeout} "${pkgs.hyprland}/bin/hyprctl dispatch dpms off" \
resume "${pkgs.hyprland}/bin/hyprctl dispatch dpms on" & resume "${pkgs.hyprland}/bin/hyprctl dispatch dpms on" &
${pkgs.hyprlock}/bin/hyprlock; ${pkgs.procps}/bin/pkill swayidle ${pkgs.swaylock}/bin/swaylock && ${pkgs.procps}/bin/pkill swayidle
''; '';
in in
[ [
"$mod, x, exec, ${pause-music}; ${dpms-lock}" "$mod, x, exec, ${pause-music}; ${dpms-lock}"
"$mod SHIFT, x, exec, ${pause-music}; ${pkgs.hyprlock}/bin/hyprlock & sleep 0.5; systemctl suspend" "$mod SHIFT, x, exec, ${pause-music}; ${pkgs.swaylock}/bin/swaylock -f; systemctl suspend"
]; ];
}; };
}; };
@@ -78,7 +59,11 @@ in
security = { security = {
polkit.enable = true; polkit.enable = true;
pam.services.hyprlock = {}; pam.services.swaylock.text = ''
# PAM configuration file for the swaylock screen locker. By default, it includes
# the 'login' configuration file (see /etc/pam.d/login)
auth include login
'';
}; };
}; };
} }
@@ -68,6 +68,17 @@
}; };
}; };
ttrpg = {
path = "/home/alex/sync/reading-material/ttrpg";
devices = [ "backwards" ];
versioning = {
type = "staggered";
params = {
maxAge = "2592000"; # 30 days
};
};
};
"phone-gps" = { "phone-gps" = {
path = "/home/alex/sync/phone-gps"; path = "/home/alex/sync/phone-gps";
devices = [ "phone" ]; devices = [ "phone" ];
+1 -17
View File
@@ -1,23 +1,7 @@
{ pkgs, ... }: { ... }:
{ {
services.tailscale.enable = true; services.tailscale.enable = true;
# Pinned to 1.96.5. 1.98.0 regressed split-DNS handling under work-vpn: the
# netmap's "resolve <tailnet>.ts.net locally via MagicDNS" hint is dropped
# when translated into systemd-resolved config, so *.ts.net queries get sent
# to a public resolver (199.247.155.53) that the corporate VPN's port-53
# egress filter blocks.
services.tailscale.package = pkgs.tailscale.overrideAttrs (_: rec {
version = "1.96.5";
src = pkgs.fetchFromGitHub {
owner = "tailscale";
repo = "tailscale";
tag = "v${version}";
hash = "sha256-vYYb+2OtuXftjGGG0zWJesHccrClB8YZpclv9KzNN/c=";
};
vendorHash = "sha256-rhuWEEN+CtumVxOw6Dy/IRxWIrZ2x6RJb6ULYwXCQc4=";
});
networking.firewall = { networking.firewall = {
checkReversePath = "loose"; checkReversePath = "loose";
allowedUDPPorts = [ 41641 ]; allowedUDPPorts = [ 41641 ];
+1 -1
View File
@@ -18,7 +18,7 @@
# https://old.reddit.com/r/tmux/comments/mesrci/tmux_2_doesnt_seem_to_use_256_colors/ # https://old.reddit.com/r/tmux/comments/mesrci/tmux_2_doesnt_seem_to_use_256_colors/
set -g default-terminal "xterm-256color" set -g default-terminal "xterm-256color"
set -ga terminal-overrides ",*256col*:Tc" set -ga terminal-overrides ",*256col*:Tc"
set -ga terminal-overrides ',*:Ss=\E[%p1%d q:Se=\E[ q' set -ga terminal-overrides "*:Ss=\E[%p1%d q:Se=\E[ q"
set-environment -g COLORTERM "truecolor" set-environment -g COLORTERM "truecolor"
set-option -g allow-rename off set-option -g allow-rename off
+1 -1
View File
@@ -18,7 +18,7 @@ in
}; };
programs.git = lib.mkIf gitEnabled { programs.git = lib.mkIf gitEnabled {
settings = { extraConfig = {
core = { core = {
editor = "vim"; editor = "vim";
}; };
+11 -28
View File
@@ -7,14 +7,14 @@
let let
hyprlandEnabled = config.mod.hyprland.enable; hyprlandEnabled = config.mod.hyprland.enable;
music-status = pkgs.writeShellScript "music-status" '' spotify-status = pkgs.writeShellScript "spotify-status" ''
STATUS=$(${pkgs.playerctl}/bin/playerctl -p naviterm,spotify status 2>&1) STATUS=$(${pkgs.playerctl}/bin/playerctl -p spotify status 2>&1)
if [ "$STATUS" = "No players found" ]; then if [ "$STATUS" = "No players found" ]; then
echo "" echo ""
else else
FORMAT="{{markup_escape(xesam:title)}} - {{markup_escape(xesam:artist)}}" FORMAT="{{markup_escape(xesam:title)}} - {{markup_escape(xesam:artist)}}"
OUTPUT=$(${pkgs.playerctl}/bin/playerctl -p naviterm,spotify metadata --format "$FORMAT") OUTPUT=$(${pkgs.playerctl}/bin/playerctl -p spotify metadata --format "$FORMAT")
case "$STATUS" in case "$STATUS" in
"Playing") "Playing")
echo "<span font='14' rise='-3000'></span> $OUTPUT" echo "<span font='14' rise='-3000'></span> $OUTPUT"
@@ -85,25 +85,11 @@ let
''; '';
work-vpn-status = pkgs.writeShellScript "work-vpn-status" '' work-vpn-status = pkgs.writeShellScript "work-vpn-status" ''
ACTIVE_ENVS="" STAGING=$(systemctl is-active openvpn-work-staging.service)
[ "$STAGING" == "active" ] && echo "WORK-VPN STAGING ON" && exit 0
STAGING_STATUS=$(systemctl is-active openvpn-work-staging.service) PRODUCTION=$(systemctl is-active openvpn-work-production.service)
if [ "$STAGING_STATUS" == "active" ]; then [ "$PRODUCTION" == "active" ] && echo "WORK-VPN PRODUCTION ON" && exit 0
ACTIVE_ENVS="S"
fi
PRODUCTION_STATUS=$(systemctl is-active openvpn-work-production.service)
if [ "$PRODUCTION_STATUS" == "active" ]; then
if [ -n "$ACTIVE_ENVS" ]; then
ACTIVE_ENVS="$ACTIVE_ENVS&amp;P"
else
ACTIVE_ENVS="P"
fi
fi
if [ -n "$ACTIVE_ENVS" ]; then
echo "WORK-VPN $ACTIVE_ENVS ON"
fi
''; '';
toggle-bt-power = pkgs.writeShellScript "toggle-bt-power" '' toggle-bt-power = pkgs.writeShellScript "toggle-bt-power" ''
@@ -140,7 +126,7 @@ in
modules-left = lib.mkIf hyprlandEnabled [ "hyprland/workspaces" ]; modules-left = lib.mkIf hyprlandEnabled [ "hyprland/workspaces" ];
modules-right = [ modules-right = [
"custom/work-vpn-status" "custom/work-vpn-status"
"custom/music" "custom/spotify"
"custom/container-status" "custom/container-status"
"custom/dunst" "custom/dunst"
"bluetooth" "bluetooth"
@@ -156,8 +142,8 @@ in
interval = 2; interval = 2;
}; };
"custom/music" = { "custom/spotify" = {
exec = music-status; exec = spotify-status;
interval = 2; interval = 2;
max-length = 70; max-length = 70;
tooltip = false; tooltip = false;
@@ -236,10 +222,7 @@ in
height = 30; height = 30;
spacing = 20; spacing = 20;
fixed-center = false; fixed-center = false;
output = [ output = [ "HDMI-A-1" ];
"HDMI-A-1"
"DP-3"
];
modules-left = lib.mkIf hyprlandEnabled [ "hyprland/workspaces" ]; modules-left = lib.mkIf hyprlandEnabled [ "hyprland/workspaces" ];
modules-right = [ modules-right = [
+29 -88
View File
@@ -1,5 +1,4 @@
{ {
inputs,
pkgs, pkgs,
lib, lib,
config, config,
@@ -12,98 +11,47 @@ let
in in
{ {
home-manager.users.alex = { home-manager.users.alex = {
# Ensure bashInteractive is first in PATH inside nix devshells.
# stdenv provides a non-interactive bash that breaks Copilot shell commands.
# Adding bashInteractive to home.packages alone isn't enough because devshell
# packages are prepended to PATH. This precmd hook runs after direnv's hook
# and re-prepends bashInteractive so it takes priority.
programs.zsh.initContent = ''
_ensure_bash_interactive() {
[[ "$PATH" == "${pkgs.bashInteractive}/bin:"* ]] || export PATH="${pkgs.bashInteractive}/bin:$PATH"
}
precmd_functions+=(_ensure_bash_interactive)
# Source the zsh-specific rc file that nix-direnv emits ($DIRENV_ZSH_RC)
# so devshell completions and zsh setup are picked up. direnv itself only
# exports env vars, so without this hook the zsh side of the devshell is
# never loaded. Guarded by LAST_LOADED_DIRENV_ZSH_RC so we don't re-source
# it on every precmd.
_nix_direnv_bridge_hook() {
if [[ -n "$DIRENV_ZSH_RC" && "$LAST_LOADED_DIRENV_ZSH_RC" != "$DIRENV_ZSH_RC" ]]; then
if [[ -f "$DIRENV_ZSH_RC" ]]; then
source "$DIRENV_ZSH_RC"
export LAST_LOADED_DIRENV_ZSH_RC="$DIRENV_ZSH_RC"
echo " direnv zsh loaded..."
fi
fi
}
autoload -Uz add-zsh-hook
add-zsh-hook precmd _nix_direnv_bridge_hook
'';
# Configure IntelliJ to exclude .direnv from indexing
home.activation.intellijIgnoreDirenv = ''
for idea_config in $HOME/.config/JetBrains/IntelliJIdea*; do
if [ -d "$idea_config" ]; then
$DRY_RUN_CMD mkdir -p "$idea_config/options"
$DRY_RUN_CMD tee "$idea_config/options/filetypes.xml" > /dev/null <<'EOF'
<application>
<component name="FileTypeManager" version="18">
<ignoreFiles list="*.pyc;*.pyo;*.rbc;*.yarb;*~;.DS_Store;.git;.hg;.svn;CVS;__pycache__;_svn;vssver.scc;vssver2.scc;.direnv" />
</component>
</application>
EOF
fi
done
'';
home.sessionVariables = { home.sessionVariables = {
GITHUB_ACTOR = "Alexander Heldt"; GITHUB_ACTOR = "Alexander Heldt";
GITHUB_TOKEN = "$(${pkgs.coreutils}/bin/cat ${config.age.secrets.work-github-token.path})"; GITHUB_TOKEN = "$(${pkgs.coreutils}/bin/cat ${config.age.secrets.work-github-token.path})";
}; };
home.packages = [ home.packages =
# (pkgs.callPackage ./pants.nix { inherit (pkgs) stdenv.hostPlatform.system; }) let
# (pkgs.callPackage ./syb-cli.nix { }) intellij = (
pkgs.jetbrains.idea-ultimate.overrideAttrs (
final: prev: {
version = "2024.2.4";
src = pkgs.fetchurl {
url = "https://download.jetbrains.com/idea/ideaIU-${final.version}.tar.gz";
sha256 = "8411fda793a20356a4982e4f18f6691839d8a471e2081ab6d8cc78b3f8b02532";
};
}
)
);
in
[
# (pkgs.callPackage ./pants.nix { inherit (pkgs) system; })
# (pkgs.callPackage ./syb-cli.nix { })
(inputs.nix-jetbrains-plugins.lib.buildIdeWithPlugins pkgs "idea" [ (pkgs.jetbrains.plugins.addPlugins intellij [ "ideavim" ])
"IdeaVIM"
"com.github.copilot"
])
pkgs.bashInteractive
(pkgs.google-cloud-sdk.withExtraComponents [ (pkgs.google-cloud-sdk.withExtraComponents [
pkgs.google-cloud-sdk.components.gke-gcloud-auth-plugin pkgs.google-cloud-sdk.components.gke-gcloud-auth-plugin
]) ])
pkgs.graphite-cli pkgs.graphite-cli
pkgs.postman pkgs.postman
pkgs.grpcurl pkgs.grpcurl
pkgs.slack # for `radio`
pkgs.go-mockery
# for `radio` pkgs.golangci-lint
pkgs.go-mockery ];
pkgs.golangci-lint
(pkgs.writeShellScriptBin "work-vpn" ''
case $1 in
up)
sudo sh -c "systemctl start openvpn-work-staging.service; systemctl start openvpn-work-production.service"
;;
down)
sudo sh -c "systemctl stop openvpn-work-staging.service; systemctl stop openvpn-work-production.service"
;;
esac
'')
];
programs.go = lib.mkIf goEnabled { programs.go = lib.mkIf goEnabled {
env = { goPrivate = [ "$(${pkgs.coreutils}/bin/cat ${config.age.secrets.work-go-private.path})" ];
GOPRIVATE = [ "$(${pkgs.coreutils}/bin/cat ${config.age.secrets.work-go-private.path})" ];
};
}; };
programs.git = lib.mkIf gitEnabled { programs.git = lib.mkIf gitEnabled {
@@ -116,13 +64,6 @@ EOF
}; };
}; };
# Needed for `copilot`
programs.nix-ld.enable = true;
programs.nix-ld.libraries = [
pkgs.stdenv.cc.cc.lib
pkgs.zlib
];
services.openvpn.servers = lib.mkIf openvpnEnabled { services.openvpn.servers = lib.mkIf openvpnEnabled {
work-staging = { work-staging = {
config = "config ${config.age.secrets.work-staging-ovpn.path}"; config = "config ${config.age.secrets.work-staging-ovpn.path}";
+2 -1
View File
@@ -1,4 +1,5 @@
{ {
system,
pkgs, pkgs,
lib, lib,
... ...
@@ -10,7 +11,7 @@ let
match = match =
v: l: builtins.elemAt (lib.lists.findFirst (x: (if_let v (builtins.elemAt x 0)) != null) null l) 1; v: l: builtins.elemAt (lib.lists.findFirst (x: (if_let v (builtins.elemAt x 0)) != null) null l) 1;
package = match { platform = pkgs.stdenv.hostPlatform.system; } [ package = match { platform = system; } [
[ [
{ platform = "aarch64-linux"; } { platform = "aarch64-linux"; }
{ {
-8
View File
@@ -56,14 +56,6 @@ in
initContent = lib.strings.concatStringsSep "\n" [ initContent = lib.strings.concatStringsSep "\n" [
"export KEYTIMEOUT=1" "export KEYTIMEOUT=1"
# Point every interactive shell at openssh's ssh-agent. home-manager's
# session vars set this too, but hm-session-vars runs once and is then
# inherited — so a tmux server that outlives this change (or started
# with the stale gcr socket) hands new panes a dead SSH_AUTH_SOCK.
# Re-exporting the fixed path per-shell keeps every pane on the same
# agent, so each key is only ever prompted for once per session.
''export SSH_AUTH_SOCK="$XDG_RUNTIME_DIR/ssh-agent"''
"bindkey -v '^?' backward-delete-char" "bindkey -v '^?' backward-delete-char"
"bindkey '^a' beginning-of-line" "bindkey '^a' beginning-of-line"
"bindkey '^e' end-of-line" "bindkey '^e' end-of-line"
+1 -1
View File
@@ -8,7 +8,7 @@
}; };
environment.systemPackages = [ environment.systemPackages = [
inputs.agenix.packages."${pkgs.stdenv.hostPlatform.system}".default inputs.agenix.packages."${pkgs.system}".default
]; ];
}; };
} }
-5
View File
@@ -18,11 +18,6 @@
group = "nginx"; group = "nginx";
}; };
"whib.ppp.pm" = {
webroot = "/var/lib/acme/acme-challenge/";
group = "nginx";
};
"api.whib.ppp.pm" = { "api.whib.ppp.pm" = {
webroot = "/var/lib/acme/acme-challenge/"; webroot = "/var/lib/acme/acme-challenge/";
group = "nginx"; group = "nginx";
-1
View File
@@ -23,7 +23,6 @@ in
pppdotpm-site.enable = true; pppdotpm-site.enable = true;
whib-backend.enable = true; whib-backend.enable = true;
whib-frontend.enable = true;
}; };
}; };
} }
-4
View File
@@ -67,10 +67,6 @@ in
HTTP_PORT = 3001; HTTP_PORT = 3001;
}; };
oauth2 = {
JWT_CLAIM_ISSUER = "https://${gitDomain}/";
};
database = { database = {
type = "sqlite3"; type = "sqlite3";
passwordFile = config.age.secrets.gitea-dbpassword.path; passwordFile = config.age.secrets.gitea-dbpassword.path;
+17 -13
View File
@@ -28,17 +28,9 @@ in
identityFile = "/home/alex/.ssh/alex.tadpole-git.ppp.pm"; identityFile = "/home/alex/.ssh/alex.tadpole-git.ppp.pm";
}; };
"*" = { "codeberg.org" = {
forwardAgent = false; hostname = "codeberg.org";
addKeysToAgent = "no"; identityFile = "/home/alex/.ssh/alex.tadpole-codeberg.org";
compression = false;
serverAliveInterval = 0;
serverAliveCountMax = 3;
hashKnownHosts = false;
userKnownHostsFile = "~/.ssh/known_hosts";
controlMaster = "no";
controlPath = "~/.ssh/master-%r@%n:%p";
controlPersist = "no";
}; };
}; };
}; };
@@ -48,7 +40,6 @@ in
mode = "0755"; mode = "0755";
text = '' text = ''
#!${pkgs.bash}/bin/bash #!${pkgs.bash}/bin/bash
[ "$1" = "alex" ] || exit 0
for file in ${authorizedKeysPath}/*; do for file in ${authorizedKeysPath}/*; do
${pkgs.coreutils}/bin/cat "$file" ${pkgs.coreutils}/bin/cat "$file"
done done
@@ -72,7 +63,7 @@ in
KbdInteractiveAuthentication = false; KbdInteractiveAuthentication = false;
}; };
authorizedKeysCommand = "/etc/ssh/authorized_keys_command %u"; authorizedKeysCommand = "/etc/ssh/authorized_keys_command";
authorizedKeysCommandUser = "root"; authorizedKeysCommandUser = "root";
}; };
}; };
@@ -110,6 +101,19 @@ in
owner = "alex"; owner = "alex";
group = "users"; group = "users";
}; };
"alex.tadpole-codeberg.org" = {
file = ../../../../secrets/tadpole/alex.tadpole-codeberg.org.age;
path = "/home/alex/.ssh/alex.tadpole-codeberg.org";
owner = "alex";
group = "users";
};
"alex.tadpole-codeberg.org.pub" = {
file = ../../../../secrets/tadpole/alex.tadpole-codeberg.org.pub.age;
path = "/home/alex/.ssh/alex.tadpole-codeberg.org.pub";
owner = "alex";
group = "users";
};
}; };
}; };
} }
+20 -35
View File
@@ -4,64 +4,49 @@
... ...
}: }:
let let
backendEnabled = config.mod.whib-backend.enable; enabled = config.mod.whib-backend.enable;
frontendEnabled = config.mod.whib-frontend.enable;
in in
{ {
options = { options = {
mod.whib-backend = { mod.whib-backend = {
enable = lib.mkEnableOption "enable WHIB backend"; enable = lib.mkEnableOption "enable WHIB backend";
}; };
mod.whib-frontend = {
enable = lib.mkEnableOption "enable WHIB frontend";
};
}; };
config = { config = lib.mkIf enabled {
assertions = [ assertions = [
{ {
assertion = backendEnabled && config.services.nginx.enable; assertion = config.services.nginx.enable;
message = "Option 'config.services.nginx' must be enabled"; message = "Option 'config.services.nginx' must be enabled";
} }
]; ];
services = { services.whib-backend = {
whib-backend = lib.mkIf backendEnabled { enable = true;
enable = true;
backend = { backend = {
domain = "api.whib.ppp.pm"; domain = "api.whib.ppp.pm";
useACMEHost = "api.whib.ppp.pm"; useACMEHost = "api.whib.ppp.pm";
environmentFile = config.age.secrets.whib-backend-env-vars.path; environmentFile = config.age.secrets.whib-backend-env-vars.path;
}; };
postgres = { postgres = {
environmentFile = config.age.secrets.whib-postgres-env-vars.path; environmentFile = config.age.secrets.whib-postgres-env-vars.path;
backup = { backup = {
interval = "*-*-* 00:00:00 UTC"; interval = "*-*-* 00:00:00 UTC";
environmentFile = config.age.secrets.whib-postgres-backup-env-vars.path; environmentFile = config.age.secrets.whib-postgres-backup-env-vars.path;
gpgPassphraseFile = config.age.secrets.whib-gpg-key.path; gpgPassphraseFile = config.age.secrets.whib-gpg-key.path;
};
};
grafana = {
domain = "grafana.whib.ppp.pm";
useACMEHost = "grafana.whib.ppp.pm";
environmentFile = config.age.secrets.whib-grafana-env-vars.path;
}; };
}; };
whib-frontend = lib.mkIf frontendEnabled { grafana = {
enable = true; domain = "grafana.whib.ppp.pm";
useACMEHost = "grafana.whib.ppp.pm";
domain = "whib.ppp.pm"; environmentFile = config.age.secrets.whib-grafana-env-vars.path;
useACMEHost = "whib.ppp.pm";
backendHost = "https://api.whib.ppp.pm";
}; };
}; };
+2 -4
View File
@@ -3,7 +3,6 @@
imports = [ imports = [
./ppp.pm-site.nix ./ppp.pm-site.nix
./whib-backend.nix ./whib-backend.nix
./whib-frontend.nix
]; ];
config = { config = {
@@ -14,7 +13,6 @@
mod = { mod = {
pppdotpm-site.enable = false; pppdotpm-site.enable = false;
whib-backend.enable = true; whib-backend.enable = true;
whib-frontend.enable = true;
}; };
users.users.a = { users.users.a = {
@@ -26,9 +24,9 @@
security.sudo.wheelNeedsPassword = false; security.sudo.wheelNeedsPassword = false;
virtualisation.vmVariant = { virtualisation.vmVariant = {
# following configuration is added only when building VM the *first* time with `build-vm` # following configuration is added only when building VM with build-vm
virtualisation = { virtualisation = {
diskSize = 8192; diskSize = 4096;
memorySize = 2048; memorySize = 2048;
cores = 3; cores = 3;
graphics = false; graphics = false;
-35
View File
@@ -1,35 +0,0 @@
{ lib, config, ... }:
let
enabled = config.mod.whib-frontend.enable;
in
{
options = {
mod.whib-frontend = {
enable = lib.mkEnableOption "enable WHIB backend";
};
};
config = lib.mkIf enabled {
services.whib-frontend = {
enable = true;
domain = "whib-frontend.local";
port = "8081";
# backendHost = "https://api.whib.ppp.pm/";
backendHost = "http://localhost:8080";
};
virtualisation.vmVariant = {
virtualisation = {
forwardPorts = [
{
# Service API
from = "host";
host.port = 8081;
guest.port = 8081;
}
];
};
};
};
}
Binary file not shown.
@@ -0,0 +1,7 @@
age-encryption.org/v1
-> ssh-ed25519 Pu0HWg eK/pdhmsF334C7rSuYsRnXCtenmlT2hOXpfW5CQEARY
odooTLu8ZQUZjCeVPZYOA6Vgb470cosE1Q1iBkE9Kc0
-> ssh-ed25519 +oNaHQ nJU52SSZ9v3+8NuXR6coSHosEYrs7T8GeZYzV/quOU4
IV5YduRGdJLy93gVwfYmwvldRXoXXX3QvAsH3ljBadw
--- 3gJg9NFmqHCrgcvgnYOeSY1H4klPEyzI+07IlKCOItc
 ¦Ì\5çܤ‰}õyñÐáAý_J§ õ“{ÂÉp± +ꜞ+¹JØeìñLìW¯å¯éµ 7šÉ=S*³¥É}fPܽқÒÉ!X©ê7úÎ3¡øœ³\`S•è‰iºözW¸À©<4Pb™ˆ—€ZI’©hH˜îÃî¦61õ'™é…ybmCmE
-9
View File
@@ -1,9 +0,0 @@
age-encryption.org/v1
-> ssh-ed25519 wkRvNA mr8aDxrNmdcxT5BEXJ32Q4DckYKidh3zblrSm8GV3hg
JngH+sfId8Z6SuXnQo9EQR0nw+y7LfdgYgI7SYisPL4
-> ssh-ed25519 +oNaHQ QI1+VLIa1sN8HSzBXoAGio7TcfxpGERw30uNlMCmejw
m3+nrTwsAb/Fg1p9JCYnc7jS9uteMO3AbUtDbKP60Dk
--- etDNlalBL2SdgfFxIhDCAWXpXcSZr+BlCoTt6yIUiBQ
èZw œ»ØÁëkáÂÎó4=UÉÜ:J
ã–›½p#"ˆà^pŠî!KÚÔ
¯’cŽC÷ô´cd¾·"g.óÒ¦ó¥_(ÄA% ¶ÝpÇÏGA2`CqÖPš¢P¾Ç$j åMººŒþ¾ƒ™_â
@@ -1,7 +0,0 @@
age-encryption.org/v1
-> ssh-ed25519 wkRvNA GW8mvnaXpspxr78xV0fKhXwHVvReyjvDc0v7uPwhuBI
Rne8JZYVhrTTesSFpRQ/IOZlFIMoX9Wmv5n1Ed7Ehv8
-> ssh-ed25519 +oNaHQ d7utzodGQ7LsD2Uht1rbT8Qq9BZp3PkJS9EDhajCjnk
qd2Vj+1TQrjEKkSVAf0cXcCdkgeN/Jbp4UrBSp3cKYQ
--- JQr5UQlutONqnTeoT/mIVZL8ME7ipUDK8zDfNcN3uhU
ø5-VŸÌ²ÂòÀ-®ªÁÅ'^žô5kú.t(d1‡)É'<u%
@@ -1,7 +0,0 @@
age-encryption.org/v1
-> ssh-ed25519 wkRvNA 5bEWAlzS34w3tMqMCTRqFoni4MKyQCcaBdEQ96BccVk
85rIfjpzL/cNx5SlpKeOUzsMl0rwvCdPgrZ0jlXE1nQ
-> ssh-ed25519 +oNaHQ d2ZZ/XQ5fgT8FMwhyfWHB5FESXM0Y9tlE1KcWwQV7HY
bCj5fLqN2zfPBcnXaJAYJYA+PXTPAdLZVy2FinZvUTM
--- vxopGi8/YrgI/VceX807yk7edWrdYgjmGYG9Zp/cuQA
žFc˜ÖÂ|ª67Ñ~uðáúÉ€Wê–ÿ’eïó‰ÍVMj5‹ÜG?³JâË_pîcf57^ƒŒ¥r@g&?„¬4ŠþÎðˆÉ‘@ƒN…œsjZ¡Šé^-âyB{\ºÔñ3«ˆýT)³ž¯uÝ\Wa\àìB)@d ™vrì8q.kÚ™˜
@@ -1,7 +0,0 @@
age-encryption.org/v1
-> ssh-ed25519 wkRvNA ykXbxHlehL+AucBCiS7NKNOWjHm51ZTbfgM7fPU+QDo
FtPBWg91yWq90n4PkzdwpIq+DnwL+nwUNzH3mQP4lgg
-> ssh-ed25519 +oNaHQ fCNig/NdBmbZqicNhSnYKz7Rmc7S2JpN+sQ50o+QFg0
AQiKSjZMsjuJGsnZWjSNePrbXBQ/f1zhNCXLznbZk1w
--- zjW49MAgjerHuHqMrSSDpgkWLD40eMSr+ZkKZ0Y9pJo
؇¯O¹Þ°}Œ/¨ïd°° ü‹ªŠÒ°ø@²ÆuJðÍz§bíïü:Yñ‚fýúë›
@@ -1,7 +0,0 @@
age-encryption.org/v1
-> ssh-ed25519 wkRvNA 1U7rIOHezvIpeb7QBkwz+NOgagrPDltkFtWY2N/hdE4
9rLqhxhc4c7cGB9hDPy9r0y5QbDp0CcYFwIWczB0mlU
-> ssh-ed25519 +oNaHQ w9QggF6DOTSMUC5n+7OKpFOM8iDSQakgx/10pIhM9ws
GOk7OQpLh5OjyZTiJQxd7hhN93EcSMnMxueNh6AwCOk
--- mzQrbeaXCVPu0MqNC0iAMLCiZbopyfYanwhsgDjFbZE
¡&ÈEÆÝ¼žÊÆ öfh(tJn´Ð†ç$^åkì[ Cå )ÉŸN¾UÚW¿üt/ræ7'NU
Binary file not shown.
Binary file not shown.
@@ -1,8 +0,0 @@
age-encryption.org/v1
-> ssh-ed25519 wkRvNA 5d22LU+2Mn6fq8SHOCwDht/ebnI2uOk6WKf+t1kwwCM
hCGnLoCy1PX5PJx2IjQnyESmtKM6wVQmyS6aHNhkb1g
-> ssh-ed25519 +oNaHQ gPUMsavbGVPOuvTtNgoDuzrT+q0I7Wbkd6QK5z4oUGc
M3HhrugFlNQkL7WxF1qrW+ocGRqOXid32AVVYLSSxPI
--- TGURCDEIuSFCfXBHxzFHA2svHES7Ubagy1uYjbWCO7g
gá¿Ó†Ò™±£Qâö
oF[H:t aÆr3úZ0ßx @:˜0Ó´¡µÂI[Õ\í=º@eâPíW€Ã†§rX,¶âÈÇ*sš$c:FlÎÙ ±z|B# päZ4ns[Ó×%\ìP±­;ÞR㣧ô$9¤7íÏÔAŠœÖ0©.xç°¾9©,ýt+ ¥ šf±AÉ)mV
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,8 @@
age-encryption.org/v1
-> ssh-ed25519 vxPbZg IKvbUY2e3wCrphoiFOKJYxyWvj4DjMlW1yv1VrZ/kBk
8sS9cNUsb8NtPHD5iKx8dfcgKxLObcZDfWJohduWEl4
-> ssh-ed25519 +oNaHQ nIY6nLvP+HIUlCumO/MxGoBGfutwfwv4zlvsLCuu2C8
eFqY9TaVsYoKulu5e++WI1zYzoBinEVUJt/lDan+ttQ
--- 67CbghCaK4yhjqH6vHEUzfeFth9PmX8h+qTKHocKGzk
iFÞ0ƒV•5œ1²·‹7¡~w&Õ´>¨Žƒ1¶bƒ…Å化ÆgŽ«cì6Fbs5àhk6¥W?0Ô@­ ûÞó³|CÑ ¼Ämð›GÍ1‹.½
0Á¨#wŠ¢A°]‚½!é¡s%´â Ðp

Some files were not shown because too many files have changed in this diff Show More