mirror of
https://github.com/warmbly/warmbly.git
synced 2026-09-12 00:05:09 +00:00
feat: install the CLI in one command from warmbly.com/cli.sh or cli.ps1, with checksum-verified archives, a Homebrew formula and Scoop manifest generated alongside them, a ghcr cli image, and a make cli-check that installs from a local mirror and proves a tampered checksum stops the install
This commit is contained in:
@@ -35,6 +35,7 @@ jobs:
|
||||
make: ${{ steps.filter.outputs.make }}
|
||||
ios: ${{ steps.filter.outputs.ios }}
|
||||
installer: ${{ steps.filter.outputs.installer }}
|
||||
cli-installer: ${{ steps.filter.outputs.cli-installer }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dorny/paths-filter@v3
|
||||
@@ -75,6 +76,12 @@ jobs:
|
||||
- 'site/public/install.sh'
|
||||
- 'site/public/install.sh.sha256'
|
||||
- 'scripts/check-installer.sh'
|
||||
cli-installer:
|
||||
- 'site/public/cli.sh'
|
||||
- 'site/public/cli.sh.sha256'
|
||||
- 'site/public/cli.ps1'
|
||||
- 'scripts/check-cli-installer.sh'
|
||||
- 'scripts/build-cli.sh'
|
||||
|
||||
migrations-ci:
|
||||
name: Migrations
|
||||
@@ -249,6 +256,22 @@ jobs:
|
||||
- name: Check the installer
|
||||
run: ./scripts/check-installer.sh
|
||||
|
||||
cli-installer-ci:
|
||||
name: CLI Installer CI
|
||||
needs: changes
|
||||
if: needs.changes.outputs.cli-installer == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
# dash is what /bin/sh is on Debian and Ubuntu, which is what most people
|
||||
# will pipe this into. Ubuntu runners already have shellcheck and pwsh.
|
||||
- name: Install dash
|
||||
run: sudo apt-get update && sudo apt-get install -y dash
|
||||
|
||||
- name: Check the CLI installer
|
||||
run: ./scripts/check-cli-installer.sh
|
||||
|
||||
make-ci:
|
||||
name: Make App CI
|
||||
needs: changes
|
||||
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
service: [backend, consumer, worker, forms, updater]
|
||||
service: [backend, consumer, worker, forms, updater, cli]
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -214,9 +214,46 @@ jobs:
|
||||
-t ${{ env.IMAGE_PREFIX }}/${{ matrix.service }}:prod \
|
||||
$(printf '${{ env.IMAGE_PREFIX }}/${{ matrix.service }}@sha256:%s ' *)
|
||||
|
||||
# The `warmbly` CLI is a plain static binary, so it cross-compiles for every
|
||||
# platform on one runner.
|
||||
#
|
||||
# Assets are named WITHOUT the version, so
|
||||
# releases/latest/download/warmbly_linux_amd64.tar.gz always resolves. That is
|
||||
# what lets the install script find the newest build with no GitHub API call,
|
||||
# which matters because the unauthenticated API is rate limited and a curl
|
||||
# installer that fails on a busy CI runner is not an installer.
|
||||
build-cli:
|
||||
name: Build CLI
|
||||
needs: validate-tag
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache: true
|
||||
|
||||
- name: Cross-compile and package
|
||||
env:
|
||||
VERSION: ${{ github.ref_name }}
|
||||
COMMIT: ${{ github.sha }}
|
||||
BUILT_AT: ${{ github.event.head_commit.timestamp }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
./scripts/build-cli.sh dist
|
||||
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: warmbly-cli
|
||||
path: dist/
|
||||
retention-days: 1
|
||||
|
||||
create-release:
|
||||
name: Create GitHub Release
|
||||
needs: [validate-tag, build-go, build-frontend, merge-native]
|
||||
needs: [validate-tag, build-go, build-frontend, merge-native, build-cli]
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
@@ -228,6 +265,12 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Download the CLI binaries
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: warmbly-cli
|
||||
path: /tmp/cli
|
||||
|
||||
# The installer verifies what it pulled against this file, so it is what
|
||||
# makes "curl | sh" checkable after the fact rather than only before it.
|
||||
# One line per service, because the thing that reads it is a POSIX shell.
|
||||
@@ -284,6 +327,18 @@ jobs:
|
||||
Add `--wizard` to be asked where each store lives, what is kept and
|
||||
for how long, and how it is backed up.
|
||||
|
||||
## The warmbly CLI
|
||||
|
||||
```
|
||||
curl -fsSL https://warmbly.com/cli.sh | sh # macOS, Linux
|
||||
irm https://warmbly.com/cli.ps1 | iex # Windows
|
||||
brew install warmbly/tap/warmbly # Homebrew
|
||||
scoop install warmbly # Scoop
|
||||
```
|
||||
|
||||
Or take an archive below and unpack it yourself; `checksums.txt`
|
||||
verifies every one of them. Already installed? `warmbly upgrade`.
|
||||
|
||||
`images.json` below lists the manifest digest of every image in this
|
||||
release. The installer checks what it pulled against it, and you can
|
||||
too:
|
||||
@@ -307,6 +362,7 @@ jobs:
|
||||
| Admin | `${{ env.IMAGE_PREFIX }}/admin:${{ github.ref_name }}` |
|
||||
| Forms | `${{ env.IMAGE_PREFIX }}/forms:${{ github.ref_name }}` |
|
||||
| Updater | `${{ env.IMAGE_PREFIX }}/updater:${{ github.ref_name }}` |
|
||||
| CLI | `${{ env.IMAGE_PREFIX }}/cli:${{ github.ref_name }}` |
|
||||
|
||||
## Deployment
|
||||
|
||||
@@ -314,12 +370,50 @@ jobs:
|
||||
EOF
|
||||
} > /tmp/release-body.md
|
||||
|
||||
# The formula and manifest are generated with the archives, so their
|
||||
# checksums can never drift from what they describe. Pushing them is
|
||||
# skipped, loudly, when the tap token is not configured: a release must
|
||||
# not fail because a downstream package repo is not set up yet.
|
||||
- name: Publish the Homebrew formula and Scoop manifest
|
||||
env:
|
||||
TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}
|
||||
TAG: ${{ github.ref_name }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [ -z "${TAP_TOKEN:-}" ]; then
|
||||
echo "HOMEBREW_TAP_TOKEN is not set; skipping the tap push."
|
||||
echo "The formula and manifest are still attached to the release."
|
||||
exit 0
|
||||
fi
|
||||
# A prerelease must never become the default `brew install`.
|
||||
case "$TAG" in
|
||||
*-*) echo "$TAG is a prerelease; not updating the taps."; exit 0 ;;
|
||||
esac
|
||||
git config --global user.name "warmbly-release"
|
||||
git config --global user.email "release@warmbly.com"
|
||||
git clone --depth 1 \
|
||||
"https://x-access-token:${TAP_TOKEN}@github.com/warmbly/homebrew-tap.git" /tmp/tap
|
||||
mkdir -p /tmp/tap/Formula /tmp/tap/bucket
|
||||
cp /tmp/cli/warmbly.rb /tmp/tap/Formula/warmbly.rb
|
||||
cp /tmp/cli/warmbly.json /tmp/tap/bucket/warmbly.json
|
||||
cd /tmp/tap
|
||||
git add Formula/warmbly.rb bucket/warmbly.json
|
||||
if git diff --cached --quiet; then
|
||||
echo "the tap already describes $TAG"
|
||||
else
|
||||
git commit -m "warmbly $TAG"
|
||||
git push
|
||||
echo "pushed warmbly $TAG to the tap"
|
||||
fi
|
||||
|
||||
- name: Create Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: ${{ github.ref_name }}
|
||||
name: ${{ github.ref_name }}
|
||||
body_path: /tmp/release-body.md
|
||||
files: /tmp/images.json
|
||||
files: |
|
||||
/tmp/images.json
|
||||
/tmp/cli/*
|
||||
draft: false
|
||||
prerelease: ${{ contains(github.ref_name, '-') }}
|
||||
|
||||
Reference in New Issue
Block a user