nixos server configurations

Compare changes

Choose any two refs to compare.

Changed files
+508 -29
modules
secrets
kuribo
servers
+212 -4
flake.lock
···
{
"nodes": {
"nixpkgs": {
"locked": {
-
"lastModified": 1764517877,
-
"narHash": "sha256-pp3uT4hHijIC8JUK5MEqeAWmParJrgBVzHLNfJDZxg4=",
"owner": "NixOS",
"repo": "nixpkgs",
-
"rev": "2d293cbfa5a793b4c50d17c05ef9e385b90edf6c",
"type": "github"
},
"original": {
···
"root": {
"inputs": {
"nixpkgs": "nixpkgs",
-
"sops-nix": "sops-nix"
}
},
"sops-nix": {
···
"owner": "Mic92",
"repo": "sops-nix",
"type": "github"
}
}
},
···
{
"nodes": {
+
"actor-typeahead-src": {
+
"flake": false,
+
"locked": {
+
"lastModified": 1762835797,
+
"narHash": "sha256-heizoWUKDdar6ymfZTnj3ytcEv/L4d4fzSmtr0HlXsQ=",
+
"ref": "refs/heads/main",
+
"rev": "677fe7f743050a4e7f09d4a6f87bbf1325a06f6b",
+
"revCount": 6,
+
"type": "git",
+
"url": "https://tangled.org/@jakelazaroff.com/actor-typeahead"
+
},
+
"original": {
+
"type": "git",
+
"url": "https://tangled.org/@jakelazaroff.com/actor-typeahead"
+
}
+
},
+
"flake-compat": {
+
"flake": false,
+
"locked": {
+
"lastModified": 1751685974,
+
"narHash": "sha256-NKw96t+BgHIYzHUjkTK95FqYRVKB8DHpVhefWSz/kTw=",
+
"rev": "549f2762aebeff29a2e5ece7a7dc0f955281a1d1",
+
"type": "tarball",
+
"url": "https://git.lix.systems/api/v1/repos/lix-project/flake-compat/archive/549f2762aebeff29a2e5ece7a7dc0f955281a1d1.tar.gz?rev=549f2762aebeff29a2e5ece7a7dc0f955281a1d1"
+
},
+
"original": {
+
"type": "tarball",
+
"url": "https://git.lix.systems/lix-project/flake-compat/archive/main.tar.gz"
+
}
+
},
+
"flake-utils": {
+
"inputs": {
+
"systems": "systems"
+
},
+
"locked": {
+
"lastModified": 1694529238,
+
"narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
+
"owner": "numtide",
+
"repo": "flake-utils",
+
"rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
+
"type": "github"
+
},
+
"original": {
+
"owner": "numtide",
+
"repo": "flake-utils",
+
"type": "github"
+
}
+
},
+
"gomod2nix": {
+
"inputs": {
+
"flake-utils": "flake-utils",
+
"nixpkgs": [
+
"tangled",
+
"nixpkgs"
+
]
+
},
+
"locked": {
+
"lastModified": 1754078208,
+
"narHash": "sha256-YVoIFDCDpYuU3riaDEJ3xiGdPOtsx4sR5eTzHTytPV8=",
+
"owner": "nix-community",
+
"repo": "gomod2nix",
+
"rev": "7f963246a71626c7fc70b431a315c4388a0c95cf",
+
"type": "github"
+
},
+
"original": {
+
"owner": "nix-community",
+
"repo": "gomod2nix",
+
"type": "github"
+
}
+
},
+
"htmx-src": {
+
"flake": false,
+
"locked": {
+
"narHash": "sha256-nm6avZuEBg67SSyyZUhjpXVNstHHgUxrtBHqJgowU08=",
+
"type": "file",
+
"url": "https://unpkg.com/htmx.org@2.0.4/dist/htmx.min.js"
+
},
+
"original": {
+
"type": "file",
+
"url": "https://unpkg.com/htmx.org@2.0.4/dist/htmx.min.js"
+
}
+
},
+
"htmx-ws-src": {
+
"flake": false,
+
"locked": {
+
"narHash": "sha256-2fg6KyEJoO24q0fQqbz9RMaYNPQrMwpZh29tkSqdqGY=",
+
"type": "file",
+
"url": "https://cdn.jsdelivr.net/npm/htmx-ext-ws@2.0.2"
+
},
+
"original": {
+
"type": "file",
+
"url": "https://cdn.jsdelivr.net/npm/htmx-ext-ws@2.0.2"
+
}
+
},
+
"ibm-plex-mono-src": {
+
"flake": false,
+
"locked": {
+
"lastModified": 1731402384,
+
"narHash": "sha256-OwUmrPfEehLDz0fl2ChYLK8FQM2p0G1+EMrGsYEq+6g=",
+
"type": "tarball",
+
"url": "https://github.com/IBM/plex/releases/download/@ibm/plex-mono@1.1.0/ibm-plex-mono.zip"
+
},
+
"original": {
+
"type": "tarball",
+
"url": "https://github.com/IBM/plex/releases/download/@ibm/plex-mono@1.1.0/ibm-plex-mono.zip"
+
}
+
},
+
"indigo": {
+
"flake": false,
+
"locked": {
+
"lastModified": 1753693716,
+
"narHash": "sha256-DMIKnCJRODQXEHUxA+7mLzRALmnZhkkbHlFT2rCQYrE=",
+
"owner": "oppiliappan",
+
"repo": "indigo",
+
"rev": "5f170569da9360f57add450a278d73538092d8ca",
+
"type": "github"
+
},
+
"original": {
+
"owner": "oppiliappan",
+
"repo": "indigo",
+
"type": "github"
+
}
+
},
+
"inter-fonts-src": {
+
"flake": false,
+
"locked": {
+
"lastModified": 1731687360,
+
"narHash": "sha256-5vdKKvHAeZi6igrfpbOdhZlDX2/5+UvzlnCQV6DdqoQ=",
+
"type": "tarball",
+
"url": "https://github.com/rsms/inter/releases/download/v4.1/Inter-4.1.zip"
+
},
+
"original": {
+
"type": "tarball",
+
"url": "https://github.com/rsms/inter/releases/download/v4.1/Inter-4.1.zip"
+
}
+
},
+
"lucide-src": {
+
"flake": false,
+
"locked": {
+
"lastModified": 1754044466,
+
"narHash": "sha256-+exBR2OToB1iv7ZQI2S4B0lXA/QRvC9n6U99UxGpJGs=",
+
"type": "tarball",
+
"url": "https://github.com/lucide-icons/lucide/releases/download/0.536.0/lucide-icons-0.536.0.zip"
+
},
+
"original": {
+
"type": "tarball",
+
"url": "https://github.com/lucide-icons/lucide/releases/download/0.536.0/lucide-icons-0.536.0.zip"
+
}
+
},
"nixpkgs": {
"locked": {
+
"lastModified": 1764667669,
+
"narHash": "sha256-7WUCZfmqLAssbDqwg9cUDAXrSoXN79eEEq17qhTNM/Y=",
"owner": "NixOS",
"repo": "nixpkgs",
+
"rev": "418468ac9527e799809c900eda37cbff999199b6",
"type": "github"
},
"original": {
···
"root": {
"inputs": {
"nixpkgs": "nixpkgs",
+
"sops-nix": "sops-nix",
+
"tangled": "tangled"
}
},
"sops-nix": {
···
"owner": "Mic92",
"repo": "sops-nix",
"type": "github"
+
}
+
},
+
"sqlite-lib-src": {
+
"flake": false,
+
"locked": {
+
"lastModified": 1706631843,
+
"narHash": "sha256-bJoMjirsBjm2Qk9KPiy3yV3+8b/POlYe76/FQbciHro=",
+
"type": "tarball",
+
"url": "https://sqlite.org/2024/sqlite-amalgamation-3450100.zip"
+
},
+
"original": {
+
"type": "tarball",
+
"url": "https://sqlite.org/2024/sqlite-amalgamation-3450100.zip"
+
}
+
},
+
"systems": {
+
"locked": {
+
"lastModified": 1681028828,
+
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+
"owner": "nix-systems",
+
"repo": "default",
+
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+
"type": "github"
+
},
+
"original": {
+
"owner": "nix-systems",
+
"repo": "default",
+
"type": "github"
+
}
+
},
+
"tangled": {
+
"inputs": {
+
"actor-typeahead-src": "actor-typeahead-src",
+
"flake-compat": "flake-compat",
+
"gomod2nix": "gomod2nix",
+
"htmx-src": "htmx-src",
+
"htmx-ws-src": "htmx-ws-src",
+
"ibm-plex-mono-src": "ibm-plex-mono-src",
+
"indigo": "indigo",
+
"inter-fonts-src": "inter-fonts-src",
+
"lucide-src": "lucide-src",
+
"nixpkgs": [
+
"nixpkgs"
+
],
+
"sqlite-lib-src": "sqlite-lib-src"
+
},
+
"locked": {
+
"lastModified": 1764871901,
+
"narHash": "sha256-P+4bXuzU66XYwvhw4SIC6T/dS5O5ascASGL4cb2TNuI=",
+
"ref": "refs/heads/master",
+
"rev": "ca41f2429eb734a16c5f5061940acda4033d437f",
+
"revCount": 1697,
+
"type": "git",
+
"url": "https://tangled.org/tangled.org/core"
+
},
+
"original": {
+
"type": "git",
+
"url": "https://tangled.org/tangled.org/core"
}
}
},
+2 -2
modules/auto-upgrade.nix
···
{
system.autoUpgrade = {
-
enable = false; # TODO
-
flake = "git+https://tangled.org/starhaven.dev/infra"; # TODO
flags = [
"-L" # print build logs
];
···
{
system.autoUpgrade = {
+
enable = true;
+
flake = "git+https://tangled.org/starhaven.dev/infra";
flags = [
"-L" # print build logs
];
+13 -4
secrets/kuribo/pds.env
···
PDS_JWT_SECRET=ENC[AES256_GCM,data:SwmU7j+3kfoCCQlZk/LAzytRoVSb7tgKI6tGdZKJThg=,iv:1WCvMVlPR4L7rO/YUmkobjHcXlSGlyIo80ir+GymdeQ=,tag:WbGeolX/pzSZ+LA8ueUygA==,type:str]
PDS_ADMIN_PASSWORD=ENC[AES256_GCM,data:+U1Tw+rRcb9rPjZTsOZ9ZYdVeTRFjv8yhuSCCFIe+wQ=,iv:TLJ+HJ8hDXcaZ/9qtSonnOE1oz4JngxuXJLjXpqdDwU=,tag:W7Hi9XMXUMUZAimnAc6uJQ==,type:str]
PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX=ENC[AES256_GCM,data:thNijhgsq106+SJVnoseWu1S8SU2AB8Z5EqjKUzMBm+29FB146dmqPOphXL5yBPDuj0gjzFvfu4W7BOAKcx7fA==,iv:zcmhJopT8WHN2GfhDGO1oYp/NeyPpXeNrg6AVmDYMGk=,tag:JLcO4aVuKMVgRU6pirks+A==,type:str]
-
PDS_EMAIL_SMTP_URL=ENC[AES256_GCM,data:ltLt4Q7CaIL4swhDA2pBcMRR2gaMGcYw/7E7JtU4bMotEXrEO19V5ySomjbdFs3ImFzMtVVNY0am9R5Q40TZq85r6zDsoGv6,iv:Kh10CNUhkzqj5PROyFgGme0KUspZL/epxiQf2Ej0G6Y=,tag:noT7j38nip6OLbnwr8AWDQ==,type:str]
-
PDS_EMAIL_FROM_ADDRESS=ENC[AES256_GCM,data:VxEX3on/7jQ/SXmr53bvFzd3O/xu,iv:yehoA4hxkJ6UOjv625834otS1Es4uKtarjZjKFk2sJI=,tag:XaplSBikfq97mLTq+XyOrQ==,type:str]
sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBkenRnNWFlMzBIOTJsclYw\nSkw3ZW9pa0NRejRQd1FmOENTaE54UU4rcHkwCjFBSXljeTdQeGhXZDZrWS9JUkx0\nUXpxWUVKZTdGWjVLT1FRUmloMXhNdWcKLS0tIEhERVFJNU5pSU00b3MxUHB1Y280\nWTFiaTh0YXJyUXFKNGNrOE84elRONVUK20OPeWSZW2A9mTnEDfQmDc7n3jvUQhxb\nBatl6b0ismrkTWcRJK8nxImcvxBtMMCLfzK5Wt/9gBLJ6VDT6UPYFg==\n-----END AGE ENCRYPTED FILE-----\n
sops_age__list_0__map_recipient=age1h08rnd0jeddf55l6l3rf6dlwwh7mngcxy92tyz0hfysjqx4wvgrq6vmah2
sops_age__list_1__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA4OVBaMzZ3UGd1R25SczNN\nd3R6bzVGTkN0SWpjb05wankvb2tpNTJvZlR3CjhwYUdHaTJ6Y1VPSTltOHhNbWdL\ncGs3OEJqaFljUFRhUVNncm13RFdETTgKLS0tIG1Mb1ZXQ3BpejdteWFkWUFyOGJu\ndFpyWkNiK2hoNlROd09xTzVueFdSUmcKDrIcoDDH2O/c9dyS/oLL0rudsrsmtOhJ\n55QagSzYouGlJbpl2xtBeUplg1WcEBX7FSW3UWFbz+Gc0/Rv76jRCA==\n-----END AGE ENCRYPTED FILE-----\n
sops_age__list_1__map_recipient=age1dhxleu7puseq4fz5gprzdssprdd452kjry2n47xaqfh22p5eyqfs68zysl
-
sops_lastmodified=2025-12-01T18:49:36Z
-
sops_mac=ENC[AES256_GCM,data:tSPG0g8XpTu0IJ8GQKIUczVlreLbZ/VFncomwSVzFEIXloJ6QQsX2hobyFCW4RwovQoZnVfO4uL8Ku/SIjsLIMCejLiGXAa4r0VDDZtxhnaX7tPBecG7gE3Ke15V4bT6B9uxB7TGhJYTsTlq/tb8D7UZG2+yWudFry8ArJRFxp0=,iv:9vxvakrxx8EmNBPSY1wnoV5cHx2/8GqGhNLYHyDj74w=,tag:5NeNRKenYykPj6b13bHFOg==,type:str]
sops_unencrypted_suffix=_unencrypted
sops_version=3.11.0
···
PDS_JWT_SECRET=ENC[AES256_GCM,data:SwmU7j+3kfoCCQlZk/LAzytRoVSb7tgKI6tGdZKJThg=,iv:1WCvMVlPR4L7rO/YUmkobjHcXlSGlyIo80ir+GymdeQ=,tag:WbGeolX/pzSZ+LA8ueUygA==,type:str]
PDS_ADMIN_PASSWORD=ENC[AES256_GCM,data:+U1Tw+rRcb9rPjZTsOZ9ZYdVeTRFjv8yhuSCCFIe+wQ=,iv:TLJ+HJ8hDXcaZ/9qtSonnOE1oz4JngxuXJLjXpqdDwU=,tag:W7Hi9XMXUMUZAimnAc6uJQ==,type:str]
PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX=ENC[AES256_GCM,data:thNijhgsq106+SJVnoseWu1S8SU2AB8Z5EqjKUzMBm+29FB146dmqPOphXL5yBPDuj0gjzFvfu4W7BOAKcx7fA==,iv:zcmhJopT8WHN2GfhDGO1oYp/NeyPpXeNrg6AVmDYMGk=,tag:JLcO4aVuKMVgRU6pirks+A==,type:str]
+
PDS_EMAIL_SMTP_URL=ENC[AES256_GCM,data:SEM3Kr3oJH+Qgf68hAFa+5kxvpak8KrQmvqNIANgUaZflik7uQQpmN+nYDLPIv7NAzKBTt+0jIWCLi9HfD5fZKYNiJCa8oJb7w==,iv:+nCm3k4SBOXsVhctz9PPkCcpuD/yv2kBQmfB1nHjKT0=,tag:IUmyDZco9Yy1z/Ahp0bDOw==,type:str]
+
PDS_EMAIL_FROM_ADDRESS=ENC[AES256_GCM,data:O+F92VuNJPZV5ihqG9KAYcTFZpCd,iv:C9g6AQ9h6seu0WYIkmLqTL9FUluky8p9WMOdavhJfwg=,tag:Ab6L4i21b4eIZ7hnPA3GHg==,type:str]
+
PDS_BLOBSTORE_S3_BUCKET=ENC[AES256_GCM,data:a2jMY4b0HZEd,iv:IL4aG6cNOI3VLS+axwr47ZLPXQ8NAz4ZmtcHsVkYZE0=,tag:PedqBtofxgtARa4L81sMOw==,type:str]
+
PDS_BLOBSTORE_S3_REGION=ENC[AES256_GCM,data:jA5Fbg==,iv:oa73XQbCcMuYlSaDzs6TgAxe8QkofaIbTLjGLYViGtE=,tag:RaJcADDkLBNAeP9deMqFqA==,type:str]
+
PDS_BLOBSTORE_S3_ENDPOINT=ENC[AES256_GCM,data:f4Sg8Zb3KK7rbi7wxDvl05YcAq7FhejgbYRwQMNkzG8jxxk=,iv:RCN8LTqEb327n0ICipVerIAHXq/EzLsDx2uPIOg0VTc=,tag:/SV9uPuAr2S3O5rRDDqwRQ==,type:str]
+
PDS_BLOBSTORE_S3_ACCESS_KEY_ID=ENC[AES256_GCM,data:3xbCb2QO3x/jI4W+OXTuLcA18XE=,iv:Bs9knoz+PVZRbUIF1TnVP8bvP8xbN7ItVgQHsDDkQd4=,tag:4yqYBRGHUA8KurW19G9X8A==,type:str]
+
PDS_BLOBSTORE_S3_SECRET_ACCESS_KEY=ENC[AES256_GCM,data:MWIwduFqqe20aYD7dGedcQskSQ6mAoDc55YvAjn1fYxzN6RCNgE28Q==,iv:C8NEL9+3UoN2uv8XG3Bpjedb1pans8g1YuyYdnQ918I=,tag:5hN28CsG67WOaL606k5Y6w==,type:str]
+
PDS_HCAPTCHA_SITE_KEY=ENC[AES256_GCM,data:qkhAjbyESlfzmeeNFBvE9OmM5bsbAG/lK0TFgJ0yRpPU8sEP,iv:3LL84eHS9A8c2FFovYi/g4+NroqLesbjIFhDnqLtEnc=,tag:EMPmyYSubtK/RJsa7GglqA==,type:str]
+
PDS_HCAPTCHA_SECRET_KEY=ENC[AES256_GCM,data:8eO1yMUYo3wnwe0z9n4WygNOpQPBmktbLukFSvAAOyC1Bic=,iv:5VllqLj5Wn0Lfj5X/Jobtk7qSRGm3rxIZuYFSyjkfJc=,tag:GQj6h6Fmzgj15LpQVKjUiQ==,type:str]
+
PDS_HCAPTCHA_TOKEN_SALT=ENC[AES256_GCM,data:no+3mQ==,iv:qXz3svuAPQFEkidBIT0xND+69UjlNbwVqbIM/2rl6rE=,tag:WIP/jBABy2+k5EAD6Mb7AA==,type:str]
+
CLOUDFLARE_API_TOKEN=ENC[AES256_GCM,data:1evMsnfzqoQtkFyzULhcKsmvOORGvRLUbUs1BvES7jQ99PFSUQ3B9w==,iv:6R40bMGDJOkD/OH9Cwzb6jjI1Syg90WvnVxHddd4yCU=,tag:U1sY/8m4VGOxh1IYQ/P3uQ==,type:str]
sops_age__list_0__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBkenRnNWFlMzBIOTJsclYw\nSkw3ZW9pa0NRejRQd1FmOENTaE54UU4rcHkwCjFBSXljeTdQeGhXZDZrWS9JUkx0\nUXpxWUVKZTdGWjVLT1FRUmloMXhNdWcKLS0tIEhERVFJNU5pSU00b3MxUHB1Y280\nWTFiaTh0YXJyUXFKNGNrOE84elRONVUK20OPeWSZW2A9mTnEDfQmDc7n3jvUQhxb\nBatl6b0ismrkTWcRJK8nxImcvxBtMMCLfzK5Wt/9gBLJ6VDT6UPYFg==\n-----END AGE ENCRYPTED FILE-----\n
sops_age__list_0__map_recipient=age1h08rnd0jeddf55l6l3rf6dlwwh7mngcxy92tyz0hfysjqx4wvgrq6vmah2
sops_age__list_1__map_enc=-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA4OVBaMzZ3UGd1R25SczNN\nd3R6bzVGTkN0SWpjb05wankvb2tpNTJvZlR3CjhwYUdHaTJ6Y1VPSTltOHhNbWdL\ncGs3OEJqaFljUFRhUVNncm13RFdETTgKLS0tIG1Mb1ZXQ3BpejdteWFkWUFyOGJu\ndFpyWkNiK2hoNlROd09xTzVueFdSUmcKDrIcoDDH2O/c9dyS/oLL0rudsrsmtOhJ\n55QagSzYouGlJbpl2xtBeUplg1WcEBX7FSW3UWFbz+Gc0/Rv76jRCA==\n-----END AGE ENCRYPTED FILE-----\n
sops_age__list_1__map_recipient=age1dhxleu7puseq4fz5gprzdssprdd452kjry2n47xaqfh22p5eyqfs68zysl
+
sops_lastmodified=2025-12-04T19:58:02Z
+
sops_mac=ENC[AES256_GCM,data:j78zbHBXs1QaklOG6Bh40WSg+Dx++Wv47Tnw+rTW/YW7Xi+dDb6w1ixoR8/563KE396+5YP3PGshzF75gG8cUICq0sg3RBC7RZUGfACeMQVc4e2Zx/w3LwxrgPaZeV1d6WeCRUyACIjogt5xNiFVYPqcvUck8wiR2IrxI5PJdA4=,iv:dy0QQg6BgutGv2F2sjhvZ0WkUGRflfVT0IpAx7RlXkg=,tag:6DOBdEdzcz7S1SDS4+d4hw==,type:str]
sops_unencrypted_suffix=_unencrypted
sops_version=3.11.0
+215
servers/kuribo/ondemand_tls_helper.py
···
···
+
"""
+
This HTTP service implements the /tls-check endpoint used by Caddy's
+
on_demand_tls.ask configuration. Behavior:
+
+
- If the query parameter `domain` is ALLOWED -> return HTTP 200 OK with body "OK".
+
- Otherwise proxy the request (including the query string) to the real PDS
+
tls-check endpoint at http://127.0.0.1:<PDS_PORT>/tls-check and forward
+
the upstream status and body.
+
"""
+
+
import logging
+
import os
+
import socket
+
import sys
+
import urllib.error
+
import urllib.request
+
from http.server import BaseHTTPRequestHandler, HTTPServer
+
from urllib.parse import parse_qs, urlparse
+
+
# Configuration (can be overridden with env vars)
+
PDS_PORT = int(os.environ.get("PDS_PORT", "3000"))
+
LISTEN_HOST = os.environ.get("LISTEN_HOST", "127.0.0.1")
+
LISTEN_PORT = int(os.environ.get("LISTEN_PORT", "8081"))
+
TIMEOUT = float(os.environ.get("TIMEOUT", "5.0"))
+
+
# Allowed domain values (lowercase)
+
ALLOWED = {"pds", "knot", "spindle"}
+
+
# Configure logging to stderr (systemd/journal-friendly)
+
logging.basicConfig(
+
level=logging.INFO,
+
format="%(asctime)s %(levelname)s %(message)s",
+
stream=sys.stderr,
+
)
+
+
+
def filter_response_headers(headers):
+
"""
+
Given an iterable of (header, value) pairs or a mapping-like object,
+
return a dict with hop-by-hop headers removed. This avoids sending
+
problematic headers to the client (Caddy).
+
"""
+
hop_by_hop = {
+
"connection",
+
"keep-alive",
+
"proxy-authenticate",
+
"proxy-authorization",
+
"te",
+
"trailers",
+
"transfer-encoding",
+
"upgrade",
+
}
+
result = {}
+
if hasattr(headers, "items"):
+
iterator = headers.items()
+
else:
+
iterator = headers
+
for k, v in iterator:
+
if k.lower() not in hop_by_hop:
+
result[k] = v
+
return result
+
+
+
class TLSCheckHandler(BaseHTTPRequestHandler):
+
# Reduce console noise from BaseHTTPRequestHandler
+
def log_message(self, format, *args):
+
# route to logging module at INFO level
+
logging.info("%s - %s", self.client_address[0], format % args)
+
+
def _send(self, status, body=b"", headers=None):
+
# Send status, headers, and body to the client
+
self.send_response(status)
+
if headers:
+
for k, v in headers.items():
+
# BaseHTTPRequestHandler will fold multiple headers set via send_header
+
# if necessary; we assume simple string values here.
+
try:
+
self.send_header(k, v)
+
except Exception:
+
# Ignore any header-setting errors; continue to send response
+
logging.debug("skipping header %r due to error", k)
+
else:
+
self.send_header("Content-Type", "text/plain; charset=utf-8")
+
self.end_headers()
+
if body:
+
if isinstance(body, str):
+
body = body.encode("utf-8")
+
try:
+
self.wfile.write(body)
+
except BrokenPipeError:
+
# Client disconnected early; nothing to do
+
pass
+
+
def _proxy_to_pds(self, path_with_query):
+
"""
+
Proxy a request to http://127.0.0.1:<PDS_PORT><path_with_query>.
+
Returns (status, body_bytes, headers_dict).
+
"""
+
target = f"http://127.0.0.1:{PDS_PORT}{path_with_query}"
+
logging.debug("proxying to upstream: %s", target)
+
req = urllib.request.Request(
+
target, headers={"User-Agent": "ondemand-tls-helper/1.0"}
+
)
+
try:
+
with urllib.request.urlopen(req, timeout=TIMEOUT) as resp:
+
data = resp.read()
+
headers = filter_response_headers(resp.getheaders())
+
return resp.status, data, headers
+
except urllib.error.HTTPError as e:
+
# Upstream returned an HTTP error; return its body and status
+
try:
+
data = e.read()
+
except Exception:
+
data = b""
+
status = getattr(e, "code", 502)
+
headers = {}
+
logging.info("upstream returned HTTPError %s for %s", status, target)
+
return status, data, headers
+
except Exception as e:
+
logging.exception("error proxying to upstream %s: %s", target, e)
+
# Return 502 Bad Gateway
+
return 502, f"upstream error: {e}".encode("utf-8"), {}
+
+
def _get_domain_param(self):
+
parsed = urlparse(self.path)
+
qs = parse_qs(parsed.query)
+
domain_vals = qs.get("domain") or []
+
if not domain_vals:
+
return ""
+
return domain_vals[0].strip().lower()
+
+
def do_GET(self):
+
parsed = urlparse(self.path)
+
path_with_query = parsed.path + ("?" + parsed.query if parsed.query else "")
+
domain = self._get_domain_param()
+
+
if domain in ALLOWED:
+
logging.debug("allowed domain %r -> returning 200", domain)
+
return self._send(200, "OK")
+
+
status, body, headers = self._proxy_to_pds(path_with_query)
+
return self._send(status, body, headers=headers)
+
+
def do_HEAD(self):
+
parsed = urlparse(self.path)
+
path_with_query = parsed.path + ("?" + parsed.query if parsed.query else "")
+
domain = self._get_domain_param()
+
+
if domain in ALLOWED:
+
logging.debug("allowed domain (HEAD) %r -> returning 200", domain)
+
return self._send(200, b"")
+
+
status, _, headers = self._proxy_to_pds(path_with_query)
+
return self._send(status, b"", headers=headers)
+
+
+
def run():
+
server_address = (LISTEN_HOST, LISTEN_PORT)
+
try:
+
httpd = HTTPServer(server_address, TLSCheckHandler)
+
except OSError as e:
+
logging.error("cannot bind to %s:%s: %s", LISTEN_HOST, LISTEN_PORT, e)
+
sys.exit(1)
+
+
sa = httpd.socket.getsockname()
+
logging.info("ondemand TLS helper listening on %s:%s", sa[0], sa[1])
+
try:
+
httpd.serve_forever()
+
except KeyboardInterrupt:
+
logging.info("shutting down on keyboard interrupt")
+
except Exception:
+
logging.exception("server crashed")
+
finally:
+
try:
+
httpd.server_close()
+
except Exception:
+
pass
+
+
+
if __name__ == "__main__":
+
# Allow override of config via arguments if invoked with simple flags:
+
# --pds-port N --listen-host HOST --listen-port N --timeout S
+
# to keep the script flexible for local testing.
+
args = sys.argv[1:]
+
it = iter(args)
+
while True:
+
try:
+
a = next(it)
+
except StopIteration:
+
break
+
if a in ("--pds-port",):
+
try:
+
PDS_PORT = int(next(it))
+
except StopIteration:
+
break
+
elif a in ("--listen-host",):
+
try:
+
LISTEN_HOST = next(it)
+
except StopIteration:
+
break
+
elif a in ("--listen-port",):
+
try:
+
LISTEN_PORT = int(next(it))
+
except StopIteration:
+
break
+
elif a in ("--timeout",):
+
try:
+
TIMEOUT = float(next(it))
+
except StopIteration:
+
break
+
else:
+
# ignore unknown args
+
continue
+
+
run()
+66 -5
servers/kuribo/pds.nix
···
-
{ config, ... }:
let
pdsSettings = config.services.bluesky-pds.settings;
in
···
enable = true;
environmentFiles = [ config.sops.secrets.pds.path ];
settings = {
PDS_PORT = 3000;
PDS_HOSTNAME = "pds.starhaven.dev";
PDS_ADMIN_EMAIL = "admin@starhaven.dev";
};
};
services.caddy = {
enable = true;
email = pdsSettings.PDS_ADMIN_EMAIL;
globalConfig = ''
on_demand_tls {
-
ask http://127.0.0.1:${toString pdsSettings.PDS_PORT}/tls-check
}
'';
-
virtualHosts.${pdsSettings.PDS_HOSTNAME} = {
-
serverAliases = [ "*.${pdsSettings.PDS_HOSTNAME}" ];
extraConfig = ''
tls {
on_demand
}
-
reverse_proxy http://127.0.0.1:${toString pdsSettings.PDS_PORT}
handle /xrpc/app.bsky.unspecced.getAgeAssuranceState {
header content-type "application/json"
header access-control-allow-headers "authorization,dpop,atproto-accept-labelers,atproto-proxy"
header access-control-allow-origin "*"
respond `{"lastInitiatedAt":"2025-07-14T14:22:43.912Z","status":"assured"}` 200
}
'';
};
};
···
+
{ config, pkgs, ... }:
let
pdsSettings = config.services.bluesky-pds.settings;
in
···
enable = true;
environmentFiles = [ config.sops.secrets.pds.path ];
settings = {
+
# https://github.com/bluesky-social/atproto/blob/main/packages/pds/src/config/env.ts
+
PDS_PORT = 3000;
PDS_HOSTNAME = "pds.starhaven.dev";
PDS_ADMIN_EMAIL = "admin@starhaven.dev";
+
PDS_CONTACT_EMAIL_ADDRESS = "admin@starhaven.dev";
+
PDS_SERVICE_HANDLE_DOMAINS = ".starhaven.dev";
+
+
# Branding
+
PDS_SERVICE_NAME = "\"Star Haven\"";
+
PDS_HOME_URL = "https://starhaven.dev";
+
#PDS_LOGO_URL
+
PDS_PRIMARY_COLOR = "#dbb23e";
+
PDS_PRIMARY_COLOR_CONTRAST = "#000";
+
+
# S3 is configured in secrets
+
PDS_BLOBSTORE_DISK_LOCATION = null;
};
};
services.caddy = {
enable = true;
+
package = pkgs.caddy.withPlugins {
+
plugins = [ "github.com/caddy-dns/cloudflare@v0.2.2" ];
+
hash = "sha256-ea8PC/+SlPRdEVVF/I3c1CBprlVp1nrumKM5cMwJJ3U=";
+
};
email = pdsSettings.PDS_ADMIN_EMAIL;
globalConfig = ''
on_demand_tls {
+
ask http://127.0.0.1:8081
}
+
+
acme_dns cloudflare {env.CLOUDFLARE_API_TOKEN}
'';
+
virtualHosts."*.starhaven.dev" = {
extraConfig = ''
tls {
on_demand
}
+
handle / {
+
redir https://starhaven.dev
+
}
+
+
@knot host ${toString config.services.tangled.knot.server.hostname}
+
handle @knot {
+
reverse_proxy http://${toString config.services.tangled.knot.server.listenAddr}
+
}
+
+
@spindle host ${toString config.services.tangled.spindle.server.hostname}
+
handle @spindle {
+
reverse_proxy http://${toString config.services.tangled.spindle.server.listenAddr}
+
}
handle /xrpc/app.bsky.unspecced.getAgeAssuranceState {
header content-type "application/json"
header access-control-allow-headers "authorization,dpop,atproto-accept-labelers,atproto-proxy"
header access-control-allow-origin "*"
respond `{"lastInitiatedAt":"2025-07-14T14:22:43.912Z","status":"assured"}` 200
+
}
+
+
handle {
+
reverse_proxy http://127.0.0.1:${toString pdsSettings.PDS_PORT}
}
'';
+
};
+
};
+
systemd.services.caddy = {
+
after = [
+
"ondemand-tls-helper.service"
+
"sops-nix.service"
+
];
+
serviceConfig.EnvironmentFile = config.sops.secrets.pds.path;
+
};
+
+
environment.etc."ondemand_tls_helper.py" = {
+
source = ./ondemand_tls_helper.py;
+
mode = "0755";
+
};
+
+
systemd.services.ondemand-tls-helper = {
+
description = "On-demand TLS helper for Caddy (returns 200 for allowed domains or proxies to PDS)";
+
wantedBy = [ "multi-user.target" ];
+
after = [ "network.target" ];
+
+
serviceConfig = {
+
ExecStart = "${pkgs.python3}/bin/python3 /etc/ondemand_tls_helper.py";
+
Environment = "PDS_PORT=${toString pdsSettings.PDS_PORT}";
+
User = "nobody";
+
Restart = "always";
+
RestartSec = 5;
};
};
-14
servers/kuribo/tangled.nix
···
-
{ config, ... }:
let
owner = "did:plc:tjgdahiw3u2djgnigyqeummg";
in
···
inherit owner;
hostname = "spindle.starhaven.dev";
};
-
};
-
};
-
-
services.caddy.virtualHosts = {
-
${config.services.tangled.knot.server.hostname} = {
-
extraConfig = ''
-
reverse_proxy http://${toString config.services.tangled.knot.server.listenAddr}
-
'';
-
};
-
${config.services.tangled.spindle.server.hostname} = {
-
extraConfig = ''
-
reverse_proxy http://${toString config.services.tangled.spindle.server.listenAddr}
-
'';
};
};
}
···
let
owner = "did:plc:tjgdahiw3u2djgnigyqeummg";
in
···
inherit owner;
hostname = "spindle.starhaven.dev";
};
};
};
}