No One Can Stop Me Now
This commit is contained in:
258
services/setec-manager/cmd/main.go
Normal file
258
services/setec-manager/cmd/main.go
Normal file
@@ -0,0 +1,258 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"setec-manager/internal/config"
|
||||
"setec-manager/internal/db"
|
||||
"setec-manager/internal/deploy"
|
||||
"setec-manager/internal/nginx"
|
||||
"setec-manager/internal/scheduler"
|
||||
"setec-manager/internal/server"
|
||||
)
|
||||
|
||||
const banner = `
|
||||
███████╗███████╗████████╗███████╗ ██████╗
|
||||
██╔════╝██╔════╝╚══██╔══╝██╔════╝██╔════╝
|
||||
███████╗█████╗ ██║ █████╗ ██║
|
||||
╚════██║██╔══╝ ██║ ██╔══╝ ██║
|
||||
███████║███████╗ ██║ ███████╗╚██████╗
|
||||
╚══════╝╚══════╝ ╚═╝ ╚══════╝ ╚═════╝
|
||||
A P P M A N A G E R v1.0
|
||||
darkHal Security Group & Setec Security Labs
|
||||
`
|
||||
|
||||
func main() {
|
||||
configPath := flag.String("config", "/opt/setec-manager/config.yaml", "Path to config file")
|
||||
setup := flag.Bool("setup", false, "Run first-time setup")
|
||||
flag.Parse()
|
||||
|
||||
fmt.Print(banner)
|
||||
|
||||
// Load config
|
||||
cfg, err := config.Load(*configPath)
|
||||
if err != nil {
|
||||
log.Fatalf("[setec] Failed to load config: %v", err)
|
||||
}
|
||||
|
||||
// Open database
|
||||
database, err := db.Open(cfg.Database.Path)
|
||||
if err != nil {
|
||||
log.Fatalf("[setec] Failed to open database: %v", err)
|
||||
}
|
||||
defer database.Close()
|
||||
|
||||
// First-time setup
|
||||
if *setup {
|
||||
runSetup(cfg, database, *configPath)
|
||||
return
|
||||
}
|
||||
|
||||
// Check if any admin users exist
|
||||
count, _ := database.ManagerUserCount()
|
||||
if count == 0 {
|
||||
log.Println("[setec] No admin users found. Creating default admin account.")
|
||||
log.Println("[setec] Username: admin")
|
||||
log.Println("[setec] Password: autarch")
|
||||
log.Println("[setec] ** CHANGE THIS IMMEDIATELY **")
|
||||
database.CreateManagerUser("admin", "autarch", "admin")
|
||||
}
|
||||
|
||||
// Load or create persistent JWT key
|
||||
dataDir := filepath.Dir(cfg.Database.Path)
|
||||
jwtKey, err := server.LoadOrCreateJWTKey(dataDir)
|
||||
if err != nil {
|
||||
log.Fatalf("[setec] Failed to load JWT key: %v", err)
|
||||
}
|
||||
|
||||
// Create and start server
|
||||
srv := server.New(cfg, database, jwtKey)
|
||||
|
||||
// Start scheduler
|
||||
sched := scheduler.New(database)
|
||||
sched.RegisterHandler(scheduler.JobSSLRenew, func(siteID *int64) error {
|
||||
log.Println("[scheduler] Running SSL renewal")
|
||||
_, err := exec.Command("certbot", "renew", "--non-interactive").CombinedOutput()
|
||||
return err
|
||||
})
|
||||
sched.RegisterHandler(scheduler.JobCleanup, func(siteID *int64) error {
|
||||
log.Println("[scheduler] Running cleanup")
|
||||
return nil
|
||||
})
|
||||
sched.RegisterHandler(scheduler.JobBackup, func(siteID *int64) error {
|
||||
if siteID == nil {
|
||||
log.Println("[scheduler] Backup job requires a site ID, skipping")
|
||||
return fmt.Errorf("backup job requires a site ID")
|
||||
}
|
||||
site, err := database.GetSite(*siteID)
|
||||
if err != nil || site == nil {
|
||||
return fmt.Errorf("backup: site %d not found", *siteID)
|
||||
}
|
||||
|
||||
backupDir := cfg.Backups.Dir
|
||||
os.MkdirAll(backupDir, 0755)
|
||||
|
||||
timestamp := time.Now().Format("20060102-150405")
|
||||
filename := fmt.Sprintf("site-%s-%s.tar.gz", site.Domain, timestamp)
|
||||
backupPath := filepath.Join(backupDir, filename)
|
||||
|
||||
cmd := exec.Command("tar", "-czf", backupPath, "-C", filepath.Dir(site.AppRoot), filepath.Base(site.AppRoot))
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("backup tar failed: %s: %w", string(out), err)
|
||||
}
|
||||
|
||||
info, _ := os.Stat(backupPath)
|
||||
size := int64(0)
|
||||
if info != nil {
|
||||
size = info.Size()
|
||||
}
|
||||
|
||||
database.CreateBackup(siteID, "site", backupPath, size)
|
||||
log.Printf("[scheduler] Backup complete for site %s: %s (%d bytes)", site.Domain, backupPath, size)
|
||||
return nil
|
||||
})
|
||||
sched.RegisterHandler(scheduler.JobGitPull, func(siteID *int64) error {
|
||||
if siteID == nil {
|
||||
return fmt.Errorf("git_pull job requires a site ID")
|
||||
}
|
||||
site, err := database.GetSite(*siteID)
|
||||
if err != nil || site == nil {
|
||||
return fmt.Errorf("git_pull: site %d not found", *siteID)
|
||||
}
|
||||
if site.GitRepo == "" {
|
||||
return fmt.Errorf("git_pull: site %s has no git repo configured", site.Domain)
|
||||
}
|
||||
|
||||
output, err := deploy.Pull(site.AppRoot)
|
||||
if err != nil {
|
||||
return fmt.Errorf("git_pull %s: %w", site.Domain, err)
|
||||
}
|
||||
log.Printf("[scheduler] Git pull for site %s: %s", site.Domain, strings.TrimSpace(output))
|
||||
return nil
|
||||
})
|
||||
sched.RegisterHandler(scheduler.JobRestart, func(siteID *int64) error {
|
||||
if siteID == nil {
|
||||
return fmt.Errorf("restart job requires a site ID")
|
||||
}
|
||||
site, err := database.GetSite(*siteID)
|
||||
if err != nil || site == nil {
|
||||
return fmt.Errorf("restart: site %d not found", *siteID)
|
||||
}
|
||||
|
||||
unitName := fmt.Sprintf("app-%s", site.Domain)
|
||||
if err := deploy.Restart(unitName); err != nil {
|
||||
return fmt.Errorf("restart %s: %w", site.Domain, err)
|
||||
}
|
||||
log.Printf("[scheduler] Restarted service for site %s (unit: %s)", site.Domain, unitName)
|
||||
return nil
|
||||
})
|
||||
sched.Start()
|
||||
|
||||
// Graceful shutdown
|
||||
done := make(chan os.Signal, 1)
|
||||
signal.Notify(done, os.Interrupt, syscall.SIGTERM)
|
||||
|
||||
go func() {
|
||||
if err := srv.Start(); err != nil {
|
||||
log.Fatalf("[setec] Server error: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
log.Printf("[setec] Dashboard: https://%s:%d", cfg.Server.Host, cfg.Server.Port)
|
||||
|
||||
<-done
|
||||
log.Println("[setec] Shutting down...")
|
||||
sched.Stop()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
srv.Shutdown(ctx)
|
||||
}
|
||||
|
||||
func runSetup(cfg *config.Config, database *db.DB, configPath string) {
|
||||
log.Println("[setup] Starting first-time setup...")
|
||||
|
||||
// Ensure directories exist
|
||||
dirs := []string{
|
||||
"/opt/setec-manager/data",
|
||||
"/opt/setec-manager/data/acme",
|
||||
"/opt/setec-manager/data/backups",
|
||||
cfg.Nginx.Webroot,
|
||||
cfg.Nginx.CertbotWebroot,
|
||||
cfg.Nginx.SitesAvailable,
|
||||
cfg.Nginx.SitesEnabled,
|
||||
}
|
||||
for _, d := range dirs {
|
||||
os.MkdirAll(d, 0755)
|
||||
}
|
||||
|
||||
// Install Nginx if needed
|
||||
log.Println("[setup] Installing nginx...")
|
||||
execQuiet("apt-get", "update", "-qq")
|
||||
execQuiet("apt-get", "install", "-y", "nginx", "certbot", "ufw")
|
||||
|
||||
// Install nginx snippets
|
||||
log.Println("[setup] Configuring nginx snippets...")
|
||||
nginx.InstallSnippets(cfg)
|
||||
|
||||
// Create admin user
|
||||
count, _ := database.ManagerUserCount()
|
||||
if count == 0 {
|
||||
log.Println("[setup] Creating default admin user (admin / autarch)")
|
||||
database.CreateManagerUser("admin", "autarch", "admin")
|
||||
}
|
||||
|
||||
// Save config
|
||||
cfg.Save(configPath)
|
||||
|
||||
// Generate self-signed cert for manager if none exists
|
||||
if _, err := os.Stat(cfg.Server.Cert); os.IsNotExist(err) {
|
||||
log.Println("[setup] Generating self-signed TLS cert for manager...")
|
||||
os.MkdirAll(cfg.ACME.AccountDir, 0755)
|
||||
execQuiet("openssl", "req", "-x509", "-newkey", "rsa:2048",
|
||||
"-keyout", cfg.Server.Key, "-out", cfg.Server.Cert,
|
||||
"-days", "3650", "-nodes",
|
||||
"-subj", "/CN=setec-manager/O=Setec Security Labs")
|
||||
}
|
||||
|
||||
// Install systemd unit for setec-manager
|
||||
unit := `[Unit]
|
||||
Description=Setec App Manager
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
ExecStart=/opt/setec-manager/setec-manager --config /opt/setec-manager/config.yaml
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
`
|
||||
os.WriteFile("/etc/systemd/system/setec-manager.service", []byte(unit), 0644)
|
||||
execQuiet("systemctl", "daemon-reload")
|
||||
|
||||
log.Println("[setup] Setup complete!")
|
||||
log.Println("[setup] Start with: systemctl start setec-manager")
|
||||
log.Printf("[setup] Dashboard will be at: https://<your-ip>:%d\n", cfg.Server.Port)
|
||||
}
|
||||
|
||||
func execQuiet(name string, args ...string) {
|
||||
log.Printf("[setup] $ %s %s", name, strings.Join(args, " "))
|
||||
cmd := exec.Command(name, args...)
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
log.Printf("[setup] Warning: %v\n%s", err, string(out))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user