見た目が綺麗で動作も軽快な「nextExplorer」

ファイルサーバーとしても利用可能なツールとして、「OxiCloud」や「Filebrowser」を紹介しましたが、見た目が綺麗で、ファイルやフォルダの共有も行いやすい「nextExplorer」も使いやすそうです。
実際の使用画面は次のような感じで、動作も軽快。GitHubで公開されており、セルフホストが可能です。

OnlyOfficeとの連携も出来るセットアップスクリプトを作ったので、これから導入するならこちらのほうが良いと思います。

LXDコンテナ内にDockerでセットアップ

#!/usr/bin/env bash
# =============================================================================
#  nextExplorer セットアップスクリプト v3
#  - /opt/nextexplorer に docker-compose.yml を配置
#  - コンテナ内ポート 3000 → ホスト 127.0.0.1:3317 にバインド
#  - Tailscale Serve で https://<hostname>:3317 → :3317 に追加(ポートベース)
#  - 既存の Serve 設定は壊さない(--bg)
#  - データディレクトリのパーミッションを正しく設定
# =============================================================================
set -euo pipefail

# ── 設定 ──────────────────────────────────────────────────────────────────────
INSTALL_DIR="/opt/nextexplorer"
HOST_PORT=3317
CONTAINER_PORT=3000
DATA_DIR="/srv/nextexplorer"   # config / cache / files の実体

# コンテナ内プロセスの実行ユーザーに合わせる
# nxzai/explorer は内部で node ユーザー(uid=1000) を使用
PUID=$(id -u)
PGID=$(id -g)

# ── 色付きログ ─────────────────────────────────────────────────────────────────
info()  { echo -e "\033[1;34m[INFO]\033[0m  $*"; }
ok()    { echo -e "\033[1;32m[ OK ]\033[0m  $*"; }
warn()  { echo -e "\033[1;33m[WARN]\033[0m  $*"; }
die()   { echo -e "\033[1;31m[ERR ]\033[0m  $*" >&2; exit 1; }

# ── 前提チェック ───────────────────────────────────────────────────────────────
info "前提確認..."
command -v docker        >/dev/null 2>&1 || die "docker が見つかりません"
docker compose version   >/dev/null 2>&1 || die "docker compose (v2) が見つかりません"
command -v tailscale     >/dev/null 2>&1 || die "tailscale が見つかりません"
tailscale status         >/dev/null 2>&1 || die "Tailscale が認証されていません"
ok "前提 OK"

# ── Tailscale hostname 取得 ────────────────────────────────────────────────────
TS_HOSTNAME=$(tailscale status --json \
  | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['Self']['DNSName'].rstrip('.'))" \
  2>/dev/null) || die "Tailscale の DNSName を取得できませんでした"

PUBLIC_URL="https://${TS_HOSTNAME}:${HOST_PORT}"
info "Tailscale ホスト : ${TS_HOSTNAME}"
info "PUBLIC_URL       : ${PUBLIC_URL}"

# ── ディレクトリ作成 & パーミッション設定 ─────────────────────────────────────
info "ディレクトリ作成..."
mkdir -p "${INSTALL_DIR}"
mkdir -p "${DATA_DIR}/config"
mkdir -p "${DATA_DIR}/cache"
mkdir -p "${DATA_DIR}/files"

# コンテナ内 node(1000) / コンテナ外の実行ユーザー 両方が書けるように
# ホスト uid と合わせるか、777 で開放するかを選択
# → ここでは実行ユーザー所有 + グループ書き込み可 にする
chown -R "${PUID}:${PGID}" "${DATA_DIR}"
chmod -R u=rwX,g=rwX,o=rX "${DATA_DIR}"
# コンテナが uid=1000 で動く場合に備えて ACL を追加(acl パッケージがある場合のみ)
if command -v setfacl >/dev/null 2>&1; then
  setfacl -R -m u:1000:rwX "${DATA_DIR}"
  setfacl -R -d -m u:1000:rwX "${DATA_DIR}"
  info "ACL: uid=1000 に rwX を付与しました"
else
  # ACL が使えない場合は o+w を付与してコンテナ書き込みを許可
  chmod -R o+w "${DATA_DIR}/config" "${DATA_DIR}/cache" "${DATA_DIR}/files"
  warn "acl パッケージが未インストールのため chmod o+w で代替しました"
  warn "  sudo apt install acl で改善できます"
