aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCollin Williams <96917990+bluedragon1221@users.noreply.github.com>2026-04-09 13:14:01 -0500
committerCollin Williams <96917990+bluedragon1221@users.noreply.github.com>2026-04-09 13:14:01 -0500
commit21de12cd6e63adf463674942f1a8f9b658f47945 (patch)
tree5229a5300c1e69d637a1e03539dc19de42244440
parent2147a65b5ff7cef352d95dfa72c705145771f3a3 (diff)
jta changes
-rw-r--r--flake.lock168
-rw-r--r--flake.nix5
-rw-r--r--hosts.toml3
-rw-r--r--hosts/ganymede/caddy.nix2
-rw-r--r--hosts/ganymede/config.nix30
-rw-r--r--hosts/ganymede/nixos.nix2
-rw-r--r--hosts/mercury/hjem.nix9
-rw-r--r--hosts/mercury/nixos.nix1
-rw-r--r--modules/desktop/hjem/programs/foot.nix2
-rw-r--r--modules/desktop/hjem/wm/sway.nix50
-rw-r--r--modules/nix/nixos/default.nix5
-rw-r--r--modules/services/nixos/jta/assignments.html148
-rw-r--r--modules/services/nixos/jta/default.nix1
-rw-r--r--modules/services/nixos/jta/index.html199
-rw-r--r--modules/services/nixos/jta/main.go31
-rw-r--r--modules/services/nixos/jta/style.css29
-rw-r--r--modules/user/nixos/default.nix18
17 files changed, 463 insertions, 240 deletions
diff --git a/flake.lock b/flake.lock
index c7b97b8..bcb5040 100644
--- a/flake.lock
+++ b/flake.lock
@@ -10,11 +10,11 @@
"systems": "systems"
},
"locked": {
- "lastModified": 1762618334,
- "narHash": "sha256-wyT7Pl6tMFbFrs8Lk/TlEs81N6L+VSybPfiIgzU8lbQ=",
+ "lastModified": 1770165109,
+ "narHash": "sha256-9VnK6Oqai65puVJ4WYtCTvlJeXxMzAp/69HhQuTdl/I=",
"owner": "ryantm",
"repo": "agenix",
- "rev": "fcdea223397448d35d9b31f798479227e80183f6",
+ "rev": "b027ee29d959fda4b60b57566d64c98a202e0feb",
"type": "github"
},
"original": {
@@ -26,11 +26,11 @@
"betterfox": {
"flake": false,
"locked": {
- "lastModified": 1769104536,
- "narHash": "sha256-D2MIFdYMS3xrfO2vDYjCmC3Ah96jg5XUzvwMX3xJQBo=",
+ "lastModified": 1774552373,
+ "narHash": "sha256-B9JG4GhjMguTUukinSrJWmEWmjU1EJ1UzlfQ9q+NTp8=",
"owner": "yokoffing",
"repo": "Betterfox",
- "rev": "310cbdee6ca20eb881749a559cb572ce9272a981",
+ "rev": "fd29d4d313eda7a93f45f2f66bb0e87ff35d1a72",
"type": "github"
},
"original": {
@@ -47,11 +47,11 @@
]
},
"locked": {
- "lastModified": 1769889994,
- "narHash": "sha256-uEn3WcpPHe3sMJMgIJ0XW3f4/+TRzZpNgv4vu5/gjmA=",
+ "lastModified": 1775334496,
+ "narHash": "sha256-dUopEMj5KhH1vnv43o29kMsayp0Ki+9EfIn4UAE9sAU=",
"owner": "9001",
"repo": "copyparty",
- "rev": "9b436eb52e5cfe7a0a8e59dd9f1a37351f3a2abd",
+ "rev": "ede692925edfc6fca7a52f483414d543e9303237",
"type": "github"
},
"original": {
@@ -62,11 +62,11 @@
},
"crane": {
"locked": {
- "lastModified": 1769287525,
- "narHash": "sha256-gABuYA6BzoRMLuPaeO5p7SLrpd4qExgkwEmYaYQY4bM=",
+ "lastModified": 1773189535,
+ "narHash": "sha256-E1G/Or6MWeP+L6mpQ0iTFLpzSzlpGrITfU2220Gq47g=",
"owner": "ipetkov",
"repo": "crane",
- "rev": "0314e365877a85c9e5758f9ea77a9972afbb4c21",
+ "rev": "6fa2fb4cf4a89ba49fc9dd5a3eb6cde99d388269",
"type": "github"
},
"original": {
@@ -104,11 +104,11 @@
]
},
"locked": {
- "lastModified": 1769524058,
- "narHash": "sha256-zygdD6X1PcVNR2PsyK4ptzrVEiAdbMqLos7utrMDEWE=",
+ "lastModified": 1773889306,
+ "narHash": "sha256-PAqwnsBSI9SVC2QugvQ3xeYCB0otOwCacB1ueQj2tgw=",
"owner": "nix-community",
"repo": "disko",
- "rev": "71a3fc97d80881e91710fe721f1158d3b96ae14d",
+ "rev": "5ad85c82cc52264f4beddc934ba57f3789f28347",
"type": "github"
},
"original": {
@@ -120,11 +120,11 @@
"firefox-csshacks": {
"flake": false,
"locked": {
- "lastModified": 1768572660,
- "narHash": "sha256-VlxdEeMqgRclKQqnKDhBXQ+19ce35ZFdC6SaKg2skUk=",
+ "lastModified": 1774892585,
+ "narHash": "sha256-TCbdj6ZedgPSDQWJWFBPzDlLdgRcnsby3DWNHxmQ+b4=",
"owner": "MrOtherGuy",
"repo": "firefox-csshacks",
- "rev": "bcb85f04764802557fc1d1dff6f53461065e4893",
+ "rev": "d02c1a92fa86777f3ae52f19e0c338a67eabe908",
"type": "github"
},
"original": {
@@ -149,6 +149,22 @@
"type": "github"
}
},
+ "flake-registry": {
+ "flake": false,
+ "locked": {
+ "lastModified": 1763556067,
+ "narHash": "sha256-q2jzJQdsJMpD3dbuNphQJgwx6XeGPonWOp43U0nY7o0=",
+ "owner": "NixOS",
+ "repo": "flake-registry",
+ "rev": "cb70c9306b44501de412649c356dee503a25f119",
+ "type": "github"
+ },
+ "original": {
+ "owner": "NixOS",
+ "repo": "flake-registry",
+ "type": "github"
+ }
+ },
"flake-utils": {
"locked": {
"lastModified": 1678901627,
@@ -183,7 +199,7 @@
"geolite-db": {
"flake": false,
"locked": {
- "narHash": "sha256-+9DF7PHaxaMdrnf3IPnuM1Eyyr8MgUCiBBsO0PuaAD4=",
+ "narHash": "sha256-ea/ne52oFzES4jUjMri2OplkHpOJyOfR38beO4LRBgY=",
"type": "file",
"url": "https://github.com/P3TERX/GeoLite.mmdb/releases/latest/download/GeoLite2-City.mmdb"
},
@@ -219,15 +235,14 @@
"nix-darwin": "nix-darwin",
"nixpkgs": [
"nixpkgs"
- ],
- "smfh": "smfh"
+ ]
},
"locked": {
- "lastModified": 1769053318,
- "narHash": "sha256-cKETEBrseo7Iz+bOzflwy1xTpDuUj3QaLA+P49yJw8k=",
+ "lastModified": 1775569958,
+ "narHash": "sha256-W+kpLQ5J/DDaY1nUmvN9aIAfh5ixdyqYM76ZUdcvMBU=",
"owner": "feel-co",
"repo": "hjem",
- "rev": "9d0c8d4b44f661910595b07e6480557644c1431c",
+ "rev": "32bd6d54d805a3eb41efa62a940eeceaf263c4a8",
"type": "github"
},
"original": {
@@ -264,14 +279,14 @@
"nixpkgs"
],
"pre-commit": "pre-commit",
- "rust-overlay": "rust-overlay_2"
+ "rust-overlay": "rust-overlay"
},
"locked": {
- "lastModified": 1769949118,
- "narHash": "sha256-Ue9kYZenqMw9yHGFnBpoWxQqhs2tlH/el4AxKVicXBE=",
+ "lastModified": 1775510693,
+ "narHash": "sha256-gZfJ07j/oOciDi8mF/V8QTm7YCeDcusNSMZzBFi8OUM=",
"owner": "nix-community",
"repo": "lanzaboote",
- "rev": "0be0641613a13323a61a6406c46b6f28b8894395",
+ "rev": "3fe0ae8cb285e0ad101a9675f4190d455fb05e85",
"type": "github"
},
"original": {
@@ -288,11 +303,11 @@
]
},
"locked": {
- "lastModified": 1765065051,
- "narHash": "sha256-b7W9WsvyMOkUScNxbzS45KEJp0iiqRPyJ1I3JBE+oEE=",
+ "lastModified": 1775037210,
+ "narHash": "sha256-KM2WYj6EA7M/FVZVCl3rqWY+TFV5QzSyyGE2gQxeODU=",
"owner": "nix-darwin",
"repo": "nix-darwin",
- "rev": "7e22bf538aa3e0937effcb1cee73d5f1bcc26f79",
+ "rev": "06648f4902343228ce2de79f291dd5a58ee12146",
"type": "github"
},
"original": {
@@ -308,11 +323,11 @@
]
},
"locked": {
- "lastModified": 1772945408,
- "narHash": "sha256-PMt48sEQ8cgCeljQ9I/32uoBq/8t8y+7W/nAZhf72TQ=",
+ "lastModified": 1775365369,
+ "narHash": "sha256-DgH5mveLoau20CuTnaU5RXZWgFQWn56onQ4Du2CqYoI=",
"owner": "nix-community",
"repo": "nix-index-database",
- "rev": "1c1d8ea87b047788fd7567adf531418c5da321ec",
+ "rev": "cef5cf82671e749ac87d69aadecbb75967e6f6c3",
"type": "github"
},
"original": {
@@ -338,11 +353,11 @@
},
"nixos-facter-modules": {
"locked": {
- "lastModified": 1766558141,
- "narHash": "sha256-Ud9v49ZPsoDBFuyJSQ2Mpw1ZgAH/aMwUwwzrVoetNus=",
+ "lastModified": 1773858690,
+ "narHash": "sha256-oW0/lC0oRG5H5LaK6Rmh9L1wmkn9TbenM4bXwnIEDKA=",
"owner": "numtide",
"repo": "nixos-facter-modules",
- "rev": "e796d536e3d83de74267069e179dc620a608ed7d",
+ "rev": "139dcef4dfc97009629c445806f197883351ab4a",
"type": "github"
},
"original": {
@@ -353,11 +368,11 @@
},
"nixpkgs": {
"locked": {
- "lastModified": 1769789167,
- "narHash": "sha256-kKB3bqYJU5nzYeIROI82Ef9VtTbu4uA3YydSk/Bioa8=",
+ "lastModified": 1775423009,
+ "narHash": "sha256-vPKLpjhIVWdDrfiUM8atW6YkIggCEKdSAlJPzzhkQlw=",
"owner": "NixOS",
"repo": "nixpkgs",
- "rev": "62c8382960464ceb98ea593cb8321a2cf8f9e3e5",
+ "rev": "68d8aa3d661f0e6bd5862291b5bb263b2a6595c9",
"type": "github"
},
"original": {
@@ -377,11 +392,11 @@
]
},
"locked": {
- "lastModified": 1769069492,
- "narHash": "sha256-Efs3VUPelRduf3PpfPP2ovEB4CXT7vHf8W+xc49RL/U=",
+ "lastModified": 1772893680,
+ "narHash": "sha256-JDqZMgxUTCq85ObSaFw0HhE+lvdOre1lx9iI6vYyOEs=",
"owner": "cachix",
"repo": "pre-commit-hooks.nix",
- "rev": "a1ef738813b15cf8ec759bdff5761b027e3e1d23",
+ "rev": "8baab586afc9c9b57645a734c820e4ac0a604af9",
"type": "github"
},
"original": {
@@ -397,6 +412,7 @@
"copyparty": "copyparty",
"disko": "disko",
"firefox-csshacks": "firefox-csshacks",
+ "flake-registry": "flake-registry",
"fx-autoconfig": "fx-autoconfig",
"geolite-db": "geolite-db",
"hjem": "hjem",
@@ -411,38 +427,16 @@
"rust-overlay": {
"inputs": {
"nixpkgs": [
- "hjem",
- "smfh",
- "nixpkgs"
- ]
- },
- "locked": {
- "lastModified": 1763347184,
- "narHash": "sha256-6QH8hpCYJxifvyHEYg+Da0BotUn03BwLIvYo3JAxuqQ=",
- "owner": "oxalica",
- "repo": "rust-overlay",
- "rev": "08895cce80433978d5bfd668efa41c5e24578cbd",
- "type": "github"
- },
- "original": {
- "owner": "oxalica",
- "repo": "rust-overlay",
- "type": "github"
- }
- },
- "rust-overlay_2": {
- "inputs": {
- "nixpkgs": [
"lanzaboote",
"nixpkgs"
]
},
"locked": {
- "lastModified": 1769309768,
- "narHash": "sha256-AbOIlNO+JoqRJkK1VrnDXhxuX6CrdtIu2hSuy4pxi3g=",
+ "lastModified": 1773544328,
+ "narHash": "sha256-Iv+qez54LAz+isij4APBk31VWA//Go81hwFOXr5iWTw=",
"owner": "oxalica",
"repo": "rust-overlay",
- "rev": "140c9dc582cb73ada2d63a2180524fcaa744fad5",
+ "rev": "4f977d776793c8bfbfdd7eca7835847ccc48874e",
"type": "github"
},
"original": {
@@ -451,29 +445,6 @@
"type": "github"
}
},
- "smfh": {
- "inputs": {
- "nixpkgs": [
- "hjem",
- "nixpkgs"
- ],
- "rust-overlay": "rust-overlay",
- "systems": "systems_2"
- },
- "locked": {
- "lastModified": 1763430012,
- "narHash": "sha256-06G7pXUdpMnUqR0JWWvV7sA8oNGOZU1cSLqQS1GMf7Y=",
- "owner": "feel-co",
- "repo": "smfh",
- "rev": "eddda76e3dd4c6deaea5f819f174fc16dbe70f90",
- "type": "github"
- },
- "original": {
- "owner": "feel-co",
- "repo": "smfh",
- "type": "github"
- }
- },
"systems": {
"locked": {
"lastModified": 1681028828,
@@ -489,21 +460,6 @@
"type": "github"
}
},
- "systems_2": {
- "locked": {
- "lastModified": 1681028828,
- "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
- "owner": "nix-systems",
- "repo": "default",
- "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
- "type": "github"
- },
- "original": {
- "owner": "nix-systems",
- "repo": "default",
- "type": "github"
- }
- },
"tmux-tsunami": {
"inputs": {
"nix-std": "nix-std",
diff --git a/flake.nix b/flake.nix
index 0cd3cd9..4acb5a1 100644
--- a/flake.nix
+++ b/flake.nix
@@ -2,6 +2,11 @@
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
+ flake-registry = {
+ url = "github:NixOS/flake-registry";
+ flake = false;
+ };
+
nix-index-database = {
url = "github:nix-community/nix-index-database";
inputs.nixpkgs.follows = "nixpkgs";
diff --git a/hosts.toml b/hosts.toml
index 812d620..cb68531 100644
--- a/hosts.toml
+++ b/hosts.toml
@@ -7,6 +7,9 @@ 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"
+[hosts.terra]
+user_pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINKwMBLWWxpf4jZN3R3iQ7XEAOMq0KAOE/6YN4iTBuJx collin@terra"
+
[hosts.jupiter]
host_pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPB7feUHl5qoD5zF9AMOV2meViA+wZYdVvbVjPkggZf8"
diff --git a/hosts/ganymede/caddy.nix b/hosts/ganymede/caddy.nix
index 8f4fe94..d10910c 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-RM7yd4LV2r6Z4CjwZdzPND28bYbPwfzNMIxZwiYHOsM=";
+ hash = "sha256-n0HVlpCkJITyEPc+ml7zCcYw9gy3r1YAXertd6zc2o0=";
});
globalConfig = ''
diff --git a/hosts/ganymede/config.nix b/hosts/ganymede/config.nix
index f4c4430..03235f0 100644
--- a/hosts/ganymede/config.nix
+++ b/hosts/ganymede/config.nix
@@ -10,10 +10,10 @@
"caddy-env".file = ./secrets/caddy-env.age;
- "collin-copyparty-password" = {
- file = ./secrets/collin-copyparty-password.age;
- owner = "copyparty";
- };
+ # "collin-copyparty-password" = {
+ # file = ./secrets/collin-copyparty-password.age;
+ # owner = "copyparty";
+ # };
"collin-forgejo-password" = {
file = ./secrets/collin-forgejo-password.age;
owner = "forgejo";
@@ -78,18 +78,18 @@
enable = true;
privateUrl = "bittorrent.ganymede";
};
- copyparty = {
- enable = true;
- listenAddr = "0.0.0.0";
- publicUrl = "up.williamsfam.us.com";
- privateUrl = "files.ganymede";
+ # 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;
- };
- };
+ # users.collin = {
+ # isAdmin = true;
+ # passwordFile = config.collinux.secrets."collin-copyparty-password".path;
+ # hasPublicDir = true;
+ # };
+ # };
jta = {
enable = true;
diff --git a/hosts/ganymede/nixos.nix b/hosts/ganymede/nixos.nix
index a706b5f..415509a 100644
--- a/hosts/ganymede/nixos.nix
+++ b/hosts/ganymede/nixos.nix
@@ -32,7 +32,7 @@
services.fail2ban.enable = true;
# merge logs from subdomains
- services.caddy.virtualHosts."up.williamsfam.us.com".logFormat = lib.mkForce ''
+ services.caddy.virtualHosts."jta.williamsfam.us.com".logFormat = lib.mkForce ''
output file /var/log/caddy/access-williamsfam.us.com.log
'';
diff --git a/hosts/mercury/hjem.nix b/hosts/mercury/hjem.nix
index 91e9c4b..7084251 100644
--- a/hosts/mercury/hjem.nix
+++ b/hosts/mercury/hjem.nix
@@ -1,6 +1,6 @@
{pkgs, ...}: {
packages = with pkgs; [
- obsidian
+ # obsidian
anki
libreoffice-qt
musescore
@@ -22,13 +22,6 @@
terminal = false;
categories = ["Application"];
})
-
- (pkgs.writeShellScriptBin "battery.sh" ''
- 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"
- '')
];
files.".config/captive-browser.toml".text = ''
diff --git a/hosts/mercury/nixos.nix b/hosts/mercury/nixos.nix
index 6459fdb..7db48b4 100644
--- a/hosts/mercury/nixos.nix
+++ b/hosts/mercury/nixos.nix
@@ -21,6 +21,7 @@
user = "collin";
dataDir = "/home/collin/.local/syncthing";
};
+ programs.kdeconnect.enable = true;
fileSystems."/home/collin/ganymede" = {
device = "collin@ganymede:/media";
diff --git a/modules/desktop/hjem/programs/foot.nix b/modules/desktop/hjem/programs/foot.nix
index 7fe88ab..d10db24 100644
--- a/modules/desktop/hjem/programs/foot.nix
+++ b/modules/desktop/hjem/programs/foot.nix
@@ -15,7 +15,7 @@
# key-bindings.spawn-terminal = "Control+Return";
- colors = with config.collinux.palette; {
+ colors-dark = with config.collinux.palette; {
alpha = "0.6";
foreground = base05;
diff --git a/modules/desktop/hjem/wm/sway.nix b/modules/desktop/hjem/wm/sway.nix
index a0bfa4e..441670b 100644
--- a/modules/desktop/hjem/wm/sway.nix
+++ b/modules/desktop/hjem/wm/sway.nix
@@ -6,6 +6,54 @@
}: let
cfg = config.collinux.desktop.wm.sway;
+ batteryNotify = pkgs.writeShellApplication {
+ name = "battery-notify";
+ runtimeInputs = [
+ pkgs.coreutils
+ pkgs.libnotify
+ ];
+ text = ''
+ 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))
+ percent_display=$(printf "%.0f%%" "$percentage")
+
+ notify-send \
+ -t 2000 \
+ -h int:value:"$percentage" \
+ -h string:x-dunst-stack-tag:battery \
+ "Battery" \
+ "$percent_display"
+ '';
+ };
+
+ powerMenu = pkgs.writeShellApplication {
+ name = "power-menu";
+ runtimeInputs = [
+ pkgs.fuzzel
+ pkgs.systemd
+ pkgs.sway
+ ];
+ text = ''
+ selection=$(printf '%s\n' logout suspend reboot shutdown | fuzzel --dmenu --prompt "power: ")
+
+ case "$selection" in
+ logout)
+ swaymsg exit
+ ;;
+ suspend)
+ systemctl suspend
+ ;;
+ reboot)
+ systemctl reboot
+ ;;
+ shutdown)
+ systemctl poweroff
+ ;;
+ esac
+ '';
+ };
+
settings = with config.collinux.palette; ''
exec {
${config.collinux.desktop.wallpaper_cmd}
@@ -59,7 +107,9 @@
Mod4+Return exec foot
Mod4+Space exec fuzzel
+ Mod4+Escape exec '${powerMenu}/bin/power-menu'
Mod4+b exec firefox
+ 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/nix/nixos/default.nix b/modules/nix/nixos/default.nix
index 7441236..f39f368 100644
--- a/modules/nix/nixos/default.nix
+++ b/modules/nix/nixos/default.nix
@@ -19,12 +19,11 @@
daemonIOSchedClass = "idle";
settings = {
+ flake-registry = "${inputs.flake-registry}/flake-registry.json";
+
extra-experimental-features = ["nix-command" "flakes" "pipe-operators"];
auto-optimise-store = true;
use-xdg-base-directories = true;
-
- # lazy-trees = true;
- # eval-cores = 0;
};
# Disable channels
diff --git a/modules/services/nixos/jta/assignments.html b/modules/services/nixos/jta/assignments.html
new file mode 100644
index 0000000..3031a29
--- /dev/null
+++ b/modules/services/nixos/jta/assignments.html
@@ -0,0 +1,148 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <title>jta@ganymede</title>
+ <link rel="stylesheet" href="style.css">
+ <style>
+ body {
+ margin: 0;
+ max-width: none;
+ padding: 0;
+ background: var(--bg);
+ }
+
+ header {
+ padding: 1.25rem 1.5rem;
+ text-align: center;
+ font-family: var(--font-heading);
+ font-size: var(--step-2);
+ line-height: 1.2;
+ letter-spacing: -0.01em;
+ font-weight: 700;
+ background: #020617;
+ border-bottom: 1px solid var(--border);
+ }
+
+ .section {
+ padding: 1rem;
+ max-width: 1100px;
+ margin: 0 auto;
+ }
+
+ .section-title {
+ font-size: var(--step-1);
+ line-height: 1.25;
+ letter-spacing: -0.01em;
+ margin: 1rem 0 0.5rem 0.5rem;
+ color: var(--text-soft);
+ }
+
+ .container {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
+ gap: 1rem;
+ padding: 0 1rem 1rem 1rem;
+ }
+
+ .card {
+ background: var(--card);
+ border: 1px solid var(--border);
+ border-radius: 16px;
+ padding: 1rem;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
+ transition: transform 0.15s ease, box-shadow 0.15s ease;
+ cursor: pointer;
+ text-decoration: none;
+ color: var(--text);
+ display: block;
+ }
+
+ .card:hover {
+ transform: translateY(-4px);
+ box-shadow: 0 10px 20px rgba(0, 0, 0, 0.6);
+ }
+
+ .title {
+ font-size: var(--step-1);
+ line-height: 1.3;
+ letter-spacing: -0.01em;
+ font-weight: 600;
+ margin-bottom: 0.5rem;
+ }
+
+ .due {
+ font-size: var(--step--1);
+ color: var(--muted);
+ margin-bottom: 0.5rem;
+ }
+
+ .desc {
+ font-size: var(--step--1);
+ color: var(--text-soft);
+ line-height: 1.55;
+ }
+ </style>
+ </head>
+ <body>
+ <header>Juksere Trives Aldri</header>
+
+ <div id="app"></div>
+
+ <script>
+ const app = document.getElementById("app");
+
+ function renderHome(data) {
+ const groups = {};
+ data.forEach((item) => {
+ const cls = item.class || "Other";
+ if (!groups[cls]) groups[cls] = [];
+ groups[cls].push(item);
+ });
+
+ Object.entries(groups).forEach(([cls, items]) => {
+ const section = document.createElement("div");
+ section.className = "section";
+
+ const title = document.createElement("div");
+ title.className = "section-title";
+ title.textContent = cls;
+ section.appendChild(title);
+
+ const container = document.createElement("div");
+ container.className = "container";
+
+ items.forEach((item) => {
+ const card = document.createElement("a");
+ card.className = "card";
+ card.href = item.link || "#";
+
+ card.innerHTML = `
+ <div class="title">${item.title}</div>
+ <div class="due">Due: ${item.due}</div>
+ ${item.description ? `<div class="desc">${item.description}</div>` : ""}
+ `;
+
+ container.appendChild(card);
+ });
+
+ section.appendChild(container);
+ app.appendChild(section);
+ });
+ }
+
+ fetch("assignments.json")
+ .then((res) => {
+ if (!res.ok) {
+ throw new Error("Failed to load data");
+ }
+ return res.json();
+ })
+ .then((data) => renderHome(data))
+ .catch(() => {
+ app.innerHTML = "<p style='padding:1rem;'>Failed to load data.</p>";
+ });
+ </script>
+ </body>
+</html>
diff --git a/modules/services/nixos/jta/default.nix b/modules/services/nixos/jta/default.nix
index c1ea012..d2908bc 100644
--- a/modules/services/nixos/jta/default.nix
+++ b/modules/services/nixos/jta/default.nix
@@ -33,6 +33,7 @@ in {
environment = {
PORT = toString cfg.port;
ROOT_DIR = "/media/jta";
+ AUTH_PASSWORD_HASH = "550b6785c4bce2ab41a5e2eaa1eb43172d5abbd58237f1c78cd32cb190edc2c5"; # alfreddabuttlerwithtwots
};
serviceConfig = {
diff --git a/modules/services/nixos/jta/index.html b/modules/services/nixos/jta/index.html
index fd93de6..96b6f5f 100644
--- a/modules/services/nixos/jta/index.html
+++ b/modules/services/nixos/jta/index.html
@@ -3,127 +3,132 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>jta@ganymede</title>
+ <title>JTA Login</title>
<link rel="stylesheet" href="style.css">
<style>
body {
margin: 0;
- max-width: none;
- padding: 0;
+ min-height: 100vh;
+ display: grid;
+ place-items: center;
+ padding: 1rem;
background: var(--bg);
}
-
- header {
- padding: 1.25rem 1.5rem;
+
+ .card {
+ width: min(520px, 100%);
+ background: var(--card);
+ border: 1px solid var(--border);
+ border-radius: 16px;
+ padding: 1.5rem;
+ box-shadow: 0 10px 28px rgba(0, 0, 0, 0.38);
+ }
+
+ h1 {
+ margin: 0 0 0.25rem 0;
text-align: center;
font-family: var(--font-heading);
font-size: var(--step-2);
- line-height: 1.2;
- letter-spacing: -0.01em;
+ line-height: 1.15;
font-weight: 700;
- background: #020617;
- border-bottom: 1px solid var(--border);
- }
-
- .section {
- padding: 1rem;
- max-width: 1100px;
- margin: 0 auto;
}
-
- .section-title {
- font-size: var(--step-1);
- line-height: 1.25;
- letter-spacing: -0.01em;
- margin: 1rem 0 0.5rem 0.5rem;
+
+ p {
+ margin: 0.5rem 0 1.2rem 0;
+ text-align: center;
color: var(--text-soft);
+ line-height: 1.5;
}
-
- .container {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
- gap: 1rem;
- padding: 0 1rem 1rem 1rem;
+
+ .actions {
+ display: flex;
+ justify-content: center;
+ gap: 0.75rem;
}
-
- .card {
- background: var(--card);
+
+ button {
+ appearance: none;
border: 1px solid var(--border);
- border-radius: 16px;
- padding: 1rem;
- box-shadow: 0 4px 12px rgba(0,0,0,0.4);
- transition: transform 0.15s ease, box-shadow 0.15s ease;
- cursor: pointer;
- text-decoration: none;
+ border-radius: 12px;
+ background: #0f172a;
color: var(--text);
- display: block;
+ padding: 0.65rem 1rem;
+ font-size: var(--step-0);
+ font-weight: 600;
+ cursor: pointer;
+ transition: transform 0.15s ease, box-shadow 0.15s ease;
}
-
- .card:hover {
- transform: translateY(-4px);
- box-shadow: 0 10px 20px rgba(0,0,0,0.6);
+
+ button:hover {
+ transform: translateY(-1px);
+ box-shadow: 0 6px 16px rgba(0, 0, 0, 0.35);
+ }
+
+ #status {
+ margin-top: 1rem;
+ text-align: center;
+ min-height: 1.25rem;
+ font-size: var(--step--1);
+ color: var(--text-soft);
}
-
- .title { font-size: var(--step-1); line-height: 1.3; letter-spacing: -0.01em; font-weight: 600; margin-bottom: 0.5rem; }
- .due { font-size: var(--step--1); color: var(--muted); margin-bottom: 0.5rem; }
- .desc { font-size: var(--step--1); color: var(--text-soft); line-height: 1.55; }
</style>
</head>
<body>
- <header>Juksere Trives Aldri</header>
+ <div class="card">
+ <h1>JTA Portal</h1>
+ <p>Public landing page. Login is required to view assignments and all other pages.</p>
+
+ <div class="actions">
+ <button id="login-btn" type="button">Login</button>
+ </div>
- <div id="app"></div>
+ <div id="status" aria-live="polite"></div>
+ </div>
<script>
- const app = document.getElementById("app");
-
- function renderHome(data) {
- // Group by class
- const groups = {};
- data.forEach(item => {
- const cls = item.class || "Other";
- if (!groups[cls]) groups[cls] = [];
- groups[cls].push(item);
- });
-
- // Render sections
- Object.entries(groups).forEach(([cls, items]) => {
- const section = document.createElement("div");
- section.className = "section";
-
- const title = document.createElement("div");
- title.className = "section-title";
- title.textContent = cls;
- section.appendChild(title);
-
- const container = document.createElement("div");
- container.className = "container";
-
- items.forEach(item => {
- const card = document.createElement("a");
- card.className = "card";
- card.href = item.link || "#";
-
- card.innerHTML = `
- <div class="title">${item.title}</div>
- <div class="due">Due: ${item.due}</div>
- ${item.description ? `<div class="desc">${item.description}</div>` : ""}
- `;
-
- container.appendChild(card);
- });
-
- section.appendChild(container);
- app.appendChild(section);
- });
- }
-
- fetch("assignments.json")
- .then(res => res.json())
- .then(data => renderHome(data))
- .catch(() => {
- app.innerHTML = "<p style='padding:1rem;'>Failed to load data.</p>";
- });
+ const authCookieName = "jta_auth";
+ const status = document.getElementById("status");
+ const loginButton = document.getElementById("login-btn");
+
+ function toHex(buffer) {
+ const bytes = new Uint8Array(buffer);
+ return Array.from(bytes)
+ .map((b) => b.toString(16).padStart(2, "0"))
+ .join("");
+ }
+
+ async function sha256Hex(input) {
+ const data = new TextEncoder().encode(input);
+ const digest = await crypto.subtle.digest("SHA-256", data);
+ return toHex(digest);
+ }
+
+ function setAuthCookie(hashValue) {
+ const maxAge = 10 * 365 * 24 * 60 * 60;
+ document.cookie = `${authCookieName}=${hashValue}; Path=/; Max-Age=${maxAge}; SameSite=Lax`;
+ }
+
+ async function loginWithPrompt() {
+ const password = prompt("Enter password");
+ if (password === null) {
+ status.textContent = "Login cancelled.";
+ return;
+ }
+
+ status.textContent = "Logging in...";
+
+ const hashValue = await sha256Hex(password);
+ setAuthCookie(hashValue);
+
+ status.textContent = "Success. Redirecting...";
+ window.location.href = "/assignments";
+ }
+
+ loginButton.addEventListener("click", () => {
+ loginWithPrompt().catch(() => {
+ status.textContent = "Network error while logging in.";
+ });
+ });
</script>
</body>
</html>
diff --git a/modules/services/nixos/jta/main.go b/modules/services/nixos/jta/main.go
index 3e5fc1f..1390d7f 100644
--- a/modules/services/nixos/jta/main.go
+++ b/modules/services/nixos/jta/main.go
@@ -29,8 +29,28 @@ var styleCSS string
//go:embed index.html
var indexHTML string
+//go:embed assignments.html
+var assignmentsHTML string
+
var pageTmpl = template.Must(template.New("md_template.html").Parse(mdTemplate))
+const authCookieName = "jta_auth"
+
+func isAuthenticated(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
+ }
+
+ providedHash := strings.TrimSpace(cookie.Value)
+ return strings.EqualFold(providedHash, expectedHash)
+}
+
func serveMarkdown(w http.ResponseWriter, r *http.Request) {
// Prevent path traversal
clean := filepath.Clean(r.URL.Path)
@@ -81,6 +101,17 @@ func main() {
return
}
+ if !isAuthenticated(r) {
+ http.Error(w, "unauthorized", http.StatusUnauthorized)
+ return
+ }
+
+ if r.URL.Path == "/assignments" || r.URL.Path == "/assignments.html" {
+ w.Header().Set("Content-Type", "text/html; charset=utf-8")
+ w.Write([]byte(assignmentsHTML))
+ return
+ }
+
// Intercept markdown files
if strings.HasSuffix(r.URL.Path, ".md") {
serveMarkdown(w, r)
diff --git a/modules/services/nixos/jta/style.css b/modules/services/nixos/jta/style.css
index 1d52d19..ec09ca9 100644
--- a/modules/services/nixos/jta/style.css
+++ b/modules/services/nixos/jta/style.css
@@ -124,6 +124,35 @@ code {
border-radius: 6px;
}
+img {
+ display: block;
+ max-width: 100%;
+ height: auto;
+ margin: 1rem 0;
+ border-radius: 10px;
+}
+
+p > img,
+li > img {
+ margin: 0.75rem 0;
+}
+
+a > img {
+ transition: transform 0.15s ease, box-shadow 0.15s ease;
+}
+
+a:hover > img {
+ transform: translateY(-1px);
+ box-shadow: 0 8px 20px rgba(2, 6, 23, 0.45);
+}
+
+img[width],
+img[height] {
+ width: auto;
+ height: auto;
+ max-width: 100%;
+}
+
/* Links */
a {
color: #93c5fd;
diff --git a/modules/user/nixos/default.nix b/modules/user/nixos/default.nix
index 71f6c14..68238d6 100644
--- a/modules/user/nixos/default.nix
+++ b/modules/user/nixos/default.nix
@@ -34,14 +34,16 @@ in {
programs.ssh = {
systemd-ssh-proxy.enable = false;
- knownHosts = builtins.mapAttrs (_: data:
- {
- publicKey = data.host_pubkey;
- }
- // (lib.optionalAttrs (data ? hostnames) {
- hostNames = data.hostnames;
- }))
- hosts;
+ knownHosts =
+ hosts
+ |> lib.filterAttrs (_: data: data ? host_pubkey)
+ |> builtins.mapAttrs (_: data:
+ {
+ publicKey = data.host_pubkey;
+ }
+ // (lib.optionalAttrs (data ? hostnames) {
+ hostNames = data.hostnames;
+ }));
};
hjem = {