nix-flakes/hosts/server/natsu/services/caddy.nix
2026-07-05 16:02:37 +09:00

234 lines
6.5 KiB
Nix

{ pkgs, ... }:
let
streamPlayer = pkgs.writeTextDir "index.html" ''
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Mizuki Stream</title>
<style>
html,
body {
margin: 0;
min-height: 100%;
background: #0d0f12;
color: #f5f3ee;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
main {
min-height: 100vh;
display: grid;
grid-template-rows: 1fr auto;
gap: 12px;
padding: 12px;
}
video {
width: 100%;
max-height: calc(100vh - 72px);
aspect-ratio: 16 / 9;
align-self: center;
background: #000;
border: 1px solid #2f343c;
border-radius: 8px;
}
footer {
display: flex;
justify-content: space-between;
gap: 12px;
color: #a9acb3;
font-size: 14px;
}
code {
color: #f5f3ee;
}
@media (max-width: 720px) {
footer {
flex-direction: column;
}
}
</style>
</head>
<body>
<main>
<video id="video" controls autoplay playsinline></video>
<footer>
<span id="stream"></span>
<code>rtmp://stream.mizuki.guru:1935/live</code>
</footer>
</main>
<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
<script>
var video = document.getElementById("video");
var label = document.getElementById("stream");
var match = window.location.pathname.match(/^\/play\/([^/]+)\/?$/);
var name = match ? decodeURIComponent(match[1]) : "stream";
var source = "https://stream.mizuki.guru/live/" + name + "/" + name + ".m3u8";
label.textContent = source;
if (video.canPlayType("application/vnd.apple.mpegurl")) {
video.src = source;
video.play();
} else if (Hls.isSupported()) {
var hls = new Hls();
hls.loadSource(source);
hls.attachMedia(video);
hls.on(Hls.Events.MANIFEST_PARSED, function () {
video.play();
});
}
</script>
</body>
</html>
'';
in
{
services.caddy = {
enable = true;
virtualHosts."http://natsu.icn.mizuki.guru".extraConfig = ''
respond "" 200
'';
virtualHosts."https://file.mizuki.guru".extraConfig = ''
reverse_proxy 10.11.8.112 {
header_up Host {host}
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
}
'';
virtualHosts."http://file.mizuki.guru".extraConfig = ''
reverse_proxy 10.11.8.112 {
header_up Host {host}
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
}
'';
virtualHosts."auth.mizuki.guru".extraConfig = ''
reverse_proxy 10.11.8.102:9080
'';
virtualHosts."plutos.dazzle.st".extraConfig = ''
reverse_proxy localhost:13000
'';
virtualHosts."event.dazzle.st".extraConfig = ''
reverse_proxy localhost:9794
'';
virtualHosts."pve.hikari.icn.mizuki.guru".extraConfig = ''
reverse_proxy https://10.11.8.100:8006 {
header_up X-Real-Ip {remote_host}
header_up X-Http-Version {http.request.proto}
transport http {
tls_insecure_skip_verify
}
}
'';
virtualHosts."cockpit.hikari.icn.mizuki.guru".extraConfig = ''
reverse_proxy https://10.11.8.100:9090 {
header_up X-Real-Ip {remote_host}
header_up X-Http-Version {http.request.proto}
transport http {
tls_insecure_skip_verify
}
}
'';
virtualHosts."cache.mizuki.guru".extraConfig = ''
reverse_proxy 127.0.0.1:5000
'';
virtualHosts."paste.mizuki.guru".extraConfig = ''
reverse_proxy 10.11.8.102:11108
'';
virtualHosts."komodo.mizuki.guru".extraConfig = ''
reverse_proxy 10.11.8.102:9120
'';
virtualHosts."ena.mizuki.guru".extraConfig = ''
basic_auth {
imnyang $2a$10$zA83XKt84pcXiwfkuvNmKOpQ5Cw0IP6m/.1AX0ahkVQaPOzrmFlxm
}
reverse_proxy 10.20.31.103:3000
'';
virtualHosts."akiyama.mizuki.guru".extraConfig = ''
rewrite / /index.avif
root * /var/static/akiyama.mizuki.guru
file_server
'';
virtualHosts."chef.mizuki.guru".extraConfig = ''
root * /var/static/chef.mizuki.guru
rewrite / /CyberChef_v10.22.1.html
file_server {
precompressed br gzip
}
header {
Cache-Control "public, max-age=31536000"
}
'';
virtualHosts."blog.imnya.ng".extraConfig = ''
reverse_proxy 127.0.0.1:4321
'';
virtualHosts."stream.mizuki.guru".extraConfig = ''
handle /live/* {
reverse_proxy 127.0.0.1:10023
}
handle /play/* {
root * ${streamPlayer}
rewrite * /index.html
file_server
}
handle / {
root * ${streamPlayer}
file_server
}
handle {
respond "Not Found" 404
}
'';
virtualHosts."netbird.mizuki.guru".extraConfig = ''
# Native gRPC (needs HTTP/2 cleartext to backend)
@grpc header Content-Type application/grpc*
reverse_proxy @grpc h2c://127.0.0.1:8081
# Combined server paths (relay, signal, management, OAuth2)
@backend path /relay* /ws-proxy/* /api/* /oauth2/*
reverse_proxy @backend 127.0.0.1:8081
# Dashboard (everything else)
reverse_proxy /* 127.0.0.1:8080
'';
package = pkgs.caddy.withPlugins {
plugins = [
"github.com/caddy-dns/cloudflare@v0.2.2"
"github.com/aksdb/caddy-cgi/v2@v2.2.6"
"github.com/shift72/caddy-geo-ip@v0.6.0"
"github.com/lolPants/caddy-requestid@v1.1.2"
"github.com/WeidiDeng/caddy-cloudflare-ip@v0.0.0-20231130002422-f53b62aa13cb"
"github.com/caddyserver/ntlm-transport@v0.1.2"
"github.com/ueffel/caddy-brotli@v1.6.0"
"github.com/RussellLuo/caddy-ext/ratelimit@v0.3.0"
"github.com/neodyme-labs/user_agent_parse@v0.0.1"
];
hash = "sha256-YqOyHxPIKreCSkXfJUWTEFi+xOlIIiDqy3bYt7/A6Rg=";
};
};
}