diff options
| author | Alexander Hunt <me@liz.coffee> | 2026-06-21 17:11:32 -0700 |
|---|---|---|
| committer | Alexander Hunt <me@liz.coffee> | 2026-06-21 17:11:32 -0700 |
| commit | 6f3ba344c1f8413a463dc6274f24ef00bf47e534 (patch) | |
| tree | 2e260c52f1ecf89ece4fad131a91cde7b3500921 | |
| parent | df7b13a248853880e50743757caedcfbc2b7eb6d (diff) | |
| download | penguins.lan-6f3ba344c1f8413a463dc6274f24ef00bf47e534.tar.gz penguins.lan-6f3ba344c1f8413a463dc6274f24ef00bf47e534.zip | |
| -rw-r--r-- | .gitignore | 3 | ||||
| -rw-r--r-- | args/args.go | 7 | ||||
| -rw-r--r-- | conf.d/penguins | 16 | ||||
| -rw-r--r-- | init.d/penguins | 45 | ||||
| -rw-r--r-- | main.go | 41 |
5 files changed, 105 insertions, 7 deletions
@@ -1,6 +1,7 @@ .env .DS_Store -penguins +/penguins +/build/ *.db /db/*.db .claude diff --git a/args/args.go b/args/args.go index 6c79fe9..a488929 100644 --- a/args/args.go +++ b/args/args.go @@ -17,6 +17,11 @@ type Arguments struct { Port int Server bool + // Socket, when set, makes the HTTP server listen on this unix socket path + // instead of the TCP Port — handy behind a reverse proxy. Port is ignored + // when Socket is set. + Socket string + // Dns runs the authoritative DNS server for the LAN. DnsZone is the suffix // it owns (e.g. penguins.lan); queries outside it are forwarded to // DnsResolvers (empty = no forwarding, since the LAN has no outside @@ -51,6 +56,7 @@ func GetArgs() (*Arguments, error) { port := flag.Int("port", 8080, "Port to listen on") server := flag.Bool("server", false, "Run the HTTP server") + socket := flag.String("socket", "", "Serve HTTP on this unix socket instead of the TCP port") dns := flag.Bool("dns", false, "Run the authoritative DNS server") dnsPort := flag.Int("dns-port", 8053, "Port for the DNS server to listen on") @@ -74,6 +80,7 @@ func GetArgs() (*Arguments, error) { Scheduler: *scheduler, Port: *port, Server: *server, + Socket: *socket, Dns: *dns, DnsPort: *dnsPort, DnsResolvers: resolvers, diff --git a/conf.d/penguins b/conf.d/penguins new file mode 100644 index 0000000..b6c95e2 --- /dev/null +++ b/conf.d/penguins @@ -0,0 +1,16 @@ +# Configuration for the penguins.lan OpenRC service (/etc/init.d/penguins). + +# User/group the daemon runs as. +penguins_user="penguins" +penguins_group="penguins" + +# Deploy tree. Must contain the binary plus .env, db/, templates/ and static/. +penguins_dir="/opt/penguins.lan" + +# Path to the compiled binary (defaults to ${penguins_dir}/penguins). +#penguins_bin="/opt/penguins.lan/penguins" + +# Subcommand flags. Add e.g. -dns-port 53 to serve real DNS, or -port 80 for +# HTTP on the privileged port (both rely on cap_net_bind_service). To sit behind +# a reverse proxy, swap the TCP port for a unix socket: -socket /run/penguins.sock +penguins_flags="-server -scheduler -dns -dns-port 53 -port 9000 -migrate" diff --git a/init.d/penguins b/init.d/penguins new file mode 100644 index 0000000..03835a3 --- /dev/null +++ b/init.d/penguins @@ -0,0 +1,45 @@ +#!/sbin/openrc-run + +# OpenRC service for penguins.lan. +# +# Install: +# install -Dm755 init.d/penguins /etc/init.d/penguins +# install -Dm644 conf.d/penguins /etc/conf.d/penguins +# rc-update add penguins default +# rc-service penguins start +# +# The binary resolves .env, ./db, ./templates and ./static relative to its +# working directory, so directory= must point at the deploy tree. + +name="penguins.lan" +description="penguins.lan portal, scheduler and DNS server" + +: "${penguins_user:=penguins}" +: "${penguins_group:=penguins}" +: "${penguins_dir:=/opt/penguins.lan}" +: "${penguins_bin:=${penguins_dir}/penguins}" +: "${penguins_flags:=-server -scheduler -dns -migrate}" + +command="${penguins_bin}" +command_args="${penguins_flags}" +command_user="${penguins_user}:${penguins_group}" +directory="${penguins_dir}" + +supervisor="supervise-daemon" +respawn_delay=5 +respawn_max=0 +output_log="/var/log/penguins.log" +error_log="/var/log/penguins.log" + +# Binding DNS on port 53 (and HTTP on 80) needs this capability when not root. +capabilities="^cap_net_bind_service" + +depend() { + need net + after firewall +} + +start_pre() { + checkpath --directory --owner "${penguins_user}:${penguins_group}" --mode 0750 "${penguins_dir}/db" + checkpath --file --owner "${penguins_user}:${penguins_group}" --mode 0640 "${output_log}" +} @@ -2,6 +2,8 @@ package main import ( "log" + "net" + "os" "github.com/joho/godotenv" @@ -41,12 +43,22 @@ func main() { if argv.Server { server := api.MakeServer(argv, dbConn) - log.Printf("listening on :%d", argv.Port) - go func() { - if err := server.ListenAndServe(); err != nil { - log.Fatal(err) - } - }() + if argv.Socket != "" { + listener := unixListener(argv.Socket) + log.Printf("listening on unix socket %s", argv.Socket) + go func() { + if err := server.Serve(listener); err != nil { + log.Fatal(err) + } + }() + } else { + log.Printf("listening on :%d", argv.Port) + go func() { + if err := server.ListenAndServe(); err != nil { + log.Fatal(err) + } + }() + } } if argv.Dns { @@ -63,3 +75,20 @@ func main() { select {} } } + +// unixListener binds an HTTP unix socket, clearing any stale socket left by a +// previous run and relaxing perms to 0660 so a same-group reverse proxy can +// reach it. +func unixListener(path string) net.Listener { + if err := os.Remove(path); err != nil && !os.IsNotExist(err) { + log.Fatalf("could not clear stale socket %s: %v", path, err) + } + listener, err := net.Listen("unix", path) + if err != nil { + log.Fatal(err) + } + if err := os.Chmod(path, 0o660); err != nil { + log.Fatalf("could not chmod socket %s: %v", path, err) + } + return listener +} |
