diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 24f0e1c..5884e18 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -46,6 +46,17 @@ Go and make are NOT installed locally. Use `scripts/dev.sh` (Docker-based): Go module cache is persisted in a Docker volume (`aethel-gomod`) for fast repeated builds. +## Release Process + +Two-workflow split in GitHub Actions: + +1. **`release.yml`** — triggers on push to master. Analyzes conventional commits since last tag, computes version bump (major/minor/patch), updates `VERSION` + `CHANGELOG.md`, commits `chore(release): vX.Y.Z`, creates git tag, pushes. Does NOT build binaries or create GitHub Release. +2. **`goreleaser.yml`** — triggers on tag push (`v*`). Runs GoReleaser to cross-compile 5 platforms (linux/amd64, linux/arm64, darwin/amd64, darwin/arm64, windows/amd64), creates `.tar.gz` (Unix) / `.zip` (Windows) archives with both `aethel` + `aetheld`, publishes GitHub Release with SHA256 checksums. + +GoReleaser config: `.goreleaser.yml` (version 2). Version injected via `-ldflags "-s -w -X main.version={{.Version}}"` on both binaries. + +Install script: `scripts/install.sh` — POSIX shell, detects OS/arch, downloads from GitHub Releases, verifies checksum, installs to `~/.local/bin/`. + ## Key Conventions - Platform-specific code uses `//go:build` tags (not `// +build`) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 12e5412..b95fbc6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,7 +22,7 @@ jobs: - uses: actions/setup-go@v5 with: - go-version: '1.24' + go-version: '1.25' - run: go vet ./... - run: go test ./... diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml new file mode 100644 index 0000000..e6ced11 --- /dev/null +++ b/.github/workflows/goreleaser.yml @@ -0,0 +1,40 @@ +name: GoReleaser + +on: + push: + tags: ["v*"] + +permissions: + contents: write + +jobs: + goreleaser: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - uses: actions/setup-go@v5 + with: + go-version: '1.25' + + - name: Extract release notes from CHANGELOG.md + run: | + VERSION="${GITHUB_REF_NAME#v}" + echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$' || { + echo "::error::Invalid version format: $VERSION" + exit 1 + } + sed -n "/^## \[${VERSION}\]/,/^## \[/{ /^## \[${VERSION}\]/d; /^## \[/d; p; }" CHANGELOG.md \ + | tr -d '\r' > /tmp/release-notes.md + echo "::notice::Release notes for v${VERSION}:" + cat /tmp/release-notes.md + + - uses: goreleaser/goreleaser-action@v6 + with: + distribution: goreleaser + version: "~> v2" + args: release --clean --release-notes /tmp/release-notes.md + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 44c295c..cc4fb56 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,7 +6,7 @@ on: workflow_dispatch: inputs: dry_run: - description: 'Dry run — compute version & build binaries but skip commit, tag, and release' + description: 'Dry run — compute version but skip commit, tag, and release' type: boolean default: true @@ -28,7 +28,7 @@ jobs: - uses: actions/setup-go@v5 with: - go-version: '1.24' + go-version: '1.25' - name: Determine version bump id: bump @@ -83,8 +83,9 @@ jobs: - name: Update version files if: steps.bump.outputs.skip != 'true' + env: + VERSION: ${{ steps.bump.outputs.version }} run: | - VERSION=${{ steps.bump.outputs.version }} echo "$VERSION" > VERSION # Update CHANGELOG: insert versioned header after [Unreleased] @@ -99,66 +100,25 @@ jobs: go vet ./... go test ./... - - name: Build binaries - if: steps.bump.outputs.skip != 'true' - run: | - VERSION=${{ steps.bump.outputs.version }} - LDFLAGS="-X main.version=${VERSION}" - mkdir -p dist - - for PAIR in linux/amd64 linux/arm64 darwin/amd64 darwin/arm64; do - OS="${PAIR%%/*}" - ARCH="${PAIR##*/}" - echo "Building ${OS}/${ARCH}..." - GOOS=$OS GOARCH=$ARCH go build -ldflags "$LDFLAGS" -o dist/aethel ./cmd/aethel - GOOS=$OS GOARCH=$ARCH go build -ldflags "$LDFLAGS" -o dist/aetheld ./cmd/aetheld - tar -czf "dist/aethel-${OS}-${ARCH}.tar.gz" -C dist aethel aetheld - rm dist/aethel dist/aetheld - done - - echo "::notice::Built archives:" - ls -lh dist/*.tar.gz - - - name: Extract changelog - if: steps.bump.outputs.skip != 'true' - run: | - VERSION=${{ steps.bump.outputs.version }} - # Extract content between ## [VERSION] and the next ## [ - BODY=$(sed -n "/^## \[${VERSION}\]/,/^## \[/{ /^## \[${VERSION}\]/d; /^## \[/d; p; }" CHANGELOG.md) - echo "$BODY" > dist/release-notes.md - echo "::notice::Release notes:" - cat dist/release-notes.md - - name: Dry run summary if: steps.bump.outputs.skip != 'true' && env.DRY_RUN == 'true' + env: + VERSION: ${{ steps.bump.outputs.version }} + BUMP: ${{ steps.bump.outputs.bump }} run: | - echo "::warning::DRY RUN — skipping commit, tag, and release creation" - echo "Would have released: v${{ steps.bump.outputs.version }}" - echo "Bump type: ${{ steps.bump.outputs.bump }}" - echo "Archives built:" - ls -lh dist/*.tar.gz + echo "::warning::DRY RUN — skipping commit, tag, and release" + echo "Would have released: v${VERSION}" + echo "Bump type: ${BUMP}" - - name: Commit version bump + - name: Commit version bump and tag if: steps.bump.outputs.skip != 'true' && env.DRY_RUN != 'true' + env: + VERSION: ${{ steps.bump.outputs.version }} run: | - VERSION=${{ steps.bump.outputs.version }} git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" git add VERSION CHANGELOG.md git commit -m "chore(release): v${VERSION}" git tag "v${VERSION}" git push origin master --tags - - - name: Create GitHub Release - if: steps.bump.outputs.skip != 'true' && env.DRY_RUN != 'true' - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - VERSION=${{ steps.bump.outputs.version }} - gh release create "v${VERSION}" \ - --title "v${VERSION}" \ - --notes-file dist/release-notes.md \ - dist/aethel-linux-amd64.tar.gz \ - dist/aethel-linux-arm64.tar.gz \ - dist/aethel-darwin-amd64.tar.gz \ - dist/aethel-darwin-arm64.tar.gz + echo "::notice::Tagged v${VERSION} — GoReleaser workflow will build and publish the release" diff --git a/.goreleaser.yml b/.goreleaser.yml new file mode 100644 index 0000000..2cb08e3 --- /dev/null +++ b/.goreleaser.yml @@ -0,0 +1,68 @@ +version: 2 + +before: + hooks: + - go mod tidy -diff + - go vet ./... + +builds: + - id: aethel + main: ./cmd/aethel + binary: aethel + env: + - CGO_ENABLED=0 + goos: + - linux + - darwin + - windows + goarch: + - amd64 + - arm64 + ignore: + - goos: windows + goarch: arm64 + ldflags: + - -s -w -X main.version={{.Version}} + + - id: aetheld + main: ./cmd/aetheld + binary: aetheld + env: + - CGO_ENABLED=0 + goos: + - linux + - darwin + - windows + goarch: + - amd64 + - arm64 + ignore: + - goos: windows + goarch: arm64 + ldflags: + - -s -w -X main.version={{.Version}} + +archives: + - id: default + builds: + - aethel + - aetheld + name_template: "aethel_{{ .Version }}_{{ .Os }}_{{ .Arch }}" + format_overrides: + - goos: windows + format: zip + +checksum: + name_template: checksums.txt + algorithm: sha256 + +changelog: + disable: true + +release: + github: + owner: artyomsv + name: aethel + draft: false + prerelease: auto + name_template: "v{{.Version}}" diff --git a/CHANGELOG.md b/CHANGELOG.md index c3b624b..287174a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - **Roadmap PRDs** — 11 detailed Product Requirements Documents in `docs/roadmap/`: workspace files, MCP server, command palette, notification center, pre-built binaries, demo GIF, community plugins, process health, tmux migration, cross-pane events, session sharing - **Restructured ROADMAP.md** — organized into Core/Growth/Advanced categories with priority matrix, strategic pain-layer analysis, and feature synergy notes - **Notification center concept (M12)** — centralized event sidebar with pane navigation and history stack; PRD covers process exit detection, plugin notification handlers, and incremental integration path +- **Pre-built binaries & release infrastructure** — GoReleaser config for 5 platforms (linux/amd64, linux/arm64, darwin/amd64, darwin/arm64, windows/amd64); two-workflow CI split: `release.yml` handles version bump + tag, `goreleaser.yml` builds + publishes GitHub Release with `.tar.gz`/`.zip` archives and SHA256 checksums +- **One-line install script** — `scripts/install.sh` detects OS/arch, fetches latest release from GitHub API, verifies SHA256 checksum, installs to `~/.local/bin/`; supports `AETHEL_VERSION` for pinned installs and `GITHUB_TOKEN` for API auth +- **Daemon version reporting** — `aetheld version` subcommand, version logged at startup; consistent `-ldflags` injection across all build paths (GoReleaser, dev.sh, dev.ps1, rebuild.ps1, Makefile) + +### Fixed + +- CI Go version mismatch — updated from 1.24 to 1.25 in `ci.yml` and `release.yml` to match `go.mod` ## [0.9.0] - 2026-03-23 diff --git a/Makefile b/Makefile index e41e9e2..65e488a 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,11 @@ .PHONY: build test test-race vet cross clean +VERSION := $(shell cat VERSION) +LDFLAGS := -X main.version=$(VERSION) + build: - go build -o aethel ./cmd/aethel - go build -o aetheld ./cmd/aetheld + go build -ldflags "$(LDFLAGS)" -o aethel ./cmd/aethel + go build -ldflags "$(LDFLAGS)" -o aetheld ./cmd/aetheld test: go test ./... @@ -14,16 +17,16 @@ vet: go vet ./... cross: - GOOS=linux GOARCH=amd64 go build -o dist/aethel-linux-amd64 ./cmd/aethel - GOOS=linux GOARCH=amd64 go build -o dist/aetheld-linux-amd64 ./cmd/aetheld - GOOS=linux GOARCH=arm64 go build -o dist/aethel-linux-arm64 ./cmd/aethel - GOOS=linux GOARCH=arm64 go build -o dist/aetheld-linux-arm64 ./cmd/aetheld - GOOS=darwin GOARCH=amd64 go build -o dist/aethel-darwin-amd64 ./cmd/aethel - GOOS=darwin GOARCH=amd64 go build -o dist/aetheld-darwin-amd64 ./cmd/aetheld - GOOS=darwin GOARCH=arm64 go build -o dist/aethel-darwin-arm64 ./cmd/aethel - GOOS=darwin GOARCH=arm64 go build -o dist/aetheld-darwin-arm64 ./cmd/aetheld - GOOS=windows GOARCH=amd64 go build -o dist/aethel-windows-amd64.exe ./cmd/aethel - GOOS=windows GOARCH=amd64 go build -o dist/aetheld-windows-amd64.exe ./cmd/aetheld + GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o dist/aethel-linux-amd64 ./cmd/aethel + GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o dist/aetheld-linux-amd64 ./cmd/aetheld + GOOS=linux GOARCH=arm64 go build -ldflags "$(LDFLAGS)" -o dist/aethel-linux-arm64 ./cmd/aethel + GOOS=linux GOARCH=arm64 go build -ldflags "$(LDFLAGS)" -o dist/aetheld-linux-arm64 ./cmd/aetheld + GOOS=darwin GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o dist/aethel-darwin-amd64 ./cmd/aethel + GOOS=darwin GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o dist/aetheld-darwin-amd64 ./cmd/aetheld + GOOS=darwin GOARCH=arm64 go build -ldflags "$(LDFLAGS)" -o dist/aethel-darwin-arm64 ./cmd/aethel + GOOS=darwin GOARCH=arm64 go build -ldflags "$(LDFLAGS)" -o dist/aetheld-darwin-arm64 ./cmd/aetheld + GOOS=windows GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o dist/aethel-windows-amd64.exe ./cmd/aethel + GOOS=windows GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o dist/aetheld-windows-amd64.exe ./cmd/aetheld clean: rm -f aethel aetheld aethel.exe aetheld.exe diff --git a/README.md b/README.md index ddcef6d..30f331d 100644 --- a/README.md +++ b/README.md @@ -94,31 +94,38 @@ See [ARCHITECTURE.md](ARCHITECTURE.md) for detailed design decisions. ## Quick Start -### Prerequisites - -- Docker **or** Go 1.25+ - -### Build +### Install ```bash -# With Docker (no local Go required) -./scripts/dev.ps1 build # PowerShell (Windows) -./scripts/dev.sh build # Bash (Linux/macOS) +# Linux / macOS — one-line install +curl -sSfL https://raw.githubusercontent.com/artyomsv/aethel/master/scripts/install.sh | sh -# With local Go -make build +# Go users +go install github.com/artyomsv/aethel/cmd/aethel@latest +go install github.com/artyomsv/aethel/cmd/aetheld@latest + +# Windows — download .zip from GitHub Releases +# https://github.com/artyomsv/aethel/releases/latest ``` ### Run ```bash -# Start the daemon -aethel daemon start - # Launch the TUI (auto-starts daemon if needed) aethel ``` +### Build from Source + +```bash +# With Docker (no local Go required) +./scripts/dev.ps1 build # PowerShell (Windows) +./scripts/dev.sh build # Bash (Linux/macOS) + +# With local Go +make build +``` + ### Key Bindings | Key | Action | @@ -221,10 +228,10 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for development guidelines. | **M2: Persistence** | Done | Workspace snapshots, ghost buffer persistence, shell respawn, reboot-proof sessions | | **M3: Resume Engine** | Done | Regex scrapers, token extraction, AI session resume via pre-assigned UUIDs | | **M4: Plugin System** | Done | TOML plugins, typed panes, pane creation dialog, error handlers, window size persistence | -| **M5: Polish** | Planned | JSON transformer, observability, encrypted tokens, OS service integration | +| **M5: Polish** | In Progress | JSON transformer, observability, encrypted tokens, OS service integration | | **M6: Pane Focus** | Done | Ctrl+E toggles active pane full-screen, other panes keep running | -| **M7: Pane Notes** | Planned | Side-by-side note-taking linked to panes | | **M8: Bubble Tea v2** | Done | Bubble Tea v2/Lipgloss v2 migration, text selection, clipboard, editor enhancements | +| **Pre-built Binaries** | In Progress | GoReleaser, GitHub Releases, install script, cross-platform archives | See [ROADMAP.md](ROADMAP.md) for detailed progress and feature descriptions. diff --git a/ROADMAP.md b/ROADMAP.md index bbf9ae9..c8cc764 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -145,9 +145,9 @@ Processes emit events when they finish or need attention. A non-modal sidebar sh ### Pre-Built Binaries & One-Line Install — [PRD](docs/roadmap/pre-built-binaries.md) -> `curl -sSfL https://get.aethel.dev | sh` — zero friction install. +> `curl -sSfL .../install.sh | sh` — zero friction install. -goreleaser for GitHub Releases, Homebrew tap, winget/scoop manifests, install script. **Priority 1** — prerequisite for everything else. Every extra step between "I want to try this" and "it's running" loses users. +GoReleaser cross-compiles 5 platform pairs (linux/amd64, linux/arm64, darwin/amd64, darwin/arm64, windows/amd64) with SHA256 checksums. Two-workflow split: `release.yml` handles version bump + tag, `goreleaser.yml` builds + publishes GitHub Release. Install script for Linux/macOS. **Homebrew tap, Scoop, Winget deferred** (need external repos). ### The "Holy Shit" Demo — [PRD](docs/roadmap/demo-gif.md) @@ -195,7 +195,7 @@ Remote workspace viewing and collaboration over TCP+TLS. Read-only by default, c | Priority | Feature | Effort | Impact | Category | |----------|---------|--------|--------|----------| -| 1 | Pre-built binaries + one-line install | Small | Critical | Growth | +| 1 | Pre-built binaries + one-line install (in progress) | Small | Critical | Growth | | 2 | "Holy Shit" demo GIF/video | Small | Critical | Growth | | 3 | Project workspace files (`.aethel.toml`) | Medium | Very High | Core | | 4 | Command palette (`Ctrl+Shift+P`) | Medium | High | Core | diff --git a/cmd/aetheld/main.go b/cmd/aetheld/main.go index 980ab96..3d5fa99 100644 --- a/cmd/aetheld/main.go +++ b/cmd/aetheld/main.go @@ -10,7 +10,14 @@ import ( "github.com/artyomsv/aethel/internal/daemon" ) +var version = "dev" // overridden at build time via -ldflags "-X main.version=..." + func main() { + if len(os.Args) > 1 && os.Args[1] == "version" { + fmt.Println("aetheld v" + version) + return + } + background := len(os.Args) > 1 && os.Args[1] == "--background" logFile := initLogging() @@ -41,7 +48,7 @@ func main() { } d := daemon.New(cfg) - log.Println("aetheld starting...") + log.Printf("aetheld v%s starting...", version) fmt.Println("aetheld — starting daemon...") if err := d.Start(); err != nil { log.Printf("failed to start daemon: %v", err) diff --git a/docs/roadmap/pre-built-binaries.md b/docs/roadmap/pre-built-binaries.md index bbcead1..57f8db9 100644 --- a/docs/roadmap/pre-built-binaries.md +++ b/docs/roadmap/pre-built-binaries.md @@ -5,7 +5,7 @@ | Priority | 1 | | Effort | Small | | Impact | Critical | -| Status | Proposed | +| Status | In Progress | | Depends on | — | ## Problem diff --git a/scripts/dev.ps1 b/scripts/dev.ps1 index 8af540a..0819143 100644 --- a/scripts/dev.ps1 +++ b/scripts/dev.ps1 @@ -25,7 +25,7 @@ function Invoke-DockerGo { switch ($Command) { "build" { Write-Host "[aethel] Building Windows binaries..." -ForegroundColor Cyan - Invoke-DockerGo sh -c "GOOS=windows GOARCH=amd64 go build -v -o aethel.exe ./cmd/aethel && GOOS=windows GOARCH=amd64 go build -v -o aetheld.exe ./cmd/aetheld" + Invoke-DockerGo sh -c 'VER=$(cat VERSION) && LDFLAGS="-X main.version=$VER" && GOOS=windows GOARCH=amd64 go build -v -ldflags "$LDFLAGS" -o aethel.exe ./cmd/aethel && GOOS=windows GOARCH=amd64 go build -v -ldflags "$LDFLAGS" -o aetheld.exe ./cmd/aetheld' Write-Host "[aethel] Built: aethel.exe, aetheld.exe" -ForegroundColor Green } @@ -49,7 +49,7 @@ switch ($Command) { "cross" { Write-Host "[aethel] Cross-compiling for all platforms..." -ForegroundColor Cyan - Invoke-DockerGo sh -c "mkdir -p dist && GOOS=linux GOARCH=amd64 go build -o dist/aethel-linux-amd64 ./cmd/aethel && GOOS=linux GOARCH=amd64 go build -o dist/aetheld-linux-amd64 ./cmd/aetheld && GOOS=linux GOARCH=arm64 go build -o dist/aethel-linux-arm64 ./cmd/aethel && GOOS=linux GOARCH=arm64 go build -o dist/aetheld-linux-arm64 ./cmd/aetheld && GOOS=darwin GOARCH=amd64 go build -o dist/aethel-darwin-amd64 ./cmd/aethel && GOOS=darwin GOARCH=amd64 go build -o dist/aetheld-darwin-amd64 ./cmd/aetheld && GOOS=darwin GOARCH=arm64 go build -o dist/aethel-darwin-arm64 ./cmd/aethel && GOOS=darwin GOARCH=arm64 go build -o dist/aetheld-darwin-arm64 ./cmd/aetheld && GOOS=windows GOARCH=amd64 go build -o dist/aethel-windows-amd64.exe ./cmd/aethel && GOOS=windows GOARCH=amd64 go build -o dist/aetheld-windows-amd64.exe ./cmd/aetheld" + Invoke-DockerGo sh -c 'VER=$(cat VERSION) && LDFLAGS="-X main.version=$VER" && mkdir -p dist && GOOS=linux GOARCH=amd64 go build -ldflags "$LDFLAGS" -o dist/aethel-linux-amd64 ./cmd/aethel && GOOS=linux GOARCH=amd64 go build -ldflags "$LDFLAGS" -o dist/aetheld-linux-amd64 ./cmd/aetheld && GOOS=linux GOARCH=arm64 go build -ldflags "$LDFLAGS" -o dist/aethel-linux-arm64 ./cmd/aethel && GOOS=linux GOARCH=arm64 go build -ldflags "$LDFLAGS" -o dist/aetheld-linux-arm64 ./cmd/aetheld && GOOS=darwin GOARCH=amd64 go build -ldflags "$LDFLAGS" -o dist/aethel-darwin-amd64 ./cmd/aethel && GOOS=darwin GOARCH=amd64 go build -ldflags "$LDFLAGS" -o dist/aetheld-darwin-amd64 ./cmd/aetheld && GOOS=darwin GOARCH=arm64 go build -ldflags "$LDFLAGS" -o dist/aethel-darwin-arm64 ./cmd/aethel && GOOS=darwin GOARCH=arm64 go build -ldflags "$LDFLAGS" -o dist/aetheld-darwin-arm64 ./cmd/aetheld && GOOS=windows GOARCH=amd64 go build -ldflags "$LDFLAGS" -o dist/aethel-windows-amd64.exe ./cmd/aethel && GOOS=windows GOARCH=amd64 go build -ldflags "$LDFLAGS" -o dist/aetheld-windows-amd64.exe ./cmd/aetheld' Write-Host "[aethel] Cross-compilation complete. See dist/" -ForegroundColor Green } diff --git a/scripts/dev.sh b/scripts/dev.sh index 28bad84..747dab2 100644 --- a/scripts/dev.sh +++ b/scripts/dev.sh @@ -10,7 +10,7 @@ DOCKER_RUN="docker run --rm -v ${PROJECT_DIR}:/src -v aethel-gomod:/go/pkg/mod - case "${1:-help}" in build) $DOCKER_RUN sh -c \ - "VER=\$(cat VERSION) && GOOS=windows GOARCH=amd64 go build -ldflags \"-X main.version=\$VER\" -o aethel.exe ./cmd/aethel && GOOS=windows GOARCH=amd64 go build -o aetheld.exe ./cmd/aetheld" + "VER=\$(cat VERSION) && LDFLAGS=\"-X main.version=\$VER\" && GOOS=windows GOARCH=amd64 go build -ldflags \"\$LDFLAGS\" -o aethel.exe ./cmd/aethel && GOOS=windows GOARCH=amd64 go build -ldflags \"\$LDFLAGS\" -o aetheld.exe ./cmd/aetheld" ;; test) @@ -31,15 +31,15 @@ case "${1:-help}" in VER=\$(cat VERSION) && LDFLAGS=\"-X main.version=\$VER\" && \ mkdir -p dist && \ GOOS=linux GOARCH=amd64 go build -ldflags \"\$LDFLAGS\" -o dist/aethel-linux-amd64 ./cmd/aethel && \ - GOOS=linux GOARCH=amd64 go build -o dist/aetheld-linux-amd64 ./cmd/aetheld && \ + GOOS=linux GOARCH=amd64 go build -ldflags \"\$LDFLAGS\" -o dist/aetheld-linux-amd64 ./cmd/aetheld && \ GOOS=linux GOARCH=arm64 go build -ldflags \"\$LDFLAGS\" -o dist/aethel-linux-arm64 ./cmd/aethel && \ - GOOS=linux GOARCH=arm64 go build -o dist/aetheld-linux-arm64 ./cmd/aetheld && \ + GOOS=linux GOARCH=arm64 go build -ldflags \"\$LDFLAGS\" -o dist/aetheld-linux-arm64 ./cmd/aetheld && \ GOOS=darwin GOARCH=amd64 go build -ldflags \"\$LDFLAGS\" -o dist/aethel-darwin-amd64 ./cmd/aethel && \ - GOOS=darwin GOARCH=amd64 go build -o dist/aetheld-darwin-amd64 ./cmd/aetheld && \ + GOOS=darwin GOARCH=amd64 go build -ldflags \"\$LDFLAGS\" -o dist/aetheld-darwin-amd64 ./cmd/aetheld && \ GOOS=darwin GOARCH=arm64 go build -ldflags \"\$LDFLAGS\" -o dist/aethel-darwin-arm64 ./cmd/aethel && \ - GOOS=darwin GOARCH=arm64 go build -o dist/aetheld-darwin-arm64 ./cmd/aetheld && \ + GOOS=darwin GOARCH=arm64 go build -ldflags \"\$LDFLAGS\" -o dist/aetheld-darwin-arm64 ./cmd/aetheld && \ GOOS=windows GOARCH=amd64 go build -ldflags \"\$LDFLAGS\" -o dist/aethel-windows-amd64.exe ./cmd/aethel && \ - GOOS=windows GOARCH=amd64 go build -o dist/aetheld-windows-amd64.exe ./cmd/aetheld" + GOOS=windows GOARCH=amd64 go build -ldflags \"\$LDFLAGS\" -o dist/aetheld-windows-amd64.exe ./cmd/aetheld" ;; image) diff --git a/scripts/install.sh b/scripts/install.sh new file mode 100644 index 0000000..4775b1d --- /dev/null +++ b/scripts/install.sh @@ -0,0 +1,126 @@ +#!/bin/sh +set -eu + +# Aethel installer — detects OS/arch, downloads the latest release, verifies checksum, installs. +# Usage: curl -sSfL https://raw.githubusercontent.com/artyomsv/aethel/master/scripts/install.sh | sh + +REPO="artyomsv/aethel" +INSTALL_DIR="${AETHEL_INSTALL_DIR:-$HOME/.local/bin}" +VERSION="${AETHEL_VERSION:-}" + +main() { + detect_platform + fetch_latest_version + download_and_verify + install_binaries + print_success +} + +detect_platform() { + OS=$(uname -s | tr '[:upper:]' '[:lower:]') + ARCH=$(uname -m) + + case "$OS" in + linux) OS="linux" ;; + darwin) OS="darwin" ;; + *) + echo "Error: unsupported OS: $OS" >&2 + echo "Download manually from https://github.com/$REPO/releases/latest" >&2 + exit 1 + ;; + esac + + case "$ARCH" in + x86_64|amd64) ARCH="amd64" ;; + aarch64|arm64) ARCH="arm64" ;; + *) + echo "Error: unsupported architecture: $ARCH" >&2 + exit 1 + ;; + esac + + echo "Detected platform: ${OS}/${ARCH}" +} + +fetch_latest_version() { + if [ -n "$VERSION" ]; then + echo "Using pinned version: v${VERSION}" + return + fi + + echo "Fetching latest release..." + RESPONSE=$(curl -sSf ${GITHUB_TOKEN:+-H "Authorization: token $GITHUB_TOKEN"} \ + "https://api.github.com/repos/$REPO/releases/latest") || { + echo "Error: failed to fetch latest release (GitHub API may be rate-limiting)" >&2 + echo "Set GITHUB_TOKEN or use AETHEL_VERSION=x.y.z to skip API call" >&2 + exit 1 + } + VERSION=$(echo "$RESPONSE" | grep '"tag_name"' | sed -E 's/.*"v([^"]+)".*/\1/') + + if [ -z "$VERSION" ]; then + echo "Error: could not determine latest version" >&2 + echo "Check https://github.com/$REPO/releases" >&2 + exit 1 + fi + + echo "Latest version: v${VERSION}" +} + +download_and_verify() { + ARCHIVE="aethel_${VERSION}_${OS}_${ARCH}.tar.gz" + BASE_URL="https://github.com/$REPO/releases/download/v${VERSION}" + TMP_DIR=$(mktemp -d) + trap 'rm -rf "$TMP_DIR"' EXIT + + echo "Downloading ${ARCHIVE}..." + curl -sSfL -o "$TMP_DIR/$ARCHIVE" "$BASE_URL/$ARCHIVE" + curl -sSfL -o "$TMP_DIR/checksums.txt" "$BASE_URL/checksums.txt" + + echo "Verifying checksum..." + EXPECTED=$(grep "$ARCHIVE" "$TMP_DIR/checksums.txt" | awk '{print $1}') + if [ -z "$EXPECTED" ]; then + echo "Error: checksum not found for $ARCHIVE in checksums.txt" >&2 + exit 1 + fi + ACTUAL=$(cd "$TMP_DIR" && { sha256sum "$ARCHIVE" 2>/dev/null || shasum -a 256 "$ARCHIVE"; } | awk '{print $1}') + if [ "$EXPECTED" != "$ACTUAL" ]; then + echo "Error: checksum mismatch" >&2 + echo " expected: $EXPECTED" >&2 + echo " got: $ACTUAL" >&2 + exit 1 + fi + echo "Checksum verified." + + echo "Extracting..." + tar -xzf "$TMP_DIR/$ARCHIVE" -C "$TMP_DIR" +} + +install_binaries() { + mkdir -p "$INSTALL_DIR" + cp "$TMP_DIR/aethel" "$INSTALL_DIR/aethel" + cp "$TMP_DIR/aetheld" "$INSTALL_DIR/aetheld" + chmod +x "$INSTALL_DIR/aethel" "$INSTALL_DIR/aetheld" +} + +print_success() { + echo "" + echo "Installed aethel v${VERSION} to ${INSTALL_DIR}/" + echo " ${INSTALL_DIR}/aethel" + echo " ${INSTALL_DIR}/aetheld" + + case ":$PATH:" in + *":$INSTALL_DIR:"*) ;; + *) + echo "" + echo "Add ${INSTALL_DIR} to your PATH:" + echo " export PATH=\"${INSTALL_DIR}:\$PATH\"" + echo "" + echo "Add this line to your ~/.bashrc or ~/.zshrc to make it permanent." + ;; + esac + + echo "" + echo "Run 'aethel' to get started." +} + +main diff --git a/scripts/rebuild.ps1 b/scripts/rebuild.ps1 index ccfc1b6..15bcb5e 100644 --- a/scripts/rebuild.ps1 +++ b/scripts/rebuild.ps1 @@ -41,7 +41,7 @@ docker run --rm ` -v "aethel-gomod:/go/pkg/mod" ` -w /src ` $GoImage ` - sh -c "GOOS=windows GOARCH=amd64 go build -ldflags ""-X main.version=$ver"" -o aethel.exe ./cmd/aethel && GOOS=windows GOARCH=amd64 go build -o aetheld.exe ./cmd/aetheld" + sh -c "GOOS=windows GOARCH=amd64 go build -ldflags ""-X main.version=$ver"" -o aethel.exe ./cmd/aethel && GOOS=windows GOARCH=amd64 go build -ldflags ""-X main.version=$ver"" -o aetheld.exe ./cmd/aetheld" if ($LASTEXITCODE -eq 0) { Write-Host "Done" -ForegroundColor Green