convert caddy to nixos builtin

This commit is contained in:
nora 2025-08-04 21:18:59 +02:00
parent 71b4ca1448
commit 33a7017375
18 changed files with 326 additions and 263 deletions

View file

@ -6,6 +6,16 @@ let
}); });
in in
{ {
services.caddy.virtualHosts = {
"does-it-build.noratrieb.dev" = {
logFormat = "";
extraConfig = ''
encode zstd gzip
reverse_proxy * localhost:3000
'';
};
};
systemd.services.does-it-build = { systemd.services.does-it-build = {
description = "https://github.com/Noratrieb/does-it-build"; description = "https://github.com/Noratrieb/does-it-build";
wantedBy = [ "multi-user.target" ]; wantedBy = [ "multi-user.target" ];

View file

@ -43,6 +43,14 @@
}; };
}; };
services.caddy.virtualHosts."git.noratrieb.dev" = {
logFormat = "";
extraConfig = ''
encode zstd gzip
reverse_proxy * localhost:5015
'';
};
services.custom-backup.jobs = [{ services.custom-backup.jobs = [{
app = "forgejo"; app = "forgejo";
file = "/var/lib/forgejo/data/forgejo.db"; file = "/var/lib/forgejo/data/forgejo.db";

View file

@ -5,6 +5,11 @@ let
"https://github.com/C0RR1T/HugoChat/releases/download/2024-08-05/HugoServer.jar"; "https://github.com/C0RR1T/HugoChat/releases/download/2024-08-05/HugoServer.jar";
hash = "sha256-hCe2UPqrSR6u3/UxsURI2KzRxN5saeTteCRq5Zfay4M="; hash = "sha256-hCe2UPqrSR6u3/UxsURI2KzRxN5saeTteCRq5Zfay4M=";
}; };
hugo-chat-client = fetchTarball {
url =
"https://github.com/C0RR1T/HugoChat/releases/download/2024-08-05/hugo-client.tar.xz";
sha256 = "sha256:121ai8q6bm7gp0pl1ajfk0k2nrfg05zid61i20z0j5gpb2qyhsib";
};
in in
{ {
age.secrets.hugochat_db_password.file = ../../secrets/hugochat_db_password.age; age.secrets.hugochat_db_password.file = ../../secrets/hugochat_db_password.age;
@ -36,6 +41,61 @@ in
}; };
}; };
services.caddy.virtualHosts = {
"hugo-chat.noratrieb.dev" = {
logFormat = "";
extraConfig = ''
encode zstd gzip
root * ${import ../../packages/caddy-static-prepare {
name = "hugo-chat-client";
src = hugo-chat-client;
inherit pkgs lib;
}}
try_files {path} /index.html
file_server {
etag_file_extensions .sha256
precompressed zstd gzip br
}
'';
};
"api.hugo-chat.noratrieb.dev" =
let
cors = pkgs.writeText "cors" ''
# https://gist.github.com/ryanburnette/d13575c9ced201e73f8169d3a793c1a3
@cors_preflight{args[0]} method OPTIONS
@cors{args[0]} header Origin {args[0]}
handle @cors_preflight{args[0]} {
header {
Access-Control-Allow-Origin "{args[0]}"
Access-Control-Allow-Methods "GET, POST, PUT, PATCH, DELETE, OPTIONS"
Access-Control-Allow-Credentials "false"
Access-Control-Allow-Headers "$${args[1]}"
Access-Control-Max-Age "86400"
defer
}
respond "" 204
}
handle @cors{args[0]} {
header {
Access-Control-Allow-Origin "{args[0]}"
Access-Control-Expose-Headers *
defer
}
}
'';
in
{
logFormat = "";
extraConfig = ''
import ${cors} https://hugo-chat.noratrieb.dev "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type"
encode zstd gzip
reverse_proxy * localhost:5001
'';
};
};
services.custom-backup.jobs = [ services.custom-backup.jobs = [
{ {
app = "hugo-chat"; app = "hugo-chat";

View file

@ -0,0 +1,41 @@
{ ... }:
let
permanent = [
{ from = "www.noratrieb.dev"; to = "noratrieb.dev"; }
{ from = "blog.noratrieb.dev"; to = "noratrieb.dev/blog"; }
{ from = "nilstrieb.dev"; to = "noratrieb.dev"; }
{ from = "www.nilstrieb.dev"; to = "noratrieb.dev"; }
{ from = "blog.nilstrieb.dev"; to = "noratrieb.dev/blog"; }
{ from = "bisect-rustc.nilstrieb.dev"; to = "bisect-rustc.noratrieb.dev"; }
{ from = "docker.nilstrieb.dev"; to = "docker.noratrieb.dev"; }
{ from = "hugo-chat.nilstrieb.dev"; to = "hugo-chat.noratrieb.dev"; }
{ from = "api.hugo-chat.nilstrieb.dev"; to = "api.hugo-chat.noratrieb.dev"; }
{ from = "uptime.nilstrieb.dev"; to = "uptime.noratrieb.dev"; }
{ from = "olat.nilstrieb.dev"; to = "olat.noratrieb.dev"; }
{ from = "olat.nilstrieb.dev:8088"; to = "olat.noratrieb.dev"; }
];
in
{
services.caddy.virtualHosts = (
{
"bisect-rustc.noratrieb.dev" = {
logFormat = "";
extraConfig = "redir https://github.com/Noratrieb/cargo-bisect-rustc-service?tab=readme-ov-file#cargo-bisect-rustc-service";
};
"uptime.noratrieb.dev" = {
logFormat = "";
extraConfig = "redir https://github.com/Noratrieb/uptime?tab=readme-ov-file#uptime";
};
}
) // (
builtins.listToAttrs (map
(redirect: {
name = redirect.from;
value = {
logFormat = "";
extraConfig = "redir https://${redirect.to}{uri} permanent";
};
})
permanent)
);
}

View file

@ -44,6 +44,24 @@ in
}; };
}; };
services.caddy.virtualHosts = {
"olat.noratrieb.dev" = {
logFormat = "";
extraConfig = ''
encode zstd gzip
reverse_proxy * localhost:5011
'';
};
# unsure if necessary... something was misconfigured in the past here...
"olat.noratrieb.dev:8088" = {
logFormat = "";
extraConfig = ''
encode zstd gzip
reverse_proxy * localhost:5011
'';
};
};
services.custom-backup.jobs = [ services.custom-backup.jobs = [
{ {
app = "openolat-db"; app = "openolat-db";

View file

@ -18,4 +18,15 @@ let upload-files = import (fetchTarball "https://github.com/Noratrieb/upload.fil
EnvironmentFile = [ config.age.secrets.upload_files_s3_secret.path ]; EnvironmentFile = [ config.age.secrets.upload_files_s3_secret.path ];
}; };
}; };
services.caddy.virtualHosts."upload.files.noratrieb.dev" = {
logFormat = "";
extraConfig = ''
encode zstd gzip
# we need HTTP/2 here because the server doesn't work with HTTP/1.1
# because it will send early 401 responses during the upload without consuming the body
# (this has been mostly fixed but still keep it)
reverse_proxy * h2c://localhost:3050
'';
};
} }

