fix: cleanup flake and repo

This commit is contained in:
Jet 2026-03-28 20:13:12 -07:00
parent 17d708eb7a
commit 9a5256e8f0
No known key found for this signature in database
7 changed files with 197 additions and 530 deletions

1
.gitignore vendored
View file

@ -10,6 +10,7 @@
# misc # misc
.DS_Store .DS_Store
*.pem *.pem
*.har
# debug # debug
npm-debug.log* npm-debug.log*

View file

@ -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.

View file

@ -1,2 +0,0 @@
$ bun run lint && tsc --noEmit
$ eslint .

121
flake.lock generated
View file

@ -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",

View file

@ -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
]; ];
}; };
} }

View file

@ -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
View file

@ -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"
}
]
}
}