diff --git a/internal/command/launch/cmd.go b/internal/command/launch/cmd.go index 3894dce5c5..6e15f8634a 100644 --- a/internal/command/launch/cmd.go +++ b/internal/command/launch/cmd.go @@ -140,6 +140,11 @@ func New() (cmd *cobra.Command) { Name: "no-create", Description: "Do not create an app, only generate configuration files", }, + flag.String{ + Name: "auto-stop", + Description: "Automatically suspend the app after a period of inactivity. Valid values are 'off', 'stop', and 'suspend", + Default: "suspend", + }, ) cmd.AddCommand(NewPlan()) diff --git a/internal/command/launch/launch.go b/internal/command/launch/launch.go index c68a3c8786..f12f101b04 100644 --- a/internal/command/launch/launch.go +++ b/internal/command/launch/launch.go @@ -6,8 +6,10 @@ import ( "path/filepath" "strings" + "github.com/docker/go-units" fly "github.com/superfly/fly-go" "github.com/superfly/fly-go/flaps" + "github.com/superfly/flyctl/helpers" "github.com/superfly/flyctl/internal/appconfig" "github.com/superfly/flyctl/internal/command/launch/plan" "github.com/superfly/flyctl/internal/flag" @@ -169,12 +171,40 @@ func (state *launchState) updateConfig(ctx context.Context) { if state.env != nil { state.appConfig.SetEnvVariables(state.env) } + + state.appConfig.Compute = state.Plan.Compute + if state.Plan.HttpServicePort != 0 { + autostop := fly.MachineAutostopStop + autostopFlag := flag.GetString(ctx, "auto-stop") + + if autostopFlag == "off" { + autostop = fly.MachineAutostopOff + } else if autostopFlag == "suspend" { + autostop = fly.MachineAutostopSuspend + + // if any compute has a GPU or more than 2GB of memory, set autostop to stop + for _, compute := range state.appConfig.Compute { + if compute.MachineGuest != nil && compute.MachineGuest.GPUKind != "" { + autostop = fly.MachineAutostopStop + break + } + + if compute.Memory != "" { + mb, err := helpers.ParseSize(compute.Memory, units.RAMInBytes, units.MiB) + if err != nil || mb >= 2048 { + autostop = fly.MachineAutostopStop + break + } + } + } + } + if state.appConfig.HTTPService == nil { state.appConfig.HTTPService = &appconfig.HTTPService{ ForceHTTPS: true, AutoStartMachines: fly.Pointer(true), - AutoStopMachines: fly.Pointer(fly.MachineAutostopStop), + AutoStopMachines: fly.Pointer(autostop), MinMachinesRunning: fly.Pointer(0), Processes: []string{"app"}, } @@ -183,7 +213,6 @@ func (state *launchState) updateConfig(ctx context.Context) { } else { state.appConfig.HTTPService = nil } - state.appConfig.Compute = state.Plan.Compute } // createApp creates the fly.io app for the plan diff --git a/test/preflight/apps_v2_integration_test.go b/test/preflight/apps_v2_integration_test.go index 700e4083de..66fb8d2da1 100644 --- a/test/preflight/apps_v2_integration_test.go +++ b/test/preflight/apps_v2_integration_test.go @@ -51,7 +51,7 @@ func TestAppsV2Example(t *testing.T) { require.NotNil(t, firstMachine.Config.Services[0].Autostop) assert.Equal( - t, fly.MachineAutostopStop, *firstMachine.Config.Services[0].Autostop, + t, fly.MachineAutostopSuspend, *firstMachine.Config.Services[0].Autostop, "autostop must be enabled", ) diff --git a/test/preflight/fly_launch_test.go b/test/preflight/fly_launch_test.go index 1782004f78..6a11ad42a4 100644 --- a/test/preflight/fly_launch_test.go +++ b/test/preflight/fly_launch_test.go @@ -50,7 +50,7 @@ func TestFlyLaunchV2(t *testing.T) { "http_service": map[string]any{ "force_https": true, "internal_port": int64(8080), - "auto_stop_machines": "stop", + "auto_stop_machines": "suspend", "auto_start_machines": true, "min_machines_running": int64(0), "processes": []any{"app"},