サーバのバックアップ用に同期ツール「Syncthing」導入

rsyncやrcloneは、どかっとまとめてバックアップするのには適していますが、データベースファイルは別に除外し、本当に大事な画像ファイルや、テキストファイルなどをバックアップするだけなら、Syncthingなどの同期ツールのほうが適していそうです。

負荷や性質の違い

項目Syncthing (inotify監視)rsync (差分スキャン)
平常時 (待機中):メモリを常時消費し、OSのファイルシステム監視枠(inotify)を占有。ゼロ:実行していない間はリソースを一切使いません。
変更発生時:変更されたファイルのみを即座に処理するため、突発的な負荷が少ない。:全ファイルをなめて変更箇所を探すため、実行の瞬間にCPU/ディスクI/Oが跳ねる。
スキャン負荷初回のみ重い:起動時にハッシュ計算を行うが、後は監視メイン。毎回重い:ファイル数が多いほど、実行時のインデックス作成に時間がかかる。

別サーバをもう1台用意出来るのであれば、LAN内でサーバ内のデータをSyncthingでリアルタイム同期し、定期的にrcloneにクラウド保存するのが良さそうです。そして、たまにデータベースも含めたバックアップを行うというのがベストではないでしょうか。

インストール

以下をコピペして貼り付ければインストール完了です。

#!/bin/bash
# =============================================================================
# Syncthing ホスト直インストールスクリプト
# 使い方: bash install-syncthing.sh  (sudo不要)
# =============================================================================

set -euo pipefail

RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC='\033[0m'
info()    { echo -e "${BLUE}[INFO]${NC}  $*"; }
success() { echo -e "${GREEN}[OK]${NC}    $*"; }
warn()    { echo -e "${YELLOW}[WARN]${NC}  $*"; }
error()   { echo -e "${RED}[ERROR]${NC} $*"; exit 1; }

# ── 実行ユーザー確認 ───────────────────────────────────────
[[ $EUID -eq 0 ]] && error "このスクリプトは一般ユーザーで実行してください(sudo不要)"
CURRENT_USER=$(whoami)

# ── config.xml パスを解決する関数 ─────────────────────────
find_config() {
    local candidates=(
        "$HOME/.local/state/syncthing/config.xml"
        "$HOME/.local/share/syncthing/config.xml"
        "$HOME/.config/syncthing/config.xml"
    )
    for p in "${candidates[@]}"; do
        [[ -f "$p" ]] && echo "$p" && return
    done
    # 見つからなければ find で探す
    find "$HOME" -name "config.xml" -path "*/syncthing/*" 2>/dev/null | head -1
}

# ── apt リポジトリ追加 & インストール ─────────────────────
info "Syncthing 公式リポジトリを追加します..."
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://syncthing.net/release-key.gpg \
    | sudo tee /etc/apt/keyrings/syncthing-archive-keyring.gpg > /dev/null
echo "deb [signed-by=/etc/apt/keyrings/syncthing-archive-keyring.gpg] https://apt.syncthing.net/ syncthing stable" \
    | sudo tee /etc/apt/sources.list.d/syncthing.list > /dev/null
sudo apt-get update -q || true   # 無関係なリポジトリのエラーは無視
sudo apt-get install -y syncthing || error "Syncthing のインストールに失敗しました。"
success "Syncthing をインストールしました。"

# ── systemd サービス登録 ───────────────────────────────────
info "systemd サービスを登録します(ユーザー: $CURRENT_USER)..."
sudo systemctl enable syncthing@$CURRENT_USER
sudo systemctl start syncthing@$CURRENT_USER
success "Syncthing サービスを起動しました。"

# ── config.xml 生成待機(最大60秒)────────────────────────
info "config.xml の生成を待機中..."
CONFIG_FILE=""
for i in $(seq 1 30); do
    CONFIG_FILE=$(find_config)
    [[ -n "$CONFIG_FILE" ]] && break
    sleep 2
done
[[ -z "$CONFIG_FILE" ]] && error "config.xml が見つかりませんでした。\n  → find \$HOME -name config.xml -path '*/syncthing/*' で確認してください。"
success "config.xml を検出しました: $CONFIG_FILE"

# ── GUI をリモートアクセス可能に変更 ──────────────────────
info "GUI をリモートアクセス可能に設定します..."
sudo systemctl stop syncthing@$CURRENT_USER

python3 - "$CONFIG_FILE" << 'PYEOF'
import xml.etree.ElementTree as ET, sys
tree = ET.parse(sys.argv[1])
root = tree.getroot()
gui = root.find('gui')

def set_or_create(parent, tag, text):
    el = parent.find(tag)
    if el is None:
        el = ET.SubElement(parent, tag)
    el.text = text

set_or_create(gui, 'address', '0.0.0.0:8384')
tree.write(sys.argv[1], encoding='unicode', xml_declaration=True)
print("GUIアドレスを 0.0.0.0:8384 に変更しました。")
PYEOF

sudo systemctl start syncthing@$CURRENT_USER
sleep 3

# ── Device ID 取得 ─────────────────────────────────────────
DEVICE_ID=$(syncthing --device-id 2>/dev/null || echo "(GUIの「情報」から確認してください)")
HOST_IP=$(hostname -I | awk '{print $1}')

# ── 完了メッセージ ─────────────────────────────────────────
echo ""
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${GREEN}  ✅ Syncthing インストール完了!${NC}"
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo ""
echo -e "  📡 Web UI        : ${BLUE}http://$HOST_IP:8384${NC}"
echo -e "  📁 設定ファイル  : $CONFIG_FILE"
echo -e "  👤 実行ユーザー  : $CURRENT_USER"
echo ""
echo -e "  🖥️  Device ID(バックアップPCへの接続時に使用):"
echo -e "  ${YELLOW}$DEVICE_ID${NC}"
echo ""
echo -e "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "  📌 次のステップ:"
echo -e "  1. Web UI にアクセスしてパスワードを設定"
echo -e "     (右上メニュー → Settings → GUI → GUI Authentication)"
echo -e "  2. フォルダを追加(ホストの絶対パスをそのまま入力)"
echo -e "     例: /opt/docker/immich/library/library/admin"
echo -e "         /opt/docker/notediscovery/data"
echo -e "         /opt/docker/nextcloud/data/data/user/files"
echo -e "  3. 各フォルダのタイプを ${YELLOW}Send Only${NC} に設定"
echo -e "  4. バックアップPCと Device ID を交換して接続"
echo -e "  5. バックアップPC側を ${YELLOW}Receive Only${NC} + ${YELLOW}階段状バージョニング${NC} に設定"
echo -e "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo ""

# ── UFW 案内 ───────────────────────────────────────────────
if command -v ufw &>/dev/null && sudo ufw status | grep -q "Status: active"; then
    echo -e "${YELLOW}🔥 UFW が有効です。以下でポートを開放してください:${NC}"
    echo ""
    echo "  sudo ufw allow 8384/tcp   # Web UI"
    echo "  sudo ufw allow 22000/tcp  # Sync (TCP)"
    echo "  sudo ufw allow 22000/udp  # Sync (QUIC)"
    echo "  sudo ufw allow 21027/udp  # ローカル探索"
    echo ""
fi

Webから設定出来ます。

バックアップ先のPCでも導入

バックアップ先となるPCでも同様にSyncthingを導入します。macやWindowsでも対応しているのがポイントですね。クラウドの専用ツールを利用するなら、Windows上で行うと簡単そうです。その後、お互いのDevice IDをGUIの「デバイスを追加」で登録し、同期フォルダを設定するだけです。片方で指定すれば、少し待つともう一方に許可確認のメッセージが表示されます。
そして、immichやNextcloud のデータディレクトリをそのままフォルダとして追加できます。

同期設定のポイント

サーバ側(送信元)

フォルダタイプ:「送信のみ(Send Only)」

  • サーバのファイルをバックアップPCへ一方向に同期
  • バックアップPC側で誤って削除・変更してもサーバ側には影響しない
  • GUIで各フォルダの編集 → 「フォルダタイプ」を Send Only に設定するだけ

バックアップPC側(受信・保管)

フォルダタイプ:「受信のみ(Receive Only)」

  • サーバからの変更のみ受け付け、手動で触っても上書きされない安全設計

バージョニング(世代管理)は3種類から選択:

種類動作向いている用途
ゴミ箱(Trash Can)削除・上書き前ファイルを .stversions/ に移動、日数で自動削除シンプルに直近N日分だけ保持したい
簡易(Simple)世代数を指定(例:5世代)して古いものから自動削除バージョン数を固定したい
階段状(Staggered)直近は細かく・古いほど間引いて長期保存(最大365日など)バックアップ用途に最もおすすめ

階段状バージョニングの保持間隔(デフォルト)

1時間以内    → 毎変更を保持
1日以内      → 1時間ごと
30日以内     → 1日ごと
365日以内    → 1週間ごと
最大保存期間 → 任意設定(例:365日)

推奨構成まとめ

サーバ側                     バックアップPC側
┌─────────────────┐          ┌──────────────────────────┐
│ フォルダタイプ    │  ──→     │ フォルダタイプ             │
│ Send Only       │  同期     │ Receive Only            │
│                 │          │                         │
│ immich data     │          │ バージョニング:階段状      │
│ nextcloud data  │          │ 最大保存期間:365日        │
│ NoteDiscovery   │          │ 保存先:.stversions/      │
└─────────────────┘          └──────────────────────────┘

注意点

  • バックアップPC側で Receive Only にしていても、サーバでファイルを削除すると同期されて消える。完全に残したい場合はバージョニングが唯一の保険になります
  • immichは内部DBも含めてバックアップするなら、immich側で定期的に pg_dump して、そのダンプファイルをSyncthingで同期するのが安全です

アプリの特性に合わせた設定など

それぞれのアプリの特性に合わせた除外設定を解説します。

Immich

同期するパス:

/opt/docker/immich/library/library/admin

Immichのlibrary配下は年/月/日のディレクトリ構造でオリジナルファイルが格納されています。サムネイル・エンコード済み動画・機械学習キャッシュは別ディレクトリなのでパスを絞るだけでOKです。

/opt/docker/immich/library/
├── library/admin/      ← ✅ ここだけ同期(オリジナル写真・動画)
├── thumbs/             ← ❌ サムネイル(再生成可能)
├── encoded-video/      ← ❌ トランスコード済み動画(再生成可能)
├── profile/            ← ❌ プロフィール画像
└── upload/             ← ❌ アップロード一時領域

Syncthing の除外パターン(フォルダ編集 → 「無視するパターン」):

(?d).trash
*.tmp
*.part
.DS_Store

/opt/docker/immich/library/library/admin をルートにすれば、それ以外のthumbsなどはそもそもパスに含まれないので除外設定はほぼ不要です。

NoteDiscovery

同期するパス:

/opt/docker/notediscovery/data
/opt/docker/notediscovery/data/
├── notes/          ← ✅ ノート本体
├── attachments/    ← ✅ 添付ファイル
├── *.db            ← ⚠️ SQLiteはロック競合に注意(後述)
└── *.db-wal        ← ❌ WALファイルは除外推奨
    *.db-shm        ← ❌ 共有メモリファイルは除外推奨

除外パターン:

*.db-wal
*.db-shm
*.db-journal
*.tmp
*.part
.DS_Store
(?d).trash

⚠️ SQLiteの注意点: アプリ稼働中は .db ファイルが書き込みロックされます。Syncthingは読み取れても中途半端な状態でコピーされる可能性があるため、DBのバックアップは別途 sqlite3 .backup コマンドや定期cronで dump してから同期するのが安全です。


Nextcloud

同期するパス:

