d
This commit is contained in:
363
netcup/Dockerfile
Normal file
363
netcup/Dockerfile
Normal file
@@ -0,0 +1,363 @@
|
||||
FROM ubuntu:24.04
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
openssh-client \
|
||||
openssl \
|
||||
curl \
|
||||
sshfs \
|
||||
restic \
|
||||
fuse3 \
|
||||
sshpass && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
WORKDIR /app
|
||||
COPY id_ed25519.enc /app/id_ed25519.enc
|
||||
|
||||
RUN cat > /app/run.sh << 'SCRIPT'
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════════════
|
||||
# VALIDATION
|
||||
# ══════════════════════════════════════════════════════════════════════════════
|
||||
REQUIRED_VARS=(KEY_PASSWORD REMOTE_HOST RESTIC_PASSWORD SSH_PASSWORD)
|
||||
for var in "${REQUIRED_VARS[@]}"; do
|
||||
if [[ -z "${!var:-}" ]]; then
|
||||
echo "❌ Error: Missing required variable: $var"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
MODE="${MODE:-BACKUP}"
|
||||
MOUNT_REMOTE="${MOUNT_REMOTE:-root@n.h-y.st:/mnt/data}"
|
||||
MOUNT_POINT="/mnt/data"
|
||||
RESTIC_REPO="$MOUNT_POINT/restic-repo"
|
||||
COMPOSE_DIR="${COMPOSE_DIR:-/root/docker}"
|
||||
|
||||
echo "
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
🚀 Backup/Restore Tool
|
||||
Mode : $MODE
|
||||
Target : root@$REMOTE_HOST
|
||||
Compose : $COMPOSE_DIR
|
||||
Mount : $MOUNT_REMOTE
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
"
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════════════
|
||||
# CLEANUP TRAP
|
||||
# ══════════════════════════════════════════════════════════════════════════════
|
||||
cleanup() {
|
||||
echo "🧹 Cleaning up local secrets..."
|
||||
rm -f ~/.ssh/id_ed25519 ~/.ssh/id_ed25519.pub
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════════════
|
||||
# DECRYPT SSH KEY
|
||||
# ══════════════════════════════════════════════════════════════════════════════
|
||||
echo "🔑 Decrypting SSH key..."
|
||||
mkdir -p ~/.ssh && chmod 700 ~/.ssh
|
||||
|
||||
if ! openssl enc -d -aes-256-cbc -pbkdf2 \
|
||||
-in /app/id_ed25519.enc \
|
||||
-out ~/.ssh/id_ed25519 \
|
||||
-pass pass:"$KEY_PASSWORD" 2>/dev/null; then
|
||||
echo "❌ Failed to decrypt SSH key — check KEY_PASSWORD"
|
||||
exit 1
|
||||
fi
|
||||
chmod 600 ~/.ssh/id_ed25519
|
||||
ssh-keygen -y -f ~/.ssh/id_ed25519 > ~/.ssh/id_ed25519.pub 2>/dev/null || true
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════════════
|
||||
# SSH HELPERS
|
||||
# ══════════════════════════════════════════════════════════════════════════════
|
||||
KEY_SSH() {
|
||||
ssh \
|
||||
-o StrictHostKeyChecking=no \
|
||||
-o ConnectTimeout=10 \
|
||||
-o BatchMode=yes \
|
||||
-o ServerAliveInterval=15 \
|
||||
-o ServerAliveCountMax=3 \
|
||||
-i ~/.ssh/id_ed25519 \
|
||||
"$@"
|
||||
}
|
||||
|
||||
PASS_SSH() {
|
||||
sshpass -p "$SSH_PASSWORD" ssh \
|
||||
-o StrictHostKeyChecking=no \
|
||||
-o ConnectTimeout=10 \
|
||||
-o ServerAliveInterval=15 \
|
||||
-o ServerAliveCountMax=3 \
|
||||
"$@"
|
||||
}
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════════════
|
||||
# CONNECTIVITY + AUTH BOOTSTRAP
|
||||
# ══════════════════════════════════════════════════════════════════════════════
|
||||
echo "🔍 Testing connectivity to $REMOTE_HOST..."
|
||||
|
||||
if KEY_SSH "root@$REMOTE_HOST" exit 2>/dev/null; then
|
||||
echo "✅ Key-based auth succeeded"
|
||||
SSH_CONNECT() { KEY_SSH "root@$REMOTE_HOST" "$@"; }
|
||||
else
|
||||
echo "⚠️ Key auth failed — attempting password auth..."
|
||||
if ! PASS_SSH "root@$REMOTE_HOST" exit 2>/dev/null; then
|
||||
echo "❌ Cannot connect to $REMOTE_HOST — both auth methods failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ Password auth succeeded"
|
||||
SSH_CONNECT() { PASS_SSH "root@$REMOTE_HOST" "$@"; }
|
||||
|
||||
echo "🔑 Installing SSH public key for future runs..."
|
||||
sshpass -p "$SSH_PASSWORD" ssh-copy-id \
|
||||
-o StrictHostKeyChecking=no \
|
||||
-i ~/.ssh/id_ed25519 \
|
||||
"root@$REMOTE_HOST" && \
|
||||
echo "✅ Key installed — password auth won't be needed next run" || \
|
||||
echo "⚠️ ssh-copy-id failed — continuing with password auth"
|
||||
fi
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════════════
|
||||
# CAPTURE PRIVATE KEY FOR REMOTE INJECTION
|
||||
# ══════════════════════════════════════════════════════════════════════════════
|
||||
PRIVATE_KEY_CONTENTS=$(cat ~/.ssh/id_ed25519)
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════════════
|
||||
# REMOTE SESSION
|
||||
# ══════════════════════════════════════════════════════════════════════════════
|
||||
echo "🚀 Starting remote session on $REMOTE_HOST..."
|
||||
|
||||
SSH_CONNECT bash << EOF
|
||||
set -euo pipefail
|
||||
|
||||
COMPOSE_DIR="$COMPOSE_DIR"
|
||||
MOUNT_POINT="$MOUNT_POINT"
|
||||
MOUNT_REMOTE="$MOUNT_REMOTE"
|
||||
RESTIC_REPO="$RESTIC_REPO"
|
||||
MODE="$MODE"
|
||||
export RESTIC_PASSWORD="$RESTIC_PASSWORD"
|
||||
export RESTIC_REPOSITORY="\$RESTIC_REPO"
|
||||
|
||||
log() { echo " \$1"; }
|
||||
step() { echo ""; echo "▶ \$1"; }
|
||||
|
||||
has_compose() {
|
||||
[ -f "\$1/docker-compose.yml" ] || [ -f "\$1/compose.yml" ]
|
||||
}
|
||||
|
||||
find_compose_dir() {
|
||||
if has_compose "\$COMPOSE_DIR"; then
|
||||
echo "\$COMPOSE_DIR"
|
||||
elif has_compose "/home/zeshan/docker"; then
|
||||
echo "/home/zeshan/docker"
|
||||
else
|
||||
echo ""
|
||||
fi
|
||||
}
|
||||
|
||||
# ── 1. Install Dependencies ───────────────────────────────────────────────────
|
||||
step "Installing dependencies"
|
||||
|
||||
log "📦 Updating package lists..."
|
||||
apt-get update -qq
|
||||
|
||||
log "📦 Installing packages..."
|
||||
apt-get install -y --no-install-recommends \
|
||||
curl \
|
||||
wget \
|
||||
ca-certificates \
|
||||
bash \
|
||||
coreutils \
|
||||
procps \
|
||||
openssh-server \
|
||||
sshfs \
|
||||
restic \
|
||||
fuse3
|
||||
log "✅ Packages installed"
|
||||
|
||||
# ── 2. Docker ─────────────────────────────────────────────────────────────────
|
||||
step "Docker"
|
||||
if ! command -v docker &>/dev/null; then
|
||||
log "🐋 Installing Docker..."
|
||||
curl -fsSL https://get.docker.com | sh
|
||||
systemctl enable --now docker
|
||||
log "✅ Docker installed"
|
||||
else
|
||||
log "✅ Docker already installed (\$(docker --version))"
|
||||
fi
|
||||
|
||||
# ── 3. Migrate legacy zeshan paths ───────────────────────────────────────────
|
||||
step "Path migration"
|
||||
if [ -d "/home/zeshan/docker" ] && [ ! -d "/root/docker" ]; then
|
||||
log "📦 Migrating /home/zeshan/docker → /root/docker..."
|
||||
cp -r /home/zeshan/docker /root/docker
|
||||
log "✅ Migration complete"
|
||||
elif [ -d "/root/docker" ]; then
|
||||
log "✅ /root/docker already exists"
|
||||
else
|
||||
log "⚠️ No compose directory found yet"
|
||||
fi
|
||||
|
||||
# ── 4. FUSE ───────────────────────────────────────────────────────────────────
|
||||
step "FUSE config"
|
||||
grep -q "^user_allow_other" /etc/fuse.conf 2>/dev/null || \
|
||||
echo "user_allow_other" >> /etc/fuse.conf
|
||||
log "✅ FUSE configured"
|
||||
|
||||
# ── 5. Storage Mount ──────────────────────────────────────────────────────────
|
||||
step "Storage mount"
|
||||
if mountpoint -q "\$MOUNT_POINT"; then
|
||||
log "⚠️ Already mounted — unmounting first..."
|
||||
umount -l "\$MOUNT_POINT"
|
||||
fi
|
||||
mkdir -p "\$MOUNT_POINT"
|
||||
|
||||
MOUNT_KEY="\$(mktemp /root/.ssh/mount_key.XXXXXX)"
|
||||
chmod 600 "\$MOUNT_KEY"
|
||||
cat > "\$MOUNT_KEY" << 'PRIVKEY'
|
||||
$PRIVATE_KEY_CONTENTS
|
||||
PRIVKEY
|
||||
chmod 600 "\$MOUNT_KEY"
|
||||
trap 'rm -f \$MOUNT_KEY' EXIT
|
||||
|
||||
log "🔗 Mounting \$MOUNT_REMOTE..."
|
||||
sshfs \
|
||||
-o StrictHostKeyChecking=no \
|
||||
-o IdentityFile="\$MOUNT_KEY" \
|
||||
-o allow_other \
|
||||
-o reconnect \
|
||||
-o ServerAliveInterval=15 \
|
||||
-o ServerAliveCountMax=3 \
|
||||
"\$MOUNT_REMOTE" "\$MOUNT_POINT"
|
||||
|
||||
rm -f "\$MOUNT_KEY"
|
||||
|
||||
if ! mountpoint -q "\$MOUNT_POINT"; then
|
||||
echo "❌ Mount failed!"
|
||||
exit 1
|
||||
fi
|
||||
log "✅ \$MOUNT_POINT mounted from \$MOUNT_REMOTE"
|
||||
# ── 6. Persist mount in fstab ─────────────────────────────────────────────────
|
||||
step "Persisting mount in fstab"
|
||||
|
||||
PERSISTENT_KEY="/root/.ssh/sshfs_mount_key"
|
||||
cat > "\$PERSISTENT_KEY" << 'PRIVKEY'
|
||||
$PRIVATE_KEY_CONTENTS
|
||||
PRIVKEY
|
||||
chmod 600 "\$PERSISTENT_KEY"
|
||||
|
||||
if grep -q "n.h-y.st:/mnt/data" /etc/fstab; then
|
||||
log "✅ fstab entry already exists — skipping"
|
||||
else
|
||||
echo "root@n.h-y.st:/mnt/data /mnt/data fuse.sshfs IdentityFile=/root/.ssh/sshfs_mount_key,StrictHostKeyChecking=no,allow_other,reconnect,ServerAliveInterval=15,ServerAliveCountMax=3,_netdev,x-systemd.automount 0 0" >> /etc/fstab
|
||||
log "✅ fstab entry added"
|
||||
fi
|
||||
|
||||
grep "n.h-y.st" /etc/fstab
|
||||
# ── 6. Restic Repo ────────────────────────────────────────────────────────────
|
||||
step "Restic repository"
|
||||
if ! restic snapshots &>/dev/null; then
|
||||
log "📦 Initialising new restic repository..."
|
||||
restic init
|
||||
log "✅ Repository initialised"
|
||||
else
|
||||
log "✅ Repository already exists"
|
||||
restic snapshots --compact
|
||||
fi
|
||||
|
||||
# ── 7. Backup or Restore ──────────────────────────────────────────────────────
|
||||
step "Task: \$MODE"
|
||||
|
||||
if [ "\$MODE" == "RESTORE" ]; then
|
||||
|
||||
log "📋 Available snapshots:"
|
||||
restic snapshots
|
||||
|
||||
log "⚠️ Restoring in 5 seconds — Ctrl+C to abort..."
|
||||
sleep 5
|
||||
|
||||
log "⏬ Restoring latest snapshot..."
|
||||
mkdir -p /root/docker /var/lib/docker/volumes
|
||||
restic restore latest --target /
|
||||
|
||||
if [ -d "/home/zeshan/docker" ] && [ ! -d "/root/docker" ]; then
|
||||
log "📦 Moving restored zeshan paths to /root/docker..."
|
||||
cp -r /home/zeshan/docker /root/docker
|
||||
fi
|
||||
|
||||
log "✅ Restore complete"
|
||||
|
||||
ACTIVE_COMPOSE="\$(find_compose_dir)"
|
||||
if [ -n "\$ACTIVE_COMPOSE" ]; then
|
||||
log "🐳 Starting Docker services from \$ACTIVE_COMPOSE..."
|
||||
cd "\$ACTIVE_COMPOSE" && docker compose up -d
|
||||
log "✅ Services started"
|
||||
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
|
||||
else
|
||||
log "⚠️ No compose file found — skipping docker compose up"
|
||||
fi
|
||||
|
||||
else # BACKUP
|
||||
|
||||
ACTIVE_COMPOSE="\$(find_compose_dir)"
|
||||
|
||||
if [ -n "\$ACTIVE_COMPOSE" ]; then
|
||||
log "⏸️ Stopping services for consistent backup..."
|
||||
cd "\$ACTIVE_COMPOSE" && docker compose stop
|
||||
else
|
||||
log "⚠️ No compose file found — skipping stop"
|
||||
fi
|
||||
|
||||
log "💾 Running backup..."
|
||||
BACKUP_PATHS="/var/lib/docker/volumes"
|
||||
[ -d "/root/docker" ] && BACKUP_PATHS="\$BACKUP_PATHS /root/docker"
|
||||
[ -d "/home/zeshan/docker" ] && BACKUP_PATHS="\$BACKUP_PATHS /home/zeshan/docker"
|
||||
|
||||
restic backup \
|
||||
--tag automated \
|
||||
--tag "\$(date +%Y-%m-%d)" \
|
||||
--exclude="*.log" \
|
||||
--exclude="*.tmp" \
|
||||
--exclude="*.cache" \
|
||||
\$BACKUP_PATHS
|
||||
|
||||
log "✂️ Pruning old snapshots..."
|
||||
restic forget \
|
||||
--tag automated \
|
||||
--keep-daily 7 \
|
||||
--keep-weekly 4 \
|
||||
--keep-monthly 3 \
|
||||
--prune
|
||||
|
||||
log "🔍 Verifying repository integrity..."
|
||||
restic check --read-data-subset=5%
|
||||
|
||||
if [ -n "\$ACTIVE_COMPOSE" ]; then
|
||||
log "▶️ Restarting services..."
|
||||
cd "\$ACTIVE_COMPOSE" && docker compose start
|
||||
log "✅ Services restarted"
|
||||
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
|
||||
fi
|
||||
|
||||
fi
|
||||
|
||||
# ── 8. Cleanup ────────────────────────────────────────────────────────────────
|
||||
step "Cleanup"
|
||||
umount "\$MOUNT_POINT" && log "✅ \$MOUNT_POINT unmounted"
|
||||
|
||||
echo "
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
✅ \$MODE completed successfully
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
"
|
||||
EOF
|
||||
|
||||
SCRIPT
|
||||
|
||||
RUN chmod +x /app/run.sh
|
||||
ENTRYPOINT ["/app/run.sh"]
|
||||
124
netcup/compose.yml
Normal file
124
netcup/compose.yml
Normal file
@@ -0,0 +1,124 @@
|
||||
services:
|
||||
|
||||
cvsite:
|
||||
image: r.h-y.st/zeshan:latest
|
||||
container_name: zeshan
|
||||
restart: always
|
||||
networks:
|
||||
- hurricane
|
||||
|
||||
vw:
|
||||
image: vaultwarden/server:latest
|
||||
container_name: vaultwarden
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
DOMAIN: "https://vault.ztariq.com"
|
||||
ADMIN_TOKEN: '$$argon2id$$v=19$$m=65540,t=3,p=4$$iFEc/H/Tg6oBMTNkeToGeA4ThFMOEudDjpgLg0uz53A$$jFTr/G3MH5hwLwfvrEzZGHWYek9TUs57SXGX1YHpdkY'
|
||||
volumes:
|
||||
- ./data/vaultwarden:/data
|
||||
networks:
|
||||
- hurricane
|
||||
|
||||
caddy:
|
||||
image: caddy:latest
|
||||
container_name: caddy
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
- "443:443/udp"
|
||||
volumes:
|
||||
- ./data/caddy/etc:/etc/caddy
|
||||
- ./data/caddy/data:/data
|
||||
- ./data/caddy/config:/config
|
||||
networks:
|
||||
- hurricane
|
||||
|
||||
plex:
|
||||
image: lscr.io/linuxserver/plex:latest
|
||||
container_name: plex
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- PUID=1000
|
||||
- PGID=1000
|
||||
- TZ=Europe/London
|
||||
- VERSION=docker
|
||||
volumes:
|
||||
- ./data/plex:/config
|
||||
- /mnt/data/media:/media
|
||||
network_mode: host
|
||||
|
||||
jellyfin:
|
||||
image: jellyfin/jellyfin:latest
|
||||
container_name: jellyfin
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- TZ=Europe/London
|
||||
volumes:
|
||||
- ./data/jellyfin/config:/config
|
||||
- ./data/jellyfin/cache:/cache
|
||||
- /mnt/data/media:/media
|
||||
network_mode: host
|
||||
|
||||
db:
|
||||
image: postgres:latest
|
||||
container_name: nextcloud-db
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_DB: nextcloud
|
||||
POSTGRES_USER: nextcloud
|
||||
POSTGRES_PASSWORD: Shan33779488
|
||||
volumes:
|
||||
- ./data/postgres:/var/lib/postgresql
|
||||
networks:
|
||||
- hurricane
|
||||
|
||||
redis:
|
||||
image: redis:alpine
|
||||
container_name: redis
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ./data/redis:/data
|
||||
networks:
|
||||
- hurricane
|
||||
|
||||
nextcloud:
|
||||
image: nextcloud:latest
|
||||
container_name: nextcloud
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_HOST: db
|
||||
POSTGRES_DB: nextcloud
|
||||
POSTGRES_USER: nextcloud
|
||||
POSTGRES_PASSWORD: Shan33779488
|
||||
REDIS_HOST: redis
|
||||
TRUSTED_PROXIES: 172.0.0/8,10.0.0/8,192.168.0/16
|
||||
OVERWRITEHOST: next.ztariq.com
|
||||
OVERWRITEPROTOCOL: https
|
||||
OVERWRITECLIURL: https://next.ztariq.com
|
||||
volumes:
|
||||
- ./data/nextcloud:/var/www/html
|
||||
networks:
|
||||
- hurricane
|
||||
depends_on:
|
||||
- db
|
||||
- redis
|
||||
|
||||
nextcloud-cron:
|
||||
image: nextcloud:latest
|
||||
container_name: nextcloud-cron
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
POSTGRES_HOST: db
|
||||
volumes:
|
||||
- ./data/nextcloud:/var/www/html
|
||||
entrypoint: /cron.sh
|
||||
networks:
|
||||
- hurricane
|
||||
depends_on:
|
||||
- db
|
||||
- redis
|
||||
|
||||
networks:
|
||||
hurricane:
|
||||
external: true
|
||||
24
netcup/usage.sh
Normal file
24
netcup/usage.sh
Normal file
@@ -0,0 +1,24 @@
|
||||
# .env — never commit this to git
|
||||
KEY_PASSWORD=your-key-password
|
||||
REMOTE_HOST=192.168.1.86
|
||||
SSH_PASSWORD=your-root-ssh-password
|
||||
RESTIC_PASSWORD=your-restic-password
|
||||
MODE=BACKUP
|
||||
|
||||
# Optional
|
||||
# MOUNT_REMOTE=root@n.h-y.st:/mnt/data
|
||||
|
||||
|
||||
docker build -t ztariq129/hurricane:restore .
|
||||
|
||||
# Build
|
||||
docker build -t ztariq129/hurricane:restore .
|
||||
|
||||
# Backup
|
||||
docker run --rm --env-file .env ztariq129/hurricane:restore
|
||||
|
||||
# Restore
|
||||
docker run --rm --env-file .env -e MODE=RESTORE ztariq129/hurricane:restore
|
||||
|
||||
# Debug
|
||||
docker run --rm -it --env-file .env --entrypoint bash ztariq129/hurricane:restore
|
||||
Reference in New Issue
Block a user