carlos / platform Public

appconfig: the marker-owned drop-in and pending marker as pure file operations

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Paul Campbell pushed by paul@keymail.dev b9baaad35b27350739c51951d5996a32804a4f65 parent 8afa6e5
2 files changed, +301 −0
  • internal/appconfig/dropin.go +172 −0
  • internal/appconfig/dropin_test.go +129 −0
diff --git a/internal/appconfig/dropin.go b/internal/appconfig/dropin.go
new file mode 100644
index 000000000..f8af494fb
--- /dev/null
+++ b/internal/appconfig/dropin.go
@@ -0,0 +1,172 @@
+// The drop-in a stamped route's unit reads its config through (spec §3.2):
+// <root>/<unit>.d/zz-carlos-env.conf, marker-owned, carrying a reset and
+// ONE EnvironmentFile line; and beside it carlos-env.pending, the record
+// that the unit owes a daemon-reload and (while it is still owned) a
+// restart. Everything here is a pure file operation with no systemd in it,
+// so the pass's protocol (unitsync.go) is testable with a temp dir.
+package appconfig
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "os"
+ "path/filepath"
+ "sort"
+ "strings"
+)
+
+const (
+ // DropInFile sorts after override.conf and every conventionally named
+ // drop-in; with the reset line that ordering is belt and braces.
+ DropInFile = "zz-carlos-env.conf"
+ // PendingFile is not a .conf on purpose: systemd ignores it.
+ PendingFile = "carlos-env.pending"
+ // dropInMarker is the first line of every drop-in this package writes,
+ // and the ONLY evidence it accepts before replacing or removing one.
+ dropInMarker = "# Managed by carlos: config delivery for the route naming this unit."
+)
+
+func dropInDir(root, unit string) string { return filepath.Join(root, unit+".d") }
+
+// renderDropIn: the reset line hands the unit's EnvironmentFile list to
+// carlos whole (spec §3.2, codex R2-8); the second line is the rendered
+// bundle, with the '-' prefix so an absent file is not a start failure.
+func renderDropIn(envDir, host string) []byte {
+ return []byte(dropInMarker + "\n" +
+ "# Edit the route's config with `carlos env` / `carlos secrets`, not this file.\n" +
+ "[Service]\n" +
+ "EnvironmentFile=\n" +
+ "EnvironmentFile=-" + filepath.Join(envDir, host+".env") + "\n")
+}
+
+// ensureDropInDir creates <unit>.d if absent and refuses to use it if it
+// is not a plain directory: a symlinked drop-in directory would let a root
+// write land somewhere else (spec §3.2 rail; codex R1-2, R2-1).
+func ensureDropInDir(root, unit string) error {
+ dir := dropInDir(root, unit)
+ st, err := os.Lstat(dir)
+ switch {
+ case err == nil:
+ if st.Mode()&os.ModeSymlink != 0 {
+ return fmt.Errorf("%s is a symlink; refusing to write through it", dir)
+ }
+ if !st.IsDir() {
+ return fmt.Errorf("%s exists and is not a directory", dir)
+ }
+ return nil
+ case os.IsNotExist(err):
+ return os.Mkdir(dir, 0o755)
+ default:
+ return err
+ }
+}
+
+// dirIsPlain is ensureDropInDir's check without the mkdir, for readers.
+func dirIsPlain(dir string) bool {
+ st, err := os.Lstat(dir)
+ return err == nil && st.IsDir() && st.Mode()&os.ModeSymlink == 0
+}
+
+func readDropIn(root, unit string) (cur []byte, exists, marked bool, err error) {
+ cur, err = os.ReadFile(filepath.Join(dropInDir(root, unit), DropInFile))
+ if err != nil {
+ if os.IsNotExist(err) {
+ return nil, false, false, nil
+ }
+ return nil, false, false, err
+ }
+ first, _, _ := bytes.Cut(cur, []byte("\n"))
+ return cur, true, string(first) == dropInMarker, nil
+}
+
+func writeDropIn(root, unit string, rendered []byte) error {
+ path := filepath.Join(dropInDir(root, unit), DropInFile)
+ tmp := path + ".tmp"
+ if err := os.WriteFile(tmp, rendered, 0o644); err != nil {
+ return err
+ }
+ if err := os.Rename(tmp, path); err != nil {
+ os.Remove(tmp)
+ return err
+ }
+ return nil
+}
+
+func removeDropIn(root, unit string) error {
+ err := os.Remove(filepath.Join(dropInDir(root, unit), DropInFile))
+ if err != nil && !os.IsNotExist(err) {
+ return err
+ }
+ return nil
+}
+
+func pendingPath(root, unit string) string { return filepath.Join(dropInDir(root, unit), PendingFile) }
+
+func writePending(root, unit string) error {
+ return os.WriteFile(pendingPath(root, unit), []byte("reload and restart owed\n"), 0o644)
+}
+
+func removePending(root, unit string) error {
+ err := os.Remove(pendingPath(root, unit))
+ if err != nil && !os.IsNotExist(err) {
+ return err
+ }
+ return nil
+}
+
+func hasPending(root, unit string) bool {
+ _, err := os.Lstat(pendingPath(root, unit))
+ return err == nil
+}
+
+// unitDirs lists <unit> for every plain <unit>.d directory under root;
+// symlinked or non-directory entries are returned in skipped and never
+// entered (codex R2-1).
+func unitDirs(root string) (units, skipped []string) {
+ ents, err := os.ReadDir(root)
+ if err != nil {
+ return nil, nil
+ }
+ for _, e := range ents {
+ name := e.Name()
+ if !strings.HasSuffix(name, ".service.d") {
+ continue
+ }
+ unit := strings.TrimSuffix(name, ".d")
+ if !dirIsPlain(filepath.Join(root, name)) {
+ skipped = append(skipped, unit)
+ continue
+ }
+ units = append(units, unit)
+ }
+ sort.Strings(units)
+ sort.Strings(skipped)
+ return units, skipped
+}
+
+// listMarkedDropIns: units whose plain <unit>.d holds a marker-bearing
+// drop-in. An unmarked file at the path is not carlos's and is not listed.
+func listMarkedDropIns(root string) (units, skipped []string) {
+ dirs, skipped := unitDirs(root)
+ for _, u := range dirs {
+ if _, exists, marked, err := readDropIn(root, u); err == nil && exists && marked {
+ units = append(units, u)
+ }
+ }
+ return units, skipped
+}
+
+// listPending: units whose plain <unit>.d holds a pending marker.
+func listPending(root string) []string {
+ dirs, _ := unitDirs(root)
+ var out []string
+ for _, u := range dirs {
+ if hasPending(root, u) {
+ out = append(out, u)
+ }
+ }
+ return out
+}
+
+var errNotOurs = errors.New("not carlos's file")
diff --git a/internal/appconfig/dropin_test.go b/internal/appconfig/dropin_test.go
new file mode 100644
index 000000000..297c529f4
--- /dev/null
+++ b/internal/appconfig/dropin_test.go
@@ -0,0 +1,129 @@
+package appconfig
+
+import (
+ "os"
+ "path/filepath"
+ "strings"
+ "testing"
+)
+
+func TestRenderDropInHasMarkerResetAndFile(t *testing.T) {
+ got := string(renderDropIn("/etc/carlos/apps", "relay.example.com"))
+ want := dropInMarker + "\n" +
+ "# Edit the route's config with `carlos env` / `carlos secrets`, not this file.\n" +
+ "[Service]\n" +
+ "EnvironmentFile=\n" +
+ "EnvironmentFile=-/etc/carlos/apps/relay.example.com.env\n"
+ if got != want {
+ t.Fatalf("renderDropIn =\n%s\nwant\n%s", got, want)
+ }
+}
+
+func TestWriteReadAndMarkDropIn(t *testing.T) {
+ root := t.TempDir()
+ if err := ensureDropInDir(root, "relay.service"); err != nil {
+ t.Fatalf("ensureDropInDir: %v", err)
+ }
+ if _, exists, _, err := readDropIn(root, "relay.service"); err != nil || exists {
+ t.Fatalf("readDropIn before write: exists=%v err=%v", exists, err)
+ }
+ body := renderDropIn("/etc/carlos/apps", "relay.example.com")
+ if err := writeDropIn(root, "relay.service", body); err != nil {
+ t.Fatalf("writeDropIn: %v", err)
+ }
+ cur, exists, marked, err := readDropIn(root, "relay.service")
+ if err != nil || !exists || !marked || string(cur) != string(body) {
+ t.Fatalf("readDropIn after write: exists=%v marked=%v err=%v", exists, marked, err)
+ }
+ st, _ := os.Stat(filepath.Join(root, "relay.service.d", DropInFile))
+ if st.Mode().Perm() != 0o644 {
+ t.Fatalf("drop-in mode = %o, want 0644", st.Mode().Perm())
+ }
+ if hasPending(root, "relay.service") {
+ t.Fatal("pending marker present before writePending")
+ }
+ if err := writePending(root, "relay.service"); err != nil {
+ t.Fatalf("writePending: %v", err)
+ }
+ if !hasPending(root, "relay.service") {
+ t.Fatal("pending marker absent after writePending")
+ }
+ if got := listPending(root); len(got) != 1 || got[0] != "relay.service" {
+ t.Fatalf("listPending = %v", got)
+ }
+ if err := removePending(root, "relay.service"); err != nil || hasPending(root, "relay.service") {
+ t.Fatalf("removePending: err=%v still pending=%v", err, hasPending(root, "relay.service"))
+ }
+ if err := removePending(root, "relay.service"); err != nil {
+ t.Fatalf("removePending twice must be a no-op, got %v", err)
+ }
+}
+
+func TestReadDropInSeesAnUnmarkedFile(t *testing.T) {
+ root := t.TempDir()
+ dir := filepath.Join(root, "relay.service.d")
+ os.MkdirAll(dir, 0o755)
+ os.WriteFile(filepath.Join(dir, DropInFile), []byte("[Service]\nEnvironmentFile=/x\n"), 0o644)
+ _, exists, marked, err := readDropIn(root, "relay.service")
+ if err != nil || !exists || marked {
+ t.Fatalf("exists=%v marked=%v err=%v, want an existing unmarked file", exists, marked, err)
+ }
+ units, _ := listMarkedDropIns(root)
+ if len(units) != 0 {
+ t.Fatalf("listMarkedDropIns = %v, want none for an unmarked file", units)
+ }
+}
+
+func TestEnsureDropInDirRefusesSymlinkAndFile(t *testing.T) {
+ root := t.TempDir()
+ elsewhere := t.TempDir()
+ os.Symlink(elsewhere, filepath.Join(root, "linked.service.d"))
+ if err := ensureDropInDir(root, "linked.service"); err == nil || !strings.Contains(err.Error(), "symlink") {
+ t.Fatalf("ensureDropInDir(symlink) = %v, want a symlink refusal", err)
+ }
+ os.WriteFile(filepath.Join(root, "file.service.d"), []byte("x"), 0o644)
+ if err := ensureDropInDir(root, "file.service"); err == nil {
+ t.Fatal("ensureDropInDir(regular file) accepted")
+ }
+ if err := ensureDropInDir(root, "fresh.service"); err != nil {
+ t.Fatalf("ensureDropInDir(fresh) = %v", err)
+ }
+ st, _ := os.Lstat(filepath.Join(root, "fresh.service.d"))
+ if !st.IsDir() || st.Mode().Perm() != 0o755 {
+ t.Fatalf("fresh dir mode = %v", st.Mode())
+ }
+}
+
+func TestListMarkedDropInsSkipsSymlinkedDirs(t *testing.T) {
+ root := t.TempDir()
+ elsewhere := t.TempDir()
+ os.WriteFile(filepath.Join(elsewhere, DropInFile), renderDropIn("/e", "h"), 0o644)
+ os.Symlink(elsewhere, filepath.Join(root, "retired.service.d"))
+ os.MkdirAll(filepath.Join(root, "real.service.d"), 0o755)
+ os.WriteFile(filepath.Join(root, "real.service.d", DropInFile), renderDropIn("/e", "h"), 0o644)
+ units, skipped := listMarkedDropIns(root)
+ if len(units) != 1 || units[0] != "real.service" {
+ t.Fatalf("units = %v, want [real.service]", units)
+ }
+ if len(skipped) != 1 || skipped[0] != "retired.service" {
+ t.Fatalf("skipped = %v, want [retired.service]", skipped)
+ }
+ if _, err := os.Stat(filepath.Join(elsewhere, DropInFile)); err != nil {
+ t.Fatal("the file behind the symlink must be untouched")
+ }
+}
+
+func TestRemoveDropIn(t *testing.T) {
+ root := t.TempDir()
+ ensureDropInDir(root, "relay.service")
+ writeDropIn(root, "relay.service", renderDropIn("/e", "h"))
+ if err := removeDropIn(root, "relay.service"); err != nil {
+ t.Fatalf("removeDropIn: %v", err)
+ }
+ if _, exists, _, _ := readDropIn(root, "relay.service"); exists {
+ t.Fatal("drop-in still present")
+ }
+ if _, err := os.Stat(filepath.Join(root, "relay.service.d")); err != nil {
+ t.Fatal("the directory must never be removed")
+ }
+}