feat: add tor service and style
feat: add tor service
This commit is contained in:
parent
f48390b15e
commit
7b842b3342
12 changed files with 203 additions and 24 deletions
84
flake.lock
generated
84
flake.lock
generated
|
|
@ -1,8 +1,53 @@
|
|||
{
|
||||
"nodes": {
|
||||
"agenix": {
|
||||
"inputs": {
|
||||
"darwin": "darwin",
|
||||
"home-manager": "home-manager",
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
],
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1770165109,
|
||||
"narHash": "sha256-9VnK6Oqai65puVJ4WYtCTvlJeXxMzAp/69HhQuTdl/I=",
|
||||
"owner": "ryantm",
|
||||
"repo": "agenix",
|
||||
"rev": "b027ee29d959fda4b60b57566d64c98a202e0feb",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "ryantm",
|
||||
"repo": "agenix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"darwin": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"agenix",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1744478979,
|
||||
"narHash": "sha256-dyN+teG9G82G+m+PX/aSAagkC+vUv0SgUw3XkPhQodQ=",
|
||||
"owner": "lnl7",
|
||||
"repo": "nix-darwin",
|
||||
"rev": "43975d782b418ebf4969e9ccba82466728c2851b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "lnl7",
|
||||
"ref": "master",
|
||||
"repo": "nix-darwin",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
"systems": "systems_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1731533236,
|
||||
|
|
@ -18,6 +63,27 @@
|
|||
"type": "github"
|
||||
}
|
||||
},
|
||||
"home-manager": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"agenix",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1745494811,
|
||||
"narHash": "sha256-YZCh2o9Ua1n9uCvrvi5pRxtuVNml8X2a03qIFfRKpFs=",
|
||||
"owner": "nix-community",
|
||||
"repo": "home-manager",
|
||||
"rev": "abfad3d2958c9e6300a883bd443512c55dfeb1be",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "home-manager",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1772542754,
|
||||
|
|
@ -52,6 +118,7 @@
|
|||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"agenix": "agenix",
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"rust-overlay": "rust-overlay"
|
||||
|
|
@ -89,6 +156,21 @@
|
|||
"repo": "default",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
|
|
|
|||
|
|
@ -4,8 +4,10 @@
|
|||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
rust-overlay.url = "github:oxalica/rust-overlay";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
agenix.url = "github:ryantm/agenix";
|
||||
agenix.inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
outputs = { self, nixpkgs, rust-overlay, flake-utils }:
|
||||
outputs = { self, nixpkgs, rust-overlay, flake-utils, agenix }:
|
||||
(flake-utils.lib.eachDefaultSystem (system:
|
||||
let
|
||||
overlays = [ (import rust-overlay) ];
|
||||
|
|
@ -81,7 +83,9 @@
|
|||
pname = "jetpham-qa-api";
|
||||
version = "0.1.0";
|
||||
src = ./api;
|
||||
cargoHash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
|
||||
cargoHash = "sha256-/EgiCn5N3E1tCcBWI3Sm3NGQt2h8l8yPwi/ZjporiVs=";
|
||||
nativeBuildInputs = [ pkgs.pkg-config ];
|
||||
buildInputs = [ pkgs.openssl ];
|
||||
};
|
||||
|
||||
in {
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@
|
|||
</head>
|
||||
<body style="background:#000">
|
||||
<canvas id="canvas" class="fixed top-0 left-0 -z-10 h-screen w-screen" aria-hidden="true"></canvas>
|
||||
<nav class="flex justify-center px-4">
|
||||
<nav aria-label="Main navigation" class="flex justify-center px-4">
|
||||
<div class="relative px-[2ch] py-[1ch] mt-[2ch] w-full max-w-[66.666667%] min-w-fit">
|
||||
<div
|
||||
class="pointer-events-none absolute inset-0"
|
||||
|
|
@ -88,9 +88,8 @@
|
|||
aria-hidden="true"
|
||||
></div>
|
||||
<div class="absolute inset-0 border-2 border-white" aria-hidden="true"></div>
|
||||
<div class="relative z-10 flex gap-[2ch]">
|
||||
<div class="relative z-10 flex justify-center gap-[2ch]">
|
||||
<a href="/">[HOME]</a>
|
||||
<a href="/projects">[PROJECTS]</a>
|
||||
<a href="/qa">[Q&A]</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
75
module.nix
75
module.nix
|
|
@ -16,6 +16,8 @@ in
|
|||
description = "Domain to serve the website on.";
|
||||
};
|
||||
|
||||
tor.enable = lib.mkEnableOption "Tor hidden service for the website";
|
||||
|
||||
envFile = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.path;
|
||||
default = null;
|
||||
|
|
@ -36,6 +38,55 @@ in
|
|||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
age.secrets.webhook-secret = {
|
||||
file = "${self}/secrets/webhook-secret.age";
|
||||
mode = "0400";
|
||||
};
|
||||
|
||||
age.secrets.tor-onion-secret-key = lib.mkIf cfg.tor.enable {
|
||||
file = "${self}/secrets/tor-onion-secret-key.age";
|
||||
owner = "tor";
|
||||
group = "tor";
|
||||
mode = "0400";
|
||||
};
|
||||
age.secrets.tor-onion-public-key = lib.mkIf cfg.tor.enable {
|
||||
file = "${self}/secrets/tor-onion-public-key.age";
|
||||
owner = "tor";
|
||||
group = "tor";
|
||||
mode = "0444";
|
||||
};
|
||||
age.secrets.tor-onion-hostname = lib.mkIf cfg.tor.enable {
|
||||
file = "${self}/secrets/tor-onion-hostname.age";
|
||||
owner = "tor";
|
||||
group = "tor";
|
||||
mode = "0444";
|
||||
};
|
||||
|
||||
services.tor = lib.mkIf cfg.tor.enable {
|
||||
enable = true;
|
||||
relay.onionServices.jetpham-website = {
|
||||
map = [{ port = 80; target = { addr = "127.0.0.1"; port = 8888; }; }];
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.tor-onion-keys = lib.mkIf cfg.tor.enable {
|
||||
description = "Copy Tor onion keys into place";
|
||||
after = [ "agenix.service" ];
|
||||
before = [ "tor.service" ];
|
||||
wantedBy = [ "tor.service" ];
|
||||
serviceConfig.Type = "oneshot";
|
||||
script = ''
|
||||
dir="/var/lib/tor/onion/jetpham-website"
|
||||
mkdir -p "$dir"
|
||||
cp ${config.age.secrets.tor-onion-secret-key.path} "$dir/hs_ed25519_secret_key"
|
||||
cp ${config.age.secrets.tor-onion-public-key.path} "$dir/hs_ed25519_public_key"
|
||||
cp ${config.age.secrets.tor-onion-hostname.path} "$dir/hostname"
|
||||
chown -R tor:tor "$dir"
|
||||
chmod 700 "$dir"
|
||||
chmod 400 "$dir/hs_ed25519_secret_key"
|
||||
chmod 444 "$dir/hs_ed25519_public_key" "$dir/hostname"
|
||||
'';
|
||||
};
|
||||
# Q&A API systemd service
|
||||
systemd.services.jetpham-qa-api = {
|
||||
description = "Jet Pham Q&A API";
|
||||
|
|
@ -47,14 +98,11 @@ in
|
|||
Environment = [ "QA_DB_PATH=/var/lib/jetpham-qa/qa.db" ];
|
||||
Restart = "on-failure";
|
||||
RestartSec = 5;
|
||||
} // lib.optionalAttrs (cfg.webhookSecretFile == null) {
|
||||
ExecStart = "${qaApi}/bin/jetpham-qa-api";
|
||||
LoadCredential = "webhook-secret:${config.age.secrets.webhook-secret.path}";
|
||||
} // lib.optionalAttrs (cfg.envFile != null) {
|
||||
EnvironmentFile = cfg.envFile;
|
||||
} // lib.optionalAttrs (cfg.webhookSecretFile != null) {
|
||||
LoadCredential = "webhook-secret:${cfg.webhookSecretFile}";
|
||||
};
|
||||
script = lib.mkIf (cfg.webhookSecretFile != null) ''
|
||||
script = ''
|
||||
export WEBHOOK_SECRET="$(cat $CREDENTIALS_DIRECTORY/webhook-secret)"
|
||||
exec ${qaApi}/bin/jetpham-qa-api
|
||||
'';
|
||||
|
|
@ -76,5 +124,22 @@ in
|
|||
}
|
||||
'';
|
||||
};
|
||||
|
||||
services.caddy.virtualHosts."http://127.0.0.1:8888" = lib.mkIf cfg.tor.enable {
|
||||
extraConfig = ''
|
||||
header Cross-Origin-Opener-Policy "same-origin"
|
||||
header Cross-Origin-Embedder-Policy "require-corp"
|
||||
|
||||
handle /api/* {
|
||||
reverse_proxy 127.0.0.1:3001
|
||||
}
|
||||
|
||||
handle {
|
||||
root * ${package}
|
||||
try_files {path} /index.html
|
||||
file_server
|
||||
}
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
|
|
|
|||
10
secrets.nix
Normal file
10
secrets.nix
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
let
|
||||
laptop = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIE40ISu3ydCqfdpb26JYD5cIN0Fu0id/FDS+xjB5zpqu";
|
||||
server = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAING219cDKTDLaZefmqvOHfXvYloA/ErsCGE0pM022vlB";
|
||||
in
|
||||
{
|
||||
"secrets/tor-onion-secret-key.age".publicKeys = [ laptop server ];
|
||||
"secrets/tor-onion-public-key.age".publicKeys = [ laptop server ];
|
||||
"secrets/tor-onion-hostname.age".publicKeys = [ laptop server ];
|
||||
"secrets/webhook-secret.age".publicKeys = [ laptop server ];
|
||||
}
|
||||
BIN
secrets/tor-onion-hostname.age
Normal file
BIN
secrets/tor-onion-hostname.age
Normal file
Binary file not shown.
7
secrets/tor-onion-public-key.age
Normal file
7
secrets/tor-onion-public-key.age
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
age-encryption.org/v1
|
||||
-> ssh-ed25519 Ziw7aw 2u0CVE/rQWpJNSRW/1xeWBKpShWT4ckke+Ih4j3WbRk
|
||||
xeE2xTlSPEDPeC4BNkaSoOckwuOhyCQWqtXkwuhBiRo
|
||||
-> ssh-ed25519 uKftJg Tt1mbTWHyXRDjvGWFBqmyrMl/PtUs45N1032luY88x8
|
||||
A51wD3tiZ0lV1TSub+Pz7hZ+kndiEpnmBliP59qYzkY
|
||||
--- 7X+mgLxb3uYfiYebJnAUwl/4jhGJJSweaolMttmoEIQ
|
||||
3à‡’Í.Úu˼’ÞZ"Àä9Ö<39>'AëâªK~.@ŽÌ‰7¾lW„÷ pý{CôÁAŠ&@¸|ÕËšV%55˜Ot¼Šr9Çîºúñ<C3BA>h»L²‡@®G’õLä˜gû€
|
||||
8
secrets/tor-onion-secret-key.age
Normal file
8
secrets/tor-onion-secret-key.age
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
age-encryption.org/v1
|
||||
-> ssh-ed25519 Ziw7aw AZTbqWYmTaudHZ8PiTZlwpf7VzwaP921guVV1iQi8WM
|
||||
7CGUyEjZoAPCBX2pqNHLd2P1KLnj/Y5nnBVToWYCjWg
|
||||
-> ssh-ed25519 uKftJg RfAkte/jcx+/SvZiUNH07cnBJcvl0Sjt7zSxdSCsXE0
|
||||
TdMsQ2u5WXw3KAi7Tk4JOdbiFStT8F88xjDlRN8LH2Q
|
||||
--- pgQnGRRVjVK02tbMgDoh3SatJFxxFLazqy5ieHu96tk
|
||||
žÞxó}ûë¥T¡½<11>SÒP^©kH
׋/æôœõþ›Ý³U~ókÅ;Ö•äÕ†=•ÁºNå«\PZï|óO¨/YíV6Ìx‹=„¹¹Â<gR+2á³C5n
|
||||
©ãÃàÚ/J…‹k¤;5ÚNÈ—Ü#q’&Øxš‹|6²ƒŸ>H
|
||||
7
secrets/webhook-secret.age
Normal file
7
secrets/webhook-secret.age
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
age-encryption.org/v1
|
||||
-> ssh-ed25519 Ziw7aw zDI2q+wM70YbQFw7hEQbU58XrgkWnM8fura+ovG0bTw
|
||||
0Aznts9gOT8ADxFY0SIv/VT0ExEEq9wZLTnlC0j15JU
|
||||
-> ssh-ed25519 uKftJg dFQxnrMwQHJ2lVBMi6P4XugljREjAHPV9bEFmLhuiBc
|
||||
VWKLaM3peiELoJigapdZv0N4Z4lRZZ8eGMEFy+WtdxA
|
||||
--- zJghsuTUoVH8bKynqq29kzEuNDuEjvFf5v/s98jQdaA
|
||||
𡂝濮+<2B>i籤V2*Hf苳儑齪<E58491><E9BDAA> c栛
|
||||
|
|
@ -2,14 +2,10 @@ import "~/styles/globals.css";
|
|||
import init, { start } from "cgol";
|
||||
import { route, initRouter } from "~/router";
|
||||
import { homePage } from "~/pages/home";
|
||||
import { projectsPage } from "~/pages/projects";
|
||||
import { projectPage } from "~/pages/project";
|
||||
import { qaPage } from "~/pages/qa";
|
||||
import { notFoundPage } from "~/pages/not-found";
|
||||
|
||||
route("/", homePage);
|
||||
route("/projects", projectsPage);
|
||||
route("/projects/:slug", projectPage);
|
||||
route("/qa", qaPage);
|
||||
route("*", notFoundPage);
|
||||
|
||||
|
|
|
|||
|
|
@ -11,14 +11,13 @@ export async function qaPage(outlet: HTMLElement) {
|
|||
outlet.innerHTML = `
|
||||
<div class="flex flex-col items-center justify-start px-4">
|
||||
${frostedBox(`
|
||||
<h1 style="color: var(--yellow);">Q&A</h1>
|
||||
<form id="qa-form" class="mt-[2ch]">
|
||||
<fieldset class="border-2 border-white px-[calc(1.5ch-0.5px)] pb-[1ch] pt-0">
|
||||
<form id="qa-form">
|
||||
<fieldset class="border-2 border-white px-[calc(1.5ch-0.5px)] pb-[1ch] pt-[1ch]">
|
||||
<legend class="-mx-[0.5ch] px-[0.5ch] text-white">Ask a Question</legend>
|
||||
<textarea id="qa-input" maxlength="200" rows="3"
|
||||
class="qa-textarea"
|
||||
placeholder="Type your question..."></textarea>
|
||||
<div class="flex justify-between mt-[0.5ch]">
|
||||
<div class="flex justify-between mt-[1ch]">
|
||||
<span id="char-count" style="color: var(--dark-gray);">0/200</span>
|
||||
<button type="submit" class="qa-button">[SUBMIT]</button>
|
||||
</div>
|
||||
|
|
@ -83,5 +82,6 @@ export async function qaPage(outlet: HTMLElement) {
|
|||
} catch {
|
||||
list.textContent = "Failed to load questions.";
|
||||
list.style.color = "var(--light-red)";
|
||||
list.style.textAlign = "center";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,14 +80,15 @@
|
|||
}
|
||||
|
||||
a:hover {
|
||||
color: var(--blue);
|
||||
background-color: var(--light-blue);
|
||||
color: var(--black);
|
||||
}
|
||||
|
||||
/* Form inputs */
|
||||
.qa-textarea {
|
||||
width: 100%;
|
||||
background-color: var(--black);
|
||||
border: 1px solid var(--white);
|
||||
border: 2px solid var(--white);
|
||||
color: var(--light-gray);
|
||||
padding: 1ch;
|
||||
resize: none;
|
||||
|
|
@ -97,8 +98,8 @@
|
|||
}
|
||||
|
||||
.qa-button {
|
||||
border: 1px solid var(--white);
|
||||
padding: 0 1ch;
|
||||
border: none;
|
||||
padding: 0.25ch 1ch;
|
||||
color: var(--yellow);
|
||||
background: transparent;
|
||||
font-family: inherit;
|
||||
|
|
@ -139,7 +140,7 @@
|
|||
}
|
||||
|
||||
.project-content pre {
|
||||
border: 1px solid var(--dark-gray);
|
||||
border: 2px solid var(--dark-gray);
|
||||
padding: 1ch;
|
||||
overflow-x: auto;
|
||||
margin-bottom: 1ch;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue