Skip to content

feat(dist): curl|sh install for the local runner + CI releases#54

Merged
BorisTyshkevich merged 6 commits into
mainfrom
feat/local-runner-curl-install
Jun 27, 2026
Merged

feat(dist): curl|sh install for the local runner + CI releases#54
BorisTyshkevich merged 6 commits into
mainfrom
feat/local-runner-curl-install

Conversation

@BorisTyshkevich

Copy link
Copy Markdown
Collaborator

Stacked on #53 (base = feat/local-accept-invalid-cert). Review/merge #53 first; GitHub will retarget this to main once #53 lands. The diff below is distribution-only.

What

Distribute the (Python) local runner so users can run it with just python3 — no clone, no Node:

curl -fsSL https://raw.githubusercontent.com/Altinity/altinity-sql-browser/main/install.sh | sh
altinity-sql-browser          # → http://localhost:8900/sql

Changes

  • install.sh (repo root) — POSIX curl | sh installer: resolves the latest release, downloads altinity-sql-browser.tar.gz, verifies the sha256, extracts to ~/.altinity-sql-browser, installs a launcher in ~/.local/bin. Env overrides ASB_VERSION / ASB_HOME / ASB_BIN. Distinct from deploy/install.sh (which deploys onto a ClickHouse cluster).
  • build/bundle.sh — assembles the release tarball: prebuilt sql.html + local.py + config.example.xml + a self-resolving run.sh + VERSION/README.txt, with a .sha256. Runnable locally; used by CI.
  • build/local.py — discovers sql.html across layouts ($SQL_BROWSER_SPA → next-to-script bundle → ../dist dev), so one runner works both bundled and in a checkout.
  • deploy/clickhouse-client-config.example.xml — a sample clickhouse-client connections file shipped in the bundle as config.example.xml. It never replaces the user's real ~/.clickhouse-client/config.xml; use it via LOCAL_CH_CONFIG.
  • .github/workflows/release.yml — on a v* tag: coverage gate → build bundle → gh release create with the tarball, checksum, and raw sql.html.
  • .github/workflows/ci.yml — new bundle job smoke-tests the artifact on every PR (extract → boot the runner → fetch /sql + /config.json) and shellchecks the installer.
  • READMEcurl | sh quick-install + a Releasing section.

Testing

  • Built the bundle, extracted as the installer does, booted run.sh under the sample config: /sql 200 (SPA found via bundle layout), /config.json hosts parsed (dev-tenantinsecure=true, secure hosts → :8443). Dev layout (build/local.py../dist) still serves. Coverage gate unchanged (952 passing).
  • CI's bundle job reproduces the extract-and-boot path on every PR.

Cut a release

git tag v0.1.0 && git push origin v0.1.0

🤖 Generated with Claude Code

https://claude.ai/code/session_01MUc6UU9qs5J3u7qoy5YoNN

BorisTyshkevich and others added 6 commits June 27, 2026 13:44
Distribute the Python local runner as a downloadable bundle so users can run it
with just python3 — no clone, no Node.

- install.sh (root): POSIX `curl | sh` installer. Resolves the latest release,
  downloads altinity-sql-browser.tar.gz, verifies the sha256, extracts to
  ~/.altinity-sql-browser, installs a launcher in ~/.local/bin. (Distinct from
  deploy/install.sh, which deploys onto a ClickHouse cluster.)
- build/bundle.sh: assemble the release tarball (prebuilt sql.html + local.py +
  config.example.xml + self-resolving run.sh + VERSION/README) with a sha256.
- build/local.py: discover sql.html across layouts — $SQL_BROWSER_SPA, then
  next-to-the-script (bundle), then ../dist (dev) — so the same runner works in
  the bundle and in a checkout.
- deploy/clickhouse-client-config.example.xml: a sample connections file shipped
  in the bundle as config.example.xml; it never replaces the user's real
  ~/.clickhouse-client/config.xml.
- .github/workflows/release.yml: on a v* tag → gate + bundle + gh release.
- .github/workflows/ci.yml: new `bundle` job smoke-tests the artifact on every
  PR (extract → boot the runner → fetch /sql + /config.json) and shellchecks the
  installer.
- README: curl|sh quick-install + a Releasing section.

Verified locally: bundle builds, extracts, boots under the sample config (SPA
found via bundle layout, config.json hosts parsed), and the dev layout still
serves from ../dist. Coverage gate unchanged (952 passing).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MUc6UU9qs5J3u7qoy5YoNN
…demos

Refines the bundle from real, verified connections and adds a second connection
source so a fresh install has something to connect to.

- build/local.py: read connections from MULTIPLE files and merge, de-duped by
  name (first wins): ~/.clickhouse-client/config.xml (the user's own, wins on a
  clash), then ~/.clickhouse-client/sql-browser.xml (installed demos), then a
  sql-browser.xml next to the runner (run-from-bundle). LOCAL_CH_CONFIG still
  overrides to a single explicit file. Document that the HTTP port comes from
  <http_port> or the 8443/8123 default — the native <port> (9440/9000) is never
  used to derive it.
