Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ selfsync/
│ ├── main.rs # axum server entry point + routes + CLI
│ ├── proto.rs # Generated protobuf types
│ ├── auth.rs # BrowserKind + ClientIdentity::from_request
│ ├── device.rs # Per-device admission (client_id query param, approval switch)
│ ├── admin/ # Admin page on SELFSYNC_ADMIN_ADDR (askama, zh/en, sync UA labels)
│ ├── progress.rs # Progress token encoding/decoding
│ ├── util.rs # Shared utilities (gen_id, now_millis, BASE64)
│ ├── db/
Expand All @@ -48,8 +50,7 @@ selfsync/
│ ├── sync.rs # POST /command dispatch
│ ├── commit.rs # COMMIT: create/update entities
│ ├── get_updates.rs # GET_UPDATES: fetch by version
│ ├── init.rs # User initialization (Nigori + bookmarks per browser)
│ └── users.rs # GET / user list page
│ └── init.rs # User initialization (Nigori + bookmarks per browser)
```

## Sync Server
Expand All @@ -58,13 +59,14 @@ selfsync/
- **Endpoints** (trailing slashes are stripped by `NormalizePathLayer`, so browser-appended `/command/` matches `/command`):
- `POST /command`, `POST /chrome-sync/command` — Chromium-family entry points
- `POST /v1/feeds/me/syncEntities/command` — Edge entry point (Edge sets `--sync-url=".../v1/feeds/me/syncEntities"`; engine appends `/command/`)
- `GET /` — HTML user dashboard
- `GET /healthz` — liveness check
- **Admin page** (separate listener `SELFSYNC_ADMIN_ADDR`, default `127.0.0.1:8081`): `GET /`, `POST /settings/device-approval`, `POST /devices/{id}/approve|reject`. Template in `templates/admin.html`; copy in `admin/i18n.rs` (ZH/EN consts of one struct). Refuses cross-site POSTs; on a loopback bind also refuses non-loopback `Host` (DNS rebinding).
- **Device approval**: `device::admit` runs before `find_or_create_user`. Device id = `client_id` query param (cache GUID). Refused devices get `TRANSIENT_ERROR` so they resume after approval. See `docs/device-approval.md`.
- **Auth**: `auth::ClientIdentity::from_request` parses `(email, browser)` per request. Email from protobuf `share` (Chromium puts the signed-in account email; Edge puts a base64 cache_guid → fallback to `anonymous@localhost`). Browser from `X-AFS-ClientInfo: app=Microsoft Edge` (Edge sets this; vanilla Chromium omits it).
- **Storage**: SQLite (WAL mode), single `sync_entities` table. Per-user monotonic counter `users.next_version`.
- **Progress tokens**: `v1,{data_type_id},{version}` base64-encoded.
- **Schema migrations**: `sea-orm-migration` runs at startup (`Migrator::up`). Append new files in `db/migrator/`, never edit released ones. SQLite has no `ALTER TABLE DROP CONSTRAINT` so removing column-level UNIQUE requires a table rebuild via raw SQL.
- **Config env vars**: `SELFSYNC_DB` (default `selfsync.db`), `SELFSYNC_ADDR` (default `127.0.0.1:8080`).
- **Config env vars**: `SELFSYNC_DB` (default `selfsync.db`), `SELFSYNC_ADDR` (default `127.0.0.1:8080`), `SELFSYNC_ADMIN_ADDR` (default `127.0.0.1:8081`).
- **User init** (per-browser branching in `handler/init.rs`):
- Chromium → KEYSTORE_PASSPHRASE Nigori (server-built, PBKDF2-derived) + 4 bookmark permanent folders
- Edge → empty IMPLICIT_PASSPHRASE Nigori (triggers client-side init via MSA keys) + 5 folders (extra: `workspace_bookmarks`)
Expand Down
97 changes: 81 additions & 16 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ COPY --from=builder /src/target/release/selfsync-server /usr/local/bin/

ENV SELFSYNC_DB=/data/selfsync.db
ENV SELFSYNC_ADDR=0.0.0.0:8080
ENV SELFSYNC_ADMIN_ADDR=0.0.0.0:8081

VOLUME /data
EXPOSE 8080
EXPOSE 8080 8081

ENTRYPOINT ["selfsync-server"]
13 changes: 12 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,22 @@ Replace `<host>` with your selfsync host (LAN IP, NAS hostname, or Tailscale nam
| Variable | Default | Description |
|----------|---------|-------------|
| `SELFSYNC_ADDR` | `127.0.0.1:8080` | TCP address to bind |
| `SELFSYNC_ADMIN_ADDR` | `127.0.0.1:8081` | TCP address of the admin page |
| `SELFSYNC_DB` | `selfsync.db` | SQLite database path |
| `RUST_LOG` | `selfsync_server=info,http=info` | Log filter (tracing-subscriber syntax) |
<!-- /AUTO-GENERATED -->

In the Docker image the defaults are overridden to `0.0.0.0:8080` and `/data/selfsync.db`.
In the Docker image the defaults are overridden to `0.0.0.0:8080`, `0.0.0.0:8081` and `/data/selfsync.db`.

## Admin page and device approval

The admin page runs on its own port, `SELFSYNC_ADMIN_ADDR` (default `127.0.0.1:8081`, this machine only). It lists accounts and devices, in English or Chinese.

Turn on **Require approval for new devices** and any browser you haven't approved is refused until you click **Approve**; it then resumes on its next retry, no restart needed. Devices already in use when you turn it on keep working. See [docs/device-approval.md](docs/device-approval.md).

The sync port has no authentication, so never expose the admin port the same way. To reach it remotely, put it behind a reverse proxy that authenticates you.

> Upgrading from 0.1.x: `GET /` on the sync port is gone; the page moved to the admin port.

## For developers

Expand Down
13 changes: 12 additions & 1 deletion README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,22 @@ adb shell am force-stop com.android.chrome
| 变量 | 默认值 | 说明 |
|------|--------|------|
| `SELFSYNC_ADDR` | `127.0.0.1:8080` | 监听地址 |
| `SELFSYNC_ADMIN_ADDR` | `127.0.0.1:8081` | 管理页监听地址 |
| `SELFSYNC_DB` | `selfsync.db` | SQLite 数据库路径 |
| `RUST_LOG` | `selfsync_server=info,http=info` | 日志过滤(tracing-subscriber 语法) |
<!-- /AUTO-GENERATED -->

Docker 镜像里默认值被覆盖为 `0.0.0.0:8080` 和 `/data/selfsync.db`。
Docker 镜像里默认值被覆盖为 `0.0.0.0:8080`、`0.0.0.0:8081` 和 `/data/selfsync.db`。

## 管理页与设备审核

管理页在单独的端口上,`SELFSYNC_ADMIN_ADDR`(默认 `127.0.0.1:8081`,只有本机能访问),列出账号和设备,支持中文和英文。

打开「新设备需要审核」后,没批准过的浏览器同步会被拒绝,直到你点「批准」;批准后浏览器下次重试就恢复,不用重启。打开开关之前已经在用的设备不受影响。设计见 [docs/device-approval.md](docs/device-approval.md)。

同步端口没有任何认证,管理端口千万别照着同步端口那样开放出去。要远程访问,请放在带认证的反向代理后面。

> 从 0.1.x 升级:同步端口上的 `GET /` 去掉了,页面挪到了管理端口。

## 开发

Expand Down
3 changes: 2 additions & 1 deletion crates/sync-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ edition = "2024"

[dependencies]
anyhow = "1"
askama = "0.16"
axum = "0.8"
base64 = "0.22"
clap = { version = "4", features = ["derive", "env"] }
html-escape = "0.2"
prost = "0.13"
rand = "0.9"
sea-orm = { version = "1", features = [
Expand All @@ -22,6 +22,7 @@ sea-orm-migration = { version = "1", default-features = false, features = [
] }
async-trait = "0.1"
selfsync-nigori = { path = "../nigori" }
serde = { version = "1", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
tower = "0.5"
tower-http = { version = "0.6", features = [
Expand Down
Loading
Loading