#!/bin/sh # HyperDrop Server installer for a Linux box with systemd. Generated by # platforms/linux/build-installer.sh from install.sh.in, the server manifest # and the provisioner in core/provision/provision.sh, which is embedded below # verbatim: this file is everything that runs, and it is the same provisioner # Server Management uploads over SSH. # # curl -fsSL https://gethyperdrop.com/install.sh | sudo sh # # Inputs (environment; the terminal is asked for what is missing): # HYPERDROP_DDNS_NAME a free .hyperdrop.link address, OR # HYPERDROP_DOMAIN your own domain (never both) # HYPERDROP_ADMIN_USER the admin username (empty: asked, default admin) # HYPERDROP_ADMIN_PASSWORD the admin password (empty: one is generated) # HYPERDROP_IP this box's public IPv4 (empty: the registry is asked) # HYPERDROP_BEHIND_NAT=1 no public IPv4 here; a router forwards 443 to this box # HYPERDROP_RESET_ADMIN=1 reinstall over an existing server, resetting admin # HYPERDROP_ACME_EMAIL, HYPERDROP_ACME_CA handed to the server as they are # HYPERDROP_INSTALL_BASE where the binaries are (tests); HYPERDROP_ALLOW_PRIVATE=1 # waives the address (development only) # # Flags: --uninstall [--purge], --help set -eu HYPERDROP_VERSION="1.3.1" SHA_AMD64="859ed4c88f40b533a536f18f12e3e9d723d633a88a5e976d02c0827c2ffa0925" SHA_ARM64="6f62526f2a37bc4db56f8afde56cd013376a6e4daddf00ad70cec63bd3823cf1" BASE="${HYPERDROP_INSTALL_BASE:-https://gethyperdrop.com/downloads/server}" REGISTRY="${HYPERDROP_NAMES_URL:-https://api.gethyperdrop.com}" STAGE_DIR=/tmp/hyperdrop-install BIN_GZ="$STAGE_DIR/hyperdrop-server.gz" PROVISIONER="$STAGE_DIR/hyperdrop-provision.sh" DATA_DIR=/var/lib/hyperdrop say() { printf '%s\n' "$*"; } die() { printf 'hyperdrop: %s\n' "$*" >&2; exit 1; } usage() { cat <&2; die "unknown argument: $arg" ;; esac done if [ "$purge" = 1 ] && [ "$uninstall" = 0 ]; then die "--purge goes with --uninstall" fi [ "$(id -u)" = "0" ] || die "run it with sudo: curl -fsSL https://gethyperdrop.com/install.sh | sudo sh" # ---------------------------------------------------------------- helpers # have_tty: whether /dev/tty can be opened. The device node exists on every # box and its permissions pass; only a process with a controlling terminal # can open it, which is what a non-interactive ssh session lacks. have_tty() { ( : < /dev/tty ) 2>/dev/null } # ask VAR "prompt" "what to set": fills VAR from the terminal when it is # empty. Under curl | sh stdin is the script itself, so /dev/tty is the only # place a question can be answered; without one the environment is the only # way in, and the refusal names the variables to set. ask() { eval "cur=\${$1:-}" [ -n "$cur" ] && return 0 have_tty || die "$3 is not set and there is no terminal to ask on: set it in the environment" printf '%s ' "$2" > /dev/tty IFS= read -r ans < /dev/tty || ans="" eval "$1=\$ans" } fetch() { # $1 = url, $2 = output file if command -v curl >/dev/null 2>&1; then curl -fsSL --max-time 300 -o "$2" "$1" else wget -q -T 300 -O "$2" "$1" fi } fetch_text() { # $1 = url; the body on stdout, empty when it cannot be fetched if command -v curl >/dev/null 2>&1; then curl -fsS --max-time 20 "$1" 2>/dev/null || true else wget -q -T 20 -O - "$1" 2>/dev/null || true fi } sha256_of() { if command -v sha256sum >/dev/null 2>&1; then sha256sum "$1" | cut -d' ' -f1 else shasum -a 256 "$1" | cut -d' ' -f1 fi } # json_true JSON KEY: whether "KEY": true appears, whatever the whitespace. json_true() { printf '%s' "$1" | grep -Eq "\"$2\"[[:space:]]*:[[:space:]]*true" } # json_number JSON KEY: the first integer value under KEY, empty when absent. json_number() { printf '%s' "$1" | sed -n "s/.*\"$2\"[[:space:]]*:[[:space:]]*\([0-9][0-9]*\).*/\1/p" | head -1 } # json_string JSON KEY: the first string value under KEY, empty when absent. json_string() { printf '%s' "$1" | sed -n "s/.*\"$2\"[[:space:]]*:[[:space:]]*\"\([^\"]*\)\".*/\1/p" | head -1 } is_public_ipv4() { printf '%s' "$1" | grep -Eq '^([0-9]{1,3}\.){3}[0-9]{1,3}$' || return 1 case "$1" in 0.*|10.*|127.*|169.254.*|192.168.*) return 1 ;; 172.1[6-9].*|172.2[0-9].*|172.3[01].*) return 1 ;; 100.6[4-9].*|100.[7-9][0-9].*|100.1[01][0-9].*|100.12[0-7].*) return 1 ;; esac return 0 } # The staging directory follows the provisioning contract: never adopt a # path someone else owns, never follow a symlink, 0700 once it is ours. prepare_stage() { if [ -L "$STAGE_DIR" ]; then die "$STAGE_DIR is a symlink; remove it and try again" fi if [ -e "$STAGE_DIR" ] && { [ ! -d "$STAGE_DIR" ] || [ ! -O "$STAGE_DIR" ]; }; then die "$STAGE_DIR exists and is not a directory owned by root; remove it and try again" fi mkdir -p "$STAGE_DIR" chmod 700 "$STAGE_DIR" trap 'rm -rf "$STAGE_DIR"' EXIT } write_provisioner() { umask 077 cat > "$PROVISIONER" <<'HYPERDROP_PROVISION_SH' #!/bin/sh # HyperDrop server provisioner. Runs as root on the target VPS. Idempotent: # safe to re-run for redeploys and upgrades (data lives in /var/lib/hyperdrop # and survives). # # The payload is a PREBUILT static binary (CGO_ENABLED=0) uploaded by the # client: nothing is compiled here, no package manager is touched, and no # container runtime is installed. That is what makes this work the same on # Ubuntu, Debian, RHEL-family, an EOL distro whose mirrors are gone, and # inside an LXC container. The only host requirement is systemd. # # Inputs (env): # HYPERDROP_ADMIN_PASSWORD required: first-boot admin password (the server # creates the first admin account from it once; an # existing admin always wins) # HYPERDROP_ADMIN_USER optional: the first admin's username (3-32 of # a-z 0-9 . _ -); empty means "admin". With # HYPERDROP_RESET_ADMIN=1 it names the admin to # reset (or to create beside the existing ones); # empty then targets the sole admin. # HYPERDROP_RESET_DOMAIN with HYPERDROP_RESET_ADMIN=1, forget the domain and free # name the volume stored so HYPERDROP_DOMAIN applies again # HYPERDROP_BIN_SHA256 optional: sha256 of the uploaded .gz; verified # HYPERDROP_DOMAIN required: the custom domain; share pages serve on it # and uploaded files on files.. Both need # an A record. (HYPERDROP_ALLOW_PRIVATE=1 waives it # for a development server: plain HTTP, one origin.) # HYPERDROP_IP required with a domain: this server's public IPv4, # the reference point for "does your DNS point here" # HYPERDROP_DDNS_NAME optional: a free .hyperdrop.link address the # server claims at boot, the alternative to a # domain (set one or the other, never both) # HYPERDROP_BEHIND_NAT optional: 1 = this box has no public IPv4 of its # own; a router forwards 443 (and 80) to it and # ACME arbitrates reachability # HYPERDROP_ACME_EMAIL optional: ACME account email # HYPERDROP_ACME_CA optional: ACME directory override (staging/tests) # HYPERDROP_UNINSTALL optional: 1 = remove the service and binary # HYPERDROP_PURGE optional: 1 = with UNINSTALL, also delete the data # # Expects the gzipped server binary at /tmp/hyperdrop-install/hyperdrop-server.gz # (uploaded by the client, into a directory it created 0700 and owns). # # Progress protocol (parsed by the HyperDrop apps): # HYPERDROP-STEP::