fi
ok "ディレクトリ & パーミッション設定完了"

# ── SESSION_SECRET 生成(既存ファイルがあれば再利用) ─────────────────────────
SECRET_FILE="${INSTALL_DIR}/.session_secret"
if [[ -f "${SECRET_FILE}" ]]; then
  SESSION_SECRET=$(cat "${SECRET_FILE}")
  info "既存の SESSION_SECRET を再利用します"
else
  SESSION_SECRET=$(python3 -c "import secrets; print(secrets.token_hex(32))")
  echo "${SESSION_SECRET}" > "${SECRET_FILE}"
  chmod 600 "${SECRET_FILE}"
  info "SESSION_SECRET を新規生成しました"
fi

# ── docker-compose.yml 生成 ───────────────────────────────────────────────────
info "docker-compose.yml を生成..."
cat > "${INSTALL_DIR}/docker-compose.yml" <<EOF
# nextExplorer — docker-compose.yml
# 生成日: $(date '+%Y-%m-%d %H:%M:%S')
# アクセス URL: ${PUBLIC_URL}

services:
  nextexplorer:
    image: nxzai/explorer:latest
    container_name: nextexplorer
    restart: unless-stopped
    ports:
      # ループバックのみ。Tailscale Serve 経由でのみアクセス可能
      - "127.0.0.1:${HOST_PORT}:${CONTAINER_PORT}"
    environment:
      NODE_ENV: production
      PORT: "${CONTAINER_PORT}"
      # ポートベースの URL(サブパスなし)
      PUBLIC_URL: "${PUBLIC_URL}"
      # Tailscale Serve (loopback) を信頼
      TRUST_PROXY: "loopback"
      # 再起動をまたいでセッションを維持
      SESSION_SECRET: "${SESSION_SECRET}"
      # コンテナ内プロセスの UID/GID をホストに合わせる → 書き込み権限の解決
      PUID: "${PUID}"
      PGID: "${PGID}"
    volumes:
      - ${DATA_DIR}/config:/config
      - ${DATA_DIR}/cache:/cache
      # /mnt/<ラベル名> が UI 上のボリュームになる。必要に応じて追加
      - ${DATA_DIR}/files:/mnt/Files
EOF
ok "docker-compose.yml → ${INSTALL_DIR}/docker-compose.yml"

# ── コンテナ起動 ──────────────────────────────────────────────────────────────
info "コンテナを起動..."
docker compose -f "${INSTALL_DIR}/docker-compose.yml" pull
docker compose -f "${INSTALL_DIR}/docker-compose.yml" up -d
ok "コンテナ起動完了"

# ── Tailscale Serve 設定(ポートベース) ──────────────────────────────────────
info "既存の Tailscale Serve 設定を確認..."
EXISTING_SERVE=$(tailscale serve status 2>/dev/null || true)

if echo "${EXISTING_SERVE}" | grep -q ":${HOST_PORT}"; then
  warn "ポート ${HOST_PORT} はすでに Tailscale Serve に登録されています。スキップします。"
else
  info "Tailscale Serve にポート ${HOST_PORT} の HTTPS プロキシを追加..."
  # --bg        : バックグラウンドモード(既存設定を保持したまま追加)
  # --https=N   : Tailnet 内で HTTPS ポート N として公開
  tailscale serve --bg --https="${HOST_PORT}" "http://127.0.0.1:${HOST_PORT}"
  ok "Tailscale Serve 設定追加完了"
fi

# ── 完了サマリー ──────────────────────────────────────────────────────────────
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
ok "セットアップ完了!"
echo ""
echo "  アクセス URL  : ${PUBLIC_URL}"
echo "  設定ファイル  : ${INSTALL_DIR}/docker-compose.yml"
echo "  データ領域    : ${DATA_DIR}/"
echo "    ├─ config/  (DB・設定)"
echo "    ├─ cache/   (サムネイルキャッシュ)"
echo "    └─ files/   (公開ファイル置き場 → UI上: Files)"
echo ""
echo "  ボリュームを追加したい場合:"
echo "    ${INSTALL_DIR}/docker-compose.yml の volumes に"
echo "    - /your/path:/mnt/LabelName  を追記して"
echo "    docker compose -f ${INSTALL_DIR}/docker-compose.yml up -d"
echo ""
echo "  現在の Tailscale Serve 設定:"
tailscale serve status
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"

