AIとも連携出来るブックマーク管理「Karakeep」

以前にも軽く紹介しましたら、改めて入れてみました。ブックマーク管理の「Karakeep」です。Linkwardenと同じようなサービスですが、若干方向性が違い、LinkwardenはPDFや画像などあらゆる方法でWebページを保存しようとするのに対し、Karakeepは検索機能が軽快で、気になったページはとりあえずどんどん追加しておく、といったような使い方になりそうかと。いずれもAIと連携して自動タグ付けなどが行えます。

LXDコンテナにインストールするスクリプト

LXDコンテナにインストールするなら、下記スクリプトをコピペすればOKです。tailscale serveによりHTTPS化され、安全に利用出来ます。

#!/bin/bash
set -euo pipefail
# =============================================================
#  Karakeep セットアップスクリプト (tailscale serve版)
#
#  構成 (LXDコンテナ内で実行):
#   - Karakeep : https://<hostname>.<tailnet>.ts.net:3313 (tailscale serve 3313)
#
#  ディレクトリ構成:
#   /opt/docker/karakeep/
#     docker-compose.yml
#     .env
#
#  前提条件:
#   - LXDコンテナ内でrootまたはsudoで実行
#   - Docker がインストール済みであること
#   - tailscale up 済みであること
#   - Tailscale管理コンソールでHTTPS Certificatesを有効化済み
#     https://login.tailscale.com/admin/dns
# =============================================================

KARAKEEP_DIR="/opt/docker/karakeep"
PORT=13000
TAILSCALE_PORT=3313

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

echo ""
echo "════════════════════════════════════════"
echo "  Karakeep セットアップ (tailscale serve版)"
echo "════════════════════════════════════════"
echo ""

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

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

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

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

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

echo -e "  ${GREEN}ドメイン: ${TAILSCALE_DOMAIN}${NC}"
echo -e "  ${GREEN}Karakeep : https://${TAILSCALE_DOMAIN}:${TAILSCALE_PORT}${NC}"

# ── ディレクトリ作成 ──────────────────────────────
echo ""
echo "==> [2/4] ディレクトリ・設定ファイルを準備..."
mkdir -p "${KARAKEEP_DIR}"
echo -e "  ${GREEN}✓ ${KARAKEEP_DIR}/${NC}"

# ── シークレット生成 ──────────────────────────────
NEXTAUTH_SECRET=$(openssl rand -base64 36)
MEILI_MASTER_KEY=$(openssl rand -base64 36)

# ── .env 生成 ─────────────────────────────────────
cat > "${KARAKEEP_DIR}/.env" <<EOF
KARAKEEP_VERSION=release
NEXTAUTH_SECRET=${NEXTAUTH_SECRET}
MEILI_MASTER_KEY=${MEILI_MASTER_KEY}
NEXTAUTH_URL=https://${TAILSCALE_DOMAIN}:${TAILSCALE_PORT}
EOF
chmod 600 "${KARAKEEP_DIR}/.env"
echo -e "  ${GREEN}✓ ${KARAKEEP_DIR}/.env${NC}"

# ── docker-compose.yml 生成 ───────────────────────
cat > "${KARAKEEP_DIR}/docker-compose.yml" <<EOF
services:
  karakeep:
    image: ghcr.io/karakeep-app/karakeep:\${KARAKEEP_VERSION:-release}
    container_name: karakeep
    restart: unless-stopped
    depends_on:
      - karakeep-chrome
      - karakeep-meilisearch
    volumes:
      - karakeep-data:/data
    ports:
      - "127.0.0.1:${PORT}:3000"
    env_file:
      - .env
    environment:
      MEILI_ADDR: http://karakeep-meilisearch:7700
      BROWSER_WEB_URL: http://karakeep-chrome:9222
      DATA_DIR: /data

  karakeep-chrome:
    image: gcr.io/zenika-hub/alpine-chrome:124
    container_name: karakeep-chrome
    restart: unless-stopped
    command:
      - --no-sandbox
      - --disable-gpu
      - --disable-dev-shm-usage
      - --remote-debugging-address=0.0.0.0
      - --remote-debugging-port=9222
      - --hide-scrollbars

  karakeep-meilisearch:
    image: getmeili/meilisearch:v1.13.3
    container_name: karakeep-meilisearch
    restart: unless-stopped
    volumes:
      - karakeep-meilisearch-data:/meili_data
    environment:
      MEILI_NO_ANALYTICS: "true"
      MEILI_MASTER_KEY: \${MEILI_MASTER_KEY}

volumes:
  karakeep-data:
  karakeep-meilisearch-data:
EOF

echo -e "  ${GREEN}✓ ${KARAKEEP_DIR}/docker-compose.yml${NC}"

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

cd "${KARAKEEP_DIR}"
docker compose pull
docker compose up -d

