diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..bb56f1d --- /dev/null +++ b/.env.example @@ -0,0 +1,4 @@ +# Signing and notarization +AC_USERNAME= +AC_PASSWORD= +AC_PROVIDER= diff --git a/.envrc b/.envrc index 3550a30..3b11770 100644 --- a/.envrc +++ b/.envrc @@ -1 +1,2 @@ use flake +dotenv diff --git a/.github/MAINTAINERS_GUIDE.md b/.github/MAINTAINERS_GUIDE.md index 906ef6c..ceb132d 100644 --- a/.github/MAINTAINERS_GUIDE.md +++ b/.github/MAINTAINERS_GUIDE.md @@ -12,7 +12,16 @@ Hey there! It's about time... Watt have you been jouling!? ## Project setup -After setting up the project for normal usage, you're ready for development! +Building from source to reflect any code changes only takes a few fast steps. + +1. Install the latest version of [Go][golang]. +2. From a directory for development, download the source and compile `etime`: + +```sh +$ git clone https://github.com/zimeg/emporia-time.git +$ cd emporia-time +$ make build +``` An [understanding of Go][learn_go] is a likely prerequisite for any programming and can be an enjoyable language to learn! @@ -35,7 +44,7 @@ currently using the following structure: - `/` – primary project files and metadata for the repository - `.github/` – information for collaboration and continuous integrations -- `cmd/` - controllers for the different stages of the command +- `cmd/` – controllers for the different stages of the command - `internal/` – helpful utilities needed to create the program - `pkg/` – various concerns that are pieced together to form the program @@ -44,6 +53,8 @@ currently using the following structure: For ease of development, some commands are added in a `Makefile`: - `make build` – build the program binary +- `make staging` – package a distribution +- `make release` – sign and notarize packages - `make test` – perform the written code tests - `make clean` – remove all program artifacts @@ -107,13 +118,60 @@ the following steps can be taken: 3. Commit these changes to a branch called by the version name – e.g. `v1.2.3` 4. Open then merge a pull request with these changes 5. Draft a [new release][releases] using the version name and entries from the -`CHANGELOG.md` + `CHANGELOG.md` 6. Publish this as the latest release! 7. Close the current milestone for the latest release then create a new one In deciding a version number, best judgement should be used to follow [semantic versioning][semver]. +### Signing notarizations + +Packaging for the release process begins after a new version tag is created. + +Builds for various targets are made with [goreleaser][goreleaser] then signed by +[gon][gon] and uploaded to the action artifacts. + +Only compilations for macOS are signed at this time. Verifying binaries made for +other operating systems is left as an exercise for the developer. + +#### Keychaining certificates + +Certain credentials and certificates are requested for the signing processes. + +Apple holds the keys for [developer credentials][credentials] and +[system certificates][certificates]. A "Developer ID Application" is needed on +the system keychain and any missing but matching certificates too. + +Account information is also needed as environment variables in the `.env` file. + +#### Processing packages + +Signing and notarizing binaries is an automatic process that happens after +making a release build. + +Special tooling and a macOS system is required for this process. Tooling can be +setup with a packaging flake: + +```sh +$ flake develop .#gon +``` + +With the above ready the following commands will hopefully officiate things: + +```sh +$ make release # Build and notarize a release +$ gon .gon.hcl # Troubleshoot specific errors +``` + +#### Verifying a signature + +Unpackage the output disk image to make sure everything was successful with: + +```sh +$ spctl -a -vvv -t install ./etime +``` + ## Runner setup A self-hosted runner is used to verify valid measurements are made when @@ -127,6 +185,10 @@ in your action repository secrets using your Emporia information. Also add these for Dependabot to configure this workflow. +[certificates]: https://www.apple.com/certificateauthority/ +[credentials]: https://developer.apple.com/account/resources/certificates/list +[gon]: https://github.com/Bearer/gon +[goreleaser]: https://github.com/goreleaser/goreleaser [learn_go]: https://go.dev/learn/ [nix]: https://zero-to-nix.com [releases]: https://github.com/zimeg/emporia-time/releases diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a9ee40c..c41c92e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -26,7 +26,7 @@ jobs: - name: Install a flaked Nix uses: DeterminateSystems/nix-installer-action@v9 - name: Create snapshots - run: nix develop -c goreleaser release --clean --snapshot --skip=publish + run: nix develop -c goreleaser release --clean --snapshot --skip=publish --config .goreleaser.staging.yml - name: Collect the current version id: tag run: | @@ -101,15 +101,30 @@ jobs: release: name: Distribute a release if: ${{ startsWith(github.ref, 'refs/tags/') }} - runs-on: ubuntu-latest + runs-on: macos-latest steps: - name: Checkout the repo uses: actions/checkout@v4 with: fetch-depth: 0 + - name: Secure the keychain + run: | + echo $CERTIFICATE_P12 | base64 --decode > certificate.p12 + security create-keychain -p $KEYCHAIN_PASSWORD build.keychain + security default-keychain -s build.keychain + security unlock-keychain -p $KEYCHAIN_PASSWORD build.keychain + security import certificate.p12 -k build.keychain -P $CERTIFICATE_PASSWORD -T /usr/bin/codesign + security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k $KEYCHAIN_PASSWORD build.keychain + env: + CERTIFICATE_P12: ${{ secrets.CERTIFICATE_P12 }} + CERTIFICATE_PASSWORD: ${{ secrets.CERTIFICATE_PASSWORD }} + KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} - name: Install a flaked Nix uses: DeterminateSystems/nix-installer-action@v9 - name: Create releases - run: nix develop -c goreleaser release --clean + run: nix develop .#gon -c goreleaser release --clean --config .goreleaser.release.yml env: + AC_USERNAME: ${{ secrets.AC_USERNAME }} + AC_PASSWORD: ${{ secrets.AC_PASSWORD }} + AC_PROVIDER: ${{ secrets.AC_PROVIDER }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index e60bc8d..006b5be 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ # Built binaries +.gon.hcl etime dist/* @@ -8,3 +9,4 @@ dist/* # Environment settings .direnv +.env diff --git a/.goreleaser.release.yml b/.goreleaser.release.yml new file mode 100644 index 0000000..a65be8e --- /dev/null +++ b/.goreleaser.release.yml @@ -0,0 +1,52 @@ +# yaml-language-server: $schema=https://goreleaser.com/static/schema.json +version: 1 +before: + hooks: + - go mod tidy +builds: + - id: etime + binary: etime_{{.Summary}} + ldflags: + - -s -w -X main.version={{.Summary}} + env: + - CGO_ENABLED=0 + goos: + - linux + - windows + - id: etime_macos + binary: etime_{{.Summary}} + goos: + - darwin + hooks: + post: |- + sh -c ' + cat > .gon.hcl << EOF + source = ["./dist/etime_macos_{{.Target}}/etime_{{.Summary}}"] + bundle_id = "net.o526.etime" + sign { + application_identity = "Developer ID Application: Ethan Garrett Zimbelman (SA8WRA75UN)" + } + dmg { + output_path = "./dist/etime_{{.Target}}_{{.Summary}}.dmg" + volume_name = "etime_{{.Target}}" + } + EOF + gon .gon.hcl + ' +archives: + - format: tar.gz + name_template: >- + etime_ + {{- .Summary }}_ + {{- .Os }}_ + {{- if eq .Arch "amd64" }}x86_64 + {{- else if eq .Arch "386" }}i386 + {{- else }}{{ .Arch }}{{ end }} + {{- if .Arm }}v{{ .Arm }}{{ end }} + format_overrides: + - goos: windows + format: zip +changelog: + skip: true +checksum: + name_template: "etime_{{ .Summary }}_checksums.txt" diff --git a/.goreleaser.yml b/.goreleaser.staging.yml similarity index 100% rename from .goreleaser.yml rename to .goreleaser.staging.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index acd6da4..b5ea876 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Output outputs in an unbuffered manner as output happens - Capture timing information for erroneous commands +- Sign and notarize packaged binaries made for macOS ### Maintenance diff --git a/Makefile b/Makefile index 4f925c2..47cccd0 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: build test release clean +.PHONY: build test staging release clean BIN=etime VERSION="$(shell git describe --dirty --tags --always)" @@ -9,10 +9,14 @@ build: test: build go test ./... +staging: clean + goreleaser build --snapshot --config .goreleaser.staging.yml + release: clean - goreleaser build --snapshot + goreleaser build --snapshot --config .goreleaser.release.yml clean: rm -f $(BIN) rm -rf ~/.config/etime + rm -f .gon.hcl rm -rf dist diff --git a/README.md b/README.md index 8064e4a..efcfd96 100644 --- a/README.md +++ b/README.md @@ -12,17 +12,10 @@ The `time` command, with energy awareness. ## Getting started 1. Purchase an [Emporia Smart Plug][plug] and set your device up. -2. Install the latest version of [Go][golang]. -3. From a directory for development, download the source and compile `etime`: - -```sh -$ git clone https://github.com/zimeg/emporia-time.git -$ cd emporia-time -$ make build -``` - -4. Optionally, create a symbolic link to or move the compiled binary into your -`/bin` to run the command globally: +2. Install the [latest released version][releases] or + [build from source][source]. +3. Optionally, create a symbolic link to or move the compiled binary into your + `/bin` to run the command globally: ```sh $ ln -s ~/path/to/emporia-time/etime /usr/local/bin @@ -30,7 +23,7 @@ $ ln -s ~/path/to/emporia-time/etime /usr/local/bin $ mv etime /usr/local/bin ``` -5. Use the binary with your favorite command or script: +4. Use the binary with your favorite command or script: ```sh $ ./etime sleep 12 @@ -134,12 +127,14 @@ Details on the processes around code for this repository are shared in the [`.github/MAINTAINERS_GUIDE.md`][maintainers]. -[plug]: https://www.emporiaenergy.com/emporia-smart-plug -[golang]: https://go.dev/dl +[contributing]: ./.github/CONTRIBUTING.md [dashboard]: https://web.emporiaenergy.com/#/home -[time]: https://stackoverflow.com/a/556411 -[energy]: https://en.wikipedia.org/wiki/Energy -[power]: https://en.wikipedia.org/wiki/Power_(physics) [docs]: https://github.com/magico13/PyEmVue/blob/master/api_docs.md -[contributing]: ./.github/CONTRIBUTING.md +[energy]: https://en.wikipedia.org/wiki/Energy +[golang]: https://go.dev/dl [maintainers]: ./.github/MAINTAINERS_GUIDE.md +[plug]: https://www.emporiaenergy.com/emporia-smart-plug +[power]: https://en.wikipedia.org/wiki/Power_(physics) +[releases]: https://github.com/zimeg/emporia-time/releases +[source]: ./.github/MAINTAINERS_GUIDE.md#project-setup +[time]: https://stackoverflow.com/a/556411 diff --git a/flake.nix b/flake.nix index 6345f0c..70289d8 100644 --- a/flake.nix +++ b/flake.nix @@ -7,15 +7,41 @@ flake-utils.lib.eachDefaultSystem (system: let pkgs = nixpkgs.legacyPackages.${system}; + gon = if system == "x86_64-darwin" || system == "aarch64-darwin" then + let + gonZip = pkgs.fetchurl { + url = "https://github.com/Bearer/gon/releases/download/v0.0.36/gon_macos.zip"; + sha256 = "1firj23pgdfx9hybjjr91chn1jzf7lzjrbx3nm1s9h3xbpx945x2"; + }; + in + pkgs.runCommand "gon" { nativeBuildInputs = [ pkgs.unzip ]; } '' + mkdir -p $out/bin + unzip ${gonZip} -d $out/bin + '' + else + null; in { devShells.default = pkgs.mkShell { - packages = [ - pkgs.gnumake - pkgs.go - pkgs.goreleaser + packages = with pkgs; [ + gnumake + go + goreleaser ]; shellHook = "go mod tidy"; }; + devShells.gon = if system == "x86_64-darwin" || system == "aarch64-darwin" then + pkgs.mkShell { + buildInputs = with pkgs; [ + go + gon + goreleaser + ]; + shellHook = '' + export PATH=/usr/bin:$PATH:${gon}/bin + ''; + } + else + null; }); }