Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ This page lists the `flock` commands. The other pages show where to find each ac
| `flock uninstall [--purge]` | Remove Flock. Keeps site settings and databases unless `--purge` is given |
| `flock status` | Show the state of the daemon and its services |
| `flock restart` | Restart the daemon and all services |
| `flock start` | Start the daemon and all services after `flock stop` |
| `flock stop` | Stop the daemon and all services until `flock start` |
| `flock logs [name] [-n 50] [-f]` | Show service logs (daemon, web, php8.5-fpm on macOS and Linux or php8.5-cgi on Windows, postgres, …). Without a name, lists the logs |
| `flock update` | Update Flock, CLI and daemon included: the app installs the new version (on Linux, apt or dnf) |
| `flock version` | Print the Flock version |
Expand Down
14 changes: 14 additions & 0 deletions docs/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,17 @@ Stop that program, then click **Restart** at the bottom of the Flock panel. On L
:::cli
Stop that program and run `flock restart`.
:::

## Stopping Flock

Quitting the menu bar or tray app leaves Flock running, so your sites keep working. To stop Flock and everything it runs, for example to free memory, or ports 80 and 443 for another local environment:

:::app
On macOS, switch off the switch at the top of the Flock panel. Switch it on to start Flock again.
:::

:::cli
Run `flock stop`, and `flock start` to start Flock again.
:::

Flock starts again at your next login.
21 changes: 18 additions & 3 deletions internal/cli/install_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -487,11 +487,26 @@ func appDirs() []string {
}
}

// restartService makes launchd kill and relaunch the daemon.
// stopDaemon unloads the agent, which stops the daemon and everything it runs, until restartService
// or the next login loads it again.
func stopDaemon() error {
stopService("gui/" + strconv.Itoa(os.Getuid()))
return nil
}