echo ""
echo -e "  ${GREEN}✓ コンテナ起動完了${NC}"

# ── tailscale serve 設定 ──────────────────────────
echo ""
echo "==> [4/4] tailscale serve を設定..."

tailscale serve --https=${TAILSCALE_PORT} off 2>/dev/null || true
tailscale serve --bg --https=${TAILSCALE_PORT} "http://localhost:${PORT}" || {
    echo -e "${RED}ERROR: tailscale serve の設定に失敗しました${NC}"
    exit 1
}
echo -e "  ${GREEN}✓ tailscale serve 設定完了${NC}"

echo ""
tailscale serve status

# ── 完了メッセージ ────────────────────────────────
echo ""
echo "════════════════════════════════════════"
echo -e "  ${GREEN}✅  起動完了!${NC}"
echo "════════════════════════════════════════"
echo ""
echo "  🌐 URL : https://${TAILSCALE_DOMAIN}:${TAILSCALE_PORT}"
echo ""
echo "  📁 ディレクトリ構成:"
echo "    設定  : ${KARAKEEP_DIR}/docker-compose.yml"
echo "    env   : ${KARAKEEP_DIR}/.env"
echo ""
echo "════════════════════════════════════════"
echo "  ⚠️  初回アクセス時の注意"
echo "════════════════════════════════════════"
echo ""
echo "  karakeep の起動完了まで1〜2分かかる場合があります。"
echo "  アカウント作成後、.env に DISABLE_SIGNUPS=true を追記し"
echo "  docker compose up -d karakeep で再起動することを推奨します。"
echo ""
echo "════════════════════════════════════════"
echo "  🔧 管理コマンド"
echo "════════════════════════════════════════"
echo ""
echo "  ログ確認 : cd ${KARAKEEP_DIR} && docker compose logs -f karakeep"
echo "  停止     : cd ${KARAKEEP_DIR} && docker compose down"
echo "  更新     : cd ${KARAKEEP_DIR} && docker compose pull && docker compose up -d"
echo ""
echo "════════════════════════════════════════"
echo ""

起動したら日本語化、インポートやバックアップ機能も搭載

インストールが完了し起動したら、まずメールアドレスやパスワードを入力して管理者ユーザーを登録します。続いてログインしたら、まず日本語化しましょう。

標準でバックアップ機能も搭載しており、バックアップを設定した場合は標準では下記以下に保存され、ブラウザ上からZIPファイルで保存出来ます。

/var/lib/docker/volumes/karakeep_karakeep-data/_data/assets

環境移行にも備えたフルバックアップと復元

標準機能でバックアップしたzipファイルの中身はブックマークのメタデータのみのjsonで、バナー画像や添付ファイルは含まれていません。完全なバックアップや復元を行うために、下記のスクリプトを作成しました。

スクリプトの使い方

# バックアップ(コンテナ起動中のまま実行OK)
sudo bash karakeep-backup.sh
# → /opt/lxd-data/karakeep/karakeep_20260527_123456.tar.gz に保存

# 復元(バックアップファイルを指定)
sudo bash karakeep-restore.sh /opt/lxd-data/karakeep/karakeep_20260527_123456.tar.gz

# 復元(最新バックアップを自動選択)
sudo bash karakeep-restore.sh

バックアップの中身

内容説明
karakeep-data/db.dbブックマーク・タグ・リスト・ユーザー情報すべて
karakeep-data/assets/バナー画像・スクリーンショット・添付ファイル
meilisearch-data/全文検索インデックス
config/.envシークレットキー含む設定
config/docker-compose.ymlcompose設定

定期自動バックアップにする場合

# cronに登録(毎日午前3時に実行)
echo "0 3 * * * root bash /opt/docker/karakeep/karakeep-backup.sh" | sudo tee /etc/cron.d/karakeep-backup

7日以上古いバックアップは自動削除されます(KEEP_DAYS で変更可能)。

バックアップスクリプト

nano /opt/docker/karakeep/karakeep-backup.sh
cd /opt/docker/karakeep
bash karakeep-backup.sh
#!/bin/bash
set -euo pipefail
# =============================================================
#  Karakeep バックアップスクリプト
#  保存先: /opt/lxd-data/karakeep/karakeep_YYYYMMDD_HHMMSS.tar.gz
# =============================================================

BACKUP_DIR="/opt/lxd-data/karakeep"
COMPOSE_DIR="/opt/docker/karakeep"
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
BACKUP_FILE="${BACKUP_DIR}/karakeep_${TIMESTAMP}.tar.gz"
KEEP_DAYS=7

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

echo ""
echo "════════════════════════════════════════"
echo "  Karakeep バックアップ開始"
echo "  $(date '+%Y-%m-%d %H:%M:%S')"
echo "════════════════════════════════════════"
echo ""

if ! docker inspect karakeep >/dev/null 2>&1; then
    echo -e "${RED}ERROR: karakeepコンテナが存在しません${NC}"
    exit 1
fi

mkdir -p "${BACKUP_DIR}"
echo -e "  ${GREEN}✓ バックアップ先: ${BACKUP_DIR}${NC}"

# ── WALチェックポイント ───────────────────────
echo ""
echo "==> [1/4] データベースをチェックポイント..."
docker exec karakeep sqlite3 /data/db.db "PRAGMA wal_checkpoint(TRUNCATE);" 2>/dev/null || \
    echo -e "  ${YELLOW}⚠ WALチェックポイントをスキップ${NC}"
echo -e "  ${GREEN}✓ 完了${NC}"

# ── ボリュームをコピー ────────────────────────
echo ""
echo "==> [2/4] ボリュームデータを取得..."
TMPDIR=$(mktemp -d)
trap 'rm -rf "${TMPDIR}"' EXIT

echo "  karakeep_karakeep-data ボリュームをコピー中..."
docker run --rm \
    -v karakeep_karakeep-data:/source:ro \
    -v "${TMPDIR}:/backup" \
    alpine \
    sh -c "cp -a /source/. /backup/karakeep-data/"
echo -e "  ${GREEN}✓ karakeep-data${NC}"

echo "  karakeep_karakeep-meilisearch-data ボリュームをコピー中..."
docker run --rm \
    -v karakeep_karakeep-meilisearch-data:/source:ro \
    -v "${TMPDIR}:/backup" \
    alpine \
    sh -c "cp -a /source/. /backup/meilisearch-data/"
echo -e "  ${GREEN}✓ meilisearch-data${NC}"

echo "  設定ファイルをコピー中..."
mkdir -p "${TMPDIR}/config"
cp "${COMPOSE_DIR}/docker-compose.yml" "${TMPDIR}/config/" 2>/dev/null || true
cp "${COMPOSE_DIR}/.env"               "${TMPDIR}/config/" 2>/dev/null || true
docker inspect karakeep --format '{{.Config.Image}}' > "${TMPDIR}/config/image_version.txt" 2>/dev/null || true
date '+%Y-%m-%d %H:%M:%S' > "${TMPDIR}/config/backup_date.txt"
echo -e "  ${GREEN}✓ 設定ファイル${NC}"

# ── 圧縮 ──────────────────────────────────────
echo ""
echo "==> [3/4] 圧縮中..."
tar -czf "${BACKUP_FILE}" -C "${TMPDIR}" .
BACKUP_SIZE=$(du -sh "${BACKUP_FILE}" | cut -f1)
echo -e "  ${GREEN}✓ ${BACKUP_FILE} (${BACKUP_SIZE})${NC}"

# ── 古いバックアップ削除 ──────────────────────
echo ""
echo "==> [4/4] 古いバックアップを整理 (${KEEP_DAYS}日以上前を削除)..."
find "${BACKUP_DIR}" -name "karakeep_*.tar.gz" -mtime +${KEEP_DAYS} -delete
REMAINING=$(find "${BACKUP_DIR}" -name "karakeep_*.tar.gz" | wc -l)
echo -e "  ${GREEN}✓ 保持中のバックアップ: ${REMAINING}件${NC}"

echo ""
echo "════════════════════════════════════════"
echo -e "  ${GREEN}✅ バックアップ完了!${NC}"
echo "════════════════════════════════════════"
echo ""
echo "  📦 ファイル : ${BACKUP_FILE}"
echo "  📏 サイズ   : ${BACKUP_SIZE}"
echo ""
echo "  復元する場合は karakeep-restore.sh を使用してください"
echo "════════════════════════════════════════"
echo ""

復元

下記でスクリプトを保存したら、バックアップファイルを/opt/lxd-data/karakeepに保存します。

mkdir -p /opt/lxd-data/karakeep
cd /opt/lxd-data/karakeep
nano karakeep-restore.sh
#!/bin/bash
set -euo pipefail
# =============================================================
#  Karakeep 復元スクリプト
#
#  使い方:
#   bash karakeep-restore.sh <バックアップファイル>  # 指定
#   bash karakeep-restore.sh                          # 最新を自動選択
# =============================================================

BACKUP_DIR="/opt/lxd-data/karakeep"
COMPOSE_DIR="/opt/docker/karakeep"

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

echo ""
echo "════════════════════════════════════════"
echo "  Karakeep 復元スクリプト"
echo "════════════════════════════════════════"
echo ""

# ── バックアップファイルの決定 ────────────────
if [ -n "${1:-}" ]; then
    BACKUP_FILE="$1"
