Skip to content

Add launchd daemon support for macos#16

Merged
brasic merged 15 commits intoBlakeWilliams:mainfrom
brasic:launchd
Feb 3, 2023
Merged

Add launchd daemon support for macos#16
brasic merged 15 commits intoBlakeWilliams:mainfrom
brasic:launchd

Conversation

@brasic
Copy link
Collaborator

@brasic brasic commented Oct 3, 2022

This adds a new service subcommand that sets up rdm to run as a launchd service on MacOS.

$ rdm service
Manage this program as a launchd system service.
  Status of gui/501/com.blakewilliams.rdm:
    Install state: [Installed]
    Run state:     [Running]

Usage:
  rdm service [command]

Available Commands:
  install     Configures rdm to run on boot as a MacOS LaunchAgent.
  start       Starts the launchd service.
  stop        Stops the launchd service.
  uninstall   Removes a previously installed LaunchAgent installation.

Flags:
  -h, --help   help for service

Use "rdm service [command] --help" for more information about a command.

Here's a quick demo:

asciicast

I originally wrote all the launchd management plumbing in this PR but after a while I realized it was kind of unrelated to this project so I spun it into a new library at https://github.com/brasic/launchd.

https://github.com/brasic/launchd

Also add support for starting, stopping and uninstalling the service.
@brasic brasic marked this pull request as ready for review October 31, 2022 18:15
This pulls in a breaking change from launchd:
brasic/launchd@f5c0c73

Now the signature of ForRunningProgram has no error so we don't need to
handle it.
@brasic brasic changed the title Launchd Add launchd daemon support for macos Oct 31, 2022
@brasic brasic requested a review from BlakeWilliams October 31, 2022 23:59
Copy link
Owner

@BlakeWilliams BlakeWilliams left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

left a few minor comments/questions, but LGTM

return
}

fmt.Printf("Configured to start at boot. Uninstall using:\n\t%s service uninstall\n", currentExecutableName())
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should these Printlns use the logger? The logger should only be file backed for the server command.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't quite grok this part of your question:

The logger should only be file backed for the server command.

That said, the service commands are always intended to be invoked by humans and these strings are for interactive presentation at the time they invoke the command. Can you help me understand why a log line would be better here?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That comment had some built-in assumed context, sorry about that.

I've used the logger to output to STDOUT in most places iirc, except for server so there's a consistent interface/place to log. That way it's easy to move how/where things are logged to without making a ton of sweeping changes. e.g. if we wanted a silent version of this command.

If there's a meaningful difference between logging via fmt.Printf and the logger, feel free to ignore me. 👍

Copy link
Collaborator Author

@brasic brasic Nov 12, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The meaningful difference is this one:

./remote-development-manager service stop
2022/11/12 10:23:31 Service is not running, nothing to do.

vs

./remote-development-manager service stop
Service is not running, nothing to do.

From an aesthetic POV if I run a command and see a message meant for me to read immediately I would think something was broken or wrong if it looked like a log entry.

This is definitely nitpicking and bikeshedding but there's a difference between "event messages" from a running daemon in a log that could happen at any time and record when a thing happened and "response messages" from a command which only happen as a direct response to user input to let them know the result of their action.

WDYT about one of the following two options?

  1. passing a separate "interactive" logger configured without timestamps to cobra command objects when invoked from the CLI? Then we could use the logger interface here without the ugly timestamps, and obtain the ability to use these things in a headless context in the future by passing them a different logger.
  2. Swapping most of these messages that are really just "hey, you asked me to start this service and I did so I'm going tell you to be nice" to use stderr instead.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is definitely nitpicking and bikeshedding but there's a difference between "event messages" from a running daemon in a log that could happen at any time and record when a thing happened and "response messages" from a command which only happen as a direct response to user input to let them know the result of their action.

Good point, I agree with that.

  • passing a separate "interactive" logger configured without timestamps to cobra command objects when invoked from the CLI? Then we could use the logger interface here without the ugly timestamps, and obtain the ability to use these things in a headless context in the future by passing them a different logger.

No strong feelings on which approach we take (feel free to use your best judgement), but the advantage of being able to log the information no matter the context (headless or interactive, etc.) makes me lean slightly towards this one.

Copy link
Collaborator Author

@brasic brasic Nov 13, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel pretty good about the approach I took in 5c6ea00. In the process I found that there were a number of places where the default stderr logger was being used instead of the server logger field.

edit: and of course tests are failing 😅 Will take a look later.

Co-authored-by: Blake Williams <blakewilliams@github.com>
Carl Brasic and others added 5 commits November 1, 2022 11:40
Some logging changes exposed the fact that we were not actually shutting
down the server on SIGINT/TERM. Change that, and add some entries to the
server log indicating when a server came up and went down.
This consolidates everything under a single logger which is unprefixed
and delivered to stderr when being used for human-readable messages (ie
CLI context) and timestamp-prefixed and multiplexed to both the log file and
stdout when used in server context. We manage the transition from CLI
to long-lived process by reconfiguring the existing logger built in
main.go. One advantage of this is that things that need to log inside
main.go but the server log should know about (like signal receipt) get
sent to the right place instead of a now-likely-disconnected stderr.
These tests are order dependendent and not parallel safe, apparently because
some of them launch goroutines that outlive the tests themselves yet
continue to use package global resources like httpClient and the
implicitly global unix socket at path. Give each test its own copy of
these resources to fix flaky tests.
@brasic
Copy link
Collaborator Author

brasic commented Feb 3, 2023

Picking this up after a few months 😅

Turns out the test failures were pre-existing flakes, I've fixed them in the most recent commit.

@brasic brasic merged commit 3042a85 into BlakeWilliams:main Feb 3, 2023
@brasic brasic deleted the launchd branch February 3, 2023 14:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants