1
0
forked from finn/tinyboard

Compare commits

...

44 Commits

Author SHA1 Message Date
Justin Oros
8ee67739f7 Update readme 2026-04-16 16:04:43 -07:00
Justin Oros
39f8f64351 clean up readme setup.sh option comments 2026-04-16 16:03:32 -07:00
Justin Oros
e924579b2e clean up readme setup.sh option comments 2026-04-16 16:02:37 -07:00
Justin Oros
912e553e06 add option 0 to reconfigure network via setup.sh 2026-04-16 15:59:35 -07:00
Justin Oros
98986e615b remove spoke/README.md 2026-04-16 15:03:47 -07:00
Justin Oros
0e792be751 add troubleshooting section for beta.armbian.com apt repo issue 2026-04-16 14:46:25 -07:00
Justin Oros
835793d396 add Armbian autoconfig docs link to README 2026-04-16 14:42:10 -07:00
Justin Oros
11f9586c5e fix directory tree in README for setup-network.sh move 2026-04-16 14:37:06 -07:00
Justin Oros
3e351f925d move setup-network.sh to spoke/ directory 2026-04-16 14:35:59 -07:00
Justin Oros
a197b7881b move setup-network.sh to spoke/ directory 2026-04-16 14:35:42 -07:00
Justin Oros
60feeca65e move setup-network.sh to spoke/ directory 2026-04-16 14:35:31 -07:00
Justin Oros
88fabcf25f update repo URL to justin/tinyboard 2026-04-16 14:23:51 -07:00
Justin Oros
51f661766f rename armb-not_logged_in_yet to armbian.not_logged_in_yet 2026-04-16 14:17:58 -07:00
Justin Oros
5326823b81 rewrite README with quickstart and updated architecture docs 2026-04-16 14:15:19 -07:00
Justin Oros
0f76283605 remove stale crontab dep from setup-hub.sh 2026-04-16 14:08:20 -07:00
Justin Oros
a02a83cae4 fix execute permissions on scripts 2026-04-16 13:53:48 -07:00
Justin Oros
4a1983d46d remove unused legacy scripts and rclone template 2026-04-16 13:48:28 -07:00
Justin Oros
395ab4ed0e add spoke registry, per-spoke crontab, and offboard-spoke.sh 2026-04-16 13:41:56 -07:00
Justin Oros
4c08f3b389 fix function ordering, hardcoded armbian user, and key name prefix in onboard-spoke.sh 2026-04-16 13:17:12 -07:00
Justin Oros
ccd324dc79 fix function ordering and RCLONE_CONF used before definition in setup-hub.sh 2026-04-16 13:15:40 -07:00
Justin Oros
664bdeaed4 fix function ordering, permission check chains, and known_hosts check timing in setup-spoke.sh 2026-04-16 13:14:27 -07:00
Justin Oros
ae49c58b13 add WiFi password extraction validation in setup-network.sh 2026-04-16 13:13:18 -07:00
Justin Oros
119b747dda fix BACKUP_FILE unbound variable and add ping to dep checks 2026-04-16 13:11:57 -07:00
Justin Oros
ea72b14696 fix function ordering, remove dead variable, fix netplan rollback approach 2026-04-16 13:10:59 -07:00
Justin Oros
26110ce8d3 add 30s connectivity check with auto-rollback to setup-network.sh 2026-04-16 13:09:40 -07:00
Justin Oros
58f6445c72 add check_deps function and dependency checks to all scripts 2026-04-16 13:05:45 -07:00
Justin Oros
08799f0f7f add SSH key permission checks with auto-fix to hub and spoke scripts 2026-04-16 12:58:06 -07:00
Justin Oros
a79b1c59b8 move password auth prompt to after SSH key setup in setup-spoke.sh 2026-04-16 10:44:12 -07:00
Justin Oros
7e64156026 fix double brace artifacts and missing SSHD_CONF in setup-spoke.sh 2026-04-16 10:42:58 -07:00
Justin Oros
3d366cd74a add disable password auth prompt with SSH restart warning to hub and spoke scripts 2026-04-16 10:42:04 -07:00
Justin Oros
d080db1db8 fix hardcoded armbian path in compose volume mount sed replacement 2026-04-16 10:37:55 -07:00
Justin Oros
37e3e91239 fix ARMBIAN_HOME unbound variable, retry_or_abort quoting, and hardcoded path in sed 2026-04-16 10:36:54 -07:00
Justin Oros
7676a907ee add SPOKE_USER prompt and replace all hardcoded armbian references in setup-spoke.sh 2026-04-16 10:35:50 -07:00
Justin Oros
e5ecdca3ff add multi-distro package manager support to setup-spoke.sh 2026-04-16 10:34:06 -07:00
Justin Oros
50fb313f9a fix hardcoded armbian string in user creation log message 2026-04-16 10:30:47 -07:00
Justin Oros
d21997af43 prompt for hub username with armbian as default, replace all hardcoded references 2026-04-16 10:29:53 -07:00
Justin Oros
95a56ef4f0 fix usermod group assignment to use if blocks instead of && chain 2026-04-16 10:26:28 -07:00
Justin Oros
b706dd211d fix pkg update handling, curl dependency, sudo group check, systemctl blocks 2026-04-16 10:25:26 -07:00
Justin Oros
f3a3f66982 rewrite setup-hub.sh with multi-distro package manager support 2026-04-16 10:22:52 -07:00
Justin Oros
384cf476ff replace hardcoded hub references with prompts, defaults: oily.dad / armbian / hubkey 2026-04-16 10:13:48 -07:00
Justin Oros
b8d2a3e5bc fix SPOKE_DIR path and replace hardcoded finn/oily.dad with dynamic HUB_HOST 2026-04-16 10:05:25 -07:00
Justin Oros
a49f830ed2 prompt user for hub hostname with oily.dad as default 2026-04-16 10:03:35 -07:00
Justin Oros
fe7f77171f fix wired DNS heredoc trailing newline 2026-04-16 09:59:23 -07:00
Justin Oros
288aa698d0 fix netplan file fallback assignment bug in setup-network.sh 2026-04-16 09:57:29 -07:00
13 changed files with 833 additions and 542 deletions

308
README.md
View File