else
    BACKUP_FILE=$(find "${BACKUP_DIR}" -name "karakeep_*.tar.gz" | sort | tail -n 1)
    if [ -z "${BACKUP_FILE}" ]; then
        echo -e "${RED}ERROR: ${BACKUP_DIR} にバックアップファイルが見つかりません${NC}"
        exit 1
    fi
    echo -e "  ${YELLOW}最新バックアップを自動選択: ${BACKUP_FILE}${NC}"
fi

[ -f "${BACKUP_FILE}" ] || { echo -e "${RED}ERROR: ファイルが見つかりません: ${BACKUP_FILE}${NC}"; exit 1; }

BACKUP_DATE=$(basename "${BACKUP_FILE}" | sed 's/karakeep_\([0-9_]*\)\.tar\.gz/\1/')
echo "  バックアップ日時 : ${BACKUP_DATE}"
echo "  ファイル         : ${BACKUP_FILE}"
echo "  サイズ           : $(du -sh "${BACKUP_FILE}" | cut -f1)"
echo ""

# ── 確認 ──────────────────────────────────────
echo -e "  ${YELLOW}⚠️  警告: 現在のデータはすべて上書きされます!${NC}"
echo ""
read -rp "  本当に復元しますか? (yes/no): " CONFIRM
[ "${CONFIRM}" = "yes" ] || { echo "  キャンセルしました。"; exit 0; }

# ── 前提チェック ──────────────────────────────
echo ""
echo "==> [1/5] 前提確認..."
if ! docker volume inspect karakeep_karakeep-data >/dev/null 2>&1; then
    echo -e "${RED}ERROR: karakeep_karakeep-dataボリュームが存在しません${NC}"
    echo "  先にインストールスクリプトでkarakeepをセットアップしてください。"
    exit 1
fi
echo -e "  ${GREEN}✓ ボリューム確認OK${NC}"

# ── コンテナ停止 ──────────────────────────────
echo ""
echo "==> [2/5] コンテナを停止..."
cd "${COMPOSE_DIR}"
docker compose down
echo -e "  ${GREEN}✓ 停止完了${NC}"

# ── 展開 ──────────────────────────────────────
echo ""
echo "==> [3/5] バックアップを展開中..."
TMPDIR=$(mktemp -d)
# 異常終了時はコンテナを再起動してTMPDIRを削除
trap 'echo -e "${YELLOW}中断されました。コンテナを再起動します...${NC}"; rm -rf "${TMPDIR}"; cd "${COMPOSE_DIR}" && docker compose up -d' EXIT

tar -xzf "${BACKUP_FILE}" -C "${TMPDIR}"
echo -e "  ${GREEN}✓ 展開完了${NC}"

# ── ボリュームに書き戻し ──────────────────────
echo ""
echo "==> [4/5] ボリュームを復元中..."

if [ -d "${TMPDIR}/karakeep-data" ]; then
    echo "  karakeep-data を復元中..."
    docker run --rm \
        -v karakeep_karakeep-data:/dest \
        -v "${TMPDIR}/karakeep-data:/source:ro" \
        alpine \
        sh -c "rm -rf /dest/* /dest/.[!.]* 2>/dev/null; cp -a /source/. /dest/"
    echo -e "  ${GREEN}✓ karakeep-data${NC}"
else
    echo -e "  ${YELLOW}⚠ karakeep-data が見つかりません(スキップ)${NC}"
fi

if [ -d "${TMPDIR}/meilisearch-data" ]; then
    echo "  meilisearch-data を復元中..."
    docker run --rm \
        -v karakeep_karakeep-meilisearch-data:/dest \
        -v "${TMPDIR}/meilisearch-data:/source:ro" \
        alpine \
        sh -c "rm -rf /dest/* /dest/.[!.]* 2>/dev/null; cp -a /source/. /dest/"
    echo -e "  ${GREEN}✓ meilisearch-data${NC}"
else
    echo -e "  ${YELLOW}⚠ meilisearch-data が見つかりません(スキップ)${NC}"
fi

# ── コンテナ再起動 ────────────────────────────
echo ""
echo "==> [5/5] コンテナを再起動..."
trap - EXIT   # 正常終了なのでEXITトラップ解除
rm -rf "${TMPDIR}"

cd "${COMPOSE_DIR}"
docker compose up -d
echo -e "  ${GREEN}✓ 再起動完了${NC}"

echo ""
echo "════════════════════════════════════════"
echo -e "  ${GREEN}✅ 復元完了!${NC}"
echo "════════════════════════════════════════"
echo ""
echo "  karakeepの起動完了まで1〜2分かかる場合があります。"
echo "  ブラウザでアクセスして動作を確認してください。"
echo ""
echo "  ログ確認: cd ${COMPOSE_DIR} && docker compose logs -f karakeep"
echo "════════════════════════════════════════"
echo ""
bash karakeep-restore.sh
タイトルとURLをコピーしました