// restartService makes launchd kill and relaunch the daemon, or loads the agent after `flock stop`.
func restartService() error {
target := "gui/" + strconv.Itoa(os.Getuid()) + "/" + launchLabel
domain := "gui/" + strconv.Itoa(os.Getuid())
target := domain + "/" + launchLabel
if err := exec.Command("launchctl", "print", target).Run(); err != nil {
return fmt.Errorf("the background daemon is not installed — run `flock install`")
path, _ := plistPath()
if !serviceInstalled() {
return fmt.Errorf("the background daemon is not installed — run `flock install`")
}
if out, err := exec.Command("launchctl", "bootstrap", domain, path).CombinedOutput(); err != nil {
return fmt.Errorf("launchctl bootstrap: %v: %s", err, out)
}
return nil
}
if out, err := exec.Command("launchctl", "kickstart", "-k", target).CombinedOutput(); err != nil {
return fmt.Errorf("launchctl kickstart: %v: %s", err, out)
Expand Down
5 changes: 5 additions & 0 deletions internal/cli/install_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,11 @@ func uninstallService() error {
return systemctl("stop", unitName(name))
}

// stopDaemon makes systemd stop the daemon until restartService or the next boot starts it.
func stopDaemon() error {
return uninstallService()
}

// restartService makes systemd stop and start the daemon.
func restartService() error {
name, err := serviceUser()
Expand Down
12 changes: 12 additions & 0 deletions internal/cli/install_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,18 @@ func stopDaemonProcess() {
_ = exec.Command("taskkill", "/F", "/IM", "flockd.exe").Run()
}

// stopDaemon ends the scheduled task and the daemon, until restartService or the next login starts it.
func stopDaemon() error {
root, err := paths.Root()
if err != nil {
return err
}
_ = exec.Command("schtasks", "/End", "/TN", taskName).Run()
stopDaemonProcess()
waitDaemonGone(root)
return nil
}

// restartService ends the scheduled task (killing the daemon and everything it runs) and starts it again.
func restartService() error {
root, err := paths.Root()
Expand Down
31 changes: 29 additions & 2 deletions internal/cli/restart.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@ func newRestartCommand() *cobra.Command {
return err
}

before, _ := s.client().Status(cmd.Context())
before, err := s.client().Status(cmd.Context())
if err == nil && cmd.Name() == "start" {
cmd.Printf("already running (pid %d)\n", before.PID)
return nil
}
if err := restartService(); err != nil {
return err
}
Expand All @@ -31,7 +35,7 @@ func newRestartCommand() *cobra.Command {
for time.Now().Before(deadline) {
st, err := s.client().Status(cmd.Context())
if err == nil && st.PID != before.PID {
cmd.Printf("restarted (pid %d)\n", st.PID)
cmd.Printf("%sed (pid %d)\n", cmd.Name(), st.PID) // restarted, started
for _, w := range st.Warnings {
cmd.Println("warning: " + w)
}
Expand All @@ -46,3 +50,26 @@ func newRestartCommand() *cobra.Command {
},
}
}

// newStartCommand starts the daemon after `flock stop` (restartService also loads it again) and
// leaves a running one alone.
func newStartCommand() *cobra.Command {
cmd := newRestartCommand()
cmd.Use, cmd.Short = "start", "Start the background daemon and all services after `flock stop`"
return cmd
}

func newStopCommand() *cobra.Command {
return &cobra.Command{
Use: "stop",
Short: "Stop the background daemon and all services until `flock start`",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, _ []string) error {
if err := stopDaemon(); err != nil {
return err
}
cmd.Println("stopped")
return nil
},
}
}
2 changes: 1 addition & 1 deletion internal/cli/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func NewRootCommand() *cobra.Command {

root.AddCommand(
newInstallCommand(), newConflictsCommand(), newUninstallCommand(), newPrivilegedSetupCommand(),
newDaemonCommand(), newRestartCommand(), newStatusCommand(), newLogsCommand(), newVersionCommand(), newUpdateCommand(),
newDaemonCommand(), newRestartCommand(), newStartCommand(), newStopCommand(), newStatusCommand(), newLogsCommand(), newVersionCommand(), newUpdateCommand(),
newSitesCommand(), newLinkCommand(), newUnlinkCommand(), newParkCommand(), newUnparkCommand(), newProxyCommand(), newUnproxyCommand(), newProxiesCommand(),
newSecureCommand(true), newSecureCommand(false), newDomainCommand(), newWorktreeCommand(), newShareCommand(), newUseCommand(),
newIsolateCommand(), newUnisolateCommand(), newIsolatedCommand(), newPinCommand(), newUnpinCommand(), newRootCommand(),
Expand Down
16 changes: 15 additions & 1 deletion macos/Sources/FlockUI/AppModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,20 @@ final class AppModel {

/// Runs `flock restart` (which also starts a stopped daemon via launchd).
func restart() async {
await run("restart")
}

/// Runs `flock start` after `stop`.
func start() async {
await run("start")
}

/// Runs `flock stop`: the daemon and everything it runs stop until `start`.
func stop() async {
await run("stop")
}

private func run(_ command: String) async {
isRestarting = true
defer { isRestarting = false }

Expand All @@ -221,7 +235,7 @@ final class AppModel {
: "Flock is not installed for this user (\(binary.path) is missing) — run `flock install` once."
return
}
let result = await Installation.run(binary, ["restart"])
let result = await Installation.run(binary, [command])
actionError = result.ok ? nil : result.output
await refresh()
}
Expand Down
23 changes: 10 additions & 13 deletions macos/Sources/FlockUI/PanelView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,14 @@ struct PanelView: View {
if model.isRestarting || model.isUpdating {
ProgressView().controlSize(.small)
}
// Flock's on/off switch: off stops the daemon and everything it runs.
if !model.needsSetup {
Toggle("Flock", isOn: Binding(get: { model.isRunning }, set: { on in Task { on ? await model.start() : await model.stop() } }))
.labelsHidden()
.toggleStyle(.switch)
.controlSize(.small)
.disabled(model.isRestarting || model.isUpdating)
}
}
}

Expand Down Expand Up @@ -199,10 +207,10 @@ struct PanelView: View {
private func toggles(_ status: Status) -> some View {
VStack(alignment: .leading, spacing: 6) {
Toggle(isOn: Binding(get: { status.xdebug }, set: { on in Task { await model.setXdebug(on) } })) {
Label("Xdebug", systemImage: "ladybug")
Label("Xdebug", systemImage: "ladybug").frame(maxWidth: .infinity, alignment: .leading)
}
Toggle(isOn: Binding(get: { status.dumps }, set: { on in Task { await model.setDumps(on) } })) {
Label("Capture dumps & queries", systemImage: "tray.and.arrow.down")
Label("Capture dumps & queries", systemImage: "tray.and.arrow.down").frame(maxWidth: .infinity, alignment: .leading)
}
}
.toggleStyle(.switch)
Expand Down Expand Up @@ -236,17 +244,6 @@ struct PanelView: View {
}
.buttonStyle(.borderedProminent)
}
} else {
VStack(alignment: .leading, spacing: 8) {
Text(model.isUpdating ? "Updating Flock…" : model.error ?? "Flock is not running")
.font(.callout)
.foregroundStyle(.secondary)
.fixedSize(horizontal: false, vertical: true)
Button("Start Flock") {
Task { await model.restart() }
}
.disabled(model.isRestarting || model.isUpdating)
}
}
}

Expand Down
Loading