- deploy/sql-browser.xml: replaces the placeholder example with PUBLIC demo
  clusters verified reachable over the HTTP interface (antalya, altinity-demo,
  clickhouse-sql) + a commented OAuth template (no real secret). Dropped
  play.clickhouse.com: it exposes no HTTP query interface (native-only), so the
  browser can't reach it.
- install.sh: write the sample to ~/.clickhouse-client/sql-browser.xml (backing
  up any existing one); never touches config.xml.
- build/bundle.sh + ci.yml: ship/smoke-test sql-browser.xml; the CI boot test
  now relies on the runner discovering it next to local.py (no LOCAL_CH_CONFIG).
- README: document the merge + the http-port rule.

Verified: merge keeps config.xml on a name clash (5 real → 5, demos deduped);
fresh machine (bundled file only) → 3 demos; gate 952 passing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MUc6UU9qs5J3u7qoy5YoNN
…int table

The picker only offers connections the browser can actually POST to. At startup
the runner resolves each connection's HTTP URL, probes /ping concurrently, prints
a status table to stdout, and serves only the reachable hosts — so a native-only
endpoint (e.g. play.clickhouse.com, no HTTP interface) is shown as skipped with a
reason instead of being a dead pick in the picker.

- build/local.py: split build_config into pure collect()/serialize(); add probe()
  (GET /ping, unverified TLS for accept-invalid hosts; "reachable" = any HTTP
  response, "unreachable" = connection-level failure) + concurrent probe_all();
  main() probes, prints the table, and serializes only kept hosts (dropping an
  unreachable OAuth host also drops its now-dangling IdP). Flushed so the table
  shows immediately. SQL_BROWSER_PROBE=0 / SQL_BROWSER_PROBE_TIMEOUT to control it.
- ci.yml: smoke test sets SQL_BROWSER_PROBE=0 (don't depend on external hosts).
- README + docstring: document the probe + skip behavior.

Verified against the real config: 4/5 reachable, clickhouse-play skipped
(timed out — native-only); served config.json excludes it. Gate 952 passing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MUc6UU9qs5J3u7qoy5YoNN
Port discovery is core to the runner: a cluster may serve the HTTP interface on
8443 (direct) or 443 (TLS-terminating proxy), and the config only carries the
native <port>. So when no <http_port> is given, try both standard ports and pin
whichever answers Ok. on /ping; only skip a host when NO port has an HTTP
interface.

- collect(): build a per-host candidate list (_alts) — [8443,443] secure /
  [8123,80] plain, or the single explicit/embedded port; default url = first.
- probe(): try candidates in order, first /ping == "Ok." wins; returns the chosen
  URL (port pinned) or None. serialize() strips the internal _alts field.
- main(): table shows the winning port per host, or the per-port failure reasons
  for a skip.

Effect: clickhouse-play, which times out on :8443, now resolves on :443 (its
/ping returns Ok.) → 5/5 reachable instead of being skipped. Gate 952 passing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MUc6UU9qs5J3u7qoy5YoNN
The Connect button required BOTH a username and a non-empty password, so a
passwordless user (e.g. ClickHouse `play` on the public playground / our bundled
demos) could never connect — the button stayed disabled. Require only a username;
an empty password sends HTTP Basic `user:`, which ClickHouse accepts.

- src/ui/login.js: hasCreds() now checks the username only.
- tests: a username alone enables Connect (primary, "as <user>"); Enter submits
  with a username and no password; selecting a passwordless saved connection
  enables Connect. Updated the prior "needs both fields" Enter test.

Verified in-browser against the public playground: selecting the passwordless
demo enables Connect, login succeeds, and the schema loads. Gate 954 passing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MUc6UU9qs5J3u7qoy5YoNN
Two fixes for cross-origin picker connections:

- src/ui/app.js: app.host() returned the serving host (loc.host) in OAuth mode,
  so signing in via a picker connection that targets another cluster showed
  "localhost:8900" instead of the cluster. Use chCtx.origin's host for both auth
  modes — it already resolves to the basic target / oauth_origin / serving origin.
  (URL.host drops a default :443, so a 443 cluster shows a bare hostname.)
- build/local.py: probe 443 before 8443 for secure connections (8123 then 80 for
  plain). 443 is the canonical public endpoint for managed clusters and avoids the
  8443 timeout when only 443 is open; the /ping=='Ok.' check still rejects a 443
  auth-gateway and falls through to 8443 (e.g. the support tenant).

Verified: header shows antalya.demo.altinity.cloud (cross-origin), all demo
clusters resolve on :443 (5/5, no 8443 stall). Gate 955 passing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MUc6UU9qs5J3u7qoy5YoNN
@BorisTyshkevich BorisTyshkevich changed the base branch from feat/local-accept-invalid-cert to main June 27, 2026 13:16
@BorisTyshkevich BorisTyshkevich merged commit a82ba04 into main Jun 27, 2026
3 checks passed
@BorisTyshkevich BorisTyshkevich deleted the feat/local-runner-curl-install branch June 27, 2026 13:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant