Four ways in, all landing on the same static binary.
| Method | Command | Notes |
|---|---|---|
| Installer script | curl -fsSL https://p10node.com/k10s/install.sh | sh |
macOS + Linux, checksum-verified, no Go needed |
| Go | go install github.com/p10node/k10s@latest |
needs Go 1.26+ |
| From a clone | just install |
stamps the version from git describe |
| Release archive | download from releases | the only route on Windows |
After the first install, every later upgrade is k10s update — the binary
replaces itself, checksum-verified, and offers to restart. See
update.md.
install.sh lives at the repo root. It is POSIX sh and
needs only curl (or wget) and tar:
- Detects
darwin/linux×amd64/arm64fromuname. Windows and anything else exits with the manual instructions instead of guessing. - Resolves the newest release — the
/releases/latestredirect first (no API quota), then the API, thenreleases.atom, which is the only one of the three that can see a pre-release. A stable release always wins over a pre-release. - Reads the archive name out of the release's
checksums.txtrather than guessing it, so a release whose version string is spelled differently still resolves. - Verifies sha256 against
checksums.txtand refuses to install on a mismatch. With nosha256sum/shasum/opensslon the box it warns and continues. - Installs with
install -m 755, which replaces the file instead of writing through it — a runningk10skeeps its own inode.
Both spellings work; the flag wins.
| Flag | Environment | Default |
|---|---|---|
--version v0.1.0 |
K10S_VERSION |
the newest release |
--dir ~/bin |
K10S_INSTALL_DIR |
see below |
--no-sudo |
K10S_NO_SUDO=1 |
escalate only from a TTY |
Piped into sh, flags go after -s --:
curl -fsSL https://p10node.com/k10s/install.sh | sh -s -- --dir ~/binIn order: --dir if given → the first of /usr/local/bin,
/opt/homebrew/bin that is already writable → /usr/local/bin with sudo,
but only when stdin is a TTY → ${XDG_BIN_HOME:-~/.local/bin}.
That TTY condition is the whole reason the fallback exists: in
curl … | sh the script's stdin is the pipe, so a sudo password prompt
would have nothing to read and would hang. A directory the user owns is the
only honest default there. If it is not on PATH, the script prints the
export PATH=… line to add.
Piping a script into a shell means trusting the host that served it. The paranoid path is the same two steps on any project:
curl -fsSL https://p10node.com/k10s/install.sh -o k10s-install.sh
less k10s-install.sh
sh k10s-install.shOr skip the script and verify by hand — it does nothing you cannot type:
tag=v0.1.0
base=https://github.com/p10node/k10s/releases/download/$tag
curl -fsSLO $base/k10s_${tag}_darwin_arm64.tar.gz
curl -fsSL $base/checksums.txt | shasum -a 256 -c --ignore-missing
tar xzf k10s_${tag}_darwin_arm64.tar.gz && sudo install -m 755 k10s /usr/local/bin/Both scripts are served straight out of the repo; the domain is a redirect in
front of them, so publishing a fix is a normal push to main with nothing to
deploy.
p10node.com is on Cloudflare, so the cheapest wiring is two Redirect
Rules (Rules → Redirect Rules → Create), one per script:
If http.request.uri.path is in |
Then static redirect 301 to |
|---|---|
/k10s/install.sh /k10s |
https://raw.githubusercontent.com/p10node/k10s/main/install.sh |
/k10s/uninstall.sh |
https://raw.githubusercontent.com/p10node/k10s/main/uninstall.sh |
Preserve query string: off, in both.
curl -fsSL follows redirects (-L), so a 301 is invisible to the user.
raw.githubusercontent.com serves it as text/plain and sh does not care
about the content type.
A Cloudflare Worker on the same route is worth it only if you want to
proxy rather than redirect (so the URL bar and curl -I never mention
GitHub), pin the script to a tag instead of main, or count installs.
Checked 2026-08-27 — recheck before buying, availability moves.
| URL | Status | Verdict |
|---|---|---|
p10node.com/k10s/install.sh |
yours already | what the docs use. Zero cost, one redirect rule, reads as "a p10node tool" |
k10s.sh |
unregistered at the .sh registry |
the upgrade. curl -fsSL https://k10s.sh | sh — the TLD is the shell, and it is the shortest thing anyone will ever type. ~$35/yr |
get.p10node.com/k10s |
yours (subdomain) | Same cost as the path version; nicer if p10node ever ships a second tool |
k10s.dev |
taken (parked, Namecheap DNS) | — |
k10s.app |
taken (WordPress DNS) | — |
k10s.io |
registered, no nameservers | Parked; a transfer would have to be negotiated |
If you buy k10s.sh, keep the p10node.com path working — installer URLs
end up pasted into other people's runbooks and CI, and breaking one is a
support ticket you never see.
curl -fsSL https://p10node.com/k10s/uninstall.sh | shuninstall.sh is the mirror of the installer, and the
same two rules apply: it prints every path before touching it, and it never
prompts without a TTY.
- It collects every copy on
PATHplus/usr/local/bin,/opt/homebrew/bin,~/.local/binand$GOBIN— ago installbuild and aninstall.shbuild can both be on one machine, and only the first onPATHis visible tocommand -v. Paths are resolved, so the same file found twice is reported once. - The config survives by default.
--purgedeletes it, honouring$K10S_CONFIGwhen set (only that file then, not the directory around it). --dry-runprints the list and stops. Worth doing first.- Anything needing root that it cannot ask about is skipped with the exact
sudo rm …line to run, and the exit status is non-zero.
| Flag | Environment | Effect |
|---|---|---|
--purge |
K10S_PURGE=1 |
also delete ~/.k10s |
--keep-config |
— | keep it, and drop the reminder that it is still there |
--dir <path> |
K10S_INSTALL_DIR |
look only there |
--dry-run, -n |
— | list, remove nothing |
-y, --yes |
— | skip the confirmation prompt |
--no-sudo |
K10S_NO_SUDO=1 |
skip anything needing root |
By hand, it is two lines:
rm "$(command -v k10s)"
rm -rf ~/.k10s # config + theme choice, see config.md