View file

@ -0,0 +1,34 @@
{ pkgs, lib, my-projects-versions, ... }:
let
website = import (fetchTarball "https://github.com/Noratrieb/website/archive/${my-projects-versions.website}.tar.gz");
blog = fetchTarball "https://github.com/Noratrieb/blog/archive/${my-projects-versions.blog}.tar.gz";
slides = fetchTarball "https://github.com/Noratrieb/slides/archive/${my-projects-versions.slides}.tar.gz";
website-build = website { inherit pkgs slides blog; };
in
{
services.caddy.virtualHosts = {
"noratrieb.dev" = {
logFormat = "";
extraConfig = ''
encode zstd gzip
header -Last-Modified
root * ${import ../../packages/caddy-static-prepare {
name = "website";
src = website-build;
inherit pkgs lib;
}}
file_server {
etag_file_extensions .sha256
precompressed zstd gzip br
}
'';
};
"files.noratrieb.dev" = {
logFormat = "";
extraConfig = ''
encode zstd gzip
reverse_proxy * localhost:3902
'';
};
};
}

View file

@ -180,6 +180,8 @@
./modules/backup ./modules/backup
# apps # apps
./apps/website
./apps/old-redirects
./apps/widetom ./apps/widetom
./apps/hugo-chat ./apps/hugo-chat
./apps/killua ./apps/killua
@ -211,6 +213,8 @@
./modules/caddy ./modules/caddy
./modules/garage ./modules/garage
./modules/prometheus ./modules/prometheus
./apps/website
]; ];
system.stateVersion = "23.11"; system.stateVersion = "23.11";
@ -225,6 +229,7 @@
./modules/backup ./modules/backup
# apps # apps
./apps/website
./apps/does-it-build ./apps/does-it-build
]; ];

