fix: cleanup flake and repo
This commit is contained in:
parent
17d708eb7a
commit
9a5256e8f0
7 changed files with 197 additions and 530 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -10,6 +10,7 @@
|
||||||
# misc
|
# misc
|
||||||
.DS_Store
|
.DS_Store
|
||||||
*.pem
|
*.pem
|
||||||
|
*.har
|
||||||
|
|
||||||
# debug
|
# debug
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
|
|
|
||||||
32
README.md
32
README.md
|
|
@ -4,6 +4,8 @@ Personal site for Jet Pham.
|
||||||
|
|
||||||
The site is a small Vite app with a terminal-style UI, ANSI-rendered text, and a WebGL2 Conway's Game of Life background.
|
The site is a small Vite app with a terminal-style UI, ANSI-rendered text, and a WebGL2 Conway's Game of Life background.
|
||||||
|
|
||||||
|
It also ships as a Nix flake with a reusable NixOS module for serving the static frontend and the Q+A API on a host.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- ASCII/ANSI-inspired visual style with the IBM VGA font
|
- ASCII/ANSI-inspired visual style with the IBM VGA font
|
||||||
|
|
@ -53,9 +55,39 @@ npm run build
|
||||||
## Structure
|
## Structure
|
||||||
|
|
||||||
```text
|
```text
|
||||||
|
api/ Q+A backend
|
||||||
|
module.nix NixOS module
|
||||||
src/ frontend app
|
src/ frontend app
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## NixOS module
|
||||||
|
|
||||||
|
Import the module from the flake and point it at the host-managed secret files you want to use.
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{
|
||||||
|
inputs.website.url = "github:jetpham/website";
|
||||||
|
|
||||||
|
outputs = { self, nixpkgs, website, ... }: {
|
||||||
|
nixosConfigurations.my-host = nixpkgs.lib.nixosSystem {
|
||||||
|
system = "x86_64-linux";
|
||||||
|
modules = [
|
||||||
|
website.nixosModules.default
|
||||||
|
({ config, ... }: {
|
||||||
|
services.jetpham-website = {
|
||||||
|
enable = true;
|
||||||
|
domain = "jetpham.com";
|
||||||
|
webhookSecretFile = config.age.secrets.webhook-secret.path;
|
||||||
|
};
|
||||||
|
})
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Optional Tor support is configured by setting `services.jetpham-website.tor.enable = true;` and providing the three onion key file paths.
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
- The homepage and Q+A page are the intended public pages.
|
- The homepage and Q+A page are the intended public pages.
|
||||||
|
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
$ bun run lint && tsc --noEmit
|
|
||||||
$ eslint .
|
|
||||||
121
flake.lock
generated
121
flake.lock
generated
|
|
@ -1,53 +1,8 @@
|
||||||
{
|
{
|
||||||
"nodes": {
|
"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": {
|
"flake-utils": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"systems": "systems_2"
|
"systems": "systems"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1731533236,
|
"lastModified": 1731533236,
|
||||||
|
|
@ -63,27 +18,6 @@
|
||||||
"type": "github"
|
"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": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1774386573,
|
"lastModified": 1774386573,
|
||||||
|
|
@ -100,46 +34,10 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs_2": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1744536153,
|
|
||||||
"narHash": "sha256-awS2zRgF4uTwrOKwwiJcByDzDOdo3Q1rPZbiHQg/N38=",
|
|
||||||
"owner": "NixOS",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "18dd725c29603f582cf1900e0d25f9f1063dbf11",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "NixOS",
|
|
||||||
"ref": "nixpkgs-unstable",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"agenix": "agenix",
|
|
||||||
"flake-utils": "flake-utils",
|
"flake-utils": "flake-utils",
|
||||||
"nixpkgs": "nixpkgs",
|
"nixpkgs": "nixpkgs"
|
||||||
"rust-overlay": "rust-overlay"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"rust-overlay": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": "nixpkgs_2"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1774581174,
|
|
||||||
"narHash": "sha256-258qgkMkYPkJ9qpIg63Wk8GoIbVjszkGGPU1wbVHYTk=",
|
|
||||||
"owner": "oxalica",
|
|
||||||
"repo": "rust-overlay",
|
|
||||||
"rev": "a313afc75b85fc77ac154bf0e62c36f68361fd0b",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "oxalica",
|
|
||||||
"repo": "rust-overlay",
|
|
||||||
"type": "github"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"systems": {
|
"systems": {
|
||||||
|
|
@ -156,21 +54,6 @@
|
||||||
"repo": "default",
|
"repo": "default",
|
||||||
"type": "github"
|
"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",
|
"root": "root",
|
||||||
|
|
|
||||||
93
flake.nix
93
flake.nix
|
|
@ -2,93 +2,38 @@
|
||||||
description = "Jet Pham's personal website";
|
description = "Jet Pham's personal website";
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||||
rust-overlay.url = "github:oxalica/rust-overlay";
|
|
||||||
flake-utils.url = "github:numtide/flake-utils";
|
flake-utils.url = "github:numtide/flake-utils";
|
||||||
agenix.url = "github:ryantm/agenix";
|
|
||||||
agenix.inputs.nixpkgs.follows = "nixpkgs";
|
|
||||||
};
|
};
|
||||||
outputs =
|
outputs =
|
||||||
{
|
{
|
||||||
self,
|
self,
|
||||||
nixpkgs,
|
nixpkgs,
|
||||||
rust-overlay,
|
|
||||||
flake-utils,
|
flake-utils,
|
||||||
agenix,
|
|
||||||
}:
|
}:
|
||||||
(flake-utils.lib.eachDefaultSystem (
|
(flake-utils.lib.eachDefaultSystem (
|
||||||
system:
|
system:
|
||||||
let
|
let
|
||||||
overlays = [ (import rust-overlay) ];
|
pkgs = import nixpkgs { inherit system; };
|
||||||
pkgs = import nixpkgs { inherit system overlays; };
|
lib = pkgs.lib;
|
||||||
agenixPkg = agenix.packages.${system}.default;
|
websiteSrc = lib.fileset.toSource {
|
||||||
rustToolchain = pkgs.rust-bin.selectLatestNightlyWith (
|
root = ./.;
|
||||||
toolchain:
|
fileset = lib.fileset.unions [
|
||||||
toolchain.default.override {
|
./index.html
|
||||||
extensions = [ "rust-src" ];
|
./package-lock.json
|
||||||
targets = [ "wasm32-unknown-unknown" ];
|
./package.json
|
||||||
}
|
./public
|
||||||
);
|
./src
|
||||||
rustPlatform = pkgs.makeRustPlatform {
|
./tsconfig.json
|
||||||
cargo = rustToolchain;
|
./vite-plugin-ansi.ts
|
||||||
rustc = rustToolchain;
|
./vite.config.ts
|
||||||
};
|
|
||||||
|
|
||||||
cgol-wasm = rustPlatform.buildRustPackage {
|
|
||||||
pname = "cgol-wasm";
|
|
||||||
version = "0.1.0";
|
|
||||||
src = ./cgol;
|
|
||||||
cargoLock.lockFile = ./cgol/Cargo.lock;
|
|
||||||
doCheck = false;
|
|
||||||
|
|
||||||
nativeBuildInputs = [
|
|
||||||
pkgs.wasm-bindgen-cli
|
|
||||||
pkgs.binaryen
|
|
||||||
];
|
];
|
||||||
|
|
||||||
buildPhase = ''
|
|
||||||
runHook preBuild
|
|
||||||
cargo build --release --frozen --target wasm32-unknown-unknown
|
|
||||||
wasm-bindgen --target web --out-dir pkg target/wasm32-unknown-unknown/release/cgol.wasm
|
|
||||||
wasm-opt pkg/cgol_bg.wasm -o pkg/cgol_bg.wasm -O4 \
|
|
||||||
--enable-bulk-memory --enable-nontrapping-float-to-int \
|
|
||||||
--enable-sign-ext --low-memory-unused --converge
|
|
||||||
runHook postBuild
|
|
||||||
'';
|
|
||||||
|
|
||||||
installPhase = ''
|
|
||||||
runHook preInstall
|
|
||||||
mkdir -p $out
|
|
||||||
cp pkg/cgol_bg.wasm $out/
|
|
||||||
cp pkg/cgol.js $out/
|
|
||||||
cp pkg/cgol.d.ts $out/
|
|
||||||
cp pkg/cgol_bg.wasm.d.ts $out/ 2>/dev/null || true
|
|
||||||
cat > $out/package.json <<'EOF'
|
|
||||||
{
|
|
||||||
"name": "cgol",
|
|
||||||
"type": "module",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"files": ["cgol_bg.wasm", "cgol.js", "cgol.d.ts"],
|
|
||||||
"main": "cgol.js",
|
|
||||||
"types": "cgol.d.ts",
|
|
||||||
"sideEffects": ["./snippets/*"]
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
runHook postInstall
|
|
||||||
'';
|
|
||||||
};
|
};
|
||||||
|
|
||||||
# Stage 2: Build the website with npm
|
|
||||||
website = pkgs.buildNpmPackage {
|
website = pkgs.buildNpmPackage {
|
||||||
pname = "jet-website";
|
pname = "jet-website";
|
||||||
version = "0.1.0";
|
version = "0.1.0";
|
||||||
src = pkgs.lib.cleanSource ./.;
|
src = websiteSrc;
|
||||||
npmDepsHash = "sha256-O4ZUSYyVWOxP15saIadsaZuRO47Y0AvsL4pwvo5b76U=";
|
npmDepsHash = "sha256-UDz4tXNvEa8uiDDGg16K9JbNeQZR3BsVNKtuOgcyurQ=";
|
||||||
|
|
||||||
# Inject the Nix-built WASM before npm install resolves file: dep
|
|
||||||
postPatch = ''
|
|
||||||
mkdir -p cgol/pkg
|
|
||||||
cp -r ${cgol-wasm}/* cgol/pkg/
|
|
||||||
'';
|
|
||||||
|
|
||||||
installPhase = ''
|
installPhase = ''
|
||||||
runHook preInstall
|
runHook preInstall
|
||||||
|
|
@ -110,7 +55,6 @@
|
||||||
{
|
{
|
||||||
packages = {
|
packages = {
|
||||||
default = website;
|
default = website;
|
||||||
cgol-wasm = cgol-wasm;
|
|
||||||
inherit qa-api;
|
inherit qa-api;
|
||||||
};
|
};
|
||||||
devShells.default = pkgs.mkShell {
|
devShells.default = pkgs.mkShell {
|
||||||
|
|
@ -119,12 +63,11 @@
|
||||||
git
|
git
|
||||||
curl
|
curl
|
||||||
openssl
|
openssl
|
||||||
agenixPkg
|
|
||||||
typescript-language-server
|
typescript-language-server
|
||||||
|
rust-analyzer
|
||||||
|
rustc
|
||||||
|
cargo
|
||||||
pkg-config
|
pkg-config
|
||||||
wasm-pack
|
|
||||||
binaryen
|
|
||||||
rustToolchain
|
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
218
module.nix
218
module.nix
|
|
@ -1,27 +1,118 @@
|
||||||
self:
|
self:
|
||||||
{ config, lib, ... }:
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
let
|
let
|
||||||
cfg = config.services.jetpham-website;
|
cfg = config.services.jetpham-website;
|
||||||
package = self.packages.x86_64-linux.default;
|
package = cfg.package;
|
||||||
qaApi = self.packages.x86_64-linux.qa-api;
|
qaApi = cfg.apiPackage;
|
||||||
webhookSecretPath =
|
apiListen = "${cfg.apiListenAddress}:${toString cfg.apiListenPort}";
|
||||||
if cfg.webhookSecretFile != null then
|
caddyCommonConfig = ''
|
||||||
cfg.webhookSecretFile
|
header Cross-Origin-Opener-Policy "same-origin"
|
||||||
else
|
header Cross-Origin-Embedder-Policy "require-corp"
|
||||||
config.age.secrets.webhook-secret.path;
|
|
||||||
|
handle /api/* {
|
||||||
|
reverse_proxy ${apiListen}
|
||||||
|
}
|
||||||
|
|
||||||
|
handle /qa/rss.xml {
|
||||||
|
reverse_proxy ${apiListen}
|
||||||
|
}
|
||||||
|
|
||||||
|
handle {
|
||||||
|
root * ${package}
|
||||||
|
try_files {path} /index.html
|
||||||
|
file_server
|
||||||
|
}
|
||||||
|
|
||||||
|
${cfg.caddy.extraConfig}
|
||||||
|
'';
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
options.services.jetpham-website = {
|
options.services.jetpham-website = {
|
||||||
enable = lib.mkEnableOption "Jet Pham's personal website";
|
enable = lib.mkEnableOption "Jet Pham's personal website";
|
||||||
|
|
||||||
|
package = lib.mkOption {
|
||||||
|
type = lib.types.package;
|
||||||
|
default = self.packages.${pkgs.system}.default;
|
||||||
|
defaultText = lib.literalExpression "self.packages.${pkgs.system}.default";
|
||||||
|
description = "Static site package served by Caddy.";
|
||||||
|
};
|
||||||
|
|
||||||
|
apiPackage = lib.mkOption {
|
||||||
|
type = lib.types.package;
|
||||||
|
default = self.packages.${pkgs.system}.qa-api;
|
||||||
|
defaultText = lib.literalExpression "self.packages.${pkgs.system}.qa-api";
|
||||||
|
description = "Q&A API package run by systemd.";
|
||||||
|
};
|
||||||
|
|
||||||
domain = lib.mkOption {
|
domain = lib.mkOption {
|
||||||
type = lib.types.str;
|
type = lib.types.str;
|
||||||
default = "jetpham.com";
|
default = "jetpham.com";
|
||||||
description = "Domain to serve the website on.";
|
description = "Domain to serve the website on.";
|
||||||
};
|
};
|
||||||
|
|
||||||
tor.enable = lib.mkEnableOption "Tor hidden service for the website";
|
openFirewall = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "Open HTTP and HTTPS ports when Caddy is enabled.";
|
||||||
|
};
|
||||||
|
|
||||||
|
apiListenAddress = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "127.0.0.1";
|
||||||
|
description = "Address for the local Q&A API listener.";
|
||||||
|
};
|
||||||
|
|
||||||
|
apiListenPort = lib.mkOption {
|
||||||
|
type = lib.types.port;
|
||||||
|
default = 3003;
|
||||||
|
description = "Port for the local Q&A API listener.";
|
||||||
|
};
|
||||||
|
|
||||||
|
caddy.enable = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = true;
|
||||||
|
description = "Serve the static site and reverse proxy the API through Caddy.";
|
||||||
|
};
|
||||||
|
|
||||||
|
caddy.extraConfig = lib.mkOption {
|
||||||
|
type = lib.types.lines;
|
||||||
|
default = "";
|
||||||
|
description = "Extra Caddy directives appended inside the virtual host block.";
|
||||||
|
};
|
||||||
|
|
||||||
|
tor = {
|
||||||
|
enable = lib.mkEnableOption "Tor hidden service for the website";
|
||||||
|
|
||||||
|
port = lib.mkOption {
|
||||||
|
type = lib.types.port;
|
||||||
|
default = 8888;
|
||||||
|
description = "Local Caddy port exposed through the onion service.";
|
||||||
|
};
|
||||||
|
|
||||||
|
onionSecretKeyFile = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.path;
|
||||||
|
default = null;
|
||||||
|
description = "Path to the Tor hidden service secret key file.";
|
||||||
|
};
|
||||||
|
|
||||||
|
onionPublicKeyFile = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.path;
|
||||||
|
default = null;
|
||||||
|
description = "Path to the Tor hidden service public key file.";
|
||||||
|
};
|
||||||
|
|
||||||
|
onionHostnameFile = lib.mkOption {
|
||||||
|
type = lib.types.nullOr lib.types.path;
|
||||||
|
default = null;
|
||||||
|
description = "Path to the Tor hidden service hostname file.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
qaNotifyEmail = lib.mkOption {
|
qaNotifyEmail = lib.mkOption {
|
||||||
type = lib.types.str;
|
type = lib.types.str;
|
||||||
|
|
@ -42,36 +133,31 @@ in
|
||||||
};
|
};
|
||||||
|
|
||||||
webhookSecretFile = lib.mkOption {
|
webhookSecretFile = lib.mkOption {
|
||||||
type = lib.types.nullOr lib.types.path;
|
type = lib.types.path;
|
||||||
default = null;
|
|
||||||
description = "File containing the WEBHOOK_SECRET for MTA Hook authentication.";
|
description = "File containing the WEBHOOK_SECRET for MTA Hook authentication.";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
config = lib.mkIf cfg.enable {
|
config = lib.mkIf cfg.enable {
|
||||||
age.secrets.webhook-secret = lib.mkIf (cfg.webhookSecretFile == null) {
|
assertions = [
|
||||||
file = "${self}/secrets/webhook-secret.age";
|
{
|
||||||
mode = "0400";
|
assertion =
|
||||||
};
|
!cfg.tor.enable
|
||||||
|
|| (
|
||||||
|
cfg.tor.onionSecretKeyFile != null
|
||||||
|
&& cfg.tor.onionPublicKeyFile != null
|
||||||
|
&& cfg.tor.onionHostnameFile != null
|
||||||
|
);
|
||||||
|
message = "services.jetpham-website.tor requires onionSecretKeyFile, onionPublicKeyFile, and onionHostnameFile.";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
age.secrets.tor-onion-secret-key = lib.mkIf cfg.tor.enable {
|
networking.firewall.allowedTCPPorts = lib.mkIf (cfg.caddy.enable && cfg.openFirewall) [
|
||||||
file = "${self}/secrets/tor-onion-secret-key.age";
|
80
|
||||||
owner = "tor";
|
443
|
||||||
group = "tor";
|
];
|
||||||
mode = "0400";
|
|
||||||
};
|
services.caddy.enable = cfg.caddy.enable;
|
||||||
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 {
|
services.tor = lib.mkIf cfg.tor.enable {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
|
@ -81,7 +167,7 @@ in
|
||||||
port = 80;
|
port = 80;
|
||||||
target = {
|
target = {
|
||||||
addr = "127.0.0.1";
|
addr = "127.0.0.1";
|
||||||
port = 8888;
|
port = cfg.tor.port;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
@ -90,39 +176,45 @@ in
|
||||||
|
|
||||||
systemd.services.tor-onion-keys = lib.mkIf cfg.tor.enable {
|
systemd.services.tor-onion-keys = lib.mkIf cfg.tor.enable {
|
||||||
description = "Copy Tor onion keys into place";
|
description = "Copy Tor onion keys into place";
|
||||||
after = [ "agenix.service" ];
|
|
||||||
before = [ "tor.service" ];
|
before = [ "tor.service" ];
|
||||||
wantedBy = [ "tor.service" ];
|
wantedBy = [ "tor.service" ];
|
||||||
serviceConfig.Type = "oneshot";
|
serviceConfig.Type = "oneshot";
|
||||||
script = ''
|
script = ''
|
||||||
dir="/var/lib/tor/onion/jetpham-website"
|
dir="/var/lib/tor/onion/jetpham-website"
|
||||||
mkdir -p "$dir"
|
mkdir -p "$dir"
|
||||||
cp ${config.age.secrets.tor-onion-secret-key.path} "$dir/hs_ed25519_secret_key"
|
cp ${cfg.tor.onionSecretKeyFile} "$dir/hs_ed25519_secret_key"
|
||||||
cp ${config.age.secrets.tor-onion-public-key.path} "$dir/hs_ed25519_public_key"
|
cp ${cfg.tor.onionPublicKeyFile} "$dir/hs_ed25519_public_key"
|
||||||
cp ${config.age.secrets.tor-onion-hostname.path} "$dir/hostname"
|
cp ${cfg.tor.onionHostnameFile} "$dir/hostname"
|
||||||
chown -R tor:tor "$dir"
|
chown -R tor:tor "$dir"
|
||||||
chmod 700 "$dir"
|
chmod 700 "$dir"
|
||||||
chmod 400 "$dir/hs_ed25519_secret_key"
|
chmod 400 "$dir/hs_ed25519_secret_key"
|
||||||
chmod 444 "$dir/hs_ed25519_public_key" "$dir/hostname"
|
chmod 444 "$dir/hs_ed25519_public_key" "$dir/hostname"
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
# Q&A API systemd service
|
|
||||||
systemd.services.jetpham-qa-api = {
|
systemd.services.jetpham-qa-api = {
|
||||||
description = "Jet Pham Q&A API";
|
description = "Jet Pham Q&A API";
|
||||||
after = [ "network.target" ];
|
after = [ "network-online.target" ];
|
||||||
|
wants = [ "network-online.target" ];
|
||||||
wantedBy = [ "multi-user.target" ];
|
wantedBy = [ "multi-user.target" ];
|
||||||
serviceConfig = {
|
serviceConfig = {
|
||||||
DynamicUser = true;
|
DynamicUser = true;
|
||||||
StateDirectory = "jetpham-qa";
|
StateDirectory = "jetpham-qa";
|
||||||
|
WorkingDirectory = "/var/lib/jetpham-qa";
|
||||||
Environment = [
|
Environment = [
|
||||||
"QA_DB_PATH=/var/lib/jetpham-qa/qa.db"
|
"QA_DB_PATH=/var/lib/jetpham-qa/qa.db"
|
||||||
"QA_NOTIFY_EMAIL=${cfg.qaNotifyEmail}"
|
"QA_NOTIFY_EMAIL=${cfg.qaNotifyEmail}"
|
||||||
"QA_MAIL_DOMAIN=${cfg.qaMailDomain}"
|
"QA_MAIL_DOMAIN=${cfg.qaMailDomain}"
|
||||||
"QA_REPLY_DOMAIN=${cfg.qaReplyDomain}"
|
"QA_REPLY_DOMAIN=${cfg.qaReplyDomain}"
|
||||||
];
|
];
|
||||||
|
NoNewPrivileges = true;
|
||||||
|
PrivateTmp = true;
|
||||||
|
ProtectHome = true;
|
||||||
|
ProtectSystem = "strict";
|
||||||
|
ReadWritePaths = [ "/var/lib/jetpham-qa" ];
|
||||||
Restart = "on-failure";
|
Restart = "on-failure";
|
||||||
RestartSec = 5;
|
RestartSec = 5;
|
||||||
LoadCredential = "webhook-secret:${webhookSecretPath}";
|
LoadCredential = "webhook-secret:${cfg.webhookSecretFile}";
|
||||||
};
|
};
|
||||||
script = ''
|
script = ''
|
||||||
if [ ! -s "$CREDENTIALS_DIRECTORY/webhook-secret" ]; then
|
if [ ! -s "$CREDENTIALS_DIRECTORY/webhook-secret" ]; then
|
||||||
|
|
@ -131,50 +223,20 @@ in
|
||||||
fi
|
fi
|
||||||
|
|
||||||
export WEBHOOK_SECRET="$(cat "$CREDENTIALS_DIRECTORY/webhook-secret")"
|
export WEBHOOK_SECRET="$(cat "$CREDENTIALS_DIRECTORY/webhook-secret")"
|
||||||
exec ${qaApi}/bin/jetpham-qa-api
|
exec ${lib.getExe qaApi}
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
services.caddy.virtualHosts.${cfg.domain} = {
|
services.caddy.virtualHosts.${cfg.domain} = lib.mkIf cfg.caddy.enable {
|
||||||
extraConfig = ''
|
extraConfig = caddyCommonConfig;
|
||||||
header Cross-Origin-Opener-Policy "same-origin"
|
|
||||||
header Cross-Origin-Embedder-Policy "require-corp"
|
|
||||||
|
|
||||||
handle /api/* {
|
|
||||||
reverse_proxy 127.0.0.1:3003
|
|
||||||
}
|
|
||||||
|
|
||||||
handle /qa/rss.xml {
|
|
||||||
reverse_proxy 127.0.0.1:3003
|
|
||||||
}
|
|
||||||
|
|
||||||
handle {
|
|
||||||
root * ${package}
|
|
||||||
try_files {path} /index.html
|
|
||||||
file_server
|
|
||||||
}
|
|
||||||
'';
|
|
||||||
};
|
};
|
||||||
|
|
||||||
services.caddy.virtualHosts."http://:8888" = lib.mkIf cfg.tor.enable {
|
services.caddy.virtualHosts."http://:${toString cfg.tor.port}" =
|
||||||
|
lib.mkIf (cfg.caddy.enable && cfg.tor.enable)
|
||||||
|
{
|
||||||
extraConfig = ''
|
extraConfig = ''
|
||||||
bind 127.0.0.1
|
bind 127.0.0.1
|
||||||
header Cross-Origin-Opener-Policy "same-origin"
|
${caddyCommonConfig}
|
||||||
header Cross-Origin-Embedder-Policy "require-corp"
|
|
||||||
|
|
||||||
handle /api/* {
|
|
||||||
reverse_proxy 127.0.0.1:3003
|
|
||||||
}
|
|
||||||
|
|
||||||
handle /qa/rss.xml {
|
|
||||||
reverse_proxy 127.0.0.1:3003
|
|
||||||
}
|
|
||||||
|
|
||||||
handle {
|
|
||||||
root * ${package}
|
|
||||||
try_files {path} /index.html
|
|
||||||
file_server
|
|
||||||
}
|
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
252
tor.har
252
tor.har
|
|
@ -1,252 +0,0 @@
|
||||||
{
|
|
||||||
"log": {
|
|
||||||
"version": "1.2",
|
|
||||||
"creator": {
|
|
||||||
"name": "Firefox",
|
|
||||||
"version": "140.8.0"
|
|
||||||
},
|
|
||||||
"browser": {
|
|
||||||
"name": "Firefox",
|
|
||||||
"version": "140.8.0"
|
|
||||||
},
|
|
||||||
"pages": [
|
|
||||||
{
|
|
||||||
"id": "page_1",
|
|
||||||
"pageTimings": {
|
|
||||||
"onContentLoad": 1578,
|
|
||||||
"onLoad": 1578
|
|
||||||
},
|
|
||||||
"startedDateTime": "2026-03-26T20:03:41.824-07:00",
|
|
||||||
"title": "http://jet7tetd43snvjx3ng5jrhuwm2yhyp76tjtct5mtofg64apokcgq7fqd.onion/"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"entries": [
|
|
||||||
{
|
|
||||||
"startedDateTime": "2026-03-26T20:03:41.824-07:00",
|
|
||||||
"request": {
|
|
||||||
"bodySize": 0,
|
|
||||||
"method": "GET",
|
|
||||||
"url": "http://jet7tetd43snvjx3ng5jrhuwm2yhyp76tjtct5mtofg64apokcgq7fqd.onion/",
|
|
||||||
"httpVersion": "HTTP/1.1",
|
|
||||||
"headers": [
|
|
||||||
{
|
|
||||||
"name": "Host",
|
|
||||||
"value": "jet7tetd43snvjx3ng5jrhuwm2yhyp76tjtct5mtofg64apokcgq7fqd.onion"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "User-Agent",
|
|
||||||
"value": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Accept",
|
|
||||||
"value": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Accept-Language",
|
|
||||||
"value": "en-US,en;q=0.5"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Accept-Encoding",
|
|
||||||
"value": "gzip, deflate, br, zstd"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Sec-GPC",
|
|
||||||
"value": "1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Connection",
|
|
||||||
"value": "keep-alive"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Upgrade-Insecure-Requests",
|
|
||||||
"value": "1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Sec-Fetch-Dest",
|
|
||||||
"value": "document"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Sec-Fetch-Mode",
|
|
||||||
"value": "navigate"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Sec-Fetch-Site",
|
|
||||||
"value": "none"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Priority",
|
|
||||||
"value": "u=0, i"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Pragma",
|
|
||||||
"value": "no-cache"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Cache-Control",
|
|
||||||
"value": "no-cache"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"cookies": [],
|
|
||||||
"queryString": [],
|
|
||||||
"headersSize": 521
|
|
||||||
},
|
|
||||||
"response": {
|
|
||||||
"status": 200,
|
|
||||||
"statusText": "OK",
|
|
||||||
"httpVersion": "HTTP/1.1",
|
|
||||||
"headers": [
|
|
||||||
{
|
|
||||||
"name": "Server",
|
|
||||||
"value": "Caddy"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Date",
|
|
||||||
"value": "Fri, 27 Mar 2026 03:03:43 GMT"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Content-Length",
|
|
||||||
"value": "0"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"cookies": [],
|
|
||||||
"content": {
|
|
||||||
"mimeType": "text/plain",
|
|
||||||
"size": 0,
|
|
||||||
"text": ""
|
|
||||||
},
|
|
||||||
"redirectURL": "",
|
|
||||||
"headersSize": 90,
|
|
||||||
"bodySize": 90
|
|
||||||
},
|
|
||||||
"cache": {},
|
|
||||||
"timings": {
|
|
||||||
"blocked": 894,
|
|
||||||
"dns": 0,
|
|
||||||
"connect": 894,
|
|
||||||
"ssl": 0,
|
|
||||||
"send": 0,
|
|
||||||
"wait": 617,
|
|
||||||
"receive": 0
|
|
||||||
},
|
|
||||||
"time": 2405,
|
|
||||||
"_securityState": "insecure",
|
|
||||||
"serverIPAddress": "0.0.0.0",
|
|
||||||
"connection": "80",
|
|
||||||
"pageref": "page_1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"startedDateTime": "2026-03-26T20:03:43.440-07:00",
|
|
||||||
"request": {
|
|
||||||
"bodySize": 0,
|
|
||||||
"method": "GET",
|
|
||||||
"url": "http://jet7tetd43snvjx3ng5jrhuwm2yhyp76tjtct5mtofg64apokcgq7fqd.onion/favicon.ico",
|
|
||||||
"httpVersion": "HTTP/1.1",
|
|
||||||
"headers": [
|
|
||||||
{
|
|
||||||
"name": "Host",
|
|
||||||
"value": "jet7tetd43snvjx3ng5jrhuwm2yhyp76tjtct5mtofg64apokcgq7fqd.onion"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "User-Agent",
|
|
||||||
"value": "Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Accept",
|
|
||||||
"value": "image/avif,image/webp,image/png,image/svg+xml,image/*;q=0.8,*/*;q=0.5"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Accept-Language",
|
|
||||||
"value": "en-US,en;q=0.5"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Accept-Encoding",
|
|
||||||
"value": "gzip, deflate, br, zstd"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Referer",
|
|
||||||
"value": "http://jet7tetd43snvjx3ng5jrhuwm2yhyp76tjtct5mtofg64apokcgq7fqd.onion/"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Sec-GPC",
|
|
||||||
"value": "1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Connection",
|
|
||||||
"value": "keep-alive"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Sec-Fetch-Dest",
|
|
||||||
"value": "image"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Sec-Fetch-Mode",
|
|
||||||
"value": "no-cors"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Sec-Fetch-Site",
|
|
||||||
"value": "same-origin"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Priority",
|
|
||||||
"value": "u=6"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Pragma",
|
|
||||||
"value": "no-cache"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Cache-Control",
|
|
||||||
"value": "no-cache"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"cookies": [],
|
|
||||||
"queryString": [],
|
|
||||||
"headersSize": 589
|
|
||||||
},
|
|
||||||
"response": {
|
|
||||||
"status": 200,
|
|
||||||
"statusText": "OK",
|
|
||||||
"httpVersion": "HTTP/1.1",
|
|
||||||
"headers": [
|
|
||||||
{
|
|
||||||
"name": "Server",
|
|
||||||
"value": "Caddy"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Date",
|
|
||||||
"value": "Fri, 27 Mar 2026 03:03:43 GMT"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Content-Length",
|
|
||||||
"value": "0"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"cookies": [],
|
|
||||||
"content": {
|
|
||||||
"mimeType": "text/plain",
|
|
||||||
"size": 0,
|
|
||||||
"text": ""
|
|
||||||
},
|
|
||||||
"redirectURL": "",
|
|
||||||
"headersSize": 90,
|
|
||||||
"bodySize": 90
|
|
||||||
},
|
|
||||||
"cache": {},
|
|
||||||
"timings": {
|
|
||||||
"blocked": 0,
|
|
||||||
"dns": 0,
|
|
||||||
"connect": 0,
|
|
||||||
"ssl": 0,
|
|
||||||
"send": 0,
|
|
||||||
"wait": 604,
|
|
||||||
"receive": 0
|
|
||||||
},
|
|
||||||
"time": 604,
|
|
||||||
"_securityState": "insecure",
|
|
||||||
"serverIPAddress": "0.0.0.0",
|
|
||||||
"connection": "80",
|
|
||||||
"pageref": "page_1"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue