package instancectl import ( "context" "encoding/json" "errors" "fmt" "os" "path/filepath" "strings" "github.com/carlosframework/platform/internal/provision" "github.com/carlosframework/platform/internal/safefile" ) // Only root configures these roots; requests cannot choose a source or target. // The private source hides secrets on the host. Inside the isolated namespace // the verified file is readable but cannot be replaced by the tenant/agent. func (u ClaimUnits) launchPaths(host string) (string, string, error) { for _, root := range []string{u.UnitRoot, u.ExeRoot, u.DataRoot} { if !filepath.IsAbs(root) || filepath.Clean(root) != root || strings.ContainsAny(root, " \t\r\n\x00:%") { return "", "", ErrClaimUnit } } if !provision.ValidHostShape(host) { return "", "", ErrClaimUnit } unit, _, err := u.Placement(host) if err != nil { return "", "", err } return filepath.Join(u.ExeRoot, ".claim", provision.InstanceID(host)), filepath.Join(u.UnitRoot, unit+".d"), nil } func (u ClaimUnits) freezeLaunch(ctx context.Context, host string, launch Launch) error { source, dropin, err := u.launchPaths(host) if err != nil { return err } if launch.Host != host || launch.Schema != LaunchSchema { return ErrClaimUnit } // These are root-owned trees, opened without symlinks or permissive modes. for _, dir := range []string{u.ExeRoot, filepath.Dir(source), source, u.UnitRoot, dropin} { if err := claimRootDir(dir); err != nil { return fmt.Errorf("root directory %s: %w", dir, err) } } private, err := safefile.OpenDir(filepath.Dir(source)) if err != nil { return err } err = private.Chmod(0700) closeErr := private.Close() if err != nil || closeErr != nil { return errors.Join(err, closeErr) } raw, err := json.Marshal(launch) if err != nil { return err } if err := safefile.WriteFile(filepath.Join(source, "launch.json"), raw, 0444); err != nil { return err } destination := filepath.Join(provision.InstanceDir(u.DataRoot, host), LaunchFileName(RoleRun)) body := "[Service]\nBindReadOnlyPaths=" + filepath.Join(source, "launch.json") + ":" + destination + "\n" if err := safefile.WriteFile(filepath.Join(dropin, "60-claim-launch.conf"), []byte(body), 0644); err != nil { return err } _, err = u.Systemctl(ctx, "daemon-reload") return err } func (u ClaimUnits) clearLaunch(ctx context.Context, host string) error { // Ordinary Start users do not configure the snapshot roots. if u.UnitRoot == "" { return nil } source, dropin, err := u.launchPaths(host) if err != nil { return err } // Each caller first proved the unit empty (or has not issued start). err = safefile.Remove(filepath.Join(dropin, "60-claim-launch.conf")) if err != nil && !errors.Is(err, os.ErrNotExist) { return err } if _, err = u.Systemctl(ctx, "daemon-reload"); err != nil { return err } for _, name := range []string{"launch.json", "permit.json", "exec.sock"} { if err := safefile.Remove(filepath.Join(source, name)); err != nil && !errors.Is(err, os.ErrNotExist) { return err } } return nil }