View file

@ -1,59 +0,0 @@
{
email noratrieb@proton.me
auto_https disable_redirects
storage s3 {
host "localhost:3900"
bucket "caddy-store"
# access_id ENV S3_ACCESS_ID
# secret_key ENV S3_SECRET_KEY
insecure true
}
servers {
metrics
}
log default {
output stdout
format json
}
}
# https://gist.github.com/ryanburnette/d13575c9ced201e73f8169d3a793c1a3
(cors) {
@cors_preflight{args[0]} method OPTIONS
@cors{args[0]} header Origin {args[0]}
handle @cors_preflight{args[0]} {
header {
Access-Control-Allow-Origin "{args[0]}"
Access-Control-Allow-Methods "GET, POST, PUT, PATCH, DELETE, OPTIONS"
Access-Control-Allow-Credentials "false"
Access-Control-Allow-Headers "${args[1]}"
Access-Control-Max-Age "86400"
defer
}
respond "" 204
}
handle @cors{args[0]} {
header {
Access-Control-Allow-Origin "{args[0]}"
Access-Control-Expose-Headers *
defer
}
}
}
http:// {
log
respond "This is an HTTPS-only server, silly you. Go to https:// instead." 418
}
# HTTP
:9010 {
log
metrics /metrics
}

View file

@ -1,4 +1,4 @@
{ pkgs, config, lib, name, my-projects-versions, ... }: { pkgs, config, lib, name, ... }:
let let
caddy = pkgs.callPackage ./caddy-build.nix { caddy = pkgs.callPackage ./caddy-build.nix {
@ -11,15 +11,6 @@ let
]; ];
vendorHash = "sha256-KP9bYitM/Pocw4DxOXPVBigWh4IykNf8yKJiBlTFZmI="; vendorHash = "sha256-KP9bYitM/Pocw4DxOXPVBigWh4IykNf8yKJiBlTFZmI=";
}; };
website = import (fetchTarball "https://github.com/Noratrieb/website/archive/${my-projects-versions.website}.tar.gz");
blog = fetchTarball "https://github.com/Noratrieb/blog/archive/${my-projects-versions.blog}.tar.gz";
slides = fetchTarball "https://github.com/Noratrieb/slides/archive/${my-projects-versions.slides}.tar.gz";
website-build = website { inherit pkgs slides blog; };
hugo-chat-client = fetchTarball {
url =
"https://github.com/C0RR1T/HugoChat/releases/download/2024-08-05/hugo-client.tar.xz";
sha256 = "sha256:121ai8q6bm7gp0pl1ajfk0k2nrfg05zid61i20z0j5gpb2qyhsib";
};
in in
{ {
environment.systemPackages = [ caddy ]; environment.systemPackages = [ caddy ];
@ -43,79 +34,56 @@ in
services.caddy = { services.caddy = {
enable = true; enable = true;
package = caddy; package = caddy;
configFile = pkgs.writeTextFile { logFormat = ''
name = "Caddyfile"; output stdout
text = ( format json
builtins.readFile ./base.Caddyfile + '';
'' globalConfig = ''
${config.networking.hostName}.infra.noratrieb.dev { email noratrieb@proton.me
log auto_https disable_redirects
encode zstd gzip
header -Last-Modified storage s3 {
root * ${import ./caddy-static-prepare { host "localhost:3900"
name = "debugging-page"; bucket "caddy-store"
src = ./debugging-page; # access_id ENV S3_ACCESS_ID
inherit pkgs lib; # secret_key ENV S3_SECRET_KEY
}}
file_server { insecure true
etag_file_extensions .sha256 }
precompressed zstd gzip br
} servers {
metrics
}
'';
virtualHosts = {
"http://" = {
logFormat = "";
extraConfig = ''
respond "This is an HTTPS-only server, silly you. Go to https:// instead." 418
'';
};
":9010" = {
logFormat = "output discard";
extraConfig = ''
metrics /metrics
'';
};
"${name}.infra.noratrieb.dev" = {
logFormat = "";
extraConfig = ''
encode zstd gzip
header -Last-Modified
root * ${import ./caddy-static-prepare {
name = "debugging-page";
src = ./debugging-page;
inherit pkgs lib;
}}
file_server {
etag_file_extensions .sha256
precompressed zstd gzip br
} }
'';
${ };
if name == "vps1" || name == "vps3" || name == "vps4" then ''
noratrieb.dev {
log
encode zstd gzip
header -Last-Modified
root * ${import ./caddy-static-prepare {
name = "website";
src = website-build;
inherit pkgs lib;
}}
file_server {
etag_file_extensions .sha256
precompressed zstd gzip br
}
}
files.noratrieb.dev {
log
encode zstd gzip
reverse_proxy * localhost:3902
}
'' else ""
}
${if name == "vps1" then ''
hugo-chat.noratrieb.dev {
log
encode zstd gzip
root * ${import ./caddy-static-prepare {
name = "hugo-chat-client";
src = hugo-chat-client;
inherit pkgs lib;
}}
try_files {path} /index.html
file_server {
etag_file_extensions .sha256
precompressed zstd gzip br
}
}
'' else ""}
${
if name == "vps1" || name == "vps3" || name == "vps4" then
builtins.readFile ./${name}.Caddyfile else ""
}
''
);
checkPhase = ''
${lib.getExe caddy} --version
${lib.getExe caddy} validate --adapter=caddyfile --config=$out
'';
}; };
}; };
} }

