Skip to content

feat: add one-line installer and v0.1.0 release prep#381

Merged
nao1215 merged 2 commits intomainfrom
release/issue-136-v010-prep
Apr 17, 2026
Merged

feat: add one-line installer and v0.1.0 release prep#381
nao1215 merged 2 commits intomainfrom
release/issue-136-v010-prep

Conversation

@nao1215
Copy link
Copy Markdown
Owner

@nao1215 nao1215 commented Apr 17, 2026

Summary

Lower the friction of the public install story (Issue #136) and make the repo ready for a clean v0.1.0 Hex publish. All changes are docs / scripts / CI — no source code touched.

Changes

  • scripts/install.sh (new): POSIX installer invoked via curl -fsSL ... | sh. Resolves the latest release tag through the GitHub API, downloads the escript into $HOME/.local/bin/sqlode, chmods it, and warns when Erlang/OTP is missing (with per-distro install hints: apt / dnf / pacman / apk / brew). Respects SQLODE_VERSION and SQLODE_INSTALL_DIR overrides. Notes when the install directory is not on PATH.
  • README.md: promote the one-line installer as the primary path. Kept the manual escript download and the gleam run workflow as alternatives. Added Hex version, Hex downloads, and CI badges in the same layout nao1215/oaspec uses.
  • .github/workflows/release.yml: pipe I am not using semantic versioning into gleam publish -y, matching oaspec's workaround so the interactive pre-1.0 semver confirmation does not hang the publish step.

Design Decisions

  • Kept the escript artifact as the canonical release asset. Shipping fully static binaries or bundling BEAM is out of scope; the installer just automates downloading it and guides the user to Erlang/OTP. This is the cheapest first-class install path that still works without users having an existing Gleam project.
  • Used the GitHub Releases API with sed (no jq dependency) so the installer runs on minimal environments.
  • install.sh is committed unexecuted-on-fetch — users invoke it via a piped sh. The raw-github URL in the README targets main, so tagged releases are unaffected if we later move the script.
  • Did not create a Homebrew tap repo. That is best handled in a follow-up alongside an actual v0.1.0 publish, since the formula needs a valid release SHA.

Limitations

  • Requires curl and sh on the host; works on Linux and macOS, not evaluated on Windows (expected to work from Git Bash / WSL).
  • Per-distro Erlang hints are advisory only — the installer does not attempt to invoke the package manager.
  • Local verification: just all (828 gleam tests + 20 shellspec examples) green. The installer's download path cannot be smoke-tested until a v0.1.0 release asset exists.

Closes #136

Summary by CodeRabbit

  • New Features

    • Added one-line installation script for simplified setup and binary downloads.
  • Documentation

    • Updated installation guide with Erlang/OTP runtime requirements and multiple installation options.
    • Added badges for version tracking and CI status.
    • Documented environment variables for customizing installation directories and versions.

Lower the friction of the public install story (Issue #136) and
clear the way for a v0.1.0 Hex publish.

- scripts/install.sh: new POSIX installer that resolves the latest
  release tag via the GitHub API, downloads the released escript into
  `$HOME/.local/bin/sqlode`, warns about missing Erlang/OTP with
  per-distro install hints, and notes when the install directory is
  not on PATH. Respects `SQLODE_VERSION` and `SQLODE_INSTALL_DIR`.
- README.md: promote the one-line `curl | sh` install as the primary
  path, reorganise the manual escript and `gleam run` options as
  fallbacks, and add Hex version, Hex downloads, and CI badges in the
  style already used by nao1215/oaspec.
- .github/workflows/release.yml: pipe `I am not using semantic
  versioning` into `gleam publish -y`, matching oaspec's workaround
  for the pre-1.0 semver confirmation prompt so the v0.1.0 publish
  step does not hang.

Closes #136
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 17, 2026

Warning

Rate limit exceeded

@nao1215 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 50 minutes and 21 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 50 minutes and 21 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 08c3d026-ad0c-4fc2-8a52-3751a00e0f29

📥 Commits

Reviewing files that changed from the base of the PR and between bf91fbb and 1c45129.

📒 Files selected for processing (2)
  • README.md
  • scripts/install.sh
📝 Walkthrough

Walkthrough

A new installation script enables one-line installation of sqlode binaries from GitHub Releases, supporting version selection and customizable install directories. README documentation is updated to promote this method as the recommended option alongside existing installation paths.

Changes

Cohort / File(s) Summary
Installation script
scripts/install.sh
New shell script providing one-line installer for sqlode escript binaries. Handles prerequisite checks (curl, uname), version resolution via GitHub Releases API, binary download and installation to configurable directory (defaulting to $HOME/.local/bin), and post-install verification with PATH hints.
Documentation and release configuration
README.md, .github/workflows/release.yml
Updated README to promote new one-line install as primary method, document Erlang/OTP runtime requirements, and reorganize existing installation paths. Modified release workflow to pipe input into gleam publish command for non-interactive publishing.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

Hop skip and jump, our script takes flight! 🐰
One line to install, no setup required,
From GitHub releases straight to your bin,
The rabbit approves: install.sh shines bright,
Zero-friction setup—what users desired! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: adding a one-line installer script and preparing for v0.1.0 release, which aligns with the primary focus of the changeset.
Linked Issues check ✅ Passed The PR fully addresses issue #136 by implementing a low-friction install path (one-line installer) that does not require Erlang/OTP or Gleam project upfront, meeting all stated objectives.
Out of Scope Changes check ✅ Passed All changes are in-scope: scripts/install.sh and README.md updates directly support the installer feature; release.yml change is necessary for publishing automation without manual input.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch release/issue-136-v010-prep

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (4)
scripts/install.sh (3)

66-75: GitHub API rate limiting will break unauthenticated latest resolution.

The unauthenticated GitHub REST API is limited to 60 requests/hour per IP. From shared networks (corporate NATs, CI runners, Codespaces) users can easily hit the limit, in which case curl -fsSL fails with 403 and users see only the generic "could not determine latest release tag" error.

Two small improvements:

  1. Mention the rate-limit possibility in the die message (e.g. "…or GitHub API rate limit exceeded; retry later or pin SQLODE_VERSION").
  2. Optionally fall back to resolving latest via the redirect from https://github.com/${REPO}/releases/latest/download/… or curl -sI .../releases/latest and parsing the Location: header, which is not rate-limited the same way.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/install.sh` around lines 66 - 75, The resolve_version function should
handle GitHub API rate limits and provide a clearer error: when tag resolution
fails, update the die message (used in resolve_version) to mention GitHub API
rate limiting and advise retrying later or pinning VERSION/SQLODE_VERSION;
additionally implement an optional fallback inside resolve_version that, if the
API call returns empty, performs a HEAD request to
"https://github.com/${REPO}/releases/latest" (e.g., curl -sI) and parses the
Location header to extract the redirect tag and set VERSION accordingly, falling
back to the improved die message only if both methods fail; reference
resolve_version, VERSION, REPO, and die in your changes.

69-69: Minor: sed pattern assumes pretty-printed JSON.

sed -n 's/.*"tag_name": *"\([^"]*\)".*/\1/p' | head -n1 works because GitHub's API currently pretty-prints responses (one JSON key per line). If that ever changes to minified output, this picks the first tag_name on a single line, which is still the release object's tag — likely still correct, but fragile. Acceptable for now given the "no jq" constraint; leaving a note for future hardening.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/install.sh` at line 69, The current extraction for tag (the
tag="$(curl ... | sed -n 's/.*"tag_name": *"\([^"]*\)".*/\1/p' | head -n1)")
relies on pretty-printed JSON; replace the sed+head approach with a more robust
single-line extractor such as using grep -oP (e.g. grep -oP -m1
'"tag_name"\s*:\s*"\K[^"]+') or an equivalent POSIX-safe pattern that matches
"tag_name":<optional spaces>"<value>" anywhere in the stream so tag is correctly
captured even if the JSON is minified.

77-94: Temp file can leak on interrupt between download and install_bin.

With set -eu and no trap, if the user hits Ctrl-C after download() completes but before/during install_bin(), or if mkdir -p "$INSTALL_DIR" fails (e.g. permission denied when users follow the README's SQLODE_INSTALL_DIR=/usr/local/bin suggestion without sudo), the mktemp'd file in $TMPDIR is left behind.

♻️ Suggested cleanup trap
 download() {
   url="https://github.com/${REPO}/releases/download/${VERSION}/${BIN_NAME}"
   info "downloading ${BIN_NAME} ${VERSION} from ${url}"
   tmp="$(mktemp -t sqlode.XXXXXX)"
+  # shellcheck disable=SC2064
+  trap "rm -f \"$tmp\"" EXIT INT HUP TERM
   if ! curl -fsSL -o "$tmp" "$url"; then
     rm -f "$tmp"
     die "failed to download ${url}. Check SQLODE_VERSION or visit https://github.com/${REPO}/releases."
   fi
   DOWNLOAD="$tmp"
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/install.sh` around lines 77 - 94, The download() function creates a
temporary file with mktemp and can leak it if the script is interrupted before
install_bin() moves/removes it; add a cleanup trap so the temp file is removed
on EXIT/ERR/INT if it's still present. Implement a trap that checks the DOWNLOAD
variable and rm -f "$DOWNLOAD" on script exit/failure (set via trap '...' EXIT
ERR INT) or register a per-download trap that is cleared after successful mv in
install_bin(); update download(), install_bin(), and the top-level script init
to set/clear that trap so temporary files are reliably deleted even if mkdir -p
or mv fails. Ensure references to the DOWNLOAD variable and functions download()
and install_bin() are used when adding and clearing the trap.
README.md (1)

20-22: Piping curl | sh is the documented install path — consider offering a safer alternative.

Standard nit for public installers: recommend documenting a two-step alternative next to the one-liner so security-conscious users can inspect the script first:

 ```console
 curl -fsSL https://raw.githubusercontent.com/nao1215/sqlode/main/scripts/install.sh | sh

+Prefer to inspect first? Download, review, then run:
+console +curl -fsSL -o install.sh https://raw.githubusercontent.com/nao1215/sqlode/main/scripts/install.sh +sh install.sh +


<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @README.md around lines 20 - 22, Add a safer two-step install alternative
next to the existing one-liner curl | sh command in the README: keep the
original curl -fsSL ... | sh example, then add a short paragraph titled like
"Prefer to inspect first?" followed by the two-step approach that downloads the
script (using curl -o or similar) so users can review the file before executing,
and then runs it (sh install.sh); update the surrounding text to present both
options clearly so security-conscious users can choose the inspect-then-run
flow.


</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>🤖 Prompt for all review comments with AI agents</summary>

Verify each finding against the current code and only fix it if needed.

Inline comments:
In @README.md:

  • Line 24: Update the README to note that overriding SQLODE_INSTALL_DIR to a
    system path like /usr/local/bin requires root, and show a safe example (e.g.
    prepend sudo: sudo SQLODE_INSTALL_DIR=/usr/local/bin sh ./scripts/install.sh) or
    prefer a user-writable example such as $HOME/.local/bin; reference the installer
    behavior in install_bin() so readers understand mkdir/mv/chmod operations will
    fail without elevated privileges.

In @scripts/install.sh:

  • Around line 77-86: The download() function currently saves the release asset
    without integrity checks; update it to fetch a released checksum (e.g.,
    ${BIN_NAME}.sha256 or ${VERSION}.sha256) from the same GitHub release and verify
    the downloaded file before returning. Modify download() to: after successful
    curl of the binary, attempt to curl the checksum file, detect available verifier
    (sha256sum or shasum -a 256), validate the binary against the checksum, rm -f
    and die on mismatch or missing verifier, and only set DOWNLOAD="$tmp" if
    verification passes; keep a clear error message mentioning the checksum URL and
    verification failure. Ensure behavior degrades gracefully (fail fast) when
    checksum is missing or verification tools are unavailable.
  • Around line 88-94: The install_bin function currently assumes INSTALL_DIR is
    writable and will produce a raw permission error if not; update install_bin to
    first check that INSTALL_DIR is writable/creatable (e.g., test -w "$INSTALL_DIR"
    or attempt mkdir -p and check its exit) and if it is not writable print a clear,
    actionable error that suggests re-running with elevated privileges (for example
    "re-run with sudo -E sh -c 'SQLODE_INSTALL_DIR=/usr/local/bin ...'") or using a
    user-local path like "$HOME/.local/bin", then exit non-zero; reference symbols:
    install_bin, INSTALL_DIR, BIN_NAME, DOWNLOAD, INSTALLED so the check happens
    before mv/chmod and no raw mv permission-denied is shown.

Nitpick comments:
In @README.md:

  • Around line 20-22: Add a safer two-step install alternative next to the
    existing one-liner curl | sh command in the README: keep the original curl -fsSL
    ... | sh example, then add a short paragraph titled like "Prefer to inspect
    first?" followed by the two-step approach that downloads the script (using curl
    -o or similar) so users can review the file before executing, and then runs it
    (sh install.sh); update the surrounding text to present both options clearly so
    security-conscious users can choose the inspect-then-run flow.

In @scripts/install.sh:

  • Around line 66-75: The resolve_version function should handle GitHub API rate
    limits and provide a clearer error: when tag resolution fails, update the die
    message (used in resolve_version) to mention GitHub API rate limiting and advise
    retrying later or pinning VERSION/SQLODE_VERSION; additionally implement an
    optional fallback inside resolve_version that, if the API call returns empty,
    performs a HEAD request to "https://github.com/${REPO}/releases/latest" (e.g.,
    curl -sI) and parses the Location header to extract the redirect tag and set
    VERSION accordingly, falling back to the improved die message only if both
    methods fail; reference resolve_version, VERSION, REPO, and die in your changes.
  • Line 69: The current extraction for tag (the tag="$(curl ... | sed -n
    's/."tag_name": "([^"])"./\1/p' | head -n1)") relies on pretty-printed
    JSON; replace the sed+head approach with a more robust single-line extractor
    such as using grep -oP (e.g. grep -oP -m1 '"tag_name"\s*:\s*"\K[^"]+') or an
    equivalent POSIX-safe pattern that matches "tag_name":""
    anywhere in the stream so tag is correctly captured even if the JSON is
    minified.
  • Around line 77-94: The download() function creates a temporary file with
    mktemp and can leak it if the script is interrupted before install_bin()
    moves/removes it; add a cleanup trap so the temp file is removed on EXIT/ERR/INT
    if it's still present. Implement a trap that checks the DOWNLOAD variable and rm
    -f "$DOWNLOAD" on script exit/failure (set via trap '...' EXIT ERR INT) or
    register a per-download trap that is cleared after successful mv in
    install_bin(); update download(), install_bin(), and the top-level script init
    to set/clear that trap so temporary files are reliably deleted even if mkdir -p
    or mv fails. Ensure references to the DOWNLOAD variable and functions download()
    and install_bin() are used when adding and clearing the trap.

</details>

<details>
<summary>🪄 Autofix (Beta)</summary>

Fix all unresolved CodeRabbit comments on this PR:

- [ ] <!-- {"checkboxId": "4b0d0e0a-96d7-4f10-b296-3a18ea78f0b9"} --> Push a commit to this branch (recommended)
- [ ] <!-- {"checkboxId": "ff5b1114-7d8c-49e6-8ac1-43f82af23a33"} --> Create a new PR with the fixes

</details>

---

<details>
<summary>ℹ️ Review info</summary>

<details>
<summary>⚙️ Run configuration</summary>

**Configuration used**: defaults

**Review profile**: CHILL

**Plan**: Pro

**Run ID**: `5323803f-96eb-4a39-9d65-0ecd09cd5e08`

</details>

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 6c99a2b52ff7f1df661ae0dda6023ed2faf490c5 and bf91fbb4930b6f2d4d0817322b24e0177c18dae5.

</details>

<details>
<summary>📒 Files selected for processing (3)</summary>

* `.github/workflows/release.yml`
* `README.md`
* `scripts/install.sh`

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

Comment thread README.md Outdated
Comment thread scripts/install.sh
Comment on lines +77 to +86
download() {
url="https://github.com/${REPO}/releases/download/${VERSION}/${BIN_NAME}"
info "downloading ${BIN_NAME} ${VERSION} from ${url}"
tmp="$(mktemp -t sqlode.XXXXXX)"
if ! curl -fsSL -o "$tmp" "$url"; then
rm -f "$tmp"
die "failed to download ${url}. Check SQLODE_VERSION or visit https://github.com/${REPO}/releases."
fi
DOWNLOAD="$tmp"
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Consider adding checksum/signature verification for the downloaded binary.

This script is intended to be run via curl ... | sh, and it then fetches and chmod +xes another binary from the internet without any integrity check. HTTPS from github.com is a reasonable baseline, but a compromised release asset, a proxy MITM, or a partial download (unlikely with -f, but possible) would be silently installed and executed by verify(). Publishing a sqlode.sha256 alongside each release and verifying it here (when sha256sum/shasum is available) would materially improve the security posture of the public install path, especially since this is the path promoted as "recommended" in the README.

Not a blocker for v0.1.0, but worth tracking as a follow-up before adoption grows.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/install.sh` around lines 77 - 86, The download() function currently
saves the release asset without integrity checks; update it to fetch a released
checksum (e.g., ${BIN_NAME}.sha256 or ${VERSION}.sha256) from the same GitHub
release and verify the downloaded file before returning. Modify download() to:
after successful curl of the binary, attempt to curl the checksum file, detect
available verifier (sha256sum or shasum -a 256), validate the binary against the
checksum, rm -f and die on mismatch or missing verifier, and only set
DOWNLOAD="$tmp" if verification passes; keep a clear error message mentioning
the checksum URL and verification failure. Ensure behavior degrades gracefully
(fail fast) when checksum is missing or verification tools are unavailable.

Comment thread scripts/install.sh
- Add EXIT/INT/HUP/TERM trap so the mktemp'd download is always removed
  if the script dies between download() and install_bin() (Ctrl-C,
  failing mkdir, etc).
- Fall back to parsing the Location header from /releases/latest when
  the JSON API call returns empty. The API is rate limited to 60 req/h
  for unauthenticated clients; shared networks and CI runners hit that
  limit. The HEAD-redirect path is not subject to the same limit, and
  the die message now mentions both the rate-limit possibility and
  pinning via SQLODE_VERSION.
- Detect non-writable INSTALL_DIR up front in install_bin() and emit a
  sudo example instead of letting `mv` fail with a raw permission
  error. Clear DOWNLOAD after a successful move so the cleanup trap
  does not try to rm the now-installed binary.

README changes:

- Add a two-step "inspect then run" install snippet alongside the
  curl | sh one-liner for security-conscious users.
- Replace the bare `SQLODE_INSTALL_DIR=/usr/local/bin` example with a
  `sudo SQLODE_INSTALL_DIR=/usr/local/bin sh` form, and call out that
  system paths need elevated privileges.
- Move environment variable docs into a bullet list so the sudo note
  is visible alongside the path example.

Deferred to follow-up:
- Checksum/signature verification needs release.yml to also publish
  sqlode.sha256 alongside the escript; too large for this PR.
- sed `tag_name` extraction is fragile if GitHub minifies the API
  response, but grep -P is not POSIX and the current API still
  pretty-prints, so the regex stays.
@nao1215 nao1215 merged commit b42f57f into main Apr 17, 2026
2 checks passed
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.

release: provide a zero-setup install path for public users

1 participant