Nextcloud&OnlyOfficeのセットアップとバックアップ・復元スクリプト

Nextcloud&OnlyOfficeのセットアップと、移行時に使えるバックアップ&復元スクリプトです。
セットアップスクリプトは、インストール時にフォントを追加するようにしています。追加フォントが不要なら過去のセットアップスクリプトでもOK。

先に追加したいフォントを配置

まずは/opt/lxd-data/Fontsに追加したいフォントを入れておきます。

/opt/lxd-dataにFonts.zipがあると仮定。解凍します。

cd /opt/lxd-data
apt install unzip
unzip Fonts.zip

その後Nextcloud&OnlyOfficeをインストール

下記コードを貼り付けてインストールします。フォントキャッシュの生成に数分かかります。

#!/bin/bash
set -euo pipefail
# =============================================================
#  Nextcloud + OnlyOffice セットアップスクリプト (tailscale serve版)
#
#  構成 (LXDコンテナ内で実行):
#   - Nextcloud  : https://<hostname>.<tailnet>.ts.net:3305 (tailscale serve 3305)
#   - OnlyOffice : https://<hostname>.<tailnet>.ts.net:3306 (tailscale serve 3306)
#
#  ディレクトリ構成:
#   /opt/docker/nextcloud/
#     docker-compose.yml
#     .secrets
#     db/ userdata/ appdata/
#   /opt/docker/onlyoffice/
#     docker-compose.yml .env
#     logs/ data/ lib/ db/
#
#  フォント追加:
#   /opt/lxd-data/Fonts/ にフォントファイルを置いておくと
#   OnlyOfficeコンテナの /usr/share/fonts/custom にマウントされます
#
#  前提条件:
#   - LXDコンテナ内でrootまたはsudoで実行
#   - Docker がインストール済みであること
#   - tailscale up 済みであること
#   - Tailscale管理コンソールでHTTPS Certificatesを有効化済み
#     https://login.tailscale.com/admin/dns
# =============================================================

NEXTCLOUD_DIR="/opt/docker/nextcloud"
ONLYOFFICE_DIR="/opt/docker/onlyoffice"
SECRETS_FILE="${NEXTCLOUD_DIR}/.secrets"
FONTS_DIR="/opt/lxd-data/Fonts"

NEXTCLOUD_PORT=8181      # ホスト内部ポート(127.0.0.1バインド)※8080は既存サービスが使用中
ONLYOFFICE_PORT=9090     # ホスト内部ポート(127.0.0.1バインド)※9000から変更
NEXTCLOUD_TS_PORT=3305   # tailscale serveで公開するポート
ONLYOFFICE_TS_PORT=3306  # tailscale serveで公開するポート

# ── カラー出力 ────────────────────────────────────
GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
NC='\033[0m'

echo ""
echo "════════════════════════════════════════"
echo "  Nextcloud + OnlyOffice セットアップ"
echo "  (tailscale serve版)"
echo "════════════════════════════════════════"
echo ""

# ── Tailscale確認 ─────────────────────────────────
if ! command -v tailscale &>/dev/null; then
    echo -e "${RED}ERROR: tailscaleがインストールされていません${NC}"
    exit 1
fi

if ! tailscale status &>/dev/null 2>&1; then
    echo -e "${RED}ERROR: tailscaleが接続されていません。tailscale up を実行してください${NC}"
    exit 1
fi

# ── tailnetドメイン取得 ───────────────────────────
echo "==> [1/7] Tailscaleドメインを取得..."
sudo tailscale set --operator=$USER 2>/dev/null || true