View file

@ -1,111 +0,0 @@
www.noratrieb.dev {
log
redir https://noratrieb.dev{uri} permanent
}
api.hugo-chat.noratrieb.dev {
log
import cors https://hugo-chat.noratrieb.dev "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type"
encode zstd gzip
reverse_proxy * localhost:5001
}
docker.noratrieb.dev {
log
reverse_proxy * localhost:5000
}
git.noratrieb.dev {
log
encode zstd gzip
reverse_proxy * localhost:5015
}
olat.noratrieb.dev {
log
encode zstd gzip
reverse_proxy * localhost:5011
}
# unsure if necessary... something was misconfigured in the past here...
olat.noratrieb.dev:8088 {
log
encode zstd gzip
reverse_proxy * localhost:5011
}
upload.files.noratrieb.dev {
log
encode zstd gzip
# we need HTTP/2 here because the server doesn't work with HTTP/1.1
# because it will send early 401 responses during the upload without consuming the body
reverse_proxy * h2c://localhost:3050
}
################################################################
# retired
bisect-rustc.noratrieb.dev {
log
redir https://github.com/Noratrieb/cargo-bisect-rustc-service?tab=readme-ov-file#cargo-bisect-rustc-service
}
uptime.noratrieb.dev {
log
redir https://github.com/Noratrieb/uptime?tab=readme-ov-file#uptime
}
blog.noratrieb.dev {
log
redir https://noratrieb.dev/blog{uri} permanent
}
nilstrieb.dev {
log
redir https://noratrieb.dev{uri} permanent
}
www.nilstrieb.dev {
log
redir https://noratrieb.dev{uri} permanent
}
blog.nilstrieb.dev {
log
redir https://noratrieb.dev/blog{uri} permanent
}
bisect-rustc.nilstrieb.dev {
log
redir https://bisect-rustc.noratrieb.dev/blog{uri} permanent
}
docker.nilstrieb.dev {
log
redir https://docker.noratrieb.dev{uri} permanent
}
hugo-chat.nilstrieb.dev {
log
redir https://hugo-chat.noratrieb.dev{uri} permanent
}
api.hugo-chat.nilstrieb.dev {
log
redir https://api.hugo-chat.noratrieb.dev{uri} permanent
}
uptime.nilstrieb.dev {
log
redir https://uptime.noratrieb.dev{uri} permanent
}
olat.nilstrieb.dev {
log
redir https://olat.noratrieb.dev{uri} permanent
}
olat.nilstrieb.dev:8088 {
log
redir https://olat.noratrieb.dev{uri} permanent
}

