aboutsummaryrefslogtreecommitdiff
path: root/modules/services/nixos/jta
diff options
context:
space:
mode:
Diffstat (limited to 'modules/services/nixos/jta')
-rw-r--r--modules/services/nixos/jta/assignments.html148
-rw-r--r--modules/services/nixos/jta/default.nix48
-rw-r--r--modules/services/nixos/jta/go.mod5
-rw-r--r--modules/services/nixos/jta/go.sum2
-rw-r--r--modules/services/nixos/jta/index.html134
-rw-r--r--modules/services/nixos/jta/main.go142
-rw-r--r--modules/services/nixos/jta/md_template.html11
-rw-r--r--modules/services/nixos/jta/pkg.nix6
-rw-r--r--modules/services/nixos/jta/style.css172
9 files changed, 0 insertions, 668 deletions
diff --git a/modules/services/nixos/jta/assignments.html b/modules/services/nixos/jta/assignments.html
deleted file mode 100644
index 3031a29..0000000
--- a/modules/services/nixos/jta/assignments.html
+++ /dev/null
@@ -1,148 +0,0 @@
-<!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
deleted file mode 100644
index 31f5bbd..0000000
--- a/modules/services/nixos/jta/default.nix
+++ /dev/null
@@ -1,48 +0,0 @@
-{
- pkgs,
- config,
- lib,
- ...
-}: let
- cfg = config.collinux.services.jta;
- package = pkgs.callPackage ./pkg.nix {};
-in {
- config = lib.mkIf cfg.enable {
- users.groups."jta" = {};
- users.users."jta" = {
- isSystemUser = true;
- group = "jta";
- extraGroups = ["fileserver"];
-
- home = "/var/lib/jta";
- createHome = true;
- homeMode = "755";
- };
-
- systemd.services."jta" = {
- restartIfChanged = true;
- wants = ["network-online.target" "caddy.service"];
- after = ["network-online.target" "caddy.service"];
-
- environment = {
- PORT = toString cfg.port;
- ROOT_DIR = "/media/jta";
- AUTH_PASSWORD_HASH = "550b6785c4bce2ab41a5e2eaa1eb43172d5abbd58237f1c78cd32cb190edc2c5"; # alfreddabuttlerwithtwots
- };
-
- serviceConfig = {
- User = "jta";
- Type = "simple";
-
- WorkingDirectory = "/var/lib/jta";
- ExecStart = "${package}/bin/jta";
- };
-
- wantedBy = ["multi-user.target"];
- };
-
- services.caddy.virtualHosts."jta.williamsfam.us.com".extraConfig = ''
- reverse_proxy 127.0.0.1:${toString cfg.port}
- '';
- };
-}
diff --git a/modules/services/nixos/jta/go.mod b/modules/services/nixos/jta/go.mod
deleted file mode 100644
index 8f27af1..0000000
--- a/modules/services/nixos/jta/go.mod
+++ /dev/null
@@ -1,5 +0,0 @@
-module git.ganymede/jta
-
-go 1.25.5
-
-require github.com/yuin/goldmark v1.8.2
diff --git a/modules/services/nixos/jta/go.sum b/modules/services/nixos/jta/go.sum
deleted file mode 100644
index 6a37955..0000000
--- a/modules/services/nixos/jta/go.sum
+++ /dev/null
@@ -1,2 +0,0 @@
-github.com/yuin/goldmark v1.8.2 h1:kEGpgqJXdgbkhcOgBxkC0X0PmoPG1ZyoZ117rDVp4zE=
-github.com/yuin/goldmark v1.8.2/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
diff --git a/modules/services/nixos/jta/index.html b/modules/services/nixos/jta/index.html
deleted file mode 100644
index 96b6f5f..0000000
--- a/modules/services/nixos/jta/index.html
+++ /dev/null
@@ -1,134 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>JTA Login</title>
- <link rel="stylesheet" href="style.css">
- <style>
- body {
- margin: 0;
- min-height: 100vh;
- display: grid;
- place-items: center;
- padding: 1rem;
- background: var(--bg);
- }
-
- .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.15;
- font-weight: 700;
- }
-
- p {
- margin: 0.5rem 0 1.2rem 0;
- text-align: center;
- color: var(--text-soft);
- line-height: 1.5;
- }
-
- .actions {
- display: flex;
- justify-content: center;
- gap: 0.75rem;
- }
-
- button {
- appearance: none;
- border: 1px solid var(--border);
- border-radius: 12px;
- background: #0f172a;
- color: var(--text);
- padding: 0.65rem 1rem;
- font-size: var(--step-0);
- font-weight: 600;
- cursor: pointer;
- transition: transform 0.15s ease, box-shadow 0.15s ease;
- }
-
- 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);
- }
- </style>
- </head>
- <body>
- <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="status" aria-live="polite"></div>
- </div>
-
- <script>
- 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
deleted file mode 100644
index e7b5a0f..0000000
--- a/modules/services/nixos/jta/main.go
+++ /dev/null
@@ -1,142 +0,0 @@
-package main
-
-import (
- "bytes"
- _ "embed"
- "html/template"
- "net/http"
- "os"
- "path/filepath"
- "strings"
-
- "github.com/yuin/goldmark"
- "github.com/yuin/goldmark/extension"
-)
-
-// Configure Goldmark with GitHub Flavored Markdown
-var mdParser = goldmark.New(
- goldmark.WithExtensions(
- extension.GFM,
- ),
-)
-
-//go:embed md_template.html
-var mdTemplate string
-
-//go:embed style.css
-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(w http.ResponseWriter, r *http.Request) bool {
- expectedHash := strings.TrimSpace(os.Getenv("AUTH_PASSWORD_HASH"))
- if expectedHash == "" {
- 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
- }
-
- return false
-}
-
-func serveMarkdown(w http.ResponseWriter, r *http.Request) {
- // Prevent path traversal
- clean := filepath.Clean(r.URL.Path)
- path := filepath.Join(rootDir, strings.TrimPrefix(clean, "/"))
-
- mdBytes, err := os.ReadFile(path)
- if err != nil {
- http.NotFound(w, r)
- return
- }
-
- var buf bytes.Buffer
- if err := mdParser.Convert(mdBytes, &buf); err != nil {
- http.Error(w, "failed to render markdown", 500)
- return
- }
-
- w.Header().Set("Content-Type", "text/html; charset=utf-8")
-
- pageTmpl.Execute(w, map[string]template.HTML{
- "Content": template.HTML(buf.String()),
- })
-}
-
-var rootDir = "."
-
-func main() {
- if envRootDir := os.Getenv("ROOT_DIR"); envRootDir != "" {
- rootDir = envRootDir
- }
-
- fs := http.FileServer(http.Dir(rootDir))
- port := os.Getenv("PORT")
- if port == "" {
- port = "8080"
- }
-
- http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
- if r.URL.Path == "/" || r.URL.Path == "/index.html" {
- w.Header().Set("Content-Type", "text/html; charset=utf-8")
- w.Write([]byte(indexHTML))
- return
- }
-
- if r.URL.Path == "/style.css" {
- w.Header().Set("Content-Type", "text/css; charset=utf-8")
- w.Write([]byte(styleCSS))
- return
- }
-
- if !isAuthenticated(w, 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)
- return
- }
-
- // Otherwise serve normally
- fs.ServeHTTP(w, r)
- })
-
- http.ListenAndServe(":"+port, nil)
-}
diff --git a/modules/services/nixos/jta/md_template.html b/modules/services/nixos/jta/md_template.html
deleted file mode 100644
index daa57b7..0000000
--- a/modules/services/nixos/jta/md_template.html
+++ /dev/null
@@ -1,11 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
- <head>
- <meta charset="UTF-8">
- <title>JTA</title>
- <link rel="stylesheet" href="/style.css">
- </head>
- <body>
- {{.Content}}
- </body>
-</html>
diff --git a/modules/services/nixos/jta/pkg.nix b/modules/services/nixos/jta/pkg.nix
deleted file mode 100644
index c8f7d77..0000000
--- a/modules/services/nixos/jta/pkg.nix
+++ /dev/null
@@ -1,6 +0,0 @@
-{pkgs ? import <nixpkgs> {system = "x86_64-linux";}, ...}:
-pkgs.buildGoModule {
- name = "jta";
- src = ./.;
- vendorHash = "sha256-aeJYO52ALbGxyTFPw6eGCmPlmuCvbXEoX9gcXatN29E=";
-}
diff --git a/modules/services/nixos/jta/style.css b/modules/services/nixos/jta/style.css
deleted file mode 100644
index ec09ca9..0000000
--- a/modules/services/nixos/jta/style.css
+++ /dev/null
@@ -1,172 +0,0 @@
-:root {
- --bg: #0f172a;
- --card: #1e293b;
- --accent: #22c55e;
- --text: #e2e8f0;
- --text-soft: #cbd5e1;
- --muted: #94a3b8;
- --border: #334155;
-
- --font-body: "Avenir Next", "Segoe UI", "Noto Sans", "Helvetica Neue", sans-serif;
- --font-heading: "Iowan Old Style", "Palatino Linotype", "Book Antiqua", Palatino, serif;
- --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
-
- --step--1: clamp(0.88rem, 0.86rem + 0.12vw, 0.96rem);
- --step-0: clamp(1rem, 0.98rem + 0.16vw, 1.08rem);
- --step-1: clamp(1.14rem, 1.08rem + 0.26vw, 1.28rem);
- --step-2: clamp(1.3rem, 1.2rem + 0.45vw, 1.56rem);
- --step-3: clamp(1.7rem, 1.45rem + 1.1vw, 2.25rem);
-}
-
-/* Base layout */
-body {
- font-family: var(--font-body);
- font-size: var(--step-0);
- background: var(--bg);
- color: var(--text);
- max-width: 850px;
- margin: 2rem auto;
- padding: 1.5rem;
- line-height: 1.7;
- text-rendering: optimizeLegibility;
-}
-
-/* Headings */
-h1 {
- font-family: var(--font-heading);
- font-size: var(--step-3);
- line-height: 1.15;
- font-weight: 700;
- letter-spacing: -0.02em;
- text-align: center;
- margin: 0 0 1.25rem;
-}
-
-h2 {
- font-family: var(--font-heading);
- margin-top: 2.25rem;
- margin-bottom: 0.75rem;
- font-size: var(--step-2);
- line-height: 1.25;
- letter-spacing: -0.01em;
-}
-
-h3 {
- font-family: var(--font-heading);
- margin-top: 1.5rem;
- margin-bottom: 0.6rem;
- font-size: var(--step-1);
- line-height: 1.3;
- color: var(--text-soft);
-}
-
-/* Text */
-p {
- color: var(--text);
- margin-bottom: 1rem;
- max-width: 65ch;
-}
-
-/* Ordered list (questions) */
-ol {
- padding-left: 1.2rem;
- max-width: 65ch;
-}
-
-/* Answer list */
-ul {
- list-style: none;
- padding-left: 1.2rem;
- max-width: 65ch;
-}
-
-ul li {
- display: flex;
- align-items: flex-start;
- gap: 0.6rem;
- padding: 0.35rem 0.4rem;
- border-radius: 8px;
- transition: background 0.15s ease;
-}
-
-/* Checkbox styling */
-input[type="checkbox"] {
- accent-color: var(--accent);
- transform: scale(1.2);
- margin-top: 0.2rem;
- pointer-events: none;
-}
-
-/* Correct answers */
-li:has(input[type="checkbox"]:checked) {
- color: #86efac;
- font-weight: 500;
-}
-
-/* Code blocks */
-pre {
- font-family: var(--font-mono);
- font-size: var(--step--1);
- line-height: 1.6;
- background: #020617;
- padding: 1rem;
- border-radius: 10px;
- overflow-x: auto;
- margin: 1rem 0;
- max-width: 65ch;
-}
-
-code {
- font-family: var(--font-mono);
- font-size: 0.95em;
- background: #020617;
- padding: 2px 6px;
- 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;
- text-decoration-line: underline;
- text-decoration-thickness: 2px;
- text-underline-offset: 0.14em;
- text-decoration-color: rgba(147, 197, 253, 0.45);
-}
-
-a:hover {
- text-decoration-color: currentColor;
-}
-
-/* Optional: make everything feel tighter and nicer */
-* {
- box-sizing: border-box;
-}