diff --git a/.github/workflows/packaging.yml b/.github/workflows/packaging.yml new file mode 100644 index 0000000..ee19fb0 --- /dev/null +++ b/.github/workflows/packaging.yml @@ -0,0 +1,111 @@ +name: Linux packaging + +permissions: + contents: read + +on: + pull_request: + branches: [ main ] + paths: + - 'packaging/**' + - 'scripts/packaging/**' + - '.github/workflows/release.yml' + - '.github/workflows/packaging.yml' + workflow_dispatch: + +env: + # Both images are pinned by digest (tag shown for humans only). + UBUNTU_IMAGE: ubuntu:24.04@sha256:a853f94d226358a79c740cfc7bce0c289748f3fe3488d921d038ccd752c61b60 + ARCH_IMAGE: archlinux:base-devel@sha256:51dd3d24f7fba779e7c471caeee7804c50e8c134ad948e19685a1c83a42facc3 + +jobs: + deb: + name: Build and install .deb (${{ matrix.deb_arch }}) + runs-on: ${{ matrix.runner }} + strategy: + fail-fast: false + matrix: + include: + - deb_arch: amd64 + runner: ubuntu-latest + archive_arch: x86_64 + sha_var: PINNED_SHA256_LINUX_X86_64 + - deb_arch: arm64 + runner: ubuntu-24.04-arm + archive_arch: arm64 + sha_var: PINNED_SHA256_LINUX_ARM64 + steps: + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + + - name: Download pinned release archive + run: | + set -euo pipefail + source packaging/pinned_release.env + expected="${{ matrix.sha_var }}" + curl -fsSL -o archive.tar.gz \ + "https://github.com/amberframework/amber_cli/releases/download/v${PINNED_VERSION}/amber_cli-linux-${{ matrix.archive_arch }}.tar.gz" + echo "${!expected} archive.tar.gz" | sha256sum -c - + echo "PINNED_VERSION=${PINNED_VERSION}" >> "$GITHUB_ENV" + echo "EXPECTED_SHA=${!expected}" >> "$GITHUB_ENV" + + - name: Build .deb in an Ubuntu container + run: | + mkdir -p out + docker run --rm -v "$PWD:/w" -w /w "$UBUNTU_IMAGE" \ + scripts/packaging/build_deb.sh "$PINNED_VERSION" "${{ matrix.deb_arch }}" archive.tar.gz "$EXPECTED_SHA" out + + - name: Install and smoke test in a fresh Ubuntu container + run: | + docker run --rm -v "$PWD/out:/out:ro" "$UBUNTU_IMAGE" bash -euxc ' + deb=/out/amber-cli_'"$PINNED_VERSION"'_${{ matrix.deb_arch }}.deb + dpkg-deb -I "$deb" + dpkg-deb -c "$deb" + dpkg -i "$deb" + test -x /usr/bin/amber + test -x /usr/bin/amber-lsp + amber --version | tee /tmp/version + grep -F "'"$PINNED_VERSION"'" /tmp/version + amber-lsp --help || true + dpkg -s amber-cli | grep -F "Recommends: crystal-alpha | crystal" + dpkg -r amber-cli + test ! -e /usr/bin/amber + ' + + - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 + with: + name: debs-${{ matrix.deb_arch }} + path: out/*.deb + + arch: + name: Build and install amber-cli-bin (Arch, x86_64) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + + - name: PKGBUILD pins agree with packaging/pinned_release.env + run: | + set -euo pipefail + source packaging/pinned_release.env + pkgbuild=packaging/arch/amber-cli-bin/PKGBUILD + grep -Fx "pkgver=${PINNED_VERSION}" "$pkgbuild" + grep -F "sha256sums_x86_64=('${PINNED_SHA256_LINUX_X86_64}')" "$pkgbuild" + grep -F "sha256sums_aarch64=('${PINNED_SHA256_LINUX_ARM64}')" "$pkgbuild" + + - name: makepkg, pacman -U, and smoke test as a non-root builder + run: | + docker run --rm -v "$PWD/packaging/arch/amber-cli-bin:/src:ro" "$ARCH_IMAGE" bash -euxc ' + useradd -m builder + cp -r /src /home/builder/pkg + chown -R builder /home/builder/pkg + cd /home/builder/pkg + # .SRCINFO must match the PKGBUILD + su builder -c "makepkg --printsrcinfo" | diff -u .SRCINFO - + # fails closed on any checksum mismatch + su builder -c "makepkg --noconfirm" + ls -l *.pkg.tar.* + pacman -U --noconfirm ./*.pkg.tar.zst + amber --version | tee /tmp/version + grep -F "$(sed -n "s/^pkgver=//p" PKGBUILD)" /tmp/version + amber-lsp --help || true + pacman -Qi amber-cli-bin + ' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bac1458..6759c80 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -171,6 +171,66 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + package-deb: + name: Build and attach .deb packages + needs: upload-assets + runs-on: ubuntu-latest + if: github.event_name == 'release' + env: + # Pinned by digest (tag shown for humans only). + UBUNTU_IMAGE: ubuntu:24.04@sha256:a853f94d226358a79c740cfc7bce0c289748f3fe3488d921d038ccd752c61b60 + + steps: + - name: Checkout code + uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6 + with: + ref: ${{ github.event.release.tag_name }} + + - name: Download Linux archives built by this run + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 + with: + path: artifacts + pattern: amber_cli-linux-* + + - name: Build .deb packages from the exact release archives + run: | + set -euo pipefail + version="${{ github.event.release.tag_name }}" + version="${version#v}" + mkdir -p debs + for pair in x86_64:amd64 arm64:arm64; do + archive_arch="${pair%%:*}"; deb_arch="${pair##*:}" + dir="artifacts/amber_cli-linux-${archive_arch}" + # Same hash the build job published; mismatch fails closed. + expected="$(cut -d' ' -f1 "${dir}/amber_cli-linux-${archive_arch}.tar.gz.sha256")" + docker run --rm -v "$PWD:/w" -w /w "$UBUNTU_IMAGE" \ + scripts/packaging/build_deb.sh "$version" "$deb_arch" \ + "${dir}/amber_cli-linux-${archive_arch}.tar.gz" "$expected" debs + done + (cd debs && sha256sum ./*.deb | sed 's# \./# #' > SHA256SUMS && cat SHA256SUMS) + + - name: Smoke test the amd64 .deb + run: | + version="${{ github.event.release.tag_name }}" + version="${version#v}" + docker run --rm -v "$PWD/debs:/debs:ro" "$UBUNTU_IMAGE" bash -euxc " + dpkg -i /debs/amber-cli_${version}_amd64.deb + amber --version + amber-lsp --help || true + " + + - name: Attach .deb packages and SHA256SUMS to the release + uses: softprops/action-gh-release@c12583777ecdfd3be55c69cf75464299dc01057e # v3 + with: + files: | + debs/amber-cli_*_amd64.deb + debs/amber-cli_*_arm64.deb + debs/SHA256SUMS + tag_name: ${{ github.event.release.tag_name }} + fail_on_unmatched_files: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + notify-homebrew: name: Notify Homebrew Tap needs: upload-assets diff --git a/README.md b/README.md index 1985bec..67d7ffd 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,44 @@ amber --version On Linux, use `sha256sum -c` for the checksum. Prefix only the `install` command with `sudo` if `/usr/local/bin` is not writable. +### Ubuntu and Debian (.deb) + +Release `2.0.6` and later attach `amber-cli__amd64.deb` and +`amber-cli__arm64.deb` plus a `SHA256SUMS` file to the GitHub release +(the package job in `release.yml` builds them from the same static archives). +The package installs `/usr/bin/amber` and `/usr/bin/amber-lsp` and recommends +a Crystal compiler (`crystal-alpha` or `crystal`). + +```bash +version=2.0.6 +deb="amber-cli_${version}_amd64.deb" # or _arm64.deb +curl -fLO "https://github.com/amberframework/amber_cli/releases/download/v${version}/${deb}" +curl -fLO "https://github.com/amberframework/amber_cli/releases/download/v${version}/SHA256SUMS" +sha256sum --ignore-missing -c SHA256SUMS +sudo apt install "./${deb}" +amber --version +``` + +Coming soon: apt repo. Until a hosting location is chosen, install the +`.deb` directly as shown above. Only `.deb` files from releases published after +this packaging lands exist; `2.0.6` itself ships archives only. + +### Arch Linux and Omarchy (pacman, AUR) + +Omarchy is Arch-based, so the Arch package covers it. The `amber-cli-bin` +`PKGBUILD` in `packaging/arch/` installs the prebuilt static binaries with +pinned sha256 sums: + +```bash +git clone https://github.com/amberframework/amber_cli +cd amber_cli/packaging/arch/amber-cli-bin +makepkg -si +amber --version +``` + +Coming soon: `yay -S amber-cli-bin` once the AUR package is published. See +`packaging/arch/README.md` for maintainer steps. + ## Create and verify a web app ```bash diff --git a/packaging/arch/README.md b/packaging/arch/README.md new file mode 100644 index 0000000..2bb2de5 --- /dev/null +++ b/packaging/arch/README.md @@ -0,0 +1,50 @@ +# Arch Linux and Omarchy packaging + +`amber-cli-bin/` is an AUR-style `PKGBUILD` that installs the prebuilt static +`amber` and `amber-lsp` binaries from a GitHub release (`x86_64` and +`aarch64`). Omarchy is Arch-based, so the same package works there. + +## Install from a local checkout + +```bash +cd packaging/arch/amber-cli-bin +makepkg -si # run as a normal user, not root +amber --version +``` + +Once published to the AUR: `yay -S amber-cli-bin`. + +## Every pin lives in the PKGBUILD + +Each source has a `sha256sums` entry. `makepkg` fails closed on a mismatch; +never run it with `--skipchecksums` or `SKIPINTEGRITY`. + +## Bump to a new release + +1. Set `pkgver` (and reset `pkgrel=1`) in `PKGBUILD`. +2. Download each release archive and the tagged `LICENSE`, hash them with + `sha256sum`, and compare the archive hashes with the release's published + `.sha256` files. Put the hashes in `sha256sums`, `sha256sums_x86_64`, and + `sha256sums_aarch64`. +3. Update `packaging/pinned_release.env` to the same version and hashes. +4. Regenerate `.SRCINFO`: `makepkg --printsrcinfo > .SRCINFO`. +5. Test in a clean Arch container as a non-root user (CI does this on every + pull request that touches `packaging/`). + +## First AUR submission (maintainer only, manual) + +1. Create an AUR account and add your SSH public key to it. +2. `git clone ssh://aur@aur.archlinux.org/amber-cli-bin.git` +3. Copy `PKGBUILD` and `.SRCINFO` into the clone. +4. `git add PKGBUILD .SRCINFO && git commit -m "Initial import: amber-cli-bin 2.0.6"` +5. `git push` (the AUR accepts the `master` branch). + +For later releases, repeat steps 3 to 5 with the bumped files. Nothing in this +repository publishes to the AUR automatically. + +## Not provided + +A source-build `amber-cli` PKGBUILD is intentionally absent. A reproducible +source build needs the Crystal toolchain plus `shards install --frozen` with +hash-verified shards, and the release job already produces the static binaries +the `-bin` package repackages. diff --git a/packaging/arch/amber-cli-bin/.SRCINFO b/packaging/arch/amber-cli-bin/.SRCINFO new file mode 100644 index 0000000..a9d898b --- /dev/null +++ b/packaging/arch/amber-cli-bin/.SRCINFO @@ -0,0 +1,22 @@ +pkgbase = amber-cli-bin + pkgdesc = Amber V2 command-line tool and language server (prebuilt static binaries) + pkgver = 2.0.6 + pkgrel = 1 + url = https://github.com/amberframework/amber_cli + arch = x86_64 + arch = aarch64 + license = MIT + optdepends = crystal-alpha: Crystal compiler used to build generated apps + optdepends = crystal: Crystal compiler used to build generated apps + provides = amber-cli=2.0.6 + conflicts = amber-cli + options = !strip + options = !debug + source = LICENSE-2.0.6::https://raw.githubusercontent.com/amberframework/amber_cli/v2.0.6/LICENSE + sha256sums = 8a859ceb1a659af317a2b597f9e763d9a63cc4392f01628c211d2a5a38d77a4a + source_x86_64 = amber_cli-2.0.6-linux-x86_64.tar.gz::https://github.com/amberframework/amber_cli/releases/download/v2.0.6/amber_cli-linux-x86_64.tar.gz + sha256sums_x86_64 = 74cb45729d8c1f208d678003a81017df4832f7dad38e9103ee17583feddd4f73 + source_aarch64 = amber_cli-2.0.6-linux-arm64.tar.gz::https://github.com/amberframework/amber_cli/releases/download/v2.0.6/amber_cli-linux-arm64.tar.gz + sha256sums_aarch64 = 8b914f3cacf8432dee388af4973bb68984676314a8a0f162ac7b6a9040412ac0 + +pkgname = amber-cli-bin diff --git a/packaging/arch/amber-cli-bin/PKGBUILD b/packaging/arch/amber-cli-bin/PKGBUILD new file mode 100644 index 0000000..ab1683c --- /dev/null +++ b/packaging/arch/amber-cli-bin/PKGBUILD @@ -0,0 +1,25 @@ +# Maintainer: Amber Framework +pkgname=amber-cli-bin +pkgver=2.0.6 +pkgrel=1 +pkgdesc='Amber V2 command-line tool and language server (prebuilt static binaries)' +arch=('x86_64' 'aarch64') +url='https://github.com/amberframework/amber_cli' +license=('MIT') +optdepends=('crystal-alpha: Crystal compiler used to build generated apps' + 'crystal: Crystal compiler used to build generated apps') +provides=("amber-cli=${pkgver}") +conflicts=('amber-cli') +options=('!strip' '!debug') +source=("LICENSE-${pkgver}::https://raw.githubusercontent.com/amberframework/amber_cli/v${pkgver}/LICENSE") +source_x86_64=("amber_cli-${pkgver}-linux-x86_64.tar.gz::https://github.com/amberframework/amber_cli/releases/download/v${pkgver}/amber_cli-linux-x86_64.tar.gz") +source_aarch64=("amber_cli-${pkgver}-linux-arm64.tar.gz::https://github.com/amberframework/amber_cli/releases/download/v${pkgver}/amber_cli-linux-arm64.tar.gz") +sha256sums=('8a859ceb1a659af317a2b597f9e763d9a63cc4392f01628c211d2a5a38d77a4a') +sha256sums_x86_64=('74cb45729d8c1f208d678003a81017df4832f7dad38e9103ee17583feddd4f73') +sha256sums_aarch64=('8b914f3cacf8432dee388af4973bb68984676314a8a0f162ac7b6a9040412ac0') + +package() { + install -Dm755 amber "${pkgdir}/usr/bin/amber" + install -Dm755 amber-lsp "${pkgdir}/usr/bin/amber-lsp" + install -Dm644 "LICENSE-${pkgver}" "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE" +} diff --git a/packaging/deb/copyright b/packaging/deb/copyright new file mode 100644 index 0000000..2b9a928 --- /dev/null +++ b/packaging/deb/copyright @@ -0,0 +1,25 @@ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: amber_cli +Upstream-Contact: The Amber Team +Source: https://github.com/amberframework/amber_cli + +Files: * +Copyright: 2025 The Amber Team +License: MIT + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + . + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + . + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. diff --git a/packaging/pinned_release.env b/packaging/pinned_release.env new file mode 100644 index 0000000..712b506 --- /dev/null +++ b/packaging/pinned_release.env @@ -0,0 +1,6 @@ +# Release whose published archives the PR packaging checks repackage. +# Every archive is verified against these sha256 values; a mismatch fails closed. +# Keep in sync with packaging/arch/amber-cli-bin/PKGBUILD (CI checks this). +PINNED_VERSION=2.0.6 +PINNED_SHA256_LINUX_X86_64=74cb45729d8c1f208d678003a81017df4832f7dad38e9103ee17583feddd4f73 +PINNED_SHA256_LINUX_ARM64=8b914f3cacf8432dee388af4973bb68984676314a8a0f162ac7b6a9040412ac0 diff --git a/scripts/packaging/build_deb.sh b/scripts/packaging/build_deb.sh new file mode 100755 index 0000000..4a06c37 --- /dev/null +++ b/scripts/packaging/build_deb.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +# Repackage a release archive (amber + amber-lsp, static) as a .deb. +# Usage: build_deb.sh +# Needs only dpkg-deb, tar, sha256sum. Fails closed on any hash mismatch. +set -euo pipefail + +version="$1"; deb_arch="$2"; archive="$3"; expected_sha="$4"; out_dir="$5" +repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" + +case "$deb_arch" in amd64|arm64) ;; *) echo "unsupported arch: $deb_arch" >&2; exit 1 ;; esac +[[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+([.+~-][0-9A-Za-z.]+)?$ ]] || { echo "bad version: $version" >&2; exit 1; } + +actual_sha="$(sha256sum "$archive" | cut -d' ' -f1)" +if [ "$actual_sha" != "$expected_sha" ]; then + echo "sha256 mismatch for $archive: expected $expected_sha, got $actual_sha" >&2 + exit 1 +fi + +work="$(mktemp -d)"; trap 'rm -rf "$work"' EXIT +root="$work/pkg" +mkdir -p "$root/usr/bin" "$root/DEBIAN" "$root/usr/share/doc/amber-cli" +tar -xzf "$archive" -C "$work" amber amber-lsp +install -m 0755 "$work/amber" "$work/amber-lsp" "$root/usr/bin/" +install -m 0644 "$repo_root/packaging/deb/copyright" "$root/usr/share/doc/amber-cli/copyright" + +installed_kb="$(du -sk "$root/usr" | cut -f1)" +cat > "$root/DEBIAN/control" < +Homepage: https://github.com/amberframework/amber_cli +Recommends: crystal-alpha | crystal +Description: Amber V2 command-line tool and language server + The amber command generates, builds, and runs Amber V2 web applications. + The amber-lsp command is the Amber language server. + . + Both programs are statically linked release binaries. A Crystal compiler + (crystal-alpha or crystal, 1.20 or newer but earlier than 2.0) is needed + to build the applications amber generates. +CONTROL + +mkdir -p "$out_dir" +dpkg-deb --root-owner-group --build "$root" "$out_dir/amber-cli_${version}_${deb_arch}.deb" >/dev/null +echo "built $out_dir/amber-cli_${version}_${deb_arch}.deb"