HikariTish91 downloadsSelf-hosted sync and share for Obsidian: Markdown + attachments + .obsidian config, LWW-based multi-device sync, lightweight blog publishing, and collaboration editing.
Self-hosted Obsidian sync & share — Markdown, attachments and collaboration in one binary.
OSS Sync is a self-hosted alternative to Obsidian Sync. It consists of a Go (Gin) backend and a TypeScript Obsidian plugin. Data stays on your own server: files, versions, shares and collaboration are all managed by you.
client_id with pending / approved / revoked states..oss-sync-state.json before transfer and resume after Obsidian restarts..obsidian config syncdefault, papertrail), public index /, per-vault /b/:vaultIduser_choice / short_poll / long_pollgithub.com/helantianshen/oss-sync/pkg/ossplugin for building trusted extensions without hand-written JSON Linescmd/server # HTTP entry
configs/ # dev / prod YAML
internal/
auth # register, login, JWT, device auth
syncapi # Vault revision, upload/download, rename/delete
vaults # Vault CRUD, members, settings
devices # device state, vault authorization, cursor
collaboration # invite, accept, content write, events
history/recycle # snapshots, restore, retention
blog # themes, public pages
serverplugin # WASM and trusted executable plugins with namespaced routes
webui # console pages, admin
plugin/src # Obsidian plugin
Sync uses only HTTP. Short polling wait=0 or long polling wait=30 per Vault. Collaboration uses an account-level channel: SSE over HTTPS (or app://obsidian.md with CORS), long polling over plain LAN HTTP.
go run ./cmd/server
First registered user automatically becomes admin. Afterwards use that admin account to create others.
Listens on http://localhost:8080 by default, data in data/. Health:
curl http://localhost:8080/healthz
curl http://localhost:8080/readyz
Config via OSS_ENV=dev|prod → configs/config.dev.yaml / configs/config.prod.yaml, overridable by env: OSS_SERVER_HOST, OSS_SERVER_PORT, OSS_DB_DRIVER, OSS_DB_DSN, OSS_STORAGE_DIR, etc.
Postgres example:
export OSS_DB_DRIVER=postgres
export OSS_DB_DSN='postgres://user:[email protected]:5432/oss?sslmode=disable'
go run ./cmd/server
One-command install or upgrade on a Linux server:
curl -fsSL https://raw.githubusercontent.com/helantianshen/oss-sync/main/install.sh | sudo bash
The official bootstrap script asks for the host port, GitHub Release source, deployment path, and a total project storage limit. The source can be the accelerated URL, GitHub official, or a custom HTTPS URL prefix. It downloads both the latest amd64/arm64 container archive and its checksums.txt through the selected source, verifies SHA-256, and imports it with docker load. It detects Docker and offers to install it with Docker's official installer. Leave the port blank to choose one randomly from 10000-25565 while skipping common service ports. New installations keep persistent data under /opt/oss-sync/data; the application-wide storage limit rejects further sync writes when reached, and 0 means unlimited.
After installation, run the global oss or oss-sync command to update or uninstall OSS Sync, inspect runtime and storage usage, start, stop or restart it, and change the project capacity or mapped port. Each update asks which source to use, so the accelerated URL can be changed without reinstalling. Uninstalling removes the container and commands while retaining project data by default.
Running the same install command again downloads the latest Release and recreates the container while reusing its port, deployment path, and capacity setting. Legacy oss-data volumes remain in place and are not migrated automatically. Non-interactive installs can set OSS_PORT, OSS_RELEASE_PROXY=official (or a custom HTTPS URL prefix), OSS_INSTALL_DIR, OSS_STORAGE_LIMIT_GB, and OSS_INSTALL_DOCKER=1. For the global manager, set OSS_RELEASE_SOURCE=official, OSS_RELEASE_SOURCE=proxy, or OSS_RELEASE_PROXY=https://example.com/ to select the source for an update. OSS_IMAGE remains an advanced override for a complete registry image such as ghcr.io/helantianshen/oss-sync-server:<version>.
The default SQLite installation does not pull PostgreSQL. If Docker Hub dependencies are added manually, a complete 1Panel mirror reference such as docker.1panel.live/library/postgres:17 can be used without changing the Release download source or the Docker daemon configuration.
Source development can still build through Docker Compose:
Build and start a complete SQLite-backed environment with Docker Compose:
docker compose up -d --build
docker compose logs -f backend
The service is available at http://localhost:8080, and persistent data is stored in the oss-data named volume. Set OSS_PORT=9090 to change the host port and OSS_STORAGE_MAX_TOTAL_SIZE_MB to apply an application-wide data-directory limit.
To build and run only the backend image:
docker build -t oss-sync-backend .
docker run --rm -p 8080:8080 \
-v oss-data:/app/data \
oss-sync-backend
Container deployments can update in place from Admin → System → Server update. The image keeps the server binary in a writable runtime directory; after a verified replacement, the process exits and Docker's restart policy starts the new binary. Rebuilding or replacing the image remains the way to apply image-level changes. Removing the container keeps the named volume; docker compose down -v deletes its data and must be used with care.
Regular users can install OSS Sync and Share directly from Obsidian Community Plugins. The following steps are only for source development:
cd plugin
npm ci
npm run build
# outputs plugin/manifest.json, main.js, styles.css
# copy to vault: <vault>/.obsidian/plugins/oss-sync/
Reload Obsidian → Enable Obsidian Sync & Share → Set the device name → Enter a server URL including http:// or https:// → Sign in. Approve the device separately in the web console, then grant Vault access. The open settings page refreshes the authorized Vault list every three seconds. The plugin keeps a local .oss-sync-state.json (v3) at Vault root; it is never uploaded and stores the durable pending-operation queue.
The extension model has one simple rule:
template.html, style.css, optional theme.js, and theme.json capabilities.theme.css, images, and fonts.Templates and themes must not contain functional settings. Do not add settings.json to a blog template. Declare settings in a plugin; OSS Sync renders them in the top-level Plugin settings menu and stores values per Vault.
examples/server-plugin-echo.manifest.json.cd examples/server-plugin-echo
go build -o plugin.exe .
Compress-Archive manifest.json,plugin.exe my-plugin.zip
Use the public Go SDK at github.com/helantianshen/oss-sync/pkg/ossplugin; it handles the JSON-lines process protocol. The short in-console guide covers settings, hooks, routes, pages, tasks, migrations, and host services. The complete reference is docs/server-plugins.md.
Executable plugins run on the server, so their binary must match the server platform. This does not require separate plugin records: one ZIP may contain plugin.exe, plugin, and plugin-arm64, with windows-amd64, linux-amd64, and linux-arm64 entries in manifest.json. OSS Sync automatically selects the matching entry. For the simplest setup, build only the platform used by your server.
Blog template:
my-template.zip
├── template.html
├── style.css
├── theme.js
├── theme.json
└── plugin.zip # optional functionality
Console theme:
my-console-theme.zip
├── theme.css
├── images/
├── fonts/
└── plugin.zip # optional functionality
To associate functionality, place the already-built plugin ZIP at the package root as plugin.zip. Uploading the template or theme automatically installs, enables, and associates the plugin. No additional association form is required. The web console includes concise Template guide, Console theme guide, and Plugin guide pages with copyable minimal examples.
| Env | Description |
|---|---|
OSS_ENV |
dev or prod |
OSS_SERVER_HOST / PORT |
listen address |
OSS_DB_DRIVER / DSN |
sqlite or postgres |
OSS_STORAGE_DIR |
file storage root |
OSS_ALLOW_ANONYMOUS_REGISTRATION |
initial register switch |
OSS_WEB_SESSION_TTL_HOURS |
web console session lifetime in hours; default 24 |
OSS_DEVICE_JWT_TTL_HOURS |
plugin device token lifetime in hours; default 720 (30 days) |
OSS_DEVICE_STALE_DAYS |
stale device threshold |
OSS_RECONCILE_INTERVAL_HOURS |
storage check interval |
OSS_UPDATE_DOWNLOAD_SOURCE |
server update source: official, proxy, or custom |
OSS_UPDATE_DOWNLOAD_PROXY |
HTTPS URL prefix used when the source is custom |
Vault settings (per Vault, admin can force):
sync_mode: user_choice | short_poll | long_pollServer updates use download_source and download_proxy from the update section in configs/config.dev.yaml or configs/config.prod.yaml. The Admin → System → Server update panel can override them for the current check and update. The selected source is used for both release metadata and the binary download, which allows updates when the server cannot reach GitHub directly.
# backend
go test ./...
go test -race ./...
go vet ./...
# plugin
cd plugin
npm exec tsc -- --noEmit
npm test
npm run build
Project conventions: Go with gofumpt + golangci-lint, TypeScript strict, uv/pnpm not required, no emoji in UI, CSS via console.css tokens, no inline styles.
data/ (SQLite file or Postgres dump) and the JWT secret stored in DB./readyz; alert on non-200 or repeated reconcile failures.MIT — see LICENSE.