package host import ( "context" "encoding/json" "errors" "github.com/carlosframework/platform/internal/hostops" "github.com/carlosframework/platform/internal/hostwrite" "github.com/carlosframework/platform/internal/instancectl" "github.com/carlosframework/platform/internal/provision" "github.com/carlosframework/platform/internal/registry" "github.com/carlosframework/platform/internal/release" "github.com/carlosframework/platform/internal/rootstate" "io/fs" "os" "path/filepath" "slices" "strings" ) const claimNamedUnit4aComplete = false var openClaimActivationState = rootstate.Open func claimActivationPreflight(reg *registry.Registry) error { if reg == nil { return hostwrite.ErrUnavailable } if claimNamedUnit4aComplete { return nil } routes, err := reg.List() if err != nil { return err } for _, route := range routes { if route.Unit != "" || route.Backing == "unit" { return hostwrite.ErrUnavailable } } return nil } func claimActivationVerified(ctx context.Context, store release.Store, r ClaimRuntimeResources) error { if claimNamedUnit4aComplete { return nil } apps, err := hostops.Apps(ctx, store) if err != nil { return err } cfg := provisionTickConfig(os.Getenv) for _, app := range apps { records, err := hostops.Records(ctx, store, app.Account, app.Name) if err != nil { return err } for _, row := range records { if row.Holder.Family != "instance" { continue } var rec provision.Record if json.Unmarshal(row.Body, &rec) != nil { return hostwrite.ErrUnavailable } if rec.Region != "" && rec.Region != cfg.Region || rec.Pool != "" && !slices.Contains(cfg.Pools, rec.Pool) { continue } if rec.Unit != "" { return hostwrite.ErrUnavailable } } } if err := claimActivationLocalUnits(r); err != nil { return err } if r.Mode == "agent" { return nil } shapes, err := productionClaimUnitShapes() if err != nil { return err } if len(shapes) != 0 { return hostwrite.ErrUnavailable } for _, dir := range []string{"/var/lib/carlos-claims/unit-bindings", "/var/lib/carlos-claims/unit-materializations"} { state, err := openClaimActivationState(dir, false, false) if errors.Is(err, os.ErrNotExist) { continue } if err != nil { return err } names, err := state.Names() state.Close() if err != nil { return err } for _, name := range names { if !strings.HasSuffix(name, ".lock") { return hostwrite.ErrUnavailable } } } return nil } func claimActivationLocalUnits(r ClaimRuntimeResources) error { if r.Mode != "agent" && os.Geteuid() == 0 { for _, dir := range []string{"/var/lib/carlos-app", "/var/lib/private/carlos-app"} { entries, err := os.ReadDir(dir) if errors.Is(err, os.ErrNotExist) { continue } if err != nil { return err } if len(entries) != 0 { return hostwrite.ErrUnavailable } } } for _, dir := range []string{r.UnitsDir, "/etc/systemd/system", "/run/systemd/system", "/usr/lib/systemd/system", "/lib/systemd/system", "/etc/carlos/apps"} { if dir == "" { continue } err := filepath.WalkDir(dir, func(path string, entry fs.DirEntry, err error) error { if errors.Is(err, os.ErrNotExist) && path == dir { return nil } if err != nil { return err } name := entry.Name() if strings.HasPrefix(name, "carlos-app@") && name != "carlos-app@.service" && name != "carlos-app@.socket" { return hostwrite.ErrUnavailable } if entry.IsDir() { if r.Mode == "agent" && filepath.Dir(path) == "/run/systemd/system" && strings.HasSuffix(name, ".d") { role, _, ordinary := instancectl.ParseUnitName(strings.TrimSuffix(name, ".d")) if ordinary && role == instancectl.RoleRun { return fs.SkipDir } } return nil } if dir == "/etc/carlos/apps" { return hostwrite.ErrUnavailable } if !strings.HasSuffix(name, ".service") && !strings.HasSuffix(name, ".conf") { return nil } if _, err := unitsFS.ReadFile("units/" + name); err == nil { return nil } raw, err := os.ReadFile(path) if err != nil { return err } for _, marker := range []string{filepath.Join(r.OptDir, "live") + "/", "/opt/carlos/live/", "/etc/carlos/apps/", "User=carlos", "User = carlos"} { if strings.Contains(string(raw), marker) { return hostwrite.ErrUnavailable } } return nil }) if err != nil { return err } } return nil }