インストールが完了してアクセスすれば次のような画面が表示されるので、メールアドレスやパスワードを設定してログインします。

Locationsにフォルダを追加する方法

docker-compose.yml の volumes セクションにマウントを追加するだけです。

volumes:
  - /srv/nextexplorer/config:/config
  - /srv/nextexplorer/cache:/cache
  - /srv/nextexplorer/files:/mnt/Files   # 既存
  - /home/user/photos:/mnt/Photos        # 追加例
  - /mnt/nas/documents:/mnt/Documents    # 追加例

/mnt/ 以降の名前(PhotosDocuments など)がそのまま UI 上のラベルになります。

編集と反映するコマンド

# 編集
nano /opt/nextexplorer/docker-compose.yml
# 編集後
docker compose -f /opt/nextexplorer/docker-compose.yml up -d

デフォルトの Files フォルダを別パスに変えたい場合も同じで、既存の行のホスト側パスを書き換えるだけです。

# 変更前
- /srv/nextexplorer/files:/mnt/Files

# 変更後(例:/data/storage を Files として公開)
- /opt/lxd-data:/mnt/Files

注意点として、追加したディレクトリにコンテナ(uid=1000)が書き込む必要がある場合は、スクリプトで設定したのと同じようにパーミッションを合わせてください。

# acl が入っている場合
sudo setfacl -R -m u:1000:rwX /your/path
sudo setfacl -R -d -m u:1000:rwX /your/path

# acl なしの場合
sudo chmod -R o+w /your/path

インストール時にマウントフォルダを登録

#!/usr/bin/env bash
# =============================================================================
#  nextExplorer セットアップスクリプト v3
#  - /opt/nextexplorer に docker-compose.yml を配置
#  - コンテナ内ポート 3000 → ホスト 127.0.0.1:3317 にバインド
#  - Tailscale Serve で https://<hostname>:3317 → :3317 に追加(ポートベース)
#  - 既存の Serve 設定は壊さない(--bg)
#  - データディレクトリのパーミッションを正しく設定
# =============================================================================
set -euo pipefail

# ── 固定設定 ──────────────────────────────────────────────────────────────────
INSTALL_DIR="/opt/nextexplorer"
HOST_PORT=3317
CONTAINER_PORT=3000
DATA_DIR="/srv/nextexplorer"   # config / cache の実体(マウントとは別)
DEFAULT_MOUNT_SRC="/opt/lxd-data"
DEFAULT_MOUNT_LABEL="Files"

# コンテナ内プロセスの実行ユーザーに合わせる
# nxzai/explorer は内部で node ユーザー(uid=1000) を使用
PUID=$(id -u)
PGID=$(id -g)

# ── 色付きログ ─────────────────────────────────────────────────────────────────
info()  { echo -e "\033[1;34m[INFO]\033[0m  $*"; }
ok()    { echo -e "\033[1;32m[ OK ]\033[0m  $*"; }
warn()  { echo -e "\033[1;33m[WARN]\033[0m  $*"; }
die()   { echo -e "\033[1;31m[ERR ]\033[0m  $*" >&2; exit 1; }

# ── 前提チェック ───────────────────────────────────────────────────────────────
info "前提確認..."
command -v docker        >/dev/null 2>&1 || die "docker が見つかりません"
docker compose version   >/dev/null 2>&1 || die "docker compose (v2) が見つかりません"
command -v tailscale     >/dev/null 2>&1 || die "tailscale が見つかりません"
tailscale status         >/dev/null 2>&1 || die "Tailscale が認証されていません"
ok "前提 OK"

# ── Tailscale hostname 取得 ────────────────────────────────────────────────────
TS_HOSTNAME=$(tailscale status --json \
  | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['Self']['DNSName'].rstrip('.'))" \
  2>/dev/null) || die "Tailscale の DNSName を取得できませんでした"

PUBLIC_URL="https://${TS_HOSTNAME}:${HOST_PORT}"
info "Tailscale ホスト : ${TS_HOSTNAME}"
info "PUBLIC_URL       : ${PUBLIC_URL}"

