#!/bin/sh # Install Echofiles on a Mac or on Linux (design/DISTRIBUTION.md §3). From the shared template # ontiqo-company/house/install.sh.tmpl; an app's build fills in the @…@ values and serves it at # https://echofiles.app/install.sh. # # curl -fsSL https://echofiles.app/install.sh | sh install (or update) # curl -fsSL https://echofiles.app/install.sh | sh -s -- --user Linux: the .tar.gz in ~/.local, no sudo # curl -fsSL https://echofiles.app/install.sh | sh -s -- --flatpak Linux: Flatpak # curl -fsSL https://echofiles.app/install.sh | sh -s -- --uninstall remove what this installed # # Linux picks the system's own packages (apt, dnf, zypper, and pacman on Arch for an app in the [ontiqo] pacman # repository) from packages.ontiqo.com, so updates come with the system's updates; then Flatpak; then the .tar.gz. # Every download is checked against the release's signed manifest.json (or, where that can't be checked, SHA256SUMS). set -eu NAME='Echofiles' # shown to people SLUG='echofiles' # file and package name: echofiles, omninput, guestbay, leafviewer DOMAIN='echofiles.app' MAC_APP='Echofiles.app' # e.g. Echofiles.app FLATPAK_ID='app.echofiles.Echofiles' # e.g. app.echofiles.EchofilesPlus LEGACY='echofilesplus' # older ~/.local uninstaller names, space-separated (may be empty) DL="https://download.$DOMAIN" PKGS='https://packages.ontiqo.com' ONTIQO_FPR='AD6792D29D6AC37BFB1F8B97726E1B9D3749D4AF' # the packages.ontiqo.com signing key, for pacman-key ONTIQO_GPG_SHA256='8117b36ae052ce2d580226c7e8eaa660de299ed6f9c57733cfd2eb183c46b91a' # sha256 of $PKGS/ontiqo.gpg: checked before it is trusted mode=auto for a in "$@"; do case "$a" in --user) mode=user ;; --flatpak) mode=flatpak ;; --uninstall) mode=uninstall ;; *) echo "Unknown option: $a" >&2; exit 2 ;; esac done say() { printf '%s\n' "$*"; } fail() { [ -f "${tmp:-/nonexistent}/failed" ] || printf '%s\n' "$*" >&2; [ -z "${tmp:-}" ] || : > "$tmp/failed"; exit 1; } # once, also from $(…) have() { command -v "$1" >/dev/null 2>&1; } sudo_() { if [ "$(id -u)" = 0 ]; then "$@"; else have sudo || fail "This needs administrator rights, and sudo isn't installed."; sudo "$@"; fi; } tmp=$(mktemp -d); trap 'rm -rf "$tmp"' EXIT # The release's list, manifest.json, signed with the Ontiqo update key (ontiqo-company design/MAILING_PLAN.md §7). The # app's own updater passes a list it has already checked (ONTIQO_MANIFEST); otherwise, where openssl can check Ed25519 # (OpenSSL 3; not macOS's LibreSSL), manifest.json.sig is checked here, and a list that fails stops the install. A # checked list's sha256s are what every download is held to; without one, the release's SHA256SUMS. UPDATE_KEY_PEM='-----BEGIN PUBLIC KEY----- MCowBQYDK2VwAyEAgrgbsOWRwvGxR77EMzaA8ojIvzepcfAqgCGwwYwu9PU= -----END PUBLIC KEY-----' # The apps whose download lists are always signed: for these, where openssl can check the signature, a list whose # signature doesn't download is refused too (otherwise deleting manifest.json.sig would switch the check off). # Add an app here once its release tools upload manifest.json.sig. case "$SLUG" in omninput|echofiles) SIGNED=1 ;; *) SIGNED=0 ;; esac unb64() { base64 -d 2>/dev/null || base64 -D; } manifest() { [ -f "$tmp/manifest.json" ] && return 0 if [ -n "${ONTIQO_MANIFEST:-}" ] && [ -f "$ONTIQO_MANIFEST" ]; then cp "$ONTIQO_MANIFEST" "$tmp/manifest.json" && : > "$tmp/manifest.checked" return fi curl -fsSL "$DL/manifest.json" -o "$tmp/manifest.json" || { rm -f "$tmp/manifest.json"; return 1; } if have openssl && openssl version 2>/dev/null | grep -q '^OpenSSL [3-9]'; then if ! curl -fsSL "$DL/manifest.json.sig" -o "$tmp/manifest.sig.b64" 2>/dev/null; then [ "$SIGNED" = 1 ] || return 0 rm -f "$tmp/manifest.json" fail "The download list on $DL isn't signed by Ontiqo, so nothing was installed." fi printf '%s\n' "$UPDATE_KEY_PEM" > "$tmp/update-key.pem" unb64 < "$tmp/manifest.sig.b64" > "$tmp/manifest.sig" if ! openssl pkeyutl -verify -pubin -inkey "$tmp/update-key.pem" -rawin -in "$tmp/manifest.json" \ -sigfile "$tmp/manifest.sig" >/dev/null 2>&1; then rm -f "$tmp/manifest.json" fail "The download list on $DL isn't signed by Ontiqo, so nothing was installed." fi : > "$tmp/manifest.checked" fi } version() { manifest || return 0; sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$tmp/manifest.json" | head -n 1; } # entry : "file url" of that file in the manifest (pretty-printed, one key per line), or nothing. # A Linux-only rebuild (1.2.3.N, DISTRIBUTION.md §8) keeps "version" at 1.2.3 and points its entries at /1.2.3.N/, # so the name and address always come from the entry, never from "version". entry() { manifest || return 0 awk -v o="$1" -v a="$2" -v k="$3" ' function val(l) { sub(/^[^:]*:[[:space:]]*"/, "", l); sub(/".*/, "", l); return l } /"os"[[:space:]]*:/ { os = val($0) } /"arch"[[:space:]]*:/ { ar = val($0) } /"kind"[[:space:]]*:/ { ki = val($0) } /"file"[[:space:]]*:/ { fi = val($0) } /"url"[[:space:]]*:/ { ur = val($0) } /}/ { if (os == o && (ar == a || ar == "universal") && ki == k && fi != "") { print fi " " ur; exit } os = ar = ki = fi = ur = "" } ' "$tmp/manifest.json" } # fetch [url]: download one file of the current release into $tmp and check its SHA-256. fetch() { manifest || fail "Couldn't read the current version from $DL. Check your connection and try again." [ -n "${VER:-}" ] || VER=$(version) [ -n "$VER" ] || fail "Couldn't read the current version from $DL. Check your connection and try again." url=${2:-"$DL/$VER/$1"} if [ -f "$tmp/manifest.checked" ]; then # Each entry's "file" comes before its "sha256" (the release tools write them so). [ -f "$tmp/SHA256SUMS" ] || awk ' /"file"[[:space:]]*:/ { f = $0; sub(/.*"file"[[:space:]]*:[[:space:]]*"/, "", f); sub(/".*/, "", f) } /"sha256"[[:space:]]*:/ { h = $0; sub(/.*"sha256"[[:space:]]*:[[:space:]]*"/, "", h); sub(/".*/, "", h); print h " " f } ' "$tmp/manifest.json" > "$tmp/SHA256SUMS" else [ -f "$tmp/SHA256SUMS" ] || curl -fsSL "${url%/*}/SHA256SUMS" -o "$tmp/SHA256SUMS" fi say "Downloading $1" curl -fSL --progress-bar "$url" -o "$tmp/$1" want=$(grep " $1\$" "$tmp/SHA256SUMS" | cut -d' ' -f1) [ -n "$want" ] || fail "$1 isn't in the release's checksums." if have sha256sum; then got=$(sha256sum "$tmp/$1" | cut -d' ' -f1); else got=$(shasum -a 256 "$tmp/$1" | cut -d' ' -f1); fi [ "$want" = "$got" ] || fail "$1 didn't download correctly (checksum mismatch). Try again." } # ---- Mac ------------------------------------------------------------------------------------------------------- mac() { if [ -w /Applications ]; then apps=/Applications; else apps="$HOME/Applications"; mkdir -p "$apps"; fi if [ "$mode" = uninstall ]; then gone= for d in /Applications "$HOME/Applications"; do if [ -d "$d/$MAC_APP" ]; then rm -rf "$d/$MAC_APP"; say "Removed $d/$MAC_APP"; gone=1; fi done [ -n "$gone" ] || say "$NAME isn't installed here." return 0 fi set -- $(entry mac universal dmg) [ -n "${1:-}" ] || set -- "$SLUG-$(version).dmg" fetch "$@"; dmg=$1 mnt="$tmp/mnt"; mkdir -p "$mnt" hdiutil attach -nobrowse -readonly -quiet -mountpoint "$mnt" "$tmp/$dmg" rm -rf "$apps/$MAC_APP"; cp -R "$mnt/$MAC_APP" "$apps/" hdiutil detach -quiet "$mnt" say "Installed $NAME $VER in $apps" open "$apps/$MAC_APP" || true } # ---- Linux ----------------------------------------------------------------------------------------------------- linux() { case "$(uname -m)" in x86_64|amd64) arch=x86_64 ;; aarch64|arm64) arch=aarch64 ;; *) fail "$NAME is for 64-bit Intel/AMD (x86_64) and Arm64 computers; this one is $(uname -m)." ;; esac if [ "$mode" = uninstall ]; then linux_uninstall; return; fi if [ "$mode" = auto ]; then if have apt-get; then apt_install; return; fi if have dnf; then rpm_install dnf; return; fi if have zypper; then rpm_install zypper; return; fi if have pacman && pacman_install; then return; fi if have flatpak; then mode=flatpak; fi fi if [ "$mode" = flatpak ]; then have flatpak || fail "Flatpak isn't installed. Install it, or run again with --user." # The .flatpakref names its runtime's home (Flathub), so this works where Flathub isn't set up yet. if flatpak info "$FLATPAK_ID" >/dev/null 2>&1; then flatpak update -y "$FLATPAK_ID" else flatpak install --user -y --from "$PKGS/flatpak/$FLATPAK_ID.flatpakref" fi say "Installed $NAME with Flatpak. Updates come with your other Flatpak apps." return fi tar_install } # The signing key must be exactly Ontiqo's before apt or pacman is told to trust it. key_ok() { if have sha256sum; then k=$(sha256sum "$tmp/ontiqo.gpg" | cut -d' ' -f1); else k=$(shasum -a 256 "$tmp/ontiqo.gpg" | cut -d' ' -f1); fi [ "$k" = "$ONTIQO_GPG_SHA256" ] || fail "The Ontiqo signing key didn't download correctly (checksum mismatch), so nothing was installed. Try again." } # The system's package manager couldn't install the package (usually a system older than the app needs). not_here() { fail "This system can't install the $NAME package (it may be older than $NAME needs), so nothing was installed and the Ontiqo package source was taken out again. Run this command again with --flatpak, or with --user to install the .tar.gz in ~/.local." } apt_install() { say "Adding the Ontiqo package source and installing $NAME (this asks for your password)." curl -fsSL "$PKGS/ontiqo.gpg" -o "$tmp/ontiqo.gpg" key_ok [ -e /usr/share/keyrings/ontiqo.gpg ] && had_key=1 || had_key= sudo_ install -D -m 0644 "$tmp/ontiqo.gpg" /usr/share/keyrings/ontiqo.gpg printf 'Types: deb\nURIs: %s/apt\nSuites: stable\nComponents: main\nSigned-By: /usr/share/keyrings/ontiqo.gpg\n' "$PKGS" > "$tmp/ontiqo.sources" sudo_ install -m 0644 "$tmp/ontiqo.sources" /etc/apt/sources.list.d/ontiqo.sources sudo_ apt-get update -qq if ! sudo_ apt-get install -y "$SLUG"; then sudo_ rm -f /etc/apt/sources.list.d/ontiqo.sources [ -n "$had_key" ] || sudo_ rm -f /usr/share/keyrings/ontiqo.gpg not_here fi say "Installed $NAME. Updates come with your system's updates." } rpm_install() { say "Adding the Ontiqo package source and installing $NAME (this asks for your password)." if [ "$1" = dnf ]; then curl -fsSL "$PKGS/rpm/ontiqo.repo" -o "$tmp/ontiqo.repo" sudo_ install -m 0644 "$tmp/ontiqo.repo" /etc/yum.repos.d/ontiqo.repo if rpm -q "$SLUG" >/dev/null 2>&1; then sudo_ dnf upgrade -y "$SLUG" # dnf install leaves an older version in place else sudo_ dnf install -y "$SLUG" || { sudo_ rm -f /etc/yum.repos.d/ontiqo.repo; not_here; } fi else # zypper's own file: signed metadata checked too (repo_gpgcheck=1), refreshed automatically. zypper repos 2>/dev/null | grep -qw ontiqo || sudo_ zypper --non-interactive addrepo --refresh "$PKGS/rpm/ontiqo-zypper.repo" >/dev/null sudo_ zypper --non-interactive --gpg-auto-import-keys install "$SLUG" || { sudo_ zypper --non-interactive removerepo ontiqo >/dev/null 2>&1; not_here; } fi say "Installed $NAME. Updates come with your system's updates." } # Arch Linux, Manjaro, EndeavourOS: the signed [ontiqo] pacman repository. Returns 1 (and installs nothing) for an # Arm computer (Arch has no Arm build) or an app that isn't in the repository yet, so the caller goes on to Flatpak or # the .tar.gz. Like any pacman install it brings the system up to date (-Syu), which Arch requires. pacman_install() { [ "$arch" = x86_64 ] || return 1 curl -fsSL "$PKGS/arch/x86_64/ontiqo.db" -o "$tmp/ontiqo.db" 2>/dev/null || return 1 tar -tzf "$tmp/ontiqo.db" 2>/dev/null | grep -q "^$SLUG-[0-9]" || return 1 say "Adding the Ontiqo package source and installing $NAME (this asks for your password, and updates the rest of the system as pacman does)." curl -fsSL "$PKGS/ontiqo.gpg" -o "$tmp/ontiqo.gpg" key_ok # Called from an `if`, where set -e doesn't stop on a failure: each step stops the script itself. # A real Arch install has pacman's keyring; a fresh container doesn't (Echofiles found this, 23 Oct). if ! sudo_ gpg --homedir /etc/pacman.d/gnupg --list-secret-keys 2>/dev/null | grep -q '^sec'; then sudo_ pacman-key --init >/dev/null && sudo_ pacman-key --populate >/dev/null || fail "Couldn't set up pacman's keyring." fi sudo_ pacman-key --add "$tmp/ontiqo.gpg" >/dev/null || fail "Couldn't add the Ontiqo signing key to pacman." sudo_ pacman-key --lsign-key "$ONTIQO_FPR" >/dev/null || fail "Couldn't trust the Ontiqo signing key in pacman." if ! grep -q '^\[ontiqo\]' /etc/pacman.conf; then # shellcheck disable=SC2016 # $arch is for pacman printf '\n[ontiqo]\nServer = %s/arch/$arch\n' "$PKGS" > "$tmp/ontiqo.conf" sudo_ sh -c "cat '$tmp/ontiqo.conf' >> /etc/pacman.conf" || fail "Couldn't add the Ontiqo repository to /etc/pacman.conf." fi sudo_ pacman -Syu --needed --noconfirm "$SLUG" || fail "pacman couldn't install $NAME." say "Installed $NAME. Updates come with your system's updates (pacman -Syu)." } tar_install() { set -- $(entry linux "$arch" tar.gz) [ -n "${1:-}" ] || set -- "$SLUG-$(version)-linux-$arch.tar.gz" fetch "$@"; tgz=$1 tar -xzf "$tmp/$tgz" -C "$tmp" dir=$(find "$tmp" -mindepth 1 -maxdepth 1 -type d ! -name mnt | head -n 1) [ -x "$dir/install.sh" ] || fail "The download is missing its installer." PREFIX="$HOME/.local" "$dir/install.sh" v=$(printf '%s\n' "$tgz" | sed -n "s/^$SLUG-\(.*\)-linux-.*/\1/p"); say "Installed $NAME ${v:-$VER} in ~/.local. Run this command again to update." } linux_uninstall() { done_= if have dpkg && dpkg -s "$SLUG" >/dev/null 2>&1; then sudo_ apt-get remove -y "$SLUG"; done_=1; fi if have rpm && rpm -q "$SLUG" >/dev/null 2>&1; then if have dnf; then sudo_ dnf remove -y "$SLUG"; else sudo_ zypper --non-interactive remove "$SLUG"; fi; done_=1 fi if have pacman && pacman -Q "$SLUG" >/dev/null 2>&1; then sudo_ pacman -R --noconfirm "$SLUG"; done_=1; fi if have flatpak && flatpak info "$FLATPAK_ID" >/dev/null 2>&1; then flatpak uninstall -y "$FLATPAK_ID"; done_=1; fi for u in "$SLUG" $LEGACY; do if [ -x "$HOME/.local/bin/$u-uninstall" ]; then "$HOME/.local/bin/$u-uninstall"; done_=1; break; fi done [ -n "$done_" ] && say "Removed $NAME. Your own files and settings are untouched." || say "$NAME isn't installed here." } case "$(uname -s)" in Darwin) mac ;; Linux) linux ;; *) fail "This installs $NAME on a Mac or Linux. On Windows: winget install Ontiqo.Echofiles, or get it from the Microsoft Store." ;; esac