aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCollin Williams <96917990+bluedragon1221@users.noreply.github.com>2026-05-22 10:23:58 -0500
committerCollin Williams <96917990+bluedragon1221@users.noreply.github.com>2026-05-22 10:24:06 -0500
commit35af4bab99da94845ebd6e9b2efae6a544d39b35 (patch)
tree47c6f76b47f1012932729c9f693316df1512197a
parent2e0c6ccbbc76cc36e92128aea44e4bdd4e5c3d0a (diff)
LOTS OF TEMP STUFF
-rw-r--r--broken_stuff11
-rw-r--r--flake.lock96
-rw-r--r--flake.nix2
-rw-r--r--hosts.toml7
-rw-r--r--hosts/ganymede/caddy.nix2
-rw-r--r--hosts/ganymede/config.nix69
-rw-r--r--hosts/ganymede/nixos.nix5
-rw-r--r--hosts/ganymede/secrets/wireguard-pk.age11
-rw-r--r--hosts/ganymede/secrets/wireguard-privkey.age8
-rw-r--r--hosts/mercury/ai.nix8
-rw-r--r--hosts/mercury/config.nix23
-rw-r--r--hosts/mercury/disks.nix12
-rw-r--r--hosts/mercury/hjem.nix6
-rw-r--r--hosts/mercury/nixos.nix86
-rw-r--r--hosts/mercury/secrets/ts-key.agebin0 -> 274 bytes
-rw-r--r--hosts/mercury/secrets/wireguard-privkey.age6
-rw-r--r--justfile7
-rw-r--r--modules/desktop/hjem/programs/firefox.nix8
-rw-r--r--modules/desktop/hjem/programs/foot.nix3
-rw-r--r--modules/desktop/hjem/wm/sway.nix9
-rw-r--r--modules/desktop/nixos/fonts.nix2
-rw-r--r--modules/desktop/nixos/programs/firefox.nix19
-rw-r--r--modules/desktop/options.nix12
-rw-r--r--modules/nix/nixos/default.nix4
-rw-r--r--modules/services/nixos/default.nix5
-rw-r--r--modules/services/nixos/ganyupload/AGENTS.md65
-rw-r--r--modules/services/nixos/ganyupload/main.go212
-rw-r--r--modules/services/nixos/jta/main.go30
-rw-r--r--modules/services/nixos/minecraft.nix12
-rw-r--r--modules/services/nixos/ngircd.nix8
-rw-r--r--modules/services/nixos/openssh.nix31
-rw-r--r--modules/services/nixos/polaris.nix26
-rw-r--r--modules/services/options.nix94
-rw-r--r--modules/system/nixos/networking/tailscale.nix33
-rw-r--r--modules/system/options.nix2
-rw-r--r--modules/terminal/hjem/programs/broot.nix7
-rw-r--r--modules/terminal/hjem/programs/git.nix9
-rw-r--r--modules/terminal/hjem/programs/tmux.nix46
-rw-r--r--pkgs/obsidian.nix24
-rw-r--r--secrets.nix3
-rw-r--r--services/caddy.nix104
-rw-r--r--services/goaccess.nix136
-rw-r--r--tmux-tsunami/flake.lock42
-rw-r--r--tmux-tsunami/flake.nix18
-rw-r--r--tmux-tsunami/modules/common/bar.nix69
-rw-r--r--tmux-tsunami/modules/common/defaultGlobalKeys.nix66
-rw-r--r--tmux-tsunami/modules/common/defaultLeaderMenu.nix51
-rw-r--r--tmux-tsunami/modules/common/findFile.nix66
-rw-r--r--tmux-tsunami/modules/common/fzfExec.nix55
-rw-r--r--tmux-tsunami/modules/common/keys.nix66
-rw-r--r--tmux-tsunami/modules/common/main.nix23
-rw-r--r--tmux-tsunami/modules/common/sessionizer.nix92
-rw-r--r--tmux-tsunami/modules/common/sessionizer2.nix50
-rw-r--r--tmux-tsunami/modules/hjem.nix58
-rw-r--r--tmux-tsunami/modules/installer.nix38
-rw-r--r--tmux-tsunami/planning.md56
56 files changed, 1592 insertions, 421 deletions
diff --git a/broken_stuff b/broken_stuff
index 84c3381..935a33b 100644
--- a/broken_stuff
+++ b/broken_stuff
@@ -1 +1,10 @@
-- automount filesganymede-dav (code removed)
+idea:
+ - tmux
+ - projects are sessions (but ditch old sessionizer)
+ - new session with C-x C-n or whatever
+ - opens new session with broot window
+ - broot window controls entire session
+ - selecting dir changes session dir
+ - <enter> on file opens it in new tmux window
+ - use standard join-pane split-pane for splitting stuff
+ - alt-t creates new terminal pane within session with dir = session dir
diff --git a/flake.lock b/flake.lock
index bcb5040..01631b4 100644
--- a/flake.lock
+++ b/flake.lock
@@ -26,11 +26,11 @@
"betterfox": {
"flake": false,
"locked": {
- "lastModified": 1774552373,
- "narHash": "sha256-B9JG4GhjMguTUukinSrJWmEWmjU1EJ1UzlfQ9q+NTp8=",
+ "lastModified": 1777825377,
+ "narHash": "sha256-4d4S0DAqCjQFHoACAUSpltPqYrs83ZecuBU+m/x7xvM=",
"owner": "yokoffing",
"repo": "Betterfox",
- "rev": "fd29d4d313eda7a93f45f2f66bb0e87ff35d1a72",
+ "rev": "392c62a03c0d63e323a9aae55bc9aff87454db16",
"type": "github"
},
"original": {
@@ -47,11 +47,11 @@
]
},
"locked": {
- "lastModified": 1775334496,
- "narHash": "sha256-dUopEMj5KhH1vnv43o29kMsayp0Ki+9EfIn4UAE9sAU=",
+ "lastModified": 1778874672,
+ "narHash": "sha256-lL5SXrufwxu9sthDnTCieH8gskhOv5KOGj2wXx2xCsw=",
"owner": "9001",
"repo": "copyparty",
- "rev": "ede692925edfc6fca7a52f483414d543e9303237",
+ "rev": "3b53a228b0e07912766d8dbf88b1dd01da57e2c5",
"type": "github"
},
"original": {
@@ -62,11 +62,11 @@
},
"crane": {
"locked": {
- "lastModified": 1773189535,
- "narHash": "sha256-E1G/Or6MWeP+L6mpQ0iTFLpzSzlpGrITfU2220Gq47g=",
+ "lastModified": 1778106249,
+ "narHash": "sha256-cM/AuKy5tMhwOOQIbha8ZRRMHVfNf7cv2aljIw+qoCg=",
"owner": "ipetkov",
"repo": "crane",
- "rev": "6fa2fb4cf4a89ba49fc9dd5a3eb6cde99d388269",
+ "rev": "6d015ea29630b7ad2402841386da2cb617a470a7",
"type": "github"
},
"original": {
@@ -104,11 +104,11 @@
]
},
"locked": {
- "lastModified": 1773889306,
- "narHash": "sha256-PAqwnsBSI9SVC2QugvQ3xeYCB0otOwCacB1ueQj2tgw=",
+ "lastModified": 1778958912,
+ "narHash": "sha256-6pvS9rIF9mZRj1ENwu9fDLHeG1JFDTCpRyy6vJhXkTA=",
"owner": "nix-community",
"repo": "disko",
- "rev": "5ad85c82cc52264f4beddc934ba57f3789f28347",
+ "rev": "6e8dc7aa0e65fce67c76e18227a13a7d529f2cdf",
"type": "github"
},
"original": {
@@ -120,11 +120,11 @@
"firefox-csshacks": {
"flake": false,
"locked": {
- "lastModified": 1774892585,
- "narHash": "sha256-TCbdj6ZedgPSDQWJWFBPzDlLdgRcnsby3DWNHxmQ+b4=",
+ "lastModified": 1778816467,
+ "narHash": "sha256-RuZ0ihlTNu3chVVYd5JZX7xrRl2+jtLmucAKROKFD3s=",
"owner": "MrOtherGuy",
"repo": "firefox-csshacks",
- "rev": "d02c1a92fa86777f3ae52f19e0c338a67eabe908",
+ "rev": "1f477a50cf7a2996a0dbd7656c877f22df5bf968",
"type": "github"
},
"original": {
@@ -183,11 +183,11 @@
"fx-autoconfig": {
"flake": false,
"locked": {
- "lastModified": 1773035931,
- "narHash": "sha256-vOsXpuU2XlbIfXGm1RJicY8ghbkU5FvnctQMBMXCXbs=",
+ "lastModified": 1777913309,
+ "narHash": "sha256-czNgt62fofg3hXw7F4wXSv/+ZAsGtO6bg3sUOiUXcu4=",
"owner": "MrOtherGuy",
"repo": "fx-autoconfig",
- "rev": "8da9268f7afcbf5d438b099e976fe8aa46a111d0",
+ "rev": "d469a80f12e286c0e937d8b93c01dfc2d55dca8f",
"type": "github"
},
"original": {
@@ -199,7 +199,7 @@
"geolite-db": {
"flake": false,
"locked": {
- "narHash": "sha256-ea/ne52oFzES4jUjMri2OplkHpOJyOfR38beO4LRBgY=",
+ "narHash": "sha256-uA7ExafgNl03y3PW+dbm79iUncMDn62eUmMjCr2guZA=",
"type": "file",
"url": "https://github.com/P3TERX/GeoLite.mmdb/releases/latest/download/GeoLite2-City.mmdb"
},
@@ -238,11 +238,11 @@
]
},
"locked": {
- "lastModified": 1775569958,
- "narHash": "sha256-W+kpLQ5J/DDaY1nUmvN9aIAfh5ixdyqYM76ZUdcvMBU=",
+ "lastModified": 1779044379,
+ "narHash": "sha256-DHV1vpUers1TbZxIuEGz4VENShZ/EXefiTXd79QIhs4=",
"owner": "feel-co",
"repo": "hjem",
- "rev": "32bd6d54d805a3eb41efa62a940eeceaf263c4a8",
+ "rev": "4a2fd57f9e5d22cd3ef4c4230d245da159ab8dce",
"type": "github"
},
"original": {
@@ -282,11 +282,11 @@
"rust-overlay": "rust-overlay"
},
"locked": {
- "lastModified": 1775510693,
- "narHash": "sha256-gZfJ07j/oOciDi8mF/V8QTm7YCeDcusNSMZzBFi8OUM=",
+ "lastModified": 1778702031,
+ "narHash": "sha256-HJ4e4IQz7TJ1wDmEnkowXCM6SYXF9sfYg8nmUYHCnpI=",
"owner": "nix-community",
"repo": "lanzaboote",
- "rev": "3fe0ae8cb285e0ad101a9675f4190d455fb05e85",
+ "rev": "f11608843ca4fa95049c096ec0a50c93b41080b8",
"type": "github"
},
"original": {
@@ -303,11 +303,11 @@
]
},
"locked": {
- "lastModified": 1775037210,
- "narHash": "sha256-KM2WYj6EA7M/FVZVCl3rqWY+TFV5QzSyyGE2gQxeODU=",
+ "lastModified": 1777780666,
+ "narHash": "sha256-8wURyQMdDkGUarSTKOGdCuFfYiwa3HbzwscUfn3STDE=",
"owner": "nix-darwin",
"repo": "nix-darwin",
- "rev": "06648f4902343228ce2de79f291dd5a58ee12146",
+ "rev": "8c62fba0854ba15c8917aed18894dbccb48a3777",
"type": "github"
},
"original": {
@@ -323,11 +323,11 @@
]
},
"locked": {
- "lastModified": 1775365369,
- "narHash": "sha256-DgH5mveLoau20CuTnaU5RXZWgFQWn56onQ4Du2CqYoI=",
+ "lastModified": 1778999127,
+ "narHash": "sha256-V5GquqJvAqwFTcpN6hxKSQAtwuJFRUEHmyNKbeaTQDg=",
"owner": "nix-community",
"repo": "nix-index-database",
- "rev": "cef5cf82671e749ac87d69aadecbb75967e6f6c3",
+ "rev": "f680e0d3c1dbefe298c423691662e238496890f2",
"type": "github"
},
"original": {
@@ -368,11 +368,11 @@
},
"nixpkgs": {
"locked": {
- "lastModified": 1775423009,
- "narHash": "sha256-vPKLpjhIVWdDrfiUM8atW6YkIggCEKdSAlJPzzhkQlw=",
+ "lastModified": 1778869304,
+ "narHash": "sha256-30sZNZoA1cqF5JNO9fVX+wgiQYjB7HJqqJ4ztCDeBZE=",
"owner": "NixOS",
"repo": "nixpkgs",
- "rev": "68d8aa3d661f0e6bd5862291b5bb263b2a6595c9",
+ "rev": "d233902339c02a9c334e7e593de68855ad26c4cb",
"type": "github"
},
"original": {
@@ -392,11 +392,11 @@
]
},
"locked": {
- "lastModified": 1772893680,
- "narHash": "sha256-JDqZMgxUTCq85ObSaFw0HhE+lvdOre1lx9iI6vYyOEs=",
+ "lastModified": 1776796298,
+ "narHash": "sha256-PcRvlWayisPSjd0UcRQbhG8Oqw78AcPE6x872cPRHN8=",
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
- "rev": "8baab586afc9c9b57645a734c820e4ac0a604af9",
+ "rev": "3cfd774b0a530725a077e17354fbdb87ea1c4aad",
"type": "github"
},
"original": {
@@ -432,11 +432,11 @@
]
},
"locked": {
- "lastModified": 1773544328,
- "narHash": "sha256-Iv+qez54LAz+isij4APBk31VWA//Go81hwFOXr5iWTw=",
+ "lastModified": 1778383025,
+ "narHash": "sha256-UK7s2LJS1YwIMFL7PSaNJvLXT9pyRgm7X+HNPgMXiEE=",
"owner": "oxalica",
"repo": "rust-overlay",
- "rev": "4f977d776793c8bfbfdd7eca7835847ccc48874e",
+ "rev": "4568a557ca325ff81fb354382d4a9968daa1001a",
"type": "github"
},
"original": {
@@ -468,18 +468,14 @@
]
},
"locked": {
- "lastModified": 1768326969,
- "narHash": "sha256-Mwo6C1u4UcVEAZOJa/CUSDx3he4CAfAx1Ns8l+blj3I=",
- "owner": "bluedragon1221",
- "repo": "tmux-tsunami",
- "rev": "f379f00fec4912b539d3659f7c74da7669785adb",
- "type": "github"
+ "path": "./tmux-tsunami",
+ "type": "path"
},
"original": {
- "owner": "bluedragon1221",
- "repo": "tmux-tsunami",
- "type": "github"
- }
+ "path": "./tmux-tsunami",
+ "type": "path"
+ },
+ "parent": []
},
"uc-css-js": {
"flake": false,
diff --git a/flake.nix b/flake.nix
index 0343c67..12a2b81 100644
--- a/flake.nix
+++ b/flake.nix
@@ -44,7 +44,7 @@
};
tmux-tsunami = {
- url = "github:bluedragon1221/tmux-tsunami";
+ url = "./tmux-tsunami";
inputs.nixpkgs.follows = "nixpkgs";
};
diff --git a/hosts.toml b/hosts.toml
index ea2f400..3f660a9 100644
--- a/hosts.toml
+++ b/hosts.toml
@@ -2,14 +2,21 @@
user_pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAvxtKW0rmRBi8J67gLrLv8Zv338AcmZ3P20DePiUfnX collin@mercury"
host_pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIQtMAgPdWwrOzlZT/lEIRQZ+ajhafG9AEJCrF2/bsmN"
+wg_pubkey = "merTd71QvuMVQiRix/0NPAjVDvwRyLor20t+N9dDSEU="
+
[hosts.ganymede]
hostnames = ["ganymede", "192.168.50.2", "williamsfam.us.com"]
user_pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFeAsRh11Jyv+7Fm4WhzV4XN0LMYQS1/yMbvT+pvFDcd collin@ganymede"
host_pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINlr+53UmlGVP1blkdNl6NFqn1w2umFJyjH1EVUPKIy9"
+wg_pubkey = "ganyuu2I5DY1VQm3fPvQGBI2veW43Rgs3lAVzckWR2I="
+
[hosts.terra]
user_pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINKwMBLWWxpf4jZN3R3iQ7XEAOMq0KAOE/6YN4iTBuJx collin@terra"
+wg_pubkey = "ter/93FM8jf5b73+psVuU5cVNaoy00Z/wB6XGrvH1G8="
+wg_privkey = "yBiEnhSZZInc+irnVhZNHF5y7Z7tsR/IS6j32F4tPWc="
+
[hosts.io]
hostnames = ["io", "192.168.50.3"]
host_pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJXSvoVpMh/Y84THjKAYqkbuPmmv/yIU8DunMov7tKKT"
diff --git a/hosts/ganymede/caddy.nix b/hosts/ganymede/caddy.nix
index d10910c..a03fcdc 100644
--- a/hosts/ganymede/caddy.nix
+++ b/hosts/ganymede/caddy.nix
@@ -9,7 +9,7 @@
"github.com/tailscale/caddy-tailscale@v0.0.0-20251204171825-f070d146dd61"
"github.com/caddy-dns/porkbun@v0.3.1"
];
- hash = "sha256-n0HVlpCkJITyEPc+ml7zCcYw9gy3r1YAXertd6zc2o0=";
+ hash = "sha256-FrAI7Fpz3bXclmKcizBMv/VI1hTAWT6DQnj7S09MwNY=";
});
globalConfig = ''
diff --git a/hosts/ganymede/config.nix b/hosts/ganymede/config.nix
index 94d67ec..47b7f49 100644
--- a/hosts/ganymede/config.nix
+++ b/hosts/ganymede/config.nix
@@ -9,19 +9,6 @@
};
"caddy-env".file = ./secrets/caddy-env.age;
-
- # "collin-copyparty-password" = {
- # file = ./secrets/collin-copyparty-password.age;
- # owner = "copyparty";
- # };
- "collin-forgejo-password" = {
- file = ./secrets/collin-forgejo-password.age;
- owner = "forgejo";
- };
- "wireguard-pk" = {
- file = ./secrets/wireguard-pk.age;
- owner = "systemd-network";
- };
};
terminal = {
@@ -41,6 +28,7 @@
ip = "192.168.50.2/24";
gateway = "192.168.50.1";
};
+
wireless.static = {
ssid = "williams";
pskFile = config.collinux.secrets."williams-psk".path;
@@ -50,58 +38,45 @@
services = {
sshd = {
enable = true;
- port = 22;
- listenAddr = "0.0.0.0";
- rootLogin = true;
- };
+ public = true;
- goaccess = {
- enable = true;
- privateUrl = "stats.ganymede";
- };
- btopweb = {
- enable = true;
- privateUrl = "btop.ganymede";
- };
-
- cgit = {
- enable = true;
- privateUrl = "git.ganymede";
+ conf.rootLogin = true;
};
minecraft = {
enable = true;
- listenAddr = "0.0.0.0";
+ public = true;
};
- ngircd.enable = true;
-
- qbittorrent = {
+ ngircd = {
enable = true;
- privateUrl = "bittorrent.ganymede";
+ public = true;
};
- # copyparty = {
- # enable = true;
- # listenAddr = "0.0.0.0";
- # publicUrl = "up.williamsfam.us.com";
- # privateUrl = "files.ganymede";
-
- # users.collin = {
- # isAdmin = true;
- # passwordFile = config.collinux.secrets."collin-copyparty-password".path;
- # hasPublicDir = true;
- # };
- # };
jta = {
enable = true;
publicUrl = "jta.williamsfam.us.com";
};
-
ganyupload = {
enable = true;
publicUrl = "upld.williamsfam.us.com";
};
+ goaccess = {
+ enable = true;
+ privateUrl = "stats.ganymede";
+ };
+ btopweb = {
+ enable = true;
+ privateUrl = "btop.ganymede";
+ };
+ qbittorrent = {
+ enable = true;
+ privateUrl = "bittorrent.ganymede";
+ };
+ cgit = {
+ enable = true;
+ privateUrl = "git.ganymede";
+ };
caddy = {
enable = true;
diff --git a/hosts/ganymede/nixos.nix b/hosts/ganymede/nixos.nix
index 415509a..77e3ad5 100644
--- a/hosts/ganymede/nixos.nix
+++ b/hosts/ganymede/nixos.nix
@@ -16,6 +16,11 @@
facter.reportPath = ./facter.json;
+ environment.systemPackages = [
+ pkgs.python313
+ pkgs.net-tools
+ ];
+
# backup usb teather configuration
systemd.network.networks."80-usb-teather" = {
name = "enp0s20f0u2";
diff --git a/hosts/ganymede/secrets/wireguard-pk.age b/hosts/ganymede/secrets/wireguard-pk.age
deleted file mode 100644
index c8e365a..0000000
--- a/hosts/ganymede/secrets/wireguard-pk.age
+++ /dev/null
@@ -1,11 +0,0 @@
------BEGIN AGE ENCRYPTED FILE-----
-YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IEpnNHlXUSBxZlFt
-bWR5NFJ1N0FBMkc2eThudnI1TnQ2WFNzK2NsVzdtaVgzQXRYNFhzCkJXTGUzSU5E
-U05jYnEvQXVwZEhIU1FMamt3bUM4UnB3bmNEZWQzZDlBSXcKLT4gc09lRVkkfTQt
-Z3JlYXNlIHMgSHYsVTZxOzgKWVJHbG1Ua3NONE9QYjVCNEQzOXQrNlZXRTBKQ3JW
-elpKeGwvYVgrOVVMRStrUGl0ay9HYmNOcW8xZFJER2tXWApPcUZxY2I5aDBoMVBO
-bmNxWE9YakZqL2h2dlFVYVdra0JrZjMKLS0tIGJWQlRmaVREL242aVdudFVOZTd4
-V1BnTnM3aTdGY1lUcm81VUVkd1QzcFUKMMrCSdf0J/xuvhvpktJO/GbpBp8ZnXue
-S0TTS/s3zCx8wCT70j/C6alX1CeMX+RpJwiba5BpvTuphGrgrMjMtA0oYDCRZzeQ
-1EtdX+A=
------END AGE ENCRYPTED FILE-----
diff --git a/hosts/ganymede/secrets/wireguard-privkey.age b/hosts/ganymede/secrets/wireguard-privkey.age
new file mode 100644
index 0000000..ee9130d
--- /dev/null
+++ b/hosts/ganymede/secrets/wireguard-privkey.age
@@ -0,0 +1,8 @@
+age-encryption.org/v1
+-> ssh-ed25519 azBilg tvscfH96l4w2AkLjaczmtAJ3CKeU1mEPJWT82tR2HiY
+F83pJqPj+qlFw4nb+ANCMJ7lOufSMluDIeWbaYvJkEU
+-> ssh-ed25519 Jg4yWQ SXz2kA4FODscXLqsNsCjuqKqBUXSkEx5SIpSdjGKpQU
+mVg50RgaBauKlTCyHUYDTPZYnDJkppOthhMC+MAsiiE
+--- dlTzxZODPVxlkdRP3HRJSK6BU3yEHrs1fo/sudC6EKo
+ÌdŸEV!Axë£r¤?z(YKÙ…ÁÐÆSäuŠÓkst]<¸o(Þ¶Ø
+š>MüïjzψÄÑ\ÆDè\B¯«sÞý./ð2Œ= \ No newline at end of file
diff --git a/hosts/mercury/ai.nix b/hosts/mercury/ai.nix
deleted file mode 100644
index 4615838..0000000
--- a/hosts/mercury/ai.nix
+++ /dev/null
@@ -1,8 +0,0 @@
-{pkgs, ...}: {
- services.ollama.enable = true;
- # environment.systemPackages = [pkgs.mcphost];
-
- hjem.users."collin".files.".config/fish/conf.d/ollama.fish".text = ''
- set -gx OLLAMA_NOHISTORY 1
- '';
-}
diff --git a/hosts/mercury/config.nix b/hosts/mercury/config.nix
index 852c393..b9882f6 100644
--- a/hosts/mercury/config.nix
+++ b/hosts/mercury/config.nix
@@ -1,13 +1,16 @@
-{
+{config, ...}: {
collinux = {
- theme = "catppuccin";
+ theme = "adwaita";
secrets = {};
user.useRun0 = true;
desktop = {
- wallpaper = ./wallpapers/abstract-swirls.jpg;
+ wallpaper =
+ if (config.collinux.theme == "catppuccin")
+ then ./wallpapers/abstract-swirls.jpg
+ else ./wallpapers/hintergrund2.png;
gtk.enable = true;
qt.enable = true;
@@ -23,14 +26,13 @@
components = {
fuzzel.enable = true;
dunst.enable = true;
- # tofi.enable = true;
};
};
programs = {
firefox = {
enable = true;
- extensions.zotero.enable = false;
+ extensions.foxyproxy.enable = true;
};
foot.enable = true;
@@ -44,9 +46,7 @@
secureBoot.enable = true;
};
- network = {
- wireless.dynamic = true;
- };
+ network.wireless.dynamic = true;
audio.enable = true;
bluetooth.enable = true;
@@ -54,8 +54,10 @@
};
terminal = {
- shells.fish.enable = true;
- shells.bash.enable = true; # for nix-shells
+ shells = {
+ fish.enable = true;
+ bash.enable = true; # for nix-shells
+ };
programs = {
starship.enable = true;
@@ -63,6 +65,7 @@
bat.enable = true;
eza.enable = true;
broot.enable = true;
+ tmux.enable = true;
helix = {
enable = true;
hardMode = true;
diff --git a/hosts/mercury/disks.nix b/hosts/mercury/disks.nix
index 60187b1..7ef2d0d 100644
--- a/hosts/mercury/disks.nix
+++ b/hosts/mercury/disks.nix
@@ -3,17 +3,25 @@
device = "/dev/disk/by-uuid/3d80a86b-3268-4209-a833-b531b8bc0ebc";
fsType = "ext4";
};
-
fileSystems."/boot" = {
device = "/dev/disk/by-uuid/45A4-2E5B";
fsType = "vfat";
options = ["fmask=0022" "dmask=0022"];
};
-
+ swapDevices = [
+ {
+ device = "/dev/disk/by-label/swap";
+ priority = 10;
+ }
+ ];
zramSwap = {
enable = true;
priority = 100;
algorithm = "lz4";
memoryPercent = 50;
};
+
+ boot.kernelParams = [
+ "resume=LABEL=swap"
+ ];
}
diff --git a/hosts/mercury/hjem.nix b/hosts/mercury/hjem.nix
index 6ecbb8e..3625e7b 100644
--- a/hosts/mercury/hjem.nix
+++ b/hosts/mercury/hjem.nix
@@ -1,9 +1,12 @@
{pkgs, ...}: {
packages = with pkgs; [
- # obsidian
anki
libreoffice-qt
musescore
+ (pkgs.callPackage ../../pkgs/obsidian.nix {})
+
+ lmms # (?)
+ eq10q
prismlauncher
mpv
@@ -12,7 +15,6 @@
opencode
(pkgs.callPackage ../../pkgs/yo {})
- (pkgs.callPackage ../../pkgs/yokey {})
captive-browser # https://words.filippo.io/captive-browser
(pkgs.makeDesktopItem {
diff --git a/hosts/mercury/nixos.nix b/hosts/mercury/nixos.nix
index a47375a..9a89fca 100644
--- a/hosts/mercury/nixos.nix
+++ b/hosts/mercury/nixos.nix
@@ -11,20 +11,17 @@
inputs.nixos-facter-modules.nixosModules.facter
inputs.lanzaboote.nixosModules.lanzaboote
];
-
facter.reportPath = ./facter.json;
+
services.dbus.implementation = "broker";
environment.defaultPackages = lib.mkForce []; # im not a noob
- services.syncthing = {
- enable = true;
- user = "collin";
- dataDir = "/home/collin/.local/syncthing";
- };
- programs.kdeconnect.enable = true;
-
- security.soteria.enable = true;
+ programs.ssh.extraConfig = ''
+ Host ganymede
+ HostName williamsfam.us.com
+ Port 22
+ '';
virtualisation.podman = {
enable = true;
@@ -45,66 +42,34 @@
}
];
};
-
- services.jupyter = {
+ virtualisation.waydroid = {
+ package = pkgs.waydroid-nftables;
enable = true;
- ip = "127.0.0.1";
- port = 8888;
-
- user = "collin"; # to access my files
-
- package = pkgs.python313Packages.jupyter;
- command = "jupyter lab --ServerApp.token='' --ServerApp.password=''";
- password = "";
-
- notebookDir = "~/brain/notes/schoolyear2025/physics";
-
- kernels = {
- python3 = let
- python = pkgs.python313.withPackages (ps:
- with ps; [
- numpy
- pandas
- matplotlib
- sympy
- ipywidgets
- ipydatagrid
- ipykernel
- ]);
- in {
- language = "python";
- displayName = "Python (Physics)";
- argv = ["${python}/bin/python" "-m" "ipykernel_launcher" "-f" "{connection_file}"];
- };
- };
};
+ programs.kdeconnect.enable = true;
+
+ services.autossh.sessions = [
+ {
+ name = "ganymede";
+ user = "collin";
+ monitoringPort = 20000;
+ extraArguments = "-N -D 9090 collin@ganymede";
+ }
+ ];
+ boot.supportedFilesystems."fuse.sshfs" = true;
fileSystems."/home/collin/ganymede" = {
device = "collin@ganymede:/media";
fsType = "fuse.sshfs";
options = [
"identityfile=/home/collin/.ssh/id_ed25519"
"idmap=user"
- "x-systemd.automount" #< mount the filesystem automatically on first access
- "allow_other" #< don't restrict access to only the user which `mount`s it (because that's probably systemd who mounts it, not you)
- "user" #< allow manual `mount`ing, as ordinary user.
+ "x-systemd.automount" # mount the filesystem automatically on first access
+ "allow_other" # don't restrict access to only the user which `mount`s it (because that's probably systemd who mounts it, not you)
+ "user" # allow manual `mount`ing, as ordinary user.
];
};
- boot.supportedFilesystems."fuse.sshfs" = true;
- services.autossh.sessions = [
- {
- name = "ganymede";
- user = "collin";
- monitoringPort = 20000;
- extraArguments = "-N -D 9090 collin@williamsfam.us.com";
- }
- ];
- programs.ssh.extraConfig = ''
- Host ganymede
- HostName williamsfam.us.com
- Port 22
- '';
security.pki.certificates = [
''
-----BEGIN CERTIFICATE-----
@@ -120,11 +85,4 @@
-----END CERTIFICATE-----
''
];
-
- programs.firefox.policies.ExtensionSettings = {
- "foxyproxy@eric.h.jung" = {
- installation_mode = "force_installed";
- install_url = "https://addons.mozilla.org/firefox/downloads/latest/foxyproxy-standard/latest.xpi";
- };
- };
}
diff --git a/hosts/mercury/secrets/ts-key.age b/hosts/mercury/secrets/ts-key.age
new file mode 100644
index 0000000..57454fe
--- /dev/null
+++ b/hosts/mercury/secrets/ts-key.age
Binary files differ
diff --git a/hosts/mercury/secrets/wireguard-privkey.age b/hosts/mercury/secrets/wireguard-privkey.age
new file mode 100644
index 0000000..614778d
--- /dev/null
+++ b/hosts/mercury/secrets/wireguard-privkey.age
@@ -0,0 +1,6 @@
+age-encryption.org/v1
+-> ssh-ed25519 azBilg GS1BNOAVj8aZPePeR4teX45zKAyCMVOBvE+GNUGCX3I
+rEzBojvbakMsQCKXROwbynFTxmlcPROD7DBsl/wzfO4
+--- ESZvgbmZ3UrE6CymN/Haej2rE+FrS13SCqZZX+gcsPw
+ö 6T݈²ð—%]’¬‚ÓÔøK2®¥›ê
+ Jï-íì{‡â;@öØ `á+E2¦ðÏGÛLÛF*Þ-Ña$ˆÒ`géœýAÝm™pü \ No newline at end of file
diff --git a/justfile b/justfile
index e092f23..f20863d 100644
--- a/justfile
+++ b/justfile
@@ -1,9 +1,8 @@
+switch:
+ yo sw
+
build:
yo build
deploy:
yo dep root@ganymede
-
-switch:
- yo sw
-
diff --git a/modules/desktop/hjem/programs/firefox.nix b/modules/desktop/hjem/programs/firefox.nix
index cf9c532..6198afb 100644
--- a/modules/desktop/hjem/programs/firefox.nix
+++ b/modules/desktop/hjem/programs/firefox.nix
@@ -1,5 +1,4 @@
{
- pkgs,
lib,
config,
inputs,
@@ -44,18 +43,13 @@ in
"${profileDir}/chrome/userChrome.css".text = mkCssHacks [
# Tabs
- (
- if cfg.theme == "catppuccin"
- then "hide_tabs_with_one_tab"
- else "hide_tabs_with_one_tab_w_window_controls"
- )
+ "hide_tabs_with_one_tab"
"tabs_on_bottom_v2"
"tab_close_button_always_on_hover"
"tabs_fill_available_width"
# Icons!
"iconized_main_menu"
- # "iconized_menubar_items"
"iconized_places_context_menu"
"iconized_tabs_context_menu"
"icon_only_context_menu_text_controls"
diff --git a/modules/desktop/hjem/programs/foot.nix b/modules/desktop/hjem/programs/foot.nix
index d10db24..a28a269 100644
--- a/modules/desktop/hjem/programs/foot.nix
+++ b/modules/desktop/hjem/programs/foot.nix
@@ -9,8 +9,7 @@
settings = {
main = {
font = "Iosevka Nerd Font:size=12";
- # shell = "${pkgs.fish}/bin/fish";
- shell = "${pkgs.fish}/bin/fish -c 'br'";
+ shell = "fish -c '${pkgs.broot}/bin/broot'";
};
# key-bindings.spawn-terminal = "Control+Return";
diff --git a/modules/desktop/hjem/wm/sway.nix b/modules/desktop/hjem/wm/sway.nix
index 441670b..33ea869 100644
--- a/modules/desktop/hjem/wm/sway.nix
+++ b/modules/desktop/hjem/wm/sway.nix
@@ -35,12 +35,15 @@
pkgs.sway
];
text = ''
- selection=$(printf '%s\n' logout suspend reboot shutdown | fuzzel --dmenu --prompt "power: ")
+ selection=$(printf '%s\n' logout hibernate suspend reboot shutdown | fuzzel --dmenu --prompt "power: ")
case "$selection" in
logout)
swaymsg exit
;;
+ hibernate)
+ systemctl hibernate
+ ;;
suspend)
systemctl suspend
;;
@@ -61,6 +64,8 @@
${pkgs.dbus}/bin/dbus-update-activation-environment --systemd WAYLAND_DISPLAY DISPLAY SWAYSOCK XDG_CURRENT_DESKTOP GTK_USE_PORTAL NIXOS_OZONE_WL MOZ_ENABLE_WAYLAND
}
+ seat seat0 xcursor_theme ${config.collinux.desktop.gtk.cursor_data.name}
+
# target title bg text indicator border
client.focused #${base07} #${base00} #${base05} #${base06} #${base13}
client.focused_inactive #${base03} #${base00} #${base05} #${base06} #${base03}
@@ -107,8 +112,8 @@
Mod4+Return exec foot
Mod4+Space exec fuzzel
- Mod4+Escape exec '${powerMenu}/bin/power-menu'
Mod4+b exec firefox
+ Mod4+Escape exec '${powerMenu}/bin/power-menu'
Mod4+k exec '${batteryNotify}/bin/battery-notify'
Mod4+w exec '${pkgs.iwmenu}/bin/iwmenu -l fuzzel -i font -s 2'
Mod4+e exec '${pkgs.bzmenu}/bin/bzmenu -l fuzzel -i font -s 2'
diff --git a/modules/desktop/nixos/fonts.nix b/modules/desktop/nixos/fonts.nix
index 5ac17ec..d2e8ba9 100644
--- a/modules/desktop/nixos/fonts.nix
+++ b/modules/desktop/nixos/fonts.nix
@@ -2,6 +2,6 @@
fonts = {
enableDefaultPackages = false;
fontconfig.enable = true;
- packages = [pkgs.nerd-fonts.iosevka pkgs.ibm-plex]; # for terminal (blackbox or foot or ghostty)
+ packages = [pkgs.nerd-fonts.iosevka pkgs.ibm-plex pkgs.liberation_ttf]; # for terminal (blackbox or foot or ghostty)
};
}
diff --git a/modules/desktop/nixos/programs/firefox.nix b/modules/desktop/nixos/programs/firefox.nix
index 61e7b7d..9e2b8ca 100644
--- a/modules/desktop/nixos/programs/firefox.nix
+++ b/modules/desktop/nixos/programs/firefox.nix
@@ -25,6 +25,8 @@ in
NewTabPage = false;
+ SearchEngines.Default = "DuckDuckGo";
+
Preferences = let
opt = Value: {
inherit Value;
@@ -32,12 +34,9 @@ in
};
in {
"toolkit.legacyUserProfileCustomizations.stylesheets" = opt true;
- "browser.tabs.inTitlebar" = opt (
- if cfg.theme == "catppuccin"
- then 0
- else 1
- );
+ "browser.tabs.inTitlebar" = opt 0;
"browser.tabs.hoverPreview.enabled" = opt 0;
+ "browser.profiles.enabled" = opt false;
"browser.theme.content-theme" = opt 0;
"browser.theme.toolbar-theme" = opt 0;
@@ -99,7 +98,6 @@ in
"datareporting.policy.dataSubmissionPolicyBypassNotification" = opt true;
"browser.startup.homepage" = opt "about:blank";
- "cookiebanners.ui.desktop.enabled" = opt false;
"browser.compactmode.show" = opt true;
"browser.uidensity" = opt 1;
@@ -107,9 +105,6 @@ in
# get that AI out of my browser
"browser.ml.chat.enabled" = opt false;
"browser.ml.enable" = opt false;
-
- # use system pki certificates
- "security.enterprise_roots.enabled" = opt true;
};
ExtensionSettings = let
@@ -130,10 +125,10 @@ in
// (lib.optionalAttrs (cfg.theme == "adwaita") {
"{f1128560-8b23-46c1-aa6f-fb3e79f23cf3}" = ext "gnome-adwaita-gtk4-dark";
})
- // (lib.optionalAttrs (cfg.extensions.zotero.enable) {
- "zotero@chnm.gmu.edu" = {
+ // (lib.optionalAttrs (cfg.extensions.foxyproxy.enable) {
+ "foxyproxy@eric.h.jung" = {
installation_mode = "force_installed";
- install_url = "https://download.zotero.org/connector/firefox/release/Zotero_Connector-5.0.181.xpi";
+ install_url = "https://addons.mozilla.org/firefox/downloads/latest/foxyproxy-standard/latest.xpi";
};
});
};
diff --git a/modules/desktop/options.nix b/modules/desktop/options.nix
index 3e20643..9a601fe 100644
--- a/modules/desktop/options.nix
+++ b/modules/desktop/options.nix
@@ -133,16 +133,8 @@ in {
programs = {
firefox = {
enable = mkEnableOption "firefox";
- profileName = mkOption {
- type = types.str;
- default = config.collinux.user.name;
- internal = true;
- };
theme = mkThemeOption "firefox";
- extensions.zotero.enable = mkOption {
- description = "install Zotero Connector for Firefox";
- default = config.collinux.desktop.programs.research.enable;
- };
+ extensions.foxyproxy.enable = mkEnableOption "install FoxyProxy extension";
};
foot = mkProgramOption "foot";
@@ -150,7 +142,7 @@ in {
ghostty.enable = mkEnableOption "ghostty";
alacritty.enable = mkEnableOption "alacritty";
- research.enable = mkEnableOption "zathura, Xournal++, Zotero";
+ research.enable = mkEnableOption "zathura, Xournal++";
};
};
};
diff --git a/modules/nix/nixos/default.nix b/modules/nix/nixos/default.nix
index f39f368..3d76e36 100644
--- a/modules/nix/nixos/default.nix
+++ b/modules/nix/nixos/default.nix
@@ -10,8 +10,6 @@
];
nix = {
- # package = inputs.determinate.packages.${pkgs.system}.default;
-
gc.automatic = false; # use nh cleaner instead
# Make builds run with low priority so my system stays responsive
@@ -43,5 +41,5 @@
environment.systemPackages = [pkgs.cached-nix-shell];
nixpkgs.hostPlatform = "x86_64-linux";
- system.stateVersion = "25.05";
+ system.stateVersion = "25.11";
}
diff --git a/modules/services/nixos/default.nix b/modules/services/nixos/default.nix
index c3652b5..13cdcf4 100644
--- a/modules/services/nixos/default.nix
+++ b/modules/services/nixos/default.nix
@@ -6,11 +6,10 @@
./forgejo.nix
./goaccess.nix
./btopweb.nix
-
./cgit
- ./jta
./ganyupload
- ./polaris.nix
+
+ ./jta
./agate.nix
./minecraft.nix
./ngircd.nix
diff --git a/modules/services/nixos/ganyupload/AGENTS.md b/modules/services/nixos/ganyupload/AGENTS.md
new file mode 100644
index 0000000..c4d9192
--- /dev/null
+++ b/modules/services/nixos/ganyupload/AGENTS.md
@@ -0,0 +1,65 @@
+# Ganyupload - Agent Instructions
+
+## Quick Context
+- Simple Go file upload service using standard `net/http`.
+- Single `main.go`, two Nix files (`default.nix`, `pkg.nix`), one README.
+- Embedded README is served on GET /.
+- Used as a NixOS module: `collinux.services.ganyupload` enables it, configures port and upload directory via systemd environment.
+
+## How to Build and Test
+
+### Build
+```bash
+nix build
+```
+
+Alternatively, from the repo root:
+```bash
+nix build .#nixosConfigurations.<hostname>.config.system.build.toplevel
+```
+
+### Test
+No dedicated test file. Verify locally by running with environment variables:
+```bash
+PORT=8080 UPLOAD_DIR=/tmp/upload go run main.go
+```
+
+Then test uploads:
+```bash
+curl -X PUT --data-binary @file.txt http://localhost:8080/file.txt
+curl http://localhost:8080/ # Read embedded README
+```
+
+## Code Style
+
+**Go:**
+- Keep `main.go` clean; use `gofmt -w .`.
+- Match existing error handling (early return, `log.Printf` for warnings, `log.Fatalf` for fatal errors).
+- Path traversal prevention is critical; do not weaken `filepath.Clean` + `..` prefix checks.
+
+**Nix:**
+- Follow `/home/collin/nixos/AGENTS.md` conventions (2-space indent, common arg pattern, `lib.mkIf` for gating).
+- `default.nix` imports `mkCaddyCfg.nix` to integrate with Caddy reverse proxy; do not remove that import.
+
+## Key Implementation Notes
+
+1. **Embedded README:** The README file is embedded at compile time using `//go:embed README`. If you update README, rebuild to reflect changes.
+2. **Environment Variables:**
+ - `UPLOAD_DIR`: Defaults to `.`, overridden by systemd service to `/media/ganyupload`.
+ - `PORT`: Defaults to `8080`, set by systemd to the configured `cfg.port`.
+3. **NixOS Integration:** The service runs as user/group `ganyupload` with home `/var/lib/ganyupload`. Ensure the service has write permission to `UPLOAD_DIR`.
+4. **Caddy Integration:** `default.nix` imports `mkCaddyCfg.nix` to configure Caddy as a reverse proxy. Changes to hostname or port must be reflected in both Nix config and the Caddy rule.
+
+## Common Tasks
+
+- **Update Go code:** Edit `main.go`, test locally, then nix build to verify.
+- **Change upload directory or port:** Update `default.nix` (systemd environment or `cfg.port`).
+- **Update README:** Edit README file, then rebuild (`nix build`) so the embedded version updates.
+- **Debug NixOS module:** Check `/etc/systemd/system/ganyupload.service` for actual service config after rebuild.
+
+## Validation
+
+After changes:
+1. Run `nix build` from this directory or `nix build .#nixosConfigurations.<hostname>.config.system.build.toplevel` from repo root.
+2. If applicable, test via `PORT=8080 UPLOAD_DIR=/tmp go run main.go`.
+3. Verify no Nix lint issues (use repo-level `yo test` or `nix run nixpkgs#alejandra -- .` for formatting).
diff --git a/modules/services/nixos/ganyupload/main.go b/modules/services/nixos/ganyupload/main.go
index 6aa219e..75852c2 100644
--- a/modules/services/nixos/ganyupload/main.go
+++ b/modules/services/nixos/ganyupload/main.go
@@ -2,11 +2,14 @@ package main
import (
_ "embed"
+ "fmt"
+ "html"
"io"
"log"
"net/http"
"os"
"path/filepath"
+ "sort"
"strings"
)
@@ -15,6 +18,42 @@ var readme string
var uploadDir = "."
+// cleanPath safely cleans and validates a path to prevent traversal attacks
+func cleanPath(filename string) (string, error) {
+ // Remove leading slash
+ filename = strings.TrimPrefix(filename, "/")
+ if filename == "" {
+ return "", nil
+ }
+
+ // Clean the path
+ filename = filepath.Clean(filename)
+
+ // Reject if it tries to go up
+ if strings.HasPrefix(filename, "..") || strings.Contains(filename, "/../") {
+ return "", fmt.Errorf("invalid filename: path traversal not allowed")
+ }
+
+ return filename, nil
+}
+
+// isPathSafe checks that the resolved fullPath stays within uploadDir
+func isPathSafe(uploadDir, fullPath string) bool {
+ // Resolve both paths to absolute to catch symlink attacks
+ absUploadDir, err := filepath.Abs(uploadDir)
+ if err != nil {
+ return false
+ }
+ absFullPath, err := filepath.Abs(fullPath)
+ if err != nil {
+ return false
+ }
+
+ // Ensure the resolved path starts with uploadDir
+ return strings.HasPrefix(absFullPath, absUploadDir+string(filepath.Separator)) ||
+ absFullPath == absUploadDir
+}
+
func handleRoot(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
@@ -32,27 +71,37 @@ func handleUpload(w http.ResponseWriter, r *http.Request) {
return
}
+ // Handle GET requests for downloads and directory listing
+ if r.Method == http.MethodGet {
+ handleDownload(w, r)
+ return
+ }
+
if r.Method != http.MethodPut {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
// Extract filename from URL path, preventing path traversal
- filename := strings.TrimPrefix(r.URL.Path, "/")
- if filename == "" {
- http.Error(w, "filename required", http.StatusBadRequest)
+ filename, err := cleanPath(r.URL.Path)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusBadRequest)
return
}
- // Prevent path traversal
- filename = filepath.Clean(filename)
- if strings.HasPrefix(filename, "..") {
- http.Error(w, "invalid filename", http.StatusBadRequest)
+ if filename == "" {
+ http.Error(w, "filename required", http.StatusBadRequest)
return
}
fullPath := filepath.Join(uploadDir, filename)
+ // Verify the path is safe
+ if !isPathSafe(uploadDir, fullPath) {
+ http.Error(w, "invalid path", http.StatusBadRequest)
+ return
+ }
+
// Ensure the directory exists
dir := filepath.Dir(fullPath)
if err := os.MkdirAll(dir, 0755); err != nil {
@@ -85,6 +134,155 @@ func handleUpload(w http.ResponseWriter, r *http.Request) {
log.Printf("uploaded %s (%d bytes)", filename, written)
}
+func handleDownload(w http.ResponseWriter, r *http.Request) {
+ // Extract filename from URL path
+ filename, err := cleanPath(r.URL.Path)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ return
+ }
+
+ fullPath := filepath.Join(uploadDir, filename)
+
+ // Verify the path is safe
+ if !isPathSafe(uploadDir, fullPath) {
+ http.Error(w, "invalid path", http.StatusBadRequest)
+ return
+ }
+
+ fileInfo, err := os.Stat(fullPath)
+ if err != nil {
+ if os.IsNotExist(err) {
+ http.Error(w, "not found", http.StatusNotFound)
+ } else {
+ log.Printf("failed to stat: %v", err)
+ http.Error(w, "internal server error", http.StatusInternalServerError)
+ }
+ return
+ }
+
+ // If it's a directory, list its contents
+ if fileInfo.IsDir() {
+ handleDirList(w, r, uploadDir, filename, fullPath)
+ return
+ }
+
+ // Serve the file
+ http.ServeFile(w, r, fullPath)
+}
+
+func handleDirList(w http.ResponseWriter, r *http.Request, uploadDir, relPath, fullPath string) {
+ entries, err := os.ReadDir(fullPath)
+ if err != nil {
+ log.Printf("failed to read directory: %v", err)
+ http.Error(w, "internal server error", http.StatusInternalServerError)
+ return
+ }
+
+ // Sort entries by name
+ sort.Slice(entries, func(i, j int) bool {
+ return entries[i].Name() < entries[j].Name()
+ })
+
+ // Build breadcrumb navigation
+ pathParts := strings.Split(strings.Trim(relPath, "/"), "/")
+ if relPath == "" {
+ pathParts = []string{}
+ }
+
+ w.Header().Set("Content-Type", "text/html; charset=utf-8")
+ w.WriteHeader(http.StatusOK)
+
+ fmt.Fprintf(w, `<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>Directory: %s</title>
+ <style>
+ body { font-family: monospace; margin: 2rem; }
+ h1 { margin-bottom: 0.5rem; }
+ .breadcrumb { margin-bottom: 2rem; font-size: 0.9rem; }
+ .breadcrumb a { text-decoration: none; color: #0066cc; }
+ .breadcrumb a:hover { text-decoration: underline; }
+ table { border-collapse: collapse; width: 100%%; }
+ td { padding: 0.5rem; border-bottom: 1px solid #ccc; }
+ td:first-child { text-align: left; }
+ td:last-child { text-align: right; color: #666; }
+ a { text-decoration: none; color: #0066cc; }
+ a:hover { text-decoration: underline; }
+ .directory { color: #0066cc; font-weight: bold; }
+ </style>
+</head>
+<body>
+ <h1>Directory: %s</h1>
+`, html.EscapeString(relPath), html.EscapeString(relPath))
+
+ // Breadcrumb navigation
+ fmt.Fprint(w, ` <div class="breadcrumb">`)
+ fmt.Fprintf(w, `<a href="/">home</a>`)
+ if len(pathParts) > 0 {
+ accumulated := ""
+ for _, part := range pathParts {
+ accumulated = accumulated + "/" + part
+ fmt.Fprintf(w, ` / <a href="%s">%s</a>`, html.EscapeString(accumulated), html.EscapeString(part))
+ }
+ }
+ fmt.Fprint(w, `</div>`)
+
+ // Directory listing
+ fmt.Fprint(w, ` <table>
+`)
+ for _, entry := range entries {
+ name := entry.Name()
+ safeName := html.EscapeString(name)
+
+ if entry.IsDir() {
+ // For directories, add trailing slash to the link
+ linkPath := relPath + "/" + name
+ if relPath == "" {
+ linkPath = "/" + name
+ }
+ linkPath = strings.TrimPrefix(linkPath, "/")
+ fmt.Fprintf(w, ` <tr><td><a href="/%s" class="directory">%s/</a></td><td>-</td></tr>
+`, html.EscapeString(linkPath), safeName)
+ } else {
+ info, _ := entry.Info()
+ linkPath := relPath + "/" + name
+ if relPath == "" {
+ linkPath = "/" + name
+ }
+ linkPath = strings.TrimPrefix(linkPath, "/")
+ size := formatSize(info.Size())
+ fmt.Fprintf(w, ` <tr><td><a href="/%s">%s</a></td><td>%s</td></tr>
+`, html.EscapeString(linkPath), safeName, size)
+ }
+ }
+ fmt.Fprint(w, ` </table>
+</body>
+</html>
+`)
+}
+
+func formatSize(bytes int64) string {
+ const (
+ KB = 1024
+ MB = KB * 1024
+ GB = MB * 1024
+ )
+
+ switch {
+ case bytes < KB:
+ return fmt.Sprintf("%d B", bytes)
+ case bytes < MB:
+ return fmt.Sprintf("%.1f KB", float64(bytes)/KB)
+ case bytes < GB:
+ return fmt.Sprintf("%.1f MB", float64(bytes)/MB)
+ default:
+ return fmt.Sprintf("%.1f GB", float64(bytes)/GB)
+ }
+}
+
func main() {
if envUploadDir := os.Getenv("UPLOAD_DIR"); envUploadDir != "" {
uploadDir = envUploadDir
diff --git a/modules/services/nixos/jta/main.go b/modules/services/nixos/jta/main.go
index 1390d7f..e7b5a0f 100644
--- a/modules/services/nixos/jta/main.go
+++ b/modules/services/nixos/jta/main.go
@@ -36,19 +36,35 @@ var pageTmpl = template.Must(template.New("md_template.html").Parse(mdTemplate))
const authCookieName = "jta_auth"
-func isAuthenticated(r *http.Request) bool {
+func isAuthenticated(w http.ResponseWriter, r *http.Request) bool {
expectedHash := strings.TrimSpace(os.Getenv("AUTH_PASSWORD_HASH"))
if expectedHash == "" {
return false
}
- cookie, err := r.Cookie(authCookieName)
- if err != nil {
- return false
+ // Try cookie first
+ if cookie, err := r.Cookie(authCookieName); err == nil {
+ providedHash := strings.TrimSpace(cookie.Value)
+ if providedHash != "" && strings.EqualFold(providedHash, expectedHash) {
+ return true
+ }
+ }
+
+ // Fallback to URL parameter
+ paramHash := strings.TrimSpace(r.URL.Query().Get(authCookieName))
+ if paramHash != "" && strings.EqualFold(paramHash, expectedHash) {
+ http.SetCookie(w, &http.Cookie{
+ Name: authCookieName,
+ Value: paramHash,
+ Path: "/",
+ HttpOnly: true, // Security: Protects against XSS cookie theft
+ Secure: false, // Set to true if you are hosting over HTTPS
+ SameSite: http.SameSiteLaxMode, // Prevents CSRF vulnerabilities
+ })
+ return true
}
- providedHash := strings.TrimSpace(cookie.Value)
- return strings.EqualFold(providedHash, expectedHash)
+ return false
}
func serveMarkdown(w http.ResponseWriter, r *http.Request) {
@@ -101,7 +117,7 @@ func main() {
return
}
- if !isAuthenticated(r) {
+ if !isAuthenticated(w, r) {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
diff --git a/modules/services/nixos/minecraft.nix b/modules/services/nixos/minecraft.nix
index 1b8b047..7e12a61 100644
--- a/modules/services/nixos/minecraft.nix
+++ b/modules/services/nixos/minecraft.nix
@@ -6,7 +6,7 @@
cfg = config.collinux.services.minecraft;
in
lib.mkIf cfg.enable {
- networking.firewall.allowedUDPPorts = lib.optional (cfg.listenAddr == "0.0.0.0") cfg.port;
+ networking.firewall.allowedUDPPorts = lib.optional cfg.public cfg.port;
virtualisation.oci-containers.containers."Minecraft" = {
environment = {
@@ -15,14 +15,20 @@ in
SERVER_NAME = "YServer";
TZ = config.time.timeZone;
- VERSION = "1.26.3.1";
+ VERSION = "1.26.20.5";
CONTENT_LOG_FILE_ENABLED = "false";
ALLOW_CHEATS = "false";
DIFFICULTY = "1";
};
image = "itzg/minecraft-bedrock-server";
- ports = ["${cfg.listenAddr}:${toString cfg.port}:19132/udp"];
+ ports = [
+ "${
+ if cfg.public
+ then "0.0.0.0"
+ else "127.0.0.1"
+ }:${toString cfg.port}:19132/udp"
+ ];
volumes = ["/var/lib/minecraft/:/data"];
extraOptions = ["--no-healthcheck"];
diff --git a/modules/services/nixos/ngircd.nix b/modules/services/nixos/ngircd.nix
index d06a497..ac93f06 100644
--- a/modules/services/nixos/ngircd.nix
+++ b/modules/services/nixos/ngircd.nix
@@ -6,7 +6,7 @@
cfg = config.collinux.services.ngircd;
in
lib.mkIf cfg.enable {
- networking.firewall.allowedTCPPorts = [cfg.port];
+ networking.firewall.allowedTCPPorts = lib.optional cfg.public cfg.port;
services.ngircd = {
enable = true;
@@ -16,7 +16,11 @@ in
Info = Ganymede IRC Chat
AdminInfo1 = Collin
- Listen = 0.0.0.0
+ Listen = ${
+ if cfg.public
+ then "0.0.0.0"
+ else "127.0.0.1"
+ }
Ports = ${toString cfg.port}
[Channel]
diff --git a/modules/services/nixos/openssh.nix b/modules/services/nixos/openssh.nix
index 1ce1abc..98f8b87 100644
--- a/modules/services/nixos/openssh.nix
+++ b/modules/services/nixos/openssh.nix
@@ -6,15 +6,9 @@
...
}: let
cfg = config.collinux.services.sshd;
-
- authorizedKeys =
- hosts
- |> builtins.mapAttrs (_: data: data.user_pubkey or null)
- |> builtins.attrValues
- |> builtins.filter (x: x != null);
in {
config = lib.mkIf cfg.enable {
- networking.firewall.allowedTCPPorts = [cfg.port];
+ networking.firewall.allowedTCPPorts = lib.optional cfg.public cfg.port;
services.openssh = {
enable = true;
@@ -29,7 +23,10 @@ in {
listenAddresses = [
{
- addr = cfg.listenAddr;
+ addr =
+ if cfg.public
+ then "0.0.0.0"
+ else "127.0.0.1";
port = cfg.port;
}
];
@@ -46,7 +43,7 @@ in {
extraConfig = lib.concatStringsSep "\n" [
"Match LocalPort ${toString cfg.port}"
(
- if cfg.otp
+ if cfg.conf.otp
then ''
ChallengeResponseAuthentication yes
PubkeyAuthentication yes
@@ -58,11 +55,11 @@ in {
AuthenticationMethods publickey
''
)
- (lib.optionalString cfg.rootLogin "PermitRootLogin yes")
+ (lib.optionalString cfg.conf.rootLogin "PermitRootLogin yes")
];
};
- security.pam.services = lib.optionalAttrs cfg.otp {
+ security.pam.services = lib.optionalAttrs cfg.conf.otp {
login.googleAuthenticator.enable = true;
sshd.text = ''
@@ -78,8 +75,16 @@ in {
'';
};
- users.users.${config.collinux.user.name}.openssh.authorizedKeys.keys = authorizedKeys;
- users.users."root".openssh.authorizedKeys.keys = lib.mkIf cfg.rootLogin authorizedKeys;
+ users.users = let
+ k.openssh.authorizedKeys.keys =
+ hosts
+ |> builtins.mapAttrs (_: data: data.user_pubkey or null)
+ |> builtins.attrValues
+ |> builtins.filter (x: x != null);
+ in {
+ ${config.collinux.user.name} = k;
+ "root" = lib.mkIf cfg.conf.rootLogin k;
+ };
systemd.services."openssh" = {
after = lib.mkAfter ["network-online.target"];
diff --git a/modules/services/nixos/polaris.nix b/modules/services/nixos/polaris.nix
deleted file mode 100644
index 89f5ef3..0000000
--- a/modules/services/nixos/polaris.nix
+++ /dev/null
@@ -1,26 +0,0 @@
-{
- lib,
- config,
- ...
-}: let
- cfg = config.collinux.services.polaris;
-in {
- imports = [
- (import ./mkCaddyCfg.nix cfg)
- ];
-
- config = lib.mkIf cfg.enable {
- services.polaris = {
- enable = true;
- port = cfg.port;
- settings = {
- mount_dirs = [
- {
- name = "Ganymede Library";
- source = "/media/library/music";
- }
- ];
- };
- };
- };
-}
diff --git a/modules/services/options.nix b/modules/services/options.nix
index 8918781..1888f5b 100644
--- a/modules/services/options.nix
+++ b/modules/services/options.nix
@@ -55,70 +55,73 @@ in {
options.collinux.services = {
sshd = {
enable = mkEnableOption "OpenSSH server";
-
port = mkOption {
description = "Port to run on";
type = lib.types.port;
+ default = 22;
};
+ public = mkEnableOption "whether to make this service accessable over the internet";
- listenAddr = mkOption {
- description = "Address to listen on";
- type = lib.types.str;
- default = "127.0.0.1";
+ conf = {
+ otp = mkEnableOption "Whether to require TOTP (Google Authenticator) 2fa codes to login";
+ rootLogin = mkEnableOption "Whether to allow root login";
+ };
+ };
+ minecraft = {
+ enable = mkEnableOption "Minecraft bedrock server";
+ port = mkOption {
+ description = "port to run on";
+ type = lib.types.port;
+ default = 19132;
+ };
+
+ public = mkEnableOption "whether to make this service accessable over the internet";
+ };
+ ngircd = {
+ enable = mkEnableOption "ncircd IRC server";
+ port = mkOption {
+ type = lib.types.port;
+ default = 6667;
};
- otp = mkEnableOption "Whether to require TOTP (Google Authenticator) 2fa codes";
- rootLogin = mkEnableOption "Whether to allow root login";
+ public = mkEnableOption "whether to make this service accessable over the internet";
+ };
+ agate = {
+ enable = mkEnableOption "Agate Gemini Protocol Server";
+ publicUrl = mkOption {
+ description = "domain to run this gemini server on";
+ type = lib.types.str;
+ };
};
+ jta = webserviceOptions {
+ service_name = "jta";
+ default_port = 8072;
+ };
+ ganyupload = webserviceOptions {
+ service_name = "ganyupload";
+ default_port = 8073;
+ };
forgejo = webserviceOptions {
service_name = "forgejo";
default_port = 8010;
};
-
btopweb = webserviceOptions {
service_name = "btopweb";
default_port = 8017;
};
-
goaccess = webserviceOptions {
service_name = "goaccess";
reverse_proxy = false;
};
-
cgit = webserviceOptions {
service_name = "cgit";
reverse_proxy = false;
};
-
- agate = {
- enable = mkEnableOption "Agate Gemini Protocol Server";
- publicUrl = mkOption {
- description = "domain to run this gemini server on";
- type = lib.types.str;
- };
- };
-
- polaris = webserviceOptions {
- service_name = "polaris";
- default_port = 8079;
- };
-
qbittorrent = webserviceOptions {
service_name = "qbittorrent";
default_port = 8076;
};
-
- jta = webserviceOptions {
- service_name = "jta";
- default_port = 8072;
- };
-
- ganyupload = webserviceOptions {
- service_name = "ganyupload";
- default_port = 8073;
- };
-
copyparty =
(webserviceOptions {
service_name = "copyparty";
@@ -146,27 +149,6 @@ in {
};
};
- ngircd = {
- enable = mkEnableOption "ncircd IRC server";
- port = mkOption {
- type = lib.types.port;
- default = 6667;
- };
- };
-
- minecraft = {
- enable = mkEnableOption "Minecraft bedrock server";
- listenAddr = mkOption {
- description = "Address to listen on";
- type = ipAddr;
- default = "127.0.0.1";
- };
- port = mkOption {
- type = lib.types.port;
- default = 19132;
- };
- };
-
caddy = {
enable = mkEnableOption "caddy https server";
envFile = mkOption {
diff --git a/modules/system/nixos/networking/tailscale.nix b/modules/system/nixos/networking/tailscale.nix
deleted file mode 100644
index 552ec8b..0000000
--- a/modules/system/nixos/networking/tailscale.nix
+++ /dev/null
@@ -1,33 +0,0 @@
-{
- config,
- pkgs,
- lib,
- ...
-}: let
- cfg = config.collinux.system.network.tailscale;
-in
- lib.mkIf cfg.enable {
- services.tailscale = {
- enable = true;
- useRoutingFeatures = "both";
- };
-
- networking.firewall = {
- trustedInterfaces = ["tailscale0"];
- allowedUDPPorts = [config.services.tailscale.port];
- };
-
- systemd.services."tailscaled" =
- if config.collinux.services.selfhost.headscale.enable
- then {
- # don't start tailscale until after headscale starts
- wants = lib.mkForce ["network.target" "headscale.target"];
- after = lib.mkForce ["network.target" "headscale.target"];
- }
- else {
- wants = lib.mkForce ["network.target"];
- after = lib.mkForce ["network.target"];
- };
-
- environment.systemPackages = [pkgs.tailscale];
- }
diff --git a/modules/system/options.nix b/modules/system/options.nix
index 8126b6a..a6db575 100644
--- a/modules/system/options.nix
+++ b/modules/system/options.nix
@@ -64,8 +64,6 @@ in {
};
dynamic = lib.mkEnableOption "Enable dynamically joining wireless networks with iwd";
};
-
- tailscale.enable = mkEnableOption "tailscale";
};
audio.enable = mkEnableOption "pipewire and wireplumber";
diff --git a/modules/terminal/hjem/programs/broot.nix b/modules/terminal/hjem/programs/broot.nix
index 59f16aa..cd425bd 100644
--- a/modules/terminal/hjem/programs/broot.nix
+++ b/modules/terminal/hjem/programs/broot.nix
@@ -7,9 +7,10 @@
cfg = config.collinux.terminal.programs.broot;
conf = {
- imports = [
- "skins/catppuccin-mocha.hjson"
- ];
+ imports =
+ if config.collinux.theme == "catppuccin"
+ then ["skins/catppuccin-mocha.hjson"]
+ else ["skins/dark-gruvbox.hjson"];
default_flags = "-g";
icon_theme = "nerdfont";
diff --git a/modules/terminal/hjem/programs/git.nix b/modules/terminal/hjem/programs/git.nix
index 918381c..85db4ab 100644
--- a/modules/terminal/hjem/programs/git.nix
+++ b/modules/terminal/hjem/programs/git.nix
@@ -8,15 +8,16 @@
git_config = lib.mkMerge [
{
+ user = {
+ email = cfg.userEmail;
+ name = cfg.userName;
+ };
alias = {
stage = "add";
unstage = "restore --staged";
};
init.defaultBranch = "main";
- user = {
- email = cfg.userEmail;
- name = cfg.userName;
- };
+ push.autoSetupRemote = true;
}
(lib.mkIf cfg.installKey {
# commit signing
diff --git a/modules/terminal/hjem/programs/tmux.nix b/modules/terminal/hjem/programs/tmux.nix
index 2c87212..2d7155e 100644
--- a/modules/terminal/hjem/programs/tmux.nix
+++ b/modules/terminal/hjem/programs/tmux.nix
@@ -7,30 +7,30 @@
}: let
cfg = config.collinux.terminal.programs.tmux;
in {
- imports = [
- inputs.tmux-tsunami.hjemModules.tsunami
- ];
+ # imports = [
+ # inputs.tmux-tsunami.hjemModules.tsunami
+ # ];
- config = lib.mkIf cfg.enable {
- tsunami = {
- enable = true;
+ # config = lib.mkIf cfg.enable {
+ # tsunami = {
+ # enable = true;
- theme = {
- bg = "#${config.collinux.palette.base00}";
- bg_dark = "#${config.collinux.palette.base01}";
- };
+ # theme = {
+ # bg = "#${config.collinux.palette.base00}";
+ # bg_dark = "#${config.collinux.palette.base01}";
+ # };
- keys.leader = [
- {
- name = "Lazygit";
- key = "C-g";
- exec = "display-popup -E -w 80% -h 80% -x C -y C -d '#{?@default-path,#{@default-path},#{pane_current_path}}' ${lib.getExe pkgs.lazygit}";
- }
- ];
- confs."main" = ''
- set -g mouse on
- set-window-option -g mode-keys vi
- '';
- };
- };
+ # keys.leader = [
+ # {
+ # name = "Lazygit";
+ # key = "C-g";
+ # exec = "display-popup -E -w 80% -h 80% -x C -y C -d '#{?@default-path,#{@default-path},#{pane_current_path}}' ${lib.getExe pkgs.lazygit}";
+ # }
+ # ];
+ # confs."main" = ''
+ # set -g mouse on
+ # set-window-option -g mode-keys vi
+ # '';
+ # };
+ # };
}
diff --git a/pkgs/obsidian.nix b/pkgs/obsidian.nix
new file mode 100644
index 0000000..b54f6a7
--- /dev/null
+++ b/pkgs/obsidian.nix
@@ -0,0 +1,24 @@
+{pkgs, ...}: let
+ version = "1.10.3";
+in
+ pkgs.stdenv.mkDerivation {
+ pname = "obsidian";
+ inherit version;
+
+ src = pkgs.fetchurl {
+ url = "https://github.com/obsidianmd/obsidian-releases/releases/download/v${version}/obsidian-${version}.tar.gz";
+ hash = "sha256-zI8CEhHt6VIGfh4kj4YxdfxJPJaf7mhMVnM45TmaoI0=";
+ };
+ nativeBuildInputs = [pkgs.makeWrapper];
+
+ installPhase = ''
+ runHook preInstall
+ mkdir -p $out/bin
+ makeWrapper ${pkgs.electron_39}/bin/electron $out/bin/obsidian \
+ --add-flags $out/share/obsidian/app.asar \
+ --add-flags "\''${NIXOS_OZONE_WL:+\''${WAYLAND_DISPLAY:+--ozone-platform=wayland --enable-wayland-ime=true --wayland-text-input-version=3}}"
+ install -m 444 -D resources/app.asar $out/share/obsidian/app.asar
+ install -m 444 -D resources/obsidian.asar $out/share/obsidian/obsidian.asar
+ runHook postInstall
+ '';
+ }
diff --git a/secrets.nix b/secrets.nix
index 58b0931..e8a737c 100644
--- a/secrets.nix
+++ b/secrets.nix
@@ -7,5 +7,6 @@ in {
"hosts/ganymede/secrets/williams-psk.age".publicKeys = [ganymede.host_pubkey];
"hosts/ganymede/secrets/collin-copyparty-password.age".publicKeys = [mercury.host_pubkey ganymede.host_pubkey];
"hosts/ganymede/secrets/collin-forgejo-password.age".publicKeys = [mercury.host_pubkey ganymede.host_pubkey];
- "hosts/ganymede/secrets/wireguard-pk.age".publicKeys = [ganymede.host_pubkey];
+
+ "hosts/mercury/secrets/ts-key.age".publicKeys = [mercury.host_pubkey];
}
diff --git a/services/caddy.nix b/services/caddy.nix
new file mode 100644
index 0000000..9870af0
--- /dev/null
+++ b/services/caddy.nix
@@ -0,0 +1,104 @@
+{
+ lib,
+ config,
+ ...
+}: let
+ cfg = config.caddy;
+in {
+ options = {
+ caddy = {
+ globalConfig = lib.mkOption {
+ description = "Configuration for top level stuff (ex. dns server api keys)";
+ type = lib.types.lines;
+ default = "";
+ };
+ tld = lib.mkOption {
+ description = "Your publically accessable domain name";
+ type = lib.types.str;
+ };
+ hostname = lib.mkOption {
+ description = "Your computers hostname";
+ type = lib.types.str;
+ };
+ virtualHosts = lib.mkOption {
+ description = "Magically configure caddy for services";
+ type = lib.types.attrsOf (lib.types.submodule ({config, ...}: {
+ options = {
+ serviceName = lib.mkOption {
+ type = lib.types.str;
+ default = config._module.args.name;
+ internal = true;
+ };
+ access = lib.mkOption {
+ type = lib.types.enum ["public" "private"];
+ description = "public: accessable at {serviceName}.my.tld. private: accessable at {serviceName}.{hostname}";
+ };
+ logFile = lib.mkOption {
+ type = lib.types.str;
+ description = "what to name the log file under /var/log/caddy";
+ default = "${config.serviceName}.log";
+ };
+ reverseProxy = lib.mkOption {
+ type = with lib.types; nullOr str;
+ description = "configure caddy to reverse-proxy this port or unix domain socket (ex. 127.0.0.1:8080 or unix//var/run/my_socket)";
+ default = null;
+ };
+ virtualHostConfig = lib.mkOption {
+ type = lib.types.lines;
+ description = "manual config lines to add to the caddy config";
+ default = "";
+ };
+ };
+ }));
+ default = {};
+ };
+ };
+ };
+ config = {
+ configData."caddyfile".text =
+ ''
+ {
+ ${cfg.globalConfig}
+ }
+ ''
+ ++ (cfg.virtualHosts
+ |> map (vhost_cfg: let
+ target =
+ if vhost_cfg.access == "public"
+ then
+ (
+ if vhost_cfg.serviceName != "root"
+ then "${vhost_cfg.serviceName}.${cfg.tld}"
+ else cfg.tld
+ )
+ else
+ (
+ if vhost_cfg.serviceName != "root"
+ then "${vhost_cfg.serviceName}.${cfg.hostName}"
+ else cfg.hostname
+ );
+ in ''
+ log {
+ output file /var/log/caddy/${vhost_cfg.logFile}
+ }
+
+ ${target} {
+ ${
+ if vhost_cfg.access == "private"
+ then "tls internal"
+ else ""
+ }
+ ${
+ if vhost_cfg.reverseProxy != null
+ then "reverse_proxy ${vhost_cfg.reverseProxy}"
+ else ""
+ }
+ ${vhost_cfg.virtualHostConfig}
+ }
+ '')
+ |> builtins.concatStringsSep "\n\n");
+
+ services."caddy".systemd.service = {
+ };
+ };
+}
diff --git a/services/goaccess.nix b/services/goaccess.nix
new file mode 100644
index 0000000..dd6b26d
--- /dev/null
+++ b/services/goaccess.nix
@@ -0,0 +1,136 @@
+{
+ lib,
+ pkgs,
+ config,
+ inputs,
+ ...
+}: let
+ cfg = config.goaccess;
+in {
+ options."caddy-goaccess" = {
+ timeZone = lib.mkOption {
+ type = lib.types.str;
+ description = "time zone";
+ };
+ logFile = lib.mkOption {
+ type = lib.types.str;
+ description = "caddy log file for goaccess to read (must be chmod 775)";
+ };
+ websocketUrl = lib.mkOption {
+ type = lib.types.str;
+ description = "url that the frontend should use to attach to the service websocket";
+ };
+ };
+ config = let
+ settings = {
+ unix-socket = "/run/caddy-goaccess/socket";
+ ws-url = cfg.websocketUrl;
+
+ date-format = "%s";
+ log-format = "CADDY";
+ tz = cfg.timeZone;
+ log-file = cfg.logFile;
+ geoip-database = inputs.geolite-db;
+
+ output = "/run/caddy-goaccess/index.html";
+ real-time-html = "true";
+ external-assets = "true";
+ all-static-files = "false";
+ html-report-title = "stats@ganymede";
+ hl-header = "true";
+ agent-list = "false";
+ with-output-resolver = "false";
+ http-method = "yes";
+ http-protocol = "yes";
+ "4xx-to-unique-count" = "false";
+ ignore-crawlers = "false";
+ crawlers-only = "false";
+ unknowns-as-crawlers = "false";
+ real-os = "true";
+ };
+ in {
+ configData."goaccess.conf".text = settings |> builtins.mapAttrs (k: v: "${k} ${toString v}") |> builtins.attrValues |> lib.concatStringsSep "\n";
+ services."caddy-goaccess" = {
+ systemd.socket = {
+ description = "caddy-goaccess uds";
+ socketConfig = {
+ ListenStream = "/run/caddy-goaccess/socket";
+ SocketMode = "0660";
+ SocketUser = "goaccess";
+ SocketGroup = "caddy";
+ };
+ wantedBy = ["sockets.target"];
+ };
+
+ systemd.service = {
+ description = "GoAccess Real-Time Log Analyzer";
+ restartIfChanged = true;
+ wants = ["network-online.target" "caddy.service"];
+ after = ["network-online.target" "caddy.service"];
+ requires = ["caddy-goaccess.socket"];
+
+ serviceConfig = {
+ Type = "simple";
+
+ DynamicUser = true;
+ SupplimentaryGroup = "caddy"; # to read caddy log files
+ RuntimeDirectory = "caddy-goaccess"; # /run/caddy-goaccess
+ ExecStart = "${pkgs.goaccess}/bin/goaccess -p ${config.configData."goaccess.conf".path}";
+
+ # hardening stuff
+ AmbientCapabilities = [];
+ CapabilityBoundingSet = [
+ "~CAP_RAWIO"
+ "~CAP_MKNOD"
+ "~CAP_AUDIT_CONTROL"
+ "~CAP_AUDIT_READ"
+ "~CAP_AUDIT_WRITE"
+ "~CAP_SYS_BOOT"
+ "~CAP_SYS_TIME"
+ "~CAP_SYS_MODULE"
+ "~CAP_SYS_PACCT"
+ "~CAP_LEASE"
+ "~CAP_LINUX_IMMUTABLE"
+ "~CAP_IPC_LOCK"
+ "~CAP_BLOCK_SUSPEND"
+ "~CAP_WAKE_ALARM"
+ "~CAP_SYS_TTY_CONFIG"
+ "~CAP_MAC_ADMIN"
+ "~CAP_MAC_OVERRIDE"
+ "~CAP_NET_ADMIN"
+ "~CAP_NET_BROADCAST"
+ "~CAP_NET_RAW"
+ "~CAP_SYS_ADMIN"
+ "~CAP_SYS_PTRACE"
+ "~CAP_SYSLOG"
+ ];
+ DevicePolicy = "closed";
+ KeyringMode = "private";
+ LockPersonality = true;
+ NoNewPrivileges = true;
+ PrivateDevices = true;
+ PrivateMounts = true;
+ PrivateTmp = true;
+ ProtectClock = true;
+ ProtectControlGroups = true;
+ ProtectHome = true;
+ ProtectHostname = true;
+ ProtectKernelLogs = true;
+ ProtectKernelModules = true;
+ ProtectKernelTunables = true;
+ ProtectSystem = "full";
+ RemoveIPC = true;
+ RestrictAddressFamilies = [
+ "AF_UNIX"
+ "AF_INET"
+ "AF_INET6"
+ ];
+ RestrictNamespaces = true;
+ RestrictRealtime = true;
+ };
+
+ wantedBy = ["multi-user.target"];
+ };
+ };
+ };
+}
diff --git a/tmux-tsunami/flake.lock b/tmux-tsunami/flake.lock
new file mode 100644
index 0000000..63066a7
--- /dev/null
+++ b/tmux-tsunami/flake.lock
@@ -0,0 +1,42 @@
+{
+ "nodes": {
+ "nix-std": {
+ "locked": {
+ "lastModified": 1710870712,
+ "narHash": "sha256-e+7MJF2gsgTBuOWv4mCimSP0D9+naeFSw9a7N3yEmv4=",
+ "owner": "chessai",
+ "repo": "nix-std",
+ "rev": "31bbc925750cc9d8f828fe55cee1a2bd985e0c00",
+ "type": "github"
+ },
+ "original": {
+ "owner": "chessai",
+ "repo": "nix-std",
+ "type": "github"
+ }
+ },
+ "nixpkgs": {
+ "locked": {
+ "lastModified": 1759578544,
+ "narHash": "sha256-GFvucgx8PDytuftANATwu7IQMydblpMDWNZwjg2Oyb4=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "b0bb1a55163139b0b24228143567f0c1c8c178f4",
+ "type": "github"
+ },
+ "original": {
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "root": {
+ "inputs": {
+ "nix-std": "nix-std",
+ "nixpkgs": "nixpkgs"
+ }
+ }
+ },
+ "root": "root",
+ "version": 7
+}
diff --git a/tmux-tsunami/flake.nix b/tmux-tsunami/flake.nix
new file mode 100644
index 0000000..e74feb7
--- /dev/null
+++ b/tmux-tsunami/flake.nix
@@ -0,0 +1,18 @@
+{
+ inputs = {
+ nixpkgs.url = "github:NixOS/nixpkgs";
+ nix-std.url = "github:chessai/nix-std";
+ };
+ outputs = inputs: {
+ hjemModules.tsunami = import ./modules/hjem.nix {
+ std = inputs.nix-std.lib;
+ };
+
+ packages."x86_64-linux".tsunamiInstaller = let
+ installer = import ./modules/installer.nix {
+ pkgs = import inputs.nixpkgs {system = "x86_64-linux";};
+ };
+ in
+ installer.mkInstaller;
+ };
+}
diff --git a/tmux-tsunami/modules/common/bar.nix b/tmux-tsunami/modules/common/bar.nix
new file mode 100644
index 0000000..cbb162a
--- /dev/null
+++ b/tmux-tsunami/modules/common/bar.nix
@@ -0,0 +1,69 @@
+{
+ config,
+ lib,
+ tsunamiLib,
+ ...
+}: let
+ inherit (tsunamiLib) scriptPath;
+in {
+ options = let
+ hexColor = lib.types.strMatching "^#?([0-9a-fA-F]{6}|[0-9a-fA-F]{3})$";
+ in {
+ tsunami.theme = {
+ bg = lib.mkOption {
+ type = hexColor;
+ default = "#fff";
+ };
+ bg_dark = lib.mkOption {
+ type = hexColor;
+ default = "#000";
+ };
+ };
+ };
+ config = {
+ tsunami = {
+ scripts = {
+ "battery" = ''
+ energy_now=$(cat /sys/class/power_supply/BAT0/energy_now)
+ energy_full=$(cat /sys/class/power_supply/BAT0/energy_full)
+ percentage=$((energy_now * 100 / energy_full))
+ printf "%.0f%%" "$percentage"
+ '';
+
+ "bar" = ''
+ tmux set -g status on
+
+ tmux set -g status-position bottom
+ tmux set -g status-justify absolute-centre
+ tmux set -g status-bg "${config.tsunami.theme.bg}"
+
+ tmux set -g status-left-style fg=green,bold
+ tmux set -g status-left " #{client_session}"
+
+ tmux set -g window-status-style fg=color243
+ tmux set -g window-status-format " #I "
+ tmux set -g window-status-current-style fg=color12,bold
+ tmux set -g window-status-current-format " #I "
+ tmux set -g window-status-separator ""
+
+ tmux set -g status-right "#[fg=color243]%l:%M #[fg=red]#(${scriptPath "battery"}) "
+
+ ## pane borders
+ tmux set -g pane-border-style fg=${config.tsunami.theme.bg_dark},bg=${config.tsunami.theme.bg_dark}
+ tmux set -g pane-active-border-style fg=${config.tsunami.theme.bg_dark},bg=${config.tsunami.theme.bg_dark}
+
+ ## pane backgrounds
+ # Set the foreground/background color for the active window
+ tmux set -g window-active-style bg=${config.tsunami.theme.bg}
+
+ # Set the foreground/background color for all other windows
+ tmux set -g window-style bg=${config.tsunami.theme.bg_dark}
+ '';
+ };
+
+ confs."bar" = ''
+ run-shell ${scriptPath "bar"}
+ '';
+ };
+ };
+}
diff --git a/tmux-tsunami/modules/common/defaultGlobalKeys.nix b/tmux-tsunami/modules/common/defaultGlobalKeys.nix
new file mode 100644
index 0000000..fc3c669
--- /dev/null
+++ b/tmux-tsunami/modules/common/defaultGlobalKeys.nix
@@ -0,0 +1,66 @@
+{
+ pkgs,
+ tsunamiLib,
+ ...
+}: let
+ inherit (tsunamiLib) scriptPath;
+in {
+ tsunami.scripts."split" = ''
+ width=$(tmux display -p "#{pane_width}")
+ height=$(tmux display -p "#{pane_height}")
+
+ if (( $(echo "$width / $height > 2.5" | ${pkgs.bc}/bin/bc -l) )); then
+ tmux split-window -h "$@"
+ else
+ tmux split-window -v "$@"
+ fi
+ '';
+
+ tsunami.scripts."clean-sessions" = ''
+ current="$(tmux display -p '#{session_name}')"
+
+ tmux list-sessions -F '#{session_name}' | while read -r line; do
+ if [[ "$line" != "$current" && "$line" =~ ^[[:digit:]]+$ ]]; then
+ tmux kill-session -t "$line"
+ fi
+ done
+ '';
+
+ tsunami.keys.global = [
+ {
+ key = "C-w";
+ exec = "kill-pane";
+ }
+ {
+ key = "M-z";
+ exec = "resize-pane -Z";
+ }
+ {
+ key = "C-Enter";
+ exec = ''run-shell "${scriptPath "split"} -c '#{?@default-path,#{@default-path},#{pane_current_path}}'"'';
+ }
+ {
+ key = "C-t";
+ exec = "new-window -c '#{?@default-path,#{@default-path},#{pane_current_path}}'";
+ }
+ {
+ key = "C-Tab";
+ exec = "next-window";
+ }
+ {
+ key = "C-S-Tab";
+ exec = "previous-window";
+ }
+ ];
+
+ tsunami.confs."windows" = ''
+ unbind -n MouseDown3Pane
+ set -g allow-rename on
+ set -g automatic-rename off
+ set -g renumber-windows on
+ set -g base-index 1
+
+ set-hook -ag client-detached 'run-shell ${scriptPath "clean-sessions"}'
+ set-hook -ag client-session-changed 'run-shell ${scriptPath "clean-sessions"}'
+ '';
+}
diff --git a/tmux-tsunami/modules/common/defaultLeaderMenu.nix b/tmux-tsunami/modules/common/defaultLeaderMenu.nix
new file mode 100644
index 0000000..71489e7
--- /dev/null
+++ b/tmux-tsunami/modules/common/defaultLeaderMenu.nix
@@ -0,0 +1,51 @@
+{tsunamiLib, ...}: let
+ inherit (tsunamiLib) scriptPath;
+in {
+ tsunami.scripts = {
+ "find-pane" = ''
+ display_format="#{window_name} #{pane_title} #{pane_current_path} #{pane_current_command}"
+ hidden_format="#{session_name}:#{window_id}:#{pane_id}"
+
+ # select pane
+ selected=$(tmux list-panes -a -F "$hidden_format:$display_format" | fzf --delimiter=: --with-nth 4 --color=hl:2)
+ [ -z "$selected" ] || exit
+
+ # switch to selected
+ args=(''\${selected//:/ })
+ tmux select-pane -t ''\${args[2]} && tmux select-window -t ''\${args[1]} && tmux switch-client -t ''\${args[0]}
+ '';
+
+ "search-pane" = ''
+ trap 'rm -f -- "''\${scrollback:-}"' EXIT
+ scrollback="$(mktemp)"
+
+ tmux capture-pane -e -p -S - > "$scrollback"
+ cat "$scrollback" | fzf --ansi
+
+ exit 0
+ '';
+ };
+
+ tsunami.keys.leader = [
+ {
+ name = "Find Pane";
+ key = "C-w";
+ exec = ''run-shell "${scriptPath "minibuffer"} '${scriptPath "find-pane"}'"'';
+ }
+ {
+ name = "Search Pane";
+ key = "C-/";
+ exec = ''run-shell "${scriptPath "minibuffer"} '${scriptPath "search-pane"}'"'';
+ }
+ {
+ name = "Detach";
+ key = "C-d";
+ exec = "detach";
+ }
+ {
+ name = "Reload";
+ key = "C-r";
+ exec = "source-file ~/.config/tmux/tmux.conf";
+ }
+ ];
+}
diff --git a/tmux-tsunami/modules/common/findFile.nix b/tmux-tsunami/modules/common/findFile.nix
new file mode 100644
index 0000000..3d08603
--- /dev/null
+++ b/tmux-tsunami/modules/common/findFile.nix
@@ -0,0 +1,66 @@
+{
+ pkgs,
+ config,
+ lib,
+ tsunamiLib,
+ std,
+ ...
+}: let
+ inherit (tsunamiLib) scriptPath filePath;
+ cfg = config.tsunami.findFile;
+in {
+ options = {
+ tsunami.findFile = {
+ enable = lib.mkOption {
+ type = lib.types.bool;
+ default = false;
+ };
+ };
+ };
+
+ config = lib.mkIf cfg.enable {
+ tsunami.keys.leader = [
+ {
+ name = "Find File";
+ key = "C-f";
+ exec = ''run-shell "${scriptPath "minibuffer"} -d '#{?@default-path,#{@default-path},#{pane_current_path}}' '${scriptPath "find-file"}'"'';
+ }
+ ];
+ tsunami.scripts = {
+ "minibuffer" = ''
+ window_height="$(tmux display -p '#{window_height}')"
+ tmux display-popup -EB \
+ -w 100% -h 16 \
+ -x 0 -y "$(($window_height + 1))" \
+ "$@"
+ '';
+
+ "find-file" = ''${pkgs.broot}/bin/broot --conf ${filePath "broot_find_file.toml"}'';
+ };
+
+ tsunami.files."broot_find_file.toml" = std.serde.toTOML {
+ imports = ["~/.config/broot/conf.hjson"];
+ quit_on_last_cancel = true;
+ verbs = [
+ {
+ invocation = "tmux-split";
+ external = ["bash" "-c" ''${scriptPath "split"} -c "#{?@default-path,#{@default-path},#{pane_current_path}}" "$EDITOR '{file}'"''];
+ key = "ctrl-s";
+ apply_to = "file";
+ leave_broot = true;
+ }
+ {
+ invocation = "tmux-window";
+ external = ["tmux" "new-window" "-c" "#{?@default-path,#{@default-path},#{pane_current_path}}" "$EDITOR '{file}'"];
+ key = "ctrl-w";
+ apply_to = "file";
+ leave_broot = true;
+ }
+ {
+ key = "enter";
+ cmd = ":tmux-window";
+ }
+ ];
+ };
+ };
+}
diff --git a/tmux-tsunami/modules/common/fzfExec.nix b/tmux-tsunami/modules/common/fzfExec.nix
new file mode 100644
index 0000000..2de3741
--- /dev/null
+++ b/tmux-tsunami/modules/common/fzfExec.nix
@@ -0,0 +1,55 @@
+{
+ tsunamiLib,
+ lib,
+ pkgs,
+ config,
+ ...
+}: let
+ inherit (tsunamiLib) scriptPath;
+ cfg = config.tsunami.fzfExec;
+in {
+ options = {
+ tsunami.fzfExec = {
+ enable = lib.mkOption {
+ type = lib.types.bool;
+ default = true;
+ };
+ };
+ };
+
+ config = lib.mkIf cfg.enable {
+ tsunami.scripts = {
+ # this is buggy af; it only works half the time.
+ # But it's the only way I know of to get docs on a specific tmux command
+ "tmux-doc" = ''
+ usage=$(tmux list-commands -F "#{command_list_name} #{command_list_usage}" "$1")
+
+ man tmux | ${pkgs.gawk}/bin/awk -v usage="$usage" '
+ index($0, usage) > 0 { found=1 }
+ found {
+ print
+ if ($0 == "") exit
+ }
+ '
+ '';
+
+ "fzf-exec" = ''
+ all_cmds() {
+ tmux list-commands -F $'#{command_list_name}#{?command_list_alias,\n#{command_list_alias},}'
+ }
+
+ selected_cmd=$(all_cmds | ${pkgs.fzf}/bin/fzf --bind 'enter:accept-or-print-query,tab:replace-query,alt-backspace:clear-query' --prompt : --preview "~/.config/tmux/scripts/tmux-doc.sh $(printf '{}' | cut -d' ' -f1)")
+ test -z "$selected_cmd" && exit
+
+ tmux $(echo "$selected_cmd" | sed "s@~@$HOME@g")
+ '';
+ };
+
+ tsunami.keys.global = [
+ {
+ key = "M-x";
+ exec = "run-shell '${scriptPath "minibuffer"} -h 10 ${scriptPath "fzf-exec"}'";
+ }
+ ];
+ };
+}
diff --git a/tmux-tsunami/modules/common/keys.nix b/tmux-tsunami/modules/common/keys.nix
new file mode 100644
index 0000000..9e3c1a2
--- /dev/null
+++ b/tmux-tsunami/modules/common/keys.nix
@@ -0,0 +1,66 @@
+{
+ lib,
+ config,
+ tsunamiLib,
+ ...
+}: let
+ inherit (tsunamiLib) scriptPath;
+ inherit (lib.strings) escapeShellArg;
+ cfg = config.tsunami.keys;
+in {
+ options = let
+ tmuxKey = lib.types.strMatching "^((C|M|S)-)*(.|Enter|Escape|Tab|Up|Down|Left|Right)$";
+ inherit (lib) mkOption;
+ in {
+ tsunami.keys = {
+ leader = mkOption {
+ type = with lib.types;
+ listOf (submodule {
+ options = {
+ name = mkOption {type = str;};
+ key = mkOption {type = tmuxKey;};
+ exec = mkOption {type = str;};
+ };
+ });
+ };
+
+ global = mkOption {
+ type = with lib.types;
+ listOf (submodule {
+ options = {
+ key = mkOption {type = tmuxKey;};
+ exec = mkOption {type = str;};
+ };
+ });
+ };
+ };
+ };
+
+ config = {
+ tsunami.scripts = {
+ "menu" = ''
+ tmux display-menu \
+ -x "#{window_width}" -y S \
+ -b none \
+ -s "bg=#313244,fg=#9399b2" \
+ -S "bg=#313244" \
+ -H "bg=#45475a fg=#b4befe" \
+ "$@"
+ '';
+
+ "leader-menu" = let
+ args = lib.concatStringsSep " " (builtins.map (f: "${escapeShellArg f.name} ${f.key} ${escapeShellArg f.exec}") cfg.leader);
+ in ''
+ ${scriptPath "menu"} ${args}
+ '';
+ };
+ tsunami.keys.global = [
+ {
+ key = "C-x";
+ exec = "run-shell ${scriptPath "leader-menu"}";
+ }
+ ];
+
+ tsunami.confs."global-keys" = cfg.global |> builtins.map (f: "bind-key -n ${f.key} ${escapeShellArg f.exec}") |> lib.concatStringsSep "\n";
+ };
+}
diff --git a/tmux-tsunami/modules/common/main.nix b/tmux-tsunami/modules/common/main.nix
new file mode 100644
index 0000000..25366d9
--- /dev/null
+++ b/tmux-tsunami/modules/common/main.nix
@@ -0,0 +1,23 @@
+{lib, ...}: let
+ inherit (lib) mkOption mkEnableOption;
+in {
+ options = {
+ tsunami = {
+ enable = mkEnableOption "tsunami tmux distro";
+ scripts = mkOption {
+ type = lib.types.attrsOf (lib.types.str);
+ default = [];
+ };
+
+ files = mkOption {
+ type = lib.types.attrsOf (lib.types.str);
+ default = [];
+ };
+
+ confs = mkOption {
+ type = lib.types.attrsOf (lib.types.str);
+ default = [];
+ };
+ };
+ };
+}
diff --git a/tmux-tsunami/modules/common/sessionizer.nix b/tmux-tsunami/modules/common/sessionizer.nix
new file mode 100644
index 0000000..09aa65a
--- /dev/null
+++ b/tmux-tsunami/modules/common/sessionizer.nix
@@ -0,0 +1,92 @@
+{
+ pkgs,
+ config,
+ lib,
+ tsunamiLib,
+ std,
+ ...
+}: let
+ inherit (tsunamiLib) scriptPath filePath;
+ cfg = config.tsunami.sessionizer;
+in {
+ options = {
+ tsunami.sessionizer = {
+ enable = lib.mkOption {
+ type = lib.types.bool;
+ default = false;
+ };
+ };
+ };
+
+ config = lib.mkIf cfg.enable {
+ tsunami.keys.leader = [
+ {
+ name = "Sessions+";
+ key = "C-s";
+ exec = "run-shell ${scriptPath "sessions-menu"}";
+ }
+ ];
+
+ tsunami.scripts = {
+ "sessionizer" =
+ # bash
+ ''
+ [ -z "$1" ] && exit
+
+ selected="$1"
+ session_name="$(basename "$selected" | tr '.' '_')"
+
+ # if the session doesn't exist, create it
+ if ! tmux has-session -t "$session_name"; then
+ tmux new-session -ds "$session_name" -c "$selected"
+ tmux set-option -t "$session_name" @default-path "$selected"
+ fi
+
+ # switch to it
+ tmux switch -t "$session_name"
+ '';
+ "launch-sessionizer" = ''${pkgs.broot}/bin/broot --only-folders --conf ${filePath "broot_sessionizer.toml"}'';
+ "new-session" = ''tmux switch-client -t "$(tmux new-session -dP)"'';
+ "sessions-menu" =
+ # bash
+ ''
+ menu_items=(
+ "Switch Session" s "run-shell '${scriptPath "minibuffer"} -d $HOME ${scriptPath "launch-sessionizer"}'"
+ "New Unnamed Session" n "run-shell ${scriptPath "new-session"}"
+ )
+
+ # put tmux session names into a list
+ mapfile -t sessions < <(tmux list-sessions -F "#{session_name}")
+
+ # ...but limit at 9 sessions
+ for i in "''\${!sessions[@]}"; do
+ (( i >= 9 )) && break # Stop after 9 sessions
+
+ session="''\${sessions[$i]}"
+ key=$((i + 1)) # 1-based key
+
+ # and put those 9 in the list of menu items
+ menu_items+=("$session" "$key" "switch-client -t $session")
+ done
+
+ # Display the menu
+ ${scriptPath "menu"} -T "#[align=centre]Sessions" "''\${menu_items[@]}"
+ '';
+ };
+ tsunami.files = {
+ "broot_sessionizer.toml" = std.serde.toTOML {
+ imports = ["~/.config/broot/conf.hjson"]; # inherit from user's config
+ quit_on_cancel = true;
+ verbs = [
+ {
+ invocation = "session";
+ external = ''bash -c -- "${scriptPath "sessionizer"} '{file}'"'';
+ key = "enter";
+ apply_to = "directory";
+ leave_broot = true;
+ }
+ ];
+ };
+ };
+ };
+}
diff --git a/tmux-tsunami/modules/common/sessionizer2.nix b/tmux-tsunami/modules/common/sessionizer2.nix
new file mode 100644
index 0000000..5f159df
--- /dev/null
+++ b/tmux-tsunami/modules/common/sessionizer2.nix
@@ -0,0 +1,50 @@
+{
+ pkgs,
+ config,
+ lib,
+ tsunamiLib,
+ std,
+ ...
+}: let
+ inherit (tsunamiLib) scriptPath filePath;
+ cfg = config.tsunami.sessionizer2;
+in {
+ options.tsunami.sessionizer2 = {
+ enable = lib.mkOption {
+ type = lib.types.bool;
+ default = true;
+ };
+ };
+
+ config = lib.mkIf cfg.enable {
+ tsunami.keys.leader = [
+ {
+ name = "New Session";
+ key = "C-n";
+ exec = "run-shell ${scriptPath "sessions-menu"}";
+ }
+ ];
+
+ tsunami.files = {
+ "broot_sessionizer2_launcher" = std.serde.toTOML {
+ imports = ["~/.config/broot/conf.hjson"]; # inherit from user's config
+ verbs = [
+ {
+ invocation = "tmux-window";
+ external = ["tmux" "new-window" "-c" "#{?@default-path,#{@default-path},#{pane_current_path}}" "$EDITOR '{file}'"];
+ key = "enter";
+ apply_to = "file";
+ leave_broot = false;
+ }
+ {
+ invocation = "tmux-window";
+ external = ["tmux" "new-window" "-c" "#{?@default-path,#{@default-path},#{pane_current_path}}" "$EDITOR '{file}'"];
+ key = "enter";
+ apply_to = "file";
+ leave_broot = false;
+ }
+ ];
+ };
+ };
+ };
+}
diff --git a/tmux-tsunami/modules/hjem.nix b/tmux-tsunami/modules/hjem.nix
new file mode 100644
index 0000000..019eada
--- /dev/null
+++ b/tmux-tsunami/modules/hjem.nix
@@ -0,0 +1,58 @@
+{std, ...}: {
+ pkgs,
+ lib,
+ config,
+ ...
+}: let
+ cfg = config.tsunami;
+in {
+ imports = lib.filesystem.listFilesRecursive ./common;
+
+ _module.args = {
+ inherit std;
+ tsunamiLib = {
+ scriptPath = f: "~/.config/tmux/scripts/${f}.sh";
+ filePath = f: "~/.config/tmux/files/${f}";
+ };
+ };
+
+ files = lib.mkIf cfg.enable (lib.mkMerge [
+ (
+ cfg.scripts
+ |> builtins.mapAttrs (name: value: {
+ name = ".config/tmux/scripts/${name}.sh";
+ value = {
+ text = value;
+ executable = true;
+ };
+ })
+ |> lib.attrValues
+ |> builtins.listToAttrs
+ )
+ (
+ cfg.files
+ |> builtins.mapAttrs (name: value: {
+ name = ".config/tmux/files/${name}";
+ value.text = value;
+ })
+ |> lib.attrValues
+ |> builtins.listToAttrs
+ )
+ (
+ cfg.confs
+ |> builtins.mapAttrs (name: value: {
+ name = ".config/tmux/conf.d/${name}.conf";
+ value.text = value;
+ })
+ |> lib.attrValues
+ |> builtins.listToAttrs
+ )
+ {
+ ".config/tmux/tmux.conf".text = ''
+ run-shell "find ~/.config/tmux/conf.d -print0 | xargs -0 -n1 tmux source-file"
+ '';
+ }
+ ]);
+
+ packages = lib.optional cfg.enable pkgs.tmux;
+}
diff --git a/tmux-tsunami/modules/installer.nix b/tmux-tsunami/modules/installer.nix
new file mode 100644
index 0000000..9cff811
--- /dev/null
+++ b/tmux-tsunami/modules/installer.nix
@@ -0,0 +1,38 @@
+{pkgs, ...}: let
+ tsunamiLib = {
+ scriptPath = f: "$HOME/.config/tmux/scripts/${f}.sh";
+ filePath = f: "$HOME/.config/tmux/files/${f}";
+ };
+ config = pkgs.lib.evalModules {
+ modules =
+ [
+ {_module.args = {inherit pkgs tsunamiLib;};}
+ ]
+ ++ (pkgs.lib.filesystem.listFilesRecursive ./common);
+ };
+
+ moduleConfig = pkgs.writeText "tsunami-installer-config.json" (builtins.toJSON {
+ inherit (config.config.tsunami) scripts files confs;
+ });
+in {
+ mkInstaller =
+ pkgs.runCommand "tsunami-installer" {
+ buildInputs = [pkgs.jq];
+ } ''
+ mkdir -p $out
+ {
+ cat ${moduleConfig} | jq -r '
+ (.scripts | to_entries[] | "cat <<\"EOF\" > $HOME/.config/tmux/scripts/\(.key).sh\n\(.value)\nEOF\n"),
+ (.files | to_entries[] | "cat <<\"EOF\" > $HOME/.config/tmux/files/\(.key)\n\(.value)\nEOF\n"),
+ (.confs | to_entries[] | "cat <<\"EOF\" > $HOME/.config/tmux/conf.d/\(.key).conf\n\(.value)\nEOF\n")
+ '
+ printf '
+ cat <<EOF > $HOME/.config/tmux/tmux.conf
+ run-shell "find $out/conf.d -print0 | xargs -0 -n1 tmux source-file"
+ EOF
+ '
+ } > $out/tsunami-installer
+
+ chmod +x $out/tsunami-installer
+ '';
+}
diff --git a/tmux-tsunami/planning.md b/tmux-tsunami/planning.md
new file mode 100644
index 0000000..872bbb5
--- /dev/null
+++ b/tmux-tsunami/planning.md
@@ -0,0 +1,56 @@
+## Pieces:
+sessionizer
+- leaderkey keybinding C-p+
+- script launch-sessionizer
+- script clean-sessions
+- script new-session
+- script projects-menu
+- extrafile broot_sessionizer
+
+```nix
+{pkgs, tsunamiLib, ...}: let
+ inherit (tsunamiLib) scriptPath filePath;
+in {
+ tsunami.keys.leader = [{
+ name = "Projects+";
+ key = "C-p";
+ exec = "run-shell ${scriptPath "projects-menu"}";
+ }];
+
+ tsunami.scripts = {
+ "launch-sessionizer" = ''${pkgs.broot}/bin/broot --conf ${filePath "broot_sessionizer.toml"}'';
+ "clean-sessions" = ''...'';
+ "new-session" = ''...'';
+ };
+ tsunami.files = {
+ "broot_sessionizer.toml" = (pkgs.formats.toml {}).generate "file.toml" {
+ ...
+ };
+ };
+}
+```
+
+find-file
+- leaderkey keybinding C-f
+- script find-file
+- extrafile broot_find_file
+fzf-exec
+- keybinding M-x
+- script tmux-doc
+- script fzf-exec
+search-buffer
+- leaderkey keybinding C-/
+- script search-buffer
+find-window
+- leaderkey keybinding C-w
+- script find-buffer
+leader-key
+- keybinding C-x
+- script leader-menu
+bar
+- script bar
+- script battery
+main config
+- script minibuffer
+- script split
+-