View file

@ -1,5 +0,0 @@
grafana.noratrieb.dev {
log
encode zstd gzip
reverse_proxy * localhost:3000
}

View file

@ -1,5 +0,0 @@
does-it-build.noratrieb.dev {
log
encode zstd gzip
reverse_proxy * localhost:3000
}

View file

@ -94,6 +94,14 @@
}; };
}; };
services.caddy.virtualHosts."grafana.noratrieb.dev" = {
logFormat = "";
extraConfig = ''
encode zstd gzip
reverse_proxy * localhost:3000
'';
};
networking.firewall.interfaces.wg0.allowedTCPPorts = [ networking.firewall.interfaces.wg0.allowedTCPPorts = [
config.services.loki.configuration.server.http_listen_port config.services.loki.configuration.server.http_listen_port
4040 # pyroscope 4040 # pyroscope

View file

@ -60,4 +60,11 @@
}; };
}; };
}; };
services.caddy.virtualHosts."docker.noratrieb.dev" = {
logFormat = "";
extraConfig = ''
reverse_proxy * localhost:5000
'';
};
} }

View file

@ -5,7 +5,7 @@
"pretense": "270b01fc1118dfd713c1c41530d1a7d98f04527d", "pretense": "270b01fc1118dfd713c1c41530d1a7d98f04527d",
"quotdd": "e922229e1d9e055be35dabd112bafc87a0686548", "quotdd": "e922229e1d9e055be35dabd112bafc87a0686548",
"does-it-build": "81790825173d87f89656f66f12a123bc99e2f6f1", "does-it-build": "81790825173d87f89656f66f12a123bc99e2f6f1",
"upload.files.noratrieb.dev": "0124fa5ba5446cb463fb6b3c4f52e7e6b84e5077", "upload.files.noratrieb.dev": "9f31fe53f040f73edbbdc8afcc9bd3cdbc1cd8ab",
"cluelessh": "c711cd405da4b7951e554577d09c9576bedf7970", "cluelessh": "c711cd405da4b7951e554577d09c9576bedf7970",
"widetom": "33d1738799618d72fe2b86896f766cbfea58dc76" "widetom": "33d1738799618d72fe2b86896f766cbfea58dc76"
} }

View file

@ -0,0 +1,13 @@
{ pkgs, lib, name, src ? null, ... }: pkgs.stdenv.mkDerivation {
inherit name src;
buildInputs = with pkgs; [ python311 python311Packages.zstandard python311Packages.brotli ];
buildPhase = ''
mkdir -p $out
cp -r $src/* $out/
chmod -R +w $out
${lib.getExe pkgs.python311} ${./prepare.py} $out
chmod -R -w $out
'';
}

View file

@ -0,0 +1,60 @@
import os
import sys
import gzip
import brotli
import zstandard
import hashlib
def usage():
print("usage: prepare.py [SRC]")
def write_etag(path, content):
shasum = hashlib.sha256(content)
etag_path = path+".sha256"
with open(etag_path, "w") as f:
print(f"Writing ETag {etag_path}")
f.write(f'"{shasum.hexdigest()}"')
def main():
if len(sys.argv) < 2:
usage()
exit(1)
src_dir = sys.argv[1]
for root, dirs, files in os.walk(src_dir):
for file in files:
path = os.path.join(root, file)
# Ignore etags
if path.endswith(".sha256") or path.endswith(".b3sum"):
continue
# Ignore already compressed files
if path.endswith(".gz") or path.endswith(".zst") or path.endswith(".br"):
continue
with open(path, "rb") as f:
content = f.read()
compressions = [
(".gz", gzip),
(".zst", zstandard),
(".br", brotli),
]
for ext, alg in compressions:
new_path = path+ext
with open(new_path, "wb") as out:
print(f"Writing {new_path}")
compressed = alg.compress(content)
out.write(compressed)
write_etag(new_path, compressed)
write_etag(path, content)
if __name__ == "__main__":
main()