# ── マウントディレクトリの入力 ────────────────────────────────────────────────
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "  マウントするディレクトリを設定します。"
echo "  複数指定可能。空 Enter で終了(1つも入力しない場合はデフォルト設定を使用)。"
echo "  デフォルト: ${DEFAULT_MOUNT_SRC} → UI ラベル「${DEFAULT_MOUNT_LABEL}」"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""

# MOUNT_ENTRIES は "ホストパス:ラベル名" の配列
declare -a MOUNT_ENTRIES=()
mount_index=1

while true; do
  read -rp "  マウント #${mount_index} ホストパス(空 Enter でスキップ/終了): " input_path
  # 空入力 → ループ終了
  [[ -z "${input_path}" ]] && break

  # パスの存在確認
  if [[ ! -d "${input_path}" ]]; then
    warn "${input_path} は存在しません。作成しますか? [y/N]: "
    read -rp "  " yn
    if [[ "${yn}" =~ ^[Yy]$ ]]; then
      mkdir -p "${input_path}"
      ok "${input_path} を作成しました"
    else
      warn "スキップします"
      continue
    fi
  fi

  # UI ラベル入力(未入力ならディレクトリ名をそのまま使用)
  default_label=$(basename "${input_path}")
  read -rp "  マウント #${mount_index} UI ラベル名(空 Enter で「${default_label}」): " input_label
  [[ -z "${input_label}" ]] && input_label="${default_label}"

  MOUNT_ENTRIES+=("${input_path}:${input_label}")
  ok "追加: ${input_path} → /mnt/${input_label}"
  (( mount_index++ ))
done

