// Package nginx regenerates one vhost file per app and reloads. Skiff never // touches nginx.conf itself - that just needs one `include skiff.d/*.conf;` // line added once, by hand, when nginx is first set up. package nginx import ( "context" "fmt" "io" "os" "skiff/internal/appmeta" "skiff/internal/layout" "skiff/internal/sh" "skiff/internal/tmpl" ) // EnsureRoute (re)writes app's vhost file from meta and reloads nginx, but // only if the rendered config changed. A hostname gets HTTPS only once it's // in both meta.Domains and meta.TLSDomains. No domains yet is a no-op. func EnsureRoute(ctx context.Context, out io.Writer, cfg layout.Config, app string, meta appmeta.Meta) error { if len(meta.Domains) == 0 { fmt.Fprintf(out, "==> no domains configured for %s, skipping nginx route\n", app) return nil } primary, ok := meta.PrimaryDomain() if !ok { return fmt.Errorf("%s has domains but none marked primary", app) } live := make(map[string]bool, len(meta.TLSDomains)) for _, h := range meta.TLSDomains { live[h] = true } doms := make([]tmpl.NginxDomain, len(meta.Domains)) for i, d := range meta.Domains { doms[i] = tmpl.NginxDomain{Hostname: d.Hostname, Primary: d.Primary, CertLive: live[d.Hostname]} } rendered, err := tmpl.NginxApp(tmpl.NginxAppData{ Name: app, IP: meta.IP, Port: meta.Port, Webroot: cfg.AcmeWebrootDir, PrimaryHostname: primary.Hostname, PrimaryCertLive: live[primary.Hostname], PrimaryLiveDir: cfg.LetsEncryptLiveDir(primary.Hostname), Domains: doms, }) if err != nil { return err } path := cfg.NginxConfPath(app) existing, _ := os.ReadFile(path) if string(existing) == rendered { return nil } if err := os.WriteFile(path, []byte(rendered), 0644); err != nil { return fmt.Errorf("write nginx conf for %s: %w", app, err) } fmt.Fprintf(out, "==> nginx route changed for %s, reloading\n", app) return Reload(ctx, out) } // Remove deletes app's vhost file entirely and reloads - used once its last // domain is removed, since there's nothing left to route. func Remove(ctx context.Context, out io.Writer, cfg layout.Config, app string) error { path := cfg.NginxConfPath(app) if err := os.Remove(path); err != nil { if os.IsNotExist(err) { return nil } return fmt.Errorf("remove nginx conf for %s: %w", app, err) } fmt.Fprintf(out, "==> nginx route removed for %s, reloading\n", app) return Reload(ctx, out) } func Reload(ctx context.Context, out io.Writer) error { return sh.Run(ctx, out, "", "nginx", "-s", "reload") }