TAILSCALE_DOMAIN=$(tailscale status --json | python3 -c "
import json, sys
d = json.load(sys.stdin)
print(d.get('Self', {}).get('DNSName', '').rstrip('.'))
" 2>/dev/null)

if [ -z "$TAILSCALE_DOMAIN" ]; then
    echo -e "${RED}ERROR: Tailscaleドメインを取得できませんでした${NC}"
    echo "Tailscale管理コンソールでMagicDNSが有効になっているか確認してください"
    exit 1
fi

echo -e "  ${GREEN}ドメイン: ${TAILSCALE_DOMAIN}${NC}"
echo -e "  ${GREEN}Nextcloud  : https://${TAILSCALE_DOMAIN}:${NEXTCLOUD_TS_PORT}${NC}"
echo -e "  ${GREEN}OnlyOffice : https://${TAILSCALE_DOMAIN}:${ONLYOFFICE_TS_PORT}${NC}"

# ── ディレクトリ作成 ──────────────────────────────
echo ""
echo "==> [2/7] ディレクトリを準備..."
mkdir -p "${NEXTCLOUD_DIR}"/{db,userdata,appdata}
mkdir -p "${ONLYOFFICE_DIR}"/{logs,data,lib,db}
echo -e "  ${GREEN}✓ /opt/docker/nextcloud/${NC}"
echo -e "  ${GREEN}✓ /opt/docker/onlyoffice/${NC}"

# ── フォントディレクトリ確認 ──────────────────────
echo ""
echo "==> [3/7] フォントディレクトリを確認..."
if [ -d "${FONTS_DIR}" ]; then
    FONT_COUNT=$(find "${FONTS_DIR}" -type f \( -name "*.ttf" -o -name "*.otf" -o -name "*.TTF" -o -name "*.OTF" \) 2>/dev/null | wc -l)
    echo -e "  ${GREEN}✓ ${FONTS_DIR} が存在します(フォントファイル: ${FONT_COUNT}個)${NC}"
    if [ "$FONT_COUNT" -eq 0 ]; then
        echo -e "  ${YELLOW}⚠ フォントファイルが見つかりません。.ttf/.otf ファイルを置いてください${NC}"
    fi
else
    echo -e "  ${YELLOW}⚠ ${FONTS_DIR} が存在しません。フォントなしで続行します${NC}"
    echo -e "  ${YELLOW}  フォントを追加する場合は mkdir -p ${FONTS_DIR} してファイルを配置後、${NC}"
    echo -e "  ${YELLOW}  docker compose up -d --force-recreate onlyoffice-docs を実行してください${NC}"
    mkdir -p "${FONTS_DIR}"
    echo -e "  ${GREEN}✓ ${FONTS_DIR} を作成しました${NC}"
fi

# ── シークレット生成 or 既存ファイルから読み込み ──
echo ""
echo "==> [4/7] シークレットを確認..."

if [ -f "${SECRETS_FILE}" ]; then
    source "${SECRETS_FILE}"
    echo -e "  ${GREEN}✓ 既存のシークレットを使用${NC}"
else
    if [ "$(find "${NEXTCLOUD_DIR}/db" -mindepth 1 -maxdepth 1 2>/dev/null | wc -l)" -gt 0 ]; then
        echo -e "${RED}ERROR: DBデータが存在しますがシークレットファイルが見つかりません${NC}"
        echo -e "${RED}       ${SECRETS_FILE} が必要です${NC}"
        echo ""
        echo "  対処法: DBデータを削除して再セットアップ:"
        echo "    docker compose -f ${NEXTCLOUD_DIR}/docker-compose.yml down -v"
        echo "    rm -rf ${NEXTCLOUD_DIR}/db/*"
        exit 1
    fi

    MYSQL_ROOT_PASSWORD=$(openssl rand -hex 16)
    MYSQL_PASSWORD=$(openssl rand -hex 16)
    JWT_SECRET=$(openssl rand -hex 32)

    cat > "${SECRETS_FILE}" <<SECRETS
MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
MYSQL_PASSWORD=${MYSQL_PASSWORD}
JWT_SECRET=${JWT_SECRET}
SECRETS
    chmod 600 "${SECRETS_FILE}"
    echo -e "  ${GREEN}✓ 新しいシークレットを生成: ${SECRETS_FILE}${NC}"
fi

# ── 共有ネットワーク作成 ──────────────────────────
echo ""
echo "==> [5/7] Dockerネットワークを確認..."
docker network inspect onlyoffice_net >/dev/null 2>&1 \
    || docker network create onlyoffice_net
echo -e "  ${GREEN}✓ onlyoffice_net${NC}"

# ── 設定ファイル生成 ──────────────────────────────
echo ""
echo "==> [6/7] 設定ファイルを生成..."

cat > "${NEXTCLOUD_DIR}/docker-compose.yml" <<EOF
services:
  db:
    image: mariadb:10.6
    container_name: nextcloud-db
    restart: unless-stopped
    command: --transaction-isolation=READ-COMMITTED --binlog-format=ROW
    volumes:
      - ${NEXTCLOUD_DIR}/db:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
      MYSQL_DATABASE: nextcloud
      MYSQL_USER: nextcloud
    healthcheck:
      test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
      interval: 10s
      timeout: 5s
      retries: 10
      start_period: 30s
    networks:
      - default

  app:
    image: nextcloud:latest
    container_name: nextcloud-app
    restart: unless-stopped
    ports:
      - "127.0.0.1:${NEXTCLOUD_PORT}:80"
    depends_on:
      db:
        condition: service_healthy
    volumes:
      - ${NEXTCLOUD_DIR}/appdata:/var/www/html
      - ${NEXTCLOUD_DIR}/userdata:/var/www/html/data
    environment:
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
      MYSQL_DATABASE: nextcloud
      MYSQL_USER: nextcloud
      MYSQL_HOST: nextcloud-db
      NEXTCLOUD_TRUSTED_DOMAINS: "${TAILSCALE_DOMAIN}"
      OVERWRITEHOST: "${TAILSCALE_DOMAIN}:${NEXTCLOUD_TS_PORT}"
      OVERWRITEPROTOCOL: https
    networks:
      - default
      - onlyoffice_net

networks:
  default:
  onlyoffice_net:
    external: true
EOF

cat > "${ONLYOFFICE_DIR}/.env" <<EOF
JWT_SECRET=${JWT_SECRET}
EOF
chmod 600 "${ONLYOFFICE_DIR}/.env"

cat > "${ONLYOFFICE_DIR}/docker-compose.yml" <<EOF
services:
  onlyoffice-docs:
    image: onlyoffice/documentserver:latest
    container_name: onlyoffice-docs
    restart: unless-stopped
    stdin_open: true
    tty: true
    ports:
      - "127.0.0.1:${ONLYOFFICE_PORT}:80"
    environment:
      JWT_ENABLED: "true"
      JWT_SECRET: "${JWT_SECRET}"
    volumes:
      - ${ONLYOFFICE_DIR}/logs:/var/log/onlyoffice
      - ${ONLYOFFICE_DIR}/data:/var/www/onlyoffice/Data
      - ${ONLYOFFICE_DIR}/lib:/var/lib/onlyoffice
      - ${ONLYOFFICE_DIR}/db:/var/lib/postgresql
      - ${FONTS_DIR}:/usr/share/fonts/custom
    networks:
      - onlyoffice_net

networks:
  onlyoffice_net:
    external: true
EOF

echo -e "  ${GREEN}✓ ${NEXTCLOUD_DIR}/docker-compose.yml${NC}"
echo -e "  ${GREEN}✓ ${ONLYOFFICE_DIR}/docker-compose.yml${NC}"
echo -e "  ${GREEN}✓ フォントマウント: ${FONTS_DIR} → /usr/share/fonts/custom${NC}"

# ── Docker起動 ────────────────────────────────────
echo ""
echo "==> [7/7] Dockerコンテナを起動..."

echo "  ⏳ Nextcloudを起動中..."
cd "${NEXTCLOUD_DIR}"
docker compose pull
docker compose up -d

echo "  ⏳ MariaDBの初期化を待機中(最大120秒)..."
for i in $(seq 1 24); do
    STATUS=$(docker inspect nextcloud-db --format='{{.State.Health.Status}}' 2>/dev/null || echo "not_found")
    if [ "$STATUS" = "healthy" ]; then
        echo -e "\n  ${GREEN}✓ MariaDB 初期化完了${NC}"
        break
    fi
    if [ "$i" -eq 24 ]; then
        echo -e "\n${RED}ERROR: MariaDBがタイムアウトしました${NC}"
        docker logs nextcloud-db --tail=20
        exit 1
    fi
    sleep 5
    echo -n "."
done

echo "  ⏳ Nextcloud appの起動を待機中(最大120秒)..."
for i in $(seq 1 24); do
    HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:${NEXTCLOUD_PORT}/" 2>/dev/null || echo "000")
    if [ "$HTTP_CODE" != "000" ]; then
        echo -e "\n  ${GREEN}✓ Nextcloud app 起動完了 (HTTP ${HTTP_CODE})${NC}"
        break
    fi
    if [ "$i" -eq 24 ]; then
        echo -e "\n${RED}ERROR: Nextcloud appがタイムアウトしました${NC}"
        docker logs nextcloud-app --tail=20
        exit 1
    fi
    sleep 5
    echo -n "."
done

echo "  ⏳ OnlyOfficeを起動中..."
cd "${ONLYOFFICE_DIR}"
docker compose pull
docker compose up -d
echo -e "  ${GREEN}✓ OnlyOffice 起動${NC}"

# ── OnlyOfficeフォント再生成 ──────────────────────
FONT_COUNT=$(find "${FONTS_DIR}" -type f \( -name "*.ttf" -o -name "*.otf" -o -name "*.TTF" -o -name "*.OTF" \) 2>/dev/null | wc -l)
if [ "$FONT_COUNT" -gt 0 ]; then
    echo "  ⏳ OnlyOfficeの起動完了を待機中(最大120秒)..."
    for i in $(seq 1 24); do
        OO_HTTP=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:${ONLYOFFICE_PORT}/healthcheck" 2>/dev/null || echo "000")
        if [ "$OO_HTTP" = "200" ]; then
            echo -e "\n  ${GREEN}✓ OnlyOffice 起動確認 (HTTP ${OO_HTTP})${NC}"
            break
        fi
        if [ "$i" -eq 24 ]; then
            echo -e "\n  ${YELLOW}⚠ OnlyOfficeのヘルスチェックタイムアウト。フォント再生成をスキップします${NC}"
            FONT_COUNT=0
            break
        fi
        sleep 5
        echo -n "."
    done
fi

if [ "$FONT_COUNT" -gt 0 ]; then
    echo "  ⏳ フォントキャッシュを再生成中(しばらくかかります)..."
    docker exec onlyoffice-docs documentserver-generate-allfonts.sh \
        && echo -e "  ${GREEN}✓ フォント再生成完了(${FONT_COUNT}個のフォントファイルを認識)${NC}" \
        || echo -e "  ${YELLOW}⚠ フォント再生成に失敗しました。後で手動で実行してください:${NC}
       docker exec -it onlyoffice-docs documentserver-generate-allfonts.sh"
fi

# ── tailscale serve 設定 ──────────────────────────
# 既存のserve設定を残しつつ、このポートのみ追加(冪等対応)
tailscale serve --https=${NEXTCLOUD_TS_PORT} off 2>/dev/null || true
tailscale serve --https=${ONLYOFFICE_TS_PORT} off 2>/dev/null || true

tailscale serve --bg --https=${NEXTCLOUD_TS_PORT} http://localhost:${NEXTCLOUD_PORT}
echo -e "  ${GREEN}✓ Nextcloud  : https://${TAILSCALE_DOMAIN}:${NEXTCLOUD_TS_PORT}${NC}"

tailscale serve --bg --https=${ONLYOFFICE_TS_PORT} http://localhost:${ONLYOFFICE_PORT}
echo -e "  ${GREEN}✓ OnlyOffice : https://${TAILSCALE_DOMAIN}:${ONLYOFFICE_TS_PORT}${NC}"

echo ""
tailscale serve status

# ── 完了メッセージ ────────────────────────────────
echo ""
echo "════════════════════════════════════════"
echo -e "  ${GREEN}✅  起動完了!${NC}"
echo "════════════════════════════════════════"
echo ""
echo "  🌐 Nextcloud  : https://${TAILSCALE_DOMAIN}:${NEXTCLOUD_TS_PORT}"
echo "  🌐 OnlyOffice : https://${TAILSCALE_DOMAIN}:${ONLYOFFICE_TS_PORT}"
echo ""
echo "════════════════════════════════════════"
echo "  📋 初回セットアップ(Nextcloud)"
echo "════════════════════════════════════════"
echo ""
echo "  ブラウザで https://${TAILSCALE_DOMAIN}:${NEXTCLOUD_TS_PORT} にアクセスして"
echo "  以下を入力してください:"
echo ""
echo "  管理者ユーザー名 : 任意"
echo "  管理者パスワード : 任意"
echo "  データベース     : MySQL/MariaDB を選択"
echo "  DBユーザー       : nextcloud"
echo "  DBパスワード     : ${MYSQL_PASSWORD}"
echo "  DB名             : nextcloud"
echo "  DBホスト         : nextcloud-db"
echo ""
echo "════════════════════════════════════════"
echo "  📋 Nextcloud → ONLYOFFICE 連携設定"
echo "════════════════════════════════════════"
echo ""
echo "  管理画面 → アプリ → ONLYOFFICE をインストール後、"
echo "  管理画面 → ONLYOFFICE で以下を設定:"
echo ""
echo "  ONLYOFFICE Docs アドレス:"
echo "    https://${TAILSCALE_DOMAIN}:${ONLYOFFICE_TS_PORT}"
echo ""
echo "  JWT シークレット:"
echo "    ${JWT_SECRET}"
echo ""
echo "  認証ヘッダー:(空白のまま)"
echo ""
echo "  サーバーから内部リクエストに利用されるアドレス:"
echo "    http://onlyoffice-docs"
echo ""
echo "  ONLYOFFICE Docsから内部リクエストに利用されるアドレス:"
echo "    http://nextcloud-app"
echo ""
echo "════════════════════════════════════════"
echo "  🔧 フォント追加手順"
echo "════════════════════════════════════════"
echo ""
echo "  1. フォントファイルを ${FONTS_DIR} に配置"
echo "  2. コンテナを再作成:"
echo "       cd ${ONLYOFFICE_DIR} && docker compose up -d --force-recreate onlyoffice-docs"
echo "  3. フォントキャッシュを再生成:"
echo "       docker exec -it onlyoffice-docs documentserver-generate-allfonts.sh"
echo ""
echo "════════════════════════════════════════"
echo "  🔧 アップデート手順"
echo "════════════════════════════════════════"
echo ""
echo "  Nextcloud:"
echo "    cd ${NEXTCLOUD_DIR} && docker compose pull && docker compose up -d"
echo ""
echo "  OnlyOffice:"
echo "    cd ${ONLYOFFICE_DIR} && docker compose pull && docker compose up -d"
echo "════════════════════════════════════════"
echo ""

OpenOfficeとの連携後シートを読み込んだりした際に、Webブラウザの広告ブロックプラグインなどによってはメニューバーなどが真っ白になってシートが読み込めないことがあります。その場合はそのページでの広告ブロックを停止します。

バックアップスクリプト

このスクリプトを使うと、/opt/lxd-data/nextcloud/20240101_120000.tar.gz のようにタイムスタンプ付きで保存されます。複数回実行すると世代管理できます。

バックアップ内容

ファイル内容
nextcloud/nextcloud_db.sqlMariaDB ダンプ
nextcloud/appdata.tar.gzアプリ設定・プラグイン
nextcloud/userdata.tar.gzユーザーファイル
nextcloud/.secretsDB/JWTパスワード
onlyoffice/onlyoffice_data.tar.gzOOデータ一式
onlyoffice/.envJWTシークレット
backup_info.txtメタ情報(日時・ドメイン等)

主な設計ポイント

  • バックアップ中: Nextcloudをメンテナンスモードにして整合性を確保。OnlyOfficeは一時停止
  • エラー時: trapでメンテナンスモードを自動解除
  • ドメイン変更対応: 復元先のTailscaleドメインが異なる場合、trusted_domains / overwritehost を自動更新
  • 復元後のphp occ files:scan: ファイルとDBの整合性を自動修復

それでは実際にスクリプトを作成します。

mkdir -p /opt/lxd-data/nextcloud
cd /opt/lxd-data/nextcloud
nano backup-nextcloud.sh
chmod +x backup-nextcloud.sh
#!/bin/bash
set -euo pipefail
# =============================================================
#  Nextcloud + OnlyOffice バックアップスクリプト
#
#  バックアップ先: /opt/lxd-data/nextcloud/
#  実行環境: LXDコンテナ内 (rootまたはsudo)
#
#  出力ファイル:
#   /opt/lxd-data/nextcloud/ncbackup_<TIMESTAMP>.tar.gz
#     ├── backup_info.txt
#     ├── nextcloud/
#     │   ├── nextcloud_db.sql
#     │   ├── appdata.tar.gz
#     │   ├── userdata.tar.gz
#     │   ├── .secrets
#     │   └── docker-compose.yml
#     └── onlyoffice/
#         ├── onlyoffice_data.tar.gz
#         ├── .env
#         └── docker-compose.yml
# =============================================================

BACKUP_BASE="/opt/lxd-data/nextcloud"
NEXTCLOUD_DIR="/opt/docker/nextcloud"
ONLYOFFICE_DIR="/opt/docker/onlyoffice"
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
WORK_DIR="${BACKUP_BASE}/.work_${TIMESTAMP}"     # 作業用一時ディレクトリ
ARCHIVE="${BACKUP_BASE}/ncbackup_${TIMESTAMP}.tar.gz"

GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
NC='\033[0m'

echo ""
echo "════════════════════════════════════════"
echo "  Nextcloud + OnlyOffice バックアップ"
echo "  出力: $(basename "${ARCHIVE}")"
echo "════════════════════════════════════════"
echo ""

# ── 前提チェック ──────────────────────────────────
if [ "$(id -u)" -ne 0 ]; then
    echo -e "${RED}ERROR: rootまたはsudoで実行してください${NC}"
    exit 1
fi

for container in nextcloud-db nextcloud-app onlyoffice-docs; do
    if ! docker inspect "$container" &>/dev/null; then
        echo -e "${RED}ERROR: コンテナ ${container} が見つかりません${NC}"
        echo "  先にセットアップスクリプトを実行してください"
        exit 1
    fi
done

# ── 作業ディレクトリ作成 ──────────────────────────
mkdir -p "${BACKUP_BASE}"
mkdir -p "${WORK_DIR}"/{nextcloud,onlyoffice}

# ── クリーンアップトラップ ────────────────────────
cleanup() {
    echo ""
    echo -e "${YELLOW}⚠ 中断されました。メンテナンスモードを解除します...${NC}"
    docker exec nextcloud-app php occ maintenance:mode --off 2>/dev/null || true
    rm -rf "${WORK_DIR}"
    # 不完全なアーカイブを削除
    rm -f "${ARCHIVE}"
    echo -e "${YELLOW}  作業ディレクトリを削除しました${NC}"
}
trap cleanup ERR INT TERM

# ── [1/7] Nextcloud メンテナンスモード ON ─────────
echo "==> [1/7] Nextcloud をメンテナンスモードに切り替え..."
docker exec nextcloud-app php occ maintenance:mode --on
echo -e "  ${GREEN}✓ メンテナンスモード: ON${NC}"

# ── [2/7] MariaDB ダンプ ──────────────────────────
echo ""
echo "==> [2/7] MariaDB をダンプ..."
source "${NEXTCLOUD_DIR}/.secrets"
docker exec nextcloud-db \
    mysqldump \
        --single-transaction \
        --quick \
        --lock-tables=false \
        -u root \
        -p"${MYSQL_ROOT_PASSWORD}" \
        nextcloud \
    > "${WORK_DIR}/nextcloud/nextcloud_db.sql"
echo -e "  ${GREEN}✓ nextcloud_db.sql ($(du -sh "${WORK_DIR}/nextcloud/nextcloud_db.sql" | cut -f1))${NC}"

# ── [3/7] Nextcloud appdata / userdata ───────────
echo ""
echo "==> [3/7] Nextcloud appdata / userdata をアーカイブ..."

echo "  ⏳ appdata をアーカイブ中..."
tar -czf "${WORK_DIR}/nextcloud/appdata.tar.gz" \
    -C "${NEXTCLOUD_DIR}" appdata
echo -e "  ${GREEN}✓ appdata.tar.gz ($(du -sh "${WORK_DIR}/nextcloud/appdata.tar.gz" | cut -f1))${NC}"

echo "  ⏳ userdata をアーカイブ中(時間がかかる場合があります)..."
tar -czf "${WORK_DIR}/nextcloud/userdata.tar.gz" \
    -C "${NEXTCLOUD_DIR}" userdata
echo -e "  ${GREEN}✓ userdata.tar.gz ($(du -sh "${WORK_DIR}/nextcloud/userdata.tar.gz" | cut -f1))${NC}"

# ── [4/7] OnlyOffice データ ───────────────────────
echo ""
echo "==> [4/7] OnlyOffice データをアーカイブ..."

echo "  ⏳ OnlyOfficeコンテナを一時停止..."
docker stop onlyoffice-docs

tar -czf "${WORK_DIR}/onlyoffice/onlyoffice_data.tar.gz" \
    -C "${ONLYOFFICE_DIR}" data lib db logs
echo -e "  ${GREEN}✓ onlyoffice_data.tar.gz ($(du -sh "${WORK_DIR}/onlyoffice/onlyoffice_data.tar.gz" | cut -f1))${NC}"

echo "  ⏳ OnlyOfficeコンテナを再起動..."
docker start onlyoffice-docs
echo -e "  ${GREEN}✓ OnlyOffice 再起動${NC}"

# ── [5/7] 設定・シークレットファイル ─────────────
echo ""
echo "==> [5/7] 設定・シークレットファイルをコピー..."

cp "${NEXTCLOUD_DIR}/.secrets"           "${WORK_DIR}/nextcloud/.secrets"
cp "${NEXTCLOUD_DIR}/docker-compose.yml" "${WORK_DIR}/nextcloud/docker-compose.yml"
chmod 600 "${WORK_DIR}/nextcloud/.secrets"

cp "${ONLYOFFICE_DIR}/.env"              "${WORK_DIR}/onlyoffice/.env"
cp "${ONLYOFFICE_DIR}/docker-compose.yml" "${WORK_DIR}/onlyoffice/docker-compose.yml"
chmod 600 "${WORK_DIR}/onlyoffice/.env"

echo -e "  ${GREEN}✓ .secrets, .env, docker-compose.yml × 2${NC}"

# ── [6/7] Nextcloud メンテナンスモード OFF ────────
echo ""
echo "==> [6/7] メンテナンスモードを解除..."
trap - ERR INT TERM
docker exec nextcloud-app php occ maintenance:mode --off
echo -e "  ${GREEN}✓ メンテナンスモード: OFF${NC}"

# ── メタ情報記録 ──────────────────────────────────
TAILSCALE_DOMAIN=$(tailscale status --json 2>/dev/null \
    | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('Self',{}).get('DNSName','').rstrip('.'))" \
    2>/dev/null || echo "unknown")

cat > "${WORK_DIR}/backup_info.txt" <<INFO
backup_timestamp=${TIMESTAMP}
backup_date=$(date "+%Y-%m-%d %H:%M:%S")
tailscale_domain=${TAILSCALE_DOMAIN}
nextcloud_dir=${NEXTCLOUD_DIR}
onlyoffice_dir=${ONLYOFFICE_DIR}
hostname=$(hostname)
INFO

# ── [7/7] tar.gz にまとめる ───────────────────────
echo ""
echo "==> [7/7] 全データを1つの tar.gz にまとめ中..."
echo "  ⏳ アーカイブ作成中(しばらくかかります)..."

# WORK_DIR の中身をそのままアーカイブ(パスを ncbackup_TIMESTAMP/ に統一)
tar -czf "${ARCHIVE}" \
    -C "${BACKUP_BASE}" \
    ".work_${TIMESTAMP}"

# アーカイブ内のパスを ncbackup_TIMESTAMP/ に見せるため再パック
# ※ tarの--transformを使ってパスを書き換える
tar -czf "${ARCHIVE}.tmp" \
    --transform="s|^\.work_${TIMESTAMP}|ncbackup_${TIMESTAMP}|" \
    -C "${BACKUP_BASE}" \
    ".work_${TIMESTAMP}"
mv "${ARCHIVE}.tmp" "${ARCHIVE}"

# 作業ディレクトリ削除
rm -rf "${WORK_DIR}"

ARCHIVE_SIZE=$(du -sh "${ARCHIVE}" | cut -f1)
echo -e "  ${GREEN}✓ アーカイブ完了: $(basename "${ARCHIVE}") (${ARCHIVE_SIZE})${NC}"

# ── 完了メッセージ ────────────────────────────────
echo ""
echo "════════════════════════════════════════"
echo -e "  ${GREEN}✅  バックアップ完了!${NC}"
echo "════════════════════════════════════════"
echo ""
echo "  📦 ファイル : ${ARCHIVE}"
echo "  📦 サイズ   : ${ARCHIVE_SIZE}"
echo ""
echo "  アーカイブ内容を確認するには:"
echo "    tar -tzf ${ARCHIVE} | head -20"
echo ""

# 既存バックアップ一覧
BACKUP_COUNT=$(ls "${BACKUP_BASE}"/ncbackup_*.tar.gz 2>/dev/null | wc -l)
echo "  📋 保存済みバックアップ一覧 (${BACKUP_COUNT}件):"
ls -lt "${BACKUP_BASE}"/ncbackup_*.tar.gz 2>/dev/null | awk '{print "    " $NF "  " $5}' | while read -r line; do
    FILE=$(echo "$line" | awk '{print $1}')
    echo "    $(basename "$FILE")  $(du -sh "$FILE" 2>/dev/null | cut -f1)"
done
echo ""
echo "  ℹ️  古いバックアップを削除する場合:"
echo "    rm ${BACKUP_BASE}/ncbackup_<タイムスタンプ>.tar.gz"
echo "════════════════════════════════════════"
echo ""

バックアップを実行します。

sudo bash backup-nextcloud.sh

復元

まずは復元先でNextcloud&OpenOfficeをインストールします。インストール後、セットアップを行いNextcloudが使えるところまで進めます。なお、復元時はOffice連携までは行わなくて大丈夫です。復元すれば連携され必要情報も入力された状態になります。

なお、復元を実行した時に、バックアップ元のtailscale serve設定が切れてしまう可能性があります。復元先のNextcloud/OnlyOfficeが同じポート(3305/3306)でTailnet上に出現したことで、元PCのTailscaleデーモンがルーティング競合を検知して接続を切る、という症状が発生します。復元作業中は元PCのTailscaleを一時的に切る、もしくは復元後に、バックアップ元のPCを再起動する必要があります(新環境を自動取得して反映するようにしたので大丈夫かもしれませんが)。なので、バックアップ元のPCを直接操作出来る環境で行うか、再起動をスケジュールするなど、準備してから行ってください。

セットアップ完了後、復元スクリプトを作成します。

mkdir -p /opt/lxd-data/nextcloud
cd /opt/lxd-data/nextcloud
nano restore-nextcloud.sh
chmod +x restore-nextcloud.sh
#!/bin/bash
set -euo pipefail
# =============================================================
#  Nextcloud + OnlyOffice 復元スクリプト
#
#  実行環境: LXDコンテナ内 (rootまたはsudo) ※セットアップ済み環境
#
#  使い方:
#    ./restore.sh                                    # 最新バックアップを自動選択
#    ./restore.sh ncbackup_20240101_120000.tar.gz    # ファイル名で指定
#    ./restore.sh /path/to/ncbackup_20240101_120000.tar.gz  # フルパスで指定
#
#  注意:
#   - 復元先では先にセットアップスクリプトを実行済みであること
#   - 復元先のNextcloud/OnlyOfficeは上書きされます
#   - DBも完全に置き換えられます
# =============================================================

BACKUP_BASE="/opt/lxd-data/nextcloud"
NEXTCLOUD_TS_PORT=3305    # tailscale serveで公開するポート(セットアップスクリプトと合わせる)
ONLYOFFICE_TS_PORT=3306   # tailscale serveで公開するポート(セットアップスクリプトと合わせる)
NEXTCLOUD_DIR="/opt/docker/nextcloud"
ONLYOFFICE_DIR="/opt/docker/onlyoffice"

GREEN='\033[0;32m'
RED='\033[0;31m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m'

# ── アーカイブファイルの特定 ──────────────────────
if [ -n "${1:-}" ]; then
    if [ -f "${1}" ]; then
        ARCHIVE="${1}"
    elif [ -f "${BACKUP_BASE}/${1}" ]; then
        ARCHIVE="${BACKUP_BASE}/${1}"
    elif [ -f "${BACKUP_BASE}/ncbackup_${1}.tar.gz" ]; then
        ARCHIVE="${BACKUP_BASE}/ncbackup_${1}.tar.gz"
    else
        echo -e "${RED}ERROR: 指定されたバックアップが見つかりません: ${1}${NC}"
        echo "  指定方法:"
        echo "    ./restore.sh ncbackup_20240101_120000.tar.gz"
        echo "    ./restore.sh 20240101_120000"
        echo "    ./restore.sh /path/to/ncbackup_20240101_120000.tar.gz"
        exit 1
    fi
else
    ARCHIVE=$(ls -t "${BACKUP_BASE}"/ncbackup_*.tar.gz 2>/dev/null | head -1 || true)
    if [ -z "$ARCHIVE" ]; then
        echo -e "${RED}ERROR: ${BACKUP_BASE} にバックアップファイルが見つかりません${NC}"
        echo "  先にバックアップスクリプトを実行してください"
        exit 1
    fi
fi

ARCHIVE_NAME=$(basename "$ARCHIVE")
TIMESTAMP=$(echo "$ARCHIVE_NAME" | sed 's/ncbackup_//;s/\.tar\.gz//')

# アーカイブ内のルートディレクトリ名を取得
# pipefail対策: head -1 でパイプを閉じても tar が SIGPIPE にならないよう残りを読み捨て
ARCHIVE_ROOT=$(tar -tzf "$ARCHIVE" 2>/dev/null | { head -1; cat > /dev/null; } | cut -d/ -f1)
if [ -z "$ARCHIVE_ROOT" ]; then
    echo -e "${RED}ERROR: アーカイブの読み取りに失敗しました: ${ARCHIVE}${NC}"
    exit 1
fi

echo ""
echo "════════════════════════════════════════"
echo "  Nextcloud + OnlyOffice 復元"
echo "════════════════════════════════════════"
echo ""
echo -e "  アーカイブ: ${CYAN}${ARCHIVE_NAME}${NC}"
echo -e "  サイズ    : ${CYAN}$(du -sh "$ARCHIVE" | cut -f1)${NC}"

# ── 前提チェック ──────────────────────────────────
if [ "$(id -u)" -ne 0 ]; then
    echo -e "${RED}ERROR: rootまたはsudoで実行してください${NC}"
    exit 1
fi

if [ ! -f "${NEXTCLOUD_DIR}/.secrets" ]; then
    echo -e "${RED}ERROR: 復元先の .secrets が見つかりません: ${NEXTCLOUD_DIR}/.secrets${NC}"
    echo "  先にセットアップスクリプトを実行してください"
    exit 1
fi

for container in nextcloud-db nextcloud-app onlyoffice-docs; do
    if ! docker inspect "$container" &>/dev/null; then
        echo -e "${RED}ERROR: コンテナ ${container} が見つかりません${NC}"
        echo "  先にセットアップスクリプトを実行してください"
        exit 1
    fi
done

# ── アーカイブを展開して内容確認 ──────────────────
echo ""
echo "==> アーカイブの内容を確認..."
WORK_DIR=$(mktemp -d "${BACKUP_BASE}/.restore_XXXXXX")
trap 'rm -rf "${WORK_DIR}"' EXIT

echo "  ⏳ アーカイブを展開中..."
tar -xzf "$ARCHIVE" -C "${WORK_DIR}"

EXTRACT_DIR="${WORK_DIR}/${ARCHIVE_ROOT}"

if [ -f "${EXTRACT_DIR}/backup_info.txt" ]; then
    BACKUP_DATE=$(grep "backup_date=" "${EXTRACT_DIR}/backup_info.txt" | cut -d= -f2-)
    SRC_HOST=$(grep "hostname=" "${EXTRACT_DIR}/backup_info.txt" | cut -d= -f2-)
    BACKUP_DOMAIN=$(grep "tailscale_domain=" "${EXTRACT_DIR}/backup_info.txt" | cut -d= -f2- || echo "")
    echo -e "  バックアップ日時      : ${CYAN}${BACKUP_DATE}${NC}"
    echo -e "  バックアップ元ホスト  : ${CYAN}${SRC_HOST}${NC}"
    echo -e "  バックアップ元ドメイン: ${CYAN}${BACKUP_DOMAIN}${NC}"
fi

REQUIRED_FILES=(
    "nextcloud/nextcloud_db.sql"
    "nextcloud/appdata.tar.gz"
    "nextcloud/userdata.tar.gz"
    "nextcloud/.secrets"
    "onlyoffice/onlyoffice_data.tar.gz"
    "onlyoffice/.env"
)
echo ""
echo "  ファイル確認:"
for f in "${REQUIRED_FILES[@]}"; do
    if [ ! -f "${EXTRACT_DIR}/${f}" ]; then
        echo -e "${RED}ERROR: 必要なファイルが見つかりません: ${f}${NC}"
        exit 1
    fi
    SIZE=$(du -sh "${EXTRACT_DIR}/${f}" | cut -f1)
    echo -e "  ${GREEN}✓ ${f} (${SIZE})${NC}"
done

# ── 確認プロンプト ────────────────────────────────
echo ""
echo -e "${YELLOW}⚠️  警告: 以下のデータが上書きされます${NC}"
echo "    - Nextcloud DB (nextcloud データベース全体)"
echo "    - Nextcloud appdata / userdata"
echo "    - OnlyOffice data / lib / db / logs"
echo "    - シークレットファイル (.secrets, .env)"
echo ""
echo -e "${RED}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${RED}  ⚠️  Tailscale 競合に関する重要な注意  ⚠️  ${NC}"
echo -e "${RED}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo ""
echo "  復元を実行すると、バックアップ元のPCと同じ"
echo "  Nextcloud/OnlyOfficeがTailnet上に出現します。"
echo ""
echo "  これにより【バックアップ元PCのTailscale接続が"
echo "  切断される】ことがあります。"
echo ""
echo -e "${YELLOW}  続行前に以下を確認してください:${NC}"
echo ""
echo "  □ バックアップ元PCのTailscaleを停止済み、または"
echo "    バックアップ元PCは今後使用しない"
echo ""
echo "    停止コマンド(バックアップ元PCで実行):"
echo "      sudo tailscale down"
echo "      または tailscale serve --https=3305 off"
echo "                tailscale serve --https=3306 off"
echo ""
echo "  □ バックアップ元PCのTailscaleが停止できない場合は"
echo "    復元後に元PCを再起動すれば接続は回復します"
echo -e "${RED}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo ""
read -r -p "  バックアップ元PCのTailscaleを停止しましたか? [yes/N]: " TAILSCALE_CONFIRM
if [ "$TAILSCALE_CONFIRM" != "yes" ]; then
    echo ""
    echo -e "${YELLOW}  バックアップ元PCのTailscaleを停止してから再実行してください${NC}"
    echo "  停止できない場合は yes を入力して続行し、"
    echo "  復元後に元PCを再起動してください"
    echo ""
    read -r -p "  それでも続行しますか? [yes/N]: " FORCE_CONFIRM
    if [ "$FORCE_CONFIRM" != "yes" ]; then
        echo "  キャンセルしました"
        exit 0
    fi
fi
echo ""
read -r -p "  復元を続行しますか? [yes/N]: " CONFIRM
if [ "$CONFIRM" != "yes" ]; then
    echo "  キャンセルしました"
    exit 0
fi

# ── [1/7] シークレット読み込み ────────────────────
echo ""
echo "==> [1/7] シークレットを読み込み..."

# 復元先の現在のシークレット(DBへの接続に使う)
source "${NEXTCLOUD_DIR}/.secrets"
DEST_MYSQL_ROOT_PASSWORD="${MYSQL_ROOT_PASSWORD}"

# .secrets のパスワードで実際に接続できるか確認
# できない場合は docker inspect の環境変数からパスワードを取得する
# (セットアップスクリプトを複数回実行すると .secrets だけ更新されてDBと不一致になるケースへの対処)
if ! docker exec nextcloud-db mysql -u root -p"${DEST_MYSQL_ROOT_PASSWORD}" -e "SELECT 1;" &>/dev/null; then
    echo -e "  ${YELLOW}⚠ .secrets のパスワードでDB接続できません。docker inspect から取得します...${NC}"
    INSPECT_PASSWORD=$(docker inspect nextcloud-db \
        --format '{{range .Config.Env}}{{println .}}{{end}}' \
        | grep "^MYSQL_ROOT_PASSWORD=" | cut -d= -f2-)
    if [ -z "$INSPECT_PASSWORD" ]; then
        echo -e "${RED}ERROR: DBのrootパスワードを特定できませんでした${NC}"
        echo "  手動で確認してください: docker inspect nextcloud-db | grep MYSQL_ROOT"
        exit 1
    fi
    if ! docker exec nextcloud-db mysql -u root -p"${INSPECT_PASSWORD}" -e "SELECT 1;" &>/dev/null; then
        echo -e "${RED}ERROR: docker inspect のパスワードでも接続できませんでした${NC}"
        exit 1
    fi
    DEST_MYSQL_ROOT_PASSWORD="${INSPECT_PASSWORD}"
    echo -e "  ${GREEN}✓ docker inspect からパスワードを取得しました${NC}"
    # .secrets を実際のDBパスワードに合わせて修正
    sed -i "s/^MYSQL_ROOT_PASSWORD=.*/MYSQL_ROOT_PASSWORD=${DEST_MYSQL_ROOT_PASSWORD}/" "${NEXTCLOUD_DIR}/.secrets"
    echo -e "  ${GREEN}✓ .secrets のMYSQL_ROOT_PASSWORDを修正しました${NC}"
fi

# バックアップのシークレット(DB内ユーザーパスワード合わせに使う)
BACKUP_MYSQL_PASSWORD=$(grep "^MYSQL_PASSWORD=" "${EXTRACT_DIR}/nextcloud/.secrets" | cut -d= -f2-)
BACKUP_MYSQL_ROOT_PASSWORD=$(grep "^MYSQL_ROOT_PASSWORD=" "${EXTRACT_DIR}/nextcloud/.secrets" | cut -d= -f2-)

# 復元後の完了メッセージ用にJWT_SECRETを取得しておく
# 復元先の現在の .secrets から取得(新環境インストール時に生成された値)
RESTORE_JWT_SECRET=$(grep "^JWT_SECRET=" "${NEXTCLOUD_DIR}/.secrets" | cut -d= -f2-)

echo -e "  ${GREEN}✓ 復元先・バックアップ双方のシークレット読み込み完了${NC}"

# ── [2/7] Nextcloud メンテナンスモード ON ─────────
echo ""
echo "==> [2/7] Nextcloud をメンテナンスモードに切り替え..."
docker exec nextcloud-app php occ maintenance:mode --on
echo -e "  ${GREEN}✓ メンテナンスモード: ON${NC}"

maintenance_cleanup() {
    echo ""
    echo -e "${YELLOW}⚠ 中断されました。メンテナンスモードを解除します...${NC}"
    docker exec nextcloud-app php occ maintenance:mode --off 2>/dev/null || true
    rm -rf "${WORK_DIR}"
}
trap maintenance_cleanup ERR INT TERM

# ── [3/7] MariaDB 復元 ────────────────────────────
echo ""
echo "==> [3/7] MariaDB を復元..."
echo "  ⏳ データベースを初期化中..."

# 復元先の現在のrootパスワードでDB操作
docker exec nextcloud-db \
    mysql -u root -p"${DEST_MYSQL_ROOT_PASSWORD}" \
    -e "DROP DATABASE IF EXISTS nextcloud; CREATE DATABASE nextcloud CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"

echo "  ⏳ データをインポート中(時間がかかる場合があります)..."
docker exec -i nextcloud-db \
    mysql -u root -p"${DEST_MYSQL_ROOT_PASSWORD}" nextcloud \
    < "${EXTRACT_DIR}/nextcloud/nextcloud_db.sql"
echo -e "  ${GREEN}✓ DBインポート完了${NC}"

# バックアップ元とパスワードが異なる場合はDB内ユーザーパスワードを更新
if [ "${DEST_MYSQL_ROOT_PASSWORD}" != "${BACKUP_MYSQL_ROOT_PASSWORD}" ]; then
    echo "  ⏳ DBユーザーパスワードをバックアップ値に更新中..."
    docker exec nextcloud-db \
        mysql -u root -p"${DEST_MYSQL_ROOT_PASSWORD}" \
        -e "ALTER USER 'nextcloud'@'%' IDENTIFIED BY '${BACKUP_MYSQL_PASSWORD}';
            ALTER USER 'root'@'localhost' IDENTIFIED BY '${BACKUP_MYSQL_ROOT_PASSWORD}';
            FLUSH PRIVILEGES;"
    echo -e "  ${GREEN}✓ DBパスワード更新完了${NC}"
fi

# シークレットファイルをバックアップのものに置き換え
cp "${EXTRACT_DIR}/nextcloud/.secrets" "${NEXTCLOUD_DIR}/.secrets"
chmod 600 "${NEXTCLOUD_DIR}/.secrets"
cp "${EXTRACT_DIR}/onlyoffice/.env" "${ONLYOFFICE_DIR}/.env"
chmod 600 "${ONLYOFFICE_DIR}/.env"
echo -e "  ${GREEN}✓ .secrets, .env をバックアップから復元${NC}"

# ── [4/7] Nextcloud appdata 復元 ──────────────────
echo ""
echo "==> [4/7] Nextcloud appdata を復元..."
rm -rf "${NEXTCLOUD_DIR}/appdata"
mkdir -p "${NEXTCLOUD_DIR}/appdata"
echo "  ⏳ アーカイブを展開中..."
tar -xzf "${EXTRACT_DIR}/nextcloud/appdata.tar.gz" -C "${NEXTCLOUD_DIR}"
echo -e "  ${GREEN}✓ appdata 復元完了${NC}"

# ── [5/7] Nextcloud userdata 復元 ─────────────────
echo ""
echo "==> [5/7] Nextcloud userdata を復元..."
rm -rf "${NEXTCLOUD_DIR}/userdata"
mkdir -p "${NEXTCLOUD_DIR}/userdata"
echo "  ⏳ アーカイブを展開中(時間がかかる場合があります)..."
tar -xzf "${EXTRACT_DIR}/nextcloud/userdata.tar.gz" -C "${NEXTCLOUD_DIR}"
echo -e "  ${GREEN}✓ userdata 復元完了${NC}"

# ── [6/7] OnlyOffice データ復元 ───────────────────
echo ""
echo "==> [6/7] OnlyOffice データを復元..."
echo "  ⏳ OnlyOfficeコンテナを停止..."
docker stop onlyoffice-docs

rm -rf "${ONLYOFFICE_DIR:?}"/{data,lib,db,logs}
for dir in data lib db logs; do mkdir -p "${ONLYOFFICE_DIR}/${dir}"; done

echo "  ⏳ アーカイブを展開中..."
tar -xzf "${EXTRACT_DIR}/onlyoffice/onlyoffice_data.tar.gz" -C "${ONLYOFFICE_DIR}"

echo "  ⏳ OnlyOfficeコンテナを再起動..."
docker start onlyoffice-docs
echo -e "  ${GREEN}✓ OnlyOffice データ復元完了${NC}"

# ── [7/7] Nextcloudの後処理 ─────────────────────────
echo ""
echo "==> [7/7] Nextcloudの後処理..."
trap - ERR INT TERM

# appdata/userdata を入れ替えたためコンテナを再起動してマウントを認識させる
# (再起動しないと "current working directory is outside of container mount namespace" エラーになる)
echo "  ⏳ nextcloud-app コンテナを再起動中..."
docker restart nextcloud-app

echo "  ⏳ Nextcloudの起動を待機中(最大120秒)..."
for i in $(seq 1 24); do
    HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:8181/" 2>/dev/null || echo "000")
    if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "302" ] || [ "$HTTP_CODE" = "503" ]; then
        echo -e "\n  ${GREEN}✓ Nextcloud 起動確認 (HTTP ${HTTP_CODE})${NC}"
        break
    fi
    if [ "$i" -eq 24 ]; then
        echo -e "\n${RED}ERROR: Nextcloudの起動タイムアウト${NC}"
        docker logs nextcloud-app --tail=20
        exit 1
    fi
    sleep 5
    echo -n "."
