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
12 changes: 8 additions & 4 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,15 @@ SYNCIDIAN_PUBLIC_URL=http://localhost:8080
# installation tokens at rest. If unset, the server writes data/secret.key.
# SYNCIDIAN_DATA_KEY=

# Restrict GitHub sign-in to these emails (comma-separated). Empty = anyone.
# Hosted Railway should set this to the operator email until public launch.
# PRODUCTION: unset this so every GitHub account can sign in, and stop hiding
# Restrict GitHub sign-in to these emails. Empty = anyone.
# Hosted Railway: keep this set until public launch so waitlist people you
# choose can sign in without opening GitHub OAuth to everyone.
# JSON array, commas, or one address per line all work:
# SYNCIDIAN_GITHUB_ALLOWED_EMAILS=["Shangeeth95@gmail.com","waitlist@example.com"]
# SYNCIDIAN_GITHUB_ALLOWED_EMAIL is still accepted as a singular alias.
# PRODUCTION: unset both so every GitHub account can sign in, and stop hiding
# the landing GitHub buttons (see AGENT.md).
# SYNCIDIAN_GITHUB_ALLOWED_EMAIL=Shangeeth95@gmail.com
# SYNCIDIAN_GITHUB_ALLOWED_EMAILS=["Shangeeth95@gmail.com"]

# Google Analytics 4 measurement ID for the public landing (hosted). Empty = off.
# Events: click_discord, click_github, join_waitlist, click_github_signin.
Expand Down
6 changes: 3 additions & 3 deletions AGENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ If a diagram no longer matches the code, rewrite it. Do not leave comments like

Document and implement these unless a later change explicitly replaces them — and if you replace them, update this list too:

1. **Public landing, private operator surface.** Unauthenticated visitors see a one-page story. On hosted Syncidian.com, vault users sign in with GitHub (hidden until the app name is tapped six times whenever `SYNCIDIAN_GITHUB_ALLOWED_EMAIL` is set). On any other host, vault users sign in with email — a GitHub App is not required to create an account. There is no public Admin link. Self-host operators create the first admin at `SYNCIDIAN_ADMIN_PATH` (default `/admin`) or via `SYNCIDIAN_BOOTSTRAP_USER` / `SYNCIDIAN_BOOTSTRAP_PASSWORD`, persist `/data`, and skip Tailscale (`SYNCIDIAN_ADMIN_PRIVATE=0` or leave `SYNCIDIAN_ADMIN_HOST` unset). A Tailscale hostname (`SYNCIDIAN_ADMIN_HOST`, for example `admin.syncidian.com`) is optional. The instance GitHub App is optional on self-host (needed only for GitHub sign-in or per-user backup). Repository install stays per user after identity.
1. **Public landing, private operator surface.** Unauthenticated visitors see a one-page story. On hosted Syncidian.com, vault users sign in with GitHub (hidden until the app name is tapped six times whenever `SYNCIDIAN_GITHUB_ALLOWED_EMAILS` is set). On any other host, vault users sign in with email — a GitHub App is not required to create an account. There is no public Admin link. Self-host operators create the first admin at `SYNCIDIAN_ADMIN_PATH` (default `/admin`) or via `SYNCIDIAN_BOOTSTRAP_USER` / `SYNCIDIAN_BOOTSTRAP_PASSWORD`, persist `/data`, and skip Tailscale (`SYNCIDIAN_ADMIN_PRIVATE=0` or leave `SYNCIDIAN_ADMIN_HOST` unset). A Tailscale hostname (`SYNCIDIAN_ADMIN_HOST`, for example `admin.syncidian.com`) is optional. The instance GitHub App is optional on self-host (needed only for GitHub sign-in or per-user backup). Repository install stays per user after identity.
2. **GitHub is per user, after identity.** One repository per `user_id`. `GET/POST /api/v1/github` requires `authenticate()`.
3. **Admin login does not need repo sync.** Admins manage users. Vault/GitHub/token-list/device/activity/MCP routes use `vaultAuthed` or `sessionVaultAuthed` and return 403 for admins. Admins may mint a **one-time** `sk_sync_` token for a vault user via `POST /api/v1/users/tokens` (by username) or `issue_token` on user create — they still cannot list existing tokens or read vault/GitHub/MCP usage data.
4. **Admin user list is public fields only:** `username`, `is_admin`, `created_at` (no `id`, vault, tokens, or GitHub).
Expand Down Expand Up @@ -71,11 +71,11 @@ Docs-only or server-only PRs do not bump the plugin version.
Hosted GitHub sign-in is **intentionally restricted** until public launch:

* The landing **Continue with GitHub** / **Log in** controls stay hidden. Tapping the Syncidian name in the header **six times** reveals them (session only).
* `SYNCIDIAN_GITHUB_ALLOWED_EMAIL` (Railway variable) is a comma-separated allowlist. Only those GitHub account emails can complete OAuth. Hosted should be `Shangeeth95@gmail.com`. An empty value allows every GitHub user.
* `SYNCIDIAN_GITHUB_ALLOWED_EMAILS` (Railway variable) is the GitHub sign-in allowlist. Only those GitHub account emails can complete OAuth. Use a JSON array (`["a@x.com","b@x.com"]`), commas, or one email per line. `SYNCIDIAN_GITHUB_ALLOWED_EMAIL` is still accepted as an alias. Hosted should at least include `Shangeeth95@gmail.com`. An empty value allows every GitHub user. The operator Waitlist page never writes this list — it only helps copy the next Railway value.

**When opening GitHub sign-in for all users, do all of the following in one change:**

1. Unset `SYNCIDIAN_GITHUB_ALLOWED_EMAIL` on Railway (and any other host). Empty allowlist = any GitHub account may sign in.
1. Unset `SYNCIDIAN_GITHUB_ALLOWED_EMAILS` and `SYNCIDIAN_GITHUB_ALLOWED_EMAIL` on Railway (and any other host). Empty allowlist = any GitHub account may sign in.
2. Stop hiding the landing GitHub buttons: `githubSignInHidden()` in `internal/server/auth_github.go` should return false for the public site (remove the hosted-domain hide). The six-tap easter egg in `internal/web/static/index.html` can then be removed.
3. Update this list, the landing/auth markdown, and the app-workflow diagrams so they again describe public GitHub sign-in for everyone.

Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Syncidian is an open-source, self-hostable **Obsidian synchronization and AI bri

It runs as an Obsidian plugin on your devices and connects to a lightweight Syncidian server that coordinates synchronization between them.

Each signed-in user can connect **one private GitHub repository**. GitHub is the durable, versioned **source of truth** for that user's vault. The public site is a one-page story: join the waitlist and sign in with GitHub on Syncidian.com, or sign in with email on a self-hosted host. GitHub sign-in is hidden on the hosted landing (tap the app name six times to reveal it) and limited to `SYNCIDIAN_GITHUB_ALLOWED_EMAIL` until public launch. Operators use a private hostname (`SYNCIDIAN_ADMIN_HOST`, for example `admin.syncidian.com` on Tailscale) or an unlisted path (`SYNCIDIAN_ADMIN_PATH`, default `/admin`) — neither is linked from the landing page. Admins manage users and an optional instance GitHub App, and never see vault or GitHub data.
Each signed-in user can connect **one private GitHub repository**. GitHub is the durable, versioned **source of truth** for that user's vault. The public site is a one-page story: join the waitlist and sign in with GitHub on Syncidian.com, or sign in with email on a self-hosted host. GitHub sign-in is hidden on the hosted landing (tap the app name six times to reveal it) and limited to `SYNCIDIAN_GITHUB_ALLOWED_EMAILS` until public launch. Operators use a private hostname (`SYNCIDIAN_ADMIN_HOST`, for example `admin.syncidian.com` on Tailscale) or an unlisted path (`SYNCIDIAN_ADMIN_PATH`, default `/admin`) — neither is linked from the landing page. Admins manage users and an optional instance GitHub App, and never see vault or GitHub data.

Syncidian also includes a built-in **MCP server**, allowing compatible AI tools and agents to securely interact with your Obsidian knowledge base.

