色々な環境で構築しているので、パスが分散していて少し分かりにくくなってきました。LXDコンテナ内にマウントする場所も、/mnt/lxd-dataになっているものや、/opt/lxd-dataになってたりと。Linuxの標準的なディレクトリ構成のルール(FHS: Filesystem Hierarchy Standard)に照らし合わせると/mnt/lxd-dataが正しいような気もしますが、大容量外部ディスクをマウントしているわけでもなく、永続的データを保存するという観点では/opt以下に全てまとめるのも理にかなっている気もします。
環境によって変わるのも混乱なので/opt以下に統一しようかと思います。
このブログ内もごちゃごちゃしていますが、いつかまとめられれば。
ここではUbuntu 26.04 betaにセットアップしています。基本的にLXDコンテナに閉じ込める設定にしています。
Tailscale
# アップデート
sudo apt update
sudo apt upgrade -y
# Tailscale
sudo apt install curl
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up
SSH
sudo apt install -y openssh-server
以降はSSHで。
Taildrop(ホストにインストール)
これはホスト側に入れておくと便利でしょう。受け取ったデータを/opt/lxd-data/taildropに保存することで、どのコンテナからも参照出来ます。サービス化で再起動しても自動実行されます。
TARGET_DIR="/opt/lxd-data/taildrop"
# ディレクトリが存在しない場合は作成
sudo mkdir -p "$TARGET_DIR"
# サービスファイル作成(sudo tee を使う)
sudo tee /etc/systemd/system/taildrop.service > /dev/null <<EOF
[Unit]
Description=Tailscale File Receiver (Taildrop)
After=network.target tailscaled.service
Requires=tailscaled.service
[Service]
Type=simple
ExecStart=tailscale file get --loop --conflict=rename $TARGET_DIR
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
# 有効化・起動
sudo systemctl daemon-reload
sudo systemctl enable taildrop
sudo systemctl start taildrop
# 確認
sudo systemctl status taildrop
よく使うコマンド
# ログ確認
journalctl -u taildrop -f
# 停止・再起動
sudo systemctl stop taildrop
sudo systemctl restart taildrop
LXD-UI
# LXD インストール
sudo snap install lxd --channel=latest/stable
# 初期化
sudo lxd init --minimal
# HTTPS API を有効化
sudo lxc config set core.https_address :8443
# UI 有効化
sudo snap set lxd ui.enable=true
sudo systemctl reload snap.lxd.daemon
# ユーザーを lxd グループに追加
sudo usermod -aG lxd $USER
一度再起動
sudo reboot
LXD-UI https://ホスト名:8443
ベースコンテナを作成(最小、Docker入り)
lxd-base-minimal
lxd-base-minimalという名前でコンテナを作成。ディスクの追加でlxd-dataをマウント。
/opt/lxd-dataを作成してコンテナに追加
コンテナを作成したら、ホストとの連携用にDiskでマウント先を追加
/opt/lxd-data
/opt/lxd-data
パーミッションを設定
lxc config set lxd-base-minimal raw.idmap "both 1000 1000"
lxc restart lxd-base-minimal
sudo chown -R 1000:1000 /opt/lxd-data
コンテナに入って最小限のインストール
次のコマンドでコンテナに入る。
lxc exec lxd-base-minimal -- bash
コンテナ内で作業。
apt update && apt upgrade -y
apt install -y nano curl
# Tailscaleインストール
curl -fsSL https://tailscale.com/install.sh | sh
shutdown now
lxd-base-docker
lxd-base-minimalコンテナをコピーして作業。
こちらはNestingをAllowにしてから起動します。
# 権限設定
lxc config set lxd-base-docker raw.idmap "both 1000 1000"
lxc restart lxd-base-docker
# コンテナに入る
lxc exec lxd-base-docker -- bash
# Dockerインストール
curl -fsSL https://get.docker.com | sh
#!/bin/bash
# Docker 関連用ディレクトリを作成
mkdir -p /opt/docker
# docker グループが存在する場合のみグループ設定
if getent group docker > /dev/null 2>&1; then
chown -R "${USER:-root}:docker" /opt/docker
chmod -R 775 /opt/docker
chmod -R g+s /opt/docker
# root以外のユーザーの場合のみ usermod を実行
if [ "${USER:-root}" != "root" ]; then
usermod -aG docker "$USER"
echo "グループ変更を反映するには、newgrp docker を実行してください(再起動不要)"
else
echo "rootユーザーのため usermod はスキップしました"
fi
else
# docker グループがない場合はシンプルな権限設定
chown -R "${USER:-root}:${USER:-root}" /opt/docker
chmod -R 755 /opt/docker
echo "docker グループが存在しないため、オーナー権限のみ設定しました"
fi
echo "/opt/docker のセットアップ完了"
作成してLXDコンテナ
これでベースコンテナの作成は終了。必要に応じて、どちらかをコピーして利用。
起動したら一度再起動を忘れずに。
共有フォルダが不要なら、ディスクから外す。
コンテナに入ったら、Tailscaleを有効にする。
tailscale up --authkey=tskey-auth-xxxx
code-server
mkdir /opt/script -p
cd /opt/script
nano install-codeserver.sh
bash install-codeserver.sh
#!/bin/bash
# ============================================================
# Code-Server ネイティブインストール
# 対象OS: Ubuntu 24.04 (LXD,Docker不使用)
# 実行方法: sudo bash install-codeserver.sh
#
# 起動後にブラウザで Japanese Language Pack をインストール後、
# Ctrl+Shift+R でリロードすれば日本語UIになります。
# ============================================================
set -euo pipefail
GREEN='\033[0;32m'; CYAN='\033[0;36m'; YELLOW='\033[1;33m'; RED='\033[0;31m'; NC='\033[0m'
info() { echo -e "${CYAN}[INFO]${NC} $*"; }
success() { echo -e "${GREEN}[OK]${NC} $*"; }
error() { echo -e "${RED}[ERR]${NC} $*"; exit 1; }
[[ $EUID -ne 0 ]] && error "root権限で実行してください: sudo bash $0"
REAL_USER="${SUDO_USER:-$(logname 2>/dev/null || echo $USER)}"
REAL_HOME=$(getent passwd "$REAL_USER" | cut -d: -f6)
info "インストール対象ユーザー: $REAL_USER ($REAL_HOME)"
# --- パスワード自動生成 ---
info "パスワードを自動生成中..."
PASSWORD=$(openssl rand -base64 24 | tr -dc 'a-zA-Z0-9' | head -c 32)
# --- 依存パッケージ ---
info "依存パッケージをインストール中..."
apt-get update -qq
apt-get install -y -qq curl wget locales
# --- 日本語ロケール ---
info "日本語ロケールを設定中..."
locale-gen ja_JP.UTF-8
update-locale LANG=ja_JP.UTF-8 LC_ALL=ja_JP.UTF-8
success "日本語ロケール設定完了"
# --- 最新バージョン取得 ---
info "最新バージョンを確認中..."
LATEST=$(curl -fsSL https://api.github.com/repos/coder/code-server/releases/latest \
| grep '"tag_name"' | sed 's/.*"v\([^"]*\)".*/\1/')
info "最新バージョン: v${LATEST}"
# --- ダウンロード & インストール ---
DEB_URL="https://github.com/coder/code-server/releases/download/v${LATEST}/code-server_${LATEST}_amd64.deb"
info "ダウンロード中..."
wget -q --show-progress -O /tmp/code-server.deb "$DEB_URL"
dpkg -i /tmp/code-server.deb
rm /tmp/code-server.deb
success "code-server v${LATEST} インストール完了"
# --- config.yaml 生成 ---
CONFIG_DIR="$REAL_HOME/.config/code-server"
mkdir -p "$CONFIG_DIR"
cat > "$CONFIG_DIR/config.yaml" <<EOF
bind-addr: 0.0.0.0:8080
auth: password
password: ${PASSWORD}
cert: false
EOF
chown -R "$REAL_USER:$REAL_USER" "$CONFIG_DIR"
chmod 600 "$CONFIG_DIR/config.yaml"
success "config.yaml 生成完了"
# --- argv.json: 日本語化のキモ ---
info "argv.json を配置中..."
VSCODE_DIR="$REAL_HOME/.local/share/code-server/User"
mkdir -p "$VSCODE_DIR"
cat > "$VSCODE_DIR/argv.json" <<'EOF'
{
"locale": "ja"
}
EOF
chown -R "$REAL_USER:$REAL_USER" "$REAL_HOME/.local/share/code-server"
success "argv.json 配置完了"
# --- systemd サービス登録 ---
info "systemd サービスを登録中..."
cat > /etc/systemd/system/code-server.service <<EOF
[Unit]
Description=code-server (VS Code in Browser)
After=network.target
[Service]
Type=exec
User=${REAL_USER}
WorkingDirectory=${REAL_HOME}
ExecStart=/usr/bin/code-server
Restart=always
RestartSec=5
Environment=LANG=ja_JP.UTF-8
Environment=LC_ALL=ja_JP.UTF-8
Environment=HOME=${REAL_HOME}
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable code-server
systemctl restart code-server
success "systemd サービス登録・起動完了"
# --- ファイアウォール ---
if ufw status 2>/dev/null | grep -q "Status: active"; then
info "ufw でポート 8080 を開放中..."
ufw allow 8080/tcp
success "ポート開放済み"
fi
# --- 起動確認 ---
info "起動確認中..."
sleep 4
systemctl is-active --quiet code-server && success "code-server 正常起動" \
|| { echo ""; journalctl -u code-server -n 20 --no-pager; }
# --- 完了メッセージ ---
HOST_IP=$(hostname -I | awk '{print $1}')
echo ""
echo -e "${GREEN}========================================${NC}"
echo -e "${GREEN} Code-Server インストール完了!${NC}"
echo -e "${GREEN}========================================${NC}"
echo ""
echo -e " 🌐 アクセスURL : ${CYAN}http://${HOST_IP}:8080${NC}"
echo -e " 🔑 パスワード : ${YELLOW}${PASSWORD}${NC}"
echo ""
echo -e " 日本語化手順:"
echo -e " 1. Extensions で 'Japanese Language Pack' をインストール"
echo -e " 2. Ctrl+Shift+R でリロード → 日本語UIになります"
echo ""
echo -e " 起動 : ${CYAN}sudo systemctl start code-server${NC}"
echo -e " 停止 : ${CYAN}sudo systemctl stop code-server${NC}"
echo -e " ログ確認: ${CYAN}journalctl -u code-server -f${NC}"
echo ""
echo -e "${YELLOW} ⚠ 本番公開時はリバースプロキシ + HTTPS を推奨します${NC}"
echo ""
Japanese Language Pack
パスワードを変更したい場合は下記ファイルを編集してリスタート。
nano ~/.config/code-server/config.yaml
systemctl restart code-server
/optを追加。以降はcodeserverで。
TSDProxy
bash -c '
INSTALL_DIR="/opt/docker/tsdproxy"
echo ""
echo "========================================="
echo " TSDProxy セットアップ"
echo "========================================="
echo " Tailscale Auth Key を入力してください"
echo " (https://login.tailscale.com/admin/settings/keys)"
echo "========================================="
read -rp " Auth Key: " TS_AUTHKEY
if [ -z "$TS_AUTHKEY" ]; then
echo "エラー: Auth Key が入力されていません。処理を中止します。"
exit 1
fi
echo ""
echo "[1/4] ディレクトリを作成中..."
mkdir -p "${INSTALL_DIR}/config"
mkdir -p "${INSTALL_DIR}/data"
echo "[2/4] Auth Key ファイルを書き込み中..."
echo "${TS_AUTHKEY}" > "${INSTALL_DIR}/config/authkey"
chmod 600 "${INSTALL_DIR}/config/authkey"
echo "[3/4] docker-compose.yml を生成中..."
cat > "${INSTALL_DIR}/docker-compose.yml" << EOF
services:
tsdproxy:
image: almeidapaulopt/tsdproxy:1
container_name: tsdproxy
restart: unless-stopped
ports:
- "8081:8080"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ${INSTALL_DIR}/data:/data
- ${INSTALL_DIR}/config:/config
environment:
- TSDPROXY_AUTHKEYFILE=/config/authkey
EOF
echo "[4/4] TSDProxy を起動中..."
cd "${INSTALL_DIR}"
docker compose up -d
echo ""
sleep 3
if docker ps --filter "name=tsdproxy" --filter "status=running" | grep -q tsdproxy; then
echo "========================================="
echo " ✅ TSDProxy が正常に起動しました!"
echo "========================================="
echo " ダッシュボード: http://$(hostname -I | awk '"'"'{print $1}'"'"'):8081"
echo " 設定ファイル: ${INSTALL_DIR}/config/tsdproxy.yaml"
echo " ログ確認: docker logs -f tsdproxy"
echo "========================================="
else
echo "❌ 起動に失敗した可能性があります。ログを確認してください:"
docker logs tsdproxy 2>&1 | tail -20
fi
'

