aboutsummaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
Diffstat (limited to 'modules')
-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
9 files changed, 374 insertions, 109 deletions
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 = {