diff --git a/.gitea/workflows/ci.yaml b/.gitea/workflows/ci.yaml new file mode 100644 index 0000000..59e48c7 --- /dev/null +++ b/.gitea/workflows/ci.yaml @@ -0,0 +1,65 @@ +name: CI + +# The release workflow gates a tag, which is late: a broken commit sits green until +# somebody decides to publish. This runs the same checks on the way in. +# +# push is scoped to main so that a branch pushed as part of a pull request is not checked +# twice. +# +# No actions/checkout, deliberately -- same as the terdut-server, letsvisit and charts +# workflows. The runner image is ubuntu:22.04 whose `nodejs` package is Node 12, and +# actions/checkout@v4 is built with ES2022 static initialiser blocks, so it dies with +# `SyntaxError: Unexpected token '{'` before running. Cloning with git directly avoids JS +# actions entirely. This repo is public, so the clone needs no credential at all. +# +# `${{ }}` values are passed through `env:` and referenced as quoted shell variables: a +# ref name is attacker-influenced by anyone who can push a branch or open a PR, and +# expanding one straight into `run:` is a shell-injection vector. +on: + push: + branches: [main] + pull_request: + +# A rapid series of pushes only needs the last one checked. +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +env: + REPO_URL: https://git.ryuvia.com/niklas/terdut-tui.git + +jobs: + test: + runs-on: ubuntu-latest + container: + image: golang:1.26.6-bookworm + # act_runner destroys a job's own volumes when it finishes, so without these every + # run re-downloads the whole module graph. The names must appear in the runner's + # container.valid_volumes allowlist (charts/act-runner in the k8s repo); unlisted + # volumes are dropped silently, so a workflow that looks correct can still be + # running uncached. + volumes: + - go-mod-cache:/go/pkg/mod + - go-build-cache:/root/.cache/go-build + - gobin-cache:/go/bin + steps: + - name: Checkout + env: + REF_NAME: ${{ github.ref_name }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + run: | + if [ -n "$HEAD_SHA" ]; then + # A pull_request ref_name is "/merge", which is not a fetchable branch. + git clone "$REPO_URL" . + git checkout -q "$HEAD_SHA" + else + git clone --depth=1 --branch "$REF_NAME" "$REPO_URL" . + fi + + - name: Vet + run: go vet ./... + + # Covers the API client against a stub server, the Update state machine, and View + # rendering -- all three are pure enough to test without a terminal. + - name: Test + run: go test ./... diff --git a/.gitea/workflows/release.yaml b/.gitea/workflows/release.yaml new file mode 100644 index 0000000..bcd1f46 --- /dev/null +++ b/.gitea/workflows/release.yaml @@ -0,0 +1,118 @@ +name: Release + +# Checkout, interpolation and caching conventions match ci.yaml -- see the header there +# for why there are no JS actions and why every `${{ }}` goes through `env:`. +# +# There is no upload-artifact/download-artifact equivalent here (both are JS actions, and +# this Gitea has no artifact store wired up), so the job that builds the binaries is also +# the job that publishes them. Nothing is handed between jobs. +# +# The asset names matter beyond being tidy: internal/updater looks for exactly +# terdut-tui--- in the latest release and reports every available name +# when it cannot find one. Renaming the pattern here breaks self-update for every +# installed binary. +on: + push: + tags: + - 'v*' + workflow_dispatch: + +concurrency: + group: release-${{ github.ref }} + cancel-in-progress: true + +env: + REPO_URL: https://git.ryuvia.com/niklas/terdut-tui.git + API: https://git.ryuvia.com/api/v1/repos/niklas/terdut-tui + +jobs: + # Gates the build, so a tag that fails here publishes no binaries. + test: + runs-on: ubuntu-latest + container: + image: golang:1.26.6-bookworm + volumes: + - go-mod-cache:/go/pkg/mod + - go-build-cache:/root/.cache/go-build + - gobin-cache:/go/bin + steps: + - name: Checkout + env: + REF_NAME: ${{ github.ref_name }} + run: git clone --depth=1 --branch "$REF_NAME" "$REPO_URL" . + + - name: Vet + run: go vet ./... + + - name: Test + run: go test ./... + + binaries: + needs: test + runs-on: ubuntu-latest + container: + image: golang:1.26.6-bookworm + volumes: + - go-mod-cache:/go/pkg/mod + - go-build-cache:/root/.cache/go-build + - gobin-cache:/go/bin + steps: + - name: Checkout + env: + REF_NAME: ${{ github.ref_name }} + run: git clone --depth=1 --branch "$REF_NAME" "$REPO_URL" . + + - name: Build every target + env: + REF_NAME: ${{ github.ref_name }} + run: | + set -eu + mkdir -p dist + for target in linux/amd64 linux/arm64 darwin/amd64 darwin/arm64; do + GOOS="${target%/*}" + GOARCH="${target#*/}" + out="dist/terdut-tui-${REF_NAME}-${GOOS}-${GOARCH}" + echo "building $out" + GOOS="$GOOS" GOARCH="$GOARCH" go build \ + -ldflags "-X main.version=${REF_NAME}" \ + -o "$out" . + done + + # Creating the release is made idempotent rather than assumed-new: a re-run of a + # failed release must not die on the release that already exists. Assets are + # replaced the same way, so a re-run repairs a partial upload. + - name: Publish the release + env: + REF_NAME: ${{ github.ref_name }} + TOKEN: ${{ secrets.REGISTRY_TOKEN }} + run: | + set -eu + auth="Authorization: token $TOKEN" + + body=$(curl -sf -H "$auth" "$API/releases/tags/$REF_NAME" || true) + if [ -z "$body" ]; then + body=$(curl -sf -X POST -H "$auth" -H 'Content-Type: application/json' \ + -d "{\"tag_name\":\"$REF_NAME\",\"name\":\"$REF_NAME\"}" \ + "$API/releases") + fi + + # The release object serialises `id` first, so the first match is the release's + # own id and not one of the nested author/asset ids. + release_id=$(printf '%s' "$body" | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2) + [ -n "$release_id" ] || { echo "::error::could not determine release id"; exit 1; } + echo "release id $release_id" + + for f in dist/*; do + name=$(basename "$f") + # Drop an existing asset of the same name first: Gitea happily stores two + # attachments with one name, and the updater matches by name. + old=$(curl -sf -H "$auth" "$API/releases/$release_id/assets" \ + | tr '}' '\n' | grep "\"name\":\"$name\"" \ + | grep -o '"id":[0-9]*' | head -1 | cut -d: -f2 || true) + if [ -n "$old" ]; then + curl -sf -X DELETE -H "$auth" "$API/releases/$release_id/assets/$old" || true + fi + echo "uploading $name" + curl -sf -X POST -H "$auth" -F "attachment=@$f" \ + "$API/releases/$release_id/assets?name=$name" > /dev/null + done diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 83294e9..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: CI - -# The release workflow gates a tag, which is late: a broken commit sits green -# until somebody decides to publish. This runs the same checks on the way in. -# -# push is scoped to main so that a branch pushed as part of a pull request is -# not checked twice. -on: - push: - branches: [main] - pull_request: - -# A rapid series of pushes only needs the last one checked. -concurrency: - group: ci-${{ github.ref }} - cancel-in-progress: true - -jobs: - test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-go@v5 - with: - go-version-file: go.mod - - - name: Vet - run: go vet ./... - - - name: Test - run: go test ./... diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 4c8f300..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,76 +0,0 @@ -name: Release - -on: - push: - tags: - - 'v*' - -jobs: - # Gates the build, so a tag that fails here publishes no binaries. The suite - # covers the API client against a stub server, the Update state machine, and - # View rendering — all three are pure enough to test without a terminal. - test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-go@v5 - with: - go-version-file: go.mod - - - name: Vet - run: go vet ./... - - - name: Test - run: go test ./... - - build: - needs: test - runs-on: ubuntu-latest - strategy: - matrix: - include: - - goos: linux - goarch: amd64 - - goos: linux - goarch: arm64 - - goos: darwin - goarch: amd64 - - goos: darwin - goarch: arm64 - - steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-go@v5 - with: - go-version-file: go.mod - - - name: Build - env: - GOOS: ${{ matrix.goos }} - GOARCH: ${{ matrix.goarch }} - run: | - go build \ - -ldflags "-X main.version=${{ github.ref_name }}" \ - -o terdut-tui-${{ github.ref_name }}-${{ matrix.goos }}-${{ matrix.goarch }} \ - . - - - uses: actions/upload-artifact@v4 - with: - name: terdut-tui-${{ github.ref_name }}-${{ matrix.goos }}-${{ matrix.goarch }} - path: terdut-tui-${{ github.ref_name }}-${{ matrix.goos }}-${{ matrix.goarch }} - - release: - needs: build - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - uses: actions/download-artifact@v4 - with: - merge-multiple: true - - - uses: softprops/action-gh-release@v2 - with: - files: 'terdut-tui-*' diff --git a/CLAUDE.md b/CLAUDE.md index b72a4ff..0247d01 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,6 +1,6 @@ # terdut-tui -TUI client for [terdut-server](https://github.com/terdut-server), a Prometheus Alertmanager receiver and incident manager. Requires server **v0.4.0+**. +TUI client for [terdut-server](https://git.ryuvia.com/niklas/terdut-server), a Prometheus Alertmanager receiver and incident manager. Requires server **v0.4.0+**. ## Domain model diff --git a/README.md b/README.md index 9c61671..ced732a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # terdut-tui -A terminal user interface for [terdut-server](https://github.com/terdut-server). Communicates with the server over its REST API. +A terminal user interface for [terdut-server](https://git.ryuvia.com/niklas/terdut-server). Communicates with the server over its REST API. Written in Go using [Bubbletea](https://github.com/charmbracelet/bubbletea). @@ -55,10 +55,10 @@ editing one reports the server's 404. ## Installation -Download the latest release binary for your platform from the [releases page](https://github.com/yeniklas/terdut-tui/releases), or build from source: +Download the latest release binary for your platform from the [releases page](https://git.ryuvia.com/niklas/terdut-tui/releases), or build from source: ```bash -go install github.com/yeniklas/terdut-tui@latest +go install git.ryuvia.com/niklas/terdut-tui@latest ``` ## Configuration diff --git a/go.mod b/go.mod index 1b51992..528a661 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/yeniklas/terdut-tui +module git.ryuvia.com/niklas/terdut-tui go 1.25.9 diff --git a/internal/tui/model.go b/internal/tui/model.go index 59acb67..4299d73 100644 --- a/internal/tui/model.go +++ b/internal/tui/model.go @@ -10,7 +10,7 @@ import ( "github.com/charmbracelet/bubbles/viewport" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" - "github.com/yeniklas/terdut-tui/internal/api" + "git.ryuvia.com/niklas/terdut-tui/internal/api" ) // ── Enums ────────────────────────────────────────────────────────────────── diff --git a/internal/tui/model_test.go b/internal/tui/model_test.go index 299ebe8..2cec137 100644 --- a/internal/tui/model_test.go +++ b/internal/tui/model_test.go @@ -5,7 +5,7 @@ import ( "time" "github.com/charmbracelet/bubbles/table" - "github.com/yeniklas/terdut-tui/internal/api" + "git.ryuvia.com/niklas/terdut-tui/internal/api" ) func TestNextFilter(t *testing.T) { diff --git a/internal/tui/styles.go b/internal/tui/styles.go index ada31ea..f3a1eae 100644 --- a/internal/tui/styles.go +++ b/internal/tui/styles.go @@ -4,7 +4,7 @@ import ( "strings" "github.com/charmbracelet/lipgloss" - "github.com/yeniklas/terdut-tui/internal/api" + "git.ryuvia.com/niklas/terdut-tui/internal/api" ) var ( diff --git a/internal/tui/update.go b/internal/tui/update.go index 5c7bd99..d8712a3 100644 --- a/internal/tui/update.go +++ b/internal/tui/update.go @@ -7,7 +7,7 @@ import ( "github.com/atotto/clipboard" "github.com/charmbracelet/bubbles/viewport" tea "github.com/charmbracelet/bubbletea" - "github.com/yeniklas/terdut-tui/internal/api" + "git.ryuvia.com/niklas/terdut-tui/internal/api" ) func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { diff --git a/internal/tui/update_test.go b/internal/tui/update_test.go index 453ed8a..acc2e9a 100644 --- a/internal/tui/update_test.go +++ b/internal/tui/update_test.go @@ -6,7 +6,7 @@ import ( "time" tea "github.com/charmbracelet/bubbletea" - "github.com/yeniklas/terdut-tui/internal/api" + "git.ryuvia.com/niklas/terdut-tui/internal/api" ) // press sends one key and returns the resulting model and command. A nil command diff --git a/internal/tui/view.go b/internal/tui/view.go index 7043a66..378b844 100644 --- a/internal/tui/view.go +++ b/internal/tui/view.go @@ -7,7 +7,7 @@ import ( "time" "github.com/charmbracelet/lipgloss" - "github.com/yeniklas/terdut-tui/internal/api" + "git.ryuvia.com/niklas/terdut-tui/internal/api" ) // Order must match the section constants — renderTabs indexes this by ordinal. diff --git a/internal/tui/view_test.go b/internal/tui/view_test.go index 1f84646..f2570a0 100644 --- a/internal/tui/view_test.go +++ b/internal/tui/view_test.go @@ -7,7 +7,7 @@ import ( "time" tea "github.com/charmbracelet/bubbletea" - "github.com/yeniklas/terdut-tui/internal/api" + "git.ryuvia.com/niklas/terdut-tui/internal/api" ) // ansi matches the escape sequences lipgloss emits when it decides the output diff --git a/internal/updater/updater.go b/internal/updater/updater.go index 61979a4..b87dc09 100644 --- a/internal/updater/updater.go +++ b/internal/updater/updater.go @@ -12,7 +12,14 @@ import ( "strings" ) -const releaseAPI = "https://api.github.com/repos/yeniklas/terdut-tui/releases/latest" +// Gitea's release payload carries the same tag_name, and its attachments the same name +// and browser_download_url, so the types below are unchanged from the GitHub original. +// +// A binary installed before the move still polls api.github.com and will never see a +// release published here. That GitHub repository is still in place, so such a build +// reports itself up to date rather than erroring -- its last GitHub release is the +// bridge, and crossing it is a one-time manual download. +const releaseAPI = "https://git.ryuvia.com/api/v1/repos/niklas/terdut-tui/releases/latest" type release struct { TagName string `json:"tag_name"` @@ -125,7 +132,7 @@ func fetchLatest() (*release, error) { if err != nil { return nil, err } - req.Header.Set("Accept", "application/vnd.github+json") + req.Header.Set("Accept", "application/json") resp, err := http.DefaultClient.Do(req) if err != nil { @@ -134,7 +141,7 @@ func fetchLatest() (*release, error) { defer resp.Body.Close() if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("GitHub API returned %s", resp.Status) + return nil, fmt.Errorf("Gitea API returned %s", resp.Status) } var rel release diff --git a/main.go b/main.go index 81daee3..57e4678 100644 --- a/main.go +++ b/main.go @@ -6,10 +6,10 @@ import ( "os" tea "github.com/charmbracelet/bubbletea" - "github.com/yeniklas/terdut-tui/internal/api" - "github.com/yeniklas/terdut-tui/internal/config" - "github.com/yeniklas/terdut-tui/internal/tui" - "github.com/yeniklas/terdut-tui/internal/updater" + "git.ryuvia.com/niklas/terdut-tui/internal/api" + "git.ryuvia.com/niklas/terdut-tui/internal/config" + "git.ryuvia.com/niklas/terdut-tui/internal/tui" + "git.ryuvia.com/niklas/terdut-tui/internal/updater" ) var version = "dev"