DeepSeek Harness Remote
DeepSeek Harness Remote 可將手機或另一台電腦與你運行 DeepSeek Harness 的電腦安全連接,隨時隨地繼續你的工作,方便隨時查看進度並繼續工作。
AI 翻譯
Self-hosted markdown notes with a canvas on top: plain .md files you own, sticky notes and arrows pinned to the text, real-time editing, GitHub sync, and a desktop app that works offline.
翻譯準備中
取自儲存庫 README
正在載入完整 README。
檢視完整 README ↗A calm, fast, self-hosted note app for the web — built for people who keep their notes in markdown and their vault in GitHub, and who want the same notes (plus real-time collaboration) from any browser.
Obi gives you two kinds of workspace:
| GitHub workspace | Online workspace | |
|---|---|---|
| Storage | your own git repository | this server (/data volume) |
| Works with Obsidian | ✅ two-way sync | — |
| Real-time co-editing | ✅ (your own devices) | ✅ with other people |
| Share a single page | publish link | ✅ per-person, editor/viewer |
| Share the whole workspace | — (private to you) | ✅ members with roles |
| History | full git history | automatic snapshots + trash |
Both kinds are plain markdown, folders and attachments — never a proprietary format. Both get exactly the same editor, whiteboards and note canvas.
Writing
Ctrl/⌘ E)[[wiki links]] with autocomplete, aliases ([[Note|label]]), heading links ([[Note#Section]]) and auto-created notes![[embeds]] for notes, images, audio, video and PDFs#tags (nested #area/sub), front-matter properties, footnote-free clean markdown/ slash menu for blocks, templates, dates> [!tip]), task lists, tables, KaTeX math, Mermaid diagrams, syntax-highlighted codekanban-plugin: basic front matter (compatible with the Obsidian Kanban plugin)Whiteboards & the note canvas (every workspace, GitHub or online)
.board files): rectangles, ellipses, diamonds, arrows that stay attached to shapes, lines with bends and curves, freehand pen, highlighter, text, sticky notes, images, frames, links to notes or web pages, eraser and a laser pointer. Hand-drawn or clean style, fills and hatching, dashes, arrowheads, grouping, locking, alignment, snapping and a grid, copy/paste, undo, zoom and pan, export to PNG/SVG![[Sketch.board]] (or /whiteboard in the slash menu) — it shows as a live drawing and you can edit it right thereCtrl/⌘ + wheel zooms whiteboardsCtrl/⌘ + wheel, Ctrl/⌘ + / − / 0 or the zoom chip in the status bar. Text and canvas scale together, so drawings stay on their lines — and a note published while zoomed still lays out at its normal size.obi/layers/<note path>.json and follows the note when it's renamed, moved, deleted or restored. Whiteboards and layers are plain JSON with one element per line, and simultaneous edits from different machines are merged element by element during git syncFinding things
Ctrl/⌘ K), quick switcher (Ctrl/⌘ O)tag:, path:, file:, "phrases" and -exclusions✅ and starts the next occurrence of a repeating task, and an Undo toast takes any move back), or add cards straight into a column. Open a card for everything about it — title, status, priority, due / scheduled / start dates, repeat, tags and a checklist made of the task's nested subtasks — each change written to its line. Search and filter by tag or priority, and a Schedule beside the board takes a card dropped on a day to give it that date. Columns are only a view: nothing about the board is stored in your notes. Type a date, priority or repeat straight into a new task — Pay rent fri !high every month, Call Sam tomorrow, Dentist 12 oct, in 3 days — and it is understood as you type (each phrase shows as a chip; its × keeps the words as they are). Right-click a task for the quick changes (done, due today / tomorrow / next week, priority, delete); with a card focused, Enter opens it, Space ticks it off and Delete deletes it (with Undo). Tasks use the Obi/Obsidian-Tasks emoji format — - [ ] Write report #work ⏫ 🔁 every week 📅 2026-10-05 — so existing vaults just work+ on it. A To plan panel lists what is overdue (move it all to today in one click) and the tasks that have no date yet, ready to drag onto a day. The week can start on Monday or Sunday (Settings)Working together (online workspaces)
GitHub sync
Note (conflict 2026-09-16 1230).md instead of being lostAdmin
/admin console: create users, reset passwords, promote admins, disable/delete accounts, one-time invite links, see every workspace and its sync stateEverything else
.zip (drop in an Obsidian vault)Create the app: Dokploy → Create Service → Compose, point it at this repository (or paste docker-compose.yml).
Environment variables (Dokploy → Environment):
APP_SECRET=<openssl rand -base64 48>
ADMIN_USERNAME=you
ADMIN_PASSWORD=<a strong password>
Volume: the compose file already declares the named volume obi-data mounted at /data. Everything (SQLite database, workspace files, git clones) lives there — back up that one volume.
Domain: add your domain in Dokploy and point it at port 3000. Enable HTTPS (Let's Encrypt). Traefik's WebSocket support is on by default, which Obi needs for live editing.
Deploy, then open https://your-domain/ and sign in with the admin account, or open https://your-domain/admin to create users.
Dokploy → Create Service → Application → Build type Dockerfile:
3000obi-data, mount path /data⚠️ Without that mount every redeploy starts from an empty database. An Application service gets a brand-new container on each deploy, and the image's
/datafalls back to an unnamed volume that the new container never sees again — users, workspaces and online notes appear to vanish (the admin comes back because it is re-created fromADMIN_USERNAME/ADMIN_PASSWORD). Obi detects this: the startup log prints a warning and admins see a red banner in the app and in/admin. The Compose option above doesn't have this problem — it declares the volume.
cp .env.example .env && nano .env
docker compose up -d --build
The old data is usually still on the server — each deploy without a mount left its database behind in an unnamed volume, and Docker doesn't delete those on its own. On the Dokploy host (SSH in as root):
# 1. list every volume that holds an Obi database, newest first
# (copy the script over first: scp scripts/find-obi-data.sh root@your-server:~ )
sh ~/find-obi-data.sh
LAST WRITE VOLUME USERS WORKSPACES USERNAMES
2026-09-16T09:58:22.412Z f546842d… users=1 workspaces=1 admin
2026-09-16T09:58:15.163Z 814109e2… users=2 workspaces=2 admin,carol ← the one you want
# 2. copy that volume into the named volume the app will use from now on
OLD=<volume id from the list>
docker volume create obi-data
docker run --rm -v "$OLD":/from:ro -v obi-data:/to alpine sh -c 'cp -a /from/. /to/'
Then add the obi-data → /data Volume Mount in Dokploy (Option B above) and redeploy. Anything created since the
wipe lives in the newest volume and is replaced by this copy, so pick the volume you want to keep.
If the old deploy ran without APP_SECRET and the new one sets it (or the other way round), passwords still work
but saved GitHub tokens can't be decrypted — re-enter them in Settings → GitHub sync.
The script is read-only: it mounts each volume :ro and inspects a copy of the database.
Redeploy (or docker compose up -d --build). The server saves open documents and pushes pending GitHub changes on shutdown; connected browsers reconnect and re-sync automatically.
| Variable | Default | What it does |
|---|---|---|
APP_SECRET | random, stored in /data/secret.key | Encrypts GitHub tokens. Set this in production — if it changes, stored tokens must be re-entered. |
ADMIN_USERNAME / ADMIN_PASSWORD | — | Creates (or promotes) this admin on start. If unset, the first visit to /admin offers a setup screen. |
ADMIN_EMAIL | — | The address that admin signs in with. Required in practice: signing in is by email, and an admin without one cannot get in. Setting it on an existing admin moves them to that address. |
ADMIN_RESET_PASSWORD | 0 | Set to 1 for one boot to reset the admin password. |
PORT | 3000 | HTTP port. |
DATA_DIR | /data | Database + workspace files. |
SESSION_DAYS | 30 | Session lifetime. |
MAX_UPLOAD_MB | 50 | Per-file upload limit. |
APP_NAME | Obi | Shown on public pages. |
ALLOW_FILE_REMOTES | 0 | Allows file:///local-path git remotes (testing only). |
PUBLIC_URL | — | This server's public address, e.g. https://notes.example.com. Used in emails. |
REGISTRATION | open | open: anyone can create an account (confirmed by an emailed code). invite: only through admin invite links. closed: admins create accounts. open needs email to be configured; without it the server behaves as invite. |
LOGIN_CODE | either | either: sign in with a password or an emailed code. always: password, then an emailed code (two-step). : password only. Codes need email to be configured. |
Settings → Appearance → Background puts a colour, a gradient or a picture of your own behind the app. Dim, blur and panel transparency are adjustable, and text always stays on a plate solid enough to read. Backgrounds never appear on published notes, in print, or in the desktop app's title bar. Nothing is fetched from the internet and no keys are needed.
A background you pick from your own device is stored in that browser only (not synced, never written into a vault or repository). Signing out removes it, and the cached background, from the browser, and somebody else signing in on that browser never sees it.
People sign in with an email address, never a username — a username is a handle other people see, not a way in. Every account therefore needs an address, confirmed by a code at sign-up.
Administrators always need a code as well as their password. The password alone opens the admin console, so it is never enough by itself; the code goes to the address on file. An admin cannot sign in with a code alone, either.
Accounts left over from before this rule (no email address) cannot sign in. List and remove them with:
npm run users:prune # list them, change nothing
npm run users:prune -- --yes # delete them, and the workspaces they own
It refuses to run if no administrator has an email address, so you cannot lock
yourself out; set ADMIN_EMAIL and restart first.
Obi sends one kind of email: a 6-digit code, for confirming a new account, signing in without a password, changing your email address, or resetting a forgotten password. Without email configured the server quietly falls back to invite-only sign-up and password-only sign-in, and says so on the sign-in page.
Use a sending service rather than your own SMTP server: a fresh IP has no reputation, most hosts block outbound port 25, and a sign-in code in someone's spam folder means they cannot get in. Any provider with an SMTP relay works — Brevo, Resend, Amazon SES, Postmark.
1. Authenticate your domain with the provider (they give you DKIM records
and usually a verification record). Send from a subdomain, e.g.
obi.example.com, so the app's mail keeps its own reputation:
| Record | Where | What it does |
|---|---|---|
| DKIM (from the provider) | obi.example.com | signs the mail as yours |
SPF TXT (from the provider) | obi.example.com | says the provider may send for you |
DMARC TXT v=DMARC1; p=none; rua=mailto:[email protected] | _dmarc.example.com | asks for reports; tighten to p=quarantine once reports look clean |
No MX record is needed to send. Add one only if you want to receive mail at
that address (Cloudflare Email Routing forwards it for free).
2. Point Obi at the relay (Brevo's values shown; the user is the login the provider gives you, not your own address):
SMTP_HOST=smtp-relay.brevo.com
SMTP_PORT=587 # 587 is STARTTLS; for 465 also set SMTP_SECURE=1
[email protected]
SMTP_PASS=<the SMTP key>
MAIL_FROM="Obi <[email protected]>"
PUBLIC_URL=https://obi.example.com
MAIL_FROM must be on the domain you authenticated. If you leave it out, mail
goes out as the SMTP login, which looks broken and some relays refuse.
3. Check it before anyone else does. From the app directory (on Docker:
docker exec -it obi node scripts/send-test-mail.mjs [email protected]):
npm run mail:test [email protected]
It prints the settings in use, asks the relay whether it accepts the
credentials, then sends one message. In Gmail, open it and use Show original:
SPF, DKIM and DMARC should all say PASS. Send to an Outlook address too — it is
the fussier of the two. The startup log also prints one [mail] line saying
where mail goes.
Free plans have a daily cap (Brevo's is 300/day). Every registration and every
"send a new code" spends one; Obi's rate limits (10 registrations an hour and 6
resends per 15 minutes, per IP) keep that in check, and REGISTRATION=invite
removes the exposure altogether if the server is only for you.
https://github.com/you/notes (the [email protected]:you/notes.git form is accepted too). An empty repo is fine.repo scope also work.)Obi clones the repo into its data volume, commits your edits (default: 30 s after you stop typing) and pulls remote changes every 2 minutes — so notes you write in Obsidian on your desktop show up here, and the other way round. Both intervals, the commit author and auto-sync itself are configurable in Settings → GitHub sync.
If the same note is edited in both places at once, git merges it line by line; only genuinely conflicting notes produce a … (conflict <date>).md copy, and the remote version stays in the original file.
Your
.obsidian/folder,.gitand other dot-files are hidden in Obi but left untouched in the repository.
Obi also runs as a desktop app (Electron) that works fully offline. It is the
same app: the server runs inside it on 127.0.0.1, for you alone.
npm run desktop # build the client and open the app
npm run desktop:build # installer for this OS in release/ (Windows .exe, macOS .dmg, Linux AppImage/.deb)
The app keeps its database (vault list, cloud sign-ins, sync state) in the
per-user app data folder; OBI_DATA_DIR overrides it. OBI_CLOUD_URL pre-fills
the server address on the sign-in screen. For development,
OBI_DESKTOP=1 OBI_DESKTOP_WEB=1 npm run start serves the desktop UI to a
browser at /desktop-web, with folders created under OBI_DESKTOP_WEB_DIR.
Pushing a vX.Y.Z tag (matching package.json) builds the Windows .exe, a
universal macOS .dmg (Apple silicon and Intel) and the Linux AppImage and
.deb on GitHub Actions, and attaches them to a draft release; publishing it
moves the intro page's download links to the new version.
Updates. The desktop app looks for a newer release a few seconds after it
starts and every six hours (and on Settings → About → Check for updates). On
Windows, and in the Linux AppImage, it downloads the update in the background
and says Obi x.y.z is ready with a Restart button; if you don't restart, it
installs the next time the app quits. The macOS build can't replace itself until
it is signed by Apple, and a .deb belongs to the system's package manager, so
there it offers the download instead. In the browser, an open tab notices when
the server has been updated (/api/version) and offers a reload.
The installers are not signed with a paid certificate: Windows SmartScreen asks
for More info → Run anyway, and macOS (the app is ad-hoc signed, not
notarised) asks for System Settings → Privacy & Security → Open Anyway the
first time. Real signing goes in package.json → build when you have the
certificates.
Someone who is not signed in lands on an intro page (client/landing/) rather
than a bare sign-in box; /welcome always shows it, and /login and
/register go straight to the app. It is plain HTML and CSS built by Vite —
none of the app's JavaScript — so it paints immediately.
The pictures on it are real screenshots of a running Obi, taken with
scripts/screenshot.cjs: write a small JSON config (url, out, width, height,
an obi_session cookie for signed-in views, an optional js snippet to run
first) and take the shot with npx electron scripts/screenshot.cjs shot.json.
Replace the PNGs in client/landing/ the same way when the UI changes.
npm install
npm run dev # API on :3000, web on :5173
Open http://localhost:5173. Data goes to ./data. Create the first admin at /admin.
npm run build # build the client into dist/
npm start # serve API + built client on :3000
Requires Node 22.13+ (uses the built-in node:sqlite) and git on the PATH for GitHub workspaces.
client/ React + CodeMirror 6 (live preview, autocomplete, widgets), Yjs client, graph canvas
server/ Express API, WebSocket hub, Yjs documents, workspace runtime, git sync, SQLite metadata
shared/ markdown parser/renderer, link resolution, path helpers, diff/merge — used by both
/data/obi.db.offSMTP_HOST / SMTP_PORT / SMTP_SECURE | — / 587 / auto | Mail server for sign-up and sign-in codes. SMTP_SECURE=1 for implicit TLS (port 465, chosen automatically on 465). |
SMTP_USER / SMTP_PASS | — | Mail server login. |
SMTP_URL | — | Alternative to the above in one line, e.g. smtps://user:[email protected]. |
MAIL_FROM | SMTP_USER | Sender, e.g. Obi <[email protected]>. |
MAIL_LOG_CODES | 0 | Development only: with no SMTP configured, print emails (and their codes) to the server log instead of sending them. |