#!/usr/bin/env bash # One-step cindertunnel installer for power users: # # curl -fsSL https://cinderapps.org/cindertunnel/install.sh | bash # # Fetches downloads/manifest.json (see deploy/release-cindertunnel.sh), # picks this machine's own OS/arch, downloads that platform's tarball, # verifies its SHA-256 against the manifest's own checksum (computed # server-side from the real uploaded file, never trusted from the # local build that produced it — see that script's own comment), and # installs the binary. No self-update beyond that — this script is # itself the update mechanism, meant to be re-run by hand whenever # tunnelweb's own update-check banner (or just habit) says a newer # version exists. See internal/tunnelupdate for that banner's own # read-only check — this script is the only thing that ever actually # fetches/replaces a binary, and only when a human runs it. # # Adapted directly from EphemNet's own site/agent/install.sh (A034's # own researched precedent) — same fetch/verify/install shape, but the # boot-start section is genuinely simpler here, not just renamed: # ephemnet-agent refuses to even start with nothing configured (no # -status-addr, no routes), so that installer has to create a real # bootstrap config.json before enabling a service or it would # crash-loop. cindertunnel has no such requirement — it starts fine # with zero flags and just serves its own local UI # (http://127.0.0.1:8089) until a human configures a tunnel there, # after which P014's own -state-file mechanism persists that config # for every future restart automatically. So there is no -config flag, # no ensure_default_config, and boot-start is always enabled+started # immediately once chosen — there's nothing left to ask. # # Interactive, but safely so when piped (every question reads from # /dev/tty specifically, never stdin — this script's own source # already consumes stdin when run via `curl ... | bash` — and every # question silently takes its default wherever no /dev/tty exists at # all, e.g. CI): # # - Where to install (prefilled with /usr/local/bin if writable, # else $HOME/.local/bin). Asked rather than silently decided: a # real deployment can already live somewhere else entirely, and an # unasked-for second copy next to it is just confusing. # - Whether to start it on boot (default: no). If yes, writes a real # systemd unit (Linux, always system-wide — `sudo systemctl` # throughout if not already root, never a per-user `systemctl # --user` unit, which depends on a lingering login session to # survive logout at all) or launchd agent (macOS) pointing at the # installed binary with -ui-addr $UI_ADDR (127.0.0.1:8089 for a # live install, 127.0.0.1:8090 for a dev4637 one — see this # script's own header comment on why this isn't overridable), # enabled and started immediately. # # This script's own relay/environment identity — which downloads # server, which cinder relay, which license environment, which local # UI port, which boot-start instance name — is NOT configurable via # environment variables at all, deliberately. It used to be (each of # BASE_URL/UI_ADDR/SERVER/LICENSE_ENV/INSTANCE independently overridable # via a CINDERTUNNEL_* env var), and that was the direct cause of a # real, repeated live confusion: a value set in one terminal session # (or, worse, persisted via `setx`/[Environment]::SetEnvironmentVariable) # during one install silently kept winning over this script's own # correct, derived defaults on a LATER, unrelated run — including a # live user running the genuinely correct script and still getting a # dev4637 install with no explanation. Direct instruction afterward: # "My environment variables shall not mutate your script installation # config." This script is now fully self-contained on that front — the # only thing that ever decides which relay you get is which URL you # fetched THIS FILE from (baked into its own BASE_URL literal below, # rewritten identically for each published copy by deploy.sh's own sed # step — see that script's own comment); nothing in your shell or your # machine's persisted environment can change it. # # Two settings remain real, intentional overrides, env vars — pinning # a version and skipping the install-directory prompt are a different # category of thing entirely (per-run install mechanics, not "which # environment is this"), and neither one has ever caused this kind of # confusion: # CINDERTUNNEL_VERSION default: whatever manifest.json's own # "latest" says — set to pin an exact # version (e.g. v0.1.0) instead. # CINDERTUNNEL_INSTALL_DIR skips the prompt entirely — e.g. for a # non-interactive/scripted install. Also # doubles as this instance's own working # directory (state/friends/identity files # included) — point two installs at two # different directories to run two fully # independent instances without them # silently sharing/clobbering each other's # state. Found live: two instances # defaulting to the same install dir ended # up with identical WebRTC identities and # corrupted friend lists. # # Needs curl, tar, sha256sum (or shasum on macOS, which has no # sha256sum by default), and either python3 or jq to read the JSON # manifest — every one of these ships on a stock Linux/macOS install # except jq, which is why python3 (present on both by default) is # tried first. set -euo pipefail # Not an env-var override — see this script's own header comment for # why that was removed outright. This literal is the ONLY thing that # differs between the copy published at cinderapps.org (this line, as # committed) and the one at dev4637.cinderapps.org (deploy.sh's own # publish step sed-rewrites this exact literal for that copy — see its # own comment); everything below derives from it. BASE_URL="https://cinderapps.org/cindertunnel/downloads" # Direct request: wisp tracking on cindertunnel downloads "and such" — # a page-driven download click gets counted via the landing page's own # JS, but a bare `curl ... | bash` run like this one never touches a # browser at all, so it needs its own ping. Backgrounded with a short # --max-time and `|| true` so a slow/unreachable tracking endpoint can # never delay or fail a real install — this is purely a side-channel # count, never load-bearing for anything below. TRACK_BASE="${BASE_URL%/cindertunnel/downloads}" ( curl -fsS --max-time 3 "${TRACK_BASE}/v1/track?key=/cindertunnel/install-sh" >/dev/null 2>&1 || true ) & PINNED_VERSION="${CINDERTUNNEL_VERSION:-}" # Every one of these is derived from BASE_URL alone, with no env-var # override of its own — this one script now covers what # cindertunnel-install-dev.sh used to be a separate wrapper for. Only # BASE_URL itself needs to actually differ between the copy published # at cinderapps.org and the one at dev4637.cinderapps.org (deploy.sh's # own sed rewrite) — port, relay, license environment, and install # directory/service naming (via INSTANCE) all follow from it # automatically. case "$BASE_URL" in *://dev4637.cinderapps.org/*) UI_ADDR="127.0.0.1:8090" SERVER="https://noteshare.dev4637.cinderapps.org" LICENSE_ENV="dev4637" INSTANCE="dev4637" ;; *) UI_ADDR="127.0.0.1:8089" SERVER="" LICENSE_ENV="" INSTANCE="" ;; esac # Direct request: "remember which folder we installed to, so just # auto pick that, next update" — a successful install writes its own # install_dir here (near the end of this script); the NEXT run reads # it back and, if found, uses it directly with no prompt at all, # rather than re-asking every single time an existing install is just # being updated. Keyed by INSTANCE (blank for the single canonical # install) so two differently-named instances on one machine each # remember their own directory independently, never each other's. # CINDERTUNNEL_INSTALL_DIR, when set, still wins outright over this, # exactly as it always has — this only changes what happens when # NEITHER that env var NOR a remembered marker exists yet (first-ever # run: unchanged, still prompts). MARKER_DIR="$HOME/.config/cindertunnel" MARKER_FILE="$MARKER_DIR/last-install-dir${INSTANCE:+-$INSTANCE}" # extra_args is appended, unexpanded-at-definition, to every generated # ExecStart/plist ProgramArguments/background start command below — # built once here so setup_systemd/setup_launchd/ # start_once_in_background can't disagree about what this instance's # own fixed flags are. extra_args=() [ -n "$SERVER" ] && extra_args+=(-server "$SERVER") [ -n "$LICENSE_ENV" ] && extra_args+=(-license-env "$LICENSE_ENV") json_get() { # json_get # e.g. json_get "$manifest" '["latest"]' if command -v python3 >/dev/null 2>&1; then python3 -c "import json,sys; data=json.load(sys.stdin); print(data$2)" <<<"$1" elif command -v jq >/dev/null 2>&1; then # Only the one specific lookup this script actually needs is # supported here — this is not a general json_get, just enough # to avoid a hard python3 dependency when jq is what's on hand. case "$2" in '["latest"]') jq -r '.latest' <<<"$1" ;; *) echo "jq fallback does not support lookup: $2" >&2; exit 1 ;; esac else echo "ERROR: need python3 or jq to read the release manifest — install one and retry." >&2 exit 1 fi } manifest_lookup() { # manifest_lookup python3 -c ' import json, sys data, version, platform, field = json.loads(sys.argv[1]), sys.argv[2], sys.argv[3], sys.argv[4] entry = next((v for v in data["versions"] if v["version"] == version), None) if entry is None: sys.exit("version " + version + " not found in manifest") if field == "path": print(entry["platforms"].get(platform, "")) else: print(entry.get("sha256", {}).get(platform, "")) ' "$1" "$2" "$3" "$4" } # prompt_tty/prompt_yn read from /dev/tty specifically, never stdin — # this script's own source already consumes stdin when run via # `curl ... | bash`, so prompting against it would just read past # whatever's left of the script and break. Wherever /dev/tty can't # actually be opened (CI, a non-interactive invocation, any process # with no controlling terminal at all — a permission-bit test like # `[ -w /dev/tty ]` says nothing about this; the device node can be rw # for everyone and opening it still fail with ENXIO), both silently # take their default, same as every env-var override already does. # `exec {fd}<>/dev/tty` is the actual open attempt, guarded so a # failure never trips `set -e`. # prompt_tty -> sets REPLY prompt_tty() { REPLY="$2" local fd=0 if { exec {fd}<>/dev/tty; } 2>/dev/null; then printf '%s [%s]: ' "$1" "$2" >&"$fd" local answer="" IFS= read -r answer <&"$fd" || answer="" exec {fd}>&- if [ -n "$answer" ]; then REPLY="$answer" fi fi } # prompt_yn -> sets REPLY_YN to 0 (yes) or 1 (no) prompt_yn() { local shown fd=0 if [ "$2" = "y" ]; then shown="Y/n" REPLY_YN=0 else shown="y/N" REPLY_YN=1 fi if { exec {fd}<>/dev/tty; } 2>/dev/null; then printf '%s [%s]: ' "$1" "$shown" >&"$fd" local answer="" IFS= read -r answer <&"$fd" || answer="" exec {fd}>&- case "$answer" in [Yy]*) REPLY_YN=0 ;; [Nn]*) REPLY_YN=1 ;; esac fi } os="$(uname -s)" case "$os" in Linux) os_name=linux ;; Darwin) os_name=darwin ;; *) echo "ERROR: unsupported OS '$os' — download manually from $BASE_URL/" >&2 exit 1 ;; esac arch="$(uname -m)" case "$arch" in x86_64 | amd64) arch_name=amd64 ;; aarch64 | arm64) arch_name=arm64 ;; i386 | i686) arch_name=386 ;; *) echo "ERROR: unsupported architecture '$arch' — download manually from $BASE_URL/" >&2 exit 1 ;; esac platform="${os_name}-${arch_name}" # Default install dir: $HOME/cindertunnel-$INSTANCE for a named # instance (direct instruction: this must also cover # cindertunnel-install-dev.sh, which sets CINDERTUNNEL_INSTANCE=dev4637 # — its own distinct default/marker, not the canonical install's, # otherwise it would default to the exact same directory as a live # install on the same machine, the exact collision A041 exists to # avoid); /usr/local/bin if writable, else ~/.local/bin for the # single canonical install (no instance name at all). Same fallback # CINDERTUNNEL_INSTALL_DIR itself overrides below. Computed before any # network call so the prompt (and CINDERTUNNEL_INSTALL_DIR, if set) # can short-circuit without wasting a fetch first. if [ -n "$INSTANCE" ]; then default_install_dir="$HOME/cindertunnel-$INSTANCE" elif [ -w /usr/local/bin ]; then default_install_dir="/usr/local/bin" else default_install_dir="$HOME/.local/bin" fi remembered_install_dir="$(cat "$MARKER_FILE" 2>/dev/null || true)" install_dir="${CINDERTUNNEL_INSTALL_DIR:-}" if [ -n "$install_dir" ]; then # Direct report, found live: CINDERTUNNEL_INSTALL_DIR set # PERSISTENTLY (setx, or a User/Machine env var set once during # earlier testing and never cleared) silently wins outright over # both the remember/confirm prompt below AND the first-run prompt # — by design, the override is meant to always win — but with # nothing printed, that's indistinguishable from this script # having silently decided to skip asking on its own, which is # exactly what it looked like. Announce it unconditionally so this # is never a silent surprise again — this is the one install_dir # source this script previously never mentioned at all. echo "==> CINDERTUNNEL_INSTALL_DIR is set — installing to $install_dir (unset it to be asked again)" fi if [ -z "$install_dir" ] && [ -n "$remembered_install_dir" ]; then # Direct follow-up to A081: ask, rather than silently reusing it — # a remembered directory is almost always right (an update), but a # yes/no confirmation costs nothing and catches the rare case where # it isn't (moved the install by hand, switching machines/users). # Defaults to yes, including with no /dev/tty at all (e.g. a piped # `curl | bash` re-run), so A081's own no-prompt-on-update behavior # is unchanged for the common, non-interactive case. prompt_yn "Use previous install folder ($remembered_install_dir)?" y if [ "$REPLY_YN" -eq 0 ]; then install_dir="$remembered_install_dir" echo "==> Using previously remembered install directory: $install_dir" fi fi if [ -z "$install_dir" ]; then # Ask, rather than silently picking the default — a real existing # install can already live somewhere else entirely, and a silent # ~/.local/bin install next to it is just a confusing, unused extra # copy nobody asked for. Reached on a genuinely first-ever run (or # if $MARKER_FILE was removed by hand), or right after answering # "no" above. prompt_tty "Install directory" "$default_install_dir" install_dir="$REPLY" fi # Only asked when CINDERTUNNEL_INSTANCE isn't already set and this # install_dir doesn't look like a brand-new, instance-specific # directory of its own — a single canonical install (the common case: # default_install_dir, or any directory with nothing of this script's # own already in it) has nothing to disambiguate, so there's no # reason to interrupt that flow with a question whose answer is # always "leave it blank" for them. Only a SECOND (or later) install # pointed at a directory that already holds a previous install's own # state file needs this asked — that's the one real case # offer_restart_if_already_set_up's own hardcoded unit name used to # get wrong (see this script's own CINDERTUNNEL_INSTANCE comment). if [ -z "$INSTANCE" ] && { [ -f "$install_dir/.cindertunnel-state.json" ] || [ -f "$install_dir/.cindertunnel-friends.json" ]; }; then echo " NOTE: ${install_dir} already looks like an existing cindertunnel install." prompt_tty "Name this instance (for its own boot-start service, e.g. 'dev4637') — leave blank to manage the existing one" "" INSTANCE="$REPLY" fi # unit_suffix/plist_label feed setup_systemd/setup_launchd/ # offer_restart_if_already_set_up below — a blank INSTANCE keeps # today's exact unit/plist names (full backward compatibility with # every install that predates CINDERTUNNEL_INSTANCE), a non-blank one # gives this install its own independently-managed service name # instead of colliding with/restarting whatever's already there. if [ -n "$INSTANCE" ]; then unit_suffix="-$INSTANCE" else unit_suffix="" fi echo "==> Fetching ${BASE_URL}/manifest.json ..." manifest="$(curl -fsSL "${BASE_URL}/manifest.json")" version="$PINNED_VERSION" if [ -z "$version" ]; then version="$(json_get "$manifest" '["latest"]')" fi echo "==> Installing cindertunnel ${version} for ${platform}..." rel_path="$(manifest_lookup "$manifest" "$version" "$platform" path)" if [ -z "$rel_path" ]; then echo "ERROR: no ${platform} build for version ${version} — see ${BASE_URL%/downloads}/ for what's actually available." >&2 exit 1 fi expected_sha="$(manifest_lookup "$manifest" "$version" "$platform" sha256)" tmp="$(mktemp -d)" trap 'rm -rf "$tmp"' EXIT archive="$tmp/$(basename "$rel_path")" echo "==> Downloading ${rel_path}..." curl -fsSL "${BASE_URL}/${rel_path}" -o "$archive" if [ -n "$expected_sha" ]; then echo "==> Verifying SHA-256..." if command -v sha256sum >/dev/null 2>&1; then actual_sha="$(sha256sum "$archive" | cut -d' ' -f1)" elif command -v shasum >/dev/null 2>&1; then actual_sha="$(shasum -a 256 "$archive" | cut -d' ' -f1)" else echo "ERROR: need sha256sum or shasum to verify the download — refusing to install unverified." >&2 exit 1 fi if [ "$actual_sha" != "$expected_sha" ]; then echo "ERROR: checksum mismatch — expected ${expected_sha}, got ${actual_sha}. Not installing." >&2 exit 1 fi echo " OK (${actual_sha})" else echo "WARNING: manifest has no checksum for this platform/version — installing unverified." >&2 fi tar -xzf "$archive" -C "$tmp" mkdir -p "$install_dir" install -m 0755 "$tmp/cindertunnel" "$install_dir/cindertunnel" mkdir -p "$MARKER_DIR" printf '%s\n' "$install_dir" >"$MARKER_FILE" echo "==> Installed ${version} to ${install_dir}/cindertunnel" case ":$PATH:" in *":$install_dir:"*) ;; *) echo " NOTE: ${install_dir} isn't on your PATH — add it, or run the binary by its full path." ;; esac # compute_systemd_unit_vars sets UNIT_DIR/UNIT_CMD/WANTED_BY/USER_LINE # — shared by setup_systemd and offer_restart_if_already_set_up below, # so the two can never disagree about where the unit actually lives. # Always a real, system-wide unit (/etc/systemd/system, # multi-user.target) — never a per-user `systemctl --user` one, which # only runs at all while that user has an active (or deliberately # lingering, via a separate `loginctl enable-linger` step) login # session. A boot-start service should survive logout and reboot # unconditionally, the same guarantee on every machine, not a # different one depending on who happened to run this installer. # `sudo` prefixes every systemctl call (and the unit file write # itself, in setup_systemd below) when not already root — this asks # for a password once, interactively, same as this script's own # prompt_tty/prompt_yn already do, rather than silently degrading to # a weaker per-user unit. compute_systemd_unit_vars() { UNIT_DIR="/etc/systemd/system" WANTED_BY="multi-user.target" if [ "$(id -u)" -eq 0 ]; then UNIT_CMD="systemctl" else UNIT_CMD="sudo systemctl" fi # The service's own User= — the real invoking human whenever # known (this script's own sudo, if that's how it got root; # otherwise whoever is actually running it), never root itself # unless genuinely invoked as root directly with no sudo in play. USER_LINE="User=${SUDO_USER:-$(id -un)}" } # setup_systemd/setup_launchd write a real, loadable unit that just # runs `cindertunnel -ui-addr 127.0.0.1:8089` — unlike ephemnet-agent, # cindertunnel needs no upfront config to start safely (see this # script's own header comment), so there's nothing to ask here and the # unit is always enabled+started immediately. The operator configures # an actual tunnel afterward via that local UI; P014's own -state-file # mechanism then persists it for every future restart on its own. # Only ever reached on a genuinely fresh setup — see # offer_restart_if_already_set_up below for what happens on a re-run # against an install that already has one. setup_systemd() { if ! command -v systemctl >/dev/null 2>&1; then echo "WARNING: systemctl not found — skipping boot-start setup; set one up by hand." >&2 return 1 fi compute_systemd_unit_vars local unit_dir="$UNIT_DIR" unit_cmd="$UNIT_CMD" wanted_by="$WANTED_BY" user_line="$USER_LINE" local unit_name="cindertunnel${unit_suffix}.service" local unit_path="$unit_dir/$unit_name" local unit_content unit_content="$( echo "[Unit]" echo "Description=cindertunnel agent" echo "After=network-online.target" echo "Wants=network-online.target" echo echo "[Service]" # WorkingDirectory pinned to install_dir, same reasoning # EphemNet's own installer found the hard way for its -config # flag: cindertunnel's default -state-file is a relative path # (".cindertunnel-state.json"), which resolves against whatever # directory the process is started from — systemd's own default # is "/", not wherever the binary lives, so without this a # restart would silently lose P014's own saved config. echo "WorkingDirectory=$install_dir" echo "ExecStart=$install_dir/cindertunnel -ui-addr $UI_ADDR${extra_args[*]:+ ${extra_args[*]}}" echo "Restart=on-failure" echo "RestartSec=5" echo "$user_line" echo echo "[Install]" echo "WantedBy=$wanted_by" )" # Writing straight into /etc/systemd/system needs root — `sudo # tee` when this script itself isn't already running as root, # a plain write otherwise (so a genuinely root-only system with # no sudo installed at all still works). if [ "$(id -u)" -eq 0 ]; then mkdir -p "$unit_dir" printf '%s\n' "$unit_content" >"$unit_path" else sudo mkdir -p "$unit_dir" printf '%s\n' "$unit_content" | sudo tee "$unit_path" >/dev/null fi echo "==> Wrote $unit_path" $unit_cmd daemon-reload $unit_cmd enable "$unit_name" $unit_cmd start "$unit_name" echo " Enabled and started — open http://$UI_ADDR to configure a tunnel." } setup_launchd() { local plist_dir="$HOME/Library/LaunchAgents" local plist_label="org.cinderapps.cindertunnel${unit_suffix}" local plist_path="$plist_dir/${plist_label}.plist" mkdir -p "$plist_dir" # Each extra_args element (e.g. -server, its URL) needs its own # element — ProgramArguments is a real argv array, not a # shell command line, so these can't just be space-joined the way # setup_systemd's own ExecStart line is. local extra_args_xml="" local a for a in "${extra_args[@]+"${extra_args[@]}"}"; do extra_args_xml="${extra_args_xml} ${a} " done cat >"$plist_path" < Label ${plist_label} ProgramArguments ${install_dir}/cindertunnel -ui-addr ${UI_ADDR} ${extra_args_xml} WorkingDirectory ${install_dir} RunAtLoad KeepAlive PLIST echo "==> Wrote $plist_path" launchctl load "$plist_path" echo " Loaded — open http://$UI_ADDR to configure a tunnel." } # start_once_in_background covers the real gap found live: declining # boot-start (or running on a platform/without systemctl where it's # unavailable) left nothing running at all, while this script's own # closing message still claimed "open http://127.0.0.1:8089 for the # UI" as if it already were — unlike ephemnet-agent's own installer, # which never makes that claim because that agent genuinely refuses # to start unconfigured (see this script's own header comment), # cindertunnel is specifically safe to run with zero flags, so there # was no good reason to leave the UI not actually reachable. Starts # it once, detached, not as a managed service — it won't survive # logout/reboot on its own; re-run this script and choose boot-start # for that. `cd` into install_dir first for the same reason # setup_systemd's own WorkingDirectory is pinned: -state-file's # default relative path must resolve next to the binary, not wherever # this installer happened to be run from. start_once_in_background() { echo "==> Starting cindertunnel now (not as a boot service)…" ( cd "$install_dir" && nohup ./cindertunnel -ui-addr "$UI_ADDR" "${extra_args[@]+"${extra_args[@]}"}" >/dev/null 2>&1 & ) sleep 1 echo " Running — open http://$UI_ADDR for the UI. It will stop when you log out or reboot; re-run this script and choose boot-start if you want it to survive that." } # offer_restart_if_already_set_up handles re-running this script # against a host that already has a boot-start service from a # previous run — without this, an already-running service just keeps # executing its old in-memory code after an "update" until someone # remembers to restart it by hand. Sets SKIP_BOOT_START_PROMPT=1 when a # service already exists (whether or not the operator chooses to # restart it now) — the fresh-setup prompt below only ever makes sense # when nothing is set up yet. offer_restart_if_already_set_up() { local unit_path unit_name restart_cmd case "$os_name" in linux) # Bare `cmd || return` would exit the function carrying that # failed test's own nonzero status — fatal under this script's # own `set -e` the instant the condition is false, which is the # COMMON case here (no existing unit yet, a fresh install). # `return 0` always, explicitly: "nothing to do" is not a # failure. if ! command -v systemctl >/dev/null 2>&1; then return 0 fi compute_systemd_unit_vars # unit_name must match setup_systemd's own naming exactly — a # different CINDERTUNNEL_INSTANCE means a different unit, never # this one, so a second instance's own first-ever boot-start # setup correctly falls through to "nothing to do" here instead # of finding (and offering to restart) some OTHER instance's # unit by accident. unit_name="cindertunnel${unit_suffix}.service" unit_path="$UNIT_DIR/$unit_name" if [ ! -f "$unit_path" ]; then return 0 fi restart_cmd="$UNIT_CMD restart $unit_name" ;; darwin) unit_name="org.cinderapps.cindertunnel${unit_suffix}" unit_path="$HOME/Library/LaunchAgents/${unit_name}.plist" if [ ! -f "$unit_path" ]; then return 0 fi restart_cmd="launchctl unload '$unit_path' && launchctl load '$unit_path'" ;; *) return 0 ;; esac SKIP_BOOT_START_PROMPT=1 prompt_yn "A cindertunnel service is already set up ($unit_path) — restart it now to use the just-installed $version" n if [ "$REPLY_YN" -ne 0 ]; then echo " Not restarted — it'll keep running the previous version until you do: $restart_cmd" return 0 fi if [ "$os_name" = "linux" ]; then $UNIT_CMD daemon-reload $UNIT_CMD restart "$unit_name" else launchctl unload "$unit_path" 2>/dev/null launchctl load "$unit_path" fi echo " Restarted." } echo SKIP_BOOT_START_PROMPT= offer_restart_if_already_set_up # Tracks whether something is actually going to be reachable at # http://127.0.0.1:8089 by the time this script finishes — see # start_once_in_background's own comment for the real gap this closes. # offer_restart_if_already_set_up already covers the case where a # boot-start service exists from a previous run (restarted or left # running on its old version either way); everything else needs an # explicit check, since choosing boot-start can itself still fail # (e.g. no systemctl on this machine) without actually starting # anything. RUNNING_BY_END= if [ -n "$SKIP_BOOT_START_PROMPT" ]; then RUNNING_BY_END=1 else prompt_yn "Start cindertunnel automatically on boot?" n if [ "$REPLY_YN" -eq 0 ]; then case "$os_name" in linux) if setup_systemd; then RUNNING_BY_END=1; fi ;; darwin) if setup_launchd; then RUNNING_BY_END=1; fi ;; esac fi fi if [ -z "$RUNNING_BY_END" ]; then start_once_in_background fi echo "Run 'cindertunnel -h' to get started, or open http://$UI_ADDR for the UI. Re-run this script any time to update."