From 7bb93d970b3a8f2517b11fc6d4bc78de9fa8d93e Mon Sep 17 00:00:00 2001 From: Vincent Taverna Date: Fri, 3 Jul 2026 18:06:34 +0000 Subject: [PATCH] Update README with Docker publishing and release workflow refinements --- .github/workflows/release.yml | 101 +++++++++++++++++++++++++++++++++- README.md | 64 ++++++--------------- crates/bullmq/src/client.rs | 2 +- pnpm-lock.yaml | 51 ++++++++++++++++- 4 files changed, 169 insertions(+), 49 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b56b025..bd059d0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,7 +2,7 @@ name: Release # Triggered by a v* tag (pushed by the auto-tag job in version.yml after a # changesets version PR is merged). Builds all platform binaries in parallel, -# then publishes the per-platform npm packages followed by the root package. +# then publishes the per-platform npm packages, root package, and Docker image. on: push: @@ -109,3 +109,102 @@ jobs: uses: softprops/action-gh-release@v3 with: generate_release_notes: true + + docker: + name: Build Docker image (${{ matrix.arch }}) + needs: build + strategy: + matrix: + include: + - arch: amd64 + os: ubuntu-latest + artifact: bulltui-linux-x64 + - arch: arm64 + os: ubuntu-24.04-arm + artifact: bulltui-linux-arm64 + runs-on: ${{ matrix.os }} + permissions: + contents: read + packages: write + steps: + - uses: actions/checkout@v7 + - name: Download binary + uses: actions/download-artifact@v8 + with: + name: ${{ matrix.artifact }} + path: dist + - name: Mark binary executable + run: chmod +x dist/bulltui + - name: Write release Dockerfile + run: | + cat > Dockerfile.release << 'EOF' + FROM gcr.io/distroless/cc-debian12:nonroot + COPY dist/bulltui /usr/local/bin/bulltui + ENV TERM=xterm-256color + ENTRYPOINT ["/usr/local/bin/bulltui"] + EOF + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Log in to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Build and push by digest + id: build + uses: docker/build-push-action@v6 + with: + context: . + file: Dockerfile.release + platforms: linux/${{ matrix.arch }} + outputs: type=image,name=ghcr.io/vinnymac/bulltui,push-by-digest=true,name-canonical=true,push=true + - name: Export digest + run: | + mkdir -p digests + echo -n "${{ steps.build.outputs.digest }}" > digests/${{ matrix.arch }} + - name: Upload digest + uses: actions/upload-artifact@v7 + with: + name: docker-digest-${{ matrix.arch }} + path: digests/${{ matrix.arch }} + if-no-files-found: error + + docker-merge: + name: Merge Docker manifests + needs: docker + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - name: Download digests + uses: actions/download-artifact@v8 + with: + pattern: docker-digest-* + path: digests + merge-multiple: true + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Log in to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Docker metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ghcr.io/vinnymac/bulltui + tags: | + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=raw,value=latest + - name: Create and push manifest + run: | + docker buildx imagetools create \ + $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ + $(printf 'ghcr.io/vinnymac/bulltui@%s ' $(cat digests/*)) + env: + DOCKER_METADATA_OUTPUT_JSON: ${{ steps.meta.outputs.json }} diff --git a/README.md b/README.md index 36f62cd..d9aa729 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,7 @@ [![Downloads](https://img.shields.io/npm/dm/bulltui.svg)](https://www.npmjs.com/package/bulltui) [![License: Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE) -A fast, keyboard-driven terminal UI for [BullMQ](https://docs.bullmq.io/) — with -feature parity with [bull-board](https://github.com/felixmosh/bull-board), in -your terminal. Written in Rust with [ratatui](https://ratatui.rs); it talks to -Redis/Valkey **directly**, so there's no Node.js runtime to stand up. +A fast, keyboard-driven terminal UI for [BullMQ](https://docs.bullmq.io/). Written in Rust with [ratatui](https://ratatui.rs), it connects to Redis/Valkey directly. ![bulltui](assets/demo.gif) @@ -15,14 +12,14 @@ Redis/Valkey **directly**, so there's no Node.js runtime to stand up. ## Install ```sh -# run it straight from npm, no install needed +# run via npx npx bulltui # or install globally npm install -g bulltui -# Docker — see "Containers" below -docker run --rm -it vinnymac/bulltui --url redis://my-redis:6379 +# Docker +docker run --rm -it ghcr.io/vinnymac/bulltui --url redis://my-redis:6379 ``` ## Usage @@ -38,41 +35,25 @@ bulltui --splash-preview # hold the splash on screen (any key exi bulltui --snapshot # render one frame to stdout and exit ``` -`BULLTUI_REDIS_URL` and `BULLTUI_PREFIX` mirror `--url` / `--prefix`. Run -`bulltui --help` for the full flag list. +`BULLTUI_REDIS_URL` and `BULLTUI_PREFIX` mirror `--url` / `--prefix`. Run `bulltui --help` for the full flag list. ### TLS -Use a `rediss://` URL to connect over TLS — the norm for managed brokers such -as AWS ElastiCache (in-transit encryption), Upstash, Redis Cloud, and Azure -Cache. The server certificate is verified against a CA bundle compiled into the -binary (Mozilla's roots via `webpki-roots`), so there's nothing to install — -even in the distroless image. +Use a `rediss://` URL to connect over TLS, the standard for managed brokers like AWS ElastiCache (in-transit encryption), Upstash, Redis Cloud, and Azure Cache. The binary bundles Mozilla's CA roots via `webpki-roots`. ```sh bulltui --url rediss://user:pass@my-redis.example.com:6380 ``` -For a broker with a self-signed or private-CA certificate on a trusted network, -`--insecure` skips certificate verification. This disables authentication of the -server (man-in-the-middle exposure), so reach for it only when you understand -the trade-off; it errors on a plaintext `redis://` URL rather than doing nothing. +For a broker with a self-signed or private-CA certificate on a trusted network, `--insecure` skips certificate verification. Use this only when you understand the trade-off. It rejects plaintext `redis://` URLs. ## Features -- **Every bull-board operation** — pause/resume (incl. all queues), empty, - obliterate, clean, retry-/promote-all, add jobs, set concurrency; per-job - retry, promote, remove, duplicate, update. -- **Full job detail** — data, options, progress, error + stack trace, logs, - timeline, and a navigable parent→children **Flow** tree. -- **Beyond bull-board** — a live `XREAD` **events feed**, a **workers/busy** - view with lock-TTL health, **job schedulers** with next-run countdowns, a - fuzzy **command palette**, and multi-select **bulk** actions. -- **Verified against real BullMQ** — a Node seeder drives authentic - queues/jobs/flows; Rust e2e tests assert the client and TUI reproduce - bullmq's own view. -- **Works over SSH & tmux** — vim-style keys throughout; `y` copies any detail - tab to the clipboard via OSC 52. +- **Every bull-board operation**: pause/resume (including all queues), empty, obliterate, clean, retry all, promote all, add jobs, set concurrency, and per-job retry, promote, remove, duplicate, update. +- **Full job detail**: data, options, progress, error and stack trace, logs, timeline, and a navigable parent-to-children Flow tree. +- **Beyond bull-board**: a live `XREAD` events feed, a workers/busy view with lock-TTL health, job schedulers with next-run countdowns, a fuzzy command palette, and multi-select bulk actions. +- **Verified against real BullMQ**: Rust end-to-end tests assert the client and TUI reproduce BullMQ's own view. +- **Works over SSH and tmux**: vim-style keys throughout. `y` copies any detail tab to the clipboard via OSC 52. ## Keys @@ -82,19 +63,15 @@ the trade-off; it errors on a plaintext `redis://` URL rather than doing nothing ## Containers -bulltui ships as a small, distroless container image, so you can run it on -cloud infra without a Node or Rust toolchain. +bulltui ships as a small, distroless container image. ```sh -docker build -t bulltui . -docker run --rm -it bulltui --url redis://host.docker.internal:6379 +docker run --rm -it ghcr.io/vinnymac/bulltui --url redis://host.docker.internal:6379 ``` ### Kubernetes -Reach a Redis/Valkey that only lives inside your cluster's VPC by running -bulltui as an ephemeral pod right next to it — no port-forward, no public -exposure: +Run bulltui as an ephemeral pod to reach a Redis/Valkey instance inside your cluster's VPC: ```sh kubectl run bulltui --rm -it --restart=Never \ @@ -102,24 +79,19 @@ kubectl run bulltui --rm -it --restart=Never \ --env="BULLTUI_REDIS_URL=redis://redis.default.svc.cluster.local:6379" ``` -`-it` gives the TUI a TTY and `--rm` cleans the pod up on exit. Point -`BULLTUI_REDIS_URL` at the in-cluster Service DNS name (or ClusterIP) of your -broker; set `BULLTUI_PREFIX` if you don't use the default `bull`. For a managed -broker outside the cluster, use a `rediss://` URL (see [TLS](#tls)). +`-it` gives the TUI a TTY and `--rm` cleans the pod up on exit. Point `BULLTUI_REDIS_URL` at the in-cluster Service DNS name (or ClusterIP) of your broker. Set `BULLTUI_PREFIX` if you use a prefix other than `bull`. For a managed broker outside the cluster, use a `rediss://` URL (see [TLS](#tls)). ## Development ```sh just # list tasks just check # fmt-check + clippy -just test # full workspace suite (needs Docker + Node) +just test # full workspace suite (needs Docker) just demo # start a local Valkey + seed it just run # run against the demo Valkey ``` -The workspace is two crates: [`bullmq`](crates/bullmq) — a reusable, -direct-to-Redis BullMQ client (reads + admin writes) — and -[`bulltui`](crates/bulltui), the ratatui TUI built on top of it. +The workspace is two crates: [`bullmq`](crates/bullmq), a reusable direct-to-Redis BullMQ client (reads and admin writes), and [`bulltui`](crates/bulltui), the ratatui TUI built on top of it. ## License diff --git a/crates/bullmq/src/client.rs b/crates/bullmq/src/client.rs index f9468b9..774dc9e 100644 --- a/crates/bullmq/src/client.rs +++ b/crates/bullmq/src/client.rs @@ -436,7 +436,7 @@ impl BullClient { } let hashes: Vec> = pipe.query_async(&mut conn).await?; let mut jobs = Vec::with_capacity(ids.len()); - for (id, hash) in ids.iter().zip(hashes.into_iter()) { + for (id, hash) in ids.iter().zip(hashes) { if let Some(job) = Job::from_hash(id, &hash) { jobs.push(job); } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2cc05e5..4417e91 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,7 +12,20 @@ importers: specifier: ^2.27.0 version: 2.31.0 - npm/bulltui: {} + npm/bulltui: + optionalDependencies: + bulltui-darwin-arm64: + specifier: '*' + version: 0.1.0 + bulltui-darwin-x64: + specifier: '*' + version: 0.1.0 + bulltui-linux-arm64: + specifier: '*' + version: 0.1.0 + bulltui-linux-x64: + specifier: '*' + version: 0.1.0 packages: @@ -131,6 +144,30 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} + bulltui-darwin-arm64@0.1.0: + resolution: {integrity: sha512-PeT3zLVtGqwcfka6Wiq4kdx58d1xILggYvGvU7aG9PPA/W8h+YScRj3QbaJj3n7CWXUtNNtriqNpYV2zy4cpvQ==} + cpu: [arm64] + os: [darwin] + hasBin: true + + bulltui-darwin-x64@0.1.0: + resolution: {integrity: sha512-a6NNNJxVLGVlklK8PF9yrhwo7GlpEaUFwUM53otYxKbFZEZ02XLD0YRtY1PoL1Bd3q0s29MdFmS4AmL2iZ86Hg==} + cpu: [x64] + os: [darwin] + hasBin: true + + bulltui-linux-arm64@0.1.0: + resolution: {integrity: sha512-NL/7cu/OAI583f0KJLRHyLmjxbgyj+0WQwpl2s+DAcrmQhaPezqFHZbdqUvXnQRrawmexFghCMcgpqCXur2Oyw==} + cpu: [arm64] + os: [linux] + hasBin: true + + bulltui-linux-x64@0.1.0: + resolution: {integrity: sha512-tsD7TnvWRlOtsxw+BhWlIoMvZ+PwPyMqVSlwmDDT6nDDJR4fjmAr8CTZP14wtTja6ta8rP8ZGih3TtUdoU4V2Q==} + cpu: [x64] + os: [linux] + hasBin: true + chardet@2.2.0: resolution: {integrity: sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA==} @@ -589,6 +626,18 @@ snapshots: dependencies: fill-range: 7.1.1 + bulltui-darwin-arm64@0.1.0: + optional: true + + bulltui-darwin-x64@0.1.0: + optional: true + + bulltui-linux-arm64@0.1.0: + optional: true + + bulltui-linux-x64@0.1.0: + optional: true + chardet@2.2.0: {} cross-spawn@7.0.6: -- 2.51.2