5
Skema Database Lokal
Termimus menyimpan seluruh data yang dapat diedit pengguna di satu berkas SQLite bernama termimus.db. get_database_path() menaruhnya di <config_dir>/termimus/termimus.db dan jatuh kembali ke PathBuf::from(".") bila dirs::config_dir() tidak tersedia. src-tauri/src/db/mod.rs:1250-12551 Database::new membuat direktori induk, membuka Connection, menyalakan PRAGMA foreign_keys = ON, lalu memanggil init_tables(); karena run() memakai expect("Failed to initialize SQLite database"), kegagalan di langkah mana pun menghentikan proses. src-tauri/src/db/mod.rs:13-262 src-tauri/src/lib.rs:233
Seluruh command berbagi satu Connection yang dibungkus Mutex, sehingga Database disimpan sebagai Arc<Database> di AppState. src-tauri/src/db/mod.rs:8-104
Skema dalam satu diagram
init_tables() menjalankan satu execute_batch berisi sembilan pernyataan CREATE TABLE IF NOT EXISTS, sehingga membuka database yang sudah ada tidak menjatuhkan tabel lama. src-tauri/src/db/mod.rs:28-1295 Kesembilan tabel itu beserta relasinya:
erDiagram
vault_meta {
text key PK
blob value
}
folders {
text id PK
text name
text parent_id
text created_at
text updated_at
}
credentials {
text id PK
text kind
blob ciphertext
blob nonce
blob passphrase_ciphertext
blob passphrase_nonce
text name
text key_type
text public_key
text fingerprint
text username
text created_at
text updated_at
}
hosts {
text id PK
text folder_id FK
text credential_id FK
text label
text address
integer port
text username
text auth_method
text tags
text last_connected_at
text os_icon
text jump_host_id
text created_at
text updated_at
}
port_forwards {
text id PK
text host_id FK
text label
text forward_type
text local_address
integer local_port
text remote_address
integer remote_port
text created_at
}
snippets {
text id PK
text title
text command
text tags
text created_at
text updated_at
}
workspace_presets {
text id PK
text name
text description
text layout
text nodes
boolean broadcast_on_launch
text created_at
text updated_at
}
known_hosts {
text address PK
integer port PK
text key_type
text fingerprint
text first_seen_at
text last_seen_at
}
tombstones {
text entity_type PK
text entity_id PK
text deleted_at
}
folders ||--o{ hosts : "folder_id, ON DELETE SET NULL"
credentials ||--o{ hosts : "credential_id, ON DELETE SET NULL"
hosts ||--o{ port_forwards : "host_id, ON DELETE CASCADE"
folders ||--o{ folders : "parent_id, tanpa constraint"
hosts ||--o{ hosts : "jump_host_id, tanpa constraint"
%% fig: Relasi antar tabel di termimus.db
%% desc: Tiga foreign key dideklarasikan pada hosts dan port_forwards, sementara parent_id dan jump_host_id hanya berupa kolom biasa. vault_meta, snippets, workspace_presets, known_hosts, dan tombstones berdiri sendiri.| Tabel | Primary key | Struct | Peran |
|---|---|---|---|
vault_meta | key | — | pasangan salt, verifier_ciphertext, dan verifier_nonce yang dipakai alur vault src-tauri/src/db/mod.rs:33-366 |
folders | id | Folder src-tauri/src/db/models.rs:3-117 | hierarki folder lewat parent_id |
credentials | id | Credential src-tauri/src/db/models.rs:89-1118 | ciphertext dan nonce secret |
hosts | id | Host src-tauri/src/db/models.rs:39-639 | daftar host SSH, menunjuk folder dan credential |
port_forwards | id | PortForwardRule src-tauri/src/db/models.rs:190-20110 | aturan tunnel per host_id |
snippets | id | Snippet src-tauri/src/db/models.rs:214-22211 | perintah tersimpan, tanpa relasi |
workspace_presets | id | WorkspacePreset src-tauri/src/db/models.rs:242-25612 | layout plus nodes berisi JSON |
known_hosts | (address, port) | KnownHost src-tauri/src/db/models.rs:269-27713 | fingerprint TOFU per alamat dan port |
tombstones | (entity_type, entity_id) | Tombstone src-tauri/src/db/models.rs:279-28414 | penanda hapus untuk penggabungan data |
Tabel credentials merangkap dua peran: penyimpan secret terenkripsi sekaligus sumber data Keychain. Credential membawa ciphertext dan nonce, sedangkan KeychainItem adalah tampilan tanpa secret yang dibentuk lewat impl From<&Credential>; list_keychain_items hanya membaca baris yang name-nya tidak kosong. src-tauri/src/db/models.rs:89-1118 src-tauri/src/db/models.rs:115-12615 src-tauri/src/db/mod.rs:489-49616
Dua kolom bertipe TEXT sebenarnya menyimpan JSON: list_hosts mem-parse tags dengan serde_json::from_str, dan save_workspace_preset men-serialize nodes dengan serde_json::to_string. src-tauri/src/db/mod.rs:208-23617 src-tauri/src/db/mod.rs:803-80818
Tidak ada CREATE INDEX eksplisit di berkas ini; setiap query daftar mengurutkan sendiri lewat ORDER BY, misalnya ORDER BY label ASC di list_hosts. src-tauri/src/db/mod.rs:208-21219
Foreign key dan pembersihan rujukan
Hanya hosts dan port_forwards yang mendeklarasikan FOREIGN KEY, dan PRAGMA foreign_keys = ON membuat aksinya benar-benar berlaku. src-tauri/src/db/mod.rs:62-9020
| Kolom | Arah | Aksi saat induk dihapus | Rujukan |
|---|---|---|---|
hosts.folder_id | hosts → folders | ON DELETE SET NULL | src-tauri/src/db/mod.rs:62-7721 |
hosts.credential_id | hosts → credentials | ON DELETE SET NULL | src-tauri/src/db/mod.rs:62-7721 |
port_forwards.host_id | port_forwards → hosts | ON DELETE CASCADE | src-tauri/src/db/mod.rs:79-9022 |
folders.parent_id | folders → folders | tidak ada constraint | src-tauri/src/db/mod.rs:38-4423 |
hosts.jump_host_id | hosts → hosts | tidak ada constraint | src-tauri/src/db/mod.rs:131-14624 |
⚠️
folders.parent_iddanhosts.jump_host_idtidak punya constraint, jadi rujukan dibersihkan manual:delete_foldermen-NULL-kanhosts.folder_iddanfolders.parent_idsebelum menghapus barisnya, dandelete_hostmen-NULL-kanjump_host_idyang menunjuk host tersebut. src-tauri/src/db/mod.rs:574-58625 src-tauri/src/db/mod.rs:319-33026 Import backup mengulang pembersihan yang sama sebagai langkah penutup lewat tigaUPDATE ... NOT IN (SELECT ...). src-tauri/src/db/mod.rs:1185-120027
Urutan hapus saat replace_all mengikuti arah foreign key: tombstones, known_hosts, snippets, workspace_presets, port_forwards, hosts, credentials, lalu folders. src-tauri/src/db/mod.rs:985-99428
Karena port_forwards.host_id memakai ON DELETE CASCADE, menghapus satu host ikut menghapus aturan tunnel-nya, sementara delete_host hanya menulis tombstone dengan entity_type 'host'. src-tauri/src/db/mod.rs:79-9022 src-tauri/src/db/mod.rs:319-33026
Operasi yang menyentuh banyak baris sekaligus dibungkus satu transaksi: rekey_vault memperbarui tiga baris vault_meta dan seluruh credentials, lalu memanggil tx.commit() di akhir. src-tauri/src/db/mod.rs:381-42029
Bootstrap dan migrasi kolom
Setelah blok CREATE TABLE, init_tables() menjalankan ALTER TABLE satu per satu dan membuang galatnya dengan let _ =, sehingga kolom yang sudah ada cukup gagal tanpa menghentikan startup. src-tauri/src/db/mod.rs:131-14624 Konsekuensinya, ALTER untuk credentials.name, key_type, public_key, fingerprint, username, created_at, dan updated_at selalu gagal pada database baru, karena kolom-kolom itu sudah ada di blok CREATE TABLE. src-tauri/src/db/mod.rs:46-6030
graph TD
PATH["get_database_path: config_dir()/termimus/termimus.db"] --> OPEN["Connection::open"]
OPEN --> FKON["PRAGMA foreign_keys = ON"]
FKON --> CREATE["init_tables: 9 x CREATE TABLE IF NOT EXISTS"]
CREATE --> ALTER["ALTER TABLE kolom tambahan"]
ALTER --> IGNORE["galat ALTER diabaikan lewat let _ ="]
ALTER --> BACKFILL["UPDATE folders SET updated_at = created_at"]
IGNORE --> READY["Database dipakai command save_* dan list_*"]
BACKFILL --> READY
style PATH fill:#336791,color:#fff
style CREATE fill:#336791,color:#fff
style ALTER fill:#f59e0b,color:#000
style IGNORE fill:#dc2626,color:#fff
style READY fill:#2d7d46,color:#fff
%% fig: Bootstrap skema saat Database::new
%% desc: Database dibuka dengan foreign key aktif, sembilan tabel dibuat bila belum ada, lalu kolom tambahan dimigrasikan dengan galat yang sengaja diabaikan.| Tabel | Kolom | Definisi di ALTER | Catatan |
|---|---|---|---|
credentials | name, key_type, public_key, fingerprint, created_at, updated_at | TEXT NOT NULL DEFAULT '' | sudah ada di blok CREATE TABLE, jadi ALTER gagal |
credentials | username | TEXT | juga sudah ada di blok CREATE TABLE |
hosts | last_connected_at | TEXT | tanpa DEFAULT |
hosts | os_icon | TEXT | komentar kode menandainya v0.4.8+ auto-detected OS icon |
hosts | jump_host_id | TEXT | komentar kode menandainya v0.5.0: bastion / jump host proxyjump |
folders | updated_at | TEXT NOT NULL DEFAULT '' | ditutup backfill UPDATE folders SET updated_at = created_at WHERE updated_at = '' |
Tombstone: jejak penghapusan
Tabel tombstones memakai primary key gabungan (entity_type, entity_id), sehingga satu entitas hanya punya satu penanda. src-tauri/src/db/mod.rs:122-12731 record_tombstone menimpa deleted_at dengan waktu sekarang, sedangkan save_tombstone memakai MAX(deleted_at, excluded.deleted_at) agar penanda dari backup yang lebih tua tidak memundurkan waktu hapus. src-tauri/src/db/mod.rs:151-17232 is_tombstoned mengembalikan Option<String> berisi deleted_at, dan nilai itulah yang dipakai saat merge untuk memutuskan baris mana yang dilewati; mekanisme merge-nya dibahas di Backup, Sync Relay, dan API Server. src-tauri/src/db/mod.rs:191-20033
entity_type | Ditulis oleh |
|---|---|
host | delete_host src-tauri/src/db/mod.rs:319-33026 |
credential | delete_credential src-tauri/src/db/mod.rs:475-48534 |
folder | delete_folder src-tauri/src/db/mod.rs:574-58625 |
port_forward | delete_port_forward src-tauri/src/db/mod.rs:663-67335 |
snippet | delete_snippet src-tauri/src/db/mod.rs:721-73136 |
workspace_preset | delete_workspace_preset src-tauri/src/db/mod.rs:831-84137 |
Tidak semua tabel ikut tercatat: vault_meta dan known_hosts tidak punya padanan entity_type, dan known_hosts dihapus langsung lewat delete_known_host tanpa meninggalkan penanda. src-tauri/src/db/mod.rs:884-89138
Saat menambah tabel baru, konvensi repository meminta CREATE TABLE IF NOT EXISTS di init_tables(), struct baris di models.rs, method CRUD di Database, dan bila tabel itu portabel juga disambungkan ke BackupBundle. AGENTS.md:3439