#!/bin/sh # Sworn for Linux — installer. # # curl -fsSL https://sworn.snowleopard.dev/install | sh # # What this does, in order (read it — it's short): # 1. Downloads Sworn's package-signing key and checks its SHA-256 against # the value pinned below. Nothing is installed unless it matches. # 2. Adds the Sworn package repository for your package manager # (apt on Debian/Ubuntu/Mint/Pop, dnf on Fedora/RHEL/Rocky/Alma). # 3. Installs the `sworn` package. Every package and every future update # is verified by apt/dnf against that same key. # 4. Starts the Sworn icon next to the clock. Pairing with your phone # happens there — the installer never touches your DNS; the agent does # that only after you pair. # # Uses sudo for steps 2–3 only. Remove later with # sudo apt remove sworn / sudo dnf remove sworn # — that notifies your accountability partner, like uninstalling the phone app. # # Manual install and checksums: https://sworn.snowleopard.dev/linux set -eu BASE="${SWORN_PKG_BASE:-https://pkg.sworn.snowleopard.dev}" # SHA-256 of the signing key as served (sworn.gpg = apt keyring, sworn.asc # = armored, for rpm). Fingerprint E162F8EE6C7C43C1E996779DD7707D8993CA1CB0. KEY_GPG_SHA256=a8da539670b466baefed730a504f946229812c974799811c6c8c16e976fa2296 KEY_ASC_SHA256=8d41ebbe03bbba05c419be0503b1040925765771d72a658ff8fdc1e11cc98f48 say() { printf '%s\n' "$*"; } die() { printf 'sworn install: %s\n' "$*" >&2; exit 1; } # --- prerequisites ----------------------------------------------------- [ "$(uname -s)" = Linux ] || die "this installer is for Linux — Sworn for Windows is at https://sworn.snowleopard.dev/windows, Sworn for Mac is with Apple for review" case "$(uname -m)" in x86_64|amd64|aarch64|arm64) ;; *) die "unsupported CPU $(uname -m) — packages are built for x86_64 and aarch64" ;; esac # https only — except a loopback BASE, which is how the test suite serves # a local repo. case "$BASE" in https://*) ;; http://127.0.0.1*|http://localhost*) ;; *) die "SWORN_PKG_BASE must be https" ;; esac if command -v curl >/dev/null 2>&1; then fetch() { curl -fsSL "$1" -o "$2"; } elif command -v wget >/dev/null 2>&1; then fetch() { wget -q "$1" -O "$2"; } else die "need curl or wget" fi if [ "$(id -u)" -eq 0 ]; then SUDO= else command -v sudo >/dev/null 2>&1 || die "need sudo (or run as root)" SUDO=sudo fi if command -v apt-get >/dev/null 2>&1; then FAMILY=deb elif command -v dnf >/dev/null 2>&1; then FAMILY=rpm; PM=dnf elif command -v yum >/dev/null 2>&1; then FAMILY=rpm; PM=yum else die "no apt-get or dnf found — download a package by hand from https://sworn.snowleopard.dev/linux" fi TMP=$(mktemp -d); trap 'rm -rf "$TMP"' EXIT # --- 1. the key ---------------------------------------------------------- check_sha() { # file expected got=$(sha256sum "$1" | cut -d' ' -f1) [ "$got" = "$2" ] || die "signing key checksum mismatch (got $got) — refusing to install. If this persists, the download was tampered with or the key was rotated; see https://sworn.snowleopard.dev/linux" } say "Sworn for Linux" say "• Fetching and verifying the signing key…" if [ "$FAMILY" = deb ]; then fetch "$BASE/sworn.gpg" "$TMP/sworn.gpg" check_sha "$TMP/sworn.gpg" "$KEY_GPG_SHA256" else fetch "$BASE/sworn.asc" "$TMP/sworn.asc" check_sha "$TMP/sworn.asc" "$KEY_ASC_SHA256" fi # --- 2 + 3. repo + package ----------------------------------------------- if [ "$FAMILY" = deb ]; then say "• Adding the Sworn apt repository (sudo may ask for your password)…" $SUDO install -m 0644 "$TMP/sworn.gpg" /usr/share/keyrings/sworn.gpg printf 'deb [signed-by=/usr/share/keyrings/sworn.gpg] %s/apt stable main\n' "$BASE" \ | $SUDO tee /etc/apt/sources.list.d/sworn.list >/dev/null say "• Installing sworn…" # Refresh ONLY the Sworn list: fast, and an unrelated broken repo on # this machine can't fail the install. $SUDO apt-get update -qq \ -o Dir::Etc::SourceList=/etc/apt/sources.list.d/sworn.list \ -o Dir::Etc::SourceParts=- -o APT::Get::List-Cleanup=0 DEBIAN_FRONTEND=noninteractive $SUDO apt-get install -y -qq sworn else say "• Adding the Sworn $PM repository (sudo may ask for your password)…" $SUDO rpm --import "$TMP/sworn.asc" { printf '[sworn]\nname=Sworn desktop agent\nbaseurl=%s/rpm/$basearch\n' "$BASE" printf 'enabled=1\ngpgcheck=1\nrepo_gpgcheck=1\ngpgkey=%s/sworn.asc\n' "$BASE" } | $SUDO tee /etc/yum.repos.d/sworn.repo >/dev/null say "• Installing sworn…" $SUDO $PM install -y -q sworn fi # --- 4. the menu ------------------------------------------------------- say "" say "Installed. Sworn is running but not yet paired — nothing about your DNS has changed." # TRAY_SHOWN=1 means a tray is running for this person and nothing says # its icon can't be drawn: the lines below then claim the icon, and the # "Next:" block promises the tab its first-run check opens. Over SSH, as # root, or on a desktop that exited the tray at once, nothing opens on # its own, and the icon is not claimed. # # A running tray is not proof of an icon: with nowhere to draw one # (vanilla GNOME without the AppIndicator extension) the tray keeps # running. So a tray that can answer is asked (`sworn-tray --has-host` # asks the session's StatusNotifierWatcher whether a panel has # registered to draw icons), and a "no" gets the no-icon lines. A tray # from before --has-host can't be asked — any second start reads as a # person opening Sworn again and opens a browser tab — so a running one # gets what every installer before the question said about a running # tray. TRAY_SHOWN=0 if command -v sworn-tray >/dev/null 2>&1 && [ -n "${DISPLAY:-}${WAYLAND_DISPLAY:-}" ] && [ "$(id -u)" -ne 0 ]; then if ! pgrep -x sworn-tray >/dev/null 2>&1; then (setsid sworn-tray >/dev/null 2>&1 &) sleep 2 fi if pgrep -x sworn-tray >/dev/null 2>&1; then # The flag's name is in every binary that answers it. if ! grep -q -e '--has-host' "$(command -v sworn-tray)" 2>/dev/null \ || sworn-tray --has-host >/dev/null 2>&1; then TRAY_SHOWN=1 fi fi if [ "$TRAY_SHOWN" -eq 1 ]; then say "The Sworn icon is next to the clock (it also starts at every login)." else say "The Sworn icon couldn't start on this desktop. Pairing works from the browser page below instead." fi else say "The Sworn icon starts at your next login (or run: sworn-tray)." fi case "${XDG_CURRENT_DESKTOP:-}" in *GNOME*) # --enabled: an installed-but-switched-off extension shows nothing. if ! gnome-extensions list --enabled 2>/dev/null | grep -qi appindicator; then say "" say "GNOME note: GNOME hides tray icons until the AppIndicator extension is on." say " Install it from https://extensions.gnome.org/extension/615/ (or your distro's" say " gnome-shell-extension-appindicator package) and log out and back in." say " Sworn deliberately doesn't install it for you — it isn't needed to pair:" say " the browser address below works regardless." fi ;; esac say "" say "Next: in Sworn on your phone open Settings → Add a computer." # Only the branch that just said the icon is there may offer the icon; # the others told the person it couldn't start or starts next login, and # sending them to look for it anyway would contradict that line. if [ "$TRAY_SHOWN" -eq 1 ]; then say "A browser tab asking for the code should open by itself. If it doesn't, either" say " click the Sworn icon → Pair with your phone… or open http://127.0.0.1:8453" else say "Then open http://127.0.0.1:8453 in a browser" fi say "and enter the code. Your accountability partner is emailed when you pause or remove Sworn."