1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
|
// Package layout defines Skiff's directory and dataset conventions in one
// place. Nothing else in the codebase should hardcode a path.
package layout
import (
"fmt"
"path/filepath"
)
const (
DefaultHealthPort = 8080
DefaultHealthPath = "/healthz"
HealthTimeoutSecs = 10
)
// Config is Skiff's host-wide configuration. Every field has a sane FreeBSD
// default; override via flags on the skiff binary if a host differs.
type Config struct {
ZFSPool string // e.g. "zroot"
BaseDataset string // template jail root, cloned per app
ReposDir string // bare git repos live here
JailsMount string // parent mountpoint for per-app datasets
JailConfDir string // /etc/jail.conf.d
NginxConfDir string // nginx include dir regenerated on every deploy
LocksDir string // per-app flock files live here, outside any per-app dataset
}
func Default() Config {
return Config{
ZFSPool: "zroot/skiff",
BaseDataset: "zroot/skiff/base",
ReposDir: "/skiff/repos",
JailsMount: "/skiff/apps",
JailConfDir: "/etc/jail.conf.d",
NginxConfDir: "/usr/local/etc/nginx/skiff.d",
LocksDir: "/skiff/locks",
}
}
func (c Config) AppDataset(app string) string {
return fmt.Sprintf("%s/apps/%s", c.ZFSPool, app)
}
func (c Config) JailRoot(app string) string {
return filepath.Join(c.JailsMount, app)
}
func (c Config) RepoPath(app string) string {
return filepath.Join(c.ReposDir, app+".git")
}
// LockPath is the per-app flock file that serializes deploy, config change,
// and rollback against each other. It deliberately lives outside
// AppDataset(app): that dataset gets `zfs rollback`ed on a failed health
// check or a manual rollback, which would discard a lock file living inside
// it along with everything else written since the snapshot.
func (c Config) LockPath(app string) string {
return filepath.Join(c.LocksDir, app+".lock")
}
func (c Config) JailConfPath(app string) string {
return filepath.Join(c.JailConfDir, app+".conf")
}
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
// persistent siblings that outlive any single release.
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.ReleasesDir(app), sha)
}
func (c Config) CurrentRelease(app string) string {
return filepath.Join(c.AppDir(app), "current")
}
// 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 (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)
}
func (c Config) MetaPath(app string) string {
return filepath.Join(c.AppDir(app), "meta.json")
}
func (c Config) EnvPath(app string) string {
return filepath.Join(c.AppDir(app), "env")
}
|