visit
As we started setting up GoReleaser, we originally used the caveats
, install
, and plist
blocks of the brews section in GoReleaser to create our formula.
The caveats
block can be used to relay textual information to the user after a homebrew installation. We use it to:
Inside the install
block, you can use the same brew shortcuts for installation used in the formula file itself. This ultimately will copy these same lines to the ruby formula file’s install block. For example, we use:
prefix.install
to copy files and directories to homebrew’s versioned formula directoryprefix.install_symlink
to create symlinks in homebrew’s versioned formula directoryetc.install
to copy files and directories to homebrew’s shared etc directorybin.install
to copy binary executable files to homebrew’s versioned formula’s “bin” directorylib.install
to copy library files to homebrew’s versioned formula’s “lib” directory
The plist
block was where we defined a plist file to allow our software to be run as a launchd service. The service
block wasn’t supported in GoReleaser when we started, once it was, we shifted to using that as it was easier to define for us and allowed a more brew native way of managing our service.
Our original plist
block looked like the XML below:
plist: |
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "//www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.observiq.collector</string>
<key>ProgramArguments</key>
<array>
<string>#{bin}/observiq-otel-collector</string>
<string>--config</string>
<string>#{prefix}/config.yaml</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>StandardErrorPath</key>
<string>/tmp/observiq-otel-collector.log</string>
<key>StandardOutPath</key>
<string>/tmp/observiq-otel-collector.log</string>
</dict>
</plist>
Once we saw that GoReleaser supported the service
block we were able to simplify it to the following:
service: |
run [opt_bin/"observiq-otel-collector", "--config", opt_prefix/"config.yaml"]
environment_variables OIQ_OTEL_COLLECTOR_HOME: opt_prefix
keep_alive true
Path Variable | Path |
---|---|
opt_prefix | $HOMEBREW_PREFIX/opt/formula_name |
opt_bin | opt_prefix/bin |
opt_include | opt_prefix/include |
opt_lib | opt_prefix/lib |
brews:
- name: observiq-otel-collector
tap:
owner: observIQ
name: homebrew-observiq-otel-collector
branch: main
download_strategy: CurlDownloadStrategy
folder: Formula
url_template: //github.com/observIQ/observiq-otel-collector/releases/download/{{ .Tag }}/{{ .ArtifactName }}
commit_author:
name: observiq
email: [email protected]
homepage: "//github.com/observIQ/observiq-otel-collector"
license: "Apache 2.0"
caveats: |
****************************************************************
The configuration file that is run by the service is located at #{prefix}/config.yaml.
If you are configuring a logreceiver the plugin directory is located at #{prefix}/plugins.
****************************************************************
****************************************************************
Below are services commands to run to manage the collector service.
If you wish to run the collector at boot prefix these commands with sudo
otherwise the service will run at login.
To start the collector service run:
brew services start observiq/observiq-otel-collector/observiq-otel-collector
To stop the collector service run:
brew services stop observiq/observiq-otel-collector/observiq-otel-collector
To restart the collector service run:
brew services restart observiq/observiq-otel-collector/observiq-otel-collector
****************************************************************
****************************************************************
To uninstall the collector and its dependencies run the following commands:
brew services stop observiq/observiq-otel-collector/observiq-otel-collector
brew uninstall observiq/observiq-otel-collector/observiq-otel-collector
launchctl remove com.observiq.collector
# If you moved the opentelemetry-java-contrib-jmx-metrics.jar
sudo rm /opt/opentelemetry-java-contrib-jmx-metrics.jar
****************************************************************
install: |
bin.install "observiq-otel-collector"
prefix.install "LICENSE", "config.yaml"
prefix.install Dir["plugins"]
lib.install "opentelemetry-java-contrib-jmx-metrics.jar"
service: |
run [opt_bin/"observiq-otel-collector", "--config", opt_prefix/"config.yaml"]
environment_variables OIQ_OTEL_COLLECTOR_HOME: opt_prefix
keep_alive true
One issue we eventually stumbled upon was versioning our software releases with Homebrew. We found after every release, GoReleaser would update the Formula repo by overwriting the previous formula. A user could update the formula and run brew upgrade
to easily get the latest version. The issue we ran into was what if you wanted a specific version of the Collector with a specific brew formula? You would have to know which commit in the Formula corresponds to the release you want. That’s not very user-friendly.
To version a formula there are a few special things to do. The formula name needs to be of the format [email protected]
. The added @major.minor.patch
allows Homebrew to know which formula to get when specified in the brew command. Inside the formula the class name must have special formatting too. It must be of the format FormulaNameAT{Major}{Minor}{Patch}
. So an example filename and corresponding class name for our Collector is [email protected]
and ObserviqOtelCollectorAT060
, respectively.
That formula file will exist in the Formula
directory of your repo next to the current main formula, the formula you get if you just run brew install X
. You can also add a version to the main formula so users can get it by version or by the basic brew command. To do this, you create an Aliases
directory on the same level as your Formula
directory. Inside that directory create a symlink to the main formula with a versioned name.
cd Aliases && ln -s ../Formula/observiq-otel-collector.rb [email protected]
Now that we know how to create a versioned formula we need to update our GoReleaser config to generate versioned formulas for us. This should be simple since the formula and class names are taken from the name
field under the brews
block. We changed our name
to observiq-otel-collector@{{ .Major }}.{{ .Minor }}.{{ .Patch }}
. When we ran a test release with GoReleaser though we saw the class for the formula wasn’t quite right. GoReleaser was generating the class name as ObserviqOtelCollectorAT0_6_0
. One quick to GoReleaser and we fixed that.
Here’s what our brews
block of our GoReleaser config now looks like to support versions.
We also made some changes to the configuration of the Collector so there are some additional flags and files in the install
and service
blocks.
brews:
- name: observiq-otel-collector@{{ .Major }}.{{ .Minor }}.{{ .Patch }}
description: "observIQ's distribution of the OpenTelemetry Collector"
tap:
owner: observIQ
name: homebrew-observiq-otel-collector
branch: main
download_strategy: CurlDownloadStrategy
folder: Formula
url_template: //github.com/observIQ/observiq-otel-collector/releases/download/{{ .Tag }}/{{ .ArtifactName }}
commit_author:
name: observiq
email: [email protected]
homepage: "//github.com/observIQ/observiq-otel-collector"
license: "Apache 2.0"
caveats: |
****************************************************************
The configuration file that is run by the service is located at #{prefix}/config.yaml.
If you are configuring a logreceiver the plugin directory is located at #{prefix}/plugins.
****************************************************************
****************************************************************
Below are services commands to run to manage the collector service.
If you wish to run the collector at boot prefix these commands with sudo
otherwise the service will run at login.
To start the collector service run:
brew services start observiq/observiq-otel-collector/observiq-otel-collector@{{ .Major }}.{{ .Minor }}.{{ .Patch }}
To stop the collector service run:
brew services stop observiq/observiq-otel-collector/observiq-otel-collector@{{ .Major }}.{{ .Minor }}.{{ .Patch }}
To restart the collector service run:
brew services restart observiq/observiq-otel-collector/observiq-otel-collector@{{ .Major }}.{{ .Minor }}.{{ .Patch }}
****************************************************************
****************************************************************
To uninstall the collector and its dependencies run the following commands:
brew services stop observiq/observiq-otel-collector/observiq-otel-collector@{{ .Major }}.{{ .Minor }}.{{ .Patch }}
brew uninstall observiq/observiq-otel-collector/observiq-otel-collector@{{ .Major }}.{{ .Minor }}.{{ .Patch }}
# If you moved the opentelemetry-java-contrib-jmx-metrics.jar
sudo rm /opt/opentelemetry-java-contrib-jmx-metrics.jar
****************************************************************
install: |
bin.install "observiq-otel-collector"
prefix.install "LICENSE", "config.yaml", "VERSION.txt", "logging.yaml"
prefix.install Dir["plugins"]
lib.install "opentelemetry-java-contrib-jmx-metrics.jar"
service: |
run [opt_bin/"observiq-otel-collector", "--config", opt_prefix/"config.yaml", "--logging", opt_prefix/"logging.yaml", "--manager", opt_prefix/"manager.yaml"]
environment_variables OIQ_OTEL_COLLECTOR_HOME: opt_prefix
keep_alive true
Initially, in our install
block of the GoReleaser config, we were using prefix.install
to place our configuration file in the main install directory of our formula.
install: |
…
prefix.install "LICENSE", "VERSION.txt", "config.yaml"
…
Ultimately, the solution was to use Homebrew’s etc
directory. This is a shared directory amongst all formulas, so we had to make an extra effort to ensure our configuration file would be uniquely named. Now our GoReleaser install block looks something like this:
install: |
…
prefix.install "LICENSE", "VERSION.txt"
etc.install "config.yaml" => "observiq_config.yaml"
…
install: |
…
prefix.install "LICENSE", "VERSION.txt"
etc.install "config.yaml" => "observiq_config.yaml"
prefix.install_symlink etc/"observiq_config.yaml" => "config.yaml"
With this solution, our configuration lived safely in Homebrew’s etc
directory with a special prefix and where it would never be automatically overwritten. At the same time, it would appear to also exist in the base installation directory without any naming prefix.
There is one more thing to note here. When there is a new installation on top of our formula, homebrew automatically adds a new version of our configuration file to its etc
directory. In our case, the file is named something like observiq_config.yaml.default
. This will contain a clean config with default settings. This is built-in behavior for Homebrew, and we haven’t found any way to change this.