基本的な書き方
services:
hoarder:
image: ghcr.io/hoarder-app/hoarder:latest
container_name: hoarder
restart: unless-stopped
ports:
- "3000:3000"
labels:
tsdproxy.enable: "true"
tsdproxy.name: "hoarder" # Tailscale上のホスト名 → hoarder.<tailnet>.ts.net
tsdproxy.containerport: "3000" # コンテナ内部のポート番号
よく使うラベル一覧
| ラベル | 必須 | 説明 |
|---|---|---|
tsdproxy.enable: "true" | ✅ | TSDProxy管理下に置く |
tsdproxy.name: "xxxxx" | 推奨 | Tailscaleホスト名(省略するとコンテナ名) |
tsdproxy.containerport: "3000" | 推奨 | コンテナのポート(省略すると最初に公開されているポート) |
tsdproxy.scheme: "http" | 任意 | http or https(デフォルトはhttps) |
tsdproxy.funnel: "true" | 任意 | Tailscale外のインターネットにも公開する場合 |
注意点
ports: の記載はなくてもTSDProxy経由ではアクセスできます。ホストから直接アクセスする必要がない場合は省略してセキュリティを高めることもできます。
Linkwarden
#!/bin/bash
set -euo pipefail
INSTALL_DIR="/opt/docker/linkwarden"
PORT=3300
# ── TailscaleのMagicDNS名を自動取得 ──────────────
TAILSCALE_URL=$(tailscale status --json | python3 -c "
import json, sys
data = json.load(sys.stdin)
dns = data.get('MagicDNSSuffix', '')
name = data.get('Self', {}).get('HostName', '')
print(f'https://linkwarden.{dns}' if dns else f'http://localhost:${PORT}')
")
echo "🌐 NEXTAUTH_URL: $TAILSCALE_URL"
# ── シークレットキー自動生成 ──────────────────────
NEXTAUTH_SECRET=$(openssl rand -base64 36)
POSTGRES_PASSWORD=$(openssl rand -base64 24 | tr -dc 'a-zA-Z0-9' | head -c 32)
# ── インストールディレクトリ ──────────────────────
mkdir -p "$INSTALL_DIR"
cd "$INSTALL_DIR"
# ── .env 生成 ────────────────────────────────────
cat > .env <<EOF
POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
NEXTAUTH_SECRET=${NEXTAUTH_SECRET}
NEXTAUTH_URL=${TAILSCALE_URL}
EOF
chmod 600 .env
# ── docker-compose.yml 生成 ──────────────────────
cat > docker-compose.yml <<EOF
services:
postgres:
image: postgres:16-alpine
container_name: linkwarden-postgres
restart: unless-stopped
env_file: .env
environment:
POSTGRES_DB: linkwarden
POSTGRES_USER: linkwarden
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U linkwarden -d linkwarden"]
interval: 10s
timeout: 5s
retries: 5
linkwarden:
image: ghcr.io/linkwarden/linkwarden:latest
container_name: linkwarden
restart: unless-stopped
env_file: .env
environment:
DATABASE_URL: postgresql://linkwarden:\${POSTGRES_PASSWORD}@postgres:5432/linkwarden
ports:
- "${PORT}:3000"
volumes:
- linkwarden_data:/data/data
depends_on:
postgres:
condition: service_healthy
labels:
tsdproxy.enable: "true"
tsdproxy.name: "linkwarden"
tsdproxy.containerport: "3000"
volumes:
postgres_data:
linkwarden_data:
EOF
# ── 起動 ─────────────────────────────────────────
docker compose pull
docker compose up -d
echo ""
echo "════════════════════════════════════════"
echo " ✅ Linkwarden 起動完了"
echo "════════════════════════════════════════"
echo ""
echo " 🌐 Tailscale URL : $TAILSCALE_URL"
echo " 🌐 ローカル URL : http://$(hostname -I | awk '{print $1}'):${PORT}"
echo " 📂 インストール先: $INSTALL_DIR"
echo ""
echo " ログ確認 : docker compose -f $INSTALL_DIR/docker-compose.yml logs -f"
echo " 停止 : docker compose -f $INSTALL_DIR/docker-compose.yml down"
echo " 更新 : docker compose -f $INSTALL_DIR/docker-compose.yml pull && docker compose -f $INSTALL_DIR/docker-compose.yml up -d"
echo ""
Vaultwarden
#!/bin/bash
set -euo pipefail
INSTALL_DIR="/opt/docker/vaultwarden"
PORT=8222
# ── TailscaleのMagicDNS名を自動取得 ──────────────
TAILSCALE_DOMAIN=$(tailscale status --json | python3 -c "
import json, sys
d = json.load(sys.stdin)
dns = d.get('MagicDNSSuffix', '')
print(f'vaultwarden.{dns}' if dns else '')
")
if [ -z "$TAILSCALE_DOMAIN" ]; then
echo "ERROR: Tailscale MagicDNS名を取得できませんでした"
exit 1
fi
echo "🌐 ドメイン: https://${TAILSCALE_DOMAIN}"
# ── インストールディレクトリ ──────────────────────
mkdir -p "$INSTALL_DIR"
cd "$INSTALL_DIR"
# ── docker-compose.yml 生成 ──────────────────────
cat > docker-compose.yml <<EOF
services:
vaultwarden:
image: vaultwarden/server:latest
container_name: vaultwarden
restart: unless-stopped
environment:
DOMAIN: "https://${TAILSCALE_DOMAIN}"
SIGNUPS_ALLOWED: "true"
volumes:
- ./data:/data
ports:
- "${PORT}:80"
labels:
tsdproxy.enable: "true"
tsdproxy.name: "vaultwarden"
tsdproxy.containerport: "80"
EOF
# ── 起動 ─────────────────────────────────────────
docker compose pull
docker compose up -d
echo ""
echo "════════════════════════════════════════"
echo " ✅ Vaultwarden 起動完了"
echo "════════════════════════════════════════"
echo ""
echo " 🌐 Tailscale URL : https://${TAILSCALE_DOMAIN}"
echo " 🌐 ローカル URL : http://$(hostname -I | awk '{print $1}'):${PORT}"
echo " 📂 インストール先: $INSTALL_DIR"
echo ""
echo " ⚠️ アカウント作成後はSIGNUPS_ALLOWEDをfalseに変更してください"
echo " sed -i 's/SIGNUPS_ALLOWED: \"true\"/SIGNUPS_ALLOWED: \"false\"/' $INSTALL_DIR/docker-compose.yml"
echo " docker compose -f $INSTALL_DIR/docker-compose.yml up -d"
echo ""
echo " ログ確認 : docker compose -f $INSTALL_DIR/docker-compose.yml logs -f"
echo " 停止 : docker compose -f $INSTALL_DIR/docker-compose.yml down"
echo " 更新 : docker compose -f $INSTALL_DIR/docker-compose.yml pull && docker compose -f $INSTALL_DIR/docker-compose.yml up -d"
echo ""
Outline
#!/bin/bash
set -euo pipefail
# =============================================================
# Outline セットアップスクリプト (TSDProxy版)
#
# 構成:
# - Outline : https://outline.<tailnet>.ts.net (TSDProxy)
# - Dex (OIDC) : https://outline-auth.<tailnet>.ts.net (TSDProxy)
# - コンテナ間通信はhttp://dex:5556で直接通信
#
# 前提条件:
# - Tailscale管理コンソールでHTTPS Certificatesを有効化済み
# https://login.tailscale.com/admin/dns
# =============================================================
INSTALL_DIR="/opt/docker/outline"
OUTLINE_PORT=3900
DEX_PORT=5556
# ── TailscaleのMagicDNS名を自動取得 ──────────────
TAILSCALE_DOMAIN=$(tailscale status --json | python3 -c "
import json, sys
d = json.load(sys.stdin)
dns = d.get('MagicDNSSuffix', '')
print(dns if dns else '')
")
if [ -z "$TAILSCALE_DOMAIN" ]; then
echo "ERROR: Tailscale MagicDNS名を取得できませんでした"
exit 1
fi
BASE_URL="https://outline.${TAILSCALE_DOMAIN}"
DEX_URL="https://outline-auth.${TAILSCALE_DOMAIN}"
DEX_INTERNAL="http://dex:${DEX_PORT}"
echo "🌐 Outline URL : $BASE_URL"
echo "🌐 Dex URL : $DEX_URL"
# ── ユーザー登録 ──────────────────────────────────
if ! command -v htpasswd &>/dev/null; then
apt-get install -y apache2-utils &>/dev/null
fi
USERS_YAML=""
USER_COUNT=0
while true; do
USER_COUNT=$((USER_COUNT + 1))
echo ""
echo "── ユーザー ${USER_COUNT} ──────────────────────"
read -rp "ユーザー名(例: yamada): " U_NAME
read -rsp "パスワード: " U_PASS
echo ""
U_HASH=$(htpasswd -bnBC 10 "" "${U_PASS}" | tr -d ':\n' | sed 's/\$2y/\$2a/')
U_UUID=$(cat /proc/sys/kernel/random/uuid)
U_EMAIL="${U_NAME}@local.invalid"
USERS_YAML="${USERS_YAML}
- email: \"${U_EMAIL}\"
hash: \"${U_HASH}\"
username: \"${U_NAME}\"
userID: \"${U_UUID}\""
read -rp "もう1人追加しますか? (y/N): " ADD_MORE
[[ "$ADD_MORE" =~ ^[Yy]$ ]] || break
done
# ── シークレットキー生成 ──────────────────────────
SECRET_KEY=$(openssl rand -hex 32)
UTILS_SECRET=$(openssl rand -hex 32)
POSTGRES_PASSWORD=$(openssl rand -hex 16)
DEX_CLIENT_SECRET=$(openssl rand -hex 16)
# ── インストールディレクトリ ──────────────────────
mkdir -p "$INSTALL_DIR/dex/config"
mkdir -p "$INSTALL_DIR/data/storage"
chown -R 1001:1001 "$INSTALL_DIR/data/storage"
chown -R 1001:1001 "$INSTALL_DIR/dex"
cd "$INSTALL_DIR"
# ── Dex設定ファイル生成 ───────────────────────────
cat > dex/config/config.yaml <<EOF
issuer: ${DEX_URL}
storage:
type: sqlite3
config:
file: /config/dex.db
web:
http: 0.0.0.0:${DEX_PORT}
oauth2:
skipApprovalScreen: true
responseTypes:
- code
staticClients:
- id: outline
name: "Outline Wiki"
secret: "${DEX_CLIENT_SECRET}"
redirectURIs:
- "${BASE_URL}/auth/oidc.callback"
enablePasswordDB: true
staticPasswords:
${USERS_YAML}
logger:
level: info
format: text
EOF
# ── .env 生成 ────────────────────────────────────
cat > .env <<EOF
SECRET_KEY=${SECRET_KEY}
UTILS_SECRET=${UTILS_SECRET}
URL=${BASE_URL}
PORT=3000
FORCE_HTTPS=false
DATABASE_URL=postgres://outline:${POSTGRES_PASSWORD}@postgres:5432/outline?sslmode=disable
REDIS_URL=redis://redis:6379
FILE_STORAGE=local
FILE_STORAGE_LOCAL_ROOT_DIR=/var/lib/outline/data
FILE_STORAGE_UPLOAD_MAX_SIZE=26214400
OIDC_CLIENT_ID=outline
OIDC_CLIENT_SECRET=${DEX_CLIENT_SECRET}
OIDC_AUTH_URI=${DEX_URL}/auth
OIDC_TOKEN_URI=${DEX_INTERNAL}/token
OIDC_USERINFO_URI=${DEX_INTERNAL}/userinfo
OIDC_DISPLAY_NAME=ログイン
OIDC_SCOPES=openid profile email offline_access
DEFAULT_LANGUAGE=ja_JP
LOG_LEVEL=info
EOF
chmod 600 .env
# ── docker-compose.yml 生成 ──────────────────────
cat > docker-compose.yml <<EOF
services:
outline:
image: outlinewiki/outline:latest
container_name: outline
restart: unless-stopped
env_file: .env
ports:
- "${OUTLINE_PORT}:3000"
volumes:
- ./data/storage:/var/lib/outline/data
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
dex:
condition: service_healthy
labels:
tsdproxy.enable: "true"
tsdproxy.name: "outline"
tsdproxy.containerport: "3000"
dex:
image: ghcr.io/dexidp/dex:latest
container_name: outline-dex
restart: unless-stopped
command: dex serve /config/config.yaml
ports:
- "${DEX_PORT}:${DEX_PORT}"
volumes:
- ./dex/config:/config
healthcheck:
test: ["CMD-SHELL", "echo OK"]
interval: 10s
timeout: 5s
retries: 10
start_period: 10s
labels:
tsdproxy.enable: "true"
tsdproxy.name: "outline-auth"
tsdproxy.containerport: "${DEX_PORT}"
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
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
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
EOF
# ── 起動 ─────────────────────────────────────────
docker compose pull
docker compose up -d
# ── TSDProxyをoutlineネットワークに接続 ───────────
echo ""
echo "⏳ TSDProxyをoutlineネットワークに接続中..."
sleep 5
docker network connect outline_default tsdproxy 2>/dev/null || true
docker restart tsdproxy
sleep 8
# ── 起動確認 ─────────────────────────────────────
echo ""
if docker compose ps | grep -E "^outline\s" | grep -q "Up"; then
echo "════════════════════════════════════════"
echo " ✅ Outline 起動完了"
echo "════════════════════════════════════════"
echo ""
echo " 🌐 Outline URL : $BASE_URL"
echo " 🌐 Dex URL : $DEX_URL"
echo " 📂 インストール先: $INSTALL_DIR"
echo ""
echo " ▼ 登録済みユーザー:"
echo "$USERS_YAML" | grep 'email:' | sed 's/.*email: "\(.*\)"/ ログインID: \1/'
echo ""
echo " ⏳ outline / outline-auth がTailscaleに登録されるまで"
echo " 初回は1〜2分ほどかかります"
echo "════════════════════════════════════════"
else
echo "❌ 起動確認に失敗しました。ログを確認してください:"
docker compose logs --tail=20
fi
# ── tsdproxy compose.ymlにネットワーク永続化 ──────
echo ""
echo "⏳ TSDProxy設定にoutlineネットワークを永続化中..."
TSDPROXY_COMPOSE="/opt/docker/tsdproxy/docker-compose.yml"
# networksブロックをservicesのtsdproxyに追加(未追加の場合のみ)
if ! grep -q "outline_default" "$TSDPROXY_COMPOSE"; then
python3 - <<PYEOF
import re
with open("$TSDPROXY_COMPOSE", "r") as f:
content = f.read()
# servicesブロック内のtsdproxyにnetworksを追加
content = re.sub(
r'( tsdproxy:(?:.*\n)*?)( \w)',
lambda m: m.group(1) + ' networks:\n - default\n - outline_default\n' + m.group(2)
if 'networks:' not in m.group(1) else m.group(0),
content
)
# ファイル末尾にnetworksブロックを追加
if 'outline_default:' not in content:
content = content.rstrip() + '''
networks:
default:
outline_default:
external: true
'''
with open("$TSDPROXY_COMPOSE", "w") as f:
f.write(content)
print("✅ tsdproxy docker-compose.yml を更新しました")
PYEOF
else
echo "✅ outline_defaultネットワークは既に設定済みです"
fi
echo ""
echo "════════════════════════════════════════"
echo " ✅ セットアップ完了"
echo "════════════════════════════════════════"
echo ""
echo " ログ確認 : docker compose -f $INSTALL_DIR/docker-compose.yml logs -f"
echo " 停止 : docker compose -f $INSTALL_DIR/docker-compose.yml down"
echo " 更新 : docker compose -f $INSTALL_DIR/docker-compose.yml pull && docker compose -f $INSTALL_DIR/docker-compose.yml up -d"
echo ""
ユーザー名@local.invalid
FleshRSS
#!/bin/bash
set -euo pipefail
INSTALL_DIR="/opt/docker/freshrss"
PORT=6060
# ── インストールディレクトリ & compose生成 ──────
mkdir -p "$INSTALL_DIR"
cd "$INSTALL_DIR"
cat > docker-compose.yml <<EOF
services:
freshrss:
image: freshrss/freshrss:latest
container_name: freshrss
restart: unless-stopped
ports:
- "${PORT}:80"
volumes:
- freshrss_data:/var/www/FreshRSS/data
- freshrss_extensions:/var/www/FreshRSS/extensions
environment:
TZ: Asia/Tokyo
CRON_MIN: '*/15'
labels:
tsdproxy.enable: "true"
tsdproxy.name: "freshrss"
tsdproxy.containerport: "80"
volumes:
freshrss_data:
freshrss_extensions:
EOF
# ── 起動 ────────────────────────────────────────
docker compose pull
docker compose up -d
echo "⏳ コンテナ起動待機中..."
sleep 10
# ── 拡張機能インストール ─────────────────────────
echo "📦 拡張機能をインストール中..."
apt-get install -y unzip &>/dev/null
# Three Panes View
curl -sL "https://framagit.org/nicofrand/xextension-threepanesview/-/archive/master/xextension-threepanesview-master.zip" -o /tmp/tpv.zip
unzip -q /tmp/tpv.zip -d /tmp/
docker cp /tmp/xextension-threepanesview-master freshrss:/var/www/FreshRSS/extensions/xExtension-ThreePanesView
rm -rf /tmp/tpv.zip /tmp/xextension-threepanesview-master
# AF Readability
curl -sL "https://github.com/Niehztog/freshrss-af-readability/archive/refs/heads/master.zip" -o /tmp/af.zip
unzip -q /tmp/af.zip -d /tmp/
docker cp /tmp/freshrss-af-readability-master freshrss:/var/www/FreshRSS/extensions/xExtension-af_readability
rm -rf /tmp/af.zip /tmp/freshrss-af-readability-master
echo ""
echo "════════════════════════════════════════"
echo " ✅ FreshRSS 起動完了"
echo "════════════════════════════════════════"
echo ""
echo " 🌐 Tailscale URL : https://freshrss.$(tailscale status --json | python3 -c "import json,sys; print(json.load(sys.stdin).get('MagicDNSSuffix',''))")"
echo " 🌐 ローカル URL : http://$(hostname -I | awk '{print $1}'):${PORT}"
echo " 📂 インストール先: $INSTALL_DIR"
echo ""
echo " ⚠️ 初回セットアップ時のURL設定は Tailscale URL を入力してください"
echo ""
echo " ログ確認 : docker compose -f $INSTALL_DIR/docker-compose.yml logs -f"
echo " 停止 : docker compose -f $INSTALL_DIR/docker-compose.yml down"
echo " 更新 : docker compose -f $INSTALL_DIR/docker-compose.yml pull && docker compose -f $INSTALL_DIR/docker-compose.yml up -d"
echo ""
CSS
#stream {
width: 280px !important;
min-width: 280px !important;
}
Dockhand
#!/bin/bash
set -euo pipefail
INSTALL_DIR="/opt/docker/dockhand"
PORT=3333
mkdir -p "$INSTALL_DIR"
cd "$INSTALL_DIR"
cat > docker-compose.yml <<EOF
services:
dockhand:
image: fnsys/dockhand:latest
container_name: dockhand
restart: unless-stopped
ports:
- "${PORT}:3000"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- dockhand_data:/app/data
labels:
tsdproxy.enable: "true"
tsdproxy.name: "dockhand"
tsdproxy.containerport: "3000"
volumes:
dockhand_data:
EOF
docker compose pull
docker compose up -d
echo ""
echo "════════════════════════════════════════"
echo " ✅ Dockhand 起動完了"
echo "════════════════════════════════════════"
echo ""
echo " 🌐 Tailscale URL : https://dockhand.$(tailscale status --json | python3 -c "import json,sys; print(json.load(sys.stdin).get('MagicDNSSuffix',''))")"
echo " 🌐 ローカル URL : http://$(hostname -I | awk '{print $1}'):${PORT}"
echo " 📂 インストール先: $INSTALL_DIR"
echo ""
echo " ログ確認 : docker compose -f $INSTALL_DIR/docker-compose.yml logs -f"
echo " 停止 : docker compose -f $INSTALL_DIR/docker-compose.yml down"
echo " 更新 : docker compose -f $INSTALL_DIR/docker-compose.yml pull && docker compose -f $INSTALL_DIR/docker-compose.yml up -d"
echo ""
Immich(/opt/lxd-data使用)
重いので単独のコンテナにします。
# 権限設定
lxc config set immich raw.idmap "both 1000 1000"
lxc restart immich
# コンテナに入る
lxc exec immich -- bash
tailscale up --authkey=tskey-auth-xxxx
nano setup-immich.sh
# 下記スクリプトを貼り付け
bash setup-immich.sh
#!/bin/bash
set -e
WORK_DIR="/opt/docker/immich"
DATA_DIR="/opt/lxd-data/docker/immich"
mkdir -p "${WORK_DIR}"
mkdir -p "${DATA_DIR}"
cd "${WORK_DIR}"
# --- .env 生成(既存があればスキップ)---
if [ ! -f .env ]; then
DB_PASSWORD=$(openssl rand -hex 16)
cat > .env <<EOF
UPLOAD_LOCATION=${DATA_DIR}/library
DB_DATA_LOCATION=${DATA_DIR}/postgres
IMMICH_VERSION=release
DB_PASSWORD=${DB_PASSWORD}
DB_USERNAME=postgres
DB_DATABASE_NAME=immich
EOF
chmod 600 .env
echo "[OK] .env を新規作成"
else
echo "[SKIP] .env は既存のものを使用"
DB_PASSWORD=$(grep DB_PASSWORD .env | cut -d= -f2)
fi
# --- docker-compose.yml 生成 ---
cat > docker-compose.yml <<'EOF'
name: immich
services:
immich-server:
container_name: immich_server
image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
volumes:
- ${UPLOAD_LOCATION}:/usr/src/app/upload
- /etc/localtime:/etc/localtime:ro
env_file:
- .env
ports:
- '127.0.0.1:2283:2283'
depends_on:
- redis
- database
restart: always
healthcheck:
disable: false
immich-machine-learning:
container_name: immich_machine_learning
image: ghcr.io/immich-app/immich-machine-learning:${IMMICH_VERSION:-release}
volumes:
- model-cache:/cache
env_file:
- .env
restart: always
healthcheck:
disable: false
redis:
container_name: immich_redis
image: docker.io/redis:6.2-alpine
healthcheck:
test: redis-cli ping || exit 1
restart: always
database:
container_name: immich_postgres
image: docker.io/tensorchord/pgvecto-rs:pg14-v0.2.0
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_USER: ${DB_USERNAME}
POSTGRES_DB: ${DB_DATABASE_NAME}
volumes:
- ${DB_DATA_LOCATION}:/var/lib/postgresql/data
healthcheck:
test: >
pg_isready --dbname='${DB_DATABASE_NAME}' --username='${DB_USERNAME}' || exit 1;
Chksum="$$(psql --dbname='${DB_DATABASE_NAME}' --username='${DB_USERNAME}'
--tuples-only --no-align --command='SELECT COALESCE(SUM(checksum_failures), 0)
FROM pg_stat_database')";
echo "checksum failure count is $$Chksum";
[ "$$Chksum" = '0' ] || exit 1
interval: 5m
start_interval: 30s
start_period: 5m
restart: always
volumes:
model-cache:
EOF
# --- Tailscale Serve 設定(冪等) ---
tailscale serve --bg 2283
# --- フルドメイン名取得 ---
TAILSCALE_DOMAIN=$(tailscale status --json | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(d['Self']['DNSName'].rstrip('.'))
")
# --- 起動 ---
docker compose up -d
echo ""
echo "======================================"
echo " Immich 起動完了"
echo "======================================"
echo " URL : https://${TAILSCALE_DOMAIN}"
echo " DB_PASSWORD: ${DB_PASSWORD}"
echo " 設定ファイル: ${WORK_DIR}/.env"
echo " データ保存先: ${DATA_DIR}"
echo "======================================"
| パス | 内容 |
|---|---|
/opt/docker/immich/docker-compose.yml | Compose定義 |
/opt/docker/immich/.env | 設定・パスワード |
/opt/lxd-data/docker/immich/library/ | 写真・動画データ |
/opt/lxd-data/docker/immich/postgres/ | DBデータ |
Nextcloud & Onlyoffice(/opt/lxd-data使用)
こちらも単独のコンテナ
# 権限設定
lxc config set nextcloud raw.idmap "both 1000 1000"
lxc restart nextcloud
# コンテナに入る
lxc exec nextcloud -- bash
tailscale up --authkey=tskey-auth-xxxx
#!/bin/bash
set -e
# === Tailscale フルドメイン自動取得 ===
TAILSCALE_DOMAIN=$(tailscale status --json | python3 -c "
import json,sys
d=json.load(sys.stdin)
print(d['Self']['DNSName'].rstrip('.'))
")
echo "[OK] Tailscale ドメイン: ${TAILSCALE_DOMAIN}"
# === JWT シークレット自動生成 ===
JWT_SECRET=$(openssl rand -hex 32)
# === 共有ネットワーク作成(既存なら再利用) ===
docker network inspect onlyoffice_net >/dev/null 2>&1 \
|| docker network create onlyoffice_net
echo "[OK] ネットワーク onlyoffice_net を確認しました"
# === Tailscale Serve 設定 ===
tailscale serve --bg --https=443 8080 || true
tailscale serve --bg --https=444 9000 || true
echo "[OK] Tailscale Serve 設定完了 (443 → 8080)"
# ============================================================
# Nextcloud
# ============================================================
NEXTCLOUD_WORK_DIR="/opt/docker/nextcloud"
NEXTCLOUD_DATA_DIR="/opt/lxd-data/docker/nextcloud"
mkdir -p "${NEXTCLOUD_WORK_DIR}"
mkdir -p "${NEXTCLOUD_DATA_DIR}"/{userdata,db}
cat > "${NEXTCLOUD_WORK_DIR}/docker-compose.yml" <<EOF
services:
db:
image: mariadb:10.6
container_name: nextcloud-db
restart: always
command: --transaction-isolation=READ-COMMITTED --binlog-format=ROW
volumes:
- ${NEXTCLOUD_DATA_DIR}/db:/var/lib/mysql
environment:
MYSQL_ROOT_PASSWORD: rootpass
MYSQL_PASSWORD: nextcloudpass
MYSQL_DATABASE: nextcloud
MYSQL_USER: nextcloud
networks:
- default
app:
image: nextcloud:latest
container_name: nextcloud-app
restart: always
ports:
- "127.0.0.1:8080:80"
depends_on:
- db
volumes:
- nextcloud_app:/var/www/html
- ${NEXTCLOUD_DATA_DIR}/userdata:/var/www/html/data
environment:
MYSQL_PASSWORD: nextcloudpass
MYSQL_DATABASE: nextcloud
MYSQL_USER: nextcloud
MYSQL_HOST: nextcloud-db
NEXTCLOUD_TRUSTED_DOMAINS: "${TAILSCALE_DOMAIN}"
OVERWRITEHOST: "${TAILSCALE_DOMAIN}"
OVERWRITEPROTOCOL: https
networks:
- default
- onlyoffice_net
volumes:
nextcloud_app:
networks:
default:
onlyoffice_net:
external: true
EOF
cd "${NEXTCLOUD_WORK_DIR}"
docker compose up -d
echo "[1/2] Nextcloud 起動完了"
# ============================================================
# OnlyOffice
# ============================================================
ONLYOFFICE_WORK_DIR="/opt/docker/onlyoffice"
mkdir -p "${ONLYOFFICE_WORK_DIR}"
cat > "${ONLYOFFICE_WORK_DIR}/.env" <<EOF
JWT_SECRET=${JWT_SECRET}
EOF
chmod 600 "${ONLYOFFICE_WORK_DIR}/.env"
cat > "${ONLYOFFICE_WORK_DIR}/docker-compose.yml" <<EOF
services:
onlyoffice-docs:
image: onlyoffice/documentserver:latest
container_name: onlyoffice-docs
restart: always
stdin_open: true
tty: true
ports:
- "127.0.0.1:9000:80"
environment:
JWT_ENABLED: "true"
JWT_SECRET: "${JWT_SECRET}"
volumes:
- onlyoffice_logs:/var/log/onlyoffice
- onlyoffice_data:/var/www/onlyoffice/Data
- onlyoffice_lib:/var/lib/onlyoffice
- onlyoffice_db:/var/lib/postgresql
networks:
- onlyoffice_net
volumes:
onlyoffice_logs:
onlyoffice_data:
onlyoffice_lib:
onlyoffice_db:
networks:
onlyoffice_net:
external: true
EOF
cd "${ONLYOFFICE_WORK_DIR}"
docker compose up -d
echo "[2/2] OnlyOffice 起動完了"
# ============================================================
# 完了メッセージ
# ============================================================
echo ""
echo "======================================"
echo " 起動完了"
echo "======================================"
echo ""
echo " Nextcloud : https://${TAILSCALE_DOMAIN}"
echo " OnlyOffice : 内部ネットワークのみ (http://onlyoffice-docs)"
echo ""
echo "======================================"
echo " Nextcloud 管理画面 → ONLYOFFICE 設定"
echo "======================================"
echo ""
echo " ONLYOFFICE Docs アドレス:"
echo " https://${TAILSCALE_DOMAIN}:444"
echo ""
echo " JWT シークレット: ${JWT_SECRET}"
echo ""
echo " 認証ヘッダー:(空白のまま)"
echo ""
echo " サーバーから内部リクエストに利用されるONLYOFFICE Docs アドレス:"
echo " http://onlyoffice-docs"
echo ""
echo " ONLYOFFICE Docsから内部リクエストに利用されるサーバーアドレス:"
echo " http://nextcloud-app"
echo ""
echo "======================================"
echo " 個別アップデート手順"
echo "======================================"
echo ""
echo " Nextcloud のみ更新:"
echo " cd ${NEXTCLOUD_WORK_DIR} && docker compose pull && docker compose up -d"
echo ""
echo " OnlyOffice のみ更新:"
echo " cd ${ONLYOFFICE_WORK_DIR} && docker compose pull && docker compose up -d"
echo ""
フォント追加
/opt/lxd-data/taildrop/fonts.zipがあると仮定。
cd /opt/lxd-data/taildrop
apt install unzip
unzip Fonts.zip
mv Fonts /opt/lxd-data
コンテナで作業
# 設定ファイル編集。下記1行を追加
cd /opt/docker/onlyoffice/
nano docker-compose.yml
OnlyOffice がフォントを探す標準パス(/usr/share/fonts 以下)にマウントします。
volumes:
- /opt/lxd-data/Fonts:/usr/share/fonts/custom
そして、コンテナを再作成すれば多数のフォントを使えるようになります。
# コンテナ再作成(ボリューム変更を反映)
docker compose up -d --force-recreate onlyoffice-docs
# フォント再生成
docker exec -it onlyoffice-docs documentserver-generate-allfonts.sh
# フォント一覧確認
docker exec -it onlyoffice-docs fc-list
Syncthings
#!/bin/bash
# =============================================================================
# Syncthing 直インストールスクリプト
# 使い方: bash install-syncthing.sh (rootで実行)
# =============================================================================
set -euo pipefail
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC='\033[0m'
info() { echo -e "${BLUE}[INFO]${NC} $*"; }
success() { echo -e "${GREEN}[OK]${NC} $*"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
error() { echo -e "${RED}[ERROR]${NC} $*"; exit 1; }
# ── root確認 ───────────────────────────────────────────────
[[ $EUID -ne 0 ]] && error "このスクリプトはrootで実行してください(sudo bash install-syncthing.sh)"
# ── Syncthing実行ユーザーの作成 ───────────────────────────
SYNCTHING_USER="syncthing"
if id "$SYNCTHING_USER" &>/dev/null; then
info "ユーザー '$SYNCTHING_USER' はすでに存在します。スキップします。"
else
info "ユーザー '$SYNCTHING_USER' を作成します..."
useradd -r -m -s /bin/bash "$SYNCTHING_USER"
success "ユーザー '$SYNCTHING_USER' を作成しました。"
fi
# www-dataグループに追加(Nextcloudファイルへのアクセス用)
usermod -aG www-data "$SYNCTHING_USER"
success "ユーザー '$SYNCTHING_USER' を www-data グループに追加しました。"
# ── config.xml パスを解決する関数 ─────────────────────────
find_config() {
local home
home=$(getent passwd "$SYNCTHING_USER" | cut -d: -f6)
local candidates=(
"$home/.local/state/syncthing/config.xml"
"$home/.local/share/syncthing/config.xml"
"$home/.config/syncthing/config.xml"
)
for p in "${candidates[@]}"; do
[[ -f "$p" ]] && echo "$p" && return
done
find "$home" -name "config.xml" -path "*/syncthing/*" 2>/dev/null | head -1
}
# ── apt リポジトリ追加 & インストール ─────────────────────
info "Syncthing 公式リポジトリを追加します..."
mkdir -p /etc/apt/keyrings
curl -fsSL https://syncthing.net/release-key.gpg \
| tee /etc/apt/keyrings/syncthing-archive-keyring.gpg > /dev/null
echo "deb [signed-by=/etc/apt/keyrings/syncthing-archive-keyring.gpg] https://apt.syncthing.net/ syncthing stable" \
| tee /etc/apt/sources.list.d/syncthing.list > /dev/null
apt-get update -q || true
apt-get install -y syncthing || error "Syncthing のインストールに失敗しました。"
success "Syncthing をインストールしました。"
# ── systemd サービス登録 ───────────────────────────────────
info "systemd サービスを登録します(ユーザー: $SYNCTHING_USER)..."
systemctl enable syncthing@$SYNCTHING_USER
systemctl start syncthing@$SYNCTHING_USER
success "Syncthing サービスを起動しました。"
# ── config.xml 生成待機(最大60秒)────────────────────────
info "config.xml の生成を待機中..."
CONFIG_FILE=""
for i in $(seq 1 30); do
CONFIG_FILE=$(find_config)
[[ -n "$CONFIG_FILE" ]] && break
sleep 2
done
[[ -z "$CONFIG_FILE" ]] && error "config.xml が見つかりませんでした。"
success "config.xml を検出しました: $CONFIG_FILE"
# ── GUI をリモートアクセス可能に変更 ──────────────────────
info "GUI をリモートアクセス可能に設定します..."
systemctl stop syncthing@$SYNCTHING_USER
python3 - "$CONFIG_FILE" << 'PYEOF'
import xml.etree.ElementTree as ET, sys
tree = ET.parse(sys.argv[1])
root = tree.getroot()
gui = root.find('gui')
def set_or_create(parent, tag, text):
el = parent.find(tag)
if el is None:
el = ET.SubElement(parent, tag)
el.text = text
set_or_create(gui, 'address', '0.0.0.0:8384')
tree.write(sys.argv[1], encoding='unicode', xml_declaration=True)
print("GUIアドレスを 0.0.0.0:8384 に変更しました。")
PYEOF
# ── Nextcloud ディレクトリのパーミッション設定 ────────────
info "Nextcloud ディレクトリのパーミッションを設定します..."
NC_DATA="/opt/lxd-data/docker/nextcloud/data/data"
# Nextcloudのユーザーディレクトリを動的に検出
if [ -d "$NC_DATA" ]; then
chmod o+rx /opt/lxd-data/docker/nextcloud
chmod o+rx /opt/lxd-data/docker/nextcloud/data
chmod o+rx "$NC_DATA"
# NC_DATA配下のユーザーディレクトリを全て処理
for USER_DIR in "$NC_DATA"/*/; do
NC_USER=$(basename "$USER_DIR")
# システムディレクトリはスキップ
[[ "$NC_USER" == "appdata_"* ]] && continue
[[ "$NC_USER" == "__groupfolders" ]] && continue
[[ "$NC_USER" == "files_external" ]] && continue
if [ -d "$USER_DIR/files" ]; then
info "Nextcloudユーザー '$NC_USER' のパーミッションを設定中..."
chmod o+rx "$USER_DIR"
chmod -R o+rX "$USER_DIR/files"
chmod o+w "$USER_DIR/files"
success " $USER_DIR/files のパーミッション設定完了"
fi
done
success "Nextcloud パーミッション設定が完了しました。"
else
warn "Nextcloudデータディレクトリが見つかりません: $NC_DATA"
warn "Nextcloudセットアップ後に手動でパーミッションを設定してください。"
fi
# ── Syncthing 再起動 ───────────────────────────────────────
info "Syncthing を起動します..."
systemctl start syncthing@$SYNCTHING_USER
sleep 3
# ── Device ID 取得 ─────────────────────────────────────────
DEVICE_ID=$(sudo -u "$SYNCTHING_USER" syncthing --device-id 2>/dev/null \
|| echo "(GUIの「情報」から確認してください)")
HOST_IP=$(hostname -I | awk '{print $1}')
# ── 完了メッセージ ─────────────────────────────────────────
echo ""
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${GREEN} Syncthing インストール完了!${NC}"
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo ""
echo -e " Web UI : ${BLUE}http://$HOST_IP:8384${NC}"
echo -e " 設定ファイル : $CONFIG_FILE"
echo -e " 実行ユーザー : $SYNCTHING_USER"
echo ""
echo -e " Device ID(バックアップPCへの接続時に使用):"
echo -e " ${YELLOW}$DEVICE_ID${NC}"
echo ""
echo -e "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e " 次のステップ:"
echo -e " 1. Web UI にアクセスしてパスワードを設定"
echo -e " (右上メニュー → Settings → GUI → GUI Authentication)"
echo -e " 2. フォルダを追加(ホストの絶対パスをそのまま入力)"
echo -e " 例: /opt/lxd-data/docker/immich/library/library/admin"
# 検出されたNextcloudユーザーを表示
if [ -d "$NC_DATA" ]; then
for USER_DIR in "$NC_DATA"/*/; do
NC_USER=$(basename "$USER_DIR")
[[ "$NC_USER" == "appdata_"* ]] && continue
[[ "$NC_USER" == "__groupfolders" ]] && continue
[[ "$NC_USER" == "files_external" ]] && continue
[ -d "$USER_DIR/files" ] && \
echo -e " /opt/lxd-data/docker/nextcloud/data/data/${NC_USER}/files"
done
fi
echo -e " 3. 各フォルダのタイプを ${YELLOW}Send Only${NC} に設定"
echo -e " 4. バックアップPCと Device ID を交換して接続"
echo -e " 5. バックアップPC側を ${YELLOW}Receive Only${NC} + ${YELLOW}階段状バージョニング${NC} に設定"
echo -e "${YELLOW}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo ""
# ── UFW 案内 ───────────────────────────────────────────────
if command -v ufw &>/dev/null && ufw status | grep -q "Status: active"; then
echo -e "${YELLOW} UFW が有効です。以下でポートを開放してください:${NC}"
echo ""
echo " ufw allow 8384/tcp # Web UI"
echo " ufw allow 22000/tcp # Sync (TCP)"
echo " ufw allow 22000/udp # Sync (QUIC)"
echo " ufw allow 21027/udp # ローカル探索"
echo ""
fi
Cockpit
VMが必要ならCockpitが便利なのでホストにインストール
#!/bin/bash
set -e
# --- Cockpit + Tailscale セットアップ ---
sudo apt install -y nano cockpit
# Ubuntu 26.04で管理者権限変更できるように
sudo update-alternatives --set sudo /usr/bin/sudo.ws
# 仮想マシンプラグイン
sudo apt install -y cockpit-machines
# ファイラー Navigator プラグイン
sudo apt install -y git
git clone https://github.com/45Drives/cockpit-navigator.git
cd cockpit-navigator
git checkout v0.5.8
sudo apt install -y make
sudo make install
cd ..
rm -rf cockpit-navigator
# --- Tailscale Serve 設定 ---
sudo tailscale serve --bg http://localhost:9090
# Tailscaleのフルドメイン名を自動取得
TAILSCALE_DOMAIN=$(tailscale status --json | python3 -c "
import json, sys
data = json.load(sys.stdin)
self = data.get('Self', {})
dns = self.get('DNSName', '').rstrip('.')
print(dns)
")
# CockpitのCSPにTailscaleドメインを許可(HTTPSアクセス対応)
sudo mkdir -p /etc/cockpit
sudo tee /etc/cockpit/cockpit.conf << EOF
[WebService]
AllowUnencrypted=true
Origins = https://${TAILSCALE_DOMAIN}
EOF
sudo systemctl restart cockpit
echo ""
echo "======================================"
echo " セットアップ完了!"
echo "======================================"
echo " アクセス先: https://${TAILSCALE_DOMAIN}"
echo "======================================"
デフォルトの場所(/var/lib/libvirt/images)よりもアクセスしやすいように変更。
# ディレクトリ作成
sudo mkdir -p /opt/vm
# グループをlibvirtに統一
sudo chown root:libvirt /opt/vm
# setgidビット付与
sudo chmod 2775 /opt/vm
# 自分をlibvirtグループに追加
sudo usermod -aG libvirt $USER
# デフォルトプールとして登録
virsh pool-define-as default dir --target /opt/vm
virsh pool-build default
virsh pool-start default
virsh pool-autostart default
一度再起動。
sudo reboot
ISOをコピー
mv ~/iso/*.iso /opt/vm

