refactor: Centralizacja konfiguracji do pliku autoscript.conf

1 Przeprowadzono refaktoryzację sposobu konfiguracji skryptu, aby uprościć wdrożenie i zarządzanie ustawieniami.
    2
    3 Kluczowe zmiany:
    4 - Zastąpiono zmienne środowiskowe dedykowanym plikiem konfiguracyjnym `autoscript.conf`.
    5 - Dodano szablon `autoscript.conf.example` z objaśnieniami wszystkich zmiennych.
    6 - Zaktualizowano skrypt `start.sh`, aby wczytywał konfigurację z nowego pliku.
    7 - Zaktualizowano dokumentację `README.md`, aby odzwierciedlała nowy proces.
    8 - Dodano plik `CHANGELOG.md` do śledzenia zmian w projekcie.
    9
   10 Ta zmiana znacząco poprawia użyteczność skryptu, czyniąc go łatwiejszym do skonfigurowania i wdrożenia przez użytkownika.
This commit is contained in:
Paweł Orzech 2025-08-03 12:27:12 +02:00
parent 8f44f1b892
commit 2d3dac11dd
4 changed files with 736 additions and 60 deletions

14
CHANGELOG.md Normal file
View file

@ -0,0 +1,14 @@
# Changelog
## 2025-08-03
### Dodano
- **Plik `CHANGELOG.md`**: Dodano dziennik zmian w celu śledzenia rozwoju projektu.
- **Plik `autoscript.conf.example`**: Stworzono szablon konfiguracyjny, aby ułatwić użytkownikom wdrożenie.
### Zmieniono
- **Refaktoryzacja Konfiguracji**: Całkowicie zmieniono sposób konfiguracji skryptu. Zamiast polegać na zmiennych środowiskowych, skrypt wczytuje teraz wszystkie ustawienia z dedykowanego pliku `autoscript.conf`. To upraszcza zarządzanie i zmniejsza ryzyko błędu.
- **Skrypt `start.sh`**: Zaktualizowano logikę skryptu, aby wczytywał konfigurację z pliku `autoscript.conf` i sprawdzał jego obecność przed uruchomieniem.
- **Dokumentacja `README.md`**: gruntownie zaktualizowano dokumentację, aby odzwierciedlała nowy proces konfiguracji oparty na pliku. Instrukcje są teraz prostsze i bardziej przejrzyste.

View file

@ -33,57 +33,38 @@ Zanim uruchomisz skrypt, upewnij się, że posiadasz:
1. **Nowy serwer** z systemem operacyjnym Debian lub Ubuntu. 1. **Nowy serwer** z systemem operacyjnym Debian lub Ubuntu.
2. **Dostęp do konta `root`** na tym serwerze. 2. **Dostęp do konta `root`** na tym serwerze.
3. **Domenę internetową** zarządzaną przez **Cloudflare**. 3. **Domenę internetową** zarządzaną przez **Cloudflare**.
4. **Klucz API Cloudflare** z uprawnieniami do edycji strefy DNS (`DNS:Edit`). Możesz go wygenerować w panelu Cloudflare: `My Profile > API Tokens > Create Token`. 4. **Klucz API Cloudflare** z uprawnieniami do edycji strefy DNS (`DNS:Edit`).
5. **Publiczny klucz SSH** (np. zawartość pliku `~/.ssh/id_ed25519.pub`), który zostanie użyty do autoryzacji nowego użytkownika `admin`. 5. **Publiczny klucz SSH** (np. zawartość pliku `~/.ssh/id_ed25519.pub`).
## 4. Konfiguracja ## 4. Konfiguracja
Skrypt jest konfigurowany za pomocą zmiennych środowiskowych. Konfiguracja skryptu odbywa się w całości poprzez plik `autoscript.conf`.
### Zmienne Wymagane 1. **Utwórz plik konfiguracyjny:**
Skopiuj szablon `autoscript.conf.example` do nowego pliku o nazwie `autoscript.conf`.
```bash
cp autoscript.conf.example autoscript.conf
```
Musisz ustawić te zmienne przed uruchomieniem skryptu: 2. **Wypełnij plik `autoscript.conf`:**
Otwórz plik `autoscript.conf` w edytorze tekstu i uzupełnij wszystkie zmienne zgodnie z komentarzami. Szczególną uwagę zwróć na sekcje `WYMAGANE`.
- `PUBLIC_KEY`: Twój publiczny klucz SSH. - `PUBLIC_KEY`: Twój publiczny klucz SSH.
```bash - `CF_DNS_API_TOKEN`: Twój token API od Cloudflare.
# Przykład - `PRIMARY_DOMAIN`: Twoja główna domena.
export PUBLIC_KEY='ssh-ed25519 AAAA... twoja_nazwa@twoj_komputer' - `ADMIN_EMAIL`: Twój adres e-mail.
``` - ...i inne ustawienia, takie jak dane do serwera SMTP czy wersje oprogramowania.
- `CF_DNS_API_TOKEN`: Twój token API od Cloudflare.
```bash
# Przykład
export CF_DNS_API_TOKEN='AbCdEfGhIjKlMnOpQrStUvWxYz1234567890'
```
### Zmienne Opcjonalne
Możesz dostosować działanie skryptu, ustawiając poniższe zmienne (mają one wartości domyślne):
- `PRIMARY_DOMAIN`: Twoja główna domena (np. `mojadomena.com`). Domyślnie: `orzech.me`.
- `ADMIN_EMAIL`: Adres e-mail do powiadomień (np. od Let's Encrypt, Alertmanager). Domyślnie: `admin@orzech.me`.
- `TIMEZONE`: Strefa czasowa serwera. Domyślnie: `Europe/Warsaw`.
- `ALERT_SMTP_...`: Zmienne do konfiguracji serwera SMTP do wysyłania alertów.
- `*_VER`: Wersje obrazów Docker (np. `TRAEFIK_VER`). Pozwala na przypięcie konkretnych wersji oprogramowania.
## 5. Użycie ## 5. Użycie
1. Sklonuj to repozytorium na swój lokalny komputer lub bezpośrednio na serwer. 1. Sklonuj to repozytorium na swój serwer.
2. Przejdź do folderu projektu: `cd autoscript`. 2. Przejdź do folderu projektu: `cd autoscript`.
3. Ustaw **wymagane** zmienne środowiskowe (patrz punkt 4). 3. Utwórz i wypełnij plik konfiguracyjny `autoscript.conf` (zgodnie z punktem 4).
```bash 4. Uruchom skrypt z uprawnieniami `root`.
export PUBLIC_KEY='...'
export CF_DNS_API_TOKEN='...'
```
4. Opcjonalnie ustaw inne zmienne, aby nadpisać wartości domyślne.
```bash
export PRIMARY_DOMAIN='twojadomena.pl'
export ADMIN_EMAIL='admin@twojadomena.pl'
```
5. Uruchom skrypt z uprawnieniami `root`.
```bash ```bash
sudo ./start.sh sudo ./start.sh
``` ```
6. Skrypt wykona wszystkie kroki automatycznie. Proces może potrwać kilka minut. 5. Skrypt automatycznie wczyta konfigurację z pliku i rozpocznie instalację.
## 6. Co robić po zakończeniu skryptu? ## 6. Co robić po zakończeniu skryptu?

81
autoscript.conf.example Normal file
View file

@ -0,0 +1,81 @@
# ===================================================================
# Konfiguracja AutoScript
# ===================================================================
#
# Instrukcja:
# 1. Skopiuj ten plik do `autoscript.conf`
# cp autoscript.conf.example autoscript.conf
# 2. Wypełnij poniższe zmienne.
#
# ===================================================================
# -------------------------------------------------------------------
# SEKCJA WYMAGANA
# -------------------------------------------------------------------
# Twój publiczny klucz SSH, który zostanie użyty do logowania na użytkownika 'admin'.
# Przykład: PUBLIC_KEY='ssh-ed25519 AAAA... user@host'
PUBLIC_KEY=''
# Token API z Cloudflare z uprawnieniami do edycji strefy DNS (DNS:Edit).
CF_DNS_API_TOKEN=''
# -------------------------------------------------------------------
# USTAWIENIA GŁÓWNE
# -------------------------------------------------------------------
# Twoja główna domena zarządzana przez Cloudflare.
# Przykład: PRIMARY_DOMAIN='example.com'
PRIMARY_DOMAIN='orzech.me'
# Adres e-mail używany do powiadomień od Let's Encrypt oraz alertów systemowych.
# Przykład: ADMIN_EMAIL='admin@example.com'
ADMIN_EMAIL='admin@orzech.me'
# Strefa czasowa serwera.
# Pełna lista: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
# Przykład: TIMEZONE='Europe/London'
TIMEZONE='Europe/Warsaw'
# -------------------------------------------------------------------
# USTAWIENIA POWIADOMIEŃ (SMTP)
# -------------------------------------------------------------------
# Ta sekcja konfiguruje serwer e-mail do wysyłania alertów z monitoringu.
# Adres hosta i port serwera SMTP.
# Przykład: ALERT_SMTP_HOST='smtp.example.com:587'
ALERT_SMTP_HOST='mail.drap.ovh:587'
# Nazwa użytkownika do logowania SMTP.
# Przykład: ALERT_SMTP_USER='alerts@example.com'
ALERT_SMTP_USER='alerts@drap.ovh'
# Hasło do logowania SMTP.
# Jeśli pozostawisz puste, zostanie wygenerowane losowe i zapisane bezpiecznie w sops.
ALERT_SMTP_PASS=''
# Adres e-mail, z którego będą wysyłane alerty (pole 'From').
# Przykład: ALERT_SMTP_FROM='server-alerts@example.com'
ALERT_SMTP_FROM='alerts@drap.ovh'
# -------------------------------------------------------------------
# USTAWIENIA USŁUG
# -------------------------------------------------------------------
# Hasło administratora dla Grafany.
# Jeśli pozostawisz puste, zostanie wygenerowane losowe i zapisane bezpiecznie w sops.
GRAFANA_ADMIN_PASSWORD=''
# -------------------------------------------------------------------
# WERSJE OBRAZÓW DOCKER
# -------------------------------------------------------------------
# Dobrą praktyką jest używanie konkretnych wersji oprogramowania dla stabilności.
# Używaj 'latest' z rozwagą.
TRAEFIK_VER='v3.0'
PROMETHEUS_VER='latest'
NODE_EXPORTER_VER='latest'
CADVISOR_VER='latest'
BLACKBOX_VER='latest'
GRAFANA_VER='latest'
ALERTMANAGER_VER='latest'

644
start.sh
View file

@ -1,33 +1,633 @@
```bash ```bash
#!/usr/bin/env bash #!/usr/bin/env bash
set -euo pipefail set -euo pipefail
IFS=$'\n\t' IFS=
log(){ printf '\n[%s] %s\n' "$(date -u +'%Y-%m-%dT%H:%M:%SZ')" "$*"; }
ensure_root(){ [[ $EUID -eq 0 ]] || { echo "Run as root"; exit 1; }; }
install_node_and_gemini(){
log "Node.js LTS + gemini-cli"
curl -fsSL https://deb.nodesource.com/setup_lts.x | bash -
apt-get update
apt-get install -y nodejs build-essential
npm install -g @google/gemini-cli
}
install_base_tools(){
log "Base packages, sops, age"
apt-get update
DEBIAN_FRONTEND=noninteractive apt-get install -y \
openssh-server ufw jq curl wget dnsutils nmap git make \
sops age aide logwatch auditd unattended-upgrades \
crowdsec crowdsec-firewall-bouncer-nftables
systemctl enable ssh
}
bootstrap_sops(){
log "Bootstrap sops + age"
mkdir -p /root/.config/sops/age
if [[ ! -f /root/.config/sops/age/keys.txt ]]; then
age-keygen -o /root/.config/sops/age/keys.txt >/dev/null
fi
AGE_PUB=$(grep -m1 'public key:' /root/.config/sops/age/keys.txt | awk '{print $4}')
install -d -m 700 /opt/services
cat > /opt/services/.sops.yaml <<EOF
creation_rules:
- path_regex: secrets/.*\\.sops$
age: ["$AGE_PUB"]
EOF
}
create_admin_user(){
log "Admin user, sudo, TOTP"
groupadd -f admin
id -u admin >/dev/null 2>&1 || useradd -m -s /bin/bash -G admin,sudo admin
install -d -m 700 -o admin -g admin /home/admin/.ssh
printf '%s\n' "$PUBLIC_KEY" > /home/admin/.ssh/authorized_keys
chown admin:admin /home/admin/.ssh/authorized_keys
chmod 600 /home/admin/.ssh/authorized_keys
DEBIAN_FRONTEND=noninteractive apt-get install -y libpam-google-authenticator
sed -i '/pam_google_authenticator/d' /etc/pam.d/sudo
sed -i '1iauth required pam_google_authenticator.so nullok' /etc/pam.d/sudo
echo "%admin ALL=(ALL) ALL" > /etc/sudoers.d/90-admin
chmod 440 /etc/sudoers.d/90-admin
sudo -u admin google-authenticator -t -d -f -r 3 -R 30 -W
}
harden_ssh(){
log "SSH hardening"
ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N "" -q
ssh-keygen -t rsa -b 4096 -f /etc/ssh/ssh_host_rsa_key -N "" -q
rm -f /etc/ssh/ssh_host_dsa_key* /etc/ssh/ssh_host_ecdsa_key*
SSH_PORT=$(shuf -i 10000-65535 -n 1)
echo "$SSH_PORT" > /root/ssh_port.txt
install -d /etc/ssh/sshd_config.d
cat > /etc/ssh/sshd_config.d/01-hardening.conf <<EOF
Port $SSH_PORT
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
X11Forwarding no
AllowTcpForwarding no
Banner /etc/ssh/banner
MaxAuthTries 3
ClientAliveInterval 300
ClientAliveCountMax 2
AllowGroups admin sudo
EOF
cat > /etc/ssh/banner <<'EOF'
******************************************************************
* Authorized users only. Activity is monitored and logged. *
******************************************************************
EOF
systemctl restart ssh
}
configure_firewall(){
log "UFW + DOCKER-USER"
SSH_PORT=$(cat /root/ssh_port.txt)
ufw --force reset
ufw default deny incoming
ufw default allow outgoing
ufw allow "$SSH_PORT"/tcp
ufw allow 80,443/tcp
ufw allow 25,465,587,110,995,143,993,4190/tcp
ufw allow 41641/udp
ufw allow from 127.0.0.0/8 to any port 53 proto udp
ufw --force enable
# DOCKER-USER chain lockdown
iptables -C DOCKER-USER -j RETURN 2>/dev/null || {
iptables -I DOCKER-USER -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
iptables -I DOCKER-USER -p tcp -m multiport --dports 80,443,25,465,587,110,995,143,993,4190 -j ACCEPT
iptables -I DOCKER-USER -p tcp --dport "$SSH_PORT" -j ACCEPT
iptables -A DOCKER-USER -j DROP
}
}
system_baseline(){
log "Kernel, journald, AIDE, unattended-upgrades"
cat > /etc/sysctl.d/99-security.conf <<'EOF'
net.ipv4.ip_forward = 0
net.ipv6.conf.all.forwarding = 0
net.ipv4.conf.all.rp_filter = 1
net.ipv4.tcp_syncookies = 1
net.ipv4.conf.all.log_martians = 1
net.ipv4.icmp_echo_ignore_broadcasts = 1
net.ipv4.icmp_ignore_bogus_error_responses = 1
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr
EOF
sysctl --system
aideinit && mv /var/lib/aide/aide.db.new /var/lib/aide/aide.db
printf 'Unattended-Upgrade::Automatic-Reboot "false";\n' > /etc/apt/apt.conf.d/50unattended-upgrades
printf 'Unattended-Upgrade::Mail "%s";\n' "$ADMIN_EMAIL" >> /etc/apt/apt.conf.d/50unattended-upgrades
install -d /etc/systemd/journald.conf.d
cat > /etc/systemd/journald.conf.d/99-storage.conf <<'EOF'
[Journal]
Storage=persistent
SystemMaxUse=1G
SystemMaxFileSize=128M
MaxRetentionSec=1month
EOF
systemctl restart systemd-journald
}
install_docker(){
log "Docker Engine + compose plugin"
curl -fsSL https://get.docker.com | sh
usermod -aG docker admin
mkdir -p /etc/docker
cat > /etc/docker/daemon.json <<'EOF'
{
"live-restore": true,
"userns-remap": "default",
"log-driver": "json-file",
"log-opts": { "max-size": "10m", "max-file": "3" },
"default-address-pools": [{ "base": "10.88.0.0/16", "size": 24 }],
"experimental": false
}
EOF
systemctl enable docker
systemctl restart docker
}
#-------------------------------#
# Secrets (sops managed) #
#-------------------------------#
generate_random(){
# urlsafe, no newline
head -c 48 /dev/urandom | base64 | tr -d '\n' | tr '/+' 'AB'
}
write_sops_file_env(){
# args: path key=value lines (plaintext via heredoc), encrypt in place
local path="$1"; shift
install -d "$(dirname "$path")"
umask 077
cat > "$path" <<EOF
$*
EOF
sops -e -i "$path"
}
write_sops_file_blob(){
# args: path content
local path="$1"; shift
install -d "$(dirname "$path")"
umask 077
printf '%s' "$*" > "$path"
sops -e -i "$path"
}
prepare_secrets(){
log "Prepare encrypted secrets with sops"
# Traefik env
write_sops_file_env /opt/services/traefik/secrets/traefik.env.sops \
"CF_DNS_API_TOKEN=$CF_DNS_API_TOKEN
ADMIN_EMAIL=$ADMIN_EMAIL
PRIMARY_DOMAIN=$PRIMARY_DOMAIN
TZ=$TIMEZONE"
# Monitoring env (Grafana admin password auto if missing)
: "${GRAFANA_ADMIN_PASSWORD:=$(generate_random)}"
write_sops_file_env /opt/services/monitoring/secrets/monitoring.env.sops \
"GRAFANA_ADMIN_PASSWORD=$GRAFANA_ADMIN_PASSWORD
TZ=$TIMEZONE"
# Alertmanager SMTP password as file secret
: "${ALERT_SMTP_PASS:=$(generate_random)}"
write_sops_file_blob /opt/services/monitoring/alertmanager/secrets/smtp_pass.sops "$ALERT_SMTP_PASS"
# scrub process env copies
unset CF_DNS_API_TOKEN GRAFANA_ADMIN_PASSWORD ALERT_SMTP_PASS
}
#-------------------------------#
# Traefik #
#-------------------------------#
deploy_traefik(){
log "Traefik"
install -d /opt/services/traefik/{data,config,dynamic,secrets}
install -m 600 /dev/null /opt/services/traefik/data/acme.json || true
( docker network create traefik_proxy >/dev/null 2>&1 || true )
cat > /opt/services/traefik/config/traefik.yml <<'EOF'
api: { dashboard: true }
ping: {}
entryPoints:
web:
address: ":80"
http: { redirections: { entryPoint: { to: websecure, scheme: https } } }
websecure:
address: ":443"
providers:
file: { directory: "/etc/traefik/dynamic", watch: true }
docker:
exposedByDefault: false
network: traefik_proxy
certificatesResolvers:
le-dns:
acme:
email: ${ADMIN_EMAIL}
storage: /acme/acme.json
dnsChallenge: { provider: cloudflare, delayBeforeCheck: 0 }
metrics: { prometheus: { addEntryPointsLabels: true, addServicesLabels: true } }
log: { level: INFO, format: json }
accessLog: { format: json }
EOF
cat > /opt/services/traefik/dynamic/middlewares.yml <<'EOF'
http:
middlewares:
security-headers:
headers:
stsSeconds: 63072000
stsIncludeSubdomains: true
stsPreload: true
contentTypeNosniff: true
referrerPolicy: "strict-origin-when-cross-origin"
frameDeny: true
browserXssFilter: true
gzip: { compress: {} }
rate-limit:
rateLimit: { burst: 100, average: 50 }
EOF
cat > /opt/services/traefik/dynamic/tls.yml <<'EOF'
tls:
options:
default:
minVersion: VersionTLS12
sniStrict: true
EOF
cat > /opt/services/traefik/versions.env <<EOF
TRAEFIK_VER=$TRAEFIK_VER
ADMIN_EMAIL=$ADMIN_EMAIL
EOF
cat > /opt/services/traefik/docker-compose.yml <<'EOF'
services:
traefik:
image: traefik:${TRAEFIK_VER}
container_name: traefik
restart: unless-stopped
networks: [traefik_proxy]
ports: ["80:80","443:443"]
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./config/traefik.yml:/etc/traefik/traefik.yml:ro
- ./dynamic:/etc/traefik/dynamic:ro
- ./data/acme.json:/acme/acme.json
environment:
CF_DNS_API_TOKEN: ${CF_DNS_API_TOKEN}
security_opt: [no-new-privileges:true]
read_only: true
tmpfs: ["/tmp:size=64m"]
cap_drop: [ALL]
cap_add: [NET_BIND_SERVICE]
healthcheck:
test: ["CMD","wget","--no-verbose","--tries=1","--spider","http://localhost:8080/ping"]
interval: 30s
timeout: 3s
retries: 5
networks:
traefik_proxy:
external: true
EOF
# sops - secure launch (no plaintext .env on disk)
( cd /opt/services/traefik && \
sops exec-file --no-fifo ./secrets/traefik.env.sops \
'docker compose --env-file ./versions.env --env-file {} up -d' )
}
#-------------------------------#
# Monitoring stack #
#-------------------------------#
deploy_monitoring(){
log "Monitoring: Prometheus, Grafana, Alertmanager, exporters"
install -d /opt/services/monitoring/{prometheus/rules,grafana/provisioning/{datasources,dashboards},blackbox,alertmanager/secrets,secrets}
# Prometheus config
cat > /opt/services/monitoring/prometheus/prometheus.yml <<'EOF'
global:
scrape_interval: 15s
evaluation_interval: 15s
rule_files:
- "/etc/prometheus/rules/*.yml"
alerting:
alertmanagers:
- static_configs: [{ targets: ['alertmanager:9093'] }]
scrape_configs:
- job_name: prometheus
static_configs: [{ targets: ['localhost:9090'] }]
- job_name: node-exporter
static_configs: [{ targets: ['node-exporter:9100'] }]
- job_name: cadvisor
static_configs: [{ targets: ['cadvisor:8080'] }]
- job_name: traefik
static_configs: [{ targets: ['traefik:8080'] }]
- job_name: blackbox
metrics_path: /probe
params: { module: [http_2xx] }
static_configs:
- targets:
- https://forum.yeswas.pl
- https://social.ovh
- https://rss.social.ovh
- https://pawelorzech.pl
- https://dash.orzech.me
- https://run.orzech.me
relabel_configs:
- source_labels: [__address__]
target_label: __param_target
- source_labels: [__param_target]
target_label: instance
- target_label: __address__
replacement: blackbox-exporter:9115
EOF
cat > /opt/services/monitoring/prometheus/rules/alerts.yml <<'EOF'
groups:
- name: general.rules
rules:
- alert: InstanceDown
expr: up == 0
for: 2m
labels: { severity: critical }
annotations:
summary: "Instance {{ $labels.instance }} down"
description: "{{ $labels.instance }} of job {{ $labels.job }} down >2m"
- alert: HighCPUUsage
expr: 100 - (avg by(instance)(rate(node_cpu_seconds_total{mode="idle"}[5m]))*100) > 80
for: 5m
labels: { severity: warning }
annotations: { summary: "High CPU {{ $labels.instance }}" }
- alert: HighMemoryUsage
expr: (node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes)/node_memory_MemTotal_bytes*100 > 90
for: 5m
labels: { severity: warning }
annotations: { summary: "High memory {{ $labels.instance }}" }
- alert: DiskSpaceLow
expr: (node_filesystem_avail_bytes{fstype!~"tmpfs|overlay"}/node_filesystem_size_bytes) < 0.1
for: 10m
labels: { severity: warning }
annotations: { summary: "Low disk {{ $labels.instance }}" }
- alert: CertificateExpiration
expr: probe_ssl_earliest_cert_expiry - time() < 604800
for: 0m
labels: { severity: warning }
annotations: { summary: "Cert expires soon {{ $labels.instance }}" }
EOF
# Blackbox config
cat > /opt/services/monitoring/blackbox/blackbox.yml <<'EOF'
modules:
http_2xx:
prober: http
timeout: 5s
http:
valid_http_versions: ["HTTP/1.1","HTTP/2.0"]
valid_status_codes: [200,301,302]
method: GET
follow_redirects: true
tls_config: { insecure_skip_verify: false }
EOF
# Alertmanager config with file-based password
cat > /opt/services/monitoring/alertmanager/alertmanager.yml <<EOF
global:
smtp_smarthost: '$ALERT_SMTP_HOST'
smtp_from: '$ALERT_SMTP_FROM'
smtp_auth_username: '$ALERT_SMTP_USER'
smtp_auth_password_file: '/etc/alertmanager/secrets/smtp_pass'
route:
group_by: ['alertname']
group_wait: 10s
group_interval: 10s
repeat_interval: 1h
receiver: 'email'
receivers:
- name: 'email'
email_configs:
- to: '$ADMIN_EMAIL'
subject: 'Alert: {{ "{{" }} .GroupLabels.alertname {{ "}}" }}'
send_resolved: true
EOF
# Grafana provisioning
cat > /opt/services/monitoring/grafana/provisioning/datasources/prometheus.yml <<'EOF'
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
access: proxy
url: http://prometheus:9090
isDefault: true
editable: false
EOF
# Versions file
cat > /opt/services/monitoring/versions.env <<EOF
PROMETHEUS_VER=$PROMETHEUS_VER
NODE_EXPORTER_VER=$NODE_EXPORTER_VER
CADVISOR_VER=$CADVISOR_VER
BLACKBOX_VER=$BLACKBOX_VER
GRAFANA_VER=$GRAFANA_VER
ALERTMANAGER_VER=$ALERTMANAGER_VER
EOF
# Compose
cat > /opt/services/monitoring/docker-compose.yml <<'EOF'
networks:
monitoring:
traefik_proxy:
external: true
services:
prometheus:
image: prom/prometheus:${PROMETHEUS_VER}
container_name: prometheus
restart: unless-stopped
networks: [monitoring, traefik_proxy]
volumes:
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
- ./prometheus/rules:/etc/prometheus/rules:ro
- prometheus_data:/prometheus
command:
- --config.file=/etc/prometheus/prometheus.yml
- --storage.tsdb.path=/prometheus
- --storage.tsdb.retention.time=30d
- --web.enable-lifecycle
- --web.external-url=https://prometheus.${PRIMARY_DOMAIN}
security_opt: [no-new-privileges:true]
read_only: true
tmpfs: ["/tmp:size=64m"]
cap_drop: [ALL]
healthcheck:
test: ["CMD","wget","-qO-","http://localhost:9090/-/healthy"]
interval: 30s
timeout: 3s
retries: 5
labels:
- traefik.enable=true
- traefik.http.routers.prom.rule=Host(`prometheus.${PRIMARY_DOMAIN}`)
- traefik.http.routers.prom.entrypoints=websecure
- traefik.http.routers.prom.tls.certresolver=le-dns
- traefik.http.routers.prom.middlewares=security-headers@file
node-exporter:
image: prom/node-exporter:${NODE_EXPORTER_VER}
container_name: node-exporter
restart: unless-stopped
networks: [monitoring]
pid: host
volumes:
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /:/rootfs:ro
command:
- --path.procfs=/host/proc
- --path.rootfs=/rootfs
- --path.sysfs=/host/sys
- --collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)
security_opt: [no-new-privileges:true]
read_only: true
cap_drop: [ALL]
cadvisor:
image: gcr.io/cadvisor/cadvisor:${CADVISOR_VER}
container_name: cadvisor
restart: unless-stopped
networks: [monitoring]
volumes:
- /:/rootfs:ro
- /var/run:/var/run:rw
- /sys:/sys:ro
- /var/lib/docker:/var/lib/docker:ro
privileged: true
devices: ["/dev/kmsg:/dev/kmsg"]
blackbox-exporter:
image: prom/blackbox-exporter:${BLACKBOX_VER}
container_name: blackbox-exporter
restart: unless-stopped
networks: [monitoring]
volumes:
- ./blackbox/blackbox.yml:/etc/blackbox_exporter/config.yml:ro
security_opt: [no-new-privileges:true]
read_only: true
tmpfs: ["/tmp:size=64m"]
cap_drop: [ALL]
alertmanager:
image: prom/alertmanager:${ALERTMANAGER_VER}
container_name: alertmanager
restart: unless-stopped
networks: [monitoring, traefik_proxy]
volumes:
- ./alertmanager/alertmanager.yml:/etc/alertmanager/alertmanager.yml:ro
- alertmanager_data:/alertmanager
- ${ALERT_SMTP_PASS_PATH}:/etc/alertmanager/secrets/smtp_pass:ro
command:
- --config.file=/etc/alertmanager/alertmanager.yml
- --storage.path=/alertmanager
- --web.external-url=https://alertmanager.${PRIMARY_DOMAIN}
security_opt: [no-new-privileges:true]
read_only: true
tmpfs: ["/tmp:size=64m"]
cap_drop: [ALL]
healthcheck:
test: ["CMD","wget","-qO-","http://localhost:9093/-/healthy"]
interval: 30s
timeout: 3s
retries: 5
labels:
- traefik.enable=true
- traefik.http.routers.alert.rule=Host(`alertmanager.${PRIMARY_DOMAIN}`)
- traefik.http.routers.alert.entrypoints=websecure
- traefik.http.routers.alert.tls.certresolver=le-dns
- traefik.http.routers.alert.middlewares=security-headers@file
grafana:
image: grafana/grafana:${GRAFANA_VER}
container_name: grafana
user: "472"
restart: unless-stopped
networks: [monitoring, traefik_proxy]
volumes:
- grafana_data:/var/lib/grafana
- ./grafana/provisioning:/etc/grafana/provisioning:ro
environment:
GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_ADMIN_PASSWORD}
GF_USERS_ALLOW_SIGN_UP: "false"
GF_SERVER_ROOT_URL: https://grafana.${PRIMARY_DOMAIN}
security_opt: [no-new-privileges:true]
cap_drop: [ALL]
healthcheck:
test: ["CMD","wget","-qO-","http://localhost:3000/api/health"]
interval: 30s
timeout: 3s
retries: 5
labels:
- traefik.enable=true
- traefik.http.routers.grafana.rule=Host(`grafana.${PRIMARY_DOMAIN}`)
- traefik.http.routers.grafana.entrypoints=websecure
- traefik.http.routers.grafana.tls.certresolver=le-dns
- traefik.http.routers.grafana.middlewares=security-headers@file
volumes:
prometheus_data:
grafana_data:
alertmanager_data:
EOF
# secure launch: 1) create temp file for smtp pass, 2) inject env via monitoring.env.sops
(
cd /opt/services/monitoring
sops exec-file --no-fifo ./alertmanager/secrets/smtp_pass.sops \
'ALERT_SMTP_PASS_PATH={} sops exec-file --no-fifo ./secrets/monitoring.env.sops \
"docker compose --env-file ./versions.env --env-file {} up -d"'
)
}
main(){
ensure_root
timedatectl set-timezone "$TIMEZONE"
install_node_and_gemini
install_base_tools
bootstrap_sops
create_admin_user
harden_ssh
configure_firewall
system_baseline
install_docker
prepare_secrets
deploy_traefik
deploy_monitoring
log "Base ready - secrets via sops, no plaintext .env, Grafana no fallback password, Alertmanager password via file."
}
main "$@"
```
'
#=============================# #=============================#
# REQUIRED ENV (export first) # # CONFIGURATION #
#=============================# #=============================#
: "${PUBLIC_KEY:?export PUBLIC_KEY='ssh-ed25519 AAAA... user@host'}" # Sprawdź, czy plik konfiguracyjny istnieje i go wczytaj
: "${CF_DNS_API_TOKEN:?export CF_DNS_API_TOKEN='cf_dns_edit_token'}" if [[ ! -f autoscript.conf ]]; then
echo "Błąd: Plik konfiguracyjny 'autoscript.conf' nie został znaleziony."
echo "Skopiuj 'autoscript.conf.example' do 'autoscript.conf' i uzupełnij go."
exit 1
fi
source autoscript.conf
# Sprawdź, czy wymagane zmienne są ustawione
: "${PUBLIC_KEY:?Ustaw PUBLIC_KEY w pliku autoscript.conf}"
: "${CF_DNS_API_TOKEN:?Ustaw CF_DNS_API_TOKEN w pliku autoscript.conf}"
#=============================# #=============================#
# CONSTANTS # # HELPER FUNCTIONS #
#=============================#
TIMEZONE="Europe/Warsaw"
ADMIN_EMAIL="${ADMIN_EMAIL:-admin@orzech.me}"
ALERT_SMTP_USER="${ALERT_SMTP_USER:-alerts@drap.ovh}"
ALERT_SMTP_HOST="${ALERT_SMTP_HOST:-mail.drap.ovh:587}"
ALERT_SMTP_FROM="${ALERT_SMTP_FROM:-alerts@drap.ovh}"
PRIMARY_DOMAIN="${PRIMARY_DOMAIN:-orzech.me}"
# Image versions (central place; adjust as needed)
TRAEFIK_VER="${TRAEFIK_VER:-v3.0}"
PROMETHEUS_VER="${PROMETHEUS_VER:-latest}"
NODE_EXPORTER_VER="${NODE_EXPORTER_VER:-latest}"
CADVISOR_VER="${CADVISOR_VER:-latest}"
BLACKBOX_VER="${BLACKBOX_VER:-latest}"
GRAFANA_VER="${GRAFANA_VER:-latest}"
ALERTMANAGER_VER="${ALERTMANAGER_VER:-latest}"
#=============================# #=============================#
log(){ printf '\n[%s] %s\n' "$(date -u +'%Y-%m-%dT%H:%M:%SZ')" "$*"; } log(){ printf '\n[%s] %s\n' "$(date -u +'%Y-%m-%dT%H:%M:%SZ')" "$*"; }
ensure_root(){ [[ $EUID -eq 0 ]] || { echo "Run as root"; exit 1; }; } ensure_root(){ [[ $EUID -eq 0 ]] || { echo "Run as root"; exit 1; }; }