feat: initial infra repo

- docker-services: 14 generic compose templates (portainer, npm, vaultwarden, gitea, jotty, nextcloud-aio, rustdesk, audiobookshelf, calibre-web, plex, erugo, jswiki, speedtest, watchtower)
- install-scripts: bash scripts for Ubuntu server bootstrap, docker, portainer, npm, backup, restore
- ansible: 4 playbooks (bootstrap, deploy-services, update-services, security-hardening) + roles skeleton
- hermes-install: Hermes CLI setup on macOS
- macos-setup: Brewfile + osx-defaults + fish-config
- home-nas-tools: metadata index for NAS projects (no binaries)
- docs: architecture, security, contributing
- README, LICENSE (MIT), .gitignore
This commit is contained in:
2026-08-01 16:25:58 +02:00
commit 444191d16d
73 changed files with 4032 additions and 0 deletions
+66
View File
@@ -0,0 +1,66 @@
# 📥 Install Scripts
> Bash skripty pro nasazení nového Ubuntu serveru (22.04 / 24.04 LTS).
> Idempotentní, s `--help` a dry-run.
## 📚 Skripty
| Skript | Účel |
|---|---|
| [`bootstrap-ubuntu-server.sh`](bootstrap-ubuntu-server.sh) | Úvodní setup: aktualizace, uživatel, SSH, firewall, fail2ban |
| [`install-docker.sh`](install-docker.sh) | Docker + Docker Compose plugin |
| [`install-portainer.sh`](install-portainer.sh) | Portainer CE |
| [`install-nginx-proxy.sh`](install-nginx-proxy.sh) | Nginx Proxy Manager (s HTTPS) |
| [`backup-all.sh`](backup-all.sh) | Záloha všech Docker dat na NAS |
| [`restore-all.sh`](restore-all.sh) | Obnova z NAS zálohy |
## 🚀 Quickstart (nový server)
```bash
# 1. Bootstrap (úvodní nastavení)
sudo bash bootstrap-ubuntu-server.sh
# 2. Docker
sudo bash install-docker.sh
# 3. Portainer
sudo bash install-portainer.sh
# 4. NPM (reverzní proxy)
sudo bash install-nginx-proxy.sh
# 5. Přidej služby
cd ~/docker-services/portainer
cp .env.example .env && nano .env
docker compose up -d
```
## ⚙️ Společné konvence
- Všechny skripty podporují `--help`
- `set -euo pipefail` (fail-fast)
- Idempotentní (můžeš spustit víckrát)
- Logy do `/var/log/<skript>.log`
- Barvy v terminále (ANSI), automaticky se vypnou v non-TTY
## 🔐 Bezpečnost
- **Nikdy nespouštěj skript, který nečteš** `less <skript>` předem
- **Všechny skripty chtějí root** kontrolují `$EUID`
- **Všechny skripty** podporují dry-run, kde to dává smysl
- **Bez hesel v skriptech** vše z `prompt` nebo `.env`
## 📁 Struktura
```
install-scripts/
├── README.md
├── bootstrap-ubuntu-server.sh
├── install-docker.sh
├── install-portainer.sh
├── install-nginx-proxy.sh
├── backup-all.sh
├── restore-all.sh
└── lib/ # Společné knihovny
└── common.sh
```
+73
View File
@@ -0,0 +1,73 @@
#!/usr/bin/env bash
#
# Záloha všech Docker dat na NAS
# - najde všechny kontejnery s volume mounty v /home/<user>/docker
# - zastaví je (volitelně), zabalí data do tar.gz, pošle na NAS
# - ponechá běžící kontejnery běžet (default)
#
# Použití:
# sudo bash backup-all.sh # zálohuj vše (bez zastavení)
# sudo bash backup-all.sh --stop # zastav před zálohou
# sudo bash backup-all.sh --dry-run # pouze ukaž co by se dělalo
# sudo bash backup-all.sh --only portainer # jen jednu službu
#
set -euo pipefail
DEPLOY_USER="${DEPLOY_USER:-deploy}"
DOCKER_HOME="/home/$DEPLOY_USER/docker"
NAS_MOUNT="${NAS_MOUNT:-/Volumes/Backups}"
BACKUP_ROOT="$NAS_MOUNT/Prace/server-backups/$(hostname)"
TS="$(date +%Y%m%d-%H%M%S)"
STOP=false
DRY_RUN=false
ONLY=""
while [[ $# -gt 0 ]]; do
case $1 in
--stop) STOP=true; shift ;;
--dry-run) DRY_RUN=true; shift ;;
--only) ONLY="$2"; shift 2 ;;
*) echo "Neznámý parametr: $1" >&2; exit 1 ;;
esac
done
[[ $EUID -eq 0 ]] || { echo "Spusť jako root: sudo bash $0" >&2; exit 1; }
[[ -d "$DOCKER_HOME" ]] || { echo "Adresář $DOCKER_HOME neexistuje" >&2; exit 1; }
run() {
if $DRY_RUN; then
echo "[DRY-RUN] $*"
else
echo "$*"
"$@"
fi
}
mkdir -p "$BACKUP_ROOT"
for SERVICE_DIR in "$DOCKER_HOME"/*/; do
SERVICE=$(basename "$SERVICE_DIR")
[[ -n "$ONLY" && "$SERVICE" != "$ONLY" ]] && continue
[[ -f "$SERVICE_DIR/docker-compose.yml" ]] || continue
BACKUP_DIR="$BACKUP_ROOT/$SERVICE"
BACKUP_FILE="$BACKUP_DIR/${SERVICE}-$TS.tar.zst"
if $STOP; then
run docker compose -f "$SERVICE_DIR/docker-compose.yml" stop
fi
if [[ -d "$SERVICE_DIR/data" ]]; then
run mkdir -p "$BACKUP_DIR"
run tar --zstd -cf "$BACKUP_FILE" -C "$SERVICE_DIR" data
echo "$SERVICE: $BACKUP_FILE"
fi
if $STOP; then
run docker compose -f "$SERVICE_DIR/docker-compose.yml" start
fi
done
echo
echo "🎯 Hotovo. Zálohy v: $BACKUP_ROOT"
+178
View File
@@ -0,0 +1,178 @@
#!/usr/bin/env bash
#
# Bootstrap Ubuntu Server (22.04/24.04 LTS)
# - aktualizace systému
# - vytvoření deploy uživatele (UID 1000)
# - základní SSH hardening
# - UFW firewall
# - fail2ban
# - základní utility (curl, wget, git, vim, …)
#
# Idempotentní můžeš spustit víckrát.
#
# Použití:
# sudo bash bootstrap-ubuntu-server.sh
#
set -euo pipefail
# Barvy
readonly RED=$'\033[0;31m'
readonly GREEN=$'\033[0;32m'
readonly YELLOW=$'\033[1;33m'
readonly BLUE=$'\033[0;34m'
readonly NC=$'\033[0m'
log() { echo -e "${BLUE}[$(date +%H:%M:%S)]${NC} $*"; }
ok() { echo -e "${GREEN}${NC} $*"; }
warn() { echo -e "${YELLOW}${NC} $*" >&2; }
err() { echo -e "${RED}${NC} $*" >&2; exit 1; }
[[ $EUID -eq 0 ]] || err "Tento skript musí běžet jako root (sudo)."
DEPLOY_USER="${DEPLOY_USER:-deploy}"
DEPLOY_UID="${DEPLOY_UID:-1000}"
SSH_PORT="${SSH_PORT:-22}"
TIMEZONE="${TIMEZONE:-Europe/Prague}"
# ────────────────────────────────────────────────────────
# 1. Aktualizace systému
# ────────────────────────────────────────────────────────
log "Aktualizace systému..."
apt-get update -qq
apt-get upgrade -y -qq
apt-get dist-upgrade -y -qq
apt-get autoremove -y -qq
ok "Systém aktualizován"
# ────────────────────────────────────────────────────────
# 2. Základní utility
# ────────────────────────────────────────────────────────
log "Instalace základních utilit..."
apt-get install -y -qq \
curl wget git vim nano htop \
ca-certificates gnupg lsb-release \
software-properties-common apt-transport-https \
ufw fail2ban \
bash-completion
ok "Utility nainstalovány"
# ────────────────────────────────────────────────────────
# 3. Timezone
# ────────────────────────────────────────────────────────
log "Nastavuji timezone: $TIMEZONE"
timedatectl set-timezone "$TIMEZONE"
ok "Timezone: $(timedatectl show -p Timezone --value)"
# ────────────────────────────────────────────────────────
# 4. Deploy uživatel (UID 1000)
# ────────────────────────────────────────────────────────
if id "$DEPLOY_USER" &>/dev/null; then
ok "Uživatel $DEPLOY_USER existuje"
else
log "Vytvářím uživatele $DEPLOY_USER (UID $DEPLOY_UID)..."
adduser --uid "$DEPLOY_UID" --gecos "" --disabled-password --shell /bin/bash "$DEPLOY_USER"
passwd -d "$DEPLOY_USER" # bez hesla (key-based auth)
mkdir -p /home/"$DEPLOY_USER"/.ssh
chmod 700 /home/"$DEPLOY_USER"/.ssh
# Přidání authorized_keys během deploymentu zde necháme prázdné
touch /home/"$DEPLOY_USER"/.ssh/authorized_keys
chmod 600 /home/"$DEPLOY_USER"/.ssh/authorized_keys
chown -R "$DEPLOY_USER":"$DEPLOY_USER" /home/"$DEPLOY_USER"/.ssh
ok "Uživatel $DEPLOY_USER vytvořen"
fi
# Přidání do sudo bez hesla (volitelné, bezpečnostní)
if ! grep -q "^$DEPLOY_USER.*NOPASSWD" /etc/sudoers.d/"$DEPLOY_USER" 2>/dev/null; then
echo "$DEPLOY_USER ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/"$DEPLOY_USER"
chmod 440 /etc/sudoers.d/"$DEPLOY_USER"
ok "$DEPLOY_USER přidán do sudoers (NOPASSWD)"
fi
# ────────────────────────────────────────────────────────
# 5. SSH hardening
# ────────────────────────────────────────────────────────
log "SSH hardening..."
SSHD_CONFIG="/etc/ssh/sshd_config"
cp "$SSHD_CONFIG" "$SSHD_CONFIG.bak.$(date +%Y%m%d)" 2>/dev/null || true
# Nastavit port
sed -i "s/^#\?Port .*/Port $SSH_PORT/" "$SSHD_CONFIG"
# Zakázat root login
sed -i 's/^#\?PermitRootLogin .*/PermitRootLogin no/' "$SSHD_CONFIG"
# Zakázat password auth (jen klíče)
sed -i 's/^#\?PasswordAuthentication .*/PasswordAuthentication no/' "$SSHD_CONFIG"
# X11 forwarding vypnout
sed -i 's/^#\?X11Forwarding .*/X11Forwarding no/' "$SSHD_CONFIG"
# ClientAliveInterval
sed -i 's/^#\?ClientAliveInterval .*/ClientAliveInterval 300/' "$SSHD_CONFIG"
sed -i 's/^#\?ClientAliveCountMax .*/ClientAliveCountMax 2/' "$SSHD_CONFIG"
# Validace configu
sshd -t && systemctl restart ssh
ok "SSH nakonfigurován (port $SSH_PORT, root login zakázán)"
# ────────────────────────────────────────────────────────
# 6. UFW firewall
# ────────────────────────────────────────────────────────
log "Konfigurace UFW..."
ufw --force reset
ufw default deny incoming
ufw default allow outgoing
ufw allow "$SSH_PORT"/tcp comment "SSH"
# Služby (zakomentuj co nepoužíváš)
ufw allow 80/tcp comment "HTTP"
ufw allow 443/tcp comment "HTTPS"
# Docker swarm (volitelně)
# ufw allow 2377/tcp comment "Docker swarm"
# ufw allow 7946/tcp comment "Docker swarm gossip"
# ufw allow 7946/udp comment "Docker swarm gossip"
# ufw allow 4789/udp comment "Docker overlay"
# SSH z home (volitelně)
# ufw allow from 192.168.0.0/24 to any port $SSH_PORT proto tcp
ufw --force enable
ok "UFW aktivní, SSH povoleno"
# ────────────────────────────────────────────────────────
# 7. Fail2ban
# ────────────────────────────────────────────────────────
log "Konfigurace fail2ban..."
cat > /etc/fail2ban/jail.local <<EOF
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5
[sshd]
enabled = true
port = $SSH_PORT
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
EOF
systemctl enable --now fail2ban
systemctl restart fail2ban
ok "Fail2ban aktivní"
# ────────────────────────────────────────────────────────
# 8. Čas a DNS
# ────────────────────────────────────────────────────────
log "Konfigurace timesyncd..."
apt-get install -y -qq systemd-timesyncd
sed -i 's/^#NTP=.*/NTP=0.pool.ntp.org 1.pool.ntp.org/' /etc/systemd/timesyncd.conf
systemctl restart systemd-timesyncd
ok "Time sync aktivní"
# ────────────────────────────────────────────────────────
# Hotovo
# ────────────────────────────────────────────────────────
echo
ok "Bootstrap dokončen!"
echo
echo "📋 Další kroky:"
echo " 1. sudo bash install-docker.sh"
echo " 2. sudo bash install-portainer.sh"
echo " 3. sudo bash install-nginx-proxy.sh"
echo " 4. Přidej svůj SSH klíč: sudo -u $DEPLOY_USER bash -c 'echo \"<tvuj-klic>\" >> ~/.ssh/authorized_keys'"
echo
echo "⚠️ Před odhlášením se ujisti, že máš funkční SSH přístup jako $DEPLOY_USER!"
+51
View File
@@ -0,0 +1,51 @@
#!/usr/bin/env bash
#
# Instalace Docker + Docker Compose plugin na Ubuntu 22.04/24.04
# - oficiální Docker repo
# - non-interactive
# - post-instalace: přidá DEPLOY_USER do docker group
#
# Idempotentní.
#
set -euo pipefail
DEPLOY_USER="${DEPLOY_USER:-deploy}"
[[ $EUID -eq 0 ]] || { echo "Spusť jako root: sudo bash $0" >&2; exit 1; }
# 1. Příprava
apt-get update -qq
apt-get install -y -qq ca-certificates curl gnupg
# 2. Docker GPG klíč
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
chmod a+r /etc/apt/keyrings/docker.gpg
# 3. Docker repo
ARCH=$(dpkg --print-architecture)
CODENAME=$(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
echo "deb [arch=$ARCH signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $CODENAME stable" \
> /etc/apt/sources.list.d/docker.list
# 4. Instalace
apt-get update -qq
apt-get install -y -qq docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
# 5. Služba
systemctl enable --now docker
systemctl is-active --quiet docker && echo "✓ Docker běží"
# 6. DEPLOY_USER do docker group (bez sudo pro docker)
if id "$DEPLOY_USER" &>/dev/null; then
usermod -aG docker "$DEPLOY_USER"
echo "$DEPLOY_USER přidán do docker group (odhlas se a prihlas pro efekt)"
fi
# 7. Test
docker --version
docker compose version
echo
echo "🎉 Docker nainstalován!"
echo " Odhlas se a prihlas se jako $DEPLOY_USER pro práci bez sudo."
+64
View File
@@ -0,0 +1,64 @@
#!/usr/bin/env bash
#
# Instalace Nginx Proxy Manager (NPM) s HTTPS
# - MariaDB backend
# - Let's Encrypt ready
# - Web UI na portu 81
#
set -euo pipefail
[[ $EUID -eq 0 ]] || { echo "Spusť jako root: sudo bash $0" >&2; exit 1; }
DEPLOY_USER="${DEPLOY_USER:-deploy}"
INSTALL_DIR="/opt/nginx-proxy-manager"
MYSQL_ROOT_PASSWORD="${MYSQL_ROOT_PASSWORD:-}"
if [[ -z "$MYSQL_ROOT_PASSWORD" ]]; then
read -rsp "MySQL root heslo (pro NPM DB): " MYSQL_ROOT_PASSWORD
echo
fi
if [[ -d "$INSTALL_DIR" ]]; then
echo "✓ NPM už v $INSTALL_DIR nainstalován"
else
mkdir -p "$INSTALL_DIR/data" "$INSTALL_DIR/mysql" "$INSTALL_DIR/letsencrypt"
chown -R "$DEPLOY_USER:$DEPLOY_USER" "$INSTALL_DIR"
cat > "$INSTALL_DIR/docker-compose.yml" <<EOF
services:
nginx-proxy-manager:
image: jc21/nginx-proxy-manager:latest
container_name: nginx-proxy-manager
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "81:81"
volumes:
- ./data:/data
- ./letsencrypt:/etc/letsencrypt
environment:
- TZ=Europe/Prague
- DISABLE_IPV6=true
depends_on:
- db
db:
image: jc21/mariadb-aria:latest
container_name: npm_db
restart: unless-stopped
volumes:
- ./mysql:/var/lib/mysql
environment:
- MYSQL_ROOT_PASSWORD=$MYSQL_ROOT_PASSWORD
- TZ=Europe/Prague
EOF
cd "$INSTALL_DIR"
docker compose up -d
echo "✓ NPM nasazen"
fi
echo
echo "🌐 NPM Admin: http://$(hostname -I | awk '{print $1}'):81"
echo " Výchozí login: admin@example.com / changeme (ZMĚŇ!)"
+43
View File
@@ -0,0 +1,43 @@
#!/usr/bin/env bash
#
# Instalace Portainer CE
# - nejdřív vyžaduje Docker (spusť install-docker.sh)
# - běží jako docker compose v /opt/portainer
#
set -euo pipefail
[[ $EUID -eq 0 ]] || { echo "Spusť jako root: sudo bash $0" >&2; exit 1; }
DEPLOY_USER="${DEPLOY_USER:-deploy}"
INSTALL_DIR="/opt/portainer"
if [[ -d "$INSTALL_DIR" ]]; then
echo "✓ Portainer už v $INSTALL_DIR nainstalován"
else
mkdir -p "$INSTALL_DIR/data"
chown -R "$DEPLOY_USER:$DEPLOY_USER" "$INSTALL_DIR"
cat > "$INSTALL_DIR/docker-compose.yml" <<'EOF'
services:
portainer:
image: portainer/portainer-ce:latest
container_name: portainer
restart: unless-stopped
ports:
- "9000:9000"
- "9443:9443"
volumes:
- ./data:/data
- /var/run/docker.sock:/var/run/docker.sock
environment:
- TZ=Europe/Prague
EOF
cd "$INSTALL_DIR"
docker compose up -d
echo "✓ Portainer nasazen"
fi
echo
echo "🌐 Portainer: http://$(hostname -I | awk '{print $1}'):9000"
echo " První přihlášení: vytvoř admin účet"
+73
View File
@@ -0,0 +1,73 @@
#!/usr/bin/env bash
#
# Obnova Docker dat z NAS zálohy
# - najde nejnovější zálohu pro danou službu
# - zastaví službu, rozbalí data, nastartuje
#
# Použití:
# sudo bash restore-all.sh # obnov vše
# sudo bash restore-all.sh --only portainer # jen portainer
# sudo bash restore-all.sh --from 20240801-120000 # konkrétní záloha
# sudo bash restore-all.sh --dry-run
#
set -euo pipefail
DEPLOY_USER="${DEPLOY_USER:-deploy}"
DOCKER_HOME="/home/$DEPLOY_USER/docker"
NAS_MOUNT="${NAS_MOUNT:-/Volumes/Backups}"
BACKUP_ROOT="$NAS_MOUNT/Prace/server-backups/$(hostname)"
DRY_RUN=false
ONLY=""
FROM=""
while [[ $# -gt 0 ]]; do
case $1 in
--only) ONLY="$2"; shift 2 ;;
--from) FROM="$2"; shift 2 ;;
--dry-run) DRY_RUN=true; shift ;;
*) echo "Neznámý parametr: $1" >&2; exit 1 ;;
esac
done
[[ $EUID -eq 0 ]] || { echo "Spusť jako root: sudo bash $0" >&2; exit 1; }
run() {
if $DRY_RUN; then
echo "[DRY-RUN] $*"
else
echo "$*"
"$@"
fi
}
[[ -d "$BACKUP_ROOT" ]] || { echo "$BACKUP_ROOT neexistuje" >&2; exit 1; }
for SERVICE_DIR in "$DOCKER_HOME"/*/; do
SERVICE=$(basename "$SERVICE_DIR")
[[ -n "$ONLY" && "$SERVICE" != "$ONLY" ]] && continue
[[ -f "$SERVICE_DIR/docker-compose.yml" ]] || continue
if [[ -n "$FROM" ]]; then
BACKUP_FILE="$BACKUP_ROOT/$SERVICE/${SERVICE}-$FROM.tar.zst"
else
# Nejnovější záloha
BACKUP_FILE=$(ls -t "$BACKUP_ROOT/$SERVICE"/${SERVICE}-*.tar.zst 2>/dev/null | head -1 || true)
fi
[[ -z "$BACKUP_FILE" || ! -f "$BACKUP_FILE" ]] && { echo "$SERVICE: žádná záloha, přeskakuji"; continue; }
echo "$SERVICE: obnovuji z $BACKUP_FILE"
if ! $DRY_RUN; then
run docker compose -f "$SERVICE_DIR/docker-compose.yml" stop
run rm -rf "$SERVICE_DIR/data"
run mkdir -p "$SERVICE_DIR/data"
run tar --zstd -xf "$BACKUP_FILE" -C "$SERVICE_DIR"
run chown -R "$DEPLOY_USER:$DEPLOY_USER" "$SERVICE_DIR/data"
run docker compose -f "$SERVICE_DIR/docker-compose.yml" up -d
echo "$SERVICE obnoven"
fi
done
echo
echo "🎯 Obnova dokončena"