ThinkRoot

bine ați navigat pe insula mea de pe internet

Am mutat serviciile self-hosted de pe Linux Mint pe un Intel NUC

De ceva timp rulam mai multe servicii self-hosted direct pe calculatorul meu cu Linux Mint. Funcționa, dar cu un dezavantaj major: dacă stingeam calculatorul, se opreau și serviciile. Nu tocmai ideal când vrei să accesezi FreshRSS sau Wallabag de pe telefon la miezul nopții.

Soluția evidentă era un server dedicat care să ruleze non-stop. Am ales un Intel NUC cu procesor Celeron J4025 și 4GB RAM - modest, dar perfect pentru uz personal. Consum mic de energie, fără zgomot, stă ascuns undeva pe un raft. Pe el am instalat Ubuntu Server 24.04.

Acest articol documentează tot procesul: de la instalarea Docker până la Tailscale, Caddy și Vaultwarden.

Starea inițială - ce rulam pe Mint

Pe Linux Mint aveam organizate serviciile în ~/.services/, fiecare cu propriul docker-compose.yml:

Serviciu Port Descriere
Domain Locker 3000 Monitorizare domenii
FreshRSS 8080 Agregator RSS
Linkding 9090 Manager de bookmark-uri
Booktracker 2341 Jurnal de lectură
Wallabag 6500 Read-it-later

Toate cu restart: unless-stopped și volume Docker gestionate de engine.

Pasul 1 - Instalarea Docker pe NUC

Mă conectez la NUC prin SSH și instalez Docker din repository-ul oficial:

# Actualizează lista de pachete și instalează dependințele
sudo apt update
sudo apt install -y ca-certificates curl gnupg

# Adaugă cheia GPG oficială Docker
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

# Adaugă repository-ul Docker
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# Instalează Docker Engine + Compose plugin
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Adaug userul meu în grupul docker ca să nu fie nevoie de sudo la fiecare comandă:

sudo usermod -aG docker $USER

Mă deconectez și reconectez prin SSH, apoi verific:

docker --version
docker compose version
docker run hello-world

Output-ul Hello from Docker! confirmă că totul e în regulă.

Pasul 2 - Structura de directoare

Pe un server, convenția e diferită față de un desktop. În loc de ~/.services/ (director ascuns în home), am ales /opt/services/ - standard Linux pentru aplicații third-party, accesibil logic, separat de fișierele personale.

Pe NUC:

sudo mkdir -p /opt/services/{domain-locker,freshrss,linkding,booktracker,wallabag}
sudo chown -R thinkroot:thinkroot /opt/services

Pasul 3 - Copierea fișierelor Compose

De pe Mint, copiez fișierele docker-compose.yml pe NUC via SCP:

scp ~/.services/domain-locker/docker-compose.yml thinkroot@192.168.1.x:/opt/services/domain-locker/
scp ~/.services/freshrss/docker-compose.yml thinkroot@192.168.1.x:/opt/services/freshrss/
scp ~/.services/linkding/docker-compose.yml thinkroot@192.168.1.x:/opt/services/linkding/
scp ~/.services/booktracker/docker-compose.yml thinkroot@192.168.1.x:/opt/services/booktracker/
scp ~/.services/wallabag/docker-compose.yml thinkroot@192.168.1.x:/opt/services/wallabag/

Pasul 4 - Exportul volumelor de pe Mint

Datele efective (feed-urile RSS, bookmark-urile, cărțile) trăiesc în volume Docker. Le export pe fiecare ca arhivă .tar.gz:

# Pe Mint - export volume
docker run --rm \
  -v freshrss_data:/data \
  -v $HOME:/backup \
  alpine tar czf /backup/freshrss_data.tar.gz -C /data .

docker run --rm \
  -v freshrss_extensions:/data \
  -v $HOME:/backup \
  alpine tar czf /backup/freshrss_extensions.tar.gz -C /data .

docker run --rm \
  -v linkding_data:/data \
  -v $HOME:/backup \
  alpine tar czf /backup/linkding_data.tar.gz -C /data .

docker run --rm \
  -v wallabag_data:/data \
  -v $HOME:/backup \
  alpine tar czf /backup/wallabag_data.tar.gz -C /data .

docker run --rm \
  -v wallabag_images:/data \
  -v $HOME:/backup \
  alpine tar czf /backup/wallabag_images.tar.gz -C /data .

Booktracker stochează datele direct în directorul serviciului (bind mount), nu în volume Docker, deci e suficient să copiez directorul data/:

scp -r ~/.services/booktracker/data thinkroot@192.168.1.x:/opt/services/booktracker/

Transfer arhivele pe NUC:

scp ~/*.tar.gz thinkroot@192.168.1.x:~/

Pasul 5 - Importul volumelor pe NUC

Pornesc întâi serviciile o dată pentru ca Docker să creeze volumele corecte, apoi le opresc și import datele:

# Pe NUC - pornire inițială pentru creare volume
cd /opt/services/freshrss && docker compose up -d
cd /opt/services/linkding && docker compose up -d
cd /opt/services/wallabag && docker compose up -d

# Oprire înainte de import
docker compose -f /opt/services/freshrss/docker-compose.yml down
docker compose -f /opt/services/linkding/docker-compose.yml down
docker compose -f /opt/services/wallabag/docker-compose.yml down

O mică lecție de Docker: numele volumelor create de Compose au prefix dublu (ex. freshrss_freshrss_data), nu simplu (freshrss_data). Verific cu:

docker volume ls | grep -E "freshrss|linkding|wallabag"

Import datele în volumele corecte:

docker run --rm \
  -v freshrss_freshrss_data:/data \
  -v $HOME:/backup \
  alpine sh -c "cd /data && tar xzf /backup/freshrss_data.tar.gz"

docker run --rm \
  -v freshrss_freshrss_extensions:/data \
  -v $HOME:/backup \
  alpine sh -c "cd /data && tar xzf /backup/freshrss_extensions.tar.gz"

docker run --rm \
  -v linkding_linkding_data:/data \
  -v $HOME:/backup \
  alpine sh -c "cd /data && tar xzf /backup/linkding_data.tar.gz"

docker run --rm \
  -v wallabag_wallabag_data:/data \
  -v $HOME:/backup \
  alpine sh -c "cd /data && tar xzf /backup/wallabag_data.tar.gz"

docker run --rm \
  -v wallabag_wallabag_images:/data \
  -v $HOME:/backup \
  alpine sh -c "cd /data && tar xzf /backup/wallabag_images.tar.gz"

Pasul 6 - Pornirea tuturor serviciilor

cd /opt/services/domain-locker && docker compose up -d
cd /opt/services/freshrss && docker compose up -d
cd /opt/services/linkding && docker compose up -d
cd /opt/services/booktracker && docker compose up -d
cd /opt/services/wallabag && docker compose up -d

Verific că totul rulează:

docker ps

Toate containerele apar cu status Up. Serviciile sunt accesibile la 192.168.1.x:PORT din rețeaua locală.

Pasul 7 - Curățarea de pe Mint

Odată confirmat că totul merge pe NUC, șterg totul de pe Mint:

# Oprire containere + ștergere volume
docker compose -f ~/.services/domain-locker/docker-compose.yml down -v
docker compose -f ~/.services/freshrss/docker-compose.yml down -v
docker compose -f ~/.services/linkding/docker-compose.yml down -v
docker compose -f ~/.services/booktracker/docker-compose.yml down -v
docker compose -f ~/.services/wallabag/docker-compose.yml down -v

# Ștergere directoare și arhive temporare
rm -rf ~/.services
rm ~/*.tar.gz

# Dezinstalare Docker
sudo apt purge -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo rm -rf /var/lib/docker /var/lib/containerd
sudo rm /etc/apt/sources.list.d/docker.list
sudo rm /etc/apt/keyrings/docker.gpg
sudo apt autoremove -y

Pasul 8 - Tailscale: acces de oriunde

Serviciile rulează, dar sunt accesibile doar în rețeaua locală. Vreau să le accesez și când sunt în deplasare, fără să expun porturile pe internet.

Tailscale rezolvă asta elegant - creează un VPN mesh privat între dispozitivele mele, cu MagicDNS și certificate SSL automate.

Instalare pe NUC:

curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up

Se generează un link de autentificare - îl deschid în browser, mă autentific cu contul Tailscale. NUC-ul apare în dashboard cu hostname-ul thinkserver.

Activez HTTPS în Tailscale Admin Console (Settings → HTTPS Certificates), apoi generez certificatele pe NUC:

sudo tailscale cert thinkserver.tailxxxx.ts.net

Aceasta scrie două fișiere în directorul curent:

Mut certificatele într-un loc logic:

sudo mkdir -p /opt/services/caddy/certs
sudo mv thinkserver.tailxxxx.ts.net.crt /opt/services/caddy/certs/
sudo mv thinkserver.tailxxxx.ts.net.key /opt/services/caddy/certs/
sudo chown -R thinkroot:thinkroot /opt/services/caddy

Instalez Tailscale și pe celelalte dispozitive (Linux Mint, telefon) cu același cont.

Pasul 9 - Caddy: reverse proxy cu HTTPS

Vreau să accesez serviciile prin HTTPS la un singur domeniu, nu prin portul 8080 sau 9090. Instalez Caddy ca reverse proxy.

/opt/services/caddy/Caddyfile:

thinkserver.tailxxxx.ts.net {
    tls /certs/thinkserver.tailxxxx.ts.net.crt /certs/thinkserver.tailxxxx.ts.net.key

    # Vaultwarden
    reverse_proxy vaultwarden:80
}

/opt/services/caddy/docker-compose.yml:

services:
  caddy:
    image: caddy:latest
    container_name: caddy
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - ./certs:/certs
    networks:
      - vaultwarden_default

networks:
  vaultwarden_default:
    external: true
cd /opt/services/caddy && docker compose up -d

Pasul 10 - Vaultwarden: manager de parole self-hosted

Trecerea de la Bitwarden cloud la Vaultwarden (implementare open-source compatibilă 100% cu clienții Bitwarden) era pe lista mea de ceva timp. Acum că am serverul și HTTPS-ul la locul lui, nu mai am scuze.

/opt/services/vaultwarden/docker-compose.yml:

services:
  vaultwarden:
    image: vaultwarden/server:latest
    container_name: vaultwarden
    restart: unless-stopped
    ports:
      - "8222:80"
    volumes:
      - vaultwarden_data:/data
    environment:
      DOMAIN: "https://thinkserver.tailxxxx.ts.net"
      SIGNUPS_ALLOWED: "true"

volumes:
  vaultwarden_data:
cd /opt/services/vaultwarden && docker compose up -d

Accesez https://thinkserver.tailxxxx.ts.net, creez contul, import parolele din Bitwarden (export CSV → import în Vaultwarden). Instalez extensia de browser și aplicația mobilă, configurate cu serverul propriu.

După ce am verificat că totul e importat corect, dezactivez înregistrările noi din Vaultwarden:

environment:
  SIGNUPS_ALLOWED: "false"
docker compose up -d --force-recreate

O perioadă am ținut ambele conturi (Bitwarden cloud + Vaultwarden) active în paralel, ca perioadă de tranziție.

Rezultatul final

Structura completă a serverului:

/opt/services/
├── caddy/
│   ├── docker-compose.yml
│   ├── Caddyfile
│   └── certs/
├── domain-locker/
│   └── docker-compose.yml
├── freshrss/
│   └── docker-compose.yml
├── linkding/
│   └── docker-compose.yml
├── booktracker/
│   ├── docker-compose.yml
│   └── data/
├── wallabag/
│   └── docker-compose.yml
└── vaultwarden/
    └── docker-compose.yml

Servicii care rulează acum non-stop pe NUC, accesibile din orice loc prin Tailscale (nu toate, dar pe viitor așa o să fie):

Serviciu Acces
Domain Locker 192.168.1.x:3000 (local)
FreshRSS 192.168.1.x:8080 (local)
Linkding 192.168.1.x:9090 (local)
Booktracker 192.168.1.x:2341 (local)
Wallabag 192.168.1.x:6500 (local)
Vaultwarden https://thinkserver.tailxxxx.ts.net (Tailscale)

⬅ The one before
Dimineața în care am vrut să arunc placa video pe geam

Up next ➡
Am instalat Scrutiny și ambele disk-uri au apărut ca Failed