feat(deploy): split obsidian stack from main + path-filtered GHA
Obsidian Self-hosted LiveSync (CouchDB) выведен в отдельный docker-compose stack и GHA workflow. Любой стек теперь редеплоится независимо. * docker-compose.obsidian.yml: только couchdb в shared external network * docker-compose.prod.yml: убрана couchdb-секция; caddy подключён к shared network для маршрута obsidian.gendsgn.ru → couchdb:5984 * .github/workflows/deploy-obsidian.yml: новый workflow с path-filters (триггерится только при изменениях obsidian-related файлов) * .github/workflows/deploy.yml: + path-filters (триггерится только при main-related файлах) + создание shared network в bootstrap * docs/obsidian-livesync.md: обновлены инструкции под split-архитектуру После push: 1. ssh gendesign 'docker network create gendesign_shared' (один раз) 2. Прописать COUCHDB_USER/PASSWORD в backend/.env.runtime 3. Добавить DNS A-record obsidian.gendsgn.ru → IP VPS 4. git push → GHA задеплоит обе части независимо Преимущества: - Изменения main не разрывают LiveSync клиентов - Изменения obsidian не рестартуют main - Падение obsidian-стека не влияет на основное приложение
This commit is contained in:
parent
79e2e94e09
commit
b0f5c38894
7 changed files with 550 additions and 3 deletions
97
.github/workflows/deploy-obsidian.yml
vendored
Normal file
97
.github/workflows/deploy-obsidian.yml
vendored
Normal file
|
|
@ -0,0 +1,97 @@
|
||||||
|
name: Deploy Obsidian
|
||||||
|
|
||||||
|
# Деплой ТОЛЬКО obsidian-стека (CouchDB).
|
||||||
|
# Триггерится при изменениях:
|
||||||
|
# - docker-compose.obsidian.yml (compose сервиса CouchDB)
|
||||||
|
# - scripts/setup-couchdb.sh (bootstrap)
|
||||||
|
# - docs/obsidian-livesync.md (документация — для history-watcher'а)
|
||||||
|
# - этот workflow
|
||||||
|
#
|
||||||
|
# Не пересобирает никаких Docker-образов (CouchDB официальный с DockerHub).
|
||||||
|
# Не трогает main-стек (backend / frontend / postgres / worker / beat / caddy).
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
paths:
|
||||||
|
- "docker-compose.obsidian.yml"
|
||||||
|
- "scripts/setup-couchdb.sh"
|
||||||
|
- "docs/obsidian-livesync.md"
|
||||||
|
- ".github/workflows/deploy-obsidian.yml"
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: deploy-obsidian
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy-obsidian:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.event_name == 'workflow_dispatch' || github.ref == 'refs/heads/main'
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Deploy obsidian stack via SSH
|
||||||
|
uses: appleboy/ssh-action@v1.0.3
|
||||||
|
with:
|
||||||
|
host: ${{ secrets.DEPLOY_HOST }}
|
||||||
|
username: ${{ secrets.DEPLOY_USER }}
|
||||||
|
key: ${{ secrets.DEPLOY_SSH_KEY }}
|
||||||
|
port: ${{ secrets.DEPLOY_PORT || 22 }}
|
||||||
|
script: |
|
||||||
|
set -euo pipefail
|
||||||
|
cd /opt/gendesign
|
||||||
|
|
||||||
|
# Свежие конфиги из репо
|
||||||
|
git fetch origin main
|
||||||
|
git reset --hard origin/main
|
||||||
|
|
||||||
|
# Создать shared network если её ещё нет (idempotent).
|
||||||
|
# Эта же сеть используется Caddy в main-stack.
|
||||||
|
docker network inspect gendesign_shared >/dev/null 2>&1 \
|
||||||
|
|| docker network create gendesign_shared
|
||||||
|
|
||||||
|
# Стек CouchDB поднимается с собственным project-name,
|
||||||
|
# чтобы main `docker compose ps` не показывал его как часть main.
|
||||||
|
docker compose -p gendesign-obsidian \
|
||||||
|
-f docker-compose.obsidian.yml pull
|
||||||
|
docker compose -p gendesign-obsidian \
|
||||||
|
-f docker-compose.obsidian.yml up -d
|
||||||
|
|
||||||
|
# Bootstrap CouchDB (CORS, db, лимиты) — idempotent
|
||||||
|
if [ -f scripts/setup-couchdb.sh ]; then
|
||||||
|
# Загружаем COUCHDB_PASSWORD из backend/.env.runtime если есть
|
||||||
|
if [ -f backend/.env.runtime ]; then
|
||||||
|
set -a; source backend/.env.runtime; set +a
|
||||||
|
fi
|
||||||
|
# Ждём CouchDB up, потом bootstrap
|
||||||
|
for i in $(seq 1 30); do
|
||||||
|
if docker compose -p gendesign-obsidian \
|
||||||
|
-f docker-compose.obsidian.yml \
|
||||||
|
exec -T couchdb curl -fsS http://localhost:5984/_up >/dev/null 2>&1; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
COUCHDB_HOST=http://localhost:5984 \
|
||||||
|
COUCHDB_USER="${COUCHDB_USER:-obsidian}" \
|
||||||
|
COUCHDB_PASSWORD="${COUCHDB_PASSWORD:?must be set in backend/.env.runtime}" \
|
||||||
|
docker compose -p gendesign-obsidian \
|
||||||
|
-f docker-compose.obsidian.yml \
|
||||||
|
exec -T couchdb bash -c "$(cat scripts/setup-couchdb.sh)" \
|
||||||
|
|| echo "(bootstrap warnings ignored — script is idempotent)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Caddy в main-stack — НЕ перезапускаем тут (другой workflow),
|
||||||
|
# но reload конфига полезен на случай если в Caddyfile добавили
|
||||||
|
# новый obsidian-route только что (главное: image main caddy уже
|
||||||
|
# запущен и подключён к gendesign_shared network).
|
||||||
|
if docker compose -p gendesign -f docker-compose.prod.yml ps caddy --quiet \
|
||||||
|
| grep -q .; then
|
||||||
|
docker compose -p gendesign -f docker-compose.prod.yml \
|
||||||
|
exec -T caddy caddy reload --config /etc/caddy/Caddyfile \
|
||||||
|
|| echo "(caddy reload skipped — main stack not running)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
sleep 3
|
||||||
|
curl -fsS https://obsidian.gendsgn.ru/_up | head -c 200 || true
|
||||||
23
.github/workflows/deploy.yml
vendored
23
.github/workflows/deploy.yml
vendored
|
|
@ -1,8 +1,18 @@
|
||||||
name: Deploy
|
name: Deploy
|
||||||
|
|
||||||
|
# Деплоится только при изменениях основного стека.
|
||||||
|
# Obsidian-стек (CouchDB) — отдельный workflow `deploy-obsidian.yml`.
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
|
paths:
|
||||||
|
- "backend/**"
|
||||||
|
- "frontend/**"
|
||||||
|
- "docker-compose.prod.yml"
|
||||||
|
- "Caddyfile"
|
||||||
|
- ".github/workflows/deploy.yml"
|
||||||
|
# NB: shared compose-fragments (network) — тоже триггерят main
|
||||||
|
# потому что Caddy шарит сеть с obsidian-stack
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
|
|
@ -98,13 +108,20 @@ jobs:
|
||||||
mkdir -p backend
|
mkdir -p backend
|
||||||
printf 'SENTRY_RELEASE=%s\n' "$IMAGE_TAG" > backend/.env.runtime
|
printf 'SENTRY_RELEASE=%s\n' "$IMAGE_TAG" > backend/.env.runtime
|
||||||
|
|
||||||
|
# Создать external network если её нет (нужна Caddy для маршрута
|
||||||
|
# obsidian.gendsgn.ru → couchdb из отдельного obsidian-stack).
|
||||||
|
docker network inspect gendesign_shared >/dev/null 2>&1 \
|
||||||
|
|| docker network create gendesign_shared
|
||||||
|
|
||||||
export IMAGE_TAG="$IMAGE_TAG"
|
export IMAGE_TAG="$IMAGE_TAG"
|
||||||
docker compose -f docker-compose.prod.yml pull
|
# Project name явно — `gendesign` (по имени папки auto), но
|
||||||
docker compose -f docker-compose.prod.yml up -d
|
# фиксируем для consistency с obsidian-stack (-p gendesign-obsidian).
|
||||||
|
docker compose -p gendesign -f docker-compose.prod.yml pull
|
||||||
|
docker compose -p gendesign -f docker-compose.prod.yml up -d
|
||||||
|
|
||||||
# Caddyfile is bind-mounted; up -d won't re-read it. Reload explicitly.
|
# Caddyfile is bind-mounted; up -d won't re-read it. Reload explicitly.
|
||||||
# `|| true` so a temporary Caddy hiccup doesn't fail the whole deploy.
|
# `|| true` so a temporary Caddy hiccup doesn't fail the whole deploy.
|
||||||
docker compose -f docker-compose.prod.yml exec -T caddy \
|
docker compose -p gendesign -f docker-compose.prod.yml exec -T caddy \
|
||||||
caddy reload --config /etc/caddy/Caddyfile || true
|
caddy reload --config /etc/caddy/Caddyfile || true
|
||||||
|
|
||||||
# Clean up disk: dangling layers + tagged images older than 72h that
|
# Clean up disk: dangling layers + tagged images older than 72h that
|
||||||
|
|
|
||||||
17
Caddyfile
17
Caddyfile
|
|
@ -27,6 +27,23 @@ www.gendsgn.ru {
|
||||||
redir https://gendsgn.ru{uri} permanent
|
redir https://gendsgn.ru{uri} permanent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Obsidian Self-hosted LiveSync (CouchDB backend).
|
||||||
|
# Auto-TLS Let's Encrypt. CORS уже включён на стороне CouchDB через bootstrap
|
||||||
|
# (см. scripts/setup-couchdb.sh). Basic-auth — на стороне CouchDB (admin user).
|
||||||
|
#
|
||||||
|
# DNS: A-record obsidian.gendsgn.ru → IP VPS.
|
||||||
|
# Клиенты Obsidian + Self-hosted LiveSync plugin указывают на этот URL.
|
||||||
|
obsidian.gendsgn.ru {
|
||||||
|
encode zstd gzip
|
||||||
|
|
||||||
|
reverse_proxy couchdb:5984 {
|
||||||
|
# Большие документы (vault attachments / images) — увеличиваем timeout
|
||||||
|
transport http {
|
||||||
|
response_header_timeout 120s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
# Plain HTTP by IP — kept for ssh-tunnel / debugging.
|
# Plain HTTP by IP — kept for ssh-tunnel / debugging.
|
||||||
# Caddy issues no TLS here (no hostname).
|
# Caddy issues no TLS here (no hostname).
|
||||||
:80 {
|
:80 {
|
||||||
|
|
|
||||||
41
docker-compose.obsidian.yml
Normal file
41
docker-compose.obsidian.yml
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
# Obsidian Self-hosted LiveSync stack — CouchDB.
|
||||||
|
#
|
||||||
|
# Деплоится ОТДЕЛЬНО от main стека:
|
||||||
|
# docker compose -f docker-compose.obsidian.yml up -d
|
||||||
|
#
|
||||||
|
# Связан с main-stack через external network `gendesign_shared`. Caddy в
|
||||||
|
# main-stack маршрутизирует obsidian.gendsgn.ru → couchdb:5984 через эту сеть.
|
||||||
|
#
|
||||||
|
# Сеть должна быть создана один раз вручную:
|
||||||
|
# docker network create gendesign_shared
|
||||||
|
# (или используй scripts/setup-obsidian-stack.sh).
|
||||||
|
|
||||||
|
services:
|
||||||
|
couchdb:
|
||||||
|
image: couchdb:3
|
||||||
|
container_name: gendesign-couchdb
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
COUCHDB_USER: ${COUCHDB_USER:-obsidian}
|
||||||
|
COUCHDB_PASSWORD: ${COUCHDB_PASSWORD}
|
||||||
|
volumes:
|
||||||
|
- couchdb_data:/opt/couchdb/data
|
||||||
|
- couchdb_config:/opt/couchdb/etc/local.d
|
||||||
|
expose:
|
||||||
|
- "5984"
|
||||||
|
networks:
|
||||||
|
- shared
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-fsS", "http://localhost:5984/_up"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 5
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
couchdb_data:
|
||||||
|
couchdb_config:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
shared:
|
||||||
|
external: true
|
||||||
|
name: gendesign_shared
|
||||||
|
|
@ -124,9 +124,22 @@ services:
|
||||||
depends_on:
|
depends_on:
|
||||||
- backend
|
- backend
|
||||||
- frontend
|
- frontend
|
||||||
|
networks:
|
||||||
|
- default
|
||||||
|
# shared — для маршрута obsidian.gendsgn.ru → couchdb (отдельный stack).
|
||||||
|
# Если obsidian-стек не задеплоен, Caddy просто получит 502 на этом маршруте,
|
||||||
|
# main-приложение не страдает.
|
||||||
|
- shared
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
postgres_data:
|
postgres_data:
|
||||||
redis_data:
|
redis_data:
|
||||||
caddy_data:
|
caddy_data:
|
||||||
caddy_config:
|
caddy_config:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
# Внешняя сеть, создаётся вне compose (см. docs/obsidian-livesync.md).
|
||||||
|
# Связывает main-stack (Caddy) и obsidian-stack (CouchDB).
|
||||||
|
shared:
|
||||||
|
external: true
|
||||||
|
name: gendesign_shared
|
||||||
|
|
|
||||||
255
docs/obsidian-livesync.md
Normal file
255
docs/obsidian-livesync.md
Normal file
|
|
@ -0,0 +1,255 @@
|
||||||
|
# Obsidian Self-hosted LiveSync (на CouchDB)
|
||||||
|
|
||||||
|
Single-tenant self-hosted vault на VPS. Multi-device sync через
|
||||||
|
[Self-hosted LiveSync plugin](https://github.com/vrtmrz/obsidian-livesync).
|
||||||
|
|
||||||
|
## Архитектура (split deploy)
|
||||||
|
|
||||||
|
Obsidian-стек **отделён** от основного приложения (backend/frontend/postgres/redis).
|
||||||
|
Каждый деплоится своим GHA workflow, никто не блокирует другого:
|
||||||
|
|
||||||
|
```
|
||||||
|
┌────────────────────────────────────┐
|
||||||
|
[laptop / mobile]──┤ Caddy (main-stack) │
|
||||||
|
│ обслуживает gendsgn.ru + api │
|
||||||
|
│ + reverse_proxy obsidian.* → │
|
||||||
|
│ ───shared network───┐ │
|
||||||
|
└─────────────────────────┼──────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌──────────────────────────────────┐
|
||||||
|
│ CouchDB (obsidian-stack) │
|
||||||
|
│ gendesign_shared network │
|
||||||
|
│ volumes couchdb_data │
|
||||||
|
└──────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
| | Main stack | Obsidian stack |
|
||||||
|
|---|---|---|
|
||||||
|
| Compose | `docker-compose.prod.yml` | `docker-compose.obsidian.yml` |
|
||||||
|
| Project | `gendesign` (`docker compose -p gendesign`) | `gendesign-obsidian` |
|
||||||
|
| Workflow | `.github/workflows/deploy.yml` | `.github/workflows/deploy-obsidian.yml` |
|
||||||
|
| Триггер push paths | `backend/**`, `frontend/**`, `Caddyfile`, main compose | `docker-compose.obsidian.yml`, `scripts/setup-couchdb.sh`, docs |
|
||||||
|
| Сервисы | backend, frontend, postgres, redis, worker, beat, caddy | couchdb |
|
||||||
|
| Восстанавливается отдельно | ✓ | ✓ |
|
||||||
|
|
||||||
|
**Связь**: внешняя docker-сеть `gendesign_shared` (создаётся вручную или
|
||||||
|
в первом деплое). Caddy в main-stack подключён к ней + к default —
|
||||||
|
маршрут `obsidian.gendsgn.ru` ходит в `couchdb:5984`.
|
||||||
|
|
||||||
|
При отсутствии obsidian-стека main работает без проблем (Caddy просто
|
||||||
|
вернёт 502 на obsidian-route).
|
||||||
|
|
||||||
|
## Deploy
|
||||||
|
|
||||||
|
### 1. DNS
|
||||||
|
|
||||||
|
A-запись:
|
||||||
|
```
|
||||||
|
obsidian.gendsgn.ru. A <VPS_IP>
|
||||||
|
```
|
||||||
|
(тот же IP что `gendsgn.ru`)
|
||||||
|
|
||||||
|
### 2. Secrets на VPS
|
||||||
|
|
||||||
|
Добавить в `backend/.env.runtime` (НЕ в репо!):
|
||||||
|
```bash
|
||||||
|
COUCHDB_USER=obsidian
|
||||||
|
COUCHDB_PASSWORD=<сгенерированный 32-байтный пароль>
|
||||||
|
```
|
||||||
|
|
||||||
|
Сгенерировать пароль:
|
||||||
|
```bash
|
||||||
|
openssl rand -base64 32
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Первое создание shared network на VPS (один раз)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh gendesign
|
||||||
|
docker network create gendesign_shared
|
||||||
|
```
|
||||||
|
|
||||||
|
(автоматически создаётся в каждом из двух workflow если её нет, но
|
||||||
|
для первого ручного запуска удобнее самому).
|
||||||
|
|
||||||
|
### 4. Деплой
|
||||||
|
|
||||||
|
Изменения в `docker-compose.obsidian.yml` / `scripts/setup-couchdb.sh` /
|
||||||
|
`docs/obsidian-livesync.md` → GHA триггерит **`deploy-obsidian.yml`**
|
||||||
|
(не трогает main!).
|
||||||
|
|
||||||
|
Изменения в `backend/**`, `frontend/**`, `docker-compose.prod.yml`,
|
||||||
|
`Caddyfile` → триггерит **`deploy.yml`** (main).
|
||||||
|
|
||||||
|
Для ручного старта впервые:
|
||||||
|
```bash
|
||||||
|
git push # любой из 2 workflow задеплоит свою часть
|
||||||
|
```
|
||||||
|
|
||||||
|
Или вручную с VPS:
|
||||||
|
```bash
|
||||||
|
cd /opt/gendesign
|
||||||
|
# main:
|
||||||
|
docker compose -p gendesign -f docker-compose.prod.yml up -d
|
||||||
|
# obsidian:
|
||||||
|
docker compose -p gendesign-obsidian -f docker-compose.obsidian.yml up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
Caddy автоматически получит Let's Encrypt сертификат для `obsidian.gendsgn.ru`
|
||||||
|
при первом обращении (если DNS уже резолвится).
|
||||||
|
|
||||||
|
### 5. Bootstrap CouchDB (один раз, идемпотентный)
|
||||||
|
|
||||||
|
`deploy-obsidian.yml` запускает bootstrap автоматически после `up -d`.
|
||||||
|
Если нужно вручную:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /opt/gendesign
|
||||||
|
source backend/.env.runtime # подгружаем COUCHDB_PASSWORD
|
||||||
|
COUCHDB_HOST=http://localhost:5984 \
|
||||||
|
COUCHDB_PASSWORD="$COUCHDB_PASSWORD" \
|
||||||
|
docker compose -p gendesign-obsidian -f docker-compose.obsidian.yml \
|
||||||
|
exec -T couchdb bash -c "$(cat scripts/setup-couchdb.sh)"
|
||||||
|
```
|
||||||
|
|
||||||
|
Скрипт:
|
||||||
|
- конвертирует CouchDB в "configured single-node" mode
|
||||||
|
- создаёт database `gendesign-vault`
|
||||||
|
- включает CORS (нужно для мобильных Obsidian)
|
||||||
|
- увеличивает лимиты на размер документов (для крупных attachments)
|
||||||
|
- настраивает auth-timeout (для длинных sync-сессий)
|
||||||
|
|
||||||
|
Идемпотентен — повторный запуск безопасен.
|
||||||
|
|
||||||
|
### 5. Проверка работы
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# из любой точки интернета:
|
||||||
|
curl https://obsidian.gendsgn.ru/_up
|
||||||
|
# → {"status":"ok",...}
|
||||||
|
|
||||||
|
curl -u obsidian:<password> https://obsidian.gendsgn.ru/gendesign-vault
|
||||||
|
# → {"db_name":"gendesign-vault","doc_count":0,...}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Настройка клиента (Obsidian Desktop)
|
||||||
|
|
||||||
|
### 1. Установить Obsidian
|
||||||
|
- https://obsidian.md/download
|
||||||
|
|
||||||
|
### 2. Создать vault
|
||||||
|
- "Create new vault" → название "gendesign" → выбрать локальную папку
|
||||||
|
|
||||||
|
### 3. Установить Self-hosted LiveSync plugin
|
||||||
|
- Settings → Community plugins → "Browse"
|
||||||
|
- Search: "Self-hosted LiveSync" (автор: vrtmrz)
|
||||||
|
- Install → Enable
|
||||||
|
|
||||||
|
### 4. Настроить подключение
|
||||||
|
|
||||||
|
Settings → Self-hosted LiveSync → **Setup wizard** (рекомендуется первый раз):
|
||||||
|
|
||||||
|
| Поле | Значение |
|
||||||
|
|---|---|
|
||||||
|
| URI | `https://obsidian.gendsgn.ru` |
|
||||||
|
| Username | `obsidian` (или то что в `COUCHDB_USER`) |
|
||||||
|
| Password | `<COUCHDB_PASSWORD>` |
|
||||||
|
| Database name | `gendesign-vault` |
|
||||||
|
| End-to-End Encryption | **OFF** (см. ниже про trade-off) |
|
||||||
|
|
||||||
|
Затем:
|
||||||
|
- "Test Database Connection" → должно быть зелёное ✓
|
||||||
|
- "Check database configuration" → если требует — выполнить "Fix all"
|
||||||
|
- "Apply settings"
|
||||||
|
- "LiveSync" → "Enable LiveSync"
|
||||||
|
|
||||||
|
### 5. Первичный sync
|
||||||
|
|
||||||
|
При первом подключении vault пустой и в БД ничего нет. После первой
|
||||||
|
правки .md файла LiveSync запушит изменение в CouchDB.
|
||||||
|
|
||||||
|
С другого устройства/у другого пользователя:
|
||||||
|
- Та же установка
|
||||||
|
- При first sync клиент скачает всё содержимое БД в локальный vault
|
||||||
|
|
||||||
|
### 6. Mobile (iOS / Android)
|
||||||
|
|
||||||
|
- Установить **Obsidian** из App Store / Google Play
|
||||||
|
- Создать **пустой** vault
|
||||||
|
- Установить Self-hosted LiveSync (как на desktop)
|
||||||
|
- Те же настройки → первый sync скачает всё с сервера
|
||||||
|
|
||||||
|
⚠️ На iOS: включить **Background App Refresh** для Obsidian, иначе sync только при
|
||||||
|
открытом приложении.
|
||||||
|
|
||||||
|
## End-to-End Encryption (E2EE)
|
||||||
|
|
||||||
|
| | E2EE ON | E2EE OFF (default у нас) |
|
||||||
|
|---|---|---|
|
||||||
|
| Контент в БД | Зашифрован (admin сервера не может прочитать) | Plain text |
|
||||||
|
| Производительность | Тяжелее на клиенте | Быстрее |
|
||||||
|
| Claude (через MCP/HTTP) сможет писать извне? | Нет (нужен passphrase у Claude) | Да |
|
||||||
|
| Восстановление при потере passphrase | Невозможно | Возможно (через CouchDB admin) |
|
||||||
|
|
||||||
|
**Рекомендую OFF** — vault это рабочие заметки, не банковская тайна.
|
||||||
|
HTTPS + basic auth + закрытый DNS защищают от внешнего читателя.
|
||||||
|
|
||||||
|
Если позже потребуется E2EE — переключить в plugin, ребутнуть БД (replicate from scratch).
|
||||||
|
|
||||||
|
## Multi-user
|
||||||
|
|
||||||
|
CouchDB поддерживает per-user permissions, но для простого MVP:
|
||||||
|
- Один shared `gendesign-vault` database
|
||||||
|
- Все пользователи логинятся как `obsidian` (один аккаунт)
|
||||||
|
|
||||||
|
Для разделения (read-only для одних, write — для других) — нужно добавить
|
||||||
|
users в `_users` DB и настроить security object базы:
|
||||||
|
```bash
|
||||||
|
curl -X PUT -u obsidian:PWD https://obsidian.gendsgn.ru/_users/org.couchdb.user:viewer \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"name":"viewer","password":"VIEWER_PWD","roles":[],"type":"user"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
Затем в vault DB:
|
||||||
|
```json
|
||||||
|
PUT /gendesign-vault/_security
|
||||||
|
{
|
||||||
|
"admins": {"names": ["obsidian"], "roles": []},
|
||||||
|
"members": {"names": ["viewer"], "roles": []}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Backup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Snapshot DB → tar.gz на VPS (можно поставить в cron)
|
||||||
|
docker compose exec couchdb tar czf /tmp/vault-backup-$(date +%F).tar.gz /opt/couchdb/data
|
||||||
|
docker cp gendesign-couchdb:/tmp/vault-backup-*.tar.gz /opt/gendesign-backups/
|
||||||
|
```
|
||||||
|
|
||||||
|
Опционально — `_replicate` через CouchDB API на S3-совместимое хранилище.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### `Failed to connect` в LiveSync
|
||||||
|
- Проверить DNS: `dig obsidian.gendsgn.ru`
|
||||||
|
- Проверить HTTPS: `curl https://obsidian.gendsgn.ru/_up` (должен вернуть `{"status":"ok"}`)
|
||||||
|
- Проверить пароль: `curl -u obsidian:PWD https://obsidian.gendsgn.ru/gendesign-vault`
|
||||||
|
|
||||||
|
### CORS errors на mobile
|
||||||
|
- Запустить `setup-couchdb.sh` ещё раз (или проверить через `_config/cors/origins`)
|
||||||
|
|
||||||
|
### Большие attachments не синкаются
|
||||||
|
- В LiveSync plugin: "Customization & advanced" → "Chunk size" = 100 KB
|
||||||
|
- На сервере: `couchdb/max_document_size` уже = 50 MB
|
||||||
|
|
||||||
|
### Конфликты при одновременной правке
|
||||||
|
- LiveSync создаёт conflict-маркер в файле
|
||||||
|
- Plugin показывает UI для merge ("Show conflict resolution dialog")
|
||||||
|
|
||||||
|
## Что дальше
|
||||||
|
|
||||||
|
После того как multi-device sync работает:
|
||||||
|
- Решить как Claude пишет в vault: MCP filesystem + bridge / самописный CouchDB-MCP / headless Obsidian
|
||||||
|
- Мигрировать существующий `memory-gendesign.jsonl` knowledge graph → .md файлы в vault
|
||||||
107
scripts/setup-couchdb.sh
Normal file
107
scripts/setup-couchdb.sh
Normal file
|
|
@ -0,0 +1,107 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Bootstrap CouchDB для Obsidian Self-hosted LiveSync.
|
||||||
|
#
|
||||||
|
# Запускается ОДИН РАЗ после первого `docker compose up -d couchdb`.
|
||||||
|
# Идемпотентен — повторный запуск безопасен.
|
||||||
|
#
|
||||||
|
# Что делает:
|
||||||
|
# 1. Конвертирует single-node setup в "single-node configured" (требование CouchDB 3.x)
|
||||||
|
# 2. Включает CORS (нужно для Obsidian мобильного / browser-client)
|
||||||
|
# 3. Увеличивает max_http_request_size до 4 GiB (для крупных attachments)
|
||||||
|
# 4. Создаёт database `gendesign-vault` для самого vault
|
||||||
|
# 5. Создаёт database `_users` (системная, для multi-user в будущем)
|
||||||
|
#
|
||||||
|
# Использование:
|
||||||
|
# COUCHDB_PASSWORD=xxx ./scripts/setup-couchdb.sh
|
||||||
|
# # или на VPS:
|
||||||
|
# docker compose exec couchdb bash -c 'curl ...' # ручной путь
|
||||||
|
#
|
||||||
|
# Требует: curl. Запускать с хоста VPS (couchdb должен быть на 127.0.0.1:5984).
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
COUCHDB_HOST="${COUCHDB_HOST:-http://localhost:5984}"
|
||||||
|
COUCHDB_USER="${COUCHDB_USER:-obsidian}"
|
||||||
|
COUCHDB_PASSWORD="${COUCHDB_PASSWORD:?COUCHDB_PASSWORD env var is required}"
|
||||||
|
VAULT_DB="${VAULT_DB:-gendesign-vault}"
|
||||||
|
|
||||||
|
AUTH="-u ${COUCHDB_USER}:${COUCHDB_PASSWORD}"
|
||||||
|
|
||||||
|
echo "=== 0. Wait for CouchDB ready ==="
|
||||||
|
for i in $(seq 1 30); do
|
||||||
|
if curl -fsS "${COUCHDB_HOST}/_up" >/dev/null 2>&1; then
|
||||||
|
echo " CouchDB up after ${i}s"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "=== 1. Convert to 'configured single-node' (idempotent) ==="
|
||||||
|
curl -fsS ${AUTH} -X POST "${COUCHDB_HOST}/_cluster_setup" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"action":"enable_single_node","bind_address":"0.0.0.0","username":"'"${COUCHDB_USER}"'","password":"'"${COUCHDB_PASSWORD}"'"}' \
|
||||||
|
|| echo " (уже configured, OK)"
|
||||||
|
|
||||||
|
echo "=== 2. Системные базы ==="
|
||||||
|
for db in _users _replicator _global_changes; do
|
||||||
|
curl -fsS ${AUTH} -X PUT "${COUCHDB_HOST}/${db}" 2>&1 \
|
||||||
|
| grep -q '"ok":true\|"file_exists"' && echo " ${db}: OK"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "=== 3. Vault database '${VAULT_DB}' ==="
|
||||||
|
curl -fsS ${AUTH} -X PUT "${COUCHDB_HOST}/${VAULT_DB}" 2>&1 \
|
||||||
|
| grep -q '"ok":true\|"file_exists"' && echo " ${VAULT_DB}: OK"
|
||||||
|
|
||||||
|
echo "=== 4. CORS (для мобильных клиентов) ==="
|
||||||
|
cors_settings=(
|
||||||
|
"httpd/enable_cors true"
|
||||||
|
"cors/origins app://obsidian.md,capacitor://localhost,http://localhost"
|
||||||
|
"cors/credentials true"
|
||||||
|
"cors/methods GET,PUT,POST,HEAD,DELETE"
|
||||||
|
"cors/headers accept,authorization,content-type,origin,referer,x-csrf-token"
|
||||||
|
)
|
||||||
|
for kv in "${cors_settings[@]}"; do
|
||||||
|
key="${kv%% *}"
|
||||||
|
value="${kv#* }"
|
||||||
|
curl -fsS ${AUTH} -X PUT \
|
||||||
|
"${COUCHDB_HOST}/_node/_local/_config/${key}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "\"${value}\"" >/dev/null
|
||||||
|
echo " ${key} = ${value}"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "=== 5. Размер запросов / chunks (для больших документов LiveSync) ==="
|
||||||
|
curl -fsS ${AUTH} -X PUT \
|
||||||
|
"${COUCHDB_HOST}/_node/_local/_config/chttpd/max_http_request_size" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '"4294967296"' >/dev/null
|
||||||
|
echo " chttpd/max_http_request_size = 4 GiB"
|
||||||
|
|
||||||
|
curl -fsS ${AUTH} -X PUT \
|
||||||
|
"${COUCHDB_HOST}/_node/_local/_config/couchdb/max_document_size" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '"50000000"' >/dev/null
|
||||||
|
echo " couchdb/max_document_size = 50 MB"
|
||||||
|
|
||||||
|
echo "=== 6. Auth settings (CouchDB 3.x требует session timeout для LiveSync) ==="
|
||||||
|
curl -fsS ${AUTH} -X PUT \
|
||||||
|
"${COUCHDB_HOST}/_node/_local/_config/chttpd_auth/require_valid_user" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '"true"' >/dev/null
|
||||||
|
curl -fsS ${AUTH} -X PUT \
|
||||||
|
"${COUCHDB_HOST}/_node/_local/_config/chttpd_auth/timeout" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '"7200"' >/dev/null
|
||||||
|
echo " chttpd_auth: require_valid_user=true, timeout=7200s"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=== DONE ==="
|
||||||
|
echo "CouchDB готова к Self-hosted LiveSync."
|
||||||
|
echo ""
|
||||||
|
echo "Клиенту Obsidian → Settings → Self-hosted LiveSync → Remote DB:"
|
||||||
|
echo " URL: https://obsidian.gendsgn.ru"
|
||||||
|
echo " Database: ${VAULT_DB}"
|
||||||
|
echo " User: ${COUCHDB_USER}"
|
||||||
|
echo " Password: <COUCHDB_PASSWORD>"
|
||||||
|
echo ""
|
||||||
|
echo "Дальше: 'Test Database Connection' → 'Apply Settings' → 'Replicate Now'."
|
||||||
Loading…
Add table
Reference in a new issue