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
18 changes: 16 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ A web-based password manager with zero-knowledge architecture. All cryptography
│ + Rate limiting │
│ + Git sync (go-git) │
└─────────────────────────────────────┘
│ HTTPS + PAT
│ HTTPS (PAT) / SSH (key)
┌──────────────▼──────────────────────┐
│ Remote Git Repo (optional) │
│ └── *.gpg (encrypted blobs) │
Expand Down Expand Up @@ -125,6 +125,18 @@ cp .env.example .env
docker compose up -d
```

### Database Migrations & Upgrades

Migrations are applied automatically on startup via embedded SQL files in `db/migrations/`.
They are numbered (`NNN-name.sql`) and tracked in a `migrations` table — each runs exactly once.

**Forward upgrade ✅** — Fully supported. All migrations only add columns/tables,
never remove or rename. Upgrading from older versions (e.g. `v0.4.6`) to latest is safe.

**Rollback ❌** — Not supported. Because sqlc generates `SELECT *` queries, the old
binary will fail to scan rows if new columns were added. To downgrade, restore both
the binary and the database from backup.

### Security Hardening

The container runs with:
Expand Down Expand Up @@ -325,8 +337,10 @@ One-way overwrite sync with fresh clone/export:
- **Push**: Local → Remote (export local DB, force-push to remote)
- **No merge conflicts** — Last write wins
- **Fresh operations** — Local git repo is temporary, cleaned before/after each operation
- **PGP-encrypted PAT** — Encrypted with user's PGP public key
- **Per-user configuration** — Each user has their own repo URL
- **Two authentication methods**:
- **HTTPS** — PGP-encrypted Personal Access Token
- **SSH** — PGP-encrypted SSH private key with TOFU host key verification

### Git Repository Structure

Expand Down
114 changes: 107 additions & 7 deletions db/dbgen/git.sql.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 15 additions & 6 deletions db/dbgen/models.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 20 additions & 0 deletions db/migrations/007-git-ssh-auth.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
-- SSH authentication support for Git sync
--
-- Adds auth_type and encrypted_ssh_key columns to git_config
-- Creates git_known_hosts table for TOFU host key verification

ALTER TABLE git_config ADD COLUMN auth_type TEXT NOT NULL DEFAULT 'https';
ALTER TABLE git_config ADD COLUMN encrypted_ssh_key TEXT NOT NULL DEFAULT '';

-- Known hosts table for SSH TOFU (Trust On First Use)
CREATE TABLE IF NOT EXISTS git_known_hosts (
fingerprint TEXT NOT NULL REFERENCES users(fingerprint) ON DELETE CASCADE,
hostname TEXT NOT NULL,
host_key_fingerprint TEXT NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (fingerprint, hostname)
);

-- Record execution of this migration
INSERT OR IGNORE INTO migrations (migration_number, migration_name)
VALUES (007, '007-git-ssh-auth');
24 changes: 22 additions & 2 deletions db/queries/git.sql
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
-- name: UpsertGitConfig :exec
INSERT INTO git_config (fingerprint, repo_url, branch, encrypted_pat, updated_at)
VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP)
INSERT INTO git_config (fingerprint, repo_url, branch, encrypted_pat, encrypted_ssh_key, auth_type, updated_at)
VALUES (?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
ON CONFLICT (fingerprint) DO UPDATE
SET repo_url = excluded.repo_url,
branch = excluded.branch,
encrypted_pat = excluded.encrypted_pat,
encrypted_ssh_key = excluded.encrypted_ssh_key,
auth_type = excluded.auth_type,
updated_at = CURRENT_TIMESTAMP;

-- name: GetGitConfig :one
Expand Down Expand Up @@ -33,9 +35,27 @@ SELECT
gc.fingerprint,
gc.repo_url,
gc.branch,
gc.auth_type,
gc.encrypted_pat,
gc.encrypted_ssh_key,
gc.created_at as config_created_at,
(SELECT COUNT(*) FROM git_sync_log WHERE fingerprint = gc.fingerprint AND status = 'success') as success_count,
(SELECT COUNT(*) FROM git_sync_log WHERE fingerprint = gc.fingerprint AND status = 'failed') as failed_count
FROM git_config gc
WHERE gc.fingerprint = ?;

-- name: UpsertKnownHost :exec
INSERT INTO git_known_hosts (fingerprint, hostname, host_key_fingerprint)
VALUES (?, ?, ?)
ON CONFLICT (fingerprint, hostname) DO UPDATE
SET host_key_fingerprint = excluded.host_key_fingerprint,
created_at = CURRENT_TIMESTAMP;

-- name: GetKnownHost :one
SELECT * FROM git_known_hosts WHERE fingerprint = ? AND hostname = ?;

-- name: DeleteKnownHost :exec
DELETE FROM git_known_hosts WHERE fingerprint = ? AND hostname = ?;

-- name: ListKnownHosts :many
SELECT * FROM git_known_hosts WHERE fingerprint = ? ORDER BY hostname;
Loading
Loading