# 1つも入力されなかった場合はデフォルト設定を使用
if [[ ${#MOUNT_ENTRIES[@]} -eq 0 ]]; then
  warn "入力なし。デフォルト設定を使用します: ${DEFAULT_MOUNT_SRC} → ${DEFAULT_MOUNT_LABEL}"
  mkdir -p "${DEFAULT_MOUNT_SRC}"
  MOUNT_ENTRIES=("${DEFAULT_MOUNT_SRC}:${DEFAULT_MOUNT_LABEL}")
fi

echo ""
info "マウント設定:"
for entry in "${MOUNT_ENTRIES[@]}"; do
  src="${entry%%:*}"
  label="${entry##*:}"
  echo "    ${src} → /mnt/${label}"
done
echo ""

# ── ディレクトリ作成 & パーミッション設定 ─────────────────────────────────────
info "ディレクトリ作成..."
mkdir -p "${INSTALL_DIR}"
mkdir -p "${DATA_DIR}/config"
mkdir -p "${DATA_DIR}/cache"

apply_permissions() {
  local target="$1"
  chown -R "${PUID}:${PGID}" "${target}"
  chmod -R u=rwX,g=rwX,o=rX "${target}"
  if command -v setfacl >/dev/null 2>&1; then
    setfacl -R -m u:1000:rwX "${target}"
    setfacl -R -d -m u:1000:rwX "${target}"
  else
    chmod -R o+w "${target}"
  fi
}

# DATA_DIR(config/cache)にパーミッション適用
chown -R "${PUID}:${PGID}" "${DATA_DIR}"
chmod -R u=rwX,g=rwX,o=rX "${DATA_DIR}"

# 各マウントソースにもパーミッション適用
for entry in "${MOUNT_ENTRIES[@]}"; do
  src="${entry%%:*}"
  apply_permissions "${src}"
done

if command -v setfacl >/dev/null 2>&1; then
  setfacl -R -m u:1000:rwX "${DATA_DIR}"
  setfacl -R -d -m u:1000:rwX "${DATA_DIR}"
  info "ACL: uid=1000 に rwX を付与しました"
else
  chmod -R o+w "${DATA_DIR}/config" "${DATA_DIR}/cache"
  warn "acl パッケージが未インストールのため chmod o+w で代替しました"
  warn "  sudo apt install acl で改善できます"
fi
ok "ディレクトリ & パーミッション設定完了"

# ── SESSION_SECRET 生成(既存ファイルがあれば再利用) ─────────────────────────
SECRET_FILE="${INSTALL_DIR}/.session_secret"
if [[ -f "${SECRET_FILE}" ]]; then
  SESSION_SECRET=$(cat "${SECRET_FILE}")
  info "既存の SESSION_SECRET を再利用します"
else
  SESSION_SECRET=$(python3 -c "import secrets; print(secrets.token_hex(32))")
  echo "${SESSION_SECRET}" > "${SECRET_FILE}"
  chmod 600 "${SECRET_FILE}"
  info "SESSION_SECRET を新規生成しました"
fi

# ── docker-compose.yml 生成 ───────────────────────────────────────────────────
info "docker-compose.yml を生成..."

# volumes セクションを動的に組み立て
VOLUMES_YAML="      - ${DATA_DIR}/config:/config"$'\n'
VOLUMES_YAML+="      - ${DATA_DIR}/cache:/cache"
for entry in "${MOUNT_ENTRIES[@]}"; do
  src="${entry%%:*}"
  label="${entry##*:}"
  VOLUMES_YAML+=$'\n'"      - ${src}:/mnt/${label}"
done

cat > "${INSTALL_DIR}/docker-compose.yml" <<EOF
# nextExplorer — docker-compose.yml
# 生成日: $(date '+%Y-%m-%d %H:%M:%S')
# アクセス URL: ${PUBLIC_URL}

services:
  nextexplorer:
    image: nxzai/explorer:latest
    container_name: nextexplorer
    restart: unless-stopped
    ports:
      # ループバックのみ。Tailscale Serve 経由でのみアクセス可能
      - "127.0.0.1:${HOST_PORT}:${CONTAINER_PORT}"
    environment:
      NODE_ENV: production
      PORT: "${CONTAINER_PORT}"
      PUBLIC_URL: "${PUBLIC_URL}"
      TRUST_PROXY: "loopback"
      SESSION_SECRET: "${SESSION_SECRET}"
      PUID: "${PUID}"
      PGID: "${PGID}"
    volumes:
${VOLUMES_YAML}
EOF
ok "docker-compose.yml → ${INSTALL_DIR}/docker-compose.yml"

# ── コンテナ起動 ──────────────────────────────────────────────────────────────
info "コンテナを起動..."
docker compose -f "${INSTALL_DIR}/docker-compose.yml" pull
docker compose -f "${INSTALL_DIR}/docker-compose.yml" up -d
ok "コンテナ起動完了"

# ── Tailscale Serve 設定(ポートベース) ──────────────────────────────────────
info "既存の Tailscale Serve 設定を確認..."
EXISTING_SERVE=$(tailscale serve status 2>/dev/null || true)

if echo "${EXISTING_SERVE}" | grep -q ":${HOST_PORT}"; then
  warn "ポート ${HOST_PORT} はすでに Tailscale Serve に登録されています。スキップします。"
else
  info "Tailscale Serve にポート ${HOST_PORT} の HTTPS プロキシを追加..."
  # --bg        : バックグラウンドモード(既存設定を保持したまま追加)
  # --https=N   : Tailnet 内で HTTPS ポート N として公開
  tailscale serve --bg --https="${HOST_PORT}" "http://127.0.0.1:${HOST_PORT}"
  ok "Tailscale Serve 設定追加完了"
fi

# ── 完了サマリー ──────────────────────────────────────────────────────────────
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
ok "セットアップ完了!"
echo ""
echo "  アクセス URL  : ${PUBLIC_URL}"
echo "  設定ファイル  : ${INSTALL_DIR}/docker-compose.yml"
echo "  データ領域    : ${DATA_DIR}/"
echo "    ├─ config/  (DB・設定)"
echo "    └─ cache/   (サムネイルキャッシュ)"
echo ""
echo "  マウント済みボリューム:"
for entry in "${MOUNT_ENTRIES[@]}"; do
  src="${entry%%:*}"
  label="${entry##*:}"
  echo "    ${src} → UI ラベル「${label}」"
done
echo ""
echo "  ボリュームを追加したい場合:"
echo "    ${INSTALL_DIR}/docker-compose.yml の volumes に"
echo "    - /your/path:/mnt/LabelName  を追記して"
echo "    docker compose -f ${INSTALL_DIR}/docker-compose.yml up -d"
echo ""
echo "  現在の Tailscale Serve 設定:"
tailscale serve status
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
タイトルとURLをコピーしました