done

CURRENT_DOMAIN=$(tailscale status --json 2>/dev/null \
    | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('Self',{}).get('DNSName','').rstrip('.'))" \
    2>/dev/null || echo "")
BACKUP_DOMAIN="${BACKUP_DOMAIN:-}"

if [ -n "$CURRENT_DOMAIN" ] && [ -n "$BACKUP_DOMAIN" ] && [ "$CURRENT_DOMAIN" != "$BACKUP_DOMAIN" ]; then
    echo -e "  ${YELLOW}⚠ Tailscaleドメインが異なります${NC}"
    echo -e "     バックアップ元: ${BACKUP_DOMAIN}"
    echo -e "     現在の環境:     ${CURRENT_DOMAIN}"
    echo "  ⏳ 信頼済みドメインとoverwritehostを更新中..."
    docker exec nextcloud-app php occ config:system:set trusted_domains 0 \
        --value="${CURRENT_DOMAIN}"
    docker exec nextcloud-app php occ config:system:set overwritehost \
        --value="${CURRENT_DOMAIN}:3305"
    echo -e "  ${GREEN}✓ ドメイン設定を更新: ${CURRENT_DOMAIN}${NC}"
else
    echo -e "  ${GREEN}✓ ドメイン変更なし${NC}"
fi

# メンテナンスモードを先に解除してからスキャン・repairを実行
# (メンテナンスモード中はアプリが無効になり files:scan 等が使えない)
docker exec nextcloud-app php occ maintenance:mode --off
echo -e "  ${GREEN}✓ メンテナンスモード: OFF${NC}"

