summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorClaude <noreply@anthropic.com>2026-08-05 23:22:25 -0500
committerClaude <noreply@anthropic.com>2026-08-05 23:22:25 -0500
commitfc0c6951d7ac36dd8f30ace929991197dc99ae54 (patch)
tree46fa11cc103ebe7805bf42bc3e4bb105e6b49721
parent9f7c9f2f31acd7bc544dd988af1c8c2fe63ec504 (diff)
Simplify: centralize paths in layout, dedup zfs snapshot refs, remove dead code
- Add layout.Config methods (ReleasesDir, PostReceiveHookPath, RCScriptPath, RCConfPath) so main.go and jailmgr no longer hand-build paths that layout already owns. - Extract zfsutil.snapRef to replace three inline dataset@tag Sprintfs. - Remove seven unused exported functions (zfsutil.Exists, jailmgr.Stop, sh.RunWithStdinFile, and four dead layout.Config path methods) and their now-unused imports.
-rw-r--r--cmd/skiff/main.go6
-rw-r--r--internal/jailmgr/jailmgr.go14
-rw-r--r--internal/layout/layout.go49
-rw-r--r--internal/sh/sh.go21
-rw-r--r--internal/zfsutil/zfsutil.go12
5 files changed, 33 insertions, 69 deletions
diff --git a/cmd/skiff/main.go b/cmd/skiff/main.go
index e709227..33aaf5e 100644
--- a/cmd/skiff/main.go
+++ b/cmd/skiff/main.go
@@ -7,7 +7,6 @@ import (
"flag"
"fmt"
"os"
- "path/filepath"
"strings"
"skiff/internal/appmeta"
@@ -90,7 +89,7 @@ func cmdNewApp(args []string) error {
return fmt.Errorf("clone base dataset: %w", err)
}
- if err := os.MkdirAll(filepath.Join(cfg.AppDir(*name), "releases"), 0755); err != nil {
+ if err := os.MkdirAll(cfg.ReleasesDir(*name), 0755); err != nil {
return fmt.Errorf("create app dir: %w", err)
}
@@ -115,8 +114,7 @@ func cmdNewApp(args []string) error {
if err != nil {
return err
}
- hookPath := filepath.Join(cfg.RepoPath(*name), "hooks", "post-receive")
- if err := os.WriteFile(hookPath, []byte(hook), 0755); err != nil {
+ if err := os.WriteFile(cfg.PostReceiveHookPath(*name), []byte(hook), 0755); err != nil {
return fmt.Errorf("install post-receive hook: %w", err)
}
diff --git a/internal/jailmgr/jailmgr.go b/internal/jailmgr/jailmgr.go
index 54cf614..b6a5d28 100644
--- a/internal/jailmgr/jailmgr.go
+++ b/internal/jailmgr/jailmgr.go
@@ -17,11 +17,7 @@ import (
// WriteConf renders jail.conf.d/<app>.conf and the jail's own rc.d service
// script + rc.conf enable line. Called once, from `skiff new-app`.
func WriteConf(cfg layout.Config, app, ip string) error {
- conf, err := tmpl.JailConf(tmpl.JailConfData{
- Name: app,
- JailRoot: cfg.JailRoot(app),
- IP: ip,
- })
+ conf, err := tmpl.JailConf(tmpl.JailConfData{Name: app, JailRoot: cfg.JailRoot(app), IP: ip})
if err != nil {
return err
}
@@ -33,7 +29,7 @@ func WriteConf(cfg layout.Config, app, ip string) error {
if err != nil {
return err
}
- rcPath := filepath.Join(cfg.JailRoot(app), "usr", "local", "etc", "rc.d", app)
+ rcPath := cfg.RCScriptPath(app)
if err := os.MkdirAll(filepath.Dir(rcPath), 0755); err != nil {
return err
}
@@ -42,7 +38,7 @@ func WriteConf(cfg layout.Config, app, ip string) error {
}
rcConfLine := fmt.Sprintf("\n%s_enable=\"YES\"\n", app)
- f, err := os.OpenFile(filepath.Join(cfg.JailRoot(app), "etc", "rc.conf"), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
+ f, err := os.OpenFile(cfg.RCConfPath(app), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return fmt.Errorf("open jail rc.conf: %w", err)
}
@@ -57,10 +53,6 @@ func Start(ctx context.Context, out io.Writer, app string) error {
return sh.Run(ctx, out, "", "service", "jail", "start", app)
}
-func Stop(ctx context.Context, out io.Writer, app string) error {
- return sh.Run(ctx, out, "", "service", "jail", "stop", app)
-}
-
// Restart restarts an already-running jail, or starts it if this is its
// first deploy and it isn't up yet.
func Restart(ctx context.Context, out io.Writer, app string) error {
diff --git a/internal/layout/layout.go b/internal/layout/layout.go
index 89e6848..636b8ce 100644
--- a/internal/layout/layout.go
+++ b/internal/layout/layout.go
@@ -55,6 +55,20 @@ func (c Config) NginxConfPath(app string) string {
return filepath.Join(c.NginxConfDir, app+".conf")
}
+func (c Config) PostReceiveHookPath(app string) string {
+ return filepath.Join(c.RepoPath(app), "hooks", "post-receive")
+}
+
+// RCScriptPath and RCConfPath are paths inside the jail's own chroot, not
+// the host's rc.d/rc.conf.
+func (c Config) RCScriptPath(app string) string {
+ return filepath.Join(c.JailRoot(app), "usr", "local", "etc", "rc.d", app)
+}
+
+func (c Config) RCConfPath(app string) string {
+ return filepath.Join(c.JailRoot(app), "etc", "rc.conf")
+}
+
// Everything below is Capistrano-style: each deploy gets its own immutable
// releases/<sha> checkout (built in place, inside the jail's own dataset),
// `current` is a symlink to whichever release is live, and data.db/env are
@@ -63,8 +77,12 @@ func (c Config) AppDir(app string) string {
return filepath.Join(c.JailRoot(app), "app")
}
+func (c Config) ReleasesDir(app string) string {
+ return filepath.Join(c.AppDir(app), "releases")
+}
+
func (c Config) ReleaseDir(app, sha string) string {
- return filepath.Join(c.AppDir(app), "releases", sha)
+ return filepath.Join(c.ReleasesDir(app), sha)
}
func (c Config) CurrentRelease(app string) string {
@@ -73,35 +91,14 @@ func (c Config) CurrentRelease(app string) string {
// ReleaseDirInJail is releases/<sha> as seen from inside the jail's own
// chroot, not the host. The `current` symlink must be written with this
-// form as its target: it's dereferenced by the jail's rc.d script (execing
-// CurrentBin), where a host-absolute path like ReleaseDir's resolves
-// relative to the jail root instead and never exists.
+// form as its target: it's dereferenced by the jail's rc.d script (which
+// execs /app/current/app, per internal/tmpl/rc.d.tmpl's command_args),
+// where a host-absolute path like ReleaseDir's resolves relative to the
+// jail root instead and never exists.
func (c Config) ReleaseDirInJail(sha string) string {
return filepath.Join("/app", "releases", sha)
}
-// CurrentBin is the path the jail's rc.d script execs - must match
-// internal/tmpl/rc.d.tmpl's command_args.
-func (c Config) CurrentBin(app string) string {
- return filepath.Join(c.CurrentRelease(app), "app")
-}
-
-func (c Config) MakefilePath(app, sha string) string {
- return filepath.Join(c.ReleaseDir(app, sha), "Makefile")
-}
-
-func (c Config) DataDB(app string) string {
- return filepath.Join(c.AppDir(app), "data.db")
-}
-
-func (c Config) EnvFile(app string) string {
- return filepath.Join(c.AppDir(app), "env")
-}
-
func (c Config) MetaPath(app string) string {
return filepath.Join(c.AppDir(app), "meta.json")
}
-
-func (c Config) SnapshotName(app, sha string) string {
- return fmt.Sprintf("%s@pre-%s", c.AppDataset(app), sha)
-}
diff --git a/internal/sh/sh.go b/internal/sh/sh.go
index b5f6f49..6e12886 100644
--- a/internal/sh/sh.go
+++ b/internal/sh/sh.go
@@ -6,7 +6,6 @@ import (
"context"
"fmt"
"io"
- "os"
"os/exec"
)
@@ -43,23 +42,3 @@ func CombinedOutput(ctx context.Context, dir string, name string, args ...string
b, err := cmd.CombinedOutput()
return string(b), err
}
-
-// RunWithStdinFile runs a command with stdinPath fed to its stdin, e.g.
-// piping a migration file into `sqlite3 db`.
-func RunWithStdinFile(ctx context.Context, out io.Writer, dir, stdinPath, name string, args ...string) error {
- f, err := os.Open(stdinPath)
- if err != nil {
- return fmt.Errorf("open %s: %w", stdinPath, err)
- }
- defer f.Close()
-
- cmd := exec.CommandContext(ctx, name, args...)
- cmd.Dir = dir
- cmd.Stdin = f
- cmd.Stdout = out
- cmd.Stderr = out
- if err := cmd.Run(); err != nil {
- return fmt.Errorf("%s %v < %s: %w", name, args, stdinPath, err)
- }
- return nil
-}
diff --git a/internal/zfsutil/zfsutil.go b/internal/zfsutil/zfsutil.go
index 3d3bd40..8f1b0e2 100644
--- a/internal/zfsutil/zfsutil.go
+++ b/internal/zfsutil/zfsutil.go
@@ -11,20 +11,18 @@ import (
"skiff/internal/sh"
)
-func Exists(ctx context.Context, dataset string) bool {
- _, err := sh.Output(ctx, "", "zfs", "list", "-H", "-o", "name", dataset)
- return err == nil
+func snapRef(dataset, tag string) string {
+ return fmt.Sprintf("%s@%s", dataset, tag)
}
// Clone creates a new dataset from a (read-only) snapshot of base, mounted
// at mountpoint. base must already have a snapshot named baseSnapshot.
func Clone(ctx context.Context, out io.Writer, base, baseSnapshot, dataset, mountpoint string) error {
- src := fmt.Sprintf("%s@%s", base, baseSnapshot)
- return sh.Run(ctx, out, "", "zfs", "clone", "-o", "mountpoint="+mountpoint, src, dataset)
+ return sh.Run(ctx, out, "", "zfs", "clone", "-o", "mountpoint="+mountpoint, snapRef(base, baseSnapshot), dataset)
}
func Snapshot(ctx context.Context, out io.Writer, dataset, tag string) error {
- return sh.Run(ctx, out, "", "zfs", "snapshot", fmt.Sprintf("%s@%s", dataset, tag))
+ return sh.Run(ctx, out, "", "zfs", "snapshot", snapRef(dataset, tag))
}
// Rollback discards everything written to dataset since the snapshot named
@@ -32,7 +30,7 @@ func Snapshot(ctx context.Context, out io.Writer, dataset, tag string) error {
// used both for the automatic post-health-check-failure rollback and for a
// manual `skiff rollback` days later.
func Rollback(ctx context.Context, out io.Writer, dataset, tag string) error {
- return sh.Run(ctx, out, "", "zfs", "rollback", "-r", fmt.Sprintf("%s@%s", dataset, tag))
+ return sh.Run(ctx, out, "", "zfs", "rollback", "-r", snapRef(dataset, tag))
}
// Snapshots lists snapshot names (without the dataset@ prefix), newest last.