summaryrefslogtreecommitdiff
path: root/cmd
diff options
context:
space:
mode:
Diffstat (limited to 'cmd')
-rw-r--r--cmd/skiff/main.go116
1 files changed, 109 insertions, 7 deletions
diff --git a/cmd/skiff/main.go b/cmd/skiff/main.go
index 3286da7..cc48c67 100644
--- a/cmd/skiff/main.go
+++ b/cmd/skiff/main.go
@@ -14,6 +14,7 @@ import (
"skiff/internal/appmeta"
"skiff/internal/deploy"
+ "skiff/internal/domains"
"skiff/internal/envfile"
"skiff/internal/jailmgr"
"skiff/internal/layout"
@@ -26,7 +27,7 @@ import (
func main() {
if len(os.Args) < 2 {
- fmt.Fprintln(os.Stderr, "usage: skiff <deploy|new-app|rollback|rebuild|config> [flags]")
+ fmt.Fprintln(os.Stderr, "usage: skiff <deploy|new-app|rollback|rebuild|config|domains> [flags]")
os.Exit(1)
}
@@ -42,6 +43,8 @@ func main() {
err = cmdRebuild(os.Args[2:])
case "config":
err = cmdConfig(os.Args[2:])
+ case "domains":
+ err = cmdDomains(os.Args[2:])
default:
fmt.Fprintf(os.Stderr, "unknown subcommand %q\n", os.Args[1])
os.Exit(1)
@@ -86,7 +89,6 @@ func cmdNewApp(args []string) error {
ip := fs.String("ip", "", "IPv4 address to assign the jail (operator-chosen)")
port := fs.Int("port", layout.DefaultHealthPort, "port the app listens on inside the jail")
healthPath := fs.String("health-path", layout.DefaultHealthPath, "health check path")
- serverName := fs.String("server-name", "", "nginx server_name (defaults to <name>)")
baseSnapshot := fs.String("base-snapshot", "ready", "snapshot on the base dataset to clone from")
iface := fs.String("iface", layout.DefaultJailIface, "host interface the jail's IP is aliased onto")
fs.Parse(args)
@@ -94,9 +96,6 @@ func cmdNewApp(args []string) error {
if *name == "" || *ip == "" {
return fmt.Errorf("--name and --ip are required")
}
- if *serverName == "" {
- *serverName = *name
- }
ctx := context.Background()
out := os.Stdout
@@ -136,7 +135,6 @@ func cmdNewApp(args []string) error {
IP: *ip,
Port: *port,
HealthPath: *healthPath,
- ServerName: *serverName,
}); err != nil {
return fmt.Errorf("write app metadata: %w", err)
}
@@ -158,7 +156,7 @@ func cmdNewApp(args []string) error {
return fmt.Errorf("start jail: %w", err)
}
- fmt.Fprintf(out, "\n%s is ready. From the app's repo:\n git remote add skiff <this-host>:%s\n git push skiff main\n", *name, cfg.RepoPath(*name))
+ fmt.Fprintf(out, "\n%s is ready. From the app's repo:\n git remote add skiff <this-host>:%s\n git push skiff main\n\nNo domains are routed yet - once DNS points at %s:\n skiff domains add --app %s <hostname>\n", *name, cfg.RepoPath(*name), *ip, *name)
return nil
}
@@ -396,3 +394,107 @@ func applyConfigChange(app string, mutate func(map[string]string) error) error {
fmt.Fprintln(out, "==> config updated")
return nil
}
+
+func cmdDomains(args []string) error {
+ if len(args) < 1 {
+ return fmt.Errorf("usage: skiff domains <add|remove|list> --app <name> [args...]")
+ }
+ switch args[0] {
+ case "add":
+ return cmdDomainsAdd(args[1:])
+ case "remove":
+ return cmdDomainsRemove(args[1:])
+ case "list":
+ return cmdDomainsList(args[1:])
+ default:
+ return fmt.Errorf("unknown domains subcommand %q", args[0])
+ }
+}
+
+// Unlike cmdConfigSet, these skip deploy.GuardedApply - see internal/domains.
+func cmdDomainsAdd(args []string) error {
+ fs := flag.NewFlagSet("domains add", flag.ExitOnError)
+ app := fs.String("app", "", "app name")
+ primary := fs.Bool("primary", false, "make this the app's primary domain - every other domain redirects to it")
+ fs.Parse(args)
+ rest := fs.Args()
+
+ if *app == "" || len(rest) != 1 {
+ return fmt.Errorf("usage: skiff domains add --app <name> [--primary] <hostname>")
+ }
+
+ cfg := layout.Default()
+ if _, err := os.Stat(cfg.MetaPath(*app)); err != nil {
+ return fmt.Errorf("app %q not found: %w", *app, err)
+ }
+
+ unlock, err := lock.Acquire(cfg.LockPath(*app))
+ if err != nil {
+ return fmt.Errorf("acquire app lock: %w", err)
+ }
+ defer unlock.Release()
+
+ return domains.Add(context.Background(), os.Stdout, cfg, *app, rest[0], *primary)
+}
+
+func cmdDomainsRemove(args []string) error {
+ fs := flag.NewFlagSet("domains remove", flag.ExitOnError)
+ app := fs.String("app", "", "app name")
+ fs.Parse(args)
+ rest := fs.Args()
+
+ if *app == "" || len(rest) != 1 {
+ return fmt.Errorf("usage: skiff domains remove --app <name> <hostname>")
+ }
+
+ cfg := layout.Default()
+ if _, err := os.Stat(cfg.MetaPath(*app)); err != nil {
+ return fmt.Errorf("app %q not found: %w", *app, err)
+ }
+
+ unlock, err := lock.Acquire(cfg.LockPath(*app))
+ if err != nil {
+ return fmt.Errorf("acquire app lock: %w", err)
+ }
+ defer unlock.Release()
+
+ return domains.Remove(context.Background(), os.Stdout, cfg, *app, rest[0])
+}
+
+func cmdDomainsList(args []string) error {
+ fs := flag.NewFlagSet("domains list", flag.ExitOnError)
+ app := fs.String("app", "", "app name")
+ fs.Parse(args)
+
+ if *app == "" {
+ return fmt.Errorf("--app is required")
+ }
+
+ cfg := layout.Default()
+ meta, err := appmeta.Read(cfg.MetaPath(*app))
+ if err != nil {
+ return fmt.Errorf("app %q not found: %w", *app, err)
+ }
+ if len(meta.Domains) == 0 {
+ fmt.Println("no domains configured")
+ return nil
+ }
+
+ tlsLive := make(map[string]bool, len(meta.TLSDomains))
+ for _, h := range meta.TLSDomains {
+ tlsLive[h] = true
+ }
+
+ fmt.Printf("%-30s %-8s %s\n", "HOSTNAME", "PRIMARY", "TLS")
+ for _, d := range meta.Domains {
+ primaryCol, tlsCol := "no", "pending"
+ if d.Primary {
+ primaryCol = "yes"
+ }
+ if tlsLive[d.Hostname] {
+ tlsCol = "issued"
+ }
+ fmt.Printf("%-30s %-8s %s\n", d.Hostname, primaryCol, tlsCol)
+ }
+ return nil
+}