#!/usr/bin/env bash set -euo pipefail RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' CYAN='\033[0;36m' NC='\033[0m' info() { echo -e "${GREEN}[+]${NC} $*"; } warn() { echo -e "${YELLOW}[!]${NC} $*"; } die() { echo -e "${RED}[ERROR]${NC} $*" >&2; exit 1; } header() { echo -e "\n${CYAN}══════════════════════════════════════════${NC}"; echo -e "${CYAN} $*${NC}"; echo -e "${CYAN}══════════════════════════════════════════${NC}"; } check_deps() { local missing=() for cmd in "$@"; do if ! command -v "$cmd" >/dev/null 2>&1; then missing+=("$cmd") fi done if [ ${#missing[@]} -gt 0 ]; then die "Missing required dependencies: ${missing[*]}" fi } check_permissions() { local file="$1" local label="$2" if [ ! -f "$file" ]; then warn "Permission check: $label not found at $file" return fi local perms perms=$(stat -c "%a" "$file" 2>/dev/null || stat -f "%OLp" "$file" 2>/dev/null) if [ -z "$perms" ]; then warn "Could not read permissions for $label ($file)" return fi local world="${perms: -1}" local group="${perms: -2:1}" if [ "$world" != "0" ] || [ "$group" != "0" ]; then warn "UNSAFE PERMISSIONS on $label ($file): $perms — should be 600 or 400" warn "Fixing permissions automatically..." chmod 600 "$file" info "Permissions fixed: $file is now 600" else info "Permissions OK: $label ($file) = $perms" fi } [ "$(id -u)" -eq 0 ] || die "Run as root" check_deps ssh ssh-keygen systemctl useradd groupadd header "TinyBoard Hub Setup" read -rp "Hub username [armbian]: " HUB_USER HUB_USER="${HUB_USER:-armbian}" header "Detecting Package Manager" if command -v apt-get >/dev/null 2>&1; then PKG_MANAGER="apt" PKG_INSTALL="apt-get install -y -q" OPENSSH_PKG="openssh-server" FUSE_PKG="fuse" info "Detected: apt (Debian/Ubuntu)" apt-get update -q elif command -v dnf >/dev/null 2>&1; then PKG_MANAGER="dnf" PKG_INSTALL="dnf install -y -q" OPENSSH_PKG="openssh-server" FUSE_PKG="fuse" info "Detected: dnf (Fedora/RHEL/Alma/Rocky)" dnf check-update -q || true elif command -v yum >/dev/null 2>&1; then PKG_MANAGER="yum" PKG_INSTALL="yum install -y -q" OPENSSH_PKG="openssh-server" FUSE_PKG="fuse" info "Detected: yum (older RHEL/CentOS)" yum check-update -q || true elif command -v pacman >/dev/null 2>&1; then PKG_MANAGER="pacman" PKG_INSTALL="pacman -S --noconfirm --quiet" OPENSSH_PKG="openssh" FUSE_PKG="fuse3" info "Detected: pacman (Arch)" pacman -Sy --quiet else die "No supported package manager found (apt, dnf, yum, pacman)" fi header "Installing Packages" info "Installing curl if missing..." if ! command -v curl >/dev/null 2>&1; then $PKG_INSTALL curl fi $PKG_INSTALL "$OPENSSH_PKG" "$FUSE_PKG" git if ! command -v rclone >/dev/null 2>&1; then info "Installing rclone..." if [ "$PKG_MANAGER" = "pacman" ]; then $PKG_INSTALL rclone else curl -fsSL https://rclone.org/install.sh | bash fi else warn "rclone already installed, skipping." fi header "Armbian User Setup" if id "$HUB_USER" >/dev/null 2>&1; then warn "User '$HUB_USER' already exists, skipping creation." else info "Creating $HUB_USER user..." groupadd -g 1000 "$HUB_USER" 2>/dev/null || true useradd -m -u 1000 -g 1000 -s /bin/bash "$HUB_USER" ADDED_TO_GROUP=false if getent group sudo >/dev/null 2>&1; then if usermod -aG sudo "$HUB_USER" 2>/dev/null; then ADDED_TO_GROUP=true fi fi if [ "$ADDED_TO_GROUP" = false ] && getent group wheel >/dev/null 2>&1; then if usermod -aG wheel "$HUB_USER" 2>/dev/null; then ADDED_TO_GROUP=true fi fi if [ "$ADDED_TO_GROUP" = false ]; then warn "Neither sudo nor wheel group found — $HUB_USER user has no sudo access." fi info "$HUB_USER user created." echo "" warn "Set a password for the $HUB_USER user:" passwd "$HUB_USER" fi ARMBIAN_HOME="/home/$HUB_USER" SSH_DIR="$ARMBIAN_HOME/.ssh" mkdir -p "$SSH_DIR" touch "$SSH_DIR/authorized_keys" chown -R "$HUB_USER":"$HUB_USER" "$SSH_DIR" chmod 700 "$SSH_DIR" chmod 600 "$SSH_DIR/authorized_keys" header "SSH Server Configuration" SSHD_CONF="/etc/ssh/sshd_config" [ -f "$SSHD_CONF" ] || die "sshd_config not found at $SSHD_CONF" for DIRECTIVE in "GatewayPorts yes" "AllowTcpForwarding yes"; do KEY="${DIRECTIVE%% *}" if grep -q "^$KEY" "$SSHD_CONF"; then sed -i "s/^$KEY.*/$DIRECTIVE/" "$SSHD_CONF" else echo "$DIRECTIVE" >> "$SSHD_CONF" fi info "$DIRECTIVE set." done if systemctl enable ssh 2>/dev/null; then systemctl restart ssh elif systemctl enable sshd 2>/dev/null; then systemctl restart sshd else warn "Could not enable/restart SSH service — please start it manually." fi info "SSH server restarted." header "Password Authentication" read -rp "Disable password auth for $HUB_USER and use keys only? [Y/n]: " DISABLE_PASS DISABLE_PASS="${DISABLE_PASS:-y}" if [[ "${DISABLE_PASS,,}" == "y" ]]; then if [ ! -s "$SSH_DIR/authorized_keys" ]; then warn "No keys found in $SSH_DIR/authorized_keys — skipping password auth disable to avoid lockout." else if grep -q "^PasswordAuthentication" "$SSHD_CONF"; then sed -i "s/^PasswordAuthentication.*/PasswordAuthentication no/" "$SSHD_CONF" else echo "PasswordAuthentication no" >> "$SSHD_CONF" fi if grep -q "^PubkeyAuthentication" "$SSHD_CONF"; then sed -i "s/^PubkeyAuthentication.*/PubkeyAuthentication yes/" "$SSHD_CONF" else echo "PubkeyAuthentication yes" >> "$SSHD_CONF" fi info "Password authentication disabled for $HUB_USER." echo "" warn "Restarting SSH will apply the new settings." warn "If you are connected via SSH, your session may drop." warn "Make sure you can reconnect using your key before continuing." read -rp "Press ENTER to restart SSH or CTRL+C to abort..." if systemctl restart ssh 2>/dev/null; then info "SSH restarted." elif systemctl restart sshd 2>/dev/null; then info "SSH restarted." else warn "Could not restart SSH — please restart it manually." fi fi else info "Password authentication left enabled." fi header "FUSE Configuration" FUSE_CONF="/etc/fuse.conf" if [ -f "$FUSE_CONF" ]; then if grep -q "^#user_allow_other" "$FUSE_CONF"; then sed -i 's/^#user_allow_other/user_allow_other/' "$FUSE_CONF" info "user_allow_other enabled in $FUSE_CONF." elif grep -q "^user_allow_other" "$FUSE_CONF"; then warn "user_allow_other already enabled." else echo "user_allow_other" >> "$FUSE_CONF" info "user_allow_other added to $FUSE_CONF." fi else echo "user_allow_other" > "$FUSE_CONF" info "$FUSE_CONF created with user_allow_other." fi groupadd fuse 2>/dev/null || true usermod -aG fuse "$HUB_USER" 2>/dev/null || true info "$HUB_USER added to fuse group." header "Rclone Setup" RCLONE_CONF="$ARMBIAN_HOME/.config/rclone/rclone.conf" mkdir -p "$(dirname "$RCLONE_CONF")" chown -R "$HUB_USER":"$HUB_USER" "$ARMBIAN_HOME/.config" if [ ! -f "$RCLONE_CONF" ]; then touch "$RCLONE_CONF" chown "$HUB_USER":"$HUB_USER" "$RCLONE_CONF" info "Empty rclone.conf created at $RCLONE_CONF." else warn "rclone.conf already exists, skipping." fi header "Permission Checks" info "Checking SSH directory permissions..." check_permissions "$SSH_DIR/authorized_keys" "authorized_keys" check_permissions "$RCLONE_CONF" "rclone.conf" header "Mount Point Setup" read -rp "Mount point for spoke filesystems [/mnt/hub]: " MOUNT_POINT MOUNT_POINT="${MOUNT_POINT:-/mnt/hub}" mkdir -p "$MOUNT_POINT" chown "$HUB_USER":"$HUB_USER" "$MOUNT_POINT" info "Mount point created at $MOUNT_POINT." header "Hub Setup Complete" echo -e " Hub user: ${GREEN}$HUB_USER${NC}" echo -e " SSH config: ${GREEN}GatewayPorts yes, AllowTcpForwarding yes${NC}" echo -e " FUSE: ${GREEN}user_allow_other enabled${NC}" echo -e " rclone config: ${GREEN}$RCLONE_CONF${NC}" echo -e " Mount point: ${GREEN}$MOUNT_POINT${NC}" echo "" echo -e "${YELLOW}Next steps:${NC}" echo " For each spoke that connects, run:" echo " ./setup.sh (choose option 2)" echo ""