# ── ONLYOFFICE連携設定を自動設定 ──────────────────
# ドメイン変更の有無に関わらず常に実行
# occ config:app:set は未設定状態でも書き込めるため初回設定も兼ねる
echo "  ⏳ ONLYOFFICE連携設定を自動設定中..."

# ONLYOFFICEアプリが有効かどうか確認
OO_APP_STATUS=$(docker exec nextcloud-app php occ app:list --output=json 2>/dev/null \
    | python3 -c "
import json,sys
d=json.load(sys.stdin)
enabled=d.get('enabled',{})
print('enabled' if 'onlyoffice' in enabled else 'disabled')
" 2>/dev/null || echo "unknown")

if [ "$OO_APP_STATUS" = "enabled" ]; then
    # 公開アドレス(ブラウザ → OnlyOffice)
    docker exec nextcloud-app php occ config:app:set onlyoffice DocumentServerUrl \
        --value="https://${CURRENT_DOMAIN}:${ONLYOFFICE_TS_PORT}"
    # 内部アドレス(Nextcloudサーバー → OnlyOfficeコンテナ直通)
    docker exec nextcloud-app php occ config:app:set onlyoffice DocumentServerInternalUrl \
        --value="http://onlyoffice-docs"
    # 内部アドレス(OnlyOffice → Nextcloudコンテナ直通)
    docker exec nextcloud-app php occ config:app:set onlyoffice StorageUrl \
        --value="http://nextcloud-app"
    # JWTシークレット(新環境の値)
    docker exec nextcloud-app php occ config:app:set onlyoffice jwt_secret \
        --value="${RESTORE_JWT_SECRET}"
    docker exec nextcloud-app php occ config:app:set onlyoffice jwt_header \
        --value="Authorization"
    echo -e "  ${GREEN}✓ ONLYOFFICE連携設定を自動設定しました${NC}"
    ONLYOFFICE_CONFIGURED=true
else
    echo -e "  ${YELLOW}⚠ ONLYOFFICEアプリが無効または未インストールのため設定をスキップ${NC}"
    echo -e "  ${YELLOW}  アプリインストール後に管理画面から手動で設定してください${NC}"
    ONLYOFFICE_CONFIGURED=false
fi

echo "  ⏳ ファイルスキャンを実行中..."
docker exec nextcloud-app php occ files:scan --all --quiet
echo -e "  ${GREEN}✓ ファイルスキャン完了${NC}"

echo "  ⏳ キャッシュをクリア中..."
docker exec nextcloud-app php occ maintenance:repair --quiet 2>/dev/null || true
echo -e "  ${GREEN}✓ キャッシュクリア完了${NC}"


rm -rf "${WORK_DIR}"
trap - EXIT

# ── 完了メッセージ ────────────────────────────────
echo ""
echo "════════════════════════════════════════"
echo -e "  ${GREEN}✅  復元完了!${NC}"
echo "════════════════════════════════════════"
echo ""

if [ -n "$CURRENT_DOMAIN" ]; then
    echo "  🌐 Nextcloud  : https://${CURRENT_DOMAIN}:3305"
    echo "  🌐 OnlyOffice : https://${CURRENT_DOMAIN}:3306"
    echo ""
fi

if [ "${ONLYOFFICE_CONFIGURED:-false}" = "true" ]; then
    echo -e "  ${GREEN}✓ ONLYOFFICE連携設定済み${NC}"
    echo ""
    echo "    設定内容:"
    echo "      ONLYOFFICE Docs アドレス : https://${CURRENT_DOMAIN}:${ONLYOFFICE_TS_PORT}"
    echo "      JWT シークレット         : ${RESTORE_JWT_SECRET}"
    echo ""
else
    echo -e "  ${YELLOW}📋 ONLYOFFICEアプリをインストール後、管理画面で設定してください:${NC}"
    echo ""
    echo "    管理画面 → ONLYOFFICE → ONLYOFFICE Docs アドレス:"
    echo "      https://${CURRENT_DOMAIN}:${ONLYOFFICE_TS_PORT}"
    echo ""
    echo "    JWT シークレット:"
    echo "      ${RESTORE_JWT_SECRET}"
    echo ""
fi

echo "  復元元: ${ARCHIVE_NAME}"
echo "════════════════════════════════════════"
echo ""

バックアップしていたファイルを保存したら、復元します。最新版から復元されます。

sudo bash restore-nextcloud.sh

特定の世代を指定して復元も可能です。

sudo bash restore-nextcloud.sh 20240101_120000.tar.gz

復元後、ONLYOFFICE DocsアドレスとJWTシークレットキーは自動入力されているはずなので、「管理者設定」-「ONLYOFFICE」で保存」ボタンだけ押して反映させてください。

タイトルとURLをコピーしました