diff --git a/deploy/ansible/roles/caddy/templates/Caddyfile.j2 b/deploy/ansible/roles/caddy/templates/Caddyfile.j2 index 2e40ff4..3b0214d 100644 --- a/deploy/ansible/roles/caddy/templates/Caddyfile.j2 +++ b/deploy/ansible/roles/caddy/templates/Caddyfile.j2 @@ -19,8 +19,8 @@ # Reverse proxy to Go app — retry during restarts for zero-downtime deploys reverse_proxy {{ go_upstream_addr | default("localhost" ~ go_listen_addr) }} { header_up X-Real-IP {remote_host} - lb_try_duration 5s - lb_try_interval 250ms + lb_try_duration 10s + lb_try_interval 100ms fail_duration 10s } } diff --git a/deploy/ansible/roles/wppackages/tasks/main.yml b/deploy/ansible/roles/wppackages/tasks/main.yml index 4591f0b..1856c01 100644 --- a/deploy/ansible/roles/wppackages/tasks/main.yml +++ b/deploy/ansible/roles/wppackages/tasks/main.yml @@ -7,17 +7,13 @@ group: www-data mode: "0640" -- name: Stop and disable legacy wppackages socket unit - service: - name: wppackages.socket - state: stopped - enabled: no - failed_when: false - -- name: Remove legacy wppackages socket unit - file: - path: /etc/systemd/system/wppackages.socket - state: absent +- name: Deploy wppackages socket + template: + src: wppackages.socket.j2 + dest: /etc/systemd/system/wppackages.socket + owner: root + group: root + mode: "0644" notify: Reload systemd - name: Deploy litestream service @@ -166,6 +162,12 @@ state: started enabled: yes +- name: Enable and start wppackages socket + service: + name: wppackages.socket + state: started + enabled: yes + - name: Enable and start wppackages service service: name: wppackages diff --git a/deploy/ansible/roles/wppackages/templates/wppackages.service.j2 b/deploy/ansible/roles/wppackages/templates/wppackages.service.j2 index 8fa8cbf..080ca13 100644 --- a/deploy/ansible/roles/wppackages/templates/wppackages.service.j2 +++ b/deploy/ansible/roles/wppackages/templates/wppackages.service.j2 @@ -1,7 +1,7 @@ [Unit] Description=WP Packages After=network.target litestream.service -Requires=litestream.service +Requires=litestream.service wppackages.socket [Service] Type=simple diff --git a/deploy/ansible/roles/wppackages/templates/wppackages.socket.j2 b/deploy/ansible/roles/wppackages/templates/wppackages.socket.j2 new file mode 100644 index 0000000..aeaa9b6 --- /dev/null +++ b/deploy/ansible/roles/wppackages/templates/wppackages.socket.j2 @@ -0,0 +1,9 @@ +[Unit] +Description=WP Packages Socket + +[Socket] +ListenStream={{ go_listen_addr }} +NoDelay=true + +[Install] +WantedBy=sockets.target diff --git a/internal/http/server.go b/internal/http/server.go index 4e09802..f5f27ff 100644 --- a/internal/http/server.go +++ b/internal/http/server.go @@ -4,9 +4,11 @@ import ( "context" "errors" "fmt" + "net" "net/http" "os" "os/signal" + "strconv" "syscall" "time" @@ -14,6 +16,31 @@ import ( "github.com/roots/wp-packages/internal/app" ) +// systemdListener returns a net.Listener from a socket fd passed by systemd +// socket activation (sd_listen_fds protocol). Returns nil if not running +// under socket activation. +func systemdListener() (net.Listener, error) { + pid, err := strconv.Atoi(os.Getenv("LISTEN_PID")) + if err != nil || pid != os.Getpid() { + return nil, nil + } + nfds, err := strconv.Atoi(os.Getenv("LISTEN_FDS")) + if err != nil || nfds < 1 { + return nil, nil + } + + f := os.NewFile(3, "systemd-socket") + ln, err := net.FileListener(f) + _ = f.Close() + if err != nil { + return nil, fmt.Errorf("creating listener from systemd fd: %w", err) + } + + _ = os.Unsetenv("LISTEN_PID") + _ = os.Unsetenv("LISTEN_FDS") + return ln, nil +} + func ListenAndServe(a *app.App) error { router := NewRouter(a) @@ -29,13 +56,29 @@ func ListenAndServe(a *app.App) error { } errCh := make(chan error, 1) - go func() { + + ln, err := systemdListener() + if err != nil { + return err + } + + if ln != nil { + a.Logger.Info("using systemd socket activation") + go func() { + if err := srv.Serve(ln); err != nil && !errors.Is(err, http.ErrServerClosed) { + errCh <- fmt.Errorf("server error: %w", err) + } + close(errCh) + }() + } else { a.Logger.Info("starting server", "addr", a.Config.Server.Addr) - if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { - errCh <- fmt.Errorf("server error: %w", err) - } - close(errCh) - }() + go func() { + if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { + errCh <- fmt.Errorf("server error: %w", err) + } + close(errCh) + }() + } sigCtx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) defer stop()