package host import ( "context" "crypto/ecdh" "os" "path/filepath" "github.com/carlosframework/platform/internal/activator" "github.com/carlosframework/platform/internal/appconfig" "github.com/carlosframework/platform/internal/edge" "github.com/carlosframework/platform/internal/provision" "github.com/carlosframework/platform/internal/registry" "github.com/carlosframework/platform/internal/release" ) // T3's production composition of a flat held row (moves design ยง7.3, // ruling F19). armPhase1 wires it as moveCfg.ComposeHeldEnv. // composeHeldEnv is T3's composition for a flat held row (ruling F19): a // scratch directory holding the configuration rendered from the merged // bundle (appconfig.LoadMergedHostVersions, rendered as RenderHeld renders // it) and the carried .env, an ExecBackend over it with this box's // region and cache, and a synthetic flat route; LaunchEnvironment composes // what the next spawn would get. func composeHeldEnv(priv *ecdh.PrivateKey, region string, cache bool) func(context.Context, release.Store, provision.Record, []byte) (map[string]string, map[string]bool, []string, provision.MoveInputs, error) { return func(ctx context.Context, s release.Store, rec provision.Record, carried []byte) (map[string]string, map[string]bool, []string, provision.MoveInputs, error) { m, v, err := appconfig.LoadMergedHostVersions(ctx, s, rec.Account, rec.App, rec.Environment, rec.Host) if err != nil { return nil, nil, nil, provision.MoveInputs{}, err } var rendered []byte if m != nil { if rendered, err = appconfig.RenderEnv(m, priv, appconfig.Binding{Account: rec.Account, App: rec.App}); err != nil { return nil, nil, nil, provision.MoveInputs{}, err } } tmp, err := os.MkdirTemp("", "carlos-move-t3-") if err != nil { return nil, nil, nil, provision.MoveInputs{}, err } defer os.RemoveAll(tmp) if err := os.WriteFile(filepath.Join(tmp, rec.Host+".config.env"), rendered, 0o600); err != nil { return nil, nil, nil, provision.MoveInputs{}, err } if carried != nil { if err := os.WriteFile(filepath.Join(tmp, rec.Host+".env"), carried, 0o600); err != nil { return nil, nil, nil, provision.MoveInputs{}, err } } eb := &activator.ExecBackend{DataDir: tmp, Region: region} if cache { eb.Cache = heldEnvCache } env, instance, shadowed, err := eb.LaunchLayers(registry.Route{Host: rec.Host, Socket: provision.DefaultSocket(rec.Host)}) if err != nil { return nil, nil, nil, provision.MoveInputs{}, err } in := provision.MoveInputs{BundleETags: map[string]string{"default": v.Default, "environment": v.Environment}, OverlayETag: v.Overlay} return env, instance, shadowed, in, nil } } // heldEnvCache is the non-nil cache T3's synthetic backend carries when the // live box has a response cache: LaunchEnvironment reads only whether one // is set (CARLOS_PURGE). var heldEnvCache = edge.NewCache(1, 1)