diff --git a/.env.example b/.env.example index 6749360..b2deb4e 100644 --- a/.env.example +++ b/.env.example @@ -31,5 +31,5 @@ SYNCIDIAN_PUBLIC_URL=http://localhost:8080 # 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. +# Events: click_discord, click_github, click_railway, join_waitlist, click_github_signin. # SYNCIDIAN_GA_ID=G-XXXXXXXXXX diff --git a/AGENT.md b/AGENT.md index 8e36f69..c84ed9d 100644 --- a/AGENT.md +++ b/AGENT.md @@ -94,4 +94,4 @@ When you change auth, GitHub scoping, admin privacy, operator hostname/Tailscale ## Help walkthrough -The dashboard **Help** button must describe the same workflow as the diagrams: public landing with waitlist + GitHub on hosted Syncidian.com (GitHub hidden until the app name is tapped six times), email signup/login on self-hosted hosts, self-host operators at `/admin` (skip Tailscale with `SYNCIDIAN_ADMIN_PRIVATE=0` or unset `SYNCIDIAN_ADMIN_HOST`), optional Tailscale hostname, persist `/data` so deploys do not wipe users, optional instance GitHub App (`docs/github-app.md`) for GitHub sign-in or backup, then optional per-user GitHub, admin without repo sync. Plugin install is Community plugins first, with BRAT or `scripts/install-plugin.sh` as fallback until the listing is live. Dashboard MCP/call/conflict stats stay behind **Stats for Nerds**. +The dashboard **Help** button must describe the same workflow as the diagrams: public landing with waitlist + GitHub on hosted Syncidian.com (GitHub hidden until the app name is tapped six times), email signup/login on self-hosted hosts, self-host via the Railway one-click button or Docker, operators at `/admin` (skip Tailscale with `SYNCIDIAN_ADMIN_PRIVATE=0` or unset `SYNCIDIAN_ADMIN_HOST`), optional Tailscale hostname, persist `/data` so deploys do not wipe users, optional instance GitHub App (`docs/github-app.md`) for GitHub sign-in or backup, then optional per-user GitHub, admin without repo sync. Plugin install is Community plugins first, with BRAT or `scripts/install-plugin.sh` as fallback until the listing is live. Dashboard MCP/call/conflict stats stay behind **Stats for Nerds**. diff --git a/README.md b/README.md index 14819c8..b06d94a 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,10 @@ docker run -d \ **Self-host Syncidian for free.** A managed service is planned at $1/month; it is not available yet. +[](https://railway.com/new/template?template=https://github.com/shangeethsivan/Syncidian&utm_medium=integration&utm_source=button&utm_campaign=syncidian) + +One click deploys this repo on [Railway](https://railway.com). Attach a volume at `/data` before you create the admin so users survive redeploys. Details: [Deploy on Railway](#deploy-on-railway). + [Buy me a coffee](https://buymeacoffee.com/shravzdev) if Syncidian is useful. --- @@ -67,9 +71,11 @@ Data is stored in `./data` by default (`SYNCIDIAN_DATA` to change it). Docker Co ### Deploy on Railway +[](https://railway.com/new/template?template=https://github.com/shangeethsivan/Syncidian&utm_medium=integration&utm_source=button&utm_campaign=syncidian) + Users, GitHub App credentials, and vault files live in SQLite on disk. Railway’s container filesystem is **empty on every deploy** unless you attach a volume. -1. New project → deploy this GitHub repo. Railway builds the `Dockerfile`. +1. Click **Deploy on Railway** (or New project → this GitHub repo). Railway clones the repo and builds the `Dockerfile` from `railway.json`. 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_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. diff --git a/docs/architecture.md b/docs/architecture.md index 4a0c8bd..c5064a7 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -541,7 +541,7 @@ One process. No extra services for the basic install. When `SYNCIDIAN_ADMIN_LIST Users, tokens, the instance GitHub App, per-user GitHub installs, and vault files all live under `SYNCIDIAN_DATA` (SQLite `syncidian.db` plus `vaults/`). A new deploy **replaces the container filesystem**, so that directory must be a named volume. -Railway: mount a volume at `/data`. `railway.json` sets `requiredMountPath` to `/data` (deploys without a volume fail instead of wiping the instance) and `overlapSeconds` to `0` (SQLite is not opened by two replicas during a rollout). The image default `SYNCIDIAN_DATA=/data` no longer hides `RAILWAY_VOLUME_MOUNT_PATH` if the volume is mounted somewhere else. The Dockerfile does not declare `VOLUME` (that broke some builders). `/admin` and `GET /api/v1/setup` report when the data directory looks ephemeral. Leave `SYNCIDIAN_ADMIN_LISTEN_IP` unset on Railway so the public `PORT` still binds as today. +Railway: the README and landing page link [Deploy on Railway](https://railway.com/new/template?template=https://github.com/shangeethsivan/Syncidian&utm_medium=integration&utm_source=button&utm_campaign=syncidian). After that click, mount a volume at `/data`. `railway.json` sets `requiredMountPath` to `/data` (deploys without a volume fail instead of wiping the instance) and `overlapSeconds` to `0` (SQLite is not opened by two replicas during a rollout). The image default `SYNCIDIAN_DATA=/data` no longer hides `RAILWAY_VOLUME_MOUNT_PATH` if the volume is mounted somewhere else. The Dockerfile does not declare `VOLUME` (that broke some builders). `/admin` and `GET /api/v1/setup` report when the data directory looks ephemeral. Leave `SYNCIDIAN_ADMIN_LISTEN_IP` unset on Railway so the public `PORT` still binds as today. --- diff --git a/docs/install-mobile.md b/docs/install-mobile.md index 8fe9dff..9bd8f56 100644 --- a/docs/install-mobile.md +++ b/docs/install-mobile.md @@ -2,7 +2,7 @@ The Syncidian plugin is **not desktop-only**. The same `syncidian` plugin runs in Obsidian on Windows, macOS, Linux, Android, and iOS. -You still need a running Syncidian **server** (Railway, Docker, or similar) and a vault-user token (`sk_sync_…`) from that server’s dashboard. The plugin never talks to GitHub itself. +You still need a running Syncidian **server** ([Deploy on Railway](https://railway.com/new/template?template=https://github.com/shangeethsivan/Syncidian&utm_medium=integration&utm_source=button&utm_campaign=syncidian), Docker, or similar) and a vault-user token (`sk_sync_…`) from that server’s dashboard. The plugin never talks to GitHub itself. On a phone, leave **Syncidian.com** selected or choose **Custom Domain** with a public `https://` address (for example your Railway domain). `http://localhost:8080` is the phone, not your computer. iOS often blocks plain `http://`. diff --git a/internal/server/agents_test.go b/internal/server/agents_test.go index 4e66811..b5b2f3d 100644 --- a/internal/server/agents_test.go +++ b/internal/server/agents_test.go @@ -66,6 +66,9 @@ func TestAgentDiscovery(t *testing.T) { if !strings.Contains(string(body), "MCP JSON-RPC") { t.Fatalf("landing markdown: %s", body) } + if !strings.Contains(string(body), "railway.com/new/template?template=https://github.com/shangeethsivan/Syncidian") { + t.Fatalf("landing markdown missing Railway one-click deploy: %s", body) + } if res.Header.Get("Link") == "" { t.Fatal("markdown response missing Link") } diff --git a/internal/server/server_test.go b/internal/server/server_test.go index 419af55..d7224a3 100644 --- a/internal/server/server_test.go +++ b/internal/server/server_test.go @@ -488,6 +488,10 @@ func TestDashboardServed(t *testing.T) { `id="landing"`, `id="landing-brand"`, `github-signin`, + `Deploy on Railway`, + `railway.com/new/template?template=https://github.com/shangeethsivan/Syncidian`, + `/assets/railway-button.svg`, + `data-cta="railway"`, `syncidian-github-signin`, `Continue with GitHub`, `id="email-form"`, @@ -638,6 +642,23 @@ func TestAdminChangePassword(t *testing.T) { } } +func TestRailwayButtonAsset(t *testing.T) { + hs, done := newTestServer(t) + defer done() + res, err := http.Get(hs.URL + "/assets/railway-button.svg") + if err != nil { + t.Fatal(err) + } + b, _ := io.ReadAll(res.Body) + res.Body.Close() + if res.StatusCode != http.StatusOK { + t.Fatalf("railway button status %d", res.StatusCode) + } + if !bytes.Contains(b, []byte("Deploy on Railway")) && !bytes.Contains(b, []byte(" \ No newline at end of file diff --git a/internal/web/static/index.html b/internal/web/static/index.html index 861c4c9..a09db48 100644 --- a/internal/web/static/index.html +++ b/internal/web/static/index.html @@ -282,6 +282,13 @@ .hero-source { padding: 12px 20px; border-radius: 14px; font-size: 15px; text-transform: lowercase; } + .railway-deploy { + display: inline-flex; align-items: center; line-height: 0; + border-radius: 8px; overflow: hidden; + } + .railway-deploy img { height: 40px; width: auto; display: block; } + .railway-note { margin: 14px 0 28px; font-size: 14px; } + .hero-actions { margin-bottom: 0; } #landing .arch div { background: color-mix(in srgb, var(--card) 58%, transparent); backdrop-filter: blur(18px); @@ -586,6 +593,17 @@
Obsidian, privately synced
Your vault. On your GitHub. Notes on every device.
+Self-host in one click. Attach a /data volume so users survive redeploys.
The public site is a one-page story. On a self-hosted host, people sign in with email. On Syncidian.com they use GitHub (tap the app name six times to reveal it until public launch).
Self-host operators: open /admin on this same URL to create the first admin. Skip Tailscale — leave SYNCIDIAN_ADMIN_HOST unset, or set SYNCIDIAN_ADMIN_PRIVATE=0.
A Tailscale-only hostname (admin.syncidian.com) is optional. Tailscale’s DNS page has no Extra records control — use a hosts file, Split DNS + Pi-hole, a public A to 100.x on a self-hosted node, or /admin on Railway. See docs/tailscale-admin.md.
Deploy on Railway from this GitHub repo, then attach a volume at /data before you create the admin.
git clone https://github.com/shangeethsivan/Syncidian.git cd Syncidian