複数での編集も可能なナレッジツール「Outline」

長かったメモアプリ探しも終わりになりそうです。
見た目が綺麗で使い勝手も良いので、しばらくはこれをメインで使おうと考えています。
それは「Outline」。こちらで公開されています。
インストールは、スクリプトファイルを作成して実行するだけ。ちなみに、後半で、バックアップやリストア用のスクリプトも含めて一括で作成する方法を紹介しているので、バックアップや復元機能も欲しければそちらを利用したほうが楽です。

sudo apt install nano
nano setup-outline.sh
#!/bin/bash
# =============================================================
#  Outline セルフホスト セットアップスクリプト
#  対象OS: Ubuntu 25.10 / インストール先: /opt/docker/outline
#
#  カスタマイズ箇所:
#   - 起動前にホスト名/IPを対話入力 → URL= に自動反映
#   - POSTGRES_PASSWORD を compose に直接埋め込み
#   - DATABASE_URL に ?sslmode=disable を付与
#   - FORCE_HTTPS=false を設定
#   - Mailhog を追加(ローカルSMTP、マジックリンク認証用)
#   - version フィールドを削除(obsolete警告を回避)
#   - data/storage を UID 1001 所有に設定(エクスポート権限エラーを回避)
#   - ACL で管理ユーザーが sudo なしで操作可能に
# =============================================================
set -e

INSTALL_DIR="/opt/docker/outline"

echo "================================================"
echo "  Outline セットアップ"
echo "  インストール先: $INSTALL_DIR"
echo "================================================"
echo ""

# ── ホスト名 / IP を入力 ─────────────────────────────────────
DETECTED=$(hostname -I | awk '{print $1}')
echo "アクセスURLを設定します。"
echo "例: 192.168.1.100  /  hostname  /  outline.example.com"
echo "(そのままEnterで検出されたIP: ${DETECTED} を使用)"
echo ""
read -rp "ホスト名またはIPアドレス: " INPUT_HOST
HOST="${INPUT_HOST:-$DETECTED}"
BASE_URL="http://${HOST}:3000"
echo ""
echo "▼ アクセスURL: ${BASE_URL}"
echo ""

# ── 既存環境をクリーンアップ ─────────────────────────────────
echo "▼ 既存環境をクリーンアップしています..."
if [ -f "$INSTALL_DIR/docker-compose.yml" ]; then
  sudo docker compose -f "$INSTALL_DIR/docker-compose.yml" down -v 2>/dev/null || true
fi
sudo rm -rf "$INSTALL_DIR"

# ── ディレクトリ作成 ─────────────────────────────────────────
sudo mkdir -p "$INSTALL_DIR"/{data/postgres,data/redis,data/storage}

# data/storage は Outline コンテナ内ユーザー(UID 1001)が書き込めるよう設定
# (これをしないとエクスポート時に permission denied が発生する)
sudo chown -R 1001:1001 "$INSTALL_DIR/data/storage"

# postgres・redis は一般ユーザー所有でOK
sudo chown -R "$USER":"$USER" "$INSTALL_DIR/data/postgres"
sudo chown -R "$USER":"$USER" "$INSTALL_DIR/data/redis"

# ACL で管理ユーザーが /opt/docker/outline 以下を sudo なしで操作可能に
if command -v setfacl &>/dev/null; then
  sudo setfacl -R -m u:"$USER":rwx "$INSTALL_DIR"
  sudo setfacl -d -m u:"$USER":rwx "$INSTALL_DIR"
  echo "✔ ACL を設定しました(sudo なしで操作可能)"
else
  # setfacl がない場合は acl パッケージをインストール
  sudo apt-get install -y acl &>/dev/null
  sudo setfacl -R -m u:"$USER":rwx "$INSTALL_DIR"
  sudo setfacl -d -m u:"$USER":rwx "$INSTALL_DIR"
  echo "✔ acl をインストールしてACLを設定しました"
fi

cd "$INSTALL_DIR"

# ── シークレットキーを自動生成 ──────────────────────────────
SECRET_KEY=$(openssl rand -hex 32)
UTILS_SECRET=$(openssl rand -hex 32)
POSTGRES_PASSWORD=$(openssl rand -hex 16)

echo "▼ シークレットキーを生成しました"
echo "  SECRET_KEY       : $SECRET_KEY"
echo "  UTILS_SECRET     : $UTILS_SECRET"
echo "  POSTGRES_PASSWORD: $POSTGRES_PASSWORD"
echo ""

# ── .env 作成 ────────────────────────────────────────────────
cat > "$INSTALL_DIR/.env" <<EOF
# ============================================================
#  Outline 環境変数
#  生成日時: $(date '+%Y-%m-%d %H:%M:%S')
# ============================================================

SECRET_KEY=${SECRET_KEY}
UTILS_SECRET=${UTILS_SECRET}

# アクセスURL
URL=${BASE_URL}
PORT=3000

# HTTPS強制を無効(HTTP運用時に必須)
FORCE_HTTPS=false

# DB接続(?sslmode=disable でローカルPostgresのSSLエラーを回避)
DATABASE_URL=postgres://outline:${POSTGRES_PASSWORD}@postgres:5432/outline?sslmode=disable

# Redis
REDIS_URL=redis://redis:6379

# ファイルストレージ(ローカル保存)
FILE_STORAGE=local
FILE_STORAGE_LOCAL_ROOT_DIR=/var/lib/outline/data
FILE_STORAGE_UPLOAD_MAX_SIZE=26214400

# SMTP(Mailhog ローカルSMTP)
SMTP_HOST=mailhog
SMTP_PORT=1025
SMTP_FROM_EMAIL=outline@example.com
SMTP_REPLY_EMAIL=outline@example.com
SMTP_SECURE=false

# 言語
DEFAULT_LANGUAGE=ja_JP

# ログ
LOG_LEVEL=info

# --- 外部認証プロバイダー(必要に応じてコメントを外す) ---
# [Slack]
# SLACK_CLIENT_ID=
# SLACK_CLIENT_SECRET=
#
# [Google]
# GOOGLE_CLIENT_ID=
# GOOGLE_CLIENT_SECRET=
EOF

echo "✔ .env を作成しました"

# ── docker-compose.yml 作成 ──────────────────────────────────
# POSTGRES_PASSWORD は値を直接埋め込む(env_file経由では environment: に展開されないため)
cat > "$INSTALL_DIR/docker-compose.yml" <<EOF
services:

  outline:
    image: outlinewiki/outline:latest
    container_name: outline
    restart: unless-stopped
    env_file: .env
    ports:
      - "3000:3000"
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
      mailhog:
        condition: service_started
    volumes:
      - ./data/storage:/var/lib/outline/data
    networks:
      - outline-net

  postgres:
    image: postgres:16-alpine
    container_name: outline-postgres
    restart: unless-stopped
    environment:
      POSTGRES_USER: outline
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: outline
    volumes:
      - ./data/postgres:/var/lib/postgresql/data
    networks:
      - outline-net
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U outline"]
      interval: 10s
      timeout: 5s
      retries: 5

  redis:
    image: redis:7-alpine
    container_name: outline-redis
    restart: unless-stopped
    command: redis-server --save 60 1 --loglevel warning
    volumes:
      - ./data/redis:/data
    networks:
      - outline-net
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5

  mailhog:
    image: mailhog/mailhog
    container_name: outline-mailhog
    restart: unless-stopped
    ports:
      - "8025:8025"
    networks:
      - outline-net

networks:
  outline-net:
    driver: bridge
EOF

echo "✔ docker-compose.yml を作成しました"

# ── 構文チェック ─────────────────────────────────────────────
docker compose config --quiet && echo "✔ docker-compose.yml 構文OK"

# ── 起動 ────────────────────────────────────────────────────
echo ""
echo "▼ コンテナを起動しています..."
docker compose up -d

echo ""
echo "▼ 起動を待機しています(15秒)..."
sleep 15
docker compose ps

echo ""
echo "================================================"
echo "  セットアップ完了!"
echo ""
echo "  Outline:  ${BASE_URL}"
echo "  Mailhog:  http://${HOST}:8025"
echo ""
echo "  ログイン手順:"
echo "  1. ${BASE_URL} を開く"
echo "  2. メールアドレスを入力して送信"
echo "  3. http://${HOST}:8025 でメールを確認"
echo "  4. メール内の「Sign In」をクリック"
echo ""
echo "  ログ確認: docker compose -f $INSTALL_DIR/docker-compose.yml logs -f outline"
echo "  停止:     docker compose -f $INSTALL_DIR/docker-compose.yml down"
echo "================================================"
sudo bash setup-outline.sh

起動したら、最初はワークスペース名、管理者名、メールアドレスを設定します。

さらに別のPCからアクセスする時は登録したメールアドレスを入力し、「http://ホスト名:8025」で確認してトークン付きのURLを取得します。

メールが送信されるようにする

外部SMTPサーバーの設定を追加すれば、メールがちゃんと送信されるようになります。
以下はGmailを使った方法の手順です。

Gmailで送信する場合に必要なもの

  • Gmailアドレス(例:yourname@gmail.com
  • アプリパスワード(Gmailのパスワードそのままではなく専用のもの)

アプリパスワードの取得手順

  1. https://myaccount.google.com/security を開く
  2. 「2段階認証プロセス」を有効化(必須)
  3. https://myaccount.google.com/apppasswords を開く
  4. アプリ名に「Outline」と入力して「作成」
  5. 表示される16桁のパスワードをコピー

取得できたら .env のSMTP設定をこう書き換えます。

sudo bash -c "
sed -i 's/SMTP_HOST=mailhog/SMTP_HOST=smtp.gmail.com/' /opt/docker/outline/.env
sed -i 's/SMTP_PORT=1025/SMTP_PORT=587/' /opt/docker/outline/.env
sed -i 's/SMTP_SECURE=false/SMTP_SECURE=true/' /opt/docker/outline/.env
sed -i 's/SMTP_FROM_EMAIL=outline@example.com/SMTP_FROM_EMAIL=yourname@gmail.com/' /opt/docker/outline/.env
sed -i 's/SMTP_REPLY_EMAIL=outline@example.com/SMTP_REPLY_EMAIL=yourname@gmail.com/' /opt/docker/outline/.env
echo 'SMTP_USERNAME=yourname@gmail.com' >> /opt/docker/outline/.env
echo 'SMTP_PASSWORD=取得した16桁のアプリパスワード' >> /opt/docker/outline/.env
"

Google OAuthによる認証を追加

メール認証だけでなく、Google OAuthを追加することも出来ます。同時に有効化し、ログイン画面に両方のボタンを表示させたりとか。Google OAuth の設定手順は次の通り。

① Google Cloud Console で認証情報を作成

  1. https://console.cloud.google.com/ を開く
  2. プロジェクトを作成(または既存を選択)
  3. 左メニュー →「APIとサービス」→「認証情報」
  4. 「認証情報を作成」→「OAuthクライアントID」
  5. アプリケーションの種類:ウェブアプリケーション
  6. 承認済みのリダイレクトURIに以下を追加:
   http://dellnt-sv:3000/auth/google.callback
  1. 作成後に表示される クライアントIDクライアントシークレット をコピー

.env に追記

クライアントIDとシークレットが取得できたら以下を実行。

# 取得した値に置き換えて実行
GOOGLE_CLIENT_ID="ここにクライアントID"
GOOGLE_CLIENT_SECRET="ここにクライアントシークレット"

sudo bash -c "
sed -i 's/# GOOGLE_CLIENT_ID=/GOOGLE_CLIENT_ID=${GOOGLE_CLIENT_ID}/' /opt/docker/outline/.env
sed -i 's/# GOOGLE_CLIENT_SECRET=/GOOGLE_CLIENT_SECRET=${GOOGLE_CLIENT_SECRET}/' /opt/docker/outline/.env
"

# 確認
grep GOOGLE /opt/docker/outline/.env

# 再起動
docker compose -f /opt/docker/outline/docker-compose.yml restart outline

設定場所の変更やパーミッションなど

.envの編集

 sudo nano /opt/docker/outline/.env
docker compose -f /opt/docker/outline/docker-compose.yml restart outline

権限を変更

セットアップの中で修正しているのでこの手順は必要ないはずですが、何かの拍子に権限が変更されてエラーが出た場合の修正方法。

# data/storage の権限を修正
sudo chown -R 1001:1001 /opt/docker/outline/data/storage

# 確認
ls -la /opt/docker/outline/data/

# outline コンテナを再起動
docker compose -f /opt/docker/outline/docker-compose.yml restart outline

バックアップと復元

PostgreSQLのダンプ+ストレージのtar圧縮を定期実行するスクリプトをcronで回して自動バックアップする方法です。
3つのスクリプトを作成して保存し(/opt/docker/outline/)、必要に応じて実行します。

各スクリプトの役割

ファイル役割
outline-backup.shバックアップ本体。DB+ストレージ+.envを1つのtarにまとめる
outline-restore.shバックアップから復元。実行前に確認プロンプトあり
outline-cron-setup.sh毎日2:00 AMにバックアップを自動実行するcronを登録

スクリプトを作成

outline-backup.sh
sudo nano /opt/docker/outline/outline-backup.sh
#!/bin/bash
# =============================================================
#  Outline バックアップスクリプト
#  保存先: /opt/docker/outline/backups/
#  推奨実行: 毎日深夜(cron設定は backup-cron-setup.sh を参照)
# =============================================================
set -e

INSTALL_DIR="/opt/docker/outline"
BACKUP_DIR="$INSTALL_DIR/backups"
DATE=$(date '+%Y%m%d_%H%M%S')
BACKUP_NAME="outline_backup_${DATE}"
KEEP_DAYS=7  # 何日分保持するか

echo "================================================"
echo "  Outline バックアップ開始: $DATE"
echo "================================================"

# ── バックアップ先ディレクトリ作成 ──────────────────────────
mkdir -p "$BACKUP_DIR/$BACKUP_NAME"

# ── PostgreSQL ダンプ ────────────────────────────────────────
echo "▼ データベースをバックアップしています..."
docker exec outline-postgres pg_dump -U outline outline \
  > "$BACKUP_DIR/$BACKUP_NAME/database.sql"
echo "✔ データベースダンプ完了"

# ── ストレージ(アップロードファイル)バックアップ ──────────
echo "▼ ストレージをバックアップしています..."
sudo tar -czf "$BACKUP_DIR/$BACKUP_NAME/storage.tar.gz" \
  -C "$INSTALL_DIR/data" storage
echo "✔ ストレージバックアップ完了"

# ── .env バックアップ(シークレットキー保全のため)──────────
sudo cp "$INSTALL_DIR/.env" "$BACKUP_DIR/$BACKUP_NAME/.env"
echo "✔ .env バックアップ完了"

# ── tar でまとめて圧縮 ───────────────────────────────────────
tar -czf "$BACKUP_DIR/${BACKUP_NAME}.tar.gz" \
  -C "$BACKUP_DIR" "$BACKUP_NAME"
rm -rf "$BACKUP_DIR/$BACKUP_NAME"
echo "✔ アーカイブ作成: $BACKUP_DIR/${BACKUP_NAME}.tar.gz"

# ── 古いバックアップを削除(KEEP_DAYS日より古いもの)────────
echo "▼ ${KEEP_DAYS}日以上前のバックアップを削除しています..."
find "$BACKUP_DIR" -name "outline_backup_*.tar.gz" \
  -mtime +${KEEP_DAYS} -delete
echo "✔ 古いバックアップを削除しました"

# ── 完了 ─────────────────────────────────────────────────────
BACKUP_SIZE=$(du -sh "$BACKUP_DIR/${BACKUP_NAME}.tar.gz" | cut -f1)
echo ""
echo "================================================"
echo "  バックアップ完了!"
echo "  ファイル: $BACKUP_DIR/${BACKUP_NAME}.tar.gz"
echo "  サイズ  : $BACKUP_SIZE"
echo "  保持数  : $(ls $BACKUP_DIR/outline_backup_*.tar.gz 2>/dev/null | wc -l) 件"
echo "================================================"

バックアップは7日分保持して古いものは自動削除されます。保持期間を変えたい場合は outline-backup.shKEEP_DAYS=7 を変更してください。

outline-restore.sh
sudo nano /opt/docker/outline/outline-restore.sh
#!/bin/bash
# =============================================================
#  Outline リストアスクリプト v2
#
#  使い方:
#    bash outline-restore.sh                        # 最新バックアップを自動選択
#    bash outline-restore.sh /path/to/backup.tar.gz # ファイルを直接指定
#
#  修正済み:
#   - DROP DATABASE を -d postgres で実行(currently open database エラーを回避)
#   - 引数省略時に backups/ 内の最新ファイルを自動選択して確認
# =============================================================
set -e

INSTALL_DIR="/opt/docker/outline"
BACKUP_DIR="$INSTALL_DIR/backups"

# ── バックアップファイルの決定 ───────────────────────────────
if [ -n "$1" ]; then
  # 引数で直接指定された場合
  BACKUP_FILE="$1"
else
  # 最新バックアップを自動検出
  BACKUP_FILE=$(ls -t "$BACKUP_DIR"/outline_backup_*.tar.gz 2>/dev/null | head -1)

  if [ -z "$BACKUP_FILE" ]; then
    echo "エラー: バックアップファイルが見つかりません: $BACKUP_DIR"
    exit 1
  fi

  BACKUP_SIZE=$(du -sh "$BACKUP_FILE" | cut -f1)
  BACKUP_DATE=$(stat -c '%y' "$BACKUP_FILE" | cut -d'.' -f1)

  echo "================================================"
  echo "  最新バックアップを検出しました"
  echo ""
  echo "  ファイル: $(basename $BACKUP_FILE)"
  echo "  サイズ  : $BACKUP_SIZE"
  echo "  作成日時: $BACKUP_DATE"
  echo "================================================"
  echo ""
fi

# ── ファイル存在チェック ─────────────────────────────────────
if [ ! -f "$BACKUP_FILE" ]; then
  echo "エラー: ファイルが見つかりません: $BACKUP_FILE"
  exit 1
fi

echo "================================================"
echo "  Outline リストア開始"
echo "  対象: $BACKUP_FILE"
echo "================================================"
echo ""
read -rp "本当にリストアしますか?現在のデータは上書きされます。(yes/no): " CONFIRM
if [ "$CONFIRM" != "yes" ]; then
  echo "キャンセルしました。"
  exit 0
fi

# ── 展開 ─────────────────────────────────────────────────────
WORK_DIR=$(mktemp -d)
echo "▼ バックアップを展開しています..."
tar -xzf "$BACKUP_FILE" -C "$WORK_DIR"
BACKUP_NAME=$(ls "$WORK_DIR")
BACKUP_PATH="$WORK_DIR/$BACKUP_NAME"
echo "✔ 展開完了"

# ── outline コンテナを停止 ───────────────────────────────────
echo "▼ outline コンテナを停止しています..."
docker compose -f "$INSTALL_DIR/docker-compose.yml" stop outline
echo "✔ 停止しました"

# ── データベースリストア ─────────────────────────────────────
# -d postgres を使うことで「currently open database」エラーを回避
echo "▼ データベースをリストアしています..."
docker exec -i outline-postgres psql -U outline -d postgres \
  -c "DROP DATABASE IF EXISTS outline;"
docker exec -i outline-postgres psql -U outline -d postgres \
  -c "CREATE DATABASE outline;"
docker exec -i outline-postgres psql -U outline -d outline \
  < "$BACKUP_PATH/database.sql"
echo "✔ データベースリストア完了"

# ── ストレージリストア ───────────────────────────────────────
echo "▼ ストレージをリストアしています..."
sudo rm -rf "$INSTALL_DIR/data/storage"
sudo tar -xzf "$BACKUP_PATH/storage.tar.gz" -C "$INSTALL_DIR/data"
sudo chown -R 1001:1001 "$INSTALL_DIR/data/storage"
echo "✔ ストレージリストア完了"

# ── クリーンアップ ───────────────────────────────────────────
rm -rf "$WORK_DIR"

# ── outline コンテナを再起動 ─────────────────────────────────
echo "▼ outline コンテナを再起動しています..."
docker compose -f "$INSTALL_DIR/docker-compose.yml" start outline
sleep 5
docker compose -f "$INSTALL_DIR/docker-compose.yml" ps

echo ""
echo "================================================"
echo "  リストア完了!"
echo "================================================"
outline-cron-setup.sh
sudo nano /opt/docker/outline/outline-cron-setup.sh
#!/bin/bash
# =============================================================
#  Outline バックアップ cron 設定スクリプト
#  毎日深夜2時に自動バックアップを実行します
# =============================================================
set -e

SCRIPT_DIR="/opt/docker/outline"
BACKUP_SCRIPT="$SCRIPT_DIR/outline-backup.sh"
LOG_FILE="$SCRIPT_DIR/backups/backup.log"

# ── バックアップスクリプトを所定の場所にコピー ──────────────
echo "▼ バックアップスクリプトを配置しています..."
sudo cp "$(dirname "$0")/outline-backup.sh" "$BACKUP_SCRIPT"
sudo chmod +x "$BACKUP_SCRIPT"
echo "✔ 配置完了: $BACKUP_SCRIPT"

# ── backups ディレクトリ作成 ─────────────────────────────────
mkdir -p "$SCRIPT_DIR/backups"

# ── cron に登録(毎日 2:00 AM)───────────────────────────────
CRON_JOB="0 2 * * * bash $BACKUP_SCRIPT >> $LOG_FILE 2>&1"

# 既存の同じジョブがあれば削除してから追加
( crontab -l 2>/dev/null | grep -v "$BACKUP_SCRIPT" ; echo "$CRON_JOB" ) | crontab -

echo "✔ cron を登録しました"
echo ""
echo "================================================"
echo "  cron 設定完了!"
echo ""
echo "  実行スケジュール: 毎日 2:00 AM"
echo "  バックアップ保存先: $SCRIPT_DIR/backups/"
echo "  ログ: $LOG_FILE"
echo "  保持期間: 7日間"
echo ""
echo "  今すぐ手動実行する場合:"
echo "  bash $BACKUP_SCRIPT"
echo ""
echo "  cron 登録確認:"
echo "  crontab -l"
echo ""
echo "  スケジュール変更例(/etc/crontab 書式):"
echo "  毎時        : 0 * * * *"
echo "  毎日 2:00   : 0 2 * * *"
echo "  毎週日曜    : 0 2 * * 0"
echo "  毎月1日     : 0 2 1 * *"
echo "================================================"

手動バックアップ・リストア

まずスクリプトの実行権限を付与。
さらに、バックアップ先を作成します。
backups ディレクトリを作成して権限を付与

sudo chmod +x /opt/docker/outline/outline-*.sh
sudo mkdir -p /opt/docker/outline/backups
sudo chown "$USER":"$USER" /opt/docker/outline/backups

今すぐバックアップ

bash /opt/docker/outline/outline-backup.sh

リストア(ファイルを指定)

最新を自動選択して確認プロンプト

bash /opt/docker/outline/outline-restore.sh 

ファイルを直接指定してリストア

bash /opt/docker/outline/outline-restore.sh /opt/docker/outline/backups/outline_backup_20260321_020000.tar.gz

バックアップをスケジュール登録

# cron 登録
bash /opt/docker/outline/outline-cron-setup.sh

# 登録確認
crontab -l
# cron をインストール
sudo apt-get install -y cron

# cron サービスを起動・自動起動設定
sudo systemctl enable cron
sudo systemctl start cron

# cron を直接登録
echo "0 2 * * * bash /opt/docker/outline/outline-backup.sh >> /opt/docker/outline/backups/backup.log 2>&1" | crontab -

# 登録確認
crontab -l

`crontab -l` でこう表示されればOKです。
0 2 * * * bash /opt/docker/outline/outline-backup.sh >> /opt/docker/outline/backups/backup.log 2>&1

バックアップや復元まで含めた一括スクリプト

全部まとめた完全版を作成します。その前に、Dockhandが3000ポートを使用していたので3333ポートに変更しておきました。

cd opt/docker/dockhand
nano docker-compose.yml 
docker compose up -d

それでは一括スクリプトを作成して実行します。;

nano setup-outline.sh
#!/bin/bash
# =============================================================
#  Outline セルフホスト 完全セットアップスクリプト
#  対象OS: Ubuntu 25.10 / インストール先: /opt/docker/outline
#
#  実施内容:
#   1. Outline インストール(メール認証 / Mailhog)
#   2. バックアップスクリプト作成・配置
#   3. リストアスクリプト作成・配置
#   4. cron 未インストールなら自動インストール
#   5. 毎日 2:00 AM バックアップを cron に登録
#
#   - backups/ ディレクトリの権限を確実に一般ユーザー所有に設定
#   - スクリプトに実行権限を付与
#   - cron 登録を一般ユーザーのcrontabに登録
#   - リストア時の chown を sudo で実行
# =============================================================

# sudo で実行されている場合は実行ユーザーを特定
if [ -n "$SUDO_USER" ]; then
  REAL_USER="$SUDO_USER"
  REAL_HOME=$(eval echo "~$SUDO_USER")
else
  REAL_USER="$USER"
  REAL_HOME="$HOME"
fi

INSTALL_DIR="/opt/docker/outline"
BACKUP_DIR="$INSTALL_DIR/backups"

echo "================================================"
echo "  Outline 完全セットアップ"
echo "  インストール先: $INSTALL_DIR"
echo "  実行ユーザー  : $REAL_USER"
echo "================================================"
echo ""

# ── ホスト名 / IP を入力 ─────────────────────────────────────
DETECTED=$(hostname -I | awk '{print $1}')
echo "アクセスURLを設定します。"
echo "例: 192.168.1.100  /  hostname  /  outline.example.com"
echo "(そのままEnterで検出されたIP: ${DETECTED} を使用)"
echo ""
read -rp "ホスト名またはIPアドレス: " INPUT_HOST
HOST="${INPUT_HOST:-$DETECTED}"
BASE_URL="http://${HOST}:3000"
echo ""
echo "▼ アクセスURL: ${BASE_URL}"
echo ""

# ================================================================
#  STEP 1: 既存環境をクリーンアップ
# ================================================================
echo "------------------------------------------------"
echo "  STEP 1: 既存環境をクリーンアップ"
echo "------------------------------------------------"
if [ -f "$INSTALL_DIR/docker-compose.yml" ]; then
  docker compose -f "$INSTALL_DIR/docker-compose.yml" down -v 2>/dev/null || true
fi
rm -rf "$INSTALL_DIR"
echo "✔ クリーンアップ完了"

# ================================================================
#  STEP 2: ディレクトリ作成・権限設定
# ================================================================
echo "------------------------------------------------"
echo "  STEP 2: ディレクトリ作成・権限設定"
echo "------------------------------------------------"

mkdir -p "$INSTALL_DIR"/{data/postgres,data/redis,data/storage}
mkdir -p "$BACKUP_DIR"

# data/storage は Outline コンテナ内ユーザー(UID 1001)が書き込めるよう設定
# (設定しないとエクスポート時に permission denied が発生する)
chown -R 1001:1001 "$INSTALL_DIR/data/storage"
chown -R "$REAL_USER":"$REAL_USER" "$INSTALL_DIR/data/postgres"
chown -R "$REAL_USER":"$REAL_USER" "$INSTALL_DIR/data/redis"
chown -R "$REAL_USER":"$REAL_USER" "$BACKUP_DIR"

# ACL で管理ユーザーが sudo なしで操作可能に
if ! command -v setfacl &>/dev/null; then
  apt-get install -y acl &>/dev/null
fi
setfacl -R -m u:"$REAL_USER":rwx "$INSTALL_DIR"
setfacl -d -m u:"$REAL_USER":rwx "$INSTALL_DIR"

echo "✔ ディレクトリ・権限設定完了"

cd "$INSTALL_DIR"

# ================================================================
#  STEP 3: シークレットキー生成
# ================================================================
echo "------------------------------------------------"
echo "  STEP 3: シークレットキー生成"
echo "------------------------------------------------"

SECRET_KEY=$(openssl rand -hex 32)
UTILS_SECRET=$(openssl rand -hex 32)
POSTGRES_PASSWORD=$(openssl rand -hex 16)

echo "✔ SECRET_KEY       : $SECRET_KEY"
echo "✔ UTILS_SECRET     : $UTILS_SECRET"
echo "✔ POSTGRES_PASSWORD: $POSTGRES_PASSWORD"

# ================================================================
#  STEP 4: .env 作成
# ================================================================
echo "------------------------------------------------"
echo "  STEP 4: .env 作成"
echo "------------------------------------------------"

cat > "$INSTALL_DIR/.env" <<EOF
# ============================================================
#  Outline 環境変数
#  生成日時: $(date '+%Y-%m-%d %H:%M:%S')
# ============================================================

SECRET_KEY=${SECRET_KEY}
UTILS_SECRET=${UTILS_SECRET}

# アクセスURL
URL=${BASE_URL}
PORT=3000

# HTTPS強制を無効(HTTP運用時に必須)
FORCE_HTTPS=false

# DB接続(?sslmode=disable でローカルPostgresのSSLエラーを回避)
DATABASE_URL=postgres://outline:${POSTGRES_PASSWORD}@postgres:5432/outline?sslmode=disable

# Redis
REDIS_URL=redis://redis:6379

# ファイルストレージ(ローカル保存)
FILE_STORAGE=local
FILE_STORAGE_LOCAL_ROOT_DIR=/var/lib/outline/data
FILE_STORAGE_UPLOAD_MAX_SIZE=26214400

# SMTP(Mailhog ローカルSMTP)
SMTP_HOST=mailhog
SMTP_PORT=1025
SMTP_FROM_EMAIL=outline@example.com
SMTP_REPLY_EMAIL=outline@example.com
SMTP_SECURE=false

# 言語
DEFAULT_LANGUAGE=ja_JP

# ログ
LOG_LEVEL=info

# --- 外部認証プロバイダー(必要に応じてコメントを外す) ---
# [Slack]
# SLACK_CLIENT_ID=
# SLACK_CLIENT_SECRET=
#
# [Google]
# GOOGLE_CLIENT_ID=
# GOOGLE_CLIENT_SECRET=
EOF

echo "✔ .env 作成完了"

# ================================================================
#  STEP 5: docker-compose.yml 作成
# ================================================================
echo "------------------------------------------------"
echo "  STEP 5: docker-compose.yml 作成"
echo "------------------------------------------------"

# POSTGRES_PASSWORD は値を直接埋め込む(env_file経由では environment: に展開されないため)
cat > "$INSTALL_DIR/docker-compose.yml" <<EOF
services:

  outline:
    image: outlinewiki/outline:latest
    container_name: outline
    restart: unless-stopped
    env_file: .env
    ports:
      - "3000:3000"
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
      mailhog:
        condition: service_started
    volumes:
      - ./data/storage:/var/lib/outline/data
    networks:
      - outline-net

  postgres:
    image: postgres:16-alpine
    container_name: outline-postgres
    restart: unless-stopped
    environment:
      POSTGRES_USER: outline
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: outline
    volumes:
      - ./data/postgres:/var/lib/postgresql/data
    networks:
      - outline-net
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U outline"]
      interval: 10s
      timeout: 5s
      retries: 5

  redis:
    image: redis:7-alpine
    container_name: outline-redis
    restart: unless-stopped
    command: redis-server --save 60 1 --loglevel warning
    volumes:
      - ./data/redis:/data
    networks:
      - outline-net
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5

  mailhog:
    image: mailhog/mailhog
    container_name: outline-mailhog
    restart: unless-stopped
    ports:
      - "8025:8025"
    networks:
      - outline-net

networks:
  outline-net:
    driver: bridge
EOF

docker compose config --quiet && echo "✔ docker-compose.yml 作成完了"

# ================================================================
#  STEP 6: バックアップスクリプト作成
# ================================================================
echo "------------------------------------------------"
echo "  STEP 6: バックアップスクリプト作成"
echo "------------------------------------------------"

cat > "$INSTALL_DIR/outline-backup.sh" <<'BACKUP_SCRIPT'
#!/bin/bash
# Outline バックアップスクリプト
set -e

INSTALL_DIR="/opt/docker/outline"
BACKUP_DIR="$INSTALL_DIR/backups"
DATE=$(date '+%Y%m%d_%H%M%S')
BACKUP_NAME="outline_backup_${DATE}"
KEEP_DAYS=7

echo "================================================"
echo "  Outline バックアップ開始: $DATE"
echo "================================================"

mkdir -p "$BACKUP_DIR/$BACKUP_NAME"

# PostgreSQL ダンプ
echo "▼ データベースをバックアップしています..."
docker exec outline-postgres pg_dump -U outline outline \
  > "$BACKUP_DIR/$BACKUP_NAME/database.sql"
echo "✔ データベースダンプ完了"

# ストレージバックアップ
echo "▼ ストレージをバックアップしています..."
tar -czf "$BACKUP_DIR/$BACKUP_NAME/storage.tar.gz" \
  -C "$INSTALL_DIR/data" storage
echo "✔ ストレージバックアップ完了"

# .env バックアップ
cp "$INSTALL_DIR/.env" "$BACKUP_DIR/$BACKUP_NAME/.env"
echo "✔ .env バックアップ完了"

# tar でまとめて圧縮
tar -czf "$BACKUP_DIR/${BACKUP_NAME}.tar.gz" \
  -C "$BACKUP_DIR" "$BACKUP_NAME"
rm -rf "$BACKUP_DIR/$BACKUP_NAME"

# 古いバックアップを削除
find "$BACKUP_DIR" -name "outline_backup_*.tar.gz" \
  -mtime +${KEEP_DAYS} -delete

BACKUP_SIZE=$(du -sh "$BACKUP_DIR/${BACKUP_NAME}.tar.gz" | cut -f1)
echo ""
echo "================================================"
echo "  バックアップ完了!"
echo "  ファイル: $BACKUP_DIR/${BACKUP_NAME}.tar.gz"
echo "  サイズ  : $BACKUP_SIZE"
echo "  保持数  : $(ls $BACKUP_DIR/outline_backup_*.tar.gz 2>/dev/null | wc -l) 件"
echo "================================================"
BACKUP_SCRIPT

chmod +x "$INSTALL_DIR/outline-backup.sh"
echo "✔ outline-backup.sh 作成完了"

# ================================================================
#  STEP 7: リストアスクリプト作成
# ================================================================
echo "------------------------------------------------"
echo "  STEP 7: リストアスクリプト作成"
echo "------------------------------------------------"

cat > "$INSTALL_DIR/outline-restore.sh" <<'RESTORE_SCRIPT'
#!/bin/bash
# Outline リストアスクリプト
# 使い方:
#   sudo bash outline-restore.sh                        # 最新バックアップを自動選択
#   sudo bash outline-restore.sh /path/to/backup.tar.gz # ファイルを直接指定
set -e

INSTALL_DIR="/opt/docker/outline"
BACKUP_DIR="$INSTALL_DIR/backups"

# バックアップファイルの決定
if [ -n "$1" ]; then
  BACKUP_FILE="$1"
else
  BACKUP_FILE=$(ls -t "$BACKUP_DIR"/outline_backup_*.tar.gz 2>/dev/null | head -1)
  if [ -z "$BACKUP_FILE" ]; then
    echo "エラー: バックアップファイルが見つかりません: $BACKUP_DIR"
    exit 1
  fi
  BACKUP_SIZE=$(du -sh "$BACKUP_FILE" | cut -f1)
  BACKUP_DATE=$(stat -c '%y' "$BACKUP_FILE" | cut -d'.' -f1)
  echo "================================================"
  echo "  最新バックアップを検出しました"
  echo ""
  echo "  ファイル: $(basename $BACKUP_FILE)"
  echo "  サイズ  : $BACKUP_SIZE"
  echo "  作成日時: $BACKUP_DATE"
  echo "================================================"
  echo ""
fi

if [ ! -f "$BACKUP_FILE" ]; then
  echo "エラー: ファイルが見つかりません: $BACKUP_FILE"
  exit 1
fi

echo "================================================"
echo "  Outline リストア開始"
echo "  対象: $BACKUP_FILE"
echo "================================================"
echo ""
read -rp "本当にリストアしますか?現在のデータは上書きされます。(yes/no): " CONFIRM
if [ "$CONFIRM" != "yes" ]; then
  echo "キャンセルしました。"
  exit 0
fi

# 展開
WORK_DIR=$(mktemp -d)
echo "▼ バックアップを展開しています..."
tar -xzf "$BACKUP_FILE" -C "$WORK_DIR"
BACKUP_NAME=$(ls "$WORK_DIR")
BACKUP_PATH="$WORK_DIR/$BACKUP_NAME"
echo "✔ 展開完了"

# outline コンテナを停止
echo "▼ outline コンテナを停止しています..."
docker compose -f "$INSTALL_DIR/docker-compose.yml" stop outline
echo "✔ 停止しました"

# データベースリストア(-d postgres で currently open database エラーを回避)
echo "▼ データベースをリストアしています..."
docker exec -i outline-postgres psql -U outline -d postgres \
  -c "DROP DATABASE IF EXISTS outline;"
docker exec -i outline-postgres psql -U outline -d postgres \
  -c "CREATE DATABASE outline;"
docker exec -i outline-postgres psql -U outline -d outline \
  < "$BACKUP_PATH/database.sql"
echo "✔ データベースリストア完了"

# ストレージリストア
echo "▼ ストレージをリストアしています..."
rm -rf "$INSTALL_DIR/data/storage"
tar -xzf "$BACKUP_PATH/storage.tar.gz" -C "$INSTALL_DIR/data"
# UID 1001 所有に設定(sudo が必要)
sudo chown -R 1001:1001 "$INSTALL_DIR/data/storage"
echo "✔ ストレージリストア完了"

# クリーンアップ
rm -rf "$WORK_DIR"

# outline コンテナを再起動
echo "▼ outline コンテナを再起動しています..."
docker compose -f "$INSTALL_DIR/docker-compose.yml" start outline
sleep 5
docker compose -f "$INSTALL_DIR/docker-compose.yml" ps

echo ""
echo "================================================"
echo "  リストア完了!"
echo "================================================"
RESTORE_SCRIPT

chmod +x "$INSTALL_DIR/outline-restore.sh"
echo "✔ outline-restore.sh 作成完了"

# ================================================================
#  STEP 8: cron 設定
# ================================================================
echo "------------------------------------------------"
echo "  STEP 8: cron 設定"
echo "------------------------------------------------"

# cron 未インストールなら自動インストール
if ! command -v crontab &>/dev/null; then
  echo "▼ cron をインストールしています..."
  apt-get install -y cron &>/dev/null
  systemctl enable cron
  systemctl start cron
  echo "✔ cron インストール完了"
fi

# 一般ユーザーのcrontabに登録(sudo -u で実行ユーザーとして登録)
CRON_JOB="0 2 * * * bash $INSTALL_DIR/outline-backup.sh >> $BACKUP_DIR/backup.log 2>&1"
( sudo -u "$REAL_USER" crontab -l 2>/dev/null | grep -v "outline-backup.sh" ; echo "$CRON_JOB" ) \
  | sudo -u "$REAL_USER" crontab -

echo "✔ cron 登録完了(毎日 2:00 AM / ユーザー: $REAL_USER)"

# ================================================================
#  STEP 9: Outline 起動
# ================================================================
echo "------------------------------------------------"
echo "  STEP 9: Outline 起動"
echo "------------------------------------------------"

docker compose up -d

echo ""
echo "▼ 起動を待機しています(15秒)..."
sleep 15
docker compose ps

# ================================================================
#  完了
# ================================================================
echo ""
echo "================================================"
echo "  セットアップ完了!"
echo ""
echo "  Outline : ${BASE_URL}"
echo "  Mailhog : http://${HOST}:8025"
echo ""
echo "  ログイン手順:"
echo "  1. ${BASE_URL} を開く"
echo "  2. メールアドレスを入力して送信"
echo "  3. http://${HOST}:8025 でメールを確認"
echo "  4. メール内の「Sign In」をクリック"
echo ""
echo "  バックアップ:"
echo "  手動実行 : bash $INSTALL_DIR/outline-backup.sh"
echo "  自動実行 : 毎日 2:00 AM(ログ: $BACKUP_DIR/backup.log)"
echo "  リストア : sudo bash $INSTALL_DIR/outline-restore.sh"
echo "  cron確認 : crontab -l"
echo ""
echo "  ログ確認 : docker compose -f $INSTALL_DIR/docker-compose.yml logs -f outline"
echo "  停止     : docker compose -f $INSTALL_DIR/docker-compose.yml down"
echo "================================================"
sudo bash setup-outline.sh

実行するとホスト名を聞いてきて、あとは自動で全部やります。

実施内容(STEP順)

STEP内容
1既存環境のクリーンアップ
2ディレクトリ作成・権限設定(UID 1001 / ACL)
3シークレットキー自動生成
4.env 作成
5docker-compose.yml 作成
6outline-backup.sh 作成・配置
7outline-restore.sh 作成・配置
8cron 未インストールなら自動インストール → 毎日2:00 AMに登録
9Outline 起動

完了後に使えるコマンド

手動バックアップ

bash /opt/docker/outline/outline-backup.sh

最新から復元

sudo bash /opt/docker/outline/outline-restore.sh

cron確認

crontab -l

おまけ – 1コピペでインストール

コピペで実行出来るようにスクリプト全体をbase64エンコードして1行の文字列に変換しています。内容はOutline紹介時の内容と全く同一です。

sudo bash -c "$(echo 'IyEvYmluL2Jhc2gKIyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CiMgIE91dGxpbmUg44K744Or44OV44Ob44K544OIIOWujOWFqOOCu+ODg+ODiOOCouODg+ODl+OCueOCr+ODquODl+ODiAojICDlr77osaFPUzogVWJ1bnR1IDI1LjEwIC8g44Kk44Oz44K544OI44O844Or5YWIOiAvb3B0L2RvY2tlci9vdXRsaW5lCiMKIyAg5a6f5pa95YaF5a65OgojICAgMS4gT3V0bGluZSDjgqTjg7Pjgrnjg4jjg7zjg6vvvIjjg6Hjg7zjg6voqo3oqLwgLyBNYWlsaG9n77yJCiMgICAyLiDjg5Djg4Pjgq/jgqLjg4Pjg5fjgrnjgq/jg6rjg5fjg4jkvZzmiJDjg7vphY3nva4KIyAgIDMuIOODquOCueODiOOCouOCueOCr+ODquODl+ODiOS9nOaIkOODu+mFjee9rgojICAgNC4gY3JvbiDmnKrjgqTjg7Pjgrnjg4jjg7zjg6vjgarjgonoh6rli5XjgqTjg7Pjgrnjg4jjg7zjg6sKIyAgIDUuIOavjuaXpSAyOjAwIEFNIOODkOODg+OCr+OCouODg+ODl+OCkiBjcm9uIOOBq+eZu+mMsgojCiMgICAtIGJhY2t1cHMvIOODh+OCo+ODrOOCr+ODiOODquOBruaoqemZkOOCkueiuuWun+OBq+S4gOiIrOODpuODvOOCtuODvOaJgOacieOBq+ioreWumgojICAgLSDjgrnjgq/jg6rjg5fjg4jjgavlrp/ooYzmqKnpmZDjgpLku5jkuI4KIyAgIC0gY3JvbiDnmbvpjLLjgpLkuIDoiKzjg6bjg7zjgrbjg7zjga5jcm9udGFi44Gr55m76YyyCiMgICAtIOODquOCueODiOOCouaZguOBriBjaG93biDjgpIgc3VkbyDjgaflrp/ooYwKIyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CgojIHN1ZG8g44Gn5a6f6KGM44GV44KM44Gm44GE44KL5aC05ZCI44Gv5a6f6KGM44Om44O844K244O844KS54m55a6aCmlmIFsgLW4gIiRTVURPX1VTRVIiIF07IHRoZW4KICBSRUFMX1VTRVI9IiRTVURPX1VTRVIiCiAgUkVBTF9IT01FPSQoZXZhbCBlY2hvICJ+JFNVRE9fVVNFUiIpCmVsc2UKICBSRUFMX1VTRVI9IiRVU0VSIgogIFJFQUxfSE9NRT0iJEhPTUUiCmZpCgpJTlNUQUxMX0RJUj0iL29wdC9kb2NrZXIvb3V0bGluZSIKQkFDS1VQX0RJUj0iJElOU1RBTExfRElSL2JhY2t1cHMiCgplY2hvICI9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0iCmVjaG8gIiAgT3V0bGluZSDlrozlhajjgrvjg4Pjg4jjgqLjg4Pjg5ciCmVjaG8gIiAg44Kk44Oz44K544OI44O844Or5YWIOiAkSU5TVEFMTF9ESVIiCmVjaG8gIiAg5a6f6KGM44Om44O844K244O8ICA6ICRSRUFMX1VTRVIiCmVjaG8gIj09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PSIKZWNobyAiIgoKIyDilIDilIAg44Ob44K544OI5ZCNIC8gSVAg44KS5YWl5YqbIOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgApERVRFQ1RFRD0kKGhvc3RuYW1lIC1JIHwgYXdrICd7cHJpbnQgJDF9JykKZWNobyAi44Ki44Kv44K744K5VVJM44KS6Kit5a6a44GX44G+44GZ44CCIgplY2hvICLkvos6IDE5Mi4xNjguMS4xMDAgIC8gIGhvc3RuYW1lICAvICBvdXRsaW5lLmV4YW1wbGUuY29tIgplY2hvICLvvIjjgZ3jga7jgb7jgb5FbnRlcuOBp+aknOWHuuOBleOCjOOBn0lQOiAke0RFVEVDVEVEfSDjgpLkvb/nlKjvvIkiCmVjaG8gIiIKcmVhZCAtcnAgIuODm+OCueODiOWQjeOBvuOBn+OBr0lQ44Ki44OJ44Os44K5OiAiIElOUFVUX0hPU1QKSE9TVD0iJHtJTlBVVF9IT1NUOi0kREVURUNURUR9IgpCQVNFX1VSTD0iaHR0cDovLyR7SE9TVH06MzAwMCIKZWNobyAiIgplY2hvICLilrwg44Ki44Kv44K744K5VVJMOiAke0JBU0VfVVJMfSIKZWNobyAiIgoKIyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CiMgIFNURVAgMTog5pei5a2Y55Kw5aKD44KS44Kv44Oq44O844Oz44Ki44OD44OXCiMgPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQplY2hvICItLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0iCmVjaG8gIiAgU1RFUCAxOiDml6LlrZjnkrDlooPjgpLjgq/jg6rjg7zjg7PjgqLjg4Pjg5ciCmVjaG8gIi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSIKaWYgWyAtZiAiJElOU1RBTExfRElSL2RvY2tlci1jb21wb3NlLnltbCIgXTsgdGhlbgogIGRvY2tlciBjb21wb3NlIC1mICIkSU5TVEFMTF9ESVIvZG9ja2VyLWNvbXBvc2UueW1sIiBkb3duIC12IDI+L2Rldi9udWxsIHx8IHRydWUKZmkKcm0gLXJmICIkSU5TVEFMTF9ESVIiCmVjaG8gIiDjgq/jg6rjg7zjg7PjgqLjg4Pjg5flrozkuoYiCgojID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KIyAgU1RFUCAyOiDjg4fjgqPjg6zjgq/jg4jjg6rkvZzmiJDjg7vmqKnpmZDoqK3lrpoKIyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CmVjaG8gIi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSIKZWNobyAiICBTVEVQIDI6IOODh+OCo+ODrOOCr+ODiOODquS9nOaIkOODu+aoqemZkOioreWumiIKZWNobyAiLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tIgoKbWtkaXIgLXAgIiRJTlNUQUxMX0RJUiIve2RhdGEvcG9zdGdyZXMsZGF0YS9yZWRpcyxkYXRhL3N0b3JhZ2V9Cm1rZGlyIC1wICIkQkFDS1VQX0RJUiIKCiMgZGF0YS9zdG9yYWdlIOOBryBPdXRsaW5lIOOCs+ODs+ODhuODiuWGheODpuODvOOCtuODvChVSUQgMTAwMSnjgYzmm7jjgY3ovrzjgoHjgovjgojjgYboqK3lrpoKIyDvvIjoqK3lrprjgZfjgarjgYTjgajjgqjjgq/jgrnjg53jg7zjg4jmmYLjgasgcGVybWlzc2lvbiBkZW5pZWQg44GM55m655Sf44GZ44KL77yJCmNob3duIC1SIDEwMDE6MTAwMSAiJElOU1RBTExfRElSL2RhdGEvc3RvcmFnZSIKY2hvd24gLVIgIiRSRUFMX1VTRVIiOiIkUkVBTF9VU0VSIiAiJElOU1RBTExfRElSL2RhdGEvcG9zdGdyZXMiCmNob3duIC1SICIkUkVBTF9VU0VSIjoiJFJFQUxfVVNFUiIgIiRJTlNUQUxMX0RJUi9kYXRhL3JlZGlzIgpjaG93biAtUiAiJFJFQUxfVVNFUiI6IiRSRUFMX1VTRVIiICIkQkFDS1VQX0RJUiIKCiMgQUNMIOOBp+euoeeQhuODpuODvOOCtuODvOOBjCBzdWRvIOOBquOBl+OBp+aTjeS9nOWPr+iDveOBqwppZiAhIGNvbW1hbmQgLXYgc2V0ZmFjbCAmPi9kZXYvbnVsbDsgdGhlbgogIGFwdC1nZXQgaW5zdGFsbCAteSBhY2wgJj4vZGV2L251bGwKZmkKc2V0ZmFjbCAtUiAtbSB1OiIkUkVBTF9VU0VSIjpyd3ggIiRJTlNUQUxMX0RJUiIKc2V0ZmFjbCAtZCAtbSB1OiIkUkVBTF9VU0VSIjpyd3ggIiRJTlNUQUxMX0RJUiIKCmVjaG8gIiDjg4fjgqPjg6zjgq/jg4jjg6rjg7vmqKnpmZDoqK3lrprlrozkuoYiCgpjZCAiJElOU1RBTExfRElSIgoKIyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CiMgIFNURVAgMzog44K344O844Kv44Os44OD44OI44Kt44O855Sf5oiQCiMgPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQplY2hvICItLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0iCmVjaG8gIiAgU1RFUCAzOiDjgrfjg7zjgq/jg6zjg4Pjg4jjgq3jg7znlJ/miJAiCmVjaG8gIi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSIKClNFQ1JFVF9LRVk9JChvcGVuc3NsIHJhbmQgLWhleCAzMikKVVRJTFNfU0VDUkVUPSQob3BlbnNzbCByYW5kIC1oZXggMzIpClBPU1RHUkVTX1BBU1NXT1JEPSQob3BlbnNzbCByYW5kIC1oZXggMTYpCgplY2hvICIgU0VDUkVUX0tFWSAgICAgICA6ICRTRUNSRVRfS0VZIgplY2hvICIgVVRJTFNfU0VDUkVUICAgICA6ICRVVElMU19TRUNSRVQiCmVjaG8gIiBQT1NUR1JFU19QQVNTV09SRDogJFBPU1RHUkVTX1BBU1NXT1JEIgoKIyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CiMgIFNURVAgNDogLmVudiDkvZzmiJAKIyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CmVjaG8gIi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSIKZWNobyAiICBTVEVQIDQ6IC5lbnYg5L2c5oiQIgplY2hvICItLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0iCgpjYXQgPiAiJElOU1RBTExfRElSLy5lbnYiIDw8RU9GCiMgPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CiMgIE91dGxpbmUg55Kw5aKD5aSJ5pWwCiMgIOeUn+aIkOaXpeaZgjogJChkYXRlICcrJVktJW0tJWQgJUg6JU06JVMnKQojID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQoKU0VDUkVUX0tFWT0ke1NFQ1JFVF9LRVl9ClVUSUxTX1NFQ1JFVD0ke1VUSUxTX1NFQ1JFVH0KCiMg44Ki44Kv44K744K5VVJMClVSTD0ke0JBU0VfVVJMfQpQT1JUPTMwMDAKCiMgSFRUUFPlvLfliLbjgpLnhKHlirnvvIhIVFRQ6YGL55So5pmC44Gr5b+F6aCI77yJCkZPUkNFX0hUVFBTPWZhbHNlCgojIERC5o6l57aa77yIP3NzbG1vZGU9ZGlzYWJsZSDjgafjg63jg7zjgqvjg6tQb3N0Z3Jlc+OBrlNTTOOCqOODqeODvOOCkuWbnumBv++8iQpEQVRBQkFTRV9VUkw9cG9zdGdyZXM6Ly9vdXRsaW5lOiR7UE9TVEdSRVNfUEFTU1dPUkR9QHBvc3RncmVzOjU0MzIvb3V0bGluZT9zc2xtb2RlPWRpc2FibGUKCiMgUmVkaXMKUkVESVNfVVJMPXJlZGlzOi8vcmVkaXM6NjM3OQoKIyDjg5XjgqHjgqTjg6vjgrnjg4jjg6zjg7zjgrjvvIjjg63jg7zjgqvjg6vkv53lrZjvvIkKRklMRV9TVE9SQUdFPWxvY2FsCkZJTEVfU1RPUkFHRV9MT0NBTF9ST09UX0RJUj0vdmFyL2xpYi9vdXRsaW5lL2RhdGEKRklMRV9TVE9SQUdFX1VQTE9BRF9NQVhfU0laRT0yNjIxNDQwMAoKIyBTTVRQ77yITWFpbGhvZyDjg63jg7zjgqvjg6tTTVRQ77yJClNNVFBfSE9TVD1tYWlsaG9nClNNVFBfUE9SVD0xMDI1ClNNVFBfRlJPTV9FTUFJTD1vdXRsaW5lQGV4YW1wbGUuY29tClNNVFBfUkVQTFlfRU1BSUw9b3V0bGluZUBleGFtcGxlLmNvbQpTTVRQX1NFQ1VSRT1mYWxzZQoKIyDoqIDoqp4KREVGQVVMVF9MQU5HVUFHRT1qYV9KUAoKIyDjg63jgrAKTE9HX0xFVkVMPWluZm8KCiMgLS0tIOWklumDqOiqjeiovOODl+ODreODkOOCpOODgOODvO+8iOW/heimgeOBq+W/nOOBmOOBpuOCs+ODoeODs+ODiOOCkuWkluOBme+8iSAtLS0KIyBbU2xhY2tdCiMgU0xBQ0tfQ0xJRU5UX0lEPQojIFNMQUNLX0NMSUVOVF9TRUNSRVQ9CiMKIyBbR29vZ2xlXQojIEdPT0dMRV9DTElFTlRfSUQ9CiMgR09PR0xFX0NMSUVOVF9TRUNSRVQ9CkVPRgoKZWNobyAiIC5lbnYg5L2c5oiQ5a6M5LqGIgoKIyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CiMgIFNURVAgNTogZG9ja2VyLWNvbXBvc2UueW1sIOS9nOaIkAojID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KZWNobyAiLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tIgplY2hvICIgIFNURVAgNTogZG9ja2VyLWNvbXBvc2UueW1sIOS9nOaIkCIKZWNobyAiLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tIgoKIyBQT1NUR1JFU19QQVNTV09SRCDjga/lgKTjgpLnm7TmjqXln4vjgoHovrzjgoDvvIhlbnZfZmlsZee1jOeUseOBp+OBryBlbnZpcm9ubWVudDog44Gr5bGV6ZaL44GV44KM44Gq44GE44Gf44KB77yJCmNhdCA+ICIkSU5TVEFMTF9ESVIvZG9ja2VyLWNvbXBvc2UueW1sIiA8PEVPRgpzZXJ2aWNlczoKCiAgb3V0bGluZToKICAgIGltYWdlOiBvdXRsaW5ld2lraS9vdXRsaW5lOmxhdGVzdAogICAgY29udGFpbmVyX25hbWU6IG91dGxpbmUKICAgIHJlc3RhcnQ6IHVubGVzcy1zdG9wcGVkCiAgICBlbnZfZmlsZTogLmVudgogICAgcG9ydHM6CiAgICAgIC0gIjMwMDA6MzAwMCIKICAgIGRlcGVuZHNfb246CiAgICAgIHBvc3RncmVzOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICAgIHJlZGlzOgogICAgICAgIGNvbmRpdGlvbjogc2VydmljZV9oZWFsdGh5CiAgICAgIG1haWxob2c6CiAgICAgICAgY29uZGl0aW9uOiBzZXJ2aWNlX3N0YXJ0ZWQKICAgIHZvbHVtZXM6CiAgICAgIC0gLi9kYXRhL3N0b3JhZ2U6L3Zhci9saWIvb3V0bGluZS9kYXRhCiAgICBuZXR3b3JrczoKICAgICAgLSBvdXRsaW5lLW5ldAoKICBwb3N0Z3JlczoKICAgIGltYWdlOiBwb3N0Z3JlczoxNi1hbHBpbmUKICAgIGNvbnRhaW5lcl9uYW1lOiBvdXRsaW5lLXBvc3RncmVzCiAgICByZXN0YXJ0OiB1bmxlc3Mtc3RvcHBlZAogICAgZW52aXJvbm1lbnQ6CiAgICAgIFBPU1RHUkVTX1VTRVI6IG91dGxpbmUKICAgICAgUE9TVEdSRVNfUEFTU1dPUkQ6ICR7UE9TVEdSRVNfUEFTU1dPUkR9CiAgICAgIFBPU1RHUkVTX0RCOiBvdXRsaW5lCiAgICB2b2x1bWVzOgogICAgICAtIC4vZGF0YS9wb3N0Z3JlczovdmFyL2xpYi9wb3N0Z3Jlc3FsL2RhdGEKICAgIG5ldHdvcmtzOgogICAgICAtIG91dGxpbmUtbmV0CiAgICBoZWFsdGhjaGVjazoKICAgICAgdGVzdDogWyJDTUQtU0hFTEwiLCAicGdfaXNyZWFkeSAtVSBvdXRsaW5lIl0KICAgICAgaW50ZXJ2YWw6IDEwcwogICAgICB0aW1lb3V0OiA1cwogICAgICByZXRyaWVzOiA1CgogIHJlZGlzOgogICAgaW1hZ2U6IHJlZGlzOjctYWxwaW5lCiAgICBjb250YWluZXJfbmFtZTogb3V0bGluZS1yZWRpcwogICAgcmVzdGFydDogdW5sZXNzLXN0b3BwZWQKICAgIGNvbW1hbmQ6IHJlZGlzLXNlcnZlciAtLXNhdmUgNjAgMSAtLWxvZ2xldmVsIHdhcm5pbmcKICAgIHZvbHVtZXM6CiAgICAgIC0gLi9kYXRhL3JlZGlzOi9kYXRhCiAgICBuZXR3b3JrczoKICAgICAgLSBvdXRsaW5lLW5ldAogICAgaGVhbHRoY2hlY2s6CiAgICAgIHRlc3Q6IFsiQ01EIiwgInJlZGlzLWNsaSIsICJwaW5nIl0KICAgICAgaW50ZXJ2YWw6IDEwcwogICAgICB0aW1lb3V0OiA1cwogICAgICByZXRyaWVzOiA1CgogIG1haWxob2c6CiAgICBpbWFnZTogbWFpbGhvZy9tYWlsaG9nCiAgICBjb250YWluZXJfbmFtZTogb3V0bGluZS1tYWlsaG9nCiAgICByZXN0YXJ0OiB1bmxlc3Mtc3RvcHBlZAogICAgcG9ydHM6CiAgICAgIC0gIjgwMjU6ODAyNSIKICAgIG5ldHdvcmtzOgogICAgICAtIG91dGxpbmUtbmV0CgpuZXR3b3JrczoKICBvdXRsaW5lLW5ldDoKICAgIGRyaXZlcjogYnJpZGdlCkVPRgoKZG9ja2VyIGNvbXBvc2UgY29uZmlnIC0tcXVpZXQgJiYgZWNobyAiIGRvY2tlci1jb21wb3NlLnltbCDkvZzmiJDlrozkuoYiCgojID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KIyAgU1RFUCA2OiDjg5Djg4Pjgq/jgqLjg4Pjg5fjgrnjgq/jg6rjg5fjg4jkvZzmiJAKIyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CmVjaG8gIi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSIKZWNobyAiICBTVEVQIDY6IOODkOODg+OCr+OCouODg+ODl+OCueOCr+ODquODl+ODiOS9nOaIkCIKZWNobyAiLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tIgoKY2F0ID4gIiRJTlNUQUxMX0RJUi9vdXRsaW5lLWJhY2t1cC5zaCIgPDwnQkFDS1VQX1NDUklQVCcKIyEvYmluL2Jhc2gKIyBPdXRsaW5lIOODkOODg+OCr+OCouODg+ODl+OCueOCr+ODquODl+ODiApzZXQgLWUKCklOU1RBTExfRElSPSIvb3B0L2RvY2tlci9vdXRsaW5lIgpCQUNLVVBfRElSPSIkSU5TVEFMTF9ESVIvYmFja3VwcyIKREFURT0kKGRhdGUgJyslWSVtJWRfJUglTSVTJykKQkFDS1VQX05BTUU9Im91dGxpbmVfYmFja3VwXyR7REFURX0iCktFRVBfREFZUz03CgplY2hvICI9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0iCmVjaG8gIiAgT3V0bGluZSDjg5Djg4Pjgq/jgqLjg4Pjg5fplovlp4s6ICREQVRFIgplY2hvICI9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0iCgpta2RpciAtcCAiJEJBQ0tVUF9ESVIvJEJBQ0tVUF9OQU1FIgoKIyBQb3N0Z3JlU1FMIOODgOODs+ODlwplY2hvICLilrwg44OH44O844K/44OZ44O844K544KS44OQ44OD44Kv44Ki44OD44OX44GX44Gm44GE44G+44GZLi4uIgpkb2NrZXIgZXhlYyBvdXRsaW5lLXBvc3RncmVzIHBnX2R1bXAgLVUgb3V0bGluZSBvdXRsaW5lIFwKICA+ICIkQkFDS1VQX0RJUi8kQkFDS1VQX05BTUUvZGF0YWJhc2Uuc3FsIgplY2hvICIg44OH44O844K/44OZ44O844K544OA44Oz44OX5a6M5LqGIgoKIyDjgrnjg4jjg6zjg7zjgrjjg5Djg4Pjgq/jgqLjg4Pjg5cKZWNobyAi4pa8IOOCueODiOODrOODvOOCuOOCkuODkOODg+OCr+OCouODg+ODl+OBl+OBpuOBhOOBvuOBmS4uLiIKdGFyIC1jemYgIiRCQUNLVVBfRElSLyRCQUNLVVBfTkFNRS9zdG9yYWdlLnRhci5neiIgXAogIC1DICIkSU5TVEFMTF9ESVIvZGF0YSIgc3RvcmFnZQplY2hvICIg44K544OI44Os44O844K444OQ44OD44Kv44Ki44OD44OX5a6M5LqGIgoKIyAuZW52IOODkOODg+OCr+OCouODg+ODlwpjcCAiJElOU1RBTExfRElSLy5lbnYiICIkQkFDS1VQX0RJUi8kQkFDS1VQX05BTUUvLmVudiIKZWNobyAiIC5lbnYg44OQ44OD44Kv44Ki44OD44OX5a6M5LqGIgoKIyB0YXIg44Gn44G+44Go44KB44Gm5Zyn57iuCnRhciAtY3pmICIkQkFDS1VQX0RJUi8ke0JBQ0tVUF9OQU1FfS50YXIuZ3oiIFwKICAtQyAiJEJBQ0tVUF9ESVIiICIkQkFDS1VQX05BTUUiCnJtIC1yZiAiJEJBQ0tVUF9ESVIvJEJBQ0tVUF9OQU1FIgoKIyDlj6TjgYTjg5Djg4Pjgq/jgqLjg4Pjg5fjgpLliYrpmaQKZmluZCAiJEJBQ0tVUF9ESVIiIC1uYW1lICJvdXRsaW5lX2JhY2t1cF8qLnRhci5neiIgXAogIC1tdGltZSArJHtLRUVQX0RBWVN9IC1kZWxldGUKCkJBQ0tVUF9TSVpFPSQoZHUgLXNoICIkQkFDS1VQX0RJUi8ke0JBQ0tVUF9OQU1FfS50YXIuZ3oiIHwgY3V0IC1mMSkKZWNobyAiIgplY2hvICI9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0iCmVjaG8gIiAg44OQ44OD44Kv44Ki44OD44OX5a6M5LqG77yBIgplY2hvICIgIOODleOCoeOCpOODqzogJEJBQ0tVUF9ESVIvJHtCQUNLVVBfTkFNRX0udGFyLmd6IgplY2hvICIgIOOCteOCpOOCuiAgOiAkQkFDS1VQX1NJWkUiCmVjaG8gIiAg5L+d5oyB5pWwICA6ICQobHMgJEJBQ0tVUF9ESVIvb3V0bGluZV9iYWNrdXBfKi50YXIuZ3ogMj4vZGV2L251bGwgfCB3YyAtbCkg5Lu2IgplY2hvICI9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0iCkJBQ0tVUF9TQ1JJUFQKCmNobW9kICt4ICIkSU5TVEFMTF9ESVIvb3V0bGluZS1iYWNrdXAuc2giCmVjaG8gIiBvdXRsaW5lLWJhY2t1cC5zaCDkvZzmiJDlrozkuoYiCgojID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KIyAgU1RFUCA3OiDjg6rjgrnjg4jjgqLjgrnjgq/jg6rjg5fjg4jkvZzmiJAKIyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CmVjaG8gIi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSIKZWNobyAiICBTVEVQIDc6IOODquOCueODiOOCouOCueOCr+ODquODl+ODiOS9nOaIkCIKZWNobyAiLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tIgoKY2F0ID4gIiRJTlNUQUxMX0RJUi9vdXRsaW5lLXJlc3RvcmUuc2giIDw8J1JFU1RPUkVfU0NSSVBUJwojIS9iaW4vYmFzaAojIE91dGxpbmUg44Oq44K544OI44Ki44K544Kv44Oq44OX44OICiMg5L2/44GE5pa5OgojICAgc3VkbyBiYXNoIG91dGxpbmUtcmVzdG9yZS5zaCAgICAgICAgICAgICAgICAgICAgICAgICMg5pyA5paw44OQ44OD44Kv44Ki44OD44OX44KS6Ieq5YuV6YG45oqeCiMgICBzdWRvIGJhc2ggb3V0bGluZS1yZXN0b3JlLnNoIC9wYXRoL3RvL2JhY2t1cC50YXIuZ3ogIyDjg5XjgqHjgqTjg6vjgpLnm7TmjqXmjIflrpoKc2V0IC1lCgpJTlNUQUxMX0RJUj0iL29wdC9kb2NrZXIvb3V0bGluZSIKQkFDS1VQX0RJUj0iJElOU1RBTExfRElSL2JhY2t1cHMiCgojIOODkOODg+OCr+OCouODg+ODl+ODleOCoeOCpOODq+OBruaxuuWumgppZiBbIC1uICIkMSIgXTsgdGhlbgogIEJBQ0tVUF9GSUxFPSIkMSIKZWxzZQogIEJBQ0tVUF9GSUxFPSQobHMgLXQgIiRCQUNLVVBfRElSIi9vdXRsaW5lX2JhY2t1cF8qLnRhci5neiAyPi9kZXYvbnVsbCB8IGhlYWQgLTEpCiAgaWYgWyAteiAiJEJBQ0tVUF9GSUxFIiBdOyB0aGVuCiAgICBlY2hvICLjgqjjg6njg7w6IOODkOODg+OCr+OCouODg+ODl+ODleOCoeOCpOODq+OBjOimi+OBpOOBi+OCiuOBvuOBm+OCkzogJEJBQ0tVUF9ESVIiCiAgICBleGl0IDEKICBmaQogIEJBQ0tVUF9TSVpFPSQoZHUgLXNoICIkQkFDS1VQX0ZJTEUiIHwgY3V0IC1mMSkKICBCQUNLVVBfREFURT0kKHN0YXQgLWMgJyV5JyAiJEJBQ0tVUF9GSUxFIiB8IGN1dCAtZCcuJyAtZjEpCiAgZWNobyAiPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09IgogIGVjaG8gIiAg5pyA5paw44OQ44OD44Kv44Ki44OD44OX44KS5qSc5Ye644GX44G+44GX44GfIgogIGVjaG8gIiIKICBlY2hvICIgIOODleOCoeOCpOODqzogJChiYXNlbmFtZSAkQkFDS1VQX0ZJTEUpIgogIGVjaG8gIiAg44K144Kk44K6ICA6ICRCQUNLVVBfU0laRSIKICBlY2hvICIgIOS9nOaIkOaXpeaZgjogJEJBQ0tVUF9EQVRFIgogIGVjaG8gIj09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PSIKICBlY2hvICIiCmZpCgppZiBbICEgLWYgIiRCQUNLVVBfRklMRSIgXTsgdGhlbgogIGVjaG8gIuOCqOODqeODvDog44OV44Kh44Kk44Or44GM6KaL44Gk44GL44KK44G+44Gb44KTOiAkQkFDS1VQX0ZJTEUiCiAgZXhpdCAxCmZpCgplY2hvICI9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0iCmVjaG8gIiAgT3V0bGluZSDjg6rjgrnjg4jjgqLplovlp4siCmVjaG8gIiAg5a++6LGhOiAkQkFDS1VQX0ZJTEUiCmVjaG8gIj09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PSIKZWNobyAiIgpyZWFkIC1ycCAi5pys5b2T44Gr44Oq44K544OI44Ki44GX44G+44GZ44GL77yf54++5Zyo44Gu44OH44O844K/44Gv5LiK5pu444GN44GV44KM44G+44GZ44CCKHllcy9ubyk6ICIgQ09ORklSTQppZiBbICIkQ09ORklSTSIgIT0gInllcyIgXTsgdGhlbgogIGVjaG8gIuOCreODo+ODs+OCu+ODq+OBl+OBvuOBl+OBn+OAgiIKICBleGl0IDAKZmkKCiMg5bGV6ZaLCldPUktfRElSPSQobWt0ZW1wIC1kKQplY2hvICLilrwg44OQ44OD44Kv44Ki44OD44OX44KS5bGV6ZaL44GX44Gm44GE44G+44GZLi4uIgp0YXIgLXh6ZiAiJEJBQ0tVUF9GSUxFIiAtQyAiJFdPUktfRElSIgpCQUNLVVBfTkFNRT0kKGxzICIkV09SS19ESVIiKQpCQUNLVVBfUEFUSD0iJFdPUktfRElSLyRCQUNLVVBfTkFNRSIKZWNobyAiIOWxlemWi+WujOS6hiIKCiMgb3V0bGluZSDjgrPjg7Pjg4bjg4rjgpLlgZzmraIKZWNobyAi4pa8IG91dGxpbmUg44Kz44Oz44OG44OK44KS5YGc5q2i44GX44Gm44GE44G+44GZLi4uIgpkb2NrZXIgY29tcG9zZSAtZiAiJElOU1RBTExfRElSL2RvY2tlci1jb21wb3NlLnltbCIgc3RvcCBvdXRsaW5lCmVjaG8gIiDlgZzmraLjgZfjgb7jgZfjgZ8iCgojIOODh+ODvOOCv+ODmeODvOOCueODquOCueODiOOCou+8iC1kIHBvc3RncmVzIOOBpyBjdXJyZW50bHkgb3BlbiBkYXRhYmFzZSDjgqjjg6njg7zjgpLlm57pgb/vvIkKZWNobyAi4pa8IOODh+ODvOOCv+ODmeODvOOCueOCkuODquOCueODiOOCouOBl+OBpuOBhOOBvuOBmS4uLiIKZG9ja2VyIGV4ZWMgLWkgb3V0bGluZS1wb3N0Z3JlcyBwc3FsIC1VIG91dGxpbmUgLWQgcG9zdGdyZXMgXAogIC1jICJEUk9QIERBVEFCQVNFIElGIEVYSVNUUyBvdXRsaW5lOyIKZG9ja2VyIGV4ZWMgLWkgb3V0bGluZS1wb3N0Z3JlcyBwc3FsIC1VIG91dGxpbmUgLWQgcG9zdGdyZXMgXAogIC1jICJDUkVBVEUgREFUQUJBU0Ugb3V0bGluZTsiCmRvY2tlciBleGVjIC1pIG91dGxpbmUtcG9zdGdyZXMgcHNxbCAtVSBvdXRsaW5lIC1kIG91dGxpbmUgXAogIDwgIiRCQUNLVVBfUEFUSC9kYXRhYmFzZS5zcWwiCmVjaG8gIiDjg4fjg7zjgr/jg5njg7zjgrnjg6rjgrnjg4jjgqLlrozkuoYiCgojIOOCueODiOODrOODvOOCuOODquOCueODiOOCogplY2hvICLilrwg44K544OI44Os44O844K444KS44Oq44K544OI44Ki44GX44Gm44GE44G+44GZLi4uIgpybSAtcmYgIiRJTlNUQUxMX0RJUi9kYXRhL3N0b3JhZ2UiCnRhciAteHpmICIkQkFDS1VQX1BBVEgvc3RvcmFnZS50YXIuZ3oiIC1DICIkSU5TVEFMTF9ESVIvZGF0YSIKIyBVSUQgMTAwMSDmiYDmnInjgavoqK3lrprvvIhzdWRvIOOBjOW/heimge+8iQpzdWRvIGNob3duIC1SIDEwMDE6MTAwMSAiJElOU1RBTExfRElSL2RhdGEvc3RvcmFnZSIKZWNobyAiIOOCueODiOODrOODvOOCuOODquOCueODiOOCouWujOS6hiIKCiMg44Kv44Oq44O844Oz44Ki44OD44OXCnJtIC1yZiAiJFdPUktfRElSIgoKIyBvdXRsaW5lIOOCs+ODs+ODhuODiuOCkuWGjei1t+WLlQplY2hvICLilrwgb3V0bGluZSDjgrPjg7Pjg4bjg4rjgpLlho3otbfli5XjgZfjgabjgYTjgb7jgZkuLi4iCmRvY2tlciBjb21wb3NlIC1mICIkSU5TVEFMTF9ESVIvZG9ja2VyLWNvbXBvc2UueW1sIiBzdGFydCBvdXRsaW5lCnNsZWVwIDUKZG9ja2VyIGNvbXBvc2UgLWYgIiRJTlNUQUxMX0RJUi9kb2NrZXItY29tcG9zZS55bWwiIHBzCgplY2hvICIiCmVjaG8gIj09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PSIKZWNobyAiICDjg6rjgrnjg4jjgqLlrozkuobvvIEiCmVjaG8gIj09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PSIKUkVTVE9SRV9TQ1JJUFQKCmNobW9kICt4ICIkSU5TVEFMTF9ESVIvb3V0bGluZS1yZXN0b3JlLnNoIgplY2hvICIgb3V0bGluZS1yZXN0b3JlLnNoIOS9nOaIkOWujOS6hiIKCiMgPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQojICBTVEVQIDg6IGNyb24g6Kit5a6aCiMgPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQplY2hvICItLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0iCmVjaG8gIiAgU1RFUCA4OiBjcm9uIOioreWumiIKZWNobyAiLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tIgoKIyBjcm9uIOacquOCpOODs+OCueODiOODvOODq+OBquOCieiHquWLleOCpOODs+OCueODiOODvOODqwppZiAhIGNvbW1hbmQgLXYgY3JvbnRhYiAmPi9kZXYvbnVsbDsgdGhlbgogIGVjaG8gIuKWvCBjcm9uIOOCkuOCpOODs+OCueODiOODvOODq+OBl+OBpuOBhOOBvuOBmS4uLiIKICBhcHQtZ2V0IGluc3RhbGwgLXkgY3JvbiAmPi9kZXYvbnVsbAogIHN5c3RlbWN0bCBlbmFibGUgY3JvbgogIHN5c3RlbWN0bCBzdGFydCBjcm9uCiAgZWNobyAiIGNyb24g44Kk44Oz44K544OI44O844Or5a6M5LqGIgpmaQoKIyDkuIDoiKzjg6bjg7zjgrbjg7zjga5jcm9udGFi44Gr55m76Yyy77yIc3VkbyAtdSDjgaflrp/ooYzjg6bjg7zjgrbjg7zjgajjgZfjgabnmbvpjLLvvIkKQ1JPTl9KT0I9IjAgMiAqICogKiBiYXNoICRJTlNUQUxMX0RJUi9vdXRsaW5lLWJhY2t1cC5zaCA+PiAkQkFDS1VQX0RJUi9iYWNrdXAubG9nIDI+JjEiCiggc3VkbyAtdSAiJFJFQUxfVVNFUiIgY3JvbnRhYiAtbCAyPi9kZXYvbnVsbCB8IGdyZXAgLXYgIm91dGxpbmUtYmFja3VwLnNoIiA7IGVjaG8gIiRDUk9OX0pPQiIgKSBcCiAgfCBzdWRvIC11ICIkUkVBTF9VU0VSIiBjcm9udGFiIC0KCmVjaG8gIiBjcm9uIOeZu+mMsuWujOS6hu+8iOavjuaXpSAyOjAwIEFNIC8g44Om44O844K244O8OiAkUkVBTF9VU0VS77yJIgoKIyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CiMgIFNURVAgOTogT3V0bGluZSDotbfli5UKIyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CmVjaG8gIi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSIKZWNobyAiICBTVEVQIDk6IE91dGxpbmUg6LW35YuVIgplY2hvICItLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0iCgpkb2NrZXIgY29tcG9zZSB1cCAtZAoKZWNobyAiIgplY2hvICLilrwg6LW35YuV44KS5b6F5qmf44GX44Gm44GE44G+44GZ77yIMTXnp5LvvIkuLi4iCnNsZWVwIDE1CmRvY2tlciBjb21wb3NlIHBzCgojID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KIyAg5a6M5LqGCiMgPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQplY2hvICIiCmVjaG8gIj09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PSIKZWNobyAiICDjgrvjg4Pjg4jjgqLjg4Pjg5flrozkuobvvIEiCmVjaG8gIiIKZWNobyAiICBPdXRsaW5lIDogJHtCQVNFX1VSTH0iCmVjaG8gIiAgTWFpbGhvZyA6IGh0dHA6Ly8ke0hPU1R9OjgwMjUiCmVjaG8gIiIKZWNobyAiICDjg63jgrDjgqTjg7PmiYvpoIY6IgplY2hvICIgIDEuICR7QkFTRV9VUkx9IOOCkumWi+OBjyIKZWNobyAiICAyLiDjg6Hjg7zjg6vjgqLjg4njg6zjgrnjgpLlhaXlipvjgZfjgabpgIHkv6EiCmVjaG8gIiAgMy4gaHR0cDovLyR7SE9TVH06ODAyNSDjgafjg6Hjg7zjg6vjgpLnorroqo0iCmVjaG8gIiAgNC4g44Oh44O844Or5YaF44Gu44CMU2lnbiBJbuOAjeOCkuOCr+ODquODg+OCryIKZWNobyAiIgplY2hvICIgIOODkOODg+OCr+OCouODg+ODlzoiCmVjaG8gIiAg5omL5YuV5a6f6KGMIDogYmFzaCAkSU5TVEFMTF9ESVIvb3V0bGluZS1iYWNrdXAuc2giCmVjaG8gIiAg6Ieq5YuV5a6f6KGMIDog5q+O5pelIDI6MDAgQU3vvIjjg63jgrA6ICRCQUNLVVBfRElSL2JhY2t1cC5sb2fvvIkiCmVjaG8gIiAg44Oq44K544OI44KiIDogc3VkbyBiYXNoICRJTlNUQUxMX0RJUi9vdXRsaW5lLXJlc3RvcmUuc2giCmVjaG8gIiAgY3JvbueiuuiqjSA6IGNyb250YWIgLWwiCmVjaG8gIiIKZWNobyAiICDjg63jgrDnorroqo0gOiBkb2NrZXIgY29tcG9zZSAtZiAkSU5TVEFMTF9ESVIvZG9ja2VyLWNvbXBvc2UueW1sIGxvZ3MgLWYgb3V0bGluZSIKZWNobyAiICDlgZzmraIgICAgIDogZG9ja2VyIGNvbXBvc2UgLWYgJElOU1RBTExfRElSL2RvY2tlci1jb21wb3NlLnltbCBkb3duIgplY2hvICI9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0iCg==' | base64 -d)"