Expand Down Expand Up @@ -72,7 +72,7 @@ Users, GitHub App credentials, and vault files live in SQLite on disk. Railway
1. New project → deploy this GitHub repo. Railway builds the `Dockerfile`.
2. **Settings → Volumes → Add volume**, mount path **`/data`**. Do this before creating the admin or registering the GitHub App. `railway.json` sets `requiredMountPath` to `/data` so a deploy without a volume fails instead of silently resetting the instance, and `overlapSeconds` to `0` so two replicas do not share SQLite during a rollout.
3. Generate a public domain. The server listens on Railway’s `PORT` and uses `RAILWAY_PUBLIC_DOMAIN` for the dashboard URL unless you set `SYNCIDIAN_PUBLIC_URL`.
4. Optional variables: `SYNCIDIAN_BOOTSTRAP_USER`, `SYNCIDIAN_BOOTSTRAP_PASSWORD`. To keep the GitHub App if the volume is missing, also set `SYNCIDIAN_GITHUB_APP_*` (see [Set up the GitHub App](docs/github-app.md)). Set `SYNCIDIAN_DATA_KEY` (32-byte hex) so GitHub App secrets stay encrypted even if `syncidian.db` is copied off the volume. Until public GitHub sign-in, set `SYNCIDIAN_GITHUB_ALLOWED_EMAIL` to the operator GitHub email (hosted: `Shangeeth95@gmail.com`) so only that account can complete GitHub OAuth.
4. Optional variables: `SYNCIDIAN_BOOTSTRAP_USER`, `SYNCIDIAN_BOOTSTRAP_PASSWORD`. To keep the GitHub App if the volume is missing, also set `SYNCIDIAN_GITHUB_APP_*` (see [Set up the GitHub App](docs/github-app.md)). Set `SYNCIDIAN_DATA_KEY` (32-byte hex) so GitHub App secrets stay encrypted even if `syncidian.db` is copied off the volume. Until public GitHub sign-in, set `SYNCIDIAN_GITHUB_ALLOWED_EMAILS` to the GitHub emails that may complete OAuth (JSON array, commas, or one per line). Hosted should include `Shangeeth95@gmail.com`. Joining the waitlist does not grant access — add each person to this variable, then redeploy.
5. Open the public URL. Create the admin at `/admin`. People can then sign up with email on `/`. Register the GitHub App ([walkthrough](docs/github-app.md)) only if you want GitHub sign-in or backup. `/admin` warns if the data directory is still ephemeral. On the hosted landing, tap **Syncidian** six times to reveal GitHub sign-in. Point the plugin at that URL with that user's token.

Health check: `GET /health`. Agents can also fetch `/robots.txt`, `/auth.md`, `/.well-known/mcp/server-card.json`, and markdown for `GET /` (`Accept: text/markdown`). `/robots.txt` disallows the operator path (`/admin` by default) and operator APIs; that HTML also sends `X-Robots-Tag: noindex` so search engines should not list it. The operator page is not in `/sitemap.xml` and is not linked from `/`. If a CDN (Cloudflare AI Crawl Control) injects its own `robots.txt`, turn that off so the origin file is what crawlers see.
Expand Down Expand Up @@ -245,7 +245,7 @@ Eventually, Syncidian should be able to detect a conflict, have a small LLM reso

# 🗺️ App workflow

The public site is a one-pager. Hosted visitors join a waitlist and sign in with GitHub (hidden until the app name is tapped six times, allowlisted via `SYNCIDIAN_GITHUB_ALLOWED_EMAIL` until public launch). Self-hosted visitors sign in with email. Operators use a private hostname (`SYNCIDIAN_ADMIN_HOST`) or an unlisted path (`SYNCIDIAN_ADMIN_PATH`, default `/admin`) to create the first admin.
The public site is a one-pager. Hosted visitors join a waitlist and sign in with GitHub (hidden until the app name is tapped six times, allowlisted via `SYNCIDIAN_GITHUB_ALLOWED_EMAILS` until public launch). Self-hosted visitors sign in with email. Operators use a private hostname (`SYNCIDIAN_ADMIN_HOST`) or an unlisted path (`SYNCIDIAN_ADMIN_PATH`, default `/admin`) to create the first admin.

```mermaid
flowchart TD
Expand Down Expand Up @@ -434,7 +434,7 @@ Optional env vars if you prefer not to use the in-dashboard manifest flow: `SYNC

Rules:

* **Public landing, private operator surface.** Create the first admin at `SYNCIDIAN_ADMIN_HOST` (Tailscale) or `SYNCIDIAN_ADMIN_PATH` (default `/admin`). Self-hosted vault users sign in with email from `/`. The GitHub App is optional. On hosted Syncidian.com, GitHub sign-in is hidden until the app name is tapped six times and limited by `SYNCIDIAN_GITHUB_ALLOWED_EMAIL`.
* **Public landing, private operator surface.** Create the first admin at `SYNCIDIAN_ADMIN_HOST` (Tailscale) or `SYNCIDIAN_ADMIN_PATH` (default `/admin`). Self-hosted vault users sign in with email from `/`. The GitHub App is optional. On hosted Syncidian.com, GitHub sign-in is hidden until the app name is tapped six times and limited by `SYNCIDIAN_GITHUB_ALLOWED_EMAILS`.
* **One repository per user.** `github_config` is keyed by `user_id`.
* **GitHub App only.** Connect with GitHub, install on a repository, Contents read and write. No personal access tokens and no deploy keys.
* **Main branch only.** Syncidian always uses `main`.
Expand Down
6 changes: 4 additions & 2 deletions cmd/syncidian/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,10 @@ Environment:
SYNCIDIAN_GITHUB_CLIENT_ID GitHub App OAuth client ID
SYNCIDIAN_GITHUB_CLIENT_SECRET GitHub App OAuth client secret
SYNCIDIAN_GITHUB_APP_PRIVATE_KEY PEM; use \n for newlines
SYNCIDIAN_GITHUB_ALLOWED_EMAIL Comma-separated GitHub emails allowed to sign in.
Empty = any GitHub user. Production: unset this.
SYNCIDIAN_GITHUB_ALLOWED_EMAILS JSON array, commas, or one GitHub email per line
allowed to sign in. Empty = any GitHub user.
SYNCIDIAN_GITHUB_ALLOWED_EMAIL is still accepted
as an alias. Production: unset both.
SYNCIDIAN_GA_ID Google Analytics 4 ID (G-…). Landing CTA clicks only when set.
`)
}
Expand Down
1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ services:
SYNCIDIAN_GITHUB_CLIENT_ID: ${SYNCIDIAN_GITHUB_CLIENT_ID:-}
SYNCIDIAN_GITHUB_CLIENT_SECRET: ${SYNCIDIAN_GITHUB_CLIENT_SECRET:-}
SYNCIDIAN_GITHUB_APP_PRIVATE_KEY: ${SYNCIDIAN_GITHUB_APP_PRIVATE_KEY:-}
SYNCIDIAN_GITHUB_ALLOWED_EMAILS: ${SYNCIDIAN_GITHUB_ALLOWED_EMAILS:-}
SYNCIDIAN_GITHUB_ALLOWED_EMAIL: ${SYNCIDIAN_GITHUB_ALLOWED_EMAIL:-}
volumes:
# Named volume survives container recreate. `docker compose down -v` deletes it.
Expand Down
2 changes: 1 addition & 1 deletion docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

This document describes the **current MVP** as implemented in this repository. GitHub renders the Mermaid diagrams below — open this file on GitHub to visualize them.

The plugin is the client (Windows, macOS, Linux, Android, and iOS — `isDesktopOnly` is false). HTTP from the plugin uses Obsidian `requestUrl`. The Go server is the coordination layer. A per-user vault on disk plus SQLite metadata is the working copy. GitHub is an **optional, per-user** durable source of truth — after identity, a user installs the App on one repository. On hosted Syncidian.com, people join a waitlist and sign in with GitHub (hidden until the app name is tapped six times, allowlisted via `SYNCIDIAN_GITHUB_ALLOWED_EMAIL` until public launch). On a self-hosted host, people sign in with email; the GitHub App is optional. Operators use a private hostname (`SYNCIDIAN_ADMIN_HOST`, for example `admin.syncidian.com` on Tailscale) or an unlisted path (`SYNCIDIAN_ADMIN_PATH`, default `/admin`) to create the first admin. MCP is the AI bridge.
The plugin is the client (Windows, macOS, Linux, Android, and iOS — `isDesktopOnly` is false). HTTP from the plugin uses Obsidian `requestUrl`. The Go server is the coordination layer. A per-user vault on disk plus SQLite metadata is the working copy. GitHub is an **optional, per-user** durable source of truth — after identity, a user installs the App on one repository. On hosted Syncidian.com, people join a waitlist and sign in with GitHub (hidden until the app name is tapped six times, allowlisted via `SYNCIDIAN_GITHUB_ALLOWED_EMAILS` until public launch). On a self-hosted host, people sign in with email; the GitHub App is optional. Operators use a private hostname (`SYNCIDIAN_ADMIN_HOST`, for example `admin.syncidian.com` on Tailscale) or an unlisted path (`SYNCIDIAN_ADMIN_PATH`, default `/admin`) to create the first admin. MCP is the AI bridge.

When you change this architecture, update the Mermaid diagrams in this file and in `README.md`. See [`AGENT.md`](../AGENT.md).

Expand Down
2 changes: 1 addition & 1 deletion docs/github-app.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ This posts a GitHub App **manifest**, so permissions and URLs are filled in for

Those credentials are stored encrypted in SQLite. Attach a volume at `/data` (or copy them into `SYNCIDIAN_GITHUB_APP_*` env vars — Option B) so the next deploy does not wipe the GitHub App. Set `SYNCIDIAN_DATA_KEY` if you want the encryption key in environment variables instead of `data/secret.key`.

People can now use **Continue with GitHub** / **Log in** / **Connect to your GitHub repository** on `{base}/`. Self-hosted email login already works without this app. On hosted Syncidian.com those GitHub buttons stay hidden until you tap the app name six times, and `SYNCIDIAN_GITHUB_ALLOWED_EMAIL` must match the GitHub account.
People can now use **Continue with GitHub** / **Log in** / **Connect to your GitHub repository** on `{base}/`. Self-hosted email login already works without this app. On hosted Syncidian.com those GitHub buttons stay hidden until you tap the app name six times, and `SYNCIDIAN_GITHUB_ALLOWED_EMAILS` must include the GitHub account.

The manifest requests:

Expand Down
Loading
Loading