/opt/docker/nextcloud/data/data/user/files
/opt/docker/nextcloud/data/data/user/
├── files/              ← ✅ ユーザーファイル本体
├── files_trashbin/     ← ❌ ゴミ箱
├── files_versions/     ← ❌ Nextcloud自身のバージョン履歴(Syncthing側で世代管理するので不要)
├── cache/              ← ❌ キャッシュ
└── thumbnails/         ← ❌ サムネイル

/files だけをフォルダとして追加すれば他は含まれませんが、files 配下に .ocdata などが混入する場合があるので除外パターンを設定:

.ocdata
.htaccess
.ncdata
*.tmp
*.part
uploads.ini
(?d).trash
.DS_Store

もし権限のエラーが出るようなら下記を実行。

# Syncthingを実行しているユーザーをdockerグループ経由で読めるように
sudo chmod o+rx /opt/docker/nextcloud
sudo chmod o+rx /opt/docker/nextcloud/data
sudo chmod o+rx /opt/docker/nextcloud/data/data
sudo chmod o+rx /opt/docker/nextcloud/data/data/user
sudo chmod -R o+rX /opt/docker/nextcloud/data/data/user/files
sudo chmod o+w /opt/docker/nextcloud/data/data/user/files

まとめ表

アプリ同期パス主な除外
Immich.../library/adminパス絞り込みで基本不要
NoteDiscovery.../notediscovery/data*.db-wal *.db-shm
Nextcloud.../files.ocdata *.part

共通の除外パターン(全フォルダに設定推奨)

(?d).trash
*.tmp
*.part
.DS_Store
~$*

(?d) は「そのディレクトリごと除外」するSyncthing独自の記法です。

おまけ1 – Dockerでインストール

Docker環境でインストールすることも出来ます。その場合、マウントするフォルダを設定するなどの必要があるかもしれません。まずフォルダを作成し、その中にセットアップ用のスクリプトファイルとdocker-compose.ymlを作成します。貼り付ける内容はそれぞれ下記になります。

sudo mkdir /opt/opt/docker/syncthing/ -p
cd /opt/opt/docker/syncthing/
sudo nano setup.sh
sudo nano docker-compose.yml

setup.sh

#!/bin/bash
# =============================================================================
# Syncthing セットアップスクリプト
# 使い方: sudo bash setup.sh
# =============================================================================

set -euo pipefail

INSTALL_DIR="/opt/docker/syncthing"
COMPOSE_FILE="$INSTALL_DIR/docker-compose.yml"

# ── カラー定義 ─────────────────────────────────────────────
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'

info()    { echo -e "${BLUE}[INFO]${NC}  $*"; }
success() { echo -e "${GREEN}[OK]${NC}    $*"; }
warn()    { echo -e "${YELLOW}[WARN]${NC}  $*"; }
error()   { echo -e "${RED}[ERROR]${NC} $*"; exit 1; }

# ── root チェック ──────────────────────────────────────────
[[ $EUID -ne 0 ]] && error "このスクリプトは sudo または root で実行してください。"

# ── インストールディレクトリ作成 ───────────────────────────
info "インストールディレクトリを作成: $INSTALL_DIR"
mkdir -p "$INSTALL_DIR"/{config,data}

# ── docker-compose.yml をコピー ────────────────────────────
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
if [[ -f "$SCRIPT_DIR/docker-compose.yml" ]]; then
    cp "$SCRIPT_DIR/docker-compose.yml" "$COMPOSE_FILE"
    success "docker-compose.yml をコピーしました。"
else
    error "docker-compose.yml が見つかりません: $SCRIPT_DIR/docker-compose.yml"
fi

# ── パーミッション設定 ─────────────────────────────────────
PUID=1000
PGID=1000
chown -R "$PUID:$PGID" "$INSTALL_DIR/config" "$INSTALL_DIR/data" || true
success "ディレクトリのパーミッションを設定しました。"

