diff options
| -rw-r--r-- | flake.lock | 168 | ||||
| -rw-r--r-- | flake.nix | 5 | ||||
| -rw-r--r-- | hosts.toml | 3 | ||||
| -rw-r--r-- | hosts/ganymede/caddy.nix | 2 | ||||
| -rw-r--r-- | hosts/ganymede/config.nix | 30 | ||||
| -rw-r--r-- | hosts/ganymede/nixos.nix | 2 | ||||
| -rw-r--r-- | hosts/mercury/hjem.nix | 9 | ||||
| -rw-r--r-- | hosts/mercury/nixos.nix | 1 | ||||
| -rw-r--r-- | modules/desktop/hjem/programs/foot.nix | 2 | ||||
| -rw-r--r-- | modules/desktop/hjem/wm/sway.nix | 50 | ||||
| -rw-r--r-- | modules/nix/nixos/default.nix | 5 | ||||
| -rw-r--r-- | modules/services/nixos/jta/assignments.html | 148 | ||||
| -rw-r--r-- | modules/services/nixos/jta/default.nix | 1 | ||||
| -rw-r--r-- | modules/services/nixos/jta/index.html | 199 | ||||
| -rw-r--r-- | modules/services/nixos/jta/main.go | 31 | ||||
| -rw-r--r-- | modules/services/nixos/jta/style.css | 29 | ||||
| -rw-r--r-- | modules/user/nixos/default.nix | 18 |
17 files changed, 463 insertions, 240 deletions
@@ -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", @@ -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"; @@ -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 = { |