@@ -1,208 +1,194 @@
# TinyBoard Hub-Spoke File Sharing System # TinyBoard
A hub-spoke architecture for secure file sharing over SSH tunnels using autossh and rclone. A hub-spoke architecture for secure file sharing over SSH tunnels using autossh and rclone.
## Architecture Overview Spokes are ARM devices (e.g. OrangePi, Raspberry Pi) running Armbian that establish reverse SSH tunnels to a central hub server. The hub mounts spoke filesystems via SFTP using rclone, making files accessible across all devices without exposing them to the internet.
This system implements a hub-and-spoke model where: ---
- **Spokes**: Raspberry Pi devices running Armbian that establish reverse SSH tunnels to the hub
- **Hub**: Central server that mounts spoke filesystems via SFTP using rclone
### Key Components ## Quickstart
1. **Spoke Side** (`spoke/` directory): ### Setting up a new Hub
- Docker-based autossh tunnel container
- Configuration files for spoke setup
- Hostname assignment based on MAC address
2. **Hub Side** (`hub/` directory): On a fresh Debian/Ubuntu VPS or server:
- Rclone SFTP mount configuration
- Systemd user service for automatic mounting
3. **Management Script** (`hubspoke-helper.sh`): ```bash
- Unified interface for managing both hub and spoke components apt install git
git clone https://gut.oily.dad/justin/tinyboard
cd tinyboard
./setup.sh # option 4 (setup new hub)
```
### Setting up a new Spoke
On a fresh Armbian device:
1. Modify `spoke/armbian.not_logged_in_yet` accordingly, then drop it onto the SD card as `/root/.not_logged_in_yet` before first boot (WiFi credentials) — see [Armbian Autoconfig docs](https://docs.armbian.com/User-Guide_Autoconfig/)
2. Boot, SSH in as root
3. Run:
```bash
apt install git
git clone https://gut.oily.dad/justin/tinyboard
cd tinyboard
./setup.sh # option 0 (configure network)
./setup.sh # option 1 (configure new spoke)
```
### Onboarding a Spoke from the Hub
Once the spoke tunnel is up, run on the hub:
```bash
cd tinyboard
./setup.sh # option 2 (onboard spoke)
```
### Offboarding a Spoke from the Hub
```bash
cd tinyboard
./setup.sh # option 3 (offboard spoke)
```
---
## Architecture
```
[ Spoke ] [ Hub ]
OrangePi / RPi VPS / Server
Armbian Any Linux
autossh container ──────────► sshd (GatewayPorts)
reverse tunnel port 111xx
rclone SFTP mount
~/mnt/<spoke-name>/
```
Spokes initiate outbound SSH connections to the hub, creating reverse tunnels. The hub then uses rclone to mount each spoke's filesystem over SFTP through the tunnel. No inbound ports need to be open on the spoke.
---
## Directory Structure ## Directory Structure
``` ```
tinyboard/ tinyboard/
├── hubspoke-helper.sh # Main management script ├── setup.sh ← entry point
├── hub/
│ └── rclone.conf # Rclone SFTP configuration
├── spoke/ ├── spoke/
│ ├── compose.yaml # Docker Compose for autossh tunnel │ ├── setup-network.sh ← configure static IP before setup
│ ├── Dockerfile # autossh container image │ ├── setup-spoke.sh ← automated spoke setup
│ ├── autohostname.sh # Hostname assignment by MAC address │ ├── compose.yaml ← Docker Compose for autossh + syncthing
│ ├── aptprimary.sh # Initial package installation │ ├── Dockerfile ← autossh container
── clean_sensitive.sh # Clean WiFi/password from configs ── armbian.not_logged_in_yet ← Armbian first-boot WiFi config template
│ └── armb-not_logged_in_yet # Armbian first-boot configuration └── hub/
└── README.md # This file ├── setup-hub.sh ← automated hub setup
├── onboard-spoke.sh ← add a new spoke to the hub
└── offboard-spoke.sh ← remove a spoke from the hub
``` ```
## Key File Handling (Manual Setup) ---
**IMPORTANT**: The following files must be manually created/configured as they contain sensitive information: ## Setup Scripts
### SSH Keys ### `setup.sh`
- `~/.ssh/oilykey2026` on spokes (referenced in `spoke/compose.yaml`) Entry point. Presents a menu:
- `~/.ssh/armbian-brie-202604` on hub (referenced in `hub/rclone.conf`) 0. Reconfigure network (static IP via netplan — SSH session will drop, reconnect)
- These keys must be manually generated and distributed 1. Set up this device as a new spoke
2. Onboard a new spoke from the hub
3. Offboard a spoke from the hub
4. Set up this device as a new hub
### Rclone Configuration ### `spoke/setup-network.sh`
- `~/.config/rclone/rclone.conf` on hub must be manually created Run as root on a new spoke before `setup.sh`. Configures a static IP via netplan. Supports both WiFi and wired interfaces. Automatically reverts if network connectivity is lost after applying the new config.
- Use `hub/rclone.conf` as a template
- Update host, port, and key_file paths as needed
- Manually create rclone mount and permission it (`/mnt/hub` for example)
### Systemd Service Files ### `spoke/setup-spoke.sh`
- `~/.config/systemd/user/rclone-mount@.service` must be manually copied from `hub/rclone-mount@.service` Run as root on a new spoke. Handles:
- Package installation (apt/dnf/yum/pacman)
- Docker installation
- SSH server setup
- Hostname configuration
- SSH key generation and hub authorization
- Tunnel port auto-detection on the hub
- Docker image build and container start
- Optional password auth disable
## Spoke Setup (Raspberry Pi / Armbian) ### `hub/setup-hub.sh`
Run as root on a new hub server. Handles:
- Package installation (apt/dnf/yum/pacman)
- rclone installation
- Hub user creation
- SSH server configuration (GatewayPorts, AllowTcpForwarding)
- FUSE configuration
- rclone config directory setup
- Optional password auth disable
### Initial Setup ### `hub/onboard-spoke.sh`
1. Write Armbian minimal image to SD card Run as the hub user after a spoke connects. Handles:
2. Copy `spoke/armb-not_logged_in_yet` to SD card root `/root/.not_logged_in_yet` (contains WiFi credentials) - SSH key generation and deployment to spoke
3. Boot device, SSH in as root with password "1234" - rclone remote configuration
4. After first login and setup tasks, `.not_logged_in_yet` will be processed for root and armbian user credentials - Spoke registration in `~/.config/tinyboard/spokes`
5. Clone this repository: `git clone <repo-url>` - Per-spoke crontab entry for auto-mount on reboot
6. Run `spoke/aptprimary.sh` to install required packages
7. Run `spoke/autohostname.sh` to assign hostname based on MAC address
8. Reboot and test as armbian user
### SSH Key Setup ### `hub/offboard-spoke.sh`
1. Generate SSH key pair on hub: `ssh-keygen -t ed25519 -f ~/.ssh/armbian-brie-202604` Run as the hub user to remove a spoke. Handles:
2. Copy public key to spoke: `ssh-copy-id -i ~/.ssh/armbian-brie-202604.pub armbian@<spoke-ip>` - Unmounting the spoke filesystem
3. Generate spoke key: `ssh-keygen -t ed25519 -f ~/.ssh/oilykey2026` - Removing the crontab entry
4. Copy public key to hub for reverse tunnel authentication - Removing the rclone remote
- Optionally removing the hub SSH key
- Removing from the spoke registry
### Docker Tunnel Setup ---
```bash
# Build the autossh container
./hubspoke-helper.sh spoke build
# Start the tunnel ## Spoke Registry
./hubspoke-helper.sh spoke start
# Check status The hub maintains a registry of connected spokes at `~/.config/tinyboard/spokes`:
./hubspoke-helper.sh spoke status
# View logs ```
./hubspoke-helper.sh spoke logs rocky 11113 /home/armbian/.ssh/armbian-rocky-202504 /home/armbian/mnt/rocky
gouda 11114 /home/armbian/.ssh/armbian-gouda-202504 /home/armbian/mnt/gouda
``` ```
## Hub Setup (Central Server) Each spoke gets its own mount point at `~/mnt/<spoke-name>/` and a dedicated rclone crontab entry.
### Rclone Configuration ---
1. Install rclone: `apt install rclone fuse`
2. Create config directory: `mkdir -p ~/.config/rclone`
3. Copy and customize `hub/rclone.conf` to `~/.config/rclone/rclone.conf`
4. Update key_file path to point to your SSH private key
### FUSE Configuration ## Security
```bash
# Allow other users to access mounts (if needed)
sudo sed -i 's/^#user_allow_other/user_allow_other/' /etc/fuse.conf
# Add user to fuse group - All communication is over SSH tunnels — no spoke ports exposed to the internet
sudo groupadd fuse - SSH keys are used for all authentication
sudo usermod -aG fuse $USER - Scripts check and auto-fix unsafe file permissions (600/400)
``` - Password authentication can be disabled during setup
- Scripts refuse to disable password auth if no authorized keys are present (lockout prevention)
- Netplan changes are verified with a 30-second connectivity check before being made permanent
## Usage ---
### Managing Spoke Tunnels ## Sensitive Files
- Docker on spoke should handle autostart of spoke tunnel
- Syncthing can be combined in this image
- Rename syncthing image and host names per-device in the compose file.
```bash Before committing, ensure the following do not contain real credentials:
# Build autossh container
./hubspoke-helper.sh spoke build
# Start/stop/restart tunnel - `spoke/armbian.not_logged_in_yet` — contains WiFi SSID, password, and user passwords
./hubspoke-helper.sh spoke start
./hubspoke-helper.sh spoke stop
./hubspoke-helper.sh spoke restart
# Check status and logs ---
./hubspoke-helper.sh spoke status
./hubspoke-helper.sh spoke logs
# Show manual autossh command
./hubspoke-helper.sh spoke show-cmd
```
### Managing Hub Mounts
#### Crontab entry:
```
@reboot /home/armbian/tinyboard/hubspoke-helper.sh hub start-background
```
#### Deprecated: systemd
```bash
# Install systemd service (after manual file placement)
./hubspoke-helper.sh hub install
# Start/stop rclone mount
./hubspoke-helper.sh hub start
./hubspoke-helper.sh hub stop
# Check service status
./hubspoke-helper.sh hub status
# Manual mount/unmount for testing
./hubspoke-helper.sh hub mount
./hubspoke-helper.sh hub unmount
```
## Configuration Variables
Environment variables can override defaults:
- `TUNNEL_DIR`: Directory containing spoke Docker files (default: `~/tinyboard/spoke`)
- `COMPOSE_FILE`: Docker compose file path (default: `$TUNNEL_DIR/compose.yaml`)
- `RCLONE_REMOTE`: Rclone remote name (default: `brie-remote`)
- `MOUNT_POINT`: Mount point on hub (default: `~/mnt/hub`)
## Security Notes
1. **SSH Keys**: Always use strong key pairs and protect private keys
2. **Configuration Files**: Use `spoke/clean_sensitive.sh` to remove WiFi credentials before committing
3. **Firewall**: Ensure proper firewall rules on hub (port 11111 for reverse tunnels)
4. **User Permissions**: Run services with minimal required privileges
## Troubleshooting ## Troubleshooting
### Spoke Tunnel Issues ### `apt update` fails with beta.armbian.com error
- Check Docker container logs: `./hubspoke-helper.sh spoke logs`
- Verify SSH key permissions: `chmod 600 ~/.ssh/oilykey2026`
- Test SSH connection manually: `ssh -p 11111 armbian@localhost`
### Hub Mount Issues On some Armbian images, a beta apt repository is enabled by default and may cause `apt update` to fail. Comment it out:
- Check service status: `./hubspoke-helper.sh hub status`
- Test rclone manually: `rclone lsd brie-sftp:`
- Verify fuse configuration: `ls -la /etc/fuse.conf`
- Check user groups: `groups $USER`
### Network Issues ```bash
- Ensure spokes can reach hub on SSH port (22) grep -r "beta.armbian" /etc/apt/sources.list /etc/apt/sources.list.d/
- Verify reverse tunnel port (11111) is not blocked by firewall ```
- Check DNS resolution on spokes for hub hostname
## Maintenance Open the file that contains it (usually `/etc/apt/sources.list.d/armbian.sources`) and comment out or remove the line referencing `beta.armbian.com`, then run `apt update` again.
### Updating Configuration ---
1. Update `spoke/compose.yaml` for new spoke hostnames
2. Update `hub/rclone.conf` for new spoke connections
3. Update `spoke/autohostname.sh` for new MAC addresses
### Adding New Spokes ## Requirements
1. Follow Spoke Setup steps for new device
2. Add MAC address to `spoke/autohostname.sh`
3. Update hub's SSH authorized_keys with new spoke public key
4. Add new rclone remote configuration if needed
## License **Spoke:** Armbian (Debian-based), ARM device, Docker, autossh, git
This project is for personal use. Adapt as needed for your environment.
**Hub:** Any Linux server (Debian/Ubuntu/RHEL/Arch), rclone, fuse, openssh-server

129
hub/offboard-spoke.sh Executable file
View File

@@ -0,0 +1,129 @@
#!/usr/bin/env bash
set -euo pipefail
RCLONE_CONF="${HOME}/.config/rclone/rclone.conf"
SSH_DIR="${HOME}/.ssh"
REGISTRY="${HOME}/.config/tinyboard/spokes"
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
}
if [ "$(id -u)" -eq 0 ]; then
die "Run as the hub user, not root."
fi
check_deps rclone crontab fusermount python3
header "TinyBoard Hub — Offboard Spoke"
[ -f "$REGISTRY" ] || die "No spoke registry found at $REGISTRY. No spokes to offboard."
echo "Registered spokes:"
echo ""
awk '{print " " $1 " (port " $2 ", mount " $4 ")"}' "$REGISTRY"
echo ""
read -rp "Spoke name to offboard: " SPOKE_NAME
[ -n "$SPOKE_NAME" ] || die "Spoke name cannot be empty"
SPOKE_LINE=$(grep "^$SPOKE_NAME " "$REGISTRY" 2>/dev/null || true)
[ -n "$SPOKE_LINE" ] || die "Spoke '$SPOKE_NAME' not found in registry."
TUNNEL_PORT=$(echo "$SPOKE_LINE" | awk '{print $2}')
KEY_PATH=$(echo "$SPOKE_LINE" | awk '{print $3}')
MOUNT_POINT=$(echo "$SPOKE_LINE" | awk '{print $4}')
echo ""
echo -e " Spoke: ${YELLOW}$SPOKE_NAME${NC}"
echo -e " Port: ${YELLOW}$TUNNEL_PORT${NC}"
echo -e " Key: ${YELLOW}$KEY_PATH${NC}"
echo -e " Mount: ${YELLOW}$MOUNT_POINT${NC}"
echo ""
read -rp "Are you sure you want to offboard $SPOKE_NAME? [y/N]: " CONFIRM
[[ "${CONFIRM,,}" == "y" ]] || die "Aborted."
header "Unmounting Spoke"
if mountpoint -q "$MOUNT_POINT" 2>/dev/null; then
if fusermount -u "$MOUNT_POINT" 2>/dev/null; then
info "Unmounted $MOUNT_POINT."
else
warn "Could not unmount $MOUNT_POINT — may already be unmounted."
fi
else
warn "$MOUNT_POINT is not currently mounted."
fi
header "Removing Crontab Entry"
EXISTING=$(crontab -l 2>/dev/null || true)
UPDATED=$(echo "$EXISTING" | grep -v "${SPOKE_NAME}-remote:" || true)
if [ "$EXISTING" = "$UPDATED" ]; then
warn "No crontab entry found for $SPOKE_NAME."
elif [ -z "$UPDATED" ]; then
crontab -r 2>/dev/null || true
info "Crontab entry for $SPOKE_NAME removed (crontab now empty)."
else
echo "$UPDATED" | crontab -
info "Crontab entry for $SPOKE_NAME removed."
fi
header "Removing rclone Remote"
if grep -q "\[${SPOKE_NAME}-remote\]" "$RCLONE_CONF" 2>/dev/null; then
python3 - "$RCLONE_CONF" "$SPOKE_NAME" <<'PYEOF'
import sys
path, name = sys.argv[1], sys.argv[2]
lines = open(path).readlines()
out, skip = [], False
for line in lines:
if line.strip() == f"[{name}-remote]":
skip = True
elif skip and line.strip().startswith("["):
skip = False
if not skip:
out.append(line)
open(path, 'w').writelines(out)
PYEOF
info "rclone remote [${SPOKE_NAME}-remote] removed from $RCLONE_CONF."
else
warn "rclone remote [${SPOKE_NAME}-remote] not found in $RCLONE_CONF."
fi
header "Removing SSH Key"
read -rp "Remove hub SSH key for $SPOKE_NAME ($KEY_PATH)? [y/N]: " REMOVE_KEY
if [[ "${REMOVE_KEY,,}" == "y" ]]; then
if [ -f "$KEY_PATH" ]; then
rm -f "$KEY_PATH" "$KEY_PATH.pub"
info "SSH key removed."
else
warn "Key not found at $KEY_PATH."
fi
else
info "SSH key left in place."
fi
header "Removing from Registry"
(grep -v "^$SPOKE_NAME " "$REGISTRY" || true) > "${REGISTRY}.tmp" && mv "${REGISTRY}.tmp" "$REGISTRY"
info "$SPOKE_NAME removed from registry."
header "Offboarding Complete"
echo -e " Spoke ${GREEN}$SPOKE_NAME${NC} has been offboarded."
echo ""

55
hub/onboard-spoke.sh Normal file → Executable file
View File

@@ -4,21 +4,29 @@ set -euo pipefail
RCLONE_CONF="${HOME}/.config/rclone/rclone.conf" RCLONE_CONF="${HOME}/.config/rclone/rclone.conf"
SSH_DIR="${HOME}/.ssh" SSH_DIR="${HOME}/.ssh"
if [ "$(id -u)" -eq 0 ]; then
echo -e "\033[0;31m[WARNING]\033[0m Running as root — keys will be written to /root/.ssh. Run as armbian instead."
exit 1
fi
mkdir -p "$SSH_DIR"
touch "$SSH_DIR/known_hosts"
chmod 700 "$SSH_DIR"
chmod 600 "$SSH_DIR/known_hosts"
RED='\033[0;31m' RED='\033[0;31m'
GREEN='\033[0;32m' GREEN='\033[0;32m'
YELLOW='\033[1;33m' YELLOW='\033[1;33m'
CYAN='\033[0;36m' CYAN='\033[0;36m'
NC='\033[0m' 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
}
retry_or_abort() { retry_or_abort() {
local test_cmd="$1" local test_cmd="$1"
local fail_msg="$2" local fail_msg="$2"
@@ -38,23 +46,30 @@ retry_or_abort() {
done done
} }
info() { echo -e "${GREEN}[+]${NC} $*"; } if [ "$(id -u)" -eq 0 ]; then
warn() { echo -e "${YELLOW}[!]${NC} $*"; } die "Running as root — keys will be written to /root/.ssh. Run as the hub user instead."
die() { echo -e "${RED}[ERROR]${NC} $*" >&2; exit 1; } fi
header() { echo -e "\n${CYAN}══════════════════════════════════════════${NC}"; echo -e "${CYAN} $*${NC}"; echo -e "${CYAN}══════════════════════════════════════════${NC}"; } mkdir -p "$SSH_DIR"
touch "$SSH_DIR/known_hosts"
chmod 700 "$SSH_DIR"
chmod 600 "$SSH_DIR/known_hosts"
check_deps ssh ssh-keygen ssh-keyscan ssh-copy-id rclone
header "TinyBoard Hub — Onboard New Spoke" header "TinyBoard Hub — Onboard New Spoke"
read -rp "Spoke local user [armbian]: " SPOKE_USER
SPOKE_USER="${SPOKE_USER:-armbian}"
read -rp "Spoke name (e.g. rocky): " SPOKE_NAME read -rp "Spoke name (e.g. rocky): " SPOKE_NAME
[ -n "$SPOKE_NAME" ] || die "Spoke name cannot be empty" [ -n "$SPOKE_NAME" ] || die "Spoke name cannot be empty"
read -rp "Tunnel port for $SPOKE_NAME: " TUNNEL_PORT read -rp "Tunnel port for $SPOKE_NAME: " TUNNEL_PORT
[[ "$TUNNEL_PORT" =~ ^[0-9]+$ ]] || die "Invalid port" [[ "$TUNNEL_PORT" =~ ^[0-9]+$ ]] || die "Invalid port"
KEY_NAME="armbian-${SPOKE_NAME}-$(date +%Y%m)" KEY_NAME="${SPOKE_USER}-${SPOKE_NAME}-$(date +%Y%m)"
KEY_PATH="$SSH_DIR/$KEY_NAME" KEY_PATH="$SSH_DIR/$KEY_NAME"
command -v rclone >/dev/null || die "rclone is not installed"
mkdir -p "$(dirname "$RCLONE_CONF")" mkdir -p "$(dirname "$RCLONE_CONF")"
header "Checking Tunnel" header "Checking Tunnel"
@@ -65,7 +80,7 @@ echo "$KEYSCAN" >> "$SSH_DIR/known_hosts"
info "Verifying spoke is reachable on port $TUNNEL_PORT..." info "Verifying spoke is reachable on port $TUNNEL_PORT..."
retry_or_abort \ retry_or_abort \
"ssh -o BatchMode=yes -o ConnectTimeout=10 -p \"$TUNNEL_PORT\" armbian@localhost exit" \ "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." "Spoke not reachable on port $TUNNEL_PORT. Make sure the tunnel is up."
header "Generating Hub SSH Key" header "Generating Hub SSH Key"
@@ -77,14 +92,14 @@ else
fi fi
header "Copying Hub Key to Spoke" header "Copying Hub Key to Spoke"
info "Running ssh-copy-id to armbian@localhost:$TUNNEL_PORT..." info "Running ssh-copy-id to $SPOKE_USER@localhost:$TUNNEL_PORT..."
info "(You will be prompted for the armbian password on the spoke)" info "(You will be prompted for the $SPOKE_USER password on the spoke)"
ssh-copy-id -i "$KEY_PATH.pub" -p "$TUNNEL_PORT" armbian@localhost ssh-copy-id -i "$KEY_PATH.pub" -p "$TUNNEL_PORT" "$SPOKE_USER"@localhost
info "Key copied." info "Key copied."
header "Testing Hub -> Spoke Key Auth" header "Testing Hub -> Spoke Key Auth"
retry_or_abort \ retry_or_abort \
"ssh -i \"$KEY_PATH\" -o BatchMode=yes -o ConnectTimeout=10 -p \"$TUNNEL_PORT\" armbian@localhost exit" \ "ssh -i \"$KEY_PATH\" -o BatchMode=yes -o ConnectTimeout=10 -p \"$TUNNEL_PORT\" \"$SPOKE_USER\"@localhost exit" \
"Key auth failed. Check authorized_keys on the spoke." "Key auth failed. Check authorized_keys on the spoke."
info "Key auth to spoke successful." info "Key auth to spoke successful."

View File

@@ -1,18 +0,0 @@
[brie-remote]
type = sftp
host = localhost
port = 11111
key_file = /home/armbian/.ssh/armbian-brie-202604
shell_type = unix
md5sum_command = md5sum
sha1sum_command = sha1sum
#[new-remote]
#type = sftp
#host = localhost
#port = 11112
#key_file = /home/armbian/.ssh/a new priv key for tunnel back to new spoke
#shell_type = unix
#md5sum_command = md5sum
#sha1sum_command = sha1sum

264
hub/setup-hub.sh Executable file
View File

@@ -0,0 +1,264 @@
#!/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 ""

View File

@@ -1,221 +0,0 @@
#!/usr/bin/env bash
#
# hubspoke-helper.sh - Manage hub/spoke rclone mounts
# Assumes spoke Docker files exist in ~/autossh-tunnel/
# Simplified hub mount uses direct rclone commands (no systemd services)
set -euo pipefail
# ------------------------------------------------------------
# Configuration (override with env vars if needed)
# ------------------------------------------------------------
TUNNEL_DIR="${TUNNEL_DIR:-$HOME/tinyboard/spoke}"
COMPOSE_FILE="${COMPOSE_FILE:-$TUNNEL_DIR/compose.yaml}"
RCLONE_REMOTE="${RCLONE_REMOTE:-brie-remote}"
MOUNT_POINT="${MOUNT_POINT:-/mnt/hub/$RCLONE_REMOTE}"
# ------------------------------------------------------------
# Usage
# ------------------------------------------------------------
usage() {
cat <<EOF
Usage: $0 {hub|spoke} {action}
SPOKE ACTIONS (docker-based, no systemd):
build Build the autossh image (run once)
start Start the tunnel container
stop Stop the tunnel container
restart Restart the tunnel container
status Show container status
logs Show container logs
show-cmd Show manual autossh command (non-docker)
HUB ACTIONS (simplified rclone mount - no systemd):
install Show simplified setup instructions
start Start rclone mount in background (uses nohup)
start-background Start rclone mount in background (for crontab)
stop Stop rclone mount
status Check mount status
mount Manual foreground mount (testing)
unmount Unmount manually
EXAMPLES:
$0 spoke build
$0 spoke start
$0 hub install
$0 hub start
EOF
}
die() {
echo "ERROR: $*" >&2
exit 1
}
# ------------------------------------------------------------
# Spoke actions (docker)
# ------------------------------------------------------------
spoke_build() {
if [ ! -f "$COMPOSE_FILE" ]; then
die "docker-compose.yaml not found at $COMPOSE_FILE"
fi
cd "$TUNNEL_DIR"
docker build --build-arg UID=$(id -u armbian) --build-arg GID=$(id -g armbian) -t spoke-autossh .
echo "Image built. Use '$0 spoke start' to run."
}
spoke_start() {
cd "$TUNNEL_DIR"
docker-compose up -d
}
spoke_stop() {
cd "$TUNNEL_DIR"
docker-compose down
}
spoke_restart() {
cd "$TUNNEL_DIR"
docker-compose restart
}
spoke_status() {
docker ps --filter name=spoke-autossh --format "table {{.Names}}\t{{.Status}}"
}
spoke_logs() {
cd "$TUNNEL_DIR"
docker-compose logs --tail=50 -f
}
spoke_show_cmd() {
cat <<EOF
Manual autossh command (run on spoke):
autossh -M 0 -NT -o "ServerAliveInterval=60" -o "ServerAliveCountMax=3" \\
-R 11111:localhost:22 -i ~/.ssh/oilykey2026 armbian@oily.dad
EOF
}
# ------------------------------------------------------------
# Hub actions (simplified - no systemd templates)
# ------------------------------------------------------------
hub_install() {
echo "Simplified hub setup:"
echo ""
echo "1. Ensure /etc/fuse.conf has 'user_allow_other' uncommented:"
echo " sudo sed -i 's/^#user_allow_other/user_allow_other/' /etc/fuse.conf"
echo ""
echo "2. Ensure you're in the 'fuse' group:"
echo " sudo usermod -aG fuse $USER"
echo " (You may need to log out and back in for this to take effect)"
echo ""
echo "3. Create mount point directory:"
echo " mkdir -p \"$MOUNT_POINT\""
echo ""
echo "4. Test manual mount:"
echo " $0 hub mount"
echo ""
echo "5. For auto-start, consider adding to crontab with @reboot:"
echo " crontab -e"
echo " Add: @reboot $0 hub start-background"
echo ""
echo "Note: This simplified version doesn't use systemd services."
}
hub_start() {
echo "Starting rclone mount in background..."
mkdir -p "$MOUNT_POINT"
nohup rclone mount "${RCLONE_REMOTE}:" "$MOUNT_POINT" \
--config "${HOME}/.config/rclone/rclone.conf" \
--vfs-cache-mode writes \
--allow-other \
--daemon >/dev/null 2>&1 &
echo "Mount started in background (PID: $!)"
echo "Check status with: $0 hub status"
}
hub_start_background() {
# Internal function for crontab/auto-start
mkdir -p "$MOUNT_POINT"
rclone mount "${RCLONE_REMOTE}:" "$MOUNT_POINT" \
--config "${HOME}/.config/rclone/rclone.conf" \
--vfs-cache-mode writes \
--allow-other \
--daemon
}
hub_stop() {
echo "Stopping rclone mount..."
if hub_unmount; then
echo "Mount stopped."
else
echo "Could not unmount. Trying force unmount..."
fusermount -uz "$MOUNT_POINT" 2>/dev/null && echo "Force unmounted." || echo "Still could not unmount."
fi
}
hub_status() {
if mountpoint -q "$MOUNT_POINT" 2>/dev/null; then
echo "Mount point $MOUNT_POINT is mounted."
mount | grep "$MOUNT_POINT"
else
echo "Mount point $MOUNT_POINT is NOT mounted."
echo "Check if rclone process is running:"
pgrep -af rclone || echo "No rclone mount processes found."
fi
}
hub_mount() {
mkdir -p "$MOUNT_POINT"
echo "Mounting in foreground. Press Ctrl+C to unmount."
rclone mount "${RCLONE_REMOTE}:" "$MOUNT_POINT" \
--config "${HOME}/.config/rclone/rclone.conf" \
--vfs-cache-mode writes \
--allow-other
}
hub_unmount() {
fusermount -u "$MOUNT_POINT" 2>/dev/null && echo "Unmounted." || echo "Not mounted."
}
# ------------------------------------------------------------
# Dispatch
# ------------------------------------------------------------
if [ $# -lt 2 ]; then
usage
exit 1
fi
ROLE="$1"
ACTION="$2"
case "$ROLE" in
spoke)
case "$ACTION" in
build) spoke_build ;;
start) spoke_start ;;
stop) spoke_stop ;;
restart) spoke_restart ;;
status) spoke_status ;;
logs) spoke_logs ;;
show-cmd) spoke_show_cmd ;;
*) die "Unknown action for spoke: $ACTION" ;;
esac
;;
hub)
case "$ACTION" in
install) hub_install ;;
start) hub_start ;;
start-background) hub_start_background ;;
stop) hub_stop ;;
status) hub_status ;;
mount) hub_mount ;;
unmount) hub_unmount ;;
*) die "Unknown action for hub: $ACTION" ;;
esac
;;
*)
usage
exit 1
;;
esac

19
setup.sh Normal file → Executable file
View File

@@ -15,12 +15,20 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
header "TinyBoard Setup" header "TinyBoard Setup"
echo "" echo ""
echo " 0) Reconfigure network"
echo " 1) Set up this device as a new spoke" echo " 1) Set up this device as a new spoke"
echo " 2) Onboard a new spoke from the hub" echo " 2) Onboard a new spoke from the hub"
echo " 3) Offboard a spoke from the hub"
echo " 4) Set up this device as a new hub"
echo "" echo ""
read -rp "Choose [1/2]: " CHOICE read -rp "Choose [0/1/2/3/4]: " CHOICE
case "$CHOICE" in case "$CHOICE" in
0)
[ "$(id -u)" -eq 0 ] || die "Network reconfiguration must be run as root"
info "Starting network reconfiguration..."
exec "$SCRIPT_DIR/spoke/setup-network.sh"
;;
1) 1)
[ "$(id -u)" -eq 0 ] || die "Spoke setup must be run as root" [ "$(id -u)" -eq 0 ] || die "Spoke setup must be run as root"
info "Starting spoke setup..." info "Starting spoke setup..."
@@ -30,6 +38,15 @@ case "$CHOICE" in
info "Starting hub onboarding..." info "Starting hub onboarding..."
exec "$SCRIPT_DIR/hub/onboard-spoke.sh" exec "$SCRIPT_DIR/hub/onboard-spoke.sh"
;; ;;
3)
info "Starting hub offboarding..."
exec "$SCRIPT_DIR/hub/offboard-spoke.sh"
;;
4)
[ "$(id -u)" -eq 0 ] || die "Hub setup must be run as root"
info "Starting hub setup..."
exec "$SCRIPT_DIR/hub/setup-hub.sh"
;;
*) *)
die "Invalid choice" die "Invalid choice"
;; ;;

View File

@@ -1,8 +0,0 @@
#!/bin/bash
# Need armbian-config?
apt install -y vim
apt install -y autossh
apt install -y docker.io docker-cli docker-compose
usermod -aG docker armbian

View File

@@ -1,39 +0,0 @@
#!/usr/bin/env bash
# Copy this along with .not_logged_in_yet to armbian root dir, then run after successful login
# Refresh: extract MAC address of wlan0
MAC=$(netplan status -f json | jq -r '.wlan0.macaddress')
# Check that we actually got a MAC address
if [[ -z "$MAC" ]]; then
echo "Error: Could not retrieve MAC address from netplan." >&2
exit 1
fi
echo "Detected MAC address: $MAC"
# Assign cheese hostname based on MAC address
case "$MAC" in
38:9c:80:46:26:c8) # ← Replace with your first real MAC
HOSTNAME="brie"
;;
68:f8:ea:22:e1:3d) # ← Replace with your second real MAC
HOSTNAME="gouda"
;;
99:88:77:66:55:44) # ← Replace with your third real MAC
HOSTNAME="camembert"
;;
*)
echo "Unknown MAC address: $MAC ... hostname not changed." >&2
exit 1
;;
esac
echo "Setting hostname to: $HOSTNAME"
sudo hostnamectl set-hostname "$HOSTNAME"
# Optional: also update /etc/hostname (hostnamectl usually does this, but to be safe)
echo "$HOSTNAME" | sudo tee /etc/hostname >/dev/null
echo "Hostname changed. Reboot or start a new shell to see the change."

View File

@@ -1,22 +0,0 @@
#!/bin/bash
# Script to clean sensitive WiFi credentials and passwords from configuration files
# Usage: ./clean_sensitive.sh [filename]
FILE="${1:-/home/finn/code/tinyboard/armb-not_logged_in_yet}"
echo "Cleaning sensitive data from: $FILE"
# Clean WiFi SSID (both commented and uncommented lines)
sed -i 's/^\(#*PRESET_NET_WIFI_SSID=\).*$/\1"[REDACTED]"/' "$FILE"
# Clean WiFi KEY (both commented and uncommented lines)
sed -i 's/^\(#*PRESET_NET_WIFI_KEY=\).*$/\1"[REDACTED]"/' "$FILE"
# Clean root password
sed -i 's/^\(PRESET_ROOT_PASSWORD=\).*$/\1"[REDACTED]"/' "$FILE"
# Clean user password
sed -i 's/^\(PRESET_USER_PASSWORD=\).*$/\1"[REDACTED]"/' "$FILE"
echo "wiped fields"

54
setup-network.sh → spoke/setup-network.sh Normal file → Executable file
View File

@@ -12,8 +12,22 @@ warn() { echo -e "${YELLOW}[!]${NC} $*"; }
die() { echo -e "${RED}[ERROR]${NC} $*" >&2; exit 1; } die() { echo -e "${RED}[ERROR]${NC} $*" >&2; exit 1; }
header() { echo -e "\n${CYAN}══════════════════════════════════════════${NC}"; echo -e "${CYAN} $*${NC}"; echo -e "${CYAN}══════════════════════════════════════════${NC}"; } 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
}
[ "$(id -u)" -eq 0 ] || die "Run as root" [ "$(id -u)" -eq 0 ] || die "Run as root"
check_deps ip netplan systemctl ping
header "TinyBoard Network Setup" header "TinyBoard Network Setup"
info "Available interfaces:" info "Available interfaces:"
@@ -73,7 +87,7 @@ echo ""
NETPLAN_FILE=$(ls /etc/netplan/*.yaml 2>/dev/null | head -1) NETPLAN_FILE=$(ls /etc/netplan/*.yaml 2>/dev/null | head -1)
read -rp "Netplan file to update [${NETPLAN_FILE}]: " INPUT_FILE read -rp "Netplan file to update [${NETPLAN_FILE}]: " INPUT_FILE
NETPLAN_FILE="${INPUT_FILE:-$INPUT_FILE}" NETPLAN_FILE="${INPUT_FILE:-$NETPLAN_FILE}"
NETPLAN_FILE="${NETPLAN_FILE:-$(ls /etc/netplan/*.yaml 2>/dev/null | head -1)}" NETPLAN_FILE="${NETPLAN_FILE:-$(ls /etc/netplan/*.yaml 2>/dev/null | head -1)}"
[ -n "$NETPLAN_FILE" ] || die "No netplan file specified" [ -n "$NETPLAN_FILE" ] || die "No netplan file specified"
@@ -100,13 +114,16 @@ if $IS_WIFI; then
else else
WIFI_SSID="$CURRENT_SSID" WIFI_SSID="$CURRENT_SSID"
WIFI_PASS=$(grep -A2 "\"${WIFI_SSID}\"" "$NETPLAN_FILE" 2>/dev/null | grep password | awk -F': ' '{print $2}' | tr -d '"' || true) WIFI_PASS=$(grep -A2 "\"${WIFI_SSID}\"" "$NETPLAN_FILE" 2>/dev/null | grep password | awk -F': ' '{print $2}' | tr -d '"' || true)
[ -n "$WIFI_PASS" ] || die "Could not extract WiFi password from existing config — please re-enter credentials."
fi fi
fi fi
header "Writing Netplan Config" header "Writing Netplan Config"
BACKUP_FILE=""
if [ -f "$NETPLAN_FILE" ]; then if [ -f "$NETPLAN_FILE" ]; then
cp "$NETPLAN_FILE" "${NETPLAN_FILE}.bak" BACKUP_FILE="/root/$(basename "${NETPLAN_FILE}").bak"
info "Backup saved to ${NETPLAN_FILE}.bak" cp "$NETPLAN_FILE" "$BACKUP_FILE"
info "Backup saved to $BACKUP_FILE"
fi fi
if $IS_WIFI; then if $IS_WIFI; then
@@ -141,18 +158,37 @@ network:
via: ${GATEWAY} via: ${GATEWAY}
nameservers: nameservers:
addresses: addresses:
$(printf '%b' "$DNS_YAML")NETEOF $(printf '%b' "$DNS_YAML")
NETEOF
fi fi
info "Netplan config written to $NETPLAN_FILE" info "Netplan config written to $NETPLAN_FILE"
header "Applying Configuration" header "Applying Configuration"
warn "Testing netplan config..." warn "Applying netplan config — will revert automatically if network is lost..."
if netplan try --timeout 10 2>/dev/null; then netplan apply
info "Netplan config applied successfully."
CONNECTED=false
for i in $(seq 1 6); do
sleep 5
if ping -c 1 -W 2 "$GATEWAY" >/dev/null 2>&1; then
CONNECTED=true
break
fi
warn "Network check $i/6 failed, retrying..."
done
if $CONNECTED; then
info "Network connectivity confirmed — config applied permanently."
else else
warn "netplan try timed out or failed — applying anyway..." warn "No network connectivity detected after 30 seconds — reverting to backup config."
if [ -f "$BACKUP_FILE" ]; then
cp "$BACKUP_FILE" "$NETPLAN_FILE"
netplan apply netplan apply
die "Config reverted to backup. Check your settings and try again."
else
die "No backup found to revert to. Restore $NETPLAN_FILE manually."
fi
fi fi
STATIC_ADDR="${STATIC_IP%%/*}" STATIC_ADDR="${STATIC_IP%%/*}"
@@ -161,6 +197,6 @@ echo -e "${YELLOW}════════════════════
echo -e "${YELLOW} Network reconfigured.${NC}" echo -e "${YELLOW} Network reconfigured.${NC}"
echo -e "${YELLOW} If you are connected via SSH, your session${NC}" echo -e "${YELLOW} If you are connected via SSH, your session${NC}"
echo -e "${YELLOW} may drop. Reconnect to: ${STATIC_ADDR}${NC}" echo -e "${YELLOW} may drop. Reconnect to: ${STATIC_ADDR}${NC}"
echo -e "${YELLOW} Then run: sudo ./setup-spoke.sh${NC}" echo -e "${YELLOW} Then run: cd .. && ./setup.sh${NC}"
echo -e "${YELLOW}══════════════════════════════════════════${NC}" echo -e "${YELLOW}══════════════════════════════════════════${NC}"
echo "" echo ""

236
spoke/setup-spoke.sh Normal file → Executable file
View File

@@ -1,12 +1,11 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -euo pipefail set -euo pipefail
HUB_HOST="oily.dad" HUB_HOST=""
HUB_USER="armbian" HUB_USER=""
ARMBIAN_HOME="/home/armbian" SPOKE_USER=""
SSH_DIR="$ARMBIAN_HOME/.ssh"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SPOKE_DIR="$SCRIPT_DIR/spoke" SPOKE_DIR="$SCRIPT_DIR"
COMPOSE="$SPOKE_DIR/compose.yaml" COMPOSE="$SPOKE_DIR/compose.yaml"
START_PORT=11111 START_PORT=11111
@@ -16,6 +15,23 @@ YELLOW='\033[1;33m'
CYAN='\033[0;36m' CYAN='\033[0;36m'
NC='\033[0m' 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
}
retry_or_abort() { retry_or_abort() {
local test_cmd="$1" local test_cmd="$1"
local fail_msg="$2" local fail_msg="$2"
@@ -35,25 +51,118 @@ retry_or_abort() {
done done
} }
info() { echo -e "${GREEN}[+]${NC} $*"; } check_permissions() {
warn() { echo -e "${YELLOW}[!]${NC} $*"; } local file="$1"
die() { echo -e "${RED}[ERROR]${NC} $*" >&2; exit 1; } local label="$2"
header() { echo -e "\n${CYAN}══════════════════════════════════════════${NC}"; echo -e "${CYAN} $*${NC}"; echo -e "${CYAN}══════════════════════════════════════════${NC}"; } 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" [ "$(id -u)" -eq 0 ] || die "Run as root"
check_deps ip ssh ssh-keygen ssh-keyscan systemctl hostnamectl
read -rp "Hub hostname [oily.dad]: " HUB_HOST
HUB_HOST="${HUB_HOST:-oily.dad}"
read -rp "Hub SSH user [armbian]: " HUB_USER
HUB_USER="${HUB_USER:-armbian}"
read -rp "Spoke local user [armbian]: " SPOKE_USER
SPOKE_USER="${SPOKE_USER:-armbian}"
ARMBIAN_HOME="/home/$SPOKE_USER"
SSH_DIR="$ARMBIAN_HOME/.ssh"
header "TinyBoard Spoke Setup" header "TinyBoard Spoke Setup"
info "Installing packages..." header "Detecting Package Manager"
apt-get update -q if command -v apt-get >/dev/null 2>&1; then
apt-get install -y -q vim autossh docker.io docker-compose-plugin git openssh-server PKG_MANAGER="apt"
PKG_INSTALL="apt-get install -y -q"
OPENSSH_PKG="openssh-server"
AUTOSSH_PKG="autossh"
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"
AUTOSSH_PKG="autossh"
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"
AUTOSSH_PKG="autossh"
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"
AUTOSSH_PKG="autossh"
info "Detected: pacman (Arch)"
pacman -Sy --quiet
else
die "No supported package manager found (apt, dnf, yum, pacman)"
fi
info "Adding armbian to docker group..." header "Installing Packages"
usermod -aG docker armbian 2>/dev/null || true if ! command -v curl >/dev/null 2>&1; then
$PKG_INSTALL curl
fi
$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
else
curl -fsSL https://get.docker.com | bash
fi
else
warn "Docker already installed, skipping."
fi
if ! docker compose version >/dev/null 2>&1; then
if [ "$PKG_MANAGER" = "apt" ]; then
$PKG_INSTALL docker-compose-plugin
else
warn "docker compose not available — Docker install script should have included it."
fi
fi
info "Adding $SPOKE_USER to docker group..."
usermod -aG docker "$SPOKE_USER" 2>/dev/null || true
info "Enabling SSH server..." info "Enabling SSH server..."
systemctl enable ssh if systemctl enable ssh 2>/dev/null; then
systemctl start ssh systemctl start ssh
elif systemctl enable sshd 2>/dev/null; then
systemctl start sshd
else
warn "Could not enable SSH service — please start it manually."
fi
SSHD_CONF="/etc/ssh/sshd_config"
header "Hostname Setup" header "Hostname Setup"
CURRENT_HOSTNAME=$(hostname) CURRENT_HOSTNAME=$(hostname)
@@ -73,43 +182,44 @@ read -rp "Choose [1/2]: " KEY_CHOICE
case "$KEY_CHOICE" in case "$KEY_CHOICE" in
1) 1)
KEY_NAME="oilykey" read -rp "Key name [hubkey]: " KEY_NAME
KEY_NAME="${KEY_NAME:-hubkey}"
KEY_PATH="$SSH_DIR/$KEY_NAME" KEY_PATH="$SSH_DIR/$KEY_NAME"
mkdir -p "$SSH_DIR" mkdir -p "$SSH_DIR"
chown armbian:armbian "$SSH_DIR" chown "$SPOKE_USER":"$SPOKE_USER" "$SSH_DIR"
chmod 700 "$SSH_DIR" chmod 700 "$SSH_DIR"
if [ -f "$KEY_PATH" ]; then if [ -f "$KEY_PATH" ]; then
warn "Key $KEY_PATH already exists, using it." warn "Key $KEY_PATH already exists, using it."
else else
info "Generating new ED25519 key..." info "Generating new ED25519 key..."
sudo -u armbian ssh-keygen -t ed25519 -f "$KEY_PATH" -N "" sudo -u "$SPOKE_USER" ssh-keygen -t ed25519 -f "$KEY_PATH" -N ""
chown armbian:armbian "$KEY_PATH" "$KEY_PATH.pub" chown "$SPOKE_USER":"$SPOKE_USER" "$KEY_PATH" "$KEY_PATH.pub"
chmod 600 "$KEY_PATH" chmod 600 "$KEY_PATH"
fi fi
echo "" echo ""
echo -e "${YELLOW}══════════════════════════════════════════${NC}" echo -e "${YELLOW}══════════════════════════════════════════${NC}"
echo -e "${YELLOW} Send this public key to finn (oily.dad owner)${NC}" echo -e "${YELLOW} Send this public key to the hub owner${NC}"
echo -e "${YELLOW} and ask him to add it to armbian@oily.dad authorized_keys:${NC}" echo -e "${YELLOW} and ask them to add it to ${HUB_USER}@${HUB_HOST} authorized_keys:${NC}"
echo -e "${YELLOW}══════════════════════════════════════════${NC}" echo -e "${YELLOW}══════════════════════════════════════════${NC}"
cat "$KEY_PATH.pub" cat "$KEY_PATH.pub"
echo -e "${YELLOW}══════════════════════════════════════════${NC}" echo -e "${YELLOW}══════════════════════════════════════════${NC}"
echo "" echo ""
read -rp "Press ENTER once finn has added the key to oily.dad..." read -rp "Press ENTER once the key has been added to ${HUB_HOST}..."
;; ;;
2) 2)
read -rp "Enter a name for the key file (e.g. oilykey): " KEY_NAME read -rp "Enter a name for the key file [hubkey]: " KEY_NAME
KEY_NAME="${KEY_NAME:-oilykey}" KEY_NAME="${KEY_NAME:-hubkey}"
KEY_PATH="$SSH_DIR/$KEY_NAME" KEY_PATH="$SSH_DIR/$KEY_NAME"
mkdir -p "$SSH_DIR" mkdir -p "$SSH_DIR"
chown armbian:armbian "$SSH_DIR" chown "$SPOKE_USER":"$SPOKE_USER" "$SSH_DIR"
chmod 700 "$SSH_DIR" chmod 700 "$SSH_DIR"
echo "Paste the private key content below, then press ENTER and CTRL+D:" echo "Paste the private key content below, then press ENTER and CTRL+D:"
KEY_CONTENT=$(cat | tr -d '\r') KEY_CONTENT=$(cat | tr -d '\r')
printf '%s\n' "$KEY_CONTENT" > "$KEY_PATH" printf '%s\n' "$KEY_CONTENT" > "$KEY_PATH"
chown armbian:armbian "$KEY_PATH" chown "$SPOKE_USER":"$SPOKE_USER" "$KEY_PATH"
chmod 600 "$KEY_PATH" chmod 600 "$KEY_PATH"
info "Key saved to $KEY_PATH" info "Key saved to $KEY_PATH"
;; ;;
@@ -118,23 +228,65 @@ case "$KEY_CHOICE" in
;; ;;
esac 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}"
if [[ "${DISABLE_PASS,,}" == "y" ]]; then
if [ ! -f "$KEY_PATH" ]; then
warn "No key found at $KEY_PATH — 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 $SPOKE_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
info "Checking SSH key permissions..."
check_permissions "$KEY_PATH" "spoke SSH private key"
if [ -f "$KEY_PATH.pub" ]; then
check_permissions "$KEY_PATH.pub" "spoke SSH public key"
fi
info "Scanning hub host key..." info "Scanning hub host key..."
sudo -u armbian touch "$SSH_DIR/known_hosts" sudo -u "$SPOKE_USER" touch "$SSH_DIR/known_hosts"
chown armbian:armbian "$SSH_DIR/known_hosts" chown "$SPOKE_USER":"$SPOKE_USER" "$SSH_DIR/known_hosts"
chmod 600 "$SSH_DIR/known_hosts" chmod 600 "$SSH_DIR/known_hosts"
sudo -u armbian ssh-keyscan -H "$HUB_HOST" >> "$SSH_DIR/known_hosts" 2>/dev/null sudo -u "$SPOKE_USER" ssh-keyscan -H "$HUB_HOST" >> "$SSH_DIR/known_hosts" 2>/dev/null
check_permissions "$SSH_DIR/known_hosts" "known_hosts"
header "Testing SSH Connection" header "Testing SSH Connection"
info "Testing connection to $HUB_HOST..." info "Testing connection to $HUB_HOST..."
retry_or_abort \ retry_or_abort \
"sudo -u armbian ssh -i \"$KEY_PATH\" -o BatchMode=yes -o ConnectTimeout=10 \"$HUB_USER@$HUB_HOST\" exit" \ "sudo -u \"$SPOKE_USER\" ssh -i \"$KEY_PATH\" -o BatchMode=yes -o ConnectTimeout=10 \"$HUB_USER@$HUB_HOST\" exit" \
"SSH connection to $HUB_HOST failed. Check that finn added your public key." "SSH connection to $HUB_HOST failed. Check that the hub owner added your public key."
header "Finding Available Tunnel Port" header "Finding Available Tunnel Port"
info "Scanning for a free port on $HUB_HOST starting from $START_PORT..." info "Scanning for a free port on $HUB_HOST starting from $START_PORT..."
TUNNEL_PORT="" TUNNEL_PORT=""
for PORT in $(seq "$START_PORT" $((START_PORT + 20))); do for PORT in $(seq "$START_PORT" $((START_PORT + 20))); do
RESULT=$(sudo -u armbian ssh -i "$KEY_PATH" "$HUB_USER@$HUB_HOST" "ss -tlnp | grep :$PORT" 2>/dev/null || true) RESULT=$(sudo -u "$SPOKE_USER" ssh -i "$KEY_PATH" "$HUB_USER@$HUB_HOST" "ss -tlnp | grep :$PORT" 2>/dev/null || true)
if [ -z "$RESULT" ]; then if [ -z "$RESULT" ]; then
TUNNEL_PORT="$PORT" TUNNEL_PORT="$PORT"
info "Port $TUNNEL_PORT is available." info "Port $TUNNEL_PORT is available."
@@ -144,14 +296,14 @@ for PORT in $(seq "$START_PORT" $((START_PORT + 20))); do
fi fi
done done
[ -n "$TUNNEL_PORT" ] || die "Could not find a free port between $START_PORT and $((START_PORT + 20)). Ask finn to free up a port." [ -n "$TUNNEL_PORT" ] || die "Could not find a free port between $START_PORT and $((START_PORT + 20)). Ask the hub owner to free up a port."
header "Configuring compose.yaml" header "Configuring compose.yaml"
info "Setting port to $TUNNEL_PORT and key to $KEY_NAME..." info "Setting port to $TUNNEL_PORT and key to $KEY_NAME..."
sed -i "s|-R [0-9]*:localhost:22|-R ${TUNNEL_PORT}:localhost:22|g" "$COMPOSE" sed -i "s|-R [0-9]*:localhost:22|-R ${TUNNEL_PORT}:localhost:22|g" "$COMPOSE"
sed -i "s|-i /home/armbian/.ssh/[^ ]*|-i /home/armbian/.ssh/${KEY_NAME}|g" "$COMPOSE" sed -i "s|-i /home/[^ ]*/\.ssh/[^ ]*|-i ${SSH_DIR}/${KEY_NAME}|g" "$COMPOSE"
sed -i "s|/home/armbian/.ssh/oilykey[^:]*:/home/armbian/.ssh/oilykey[^:]*|${SSH_DIR}/${KEY_NAME}:${SSH_DIR}/${KEY_NAME}|g" "$COMPOSE" sed -i "s|/home/[^/]*/\.ssh/[^:]*:/home/[^/]*/\.ssh/[^:]*|${SSH_DIR}/${KEY_NAME}:${SSH_DIR}/${KEY_NAME}|g" "$COMPOSE"
sed -i "s|container_name: spoke-autossh|container_name: ${SPOKE_NAME}-autossh|g" "$COMPOSE" sed -i "s|container_name: spoke-autossh|container_name: ${SPOKE_NAME}-autossh|g" "$COMPOSE"
sed -i "s|container_name: spoke-syncthing|container_name: ${SPOKE_NAME}-syncthing|g" "$COMPOSE" sed -i "s|container_name: spoke-syncthing|container_name: ${SPOKE_NAME}-syncthing|g" "$COMPOSE"
sed -i "s|hostname: spoke-syncthing|hostname: ${SPOKE_NAME}-syncthing|g" "$COMPOSE" sed -i "s|hostname: spoke-syncthing|hostname: ${SPOKE_NAME}-syncthing|g" "$COMPOSE"
@@ -159,13 +311,13 @@ sed -i '/^version:/d' "$COMPOSE"
SYNCTHING_MOUNT="$ARMBIAN_HOME/st" SYNCTHING_MOUNT="$ARMBIAN_HOME/st"
mkdir -p "$SYNCTHING_MOUNT" mkdir -p "$SYNCTHING_MOUNT"
chown armbian:armbian "$SYNCTHING_MOUNT" chown "$SPOKE_USER":"$SPOKE_USER" "$SYNCTHING_MOUNT"
header "Building Docker Image" header "Building Docker Image"
cd "$SPOKE_DIR" cd "$SPOKE_DIR"
docker build \ docker build \
--build-arg UID="$(id -u armbian)" \ --build-arg UID="$(id -u "$SPOKE_USER")" \
--build-arg GID="$(id -g armbian)" \ --build-arg GID="$(id -g "$SPOKE_USER")" \
-t spoke-autossh . -t spoke-autossh .
header "Starting Containers" header "Starting Containers"
@@ -187,20 +339,20 @@ echo -e " Spoke name: ${GREEN}$SPOKE_NAME${NC}"
echo -e " Tunnel port: ${GREEN}$TUNNEL_PORT${NC} on $HUB_HOST" echo -e " Tunnel port: ${GREEN}$TUNNEL_PORT${NC} on $HUB_HOST"
echo -e " SSH key: ${GREEN}$KEY_PATH${NC}" echo -e " SSH key: ${GREEN}$KEY_PATH${NC}"
echo "" echo ""
echo -e "${YELLOW}Finn needs to do the following on oily.dad:${NC}" echo -e "${YELLOW}The hub owner needs to do the following on ${HUB_HOST}:${NC}"
echo "" echo ""
echo " 1. Generate a hub->spoke key:" echo " 1. Generate a hub->spoke key:"
echo " ssh-keygen -t ed25519 -f ~/.ssh/armbian-${SPOKE_NAME}-$(date +%Y%m)" echo " ssh-keygen -t ed25519 -f ~/.ssh/armbian-${SPOKE_NAME}-$(date +%Y%m)"
echo "" echo ""
echo " 2. Copy it to this spoke through the tunnel:" echo " 2. Copy it to this spoke through the tunnel:"
echo " ssh-copy-id -i ~/.ssh/armbian-${SPOKE_NAME}-$(date +%Y%m).pub -p $TUNNEL_PORT armbian@localhost" echo " ssh-copy-id -i ~/.ssh/armbian-${SPOKE_NAME}-$(date +%Y%m).pub -p $TUNNEL_PORT ${HUB_USER}@localhost"
echo "" echo ""
echo " 3. Add an rclone remote in ~/.config/rclone/rclone.conf:" echo " 3. Add an rclone remote in ~/.config/rclone/rclone.conf:"
echo " [${SPOKE_NAME}-remote]" echo " [${SPOKE_NAME}-remote]"
echo " type = sftp" echo " type = sftp"
echo " host = localhost" echo " host = localhost"
echo " port = $TUNNEL_PORT" echo " port = $TUNNEL_PORT"
echo " key_file = /home/armbian/.ssh/armbian-${SPOKE_NAME}-$(date +%Y%m)" echo " key_file = /home/$HUB_USER/.ssh/armbian-${SPOKE_NAME}-$(date +%Y%m)"
echo " shell_type = unix" echo " shell_type = unix"
echo " md5sum_command = md5sum" echo " md5sum_command = md5sum"
echo " sha1sum_command = sha1sum" echo " sha1sum_command = sha1sum"