forked from finn/tinyboard
Compare commits
17 Commits
395ab4ed0e
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8ee67739f7 | ||
|
|
39f8f64351 | ||
|
|
e924579b2e | ||
|
|
912e553e06 | ||
|
|
98986e615b | ||
|
|
0e792be751 | ||
|
|
835793d396 | ||
|
|
11f9586c5e | ||
|
|
3e351f925d | ||
|
|
a197b7881b | ||
|
|
60feeca65e | ||
|
|
88fabcf25f | ||
|
|
51f661766f | ||
|
|
5326823b81 | ||
|
|
0f76283605 | ||
|
|
a02a83cae4 | ||
|
|
4a1983d46d |
308
README.md
308
README.md
@@ -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
129
hub/offboard-spoke.sh
Executable 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 ""
|
||||||
0
hub/onboard-spoke.sh
Normal file → Executable file
0
hub/onboard-spoke.sh
Normal file → Executable 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
|
|
||||||
|
|
||||||
2
hub/setup-hub.sh
Normal file → Executable file
2
hub/setup-hub.sh
Normal file → Executable file
@@ -51,7 +51,7 @@ check_permissions() {
|
|||||||
|
|
||||||
[ "$(id -u)" -eq 0 ] || die "Run as root"
|
[ "$(id -u)" -eq 0 ] || die "Run as root"
|
||||||
|
|
||||||
check_deps ssh ssh-keygen systemctl useradd groupadd crontab
|
check_deps ssh ssh-keygen systemctl useradd groupadd
|
||||||
|
|
||||||
header "TinyBoard Hub Setup"
|
header "TinyBoard Hub Setup"
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
|
||||||
8
setup.sh
8
setup.sh
@@ -15,14 +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 " 3) Offboard a spoke from the hub"
|
||||||
echo " 4) Set up this device as a new hub"
|
echo " 4) Set up this device as a new hub"
|
||||||
echo ""
|
echo ""
|
||||||
read -rp "Choose [1/2/3/4]: " 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..."
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -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."
|
|
||||||
@@ -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"
|
|
||||||
@@ -197,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: ./setup.sh${NC}"
|
echo -e "${YELLOW} Then run: cd .. && ./setup.sh${NC}"
|
||||||
echo -e "${YELLOW}══════════════════════════════════════════${NC}"
|
echo -e "${YELLOW}══════════════════════════════════════════${NC}"
|
||||||
echo ""
|
echo ""
|
||||||
0
spoke/setup-spoke.sh
Normal file → Executable file
0
spoke/setup-spoke.sh
Normal file → Executable file
Reference in New Issue
Block a user