#!/bin/sh # a0 installer — https://a0.0a0.dev # # curl -fsSL https://a0.0a0.dev/install.sh | sh install / update # curl -fsSL https://a0.0a0.dev/install.sh | sh -s -- --home also manage your home folder # curl -fsSL https://a0.0a0.dev/install.sh | sh -s -- --uninstall # # Works on any Linux with a fanotify-capable kernel (Arch, Debian, Ubuntu, Mint, Fedora, # openSUSE, Alpine, ...). Installs one static binary, a config and a service. Nothing is # compressed until a folder is configured (or you pass --home). # # The whole script is wrapped in main() so a half-downloaded file never runs. main() { set -eu A0_BASE="${A0_BASE:-https://a0.0a0.dev}" A0_VERSION="${A0_VERSION:-latest}" PREFIX="${PREFIX:-/usr/local}" ACTION=install MANAGE_HOME=0 FORCE=0 PURGE=0 while [ $# -gt 0 ]; do case "$1" in --home) MANAGE_HOME=1 ;; --uninstall) ACTION=uninstall ;; --purge) PURGE=1 ;; --force) FORCE=1 ;; --version) shift; A0_VERSION="${1:?--version needs a value}" ;; --version=*) A0_VERSION="${1#*=}" ;; -h|--help) sed -n '2,12p' "$0" 2>/dev/null || true; echo "options: --home --uninstall [--purge] --version X --force"; exit 0 ;; *) die "unknown option: $1" ;; esac shift done if [ -t 1 ]; then B="$(printf '\033[1m')"; D="$(printf '\033[2m')"; R="$(printf '\033[0m')"; else B=; D=; R=; fi [ "$(uname -s)" = Linux ] || die "a0 only runs on Linux." # -- privileges --------------------------------------------------------------- if [ "$(id -u)" -eq 0 ]; then SUDO= elif command -v sudo >/dev/null 2>&1; then SUDO=sudo elif command -v doas >/dev/null 2>&1; then SUDO=doas else die "run this as root (no sudo or doas found)." fi # The person who ran the installer (for --home), even through sudo. REAL_USER="${SUDO_USER:-${DOAS_USER:-$(id -un)}}" REAL_HOME="$(getent passwd "$REAL_USER" 2>/dev/null | cut -d: -f6)" [ -n "$REAL_HOME" ] || REAL_HOME="$HOME" if [ "$ACTION" = uninstall ]; then uninstall exit 0 fi # -- platform checks ------------------------------------------------------------ case "$(uname -m)" in x86_64|amd64) ARCH=x86_64 ;; aarch64|arm64) ARCH=aarch64 ;; *) die "no build for $(uname -m) yet (x86_64 and aarch64 only)." ;; esac kcfg() { if [ -r /proc/config.gz ]; then (zcat /proc/config.gz 2>/dev/null || gzip -dc /proc/config.gz) 2>/dev/null elif [ -r "/boot/config-$(uname -r)" ]; then cat "/boot/config-$(uname -r)" elif [ -r "/lib/modules/$(uname -r)/build/.config" ]; then cat "/lib/modules/$(uname -r)/build/.config" fi } KCFG="$(kcfg || true)" if [ -n "$KCFG" ]; then if ! printf '%s\n' "$KCFG" | grep -q '^CONFIG_FANOTIFY_ACCESS_PERMISSIONS=y'; then [ "$FORCE" = 1 ] || die "your kernel lacks CONFIG_FANOTIFY_ACCESS_PERMISSIONS, which a0 needs to unpack files on open. (--force installs anyway)" warn "kernel lacks fanotify permission events; the daemon will not start." fi else warn "couldn't read the kernel config; 'a0 doctor' will check after install." fi # -- download ------------------------------------------------------------------ if command -v curl >/dev/null 2>&1; then case "$A0_BASE" in https://*) fetch() { curl -fsSL --proto '=https' --tlsv1.2 -o "$2" "$1"; } ;; *) fetch() { curl -fsSL -o "$2" "$1"; } ;; # localhost test server only (checked below) esac elif command -v wget >/dev/null 2>&1; then if wget --help 2>&1 | grep -q -- --https-only; then fetch() { wget -q --https-only -O "$2" "$1"; } else fetch() { wget -q -O "$2" "$1"; } # busybox wget fi else die "need curl or wget." fi case "$A0_BASE" in https://*|http://localhost*|http://127.0.0.1*) ;; *) die "A0_BASE must be https." ;; esac TMP="$(mktemp -d)" trap 'rm -rf "$TMP"' EXIT INT TERM say "downloading a0 ($A0_VERSION, $ARCH)" fetch "$A0_BASE/dl/$A0_VERSION/a0-$ARCH" "$TMP/a0" || die "download failed: $A0_BASE/dl/$A0_VERSION/a0-$ARCH" fetch "$A0_BASE/dl/$A0_VERSION/SHA256SUMS" "$TMP/SHA256SUMS" || die "download failed: SHA256SUMS" want="$(grep " a0-$ARCH\$" "$TMP/SHA256SUMS" | cut -d' ' -f1)" if command -v sha256sum >/dev/null 2>&1; then have="$(sha256sum "$TMP/a0" | cut -d' ' -f1)" elif command -v shasum >/dev/null 2>&1; then have="$(shasum -a 256 "$TMP/a0" | cut -d' ' -f1)" elif command -v openssl >/dev/null 2>&1; then have="$(openssl dgst -sha256 -r "$TMP/a0" | cut -d' ' -f1)" else die "need sha256sum, shasum or openssl to verify the download."; fi [ -n "$want" ] && [ "$want" = "$have" ] || die "checksum mismatch — refusing to install." # -- install ------------------------------------------------------------------- UPGRADE=0 [ -x "$PREFIX/bin/a0" ] && UPGRADE=1 $SUDO mkdir -p "$PREFIX/bin" /etc/a0 $SUDO install -m 755 "$TMP/a0" "$PREFIX/bin/a0.new" "$PREFIX/bin/a0.new" --version >/dev/null 2>&1 || { $SUDO rm -f "$PREFIX/bin/a0.new"; die "the a0 binary doesn't run on this system."; } $SUDO mv -f "$PREFIX/bin/a0.new" "$PREFIX/bin/a0" ok "installed $PREFIX/bin/a0 ($("$PREFIX/bin/a0" --version))" if [ ! -e /etc/a0/config.toml ]; then write_config | $SUDO tee /etc/a0/config.toml >/dev/null if [ "$MANAGE_HOME" = 1 ]; then ok "config: managing $REAL_HOME (files untouched for 7+ days)" else ok "config: /etc/a0/config.toml (no folders yet — nothing will be compressed)"; fi elif [ "$MANAGE_HOME" = 1 ]; then warn "/etc/a0/config.toml already exists; left as is (add $REAL_HOME there yourself)." fi install_service # -- done ---------------------------------------------------------------------- echo if [ "$UPGRADE" = 1 ]; then echo "${B}a0 updated.${R}"; else echo "${B}a0 is installed.${R}"; fi echo echo " see what you'd get back: ${B}a0 estimate ~${R}" if [ "$MANAGE_HOME" = 1 ]; then echo " what it will compress: ${B}sudo a0 scan --dry-run${R}" echo " start now (else ≤6h): ${B}sudo a0 scan${R}" else echo " pick folders: ${B}sudoedit /etc/a0/config.toml${R} ${D}then: sudo systemctl restart a0${R}" fi echo " status: ${B}a0 status${R}" echo " undo everything: ${B}sudo a0 thaw-all${R}" echo } say() { printf '%s\n' "${D}::${R} $*"; } ok() { printf '%s\n' "${B}ok${R} $*"; } warn() { printf '%s\n' "${B}!!${R} $*" >&2; } die() { printf '%s\n' "a0 install: $*" >&2; exit 1; } write_config() { cat </dev/null 2>&1; } install_service() { if has_systemd; then $SUDO tee /etc/systemd/system/a0.service >/dev/null </dev/null </dev/null 2>&1 || warn "couldn't start a0.service; see: journalctl -u a0" fi $SUDO systemctl is-active --quiet a0.service && ok "service a0 running (starts at boot)" command -v notify-send >/dev/null 2>&1 && say "optional popups: systemctl --user enable --now a0-notify" elif [ -x /sbin/openrc-run ] || command -v rc-service >/dev/null 2>&1; then $SUDO tee /etc/init.d/a0 >/dev/null </dev/null 2>&1 || true $SUDO rc-service a0 restart >/dev/null 2>&1 || $SUDO rc-service a0 start >/dev/null 2>&1 || warn "couldn't start a0 (OpenRC)" ok "service a0 added to OpenRC (boot runlevel)" else warn "no systemd or OpenRC found: start '$PREFIX/bin/a0 daemon' as root at boot yourself." fi } uninstall() { A0="$(command -v a0 || echo "$PREFIX/bin/a0")" if [ -x "$A0" ]; then say "restoring every compressed file (a0 thaw-all)" $SUDO "$A0" thaw-all || die "thaw-all failed — NOT uninstalling. Fix the errors above and retry." left="$($SUDO "$A0" status --json 2>/dev/null | sed -n 's/.*"frozen_files":\([0-9]*\).*/\1/p')" [ "${left:-0}" = 0 ] || die "$left files are still compressed — NOT uninstalling." fi if has_systemd; then $SUDO systemctl disable --now a0.service >/dev/null 2>&1 || true $SUDO rm -f /etc/systemd/system/a0.service /etc/systemd/user/a0-notify.service $SUDO systemctl daemon-reload elif [ -e /etc/init.d/a0 ]; then $SUDO rc-service a0 stop >/dev/null 2>&1 || true $SUDO rc-update del a0 boot >/dev/null 2>&1 || true $SUDO rm -f /etc/init.d/a0 fi $SUDO rm -f "$PREFIX/bin/a0" if [ "$PURGE" = 1 ]; then $SUDO rm -rf /etc/a0 /var/lib/a0 ok "a0 removed, including config and store." else ok "a0 removed. Kept /etc/a0 and /var/lib/a0 (add --purge to delete them)." fi } main "$@"