forked from finn/tinyboard
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5941f95b00 | |||
| 747c8a81d8 | |||
| 5a9e55b673 | |||
| e5bdf95dcf | |||
| 1f4e8555da | |||
| 56325a1b06 | |||
| 0553420d04 | |||
| 4cdddd649d | |||
| 0fd7d94d58 | |||
| f3c9cf2344 | |||
| f486795154 | |||
| fe3f2c5b77 | |||
| 4e1e9282ac | |||
| 07f4601bad | |||
| 9bdd12ebbd |
@@ -35,6 +35,24 @@ cd tinyboard
|
||||
./setup.sh # option 1 (configure new spoke)
|
||||
```
|
||||
|
||||
### Adding the Spoke's Public Key to the Hub
|
||||
|
||||
During `setup-spoke.sh`, a key pair is generated on the spoke for the autossh tunnel. The script will display the public key and pause. Before pressing ENTER, the hub owner must add the public key to the hub user's `authorized_keys`. Run this on the hub as the hub user (e.g. `armbian`):
|
||||
|
||||
```bash
|
||||
echo "<paste public key here>" >> ~/.ssh/authorized_keys
|
||||
```
|
||||
|
||||
Or as root:
|
||||
|
||||
```bash
|
||||
echo "<paste public key here>" >> /home/armbian/.ssh/authorized_keys
|
||||
```
|
||||
|
||||
Once the key is added, press ENTER on the spoke to continue. The script will test the SSH connection and if successful, bring up the tunnel.
|
||||
|
||||
The private key never leaves the spoke — only the public key is shared.
|
||||
|
||||
### Onboarding a Spoke from the Hub
|
||||
|
||||
Once the spoke tunnel is up, run on the hub:
|
||||
|
||||
+44
-12
@@ -13,8 +13,15 @@ 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}"; }
|
||||
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=()
|
||||
@@ -74,6 +81,11 @@ KEY_PATH="$SSH_DIR/$KEY_NAME"
|
||||
mkdir -p "$(dirname "$RCLONE_CONF")"
|
||||
|
||||
header "Checking Tunnel"
|
||||
info "Verifying spoke SSH service is reachable on port $TUNNEL_PORT..."
|
||||
# Test TCP connectivity first
|
||||
if ! timeout 5 bash -c "cat < /dev/null > /dev/tcp/localhost/$TUNNEL_PORT" 2>/dev/null; then
|
||||
die "Cannot connect to port $TUNNEL_PORT on localhost — is the tunnel up?"
|
||||
fi
|
||||
info "Scanning spoke host key..."
|
||||
KEYSCAN=$(ssh-keyscan -p "$TUNNEL_PORT" -H localhost 2>/dev/null)
|
||||
[ -n "$KEYSCAN" ] || die "Spoke not reachable on port $TUNNEL_PORT — is the tunnel up?"
|
||||
@@ -84,23 +96,18 @@ while IFS= read -r KEYSCAN_LINE; do
|
||||
fi
|
||||
done <<<"$KEYSCAN"
|
||||
|
||||
info "Verifying spoke is reachable on port $TUNNEL_PORT..."
|
||||
retry_or_abort \
|
||||
"ssh -o BatchMode=yes -o ConnectTimeout=10 -p \"$TUNNEL_PORT\" \"$SPOKE_USER\"@localhost exit" \
|
||||
"Spoke not reachable on port $TUNNEL_PORT. Make sure the tunnel is up."
|
||||
|
||||
header "Generating Hub SSH Key"
|
||||
if [ -f "$KEY_PATH" ]; then
|
||||
warn "Key $KEY_PATH already exists, skipping generation."
|
||||
else
|
||||
ssh-keygen -t ed25519 -f "$KEY_PATH" -N ""
|
||||
ssh-keygen -t ed25519 -f "$KEY_PATH" -N "" -C "$KEY_NAME"
|
||||
info "Key generated: $KEY_PATH"
|
||||
fi
|
||||
chmod 600 "$KEY_PATH"
|
||||
info "Permissions set: $KEY_PATH is 600"
|
||||
|
||||
header "Copying Hub Key to Spoke"
|
||||
info "Running ssh-copy-id to $SPOKE_USER@localhost:$TUNNEL_PORT..."
|
||||
header "Installing Hub Access Key on Spoke"
|
||||
info "Copying hub public key to spoke's authorized_keys so the hub can SSH in for rclone..."
|
||||
info "(You will be prompted for the $SPOKE_USER password on the spoke)"
|
||||
if ssh-copy-id -i "$KEY_PATH.pub" -p "$TUNNEL_PORT" "$SPOKE_USER"@localhost; then
|
||||
info "Key copied."
|
||||
@@ -161,7 +168,10 @@ if [[ "${ADD_UNION,,}" == "y" ]]; then
|
||||
1) UPSTREAM_TAG=":ro" ;;
|
||||
2) UPSTREAM_TAG=":nc" ;;
|
||||
3) UPSTREAM_TAG=":writeback" ;;
|
||||
*) warn "Invalid choice, defaulting to full read/write."; UPSTREAM_TAG="" ;;
|
||||
*)
|
||||
warn "Invalid choice, defaulting to full read/write."
|
||||
UPSTREAM_TAG=""
|
||||
;;
|
||||
esac
|
||||
if [ -n "$UNION_PATH" ]; then
|
||||
UPSTREAM="${SPOKE_NAME}-remote:${UNION_PATH}${UPSTREAM_TAG}"
|
||||
@@ -169,7 +179,8 @@ if [[ "${ADD_UNION,,}" == "y" ]]; then
|
||||
UPSTREAM="${SPOKE_NAME}-remote:${UPSTREAM_TAG}"
|
||||
fi
|
||||
if grep -q "^\[${UNION_NAME}\]" "$RCLONE_CONF" 2>/dev/null; then
|
||||
ALREADY=$(python3 - "$RCLONE_CONF" "$UNION_NAME" "${SPOKE_NAME}-remote:" <<'PYEOF'
|
||||
ALREADY=$(
|
||||
python3 - "$RCLONE_CONF" "$UNION_NAME" "${SPOKE_NAME}-remote:" <<'PYEOF'
|
||||
import sys
|
||||
path, section, prefix = sys.argv[1], sys.argv[2], sys.argv[3]
|
||||
with open(path) as f:
|
||||
@@ -235,6 +246,27 @@ fi
|
||||
echo "${SPOKE_NAME} ${TUNNEL_PORT} ${KEY_PATH} ${MOUNT_POINT}" >>"$REGISTRY"
|
||||
info "$SPOKE_NAME registered."
|
||||
|
||||
header "Setting Up Auto-Mount"
|
||||
MOUNT_CMD="rclone mount ${SPOKE_NAME}-remote: ${MOUNT_POINT} --config ${HOME}/.config/rclone/rclone.conf --vfs-cache-mode writes --allow-other --daemon"
|
||||
CRON_ENTRY="@reboot ${MOUNT_CMD}"
|
||||
EXISTING=$(crontab -l 2>/dev/null || true)
|
||||
if echo "$EXISTING" | grep -qF "${SPOKE_NAME}-remote:"; then
|
||||
warn "Crontab entry for ${SPOKE_NAME}-remote already exists, skipping."
|
||||
else
|
||||
CRONTAB_BACKUP="${HOME}/.config/tinyboard/crontab.$(date +%Y%m%d%H%M%S)"
|
||||
mkdir -p "$(dirname "$CRONTAB_BACKUP")"
|
||||
echo "$EXISTING" >"$CRONTAB_BACKUP"
|
||||
info "Crontab backed up to $CRONTAB_BACKUP"
|
||||
{
|
||||
echo "$EXISTING"
|
||||
echo "$CRON_ENTRY"
|
||||
} | crontab -
|
||||
info "Auto-mount crontab entry added for ${SPOKE_NAME}."
|
||||
fi
|
||||
info "Starting mount now..."
|
||||
mkdir -p "$MOUNT_POINT"
|
||||
eval "$MOUNT_CMD" 2>/dev/null && info "Mounted ${SPOKE_NAME} at ${MOUNT_POINT}." || warn "Mount failed — will retry on next reboot."
|
||||
|
||||
header "Onboarding Complete"
|
||||
echo -e " Spoke: ${GREEN}$SPOKE_NAME${NC}"
|
||||
echo -e " Port: ${GREEN}$TUNNEL_PORT${NC}"
|
||||
|
||||
+41
-23
@@ -136,7 +136,7 @@ $PKG_INSTALL vim "$AUTOSSH_PKG" "$OPENSSH_PKG" git
|
||||
info "Installing Docker..."
|
||||
if ! command -v docker >/dev/null 2>&1; then
|
||||
if [ "$PKG_MANAGER" = "apt" ]; then
|
||||
$PKG_INSTALL docker.io docker-compose-plugin
|
||||
$PKG_INSTALL docker.io docker-cli docker-compose
|
||||
else
|
||||
curl -fsSL https://get.docker.com | bash
|
||||
fi
|
||||
@@ -146,7 +146,7 @@ fi
|
||||
|
||||
if ! docker compose version >/dev/null 2>&1; then
|
||||
if [ "$PKG_MANAGER" = "apt" ]; then
|
||||
$PKG_INSTALL docker-compose-plugin
|
||||
$PKG_INSTALL docker-compose
|
||||
else
|
||||
warn "docker compose not available — Docker install script should have included it."
|
||||
fi
|
||||
@@ -184,9 +184,10 @@ info "Hostname set to: $SPOKE_NAME"
|
||||
header "SSH Key Setup"
|
||||
echo "How would you like to handle the SSH key for the tunnel to $HUB_HOST?"
|
||||
echo " 1) Generate a new key automatically"
|
||||
echo " 2) Use an existing key (paste the private key)"
|
||||
echo " 2) Choose an existing key from $SSH_DIR"
|
||||
echo " 3) Paste a private key manually"
|
||||
echo ""
|
||||
read -rp "Choose [1/2]: " KEY_CHOICE
|
||||
read -rp "Choose [1/2/3]: " KEY_CHOICE
|
||||
|
||||
case "$KEY_CHOICE" in
|
||||
1)
|
||||
@@ -217,6 +218,34 @@ case "$KEY_CHOICE" in
|
||||
read -rp "Press ENTER once the key has been added to ${HUB_HOST}..."
|
||||
;;
|
||||
2)
|
||||
mkdir -p "$SSH_DIR"
|
||||
chown "$SPOKE_USER":"$SPOKE_USER" "$SSH_DIR"
|
||||
chmod 700 "$SSH_DIR"
|
||||
|
||||
AVAILABLE_KEYS=()
|
||||
while IFS= read -r keyfile; do
|
||||
AVAILABLE_KEYS+=("$keyfile")
|
||||
done < <(find "$SSH_DIR" -maxdepth 1 -type f ! -name "*.pub" ! -name "known_hosts" ! -name "authorized_keys" ! -name "config" | sort)
|
||||
|
||||
if [ ${#AVAILABLE_KEYS[@]} -eq 0 ]; then
|
||||
die "No private keys found in $SSH_DIR."
|
||||
fi
|
||||
|
||||
echo "Available keys:"
|
||||
for i in "${!AVAILABLE_KEYS[@]}"; do
|
||||
echo " $i) ${AVAILABLE_KEYS[$i]}"
|
||||
done
|
||||
echo ""
|
||||
read -rp "Choose key [0]: " KEY_IDX
|
||||
KEY_IDX="${KEY_IDX:-0}"
|
||||
[[ "$KEY_IDX" =~ ^[0-9]+$ ]] && [ "$KEY_IDX" -lt "${#AVAILABLE_KEYS[@]}" ] || die "Invalid choice."
|
||||
KEY_PATH="${AVAILABLE_KEYS[$KEY_IDX]}"
|
||||
KEY_NAME="$(basename "$KEY_PATH")"
|
||||
info "Using existing key: $KEY_PATH"
|
||||
echo ""
|
||||
read -rp "Press ENTER once the public key has been added to ${HUB_HOST} authorized_keys..."
|
||||
;;
|
||||
3)
|
||||
read -rp "Enter a name for the key file [hubkey]: " KEY_NAME
|
||||
KEY_NAME="${KEY_NAME:-hubkey}"
|
||||
KEY_PATH="$SSH_DIR/$KEY_NAME"
|
||||
@@ -237,8 +266,11 @@ case "$KEY_CHOICE" in
|
||||
esac
|
||||
|
||||
header "Password Authentication"
|
||||
read -rp "Disable password auth for $SPOKE_USER and use keys only? [Y/n]: " DISABLE_PASS
|
||||
DISABLE_PASS="${DISABLE_PASS:-y}"
|
||||
warn "Do not disable password auth yet — the hub still needs password access to install its key via ssh-copy-id."
|
||||
warn "Only disable this after running onboard-spoke.sh on the hub."
|
||||
echo ""
|
||||
read -rp "Disable password auth for $SPOKE_USER and use keys only? [y/N]: " DISABLE_PASS
|
||||
DISABLE_PASS="${DISABLE_PASS:-n}"
|
||||
if [[ "${DISABLE_PASS,,}" == "y" ]]; then
|
||||
if [ ! -f "$KEY_PATH" ]; then
|
||||
warn "No key found at $KEY_PATH — skipping password auth disable to avoid lockout."
|
||||
@@ -305,7 +337,7 @@ find_free_port() {
|
||||
echo "$port"
|
||||
return 0
|
||||
fi
|
||||
warn "Port $port is in use, trying next..."
|
||||
echo -e "${YELLOW}[!]${NC} Port $port is in use, trying next..." >&2
|
||||
done
|
||||
return 1
|
||||
}
|
||||
@@ -373,21 +405,7 @@ echo -e " Spoke name: ${GREEN}$SPOKE_NAME${NC}"
|
||||
echo -e " Tunnel port: ${GREEN}$TUNNEL_PORT${NC} on $HUB_HOST"
|
||||
echo -e " SSH key: ${GREEN}$KEY_PATH${NC}"
|
||||
echo ""
|
||||
echo -e "${YELLOW}The hub owner needs to do the following on ${HUB_HOST}:${NC}"
|
||||
echo -e "${YELLOW}Next step — on the hub, run as ${HUB_USER}:${NC}"
|
||||
echo ""
|
||||
echo " 1. Generate a hub->spoke key:"
|
||||
echo " ssh-keygen -t ed25519 -f ~/.ssh/${HUB_USER}-${SPOKE_NAME}-$(date +%Y%m)"
|
||||
echo ""
|
||||
echo " 2. Copy it to this spoke through the tunnel:"
|
||||
echo " ssh-copy-id -i ~/.ssh/${HUB_USER}-${SPOKE_NAME}-$(date +%Y%m).pub -p $TUNNEL_PORT ${HUB_USER}@localhost"
|
||||
echo ""
|
||||
echo " 3. Add an rclone remote in ~/.config/rclone/rclone.conf:"
|
||||
echo " [${SPOKE_NAME}-remote]"
|
||||
echo " type = sftp"
|
||||
echo " host = localhost"
|
||||
echo " port = $TUNNEL_PORT"
|
||||
echo " key_file = /home/$HUB_USER/.ssh/${HUB_USER}-${SPOKE_NAME}-$(date +%Y%m)"
|
||||
echo " shell_type = unix"
|
||||
echo " md5sum_command = md5sum"
|
||||
echo " sha1sum_command = sha1sum"
|
||||
echo " cd tinyboard && ./setup.sh # choose option 2 (onboard spoke)"
|
||||
echo ""
|
||||
|
||||
Reference in New Issue
Block a user