# ── コンテナ起動(設定ファイル生成のため一時起動)─────────
info "Syncthing コンテナを一時起動して設定ファイルを生成します..."
cd "$INSTALL_DIR"
docker compose up -d

# 設定ファイルが生成されるまで待機
info "設定ファイルの生成を待機中..."
for i in $(seq 1 30); do
    if [[ -f "$INSTALL_DIR/config/config.xml" ]]; then
        success "設定ファイルを検出しました。"
        break
    fi
    sleep 2
    if [[ $i -eq 30 ]]; then
        error "設定ファイルの生成がタイムアウトしました。ログを確認してください: docker logs syncthing"
    fi
done

# ── GUI パスワードの設定 ───────────────────────────────────
echo ""
echo -e "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${YELLOW}  Syncthing GUI パスワード設定${NC}"
echo -e "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
read -rp "GUIのユーザー名を入力してください [デフォルト: admin]: " GUI_USER
GUI_USER="${GUI_USER:-admin}"

while true; do
    read -rsp "GUIのパスワードを入力してください: " GUI_PASS
    echo ""
    read -rsp "パスワードを再入力してください: " GUI_PASS2
    echo ""
    [[ "$GUI_PASS" == "$GUI_PASS2" ]] && break
    warn "パスワードが一致しません。再入力してください。"
done

# bcrypt ハッシュ生成(Python3 を使用)
if command -v python3 &>/dev/null && python3 -c "import bcrypt" 2>/dev/null; then
    HASHED_PASS=$(python3 -c "
import bcrypt
password = b'${GUI_PASS}'
hashed = bcrypt.hashpw(password, bcrypt.gensalt())
print(hashed.decode())
")
else
    info "bcrypt (Python) が見つかりません。pip でインストールします..."
    pip3 install bcrypt -q 2>/dev/null || apt-get install -y python3-bcrypt -q 2>/dev/null || true
    HASHED_PASS=$(python3 -c "
import bcrypt
password = b'${GUI_PASS}'
hashed = bcrypt.hashpw(password, bcrypt.gensalt())
print(hashed.decode())
" 2>/dev/null) || true
fi

# config.xml に認証情報を書き込む
CONFIG_FILE="$INSTALL_DIR/config/config.xml"

if [[ -n "${HASHED_PASS:-}" ]]; then
    # GUI アドレスを 0.0.0.0 にバインドし、認証を設定
    python3 - <<PYEOF
import xml.etree.ElementTree as ET
import sys

tree = ET.parse('$CONFIG_FILE')
root = tree.getroot()

gui = root.find('gui')
if gui is None:
    sys.exit(1)

# アドレス設定
addr = gui.find('address')
if addr is None:
    addr = ET.SubElement(gui, 'address')
addr.text = '0.0.0.0:8384'

# ユーザー名
user = gui.find('user')
if user is None:
    user = ET.SubElement(gui, 'user')
user.text = '$GUI_USER'

# ハッシュ済みパスワード
passwd = gui.find('password')
if passwd is None:
    passwd = ET.SubElement(gui, 'password')
passwd.text = '$HASHED_PASS'

# 認証モード
authmode = gui.find('authMode')
if authmode is None:
    authmode = ET.SubElement(gui, 'authMode')
authmode.text = 'static'

tree.write('$CONFIG_FILE', encoding='unicode', xml_declaration=True)
print("設定ファイルを更新しました。")
PYEOF
    success "GUI認証情報を設定しました(ユーザー: $GUI_USER)"
else
    warn "bcrypt が利用できなかったため、パスワードは後でGUI上で設定してください。"
fi

# ── コンテナ再起動 ─────────────────────────────────────────
info "設定を反映するためコンテナを再起動します..."
docker compose restart syncthing
sleep 3

# ── Device ID の取得と表示 ─────────────────────────────────
echo ""
info "Device ID を取得中..."
sleep 5
DEVICE_ID=$(docker exec syncthing syncthing --device-id 2>/dev/null || \
            docker logs syncthing 2>&1 | grep -oP 'myID=\K[A-Z0-9-]{63}' | tail -1 || \
            echo "(取得できませんでした。起動後にGUIで確認してください)")

# ── 完了メッセージ ─────────────────────────────────────────
HOST_IP=$(hostname -I | awk '{print $1}')
echo ""
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${GREEN}  ✅ Syncthing セットアップ完了!${NC}"
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo ""
echo -e "  📡 Web UI:       ${BLUE}http://$HOST_IP:8384${NC}"
echo -e "  👤 ユーザー名:   ${YELLOW}$GUI_USER${NC}"
echo -e "  🔑 パスワード:   (設定済み)"
echo -e "  📁 設定ディレクトリ: ${INSTALL_DIR}/config"
echo -e "  📁 データディレクトリ: ${INSTALL_DIR}/data"
echo ""
echo -e "  🖥️  Device ID:"
echo -e "  ${YELLOW}$DEVICE_ID${NC}"
echo ""
echo -e "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "  📌 次のステップ:"
echo -e "  1. Web UI にアクセスして動作を確認"
echo -e "  2. バックアップ先PC にも Syncthing を導入"
echo -e "  3. お互いの Device ID を「デバイスを追加」で登録"
echo -e "  4. 同期したいフォルダを「フォルダを追加」で設定"
echo -e "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo ""

# ── ファイアウォール案内 ───────────────────────────────────
if command -v ufw &>/dev/null && ufw status | grep -q "Status: active"; then
    echo -e "${YELLOW}🔥 UFW が有効です。以下のコマンドでポートを開放してください:${NC}"
    echo ""
    echo "  sudo ufw allow 8384/tcp   # Web UI"
    echo "  sudo ufw allow 22000/tcp  # Sync (TCP)"
    echo "  sudo ufw allow 22000/udp  # Sync (QUIC)"
    echo "  sudo ufw allow 21027/udp  # ローカル探索"
    echo ""
fi

docker-compose.yml

services:
  syncthing:
    image: syncthing/syncthing:latest
    container_name: syncthing
    hostname: syncthing-server
    restart: unless-stopped
    environment:
      - PUID=1000
      - PGID=1000
    volumes:
      - ./config:/var/syncthing/config
      - ./data:/var/syncthing
    ports:
      - "8384:8384"   # Web UI
      - "22000:22000/tcp"  # Sync protocol (TCP)
      - "22000:22000/udp"  # Sync protocol (QUIC)
      - "21027:21027/udp"  # Local discovery
    networks:
      - syncthing_net
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://localhost:8384/rest/noauth/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 30s

networks:
  syncthing_net:
    driver: bridge

作成が終了したらセットアップを実行します。

sudo bash setup.sh

セットアップの内容

setup.sh が自動で以下をすべて処理します:

  1. ディレクトリ作成config/data/ を自動生成
  2. コンテナ起動docker compose up -d で初回起動し、config.xml を自動生成
  3. GUI認証設定 — ユーザー名とパスワードを対話入力 → bcrypt ハッシュ化して config.xml に自己書き込み(シークレットキーの手動生成は不要)
  4. 再起動 & Device ID 表示 — 設定反映後、接続用 Device ID をターミナルに表示

ポート構成

ポート用途
8384Web UI
22000/tcpデバイス間同期(TCP)
22000/udpデバイス間同期(QUIC)
21027/udpLAN内自動探索

UFW が有効な場合は、スクリプト完了時に開放コマンドも案内されます。

マウント箇所を変更したり、rootで動かしたりするには。

sudo nano /opt/docker/syncthing/docker-compose.yml
services:
  syncthing:
    image: syncthing/syncthing:latest
    container_name: syncthing
    hostname: syncthing-server
    restart: unless-stopped
    user: root        # ← この行を追加
    environment:
      - PUID=0
      - PGID=0
    volumes:
      - ./config:/var/syncthing/config
      - ./data:/var/syncthing
      - /opt/docker/immich/library/library/admin:/sync/immich:ro    # ← ro=読み取り専用
      - /opt/docker/notediscovery/data:/sync/notediscovery:ro
      - /opt/docker/nextcloud/data/data/user/files:/sync/nextcloud:ro
    ports:
      - "8384:8384"
      - "22000:22000/tcp"
      - "22000:22000/udp"
      - "21027:21027/udp"
    networks:
      - syncthing_net

networks:
  syncthing_net:
    driver: bridge

おまけ2 – コピペ1回に

nano install.sh
sudo bash install.sh

install.sh

#!/bin/bash
# =============================================================================
# Syncthing ワンライナーインストールスクリプト
# 使い方: sudo bash install.sh
# =============================================================================

set -euo pipefail

INSTALL_DIR="/opt/docker/syncthing"

# ── カラー定義 ─────────────────────────────────────────────
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC='\033[0m'
info()    { echo -e "${BLUE}[INFO]${NC}  $*"; }
success() { echo -e "${GREEN}[OK]${NC}    $*"; }
warn()    { echo -e "${YELLOW}[WARN]${NC}  $*"; }
error()   { echo -e "${RED}[ERROR]${NC} $*"; exit 1; }

# ── root チェック ──────────────────────────────────────────
[[ $EUID -ne 0 ]] && error "sudo または root で実行してください。"

# ── ディレクトリ作成 ───────────────────────────────────────
info "ディレクトリを作成: $INSTALL_DIR"
mkdir -p "$INSTALL_DIR"/{config,data}

# ── docker-compose.yml を生成 ──────────────────────────────
info "docker-compose.yml を生成中..."
cat > "$INSTALL_DIR/docker-compose.yml" << 'EOF'
services:
  syncthing:
    image: syncthing/syncthing:latest
    container_name: syncthing
    hostname: syncthing-server
    restart: unless-stopped
    environment:
      - PUID=1000
      - PGID=1000
    volumes:
      - ./config:/var/syncthing/config
      - ./data:/var/syncthing
    ports:
      - "8384:8384"        # Web UI
      - "22000:22000/tcp"  # Sync protocol (TCP)
      - "22000:22000/udp"  # Sync protocol (QUIC)
      - "21027:21027/udp"  # Local discovery
    networks:
      - syncthing_net
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://localhost:8384/rest/noauth/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 30s

networks:
  syncthing_net:
    driver: bridge
EOF
success "docker-compose.yml を生成しました。"

# ── パーミッション設定 ─────────────────────────────────────
chown -R 1000:1000 "$INSTALL_DIR/config" "$INSTALL_DIR/data" || true

# ── コンテナ起動(config.xml 生成のため)─────────────────
info "Syncthing を起動して設定ファイルを生成します..."
cd "$INSTALL_DIR"
docker compose up -d

info "設定ファイルの生成を待機中..."
for i in $(seq 1 30); do
    if [[ -f "$INSTALL_DIR/config/config.xml" ]]; then
        success "config.xml を検出しました。"
        break
    fi
    sleep 2
    [[ $i -eq 30 ]] && error "設定ファイルの生成がタイムアウトしました。\n  → docker logs syncthing で確認してください。"
done

# ── GUI 認証情報の入力 ─────────────────────────────────────
echo ""
echo -e "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${YELLOW}  Syncthing GUI パスワード設定${NC}"
echo -e "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
read -rp "GUIユーザー名 [デフォルト: admin]: " GUI_USER
GUI_USER="${GUI_USER:-admin}"

while true; do
    read -rsp "GUIパスワード: " GUI_PASS; echo ""
    read -rsp "パスワード(確認): " GUI_PASS2; echo ""
    [[ "$GUI_PASS" == "$GUI_PASS2" ]] && break
    warn "パスワードが一致しません。再入力してください。"
done

# ── bcrypt ハッシュ生成 ────────────────────────────────────
if ! python3 -c "import bcrypt" 2>/dev/null; then
    info "python3-bcrypt をインストールします..."
    apt-get install -y python3-bcrypt -q 2>/dev/null || pip3 install bcrypt -q 2>/dev/null || true
fi

HASHED_PASS=$(python3 -c "
import bcrypt, sys
pw = sys.argv[1].encode()
print(bcrypt.hashpw(pw, bcrypt.gensalt()).decode())
" "$GUI_PASS" 2>/dev/null) || { warn "bcrypt が使えませんでした。パスワードはGUIで後から設定してください。"; HASHED_PASS=""; }

# ── config.xml を更新 ─────────────────────────────────────
if [[ -n "${HASHED_PASS:-}" ]]; then
    python3 - "$GUI_USER" "$HASHED_PASS" "$INSTALL_DIR/config/config.xml" << 'PYEOF'
import xml.etree.ElementTree as ET, sys

gui_user, hashed_pass, config_path = sys.argv[1], sys.argv[2], sys.argv[3]

ET.register_namespace('', '')
tree = ET.parse(config_path)
root = tree.getroot()
gui  = root.find('gui')

def set_or_create(parent, tag, text):
    el = parent.find(tag)
    if el is None:
        el = ET.SubElement(parent, tag)
    el.text = text

set_or_create(gui, 'address',  '0.0.0.0:8384')
set_or_create(gui, 'user',     gui_user)
set_or_create(gui, 'password', hashed_pass)
set_or_create(gui, 'authMode', 'static')

tree.write(config_path, encoding='unicode', xml_declaration=True)
print("config.xml を更新しました。")
PYEOF
    success "GUI認証情報を設定しました(ユーザー: $GUI_USER)"
fi

# ── コンテナ再起動 ─────────────────────────────────────────
info "設定を反映するためコンテナを再起動します..."
docker compose restart syncthing
sleep 5

# ── Device ID 取得 ─────────────────────────────────────────
DEVICE_ID=$(docker exec syncthing syncthing --device-id 2>/dev/null \
    || grep -oP '(?<=device id=")[A-Z0-9-]{63}' "$INSTALL_DIR/config/config.xml" 2>/dev/null \
    || echo "(GUIの「情報」から確認してください)")

# ── 完了メッセージ ─────────────────────────────────────────
HOST_IP=$(hostname -I | awk '{print $1}')
echo ""
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${GREEN}  ✅ Syncthing セットアップ完了!${NC}"
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo ""
echo -e "  📡 Web UI          : ${BLUE}http://$HOST_IP:8384${NC}"
echo -e "  👤 ユーザー名      : ${YELLOW}$GUI_USER${NC}"
echo -e "  📁 インストール先  : $INSTALL_DIR"
echo ""
echo -e "  🖥️  Device ID(バックアップPCへの接続時に使用):"
echo -e "  ${YELLOW}$DEVICE_ID${NC}"
echo ""
echo -e "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "  📌 次のステップ:"
echo -e "  1. Web UI にアクセスして動作確認"
echo -e "  2. 各フォルダのタイプを ${YELLOW}Send Only${NC} に設定"
echo -e "  3. バックアップPC にも Syncthing を導入"
echo -e "  4. お互いの Device ID を「デバイスを追加」で登録"
echo -e "  5. バックアップPC側フォルダを ${YELLOW}Receive Only${NC} + ${YELLOW}階段状バージョニング${NC} に設定"
echo -e "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo ""

# ── UFW 案内 ───────────────────────────────────────────────
if command -v ufw &>/dev/null && ufw status | grep -q "Status: active"; then
    echo -e "${YELLOW}🔥 UFW が有効です。以下でポートを開放してください:${NC}"
    echo ""
    echo "  sudo ufw allow 8384/tcp   # Web UI"
    echo "  sudo ufw allow 22000/tcp  # Sync (TCP)"
    echo "  sudo ufw allow 22000/udp  # Sync (QUIC)"
    echo "  sudo ufw allow 21027/udp  # ローカル探索"
    echo ""
fi