Altyapı Çalışması — Tailscale VPN ve Quartz Geçişi

İki fazlı büyük altyapı revizyonu: (1) VPS servislerini Tailscale VPN arkasına alma, (2) Ghost blog’u Quartz (Obsidian tabanlı SSG) ile değiştirme.

Faz 1: Tailscale Kurulumu

Amaç

VPS’teki servisleri (Coolify, n8n, SSH) public internet’ten gizleyip sadece Tailscale VPN üzerinden erişilebilir yapmak. Blog public kalacak.

Cihazlar ve IP’ler

CihazTailscale IPNot
PC100.111.5.43Windows 11
Telefon100.119.64.118Android 16
VPS (Hetzner)100.90.102.23Ubuntu 24.04
VPS Public IP37.27.25.86Sadece blog için açık

1.1 — PC ve Telefon

  • tailscale.com’dan hesap (GitHub OAuth)
  • PC: tailscale.com/download → Windows installer
  • Telefon: Play Store → Tailscale app
  • Ücretsiz plan, 3 cihaz için yeterli

1.2 — VPS Kurulumu

curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up
# Auth URL tarayıcıda onaylandı
tailscale ip -4  # 100.90.102.23

1.3 — Tailscale Üzerinden SSH Doğrulama

# PC'den (PowerShell):
ssh [email protected]

1.4 — UFW ile Public SSH Kapatma

sudo ufw allow in on tailscale0
sudo ufw delete allow OpenSSH

Sonuç UFW tablosu:

80    ALLOW  Anywhere       # Blog
443   ALLOW  Anywhere       # Blog SSL
Anywhere on tailscale0 ALLOW Anywhere  # Tailscale tam erişim

1.5 — Docker Portlarını Tailscale Arkasına Alma

Docker portları UFW’yi bypass ediyor. DOCKER-USER chain ile düzeltildi:

# Tailscale'den gelen her şeye izin
sudo iptables -I DOCKER-USER 1 -i tailscale0 -j RETURN
 
# Mevcut bağlantılar kopmasın
sudo iptables -I DOCKER-USER 2 -m conntrack --ctstate ESTABLISHED,RELATED -j RETURN
 
# Blog için 80/443 public kalsın
sudo iptables -I DOCKER-USER 3 -i eth0 -p tcp -m multiport --dports 80,443 -j RETURN
 
# Geri kalan dış trafiği engelle
sudo iptables -I DOCKER-USER 4 -i eth0 -j DROP

1.6 — iptables Kurallarını Kalıcı Yapma

Bu adımda UFW kaldırıldı, yerine iptables-persistent geldi. Aynı işi yapıyor.

sudo apt install iptables-persistent -y  # İki kez "Yes"
sudo netfilter-persistent save

Erişim Bilgileri (Tailscale Sonrası)

ServisEski ErişimYeni Erişim
Coolifypanel.egebostanci.mehttp://100.90.102.23:8000
n8nn8n.egebostanci.mehttps://n8n.egebostanci.me (auth arkasında)
SSHssh [email protected]ssh [email protected]
Blogegebostanci.meegebostanci.me (değişmedi)

n8n.egebostanci.me ve panel.egebostanci.me hâlâ Coolify proxy üzerinden açılıyor (80/443 public). Auth koruması yeterli görüldü, domain’ler bırakıldı.


Faz 2: Ghost → Quartz Blog Geçişi

Amaç

Ghost blog’u kaldırıp yerine Quartz (Obsidian tabanlı static site generator) koymak. Obsidian’dan yazıp otomatik yayınlama.

2.1 — WordPress XML’den Markdown’a Dönüştürme

Eski blogumun WordPress XML export’undan başladım: 165 yazı, 9 kategori (2012’den 2025’e uzanan kişisel arşiv).

Dönüştürme Python script ile yapıldı. Her yazı:

  • Yıl klasörüne ayrıldı (2012-2019, 2025)
  • Frontmatter eklendi (title, date, publish, categories, tags)
  • HTML → Markdown dönüştürüldü
  • Aynı tarih+isimli yazılar için numara suffix’i eklendi

2.2 — Quartz Kurulumu (VPS)

cd /opt
git clone https://github.com/jackyzha0/quartz.git
cd quartz
npm i

2.3 — Config Düzenlemesi

/opt/quartz/quartz.config.ts:

pageTitle: "Ege Bostancı"
pageTitleSuffix: ""
analytics: null
locale: "tr-TR"
baseUrl: "egebostanci.me"
defaultDateType: "created"
// CustomOgImages kaldırıldı (build hızı için)

2.4 — XML’i VPS’e Atıp Çevirme

# PC'den (PowerShell):
scp "${env:USERPROFILE}\Desktop\izadam.WordPress.2025-08-22.xml" [email protected]:/opt/quartz/export.xml
 
# VPS'te Python script ile çevrildi
# Çıktı: /opt/quartz/content/arsiv/2012-2019/ altında 165 .md dosyası

2.5 — Ana Sayfa

/opt/quartz/content/index.md:

---
title: Ege Bostancı
publish: true
---
 
Merhaba, ben Ege. Burası benim kişisel köşem.
 
## Arşiv (2012-2019)
 
Eski blogum **İşiz Adam**'dan taşınan yazılar. Lise yıllarından başlayıp uzanan, çoğu kişisel, çoğu ham.
 
→ [Arşive gözat](arsiv)

2.6 — Build ve Test

cd /opt/quartz && npx quartz build
# 166 dosya işlendi, 74 draft filtrelendi, 114 sayfa yayınlandı

2.7 — Nginx ile Serve Etme (Coolify Üzerinden)

Ghost durdurulup silindi. Coolify’da yeni Docker Compose resource oluşturuldu:

services:
  quartz:
    image: nginx:alpine
    volumes:
      - /opt/quartz-public:/usr/share/nginx/html:ro
      - /opt/quartz-serve/nginx.conf:/etc/nginx/conf.d/default.conf:ro
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost"]
      interval: 30s
      timeout: 5s
      retries: 3

Domain: https://egebostanci.me — Coolify servis settings’inden ayarlandı.

Nginx config (/opt/quartz-serve/nginx.conf):

server {
    listen 80;
    root /usr/share/nginx/html;
    index index.html;
    location / {
        try_files $uri $uri.html $uri/ =404;
    }
    error_page 404 /404.html;
}

Önemli: quartz build her seferinde public/ klasörünü silip yeniden oluşturur. Bu Docker bind mount’u kırıyor. Çözüm: ayrı /opt/quartz-public klasörü + rsync.

mkdir -p /opt/quartz-public
rsync -a --delete /opt/quartz/public/ /opt/quartz-public/
chmod -R 755 /opt/quartz-public

2.8 — Obsidian → Syncthing → VPS → Auto-Rebuild Pipeline

Syncthing zaten Obsidian vault’unu VPS’e sync ediyor:

  • Host path: /root/syncthing/data/obsidian/
  • Container path: /var/syncthing/Sync/obsidian/

Obsidian’da blog/ klasörü oluşturuldu. VPS’te symlink:

ln -s /root/syncthing/data/obsidian/blog /opt/quartz/content/blog

Auto-rebuild script (/opt/quartz/auto-rebuild.sh):

#!/bin/bash
WATCH_DIR="/root/syncthing/data/obsidian/blog"
QUARTZ_DIR="/opt/quartz"
SERVE_DIR="/opt/quartz-public"
 
echo "Blog klasörü izleniyor: $WATCH_DIR"
 
inotifywait -m -r -e modify,create,delete,moved_to "$WATCH_DIR" |
while read -r directory events filename; do
    if [[ "$filename" == *.md ]]; then
        echo "$(date): Değişiklik - $filename ($events)"
        sleep 3
        cd "$QUARTZ_DIR" && npx quartz build 2>&1 | tail -3
        rsync -a --delete "$QUARTZ_DIR/public/" "$SERVE_DIR/"
        chmod -R 755 "$SERVE_DIR"
        echo "$(date): Rebuild + sync tamamlandı"
    fi
done

Gerekli paket:

apt install inotify-tools -y

Systemd service (/etc/systemd/system/quartz-rebuild.service):

[Unit]
Description=Quartz Auto Rebuild
After=network.target
 
[Service]
Type=simple
ExecStart=/opt/quartz/auto-rebuild.sh
Restart=always
RestartSec=5
 
[Install]
WantedBy=multi-user.target

Başlatma:

systemctl daemon-reload
systemctl enable quartz-rebuild
systemctl start quartz-rebuild

Yeni Yazı Akışı

  1. Obsidian’da blog/ klasörüne .md dosyası oluştur
  2. Frontmatter ekle:
    ---
    title: "Yazı Başlığı"
    date: 2026-04-23
    publish: true
    ---
  3. Kaydet
  4. ~15 saniye bekle (Syncthing + rebuild + rsync)
  5. egebostanci.me güncellenir

Content Yapısı

/opt/quartz/content/
├── index.md                    ← Ana sayfa
├── arsiv/                      ← WordPress'ten taşınan 165 yazı
│   ├── 2012/ (16 yazı)
│   ├── 2013/ (33 yazı)
│   ├── 2014/ (32 yazı)
│   ├── 2015/ (31 yazı)
│   ├── 2016/ (24 yazı)
│   ├── 2017/ (9 yazı)
│   ├── 2018/ (3 yazı)
│   ├── 2019/ (1 yazı)
│   └── 2025/ (16 yazı)
└── blog/ → symlink → /root/syncthing/data/obsidian/blog/

Sonrasında (24 Nisan)

Bir gün sonra n8n tarafında self-healing paketi tamamlandı: Idempotency Gate, Finalizer node, Global Error Handler workflow, Postgres ledger tablosu (processed_requests). Detaylar ayrı bir yazıda: n8n İzleme Mimarisi ve Hermes Pivotu.

Aynı gün ayrıca public SSH kapatıldı (kritik düzeltme — UFW → iptables-persistent geçişinde eski delete allow OpenSSH kuralı migrate olmamış, fark edildi). Şimdi 22 portu eth0’dan DROP’lu, sadece Tailscale üzerinden erişilebilir.

Dış Port Tarama Sonucu (37.27.25.86)

PortDurumAmaç
22KapalıSSH (Tailscale-only)
80AçıkBlog HTTP
443AçıkBlog HTTPS
6001, 6002, 8000, 8080KapalıCoolify/Traefik iç portlar (DOCKER-USER bloklu)
22000, 21027KapalıSyncthing (INPUT DROP)

Önemli Dosya Yolları (VPS)

Dosya/KlasörYol
Quartz repo/opt/quartz/
Quartz config/opt/quartz/quartz.config.ts
Quartz content/opt/quartz/content/
Quartz public (build çıktısı)/opt/quartz/public/
Nginx serve klasörü/opt/quartz-public/
Nginx config/opt/quartz-serve/nginx.conf
Auto-rebuild script/opt/quartz/auto-rebuild.sh
Systemd service/etc/systemd/system/quartz-rebuild.service
Obsidian vault (Syncthing)/root/syncthing/data/obsidian/
Blog symlink/opt/quartz/content/blog → /root/syncthing/data/obsidian/blog/
iptables kuralları/etc/iptables/rules.v4

Faydalı Komutlar

# Quartz manual rebuild
cd /opt/quartz && npx quartz build && rsync -a --delete /opt/quartz/public/ /opt/quartz-public/ && chmod -R 755 /opt/quartz-public
 
# Auto-rebuild servis durumu
systemctl status quartz-rebuild
 
# Auto-rebuild logları
journalctl -u quartz-rebuild -f
 
# Tailscale durumu
tailscale status
 
# Docker container'ları
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
 
# iptables kuralları
sudo iptables -L DOCKER-USER -n -v

İlgili yazılar