202 lines
4.9 KiB
Go
202 lines
4.9 KiB
Go
|
|
package nginx
|
||
|
|
|
||
|
|
import (
|
||
|
|
"fmt"
|
||
|
|
"os"
|
||
|
|
"os/exec"
|
||
|
|
"path/filepath"
|
||
|
|
"strings"
|
||
|
|
"text/template"
|
||
|
|
|
||
|
|
"setec-manager/internal/config"
|
||
|
|
"setec-manager/internal/db"
|
||
|
|
)
|
||
|
|
|
||
|
|
const reverseProxyTemplate = `# Managed by Setec App Manager — do not edit manually
|
||
|
|
server {
|
||
|
|
listen 80;
|
||
|
|
server_name {{.Domain}}{{if .Aliases}} {{.Aliases}}{{end}};
|
||
|
|
|
||
|
|
location /.well-known/acme-challenge/ {
|
||
|
|
root {{.CertbotWebroot}};
|
||
|
|
}
|
||
|
|
|
||
|
|
location / {
|
||
|
|
return 301 https://$host$request_uri;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
{{if .SSLEnabled}}server {
|
||
|
|
listen 443 ssl http2;
|
||
|
|
server_name {{.Domain}}{{if .Aliases}} {{.Aliases}}{{end}};
|
||
|
|
|
||
|
|
ssl_certificate {{.SSLCertPath}};
|
||
|
|
ssl_certificate_key {{.SSLKeyPath}};
|
||
|
|
include snippets/ssl-params.conf;
|
||
|
|
|
||
|
|
location / {
|
||
|
|
proxy_pass http://127.0.0.1:{{.AppPort}};
|
||
|
|
include snippets/proxy-params.conf;
|
||
|
|
}
|
||
|
|
|
||
|
|
# WebSocket / SSE support
|
||
|
|
location /api/ {
|
||
|
|
proxy_pass http://127.0.0.1:{{.AppPort}};
|
||
|
|
proxy_http_version 1.1;
|
||
|
|
proxy_set_header Upgrade $http_upgrade;
|
||
|
|
proxy_set_header Connection "upgrade";
|
||
|
|
proxy_read_timeout 86400;
|
||
|
|
include snippets/proxy-params.conf;
|
||
|
|
}
|
||
|
|
}{{end}}
|
||
|
|
`
|
||
|
|
|
||
|
|
const staticSiteTemplate = `# Managed by Setec App Manager — do not edit manually
|
||
|
|
server {
|
||
|
|
listen 80;
|
||
|
|
server_name {{.Domain}}{{if .Aliases}} {{.Aliases}}{{end}};
|
||
|
|
|
||
|
|
location /.well-known/acme-challenge/ {
|
||
|
|
root {{.CertbotWebroot}};
|
||
|
|
}
|
||
|
|
|
||
|
|
location / {
|
||
|
|
return 301 https://$host$request_uri;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
{{if .SSLEnabled}}server {
|
||
|
|
listen 443 ssl http2;
|
||
|
|
server_name {{.Domain}}{{if .Aliases}} {{.Aliases}}{{end}};
|
||
|
|
root {{.AppRoot}};
|
||
|
|
index index.html;
|
||
|
|
|
||
|
|
ssl_certificate {{.SSLCertPath}};
|
||
|
|
ssl_certificate_key {{.SSLKeyPath}};
|
||
|
|
include snippets/ssl-params.conf;
|
||
|
|
|
||
|
|
location / {
|
||
|
|
try_files $uri $uri/ =404;
|
||
|
|
}
|
||
|
|
}{{else}}server {
|
||
|
|
listen 80;
|
||
|
|
server_name {{.Domain}}{{if .Aliases}} {{.Aliases}}{{end}};
|
||
|
|
root {{.AppRoot}};
|
||
|
|
index index.html;
|
||
|
|
|
||
|
|
location / {
|
||
|
|
try_files $uri $uri/ =404;
|
||
|
|
}
|
||
|
|
}{{end}}
|
||
|
|
`
|
||
|
|
|
||
|
|
type configData struct {
|
||
|
|
Domain string
|
||
|
|
Aliases string
|
||
|
|
AppRoot string
|
||
|
|
AppPort int
|
||
|
|
SSLEnabled bool
|
||
|
|
SSLCertPath string
|
||
|
|
SSLKeyPath string
|
||
|
|
CertbotWebroot string
|
||
|
|
}
|
||
|
|
|
||
|
|
func GenerateConfig(cfg *config.Config, site *db.Site) error {
|
||
|
|
data := configData{
|
||
|
|
Domain: site.Domain,
|
||
|
|
Aliases: site.Aliases,
|
||
|
|
AppRoot: site.AppRoot,
|
||
|
|
AppPort: site.AppPort,
|
||
|
|
SSLEnabled: site.SSLEnabled,
|
||
|
|
SSLCertPath: site.SSLCertPath,
|
||
|
|
SSLKeyPath: site.SSLKeyPath,
|
||
|
|
CertbotWebroot: cfg.Nginx.CertbotWebroot,
|
||
|
|
}
|
||
|
|
|
||
|
|
var tmplStr string
|
||
|
|
switch site.AppType {
|
||
|
|
case "static":
|
||
|
|
tmplStr = staticSiteTemplate
|
||
|
|
default:
|
||
|
|
tmplStr = reverseProxyTemplate
|
||
|
|
}
|
||
|
|
|
||
|
|
tmpl, err := template.New("nginx").Parse(tmplStr)
|
||
|
|
if err != nil {
|
||
|
|
return fmt.Errorf("parse template: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
path := filepath.Join(cfg.Nginx.SitesAvailable, site.Domain)
|
||
|
|
f, err := os.Create(path)
|
||
|
|
if err != nil {
|
||
|
|
return fmt.Errorf("create config: %w", err)
|
||
|
|
}
|
||
|
|
defer f.Close()
|
||
|
|
|
||
|
|
return tmpl.Execute(f, data)
|
||
|
|
}
|
||
|
|
|
||
|
|
func EnableSite(cfg *config.Config, domain string) error {
|
||
|
|
src := filepath.Join(cfg.Nginx.SitesAvailable, domain)
|
||
|
|
dst := filepath.Join(cfg.Nginx.SitesEnabled, domain)
|
||
|
|
|
||
|
|
// Remove existing symlink
|
||
|
|
os.Remove(dst)
|
||
|
|
|
||
|
|
return os.Symlink(src, dst)
|
||
|
|
}
|
||
|
|
|
||
|
|
func DisableSite(cfg *config.Config, domain string) error {
|
||
|
|
dst := filepath.Join(cfg.Nginx.SitesEnabled, domain)
|
||
|
|
return os.Remove(dst)
|
||
|
|
}
|
||
|
|
|
||
|
|
func Reload() error {
|
||
|
|
return exec.Command("systemctl", "reload", "nginx").Run()
|
||
|
|
}
|
||
|
|
|
||
|
|
func Restart() error {
|
||
|
|
return exec.Command("systemctl", "restart", "nginx").Run()
|
||
|
|
}
|
||
|
|
|
||
|
|
func Test() (string, error) {
|
||
|
|
out, err := exec.Command("nginx", "-t").CombinedOutput()
|
||
|
|
return string(out), err
|
||
|
|
}
|
||
|
|
|
||
|
|
func Status() (string, bool) {
|
||
|
|
out, err := exec.Command("systemctl", "is-active", "nginx").Output()
|
||
|
|
status := strings.TrimSpace(string(out))
|
||
|
|
return status, err == nil && status == "active"
|
||
|
|
}
|
||
|
|
|
||
|
|
func InstallSnippets(cfg *config.Config) error {
|
||
|
|
os.MkdirAll(cfg.Nginx.Snippets, 0755)
|
||
|
|
|
||
|
|
sslParams := `# SSL params — managed by Setec App Manager
|
||
|
|
ssl_protocols TLSv1.2 TLSv1.3;
|
||
|
|
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
|
||
|
|
ssl_prefer_server_ciphers off;
|
||
|
|
ssl_session_timeout 1d;
|
||
|
|
ssl_session_cache shared:SSL:10m;
|
||
|
|
ssl_session_tickets off;
|
||
|
|
ssl_stapling on;
|
||
|
|
ssl_stapling_verify on;
|
||
|
|
add_header Strict-Transport-Security "max-age=63072000" always;
|
||
|
|
`
|
||
|
|
|
||
|
|
proxyParams := `# Proxy params — managed by Setec App Manager
|
||
|
|
proxy_set_header Host $host;
|
||
|
|
proxy_set_header X-Real-IP $remote_addr;
|
||
|
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||
|
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||
|
|
proxy_buffering off;
|
||
|
|
proxy_request_buffering off;
|
||
|
|
`
|
||
|
|
|
||
|
|
if err := os.WriteFile(filepath.Join(cfg.Nginx.Snippets, "ssl-params.conf"), []byte(sslParams), 0644); err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
return os.WriteFile(filepath.Join(cfg.Nginx.Snippets, "proxy-params.conf"), []byte(proxyParams), 0644)
|
||
|
|
}
|