[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-82727":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":10,"language":11,"languages":10,"totalLinesOfCode":10,"stars":12,"forks":13,"watchers":14,"openIssues":14,"contributorsCount":15,"subscribersCount":15,"size":15,"stars1d":16,"stars7d":17,"stars30d":18,"stars90d":15,"forks30d":15,"starsTrendScore":19,"compositeScore":20,"rankGlobal":10,"rankLanguage":10,"license":21,"archived":22,"fork":22,"defaultBranch":23,"hasWiki":24,"hasPages":22,"topics":25,"createdAt":10,"pushedAt":10,"updatedAt":37,"readmeContent":38,"aiSummary":39,"trendingCount":15,"starSnapshotCount":15,"syncStatus":40,"lastSyncTime":41,"discoverSource":42},82727,"skiff","Priyanshu-1622\u002Fskiff","Priyanshu-1622","Self-hosted SSH connection manager. Open-source alternative to Termius.","",null,"TypeScript",132,11,1,0,4,29,62,18,71.94,"Other",false,"main",true,[26,27,28,29,30,31,32,33,34,35,36],"connection-manager","encryption","fastify","react","self-hosted","ssh","terminal","termius-alternative","typescript","websocket","xterm","2026-06-12 04:01:38","# Skiff\n\nA self-hosted SSH connection manager. Store hosts, organize them in folders, connect through an in-browser terminal. Credentials are encrypted at rest.\n\nOpen-source. AGPL-3.0. Single binary + SQLite — no cloud, no telemetry.\n\n![License](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002Flicense-AGPL--3.0-blue.svg)\n![Node](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002Fnode-%3E%3D20-brightgreen.svg)\n![TypeScript](https:\u002F\u002Fimg.shields.io\u002Fbadge\u002FTypeScript-5.x-blue.svg)\n\n## Demo\n\n![Skiff demo](docs\u002Fdemo.gif)\n\n### Screenshots\n\n![Empty state](docs\u002Fempty-state.png)\n*First-time setup — empty dashboard with import \u002F add host CTAs.*\n\n![Add host dialog](docs\u002Fadd-host.png)\n*Adding a host. Password or private key auth.*\n\n![Import from ssh config](docs\u002Fimport.png)\n*Paste your ~\u002F.ssh\u002Fconfig and Skiff parses it.*\n\n## Why I built this\n\nI had a text file called `servers.md` with a growing list of SSH commands I kept copy-pasting into my terminal. Every time I added a new VPS or rotated a key I'd promise myself I'd \"set up a proper tool\", then I'd open Termius, see the sign-in screen, and close it again. I didn't want my SSH inventory in someone else's cloud — and the desktop alternatives are either paid or feel like they were designed in 2011.\n\nSo one weekend I figured how hard could it really be: encrypted SQLite + ssh2 + xterm.js. A couple of weeks later I had something I actually use every day. It's not finished — see \"Known issues\" for the rough edges — but it scratches the itch.\n\n## How it compares\n\n| | **Skiff** | Termius | Royal TSX | SecureCRT |\n|---|---|---|---|---|\n| Self-hosted | yes | no | no | no |\n| Open source | yes | no | no | no |\n| Price | free | freemium ($) | paid ($45+) | paid ($99+) |\n| Encryption at rest | yes | yes | yes | yes |\n| In-browser terminal | yes | no (native app) | no | no |\n| Mobile access | works on phone | iOS + Android apps | no | no |\n| Cloud sync | optional \u002F never | required for sync | local only | local only |\n| Telemetry | none | yes | unknown | unknown |\n| Docker deploy | one command | n\u002Fa | n\u002Fa | n\u002Fa |\n| Team features | no (yet) | yes (paid) | yes | yes |\n\nSkiff is for one person who wants to own their SSH inventory. If you need shared team access, audit logs, and a slick mobile app, the paid tools are still ahead — Termius in particular is genuinely good if you're okay with the cloud sync. Skiff is for the case where you specifically don't want that.\n\n## Features\n\n- Encrypted credential vault. AES-256-GCM at rest, key derived from your master password with argon2id (OWASP params). The key lives in memory while you're unlocked and gets zeroed on lock or idle timeout. The password itself is never stored — only an HMAC verifier so we can tell you \"wrong password\" without having to decrypt anything.\n- In-browser terminal. xterm.js over a WebSocket. Real SSH session, no fake shell.\n- Folders, favorites, search. Search hits labels, hostnames, and usernames.\n- Imports your `~\u002F.ssh\u002Fconfig`. Parses `Host`, `HostName`, `Port`, `User`, `IdentityFile`. Doesn't handle `Include` directives yet (see Known issues).\n- Dark \u002F light themes. Persists in localStorage.\n- SSH host fingerprint pinning. First connect saves it; mismatches block the connection.\n- Auto-lock on idle. Configurable, defaults to 15 minutes.\n- Single docker compose command to deploy. SQLite, so no separate database to run.\n\n## Quick start\n\n### Dev\n\n```bash\ngit clone https:\u002F\u002Fgithub.com\u002FPriyanshu-1622\u002Fskiff.git\ncd skiff\npnpm install\npnpm dev\n```\n\nThen http:\u002F\u002Flocalhost:5173. First load asks you to set a master password — that's the vault.\n\nRequires Node 20+ and pnpm 9+. On Windows you'll need Visual Studio Build Tools because `better-sqlite3` and `argon2` are native. There's a note in Troubleshooting about this.\n\n### Docker\n\n```bash\ncp .env.example .env\n# set SKIFF_COOKIE_SECRET=$(openssl rand -hex 32) in .env\n\n# Create the data directory as your own user BEFORE starting.\n# If you skip this, Docker creates it as root and the container\n# (which runs as the unprivileged node user) can't write the database.\nmkdir -p data\n\ndocker compose up -d --build\n```\n\nThen http:\u002F\u002Flocalhost:8080. Back up `.\u002Fdata\u002F` — that's where the encrypted vault lives.\n\n## Project structure\n\n```\nskiff\u002F\n├── apps\u002F\n│   ├── web\u002F                    React frontend (Vite)\n│   │   ├── src\u002F\n│   │   │   ├── components\u002F     Shell (Topbar, Sidebar, AppShell), icons\n│   │   │   ├── routes\u002F         unlock, dashboard, terminal, settings\n│   │   │   ├── lib\u002F            API client, vault store, theme, ws client\n│   │   │   └── styles\u002F         Design tokens + per-screen CSS\n│   │   └── vite.config.ts\n│   └── api\u002F                    Fastify backend\n│       ├── src\u002F\n│       │   ├── crypto\u002F         AES-256-GCM + argon2id, session store\n│       │   ├── routes\u002F         auth, hosts, folders, terminal, import, settings\n│       │   ├── db\u002F             SQLite schema + connection\n│       │   └── lib\u002F            auth middleware, response helpers, id gen\n│       └── server.ts\n├── packages\u002F\n│   └── shared\u002F                 Types shared between web and api\n├── docs\u002F                       Demo gif + screenshots\n├── Dockerfile                  Multi-stage production build\n├── docker-compose.yml          One-command production deploy\n└── .env.example                All configurable env vars\n```\n\nIt's a pnpm workspace with two apps and one shared types package. The frontend and backend are decoupled — the web app talks to the API over HTTP + WebSocket, no direct imports between them.\n\n## Known issues \u002F rough edges\n\nThese are real things I've noticed using Skiff. Some are easy fixes I haven't gotten to; some need a redesign.\n\n- **Import doesn't handle `Include` directives.** If your `~\u002F.ssh\u002Fconfig` does `Include ~\u002F.ssh\u002Fconfig.d\u002F*`, those hosts get skipped silently. I'll fix it when I personally need it — right now my config is one file.\n- **No folder reordering.** You can create folders and delete them, but you can't drag to reorder or nest existing ones. Plan the hierarchy before you create them, or be prepared to delete and re-create.\n- **No \"restore from backup\" UI.** Settings → Backup downloads an encrypted JSON. To restore you stop the server, replace `data\u002Fskiff.sqlite` manually, restart. There's no in-app flow yet.\n- **Terminal resize is occasionally laggy.** xterm.js sends the new dimensions on resize, but during the resize gesture the output can wrap weirdly for a few frames. Stops looking broken once you let go.\n- **The `.sqlite-shm` and `.sqlite-wal` files.** SQLite in WAL mode creates two sidecar files next to the main DB. They're normal — do **not** delete them while Skiff is running. You'll corrupt the database. Ask me how I know.\n- **First-time docker build is slow.** Native modules (better-sqlite3, argon2) compile from source the first time, which can take 3-5 minutes on a small VPS. After that it's cached.\n\n## What Skiff doesn't do\n\nIf you need any of these, Skiff isn't the right tool yet:\n\n- Multi-user. It's single-vault, single-user. No teams, no RBAC.\n- SFTP \u002F file transfers. SSH sessions only.\n- Bastion \u002F jump host chains. Direct connections only.\n- Recording sessions or audit logs.\n- Mobile-optimized terminal. The dashboard works on phones but the terminal really wants a keyboard.\n- LDAP \u002F SAML \u002F SSO. Just the master password.\n\nMost of these are on the roadmap. Some might never be — if you need a polished team-grade tool today, Termius or Teleport will serve you better.\n\n## Stack\n\n| | |\n|---|---|\n| Frontend | React 18, TypeScript, Vite, TanStack Router, TanStack Query, Zustand |\n| Backend  | Node 20, Fastify, ssh2, better-sqlite3 (WAL mode) |\n| Crypto   | Node `crypto` (AES-256-GCM), argon2 |\n| Terminal | xterm.js + FitAddon + WebLinksAddon |\n| Styling  | Plain CSS with design tokens — no Tailwind, no CSS-in-JS |\n\nPicked `better-sqlite3` over the async sqlite drivers because the synchronous API makes transactions and prepared statements way less fiddly — and SQLite is fast enough that the \"blocking\" concern doesn't actually matter for a single-user app.\n\n## Security model — the short version\n\nMaster password → argon2id → 32-byte vault key (in memory only).\nEach credential → AES-256-GCM(plaintext, vault key) → SQLite as `(nonce, ciphertext)`.\nOn unlock, we derive the key from your input, compare its HMAC to the stored verifier, and if they match the key sits in memory until you lock or go idle.\n\nWhat's encrypted: SSH passwords, private keys, passphrases.\nWhat's not: labels, hostnames, ports, usernames, folder names. These aren't secrets — they're metadata.\n\nIf you forget your master password your credentials are gone. There's no recovery and there can't be one — that's the whole point.\n\nFull version: [SECURITY.md](.\u002FSECURITY.md).\n\n## Configuration\n\n`.env.example` lists everything. The ones you actually need to think about:\n\n| Variable | Default | What it does |\n|---|---|---|\n| `SKIFF_COOKIE_SECRET` | random | **Set this in production.** Signs session cookies. |\n| `SKIFF_PORT` | `8080` | API port |\n| `SKIFF_DB_PATH` | `.\u002Fdata\u002Fskiff.sqlite` | Where the vault lives |\n\n## Troubleshooting\n\n### Windows: `better-sqlite3` won't compile\n\nYou need Visual Studio Build Tools with \"Desktop development with C++\". The standalone Node installer doesn't include them. Grab them from https:\u002F\u002Faka.ms\u002Fvs\u002F17\u002Frelease\u002Fvs_BuildTools.exe, restart your terminal, then `pnpm install` again.\n\n### Port 8080 is already in use\n\nSet `SKIFF_PORT=3000` (or whatever) in `.env`. The Vite dev server's proxy follows it automatically.\n\n## Contributing\n\nIssues and PRs welcome. If it's a feature, open an issue first — easier to discuss before code than after.\n\nRepo conventions: TypeScript strict mode is on, please don't turn it off. Format with Prettier. Commit messages — I don't care much, just be specific enough that `git log --oneline` is readable.\n\n## License\n\nAGPL-3.0. You can run it, modify it, host it. If you run a modified version as a service, the modified source has to be available under AGPL too.\n\n[LICENSE](.\u002FLICENSE).\n\n---\n\nBuilt by Priyanshu. Bug reports and PRs welcome.","Skiff 是一个自托管的 SSH 连接管理器，提供开源替代方案如 Termius。项目使用 TypeScript 编写，核心功能包括通过浏览器内终端连接和管理 SSH 主机、加密存储凭证以及支持文件夹组织和搜索功能。技术上采用 Fastify 和 React 构建后端与前端界面，利用 xterm.js 实现基于 WebSocket 的实时终端交互，并通过 SQLite 存储数据，确保无云依赖且不收集任何遥测信息。适合需要完全控制自己 SSH 库存、偏好自托管解决方案并希望避免商业工具成本的个人用户使用。",2,"2026-06-11 04:09:02","CREATED_QUERY"]