diff --git a/Makefile b/Makefile index a47517ac8..52018f3ac 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ EMBASSY_UIS := frontend/dist/ui frontend/dist/setup-wizard frontend/dist/diagnos EMBASSY_SRC := raspios.img product_key.txt $(EMBASSY_BINS) backend/embassyd.service backend/embassy-init.service $(EMBASSY_UIS) $(shell find build) COMPAT_SRC := $(shell find system-images/compat/src) UTILS_SRC := $(shell find system-images/utils/Dockerfile) -BACKEND_SRC := $(shell find backend/src) $(shell find patch-db/*/src) backend/Cargo.toml backend/Cargo.lock +BACKEND_SRC := $(shell find backend/src) $(shell find backend/migrations) $(shell find patch-db/*/src) backend/Cargo.toml backend/Cargo.lock FRONTEND_SHARED_SRC := $(shell find frontend/projects/shared) $(shell find frontend/assets) $(shell ls -p frontend/ | grep -v / | sed 's/^/frontend\//g') frontend/node_modules frontend/config.json patch-db/client/dist FRONTEND_UI_SRC := $(shell find frontend/projects/ui) FRONTEND_SETUP_WIZARD_SRC := $(shell find frontend/projects/setup-wizard) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index d89cd7ad2..1907f3dc3 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -982,6 +982,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "dirs" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" +dependencies = [ + "dirs-sys", +] + [[package]] name = "dirs-next" version = "2.0.0" @@ -992,6 +1001,17 @@ dependencies = [ "dirs-sys-next", ] +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users 0.4.3", + "winapi", +] + [[package]] name = "dirs-sys-next" version = "0.1.2" @@ -1302,18 +1322,6 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" -[[package]] -name = "flume" -version = "0.10.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577" -dependencies = [ - "futures-core", - "futures-sink", - "pin-project", - "spin 0.9.4", -] - [[package]] name = "fnv" version = "1.0.7" @@ -1641,6 +1649,15 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hkdf" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" +dependencies = [ + "hmac 0.12.1", +] + [[package]] name = "hmac" version = "0.11.0" @@ -2151,17 +2168,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "libsqlite3-sys" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "898745e570c7d0453cc1fbc4a701eb6c662ed54e8fec8b7d14be137ebeeb9d14" -dependencies = [ - "cc", - "pkg-config", - "vcpkg", -] - [[package]] name = "linked-hash-map" version = "0.5.6" @@ -2213,6 +2219,15 @@ dependencies = [ "opaque-debug", ] +[[package]] +name = "md-5" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "658646b21e0b72f7866c7038ab086d3d5e1cd6271f060fd37defb241949d0582" +dependencies = [ + "digest 0.10.3", +] + [[package]] name = "memchr" version = "2.5.0" @@ -2509,7 +2524,7 @@ checksum = "e7249a699cdeea261ac73f1bf9350777cb867324f44373aafb5a287365bf1771" dependencies = [ "base64 0.13.0", "byteorder", - "md-5", + "md-5 0.9.1", "sha2 0.9.9", "thiserror", ] @@ -3219,7 +3234,7 @@ dependencies = [ "cc", "libc", "once_cell", - "spin 0.5.2", + "spin", "untrusted", "web-sys", "winapi", @@ -3725,15 +3740,6 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" -[[package]] -name = "spin" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6002a767bff9e83f8eeecf883ecb8011875a21ae8da43bffb817a57e78cc09" -dependencies = [ - "lock_api", -] - [[package]] name = "spki" version = "0.6.0" @@ -3773,34 +3779,39 @@ checksum = "6b69bf218860335ddda60d6ce85ee39f6cf6e5630e300e19757d1de15886a093" dependencies = [ "ahash", "atoi", + "base64 0.13.0", "bitflags", "byteorder", "bytes", "chrono", "crc", "crossbeam-queue", + "dirs 4.0.0", "either", "event-listener", - "flume", "futures-channel", "futures-core", - "futures-executor", "futures-intrusive", "futures-util", "hashlink", "hex", + "hkdf", + "hmac 0.12.1", "indexmap", "itoa 1.0.2", "libc", - "libsqlite3-sys", "log", + "md-5 0.10.1", "memchr", "once_cell", "paste", "percent-encoding", + "rand 0.8.5", "rustls", "rustls-pemfile", "serde", + "serde_json", + "sha-1", "sha2 0.10.2", "smallvec", "sqlformat", @@ -3810,6 +3821,7 @@ dependencies = [ "tokio-stream", "url", "webpki-roots", + "whoami", ] [[package]] @@ -4356,7 +4368,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edd106a334b7657c10b7c540a0106114feadeb4dc314513e97df481d5d966f42" dependencies = [ "byteorder", - "dirs", + "dirs 1.0.5", "winapi", ] @@ -5167,6 +5179,16 @@ dependencies = [ "libc", ] +[[package]] +name = "whoami" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524b58fa5a20a2fb3014dd6358b70e6579692a56ef6fce928834e488f42f65e8" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + [[package]] name = "winapi" version = "0.3.9" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index ca453ceba..5a377f572 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -123,7 +123,7 @@ sqlx = { version = "0.6.0", features = [ "chrono", "offline", "runtime-tokio-rustls", - "sqlite", + "postgres", ] } stderrlog = "0.5.3" tar = "0.4.38" @@ -142,8 +142,11 @@ trust-dns-server = "0.21.2" typed-builder = "0.10.0" url = { version = "2.2.2", features = ["serde"] } +[profile.test] +opt-level = 3 + [profile.dev.package.backtrace] opt-level = 3 -[profile.test] +[profile.dev.package.sqlx-macros] opt-level = 3 diff --git a/backend/migrations/20210629193146_Init.sql b/backend/migrations/20210629193146_Init.sql index 80543e22a..af3188137 100644 --- a/backend/migrations/20210629193146_Init.sql +++ b/backend/migrations/20210629193146_Init.sql @@ -1,45 +1,47 @@ -- Add migration script here -CREATE TABLE IF NOT EXISTS tor -( - package TEXT NOT NULL, - interface TEXT NOT NULL, - key BLOB NOT NULL CHECK (length(key) = 64), +CREATE TABLE IF NOT EXISTS tor ( + package TEXT NOT NULL, + interface TEXT NOT NULL, + key BYTEA NOT NULL CHECK (length(key) = 64), PRIMARY KEY (package, interface) ); -CREATE TABLE IF NOT EXISTS session -( - id TEXT NOT NULL PRIMARY KEY, + +CREATE TABLE IF NOT EXISTS session ( + id TEXT NOT NULL PRIMARY KEY, logged_in TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, logged_out TIMESTAMP, last_active TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, user_agent TEXT, - metadata TEXT NOT NULL DEFAULT 'null' + metadata TEXT NOT NULL DEFAULT 'null' ); -CREATE TABLE IF NOT EXISTS account -( - id INTEGER PRIMARY KEY CHECK (id = 0), + +CREATE TABLE IF NOT EXISTS account ( + id SERIAL PRIMARY KEY CHECK (id = 0), password TEXT NOT NULL, - tor_key BLOB NOT NULL CHECK (length(tor_key) = 64) + tor_key BYTEA NOT NULL CHECK (length(tor_key) = 64) ); -CREATE TABLE IF NOT EXISTS ssh_keys -( - fingerprint TEXT NOT NULL, - openssh_pubkey TEXT NOT NULL, - created_at TEXT NOT NULL, + +CREATE TABLE IF NOT EXISTS ssh_keys ( + fingerprint TEXT NOT NULL, + openssh_pubkey TEXT NOT NULL, + created_at TEXT NOT NULL, PRIMARY KEY (fingerprint) ); -CREATE TABLE IF NOT EXISTS certificates -( - id INTEGER PRIMARY KEY, -- Root = 0, Int = 1, Other = 2.. + +CREATE TABLE IF NOT EXISTS certificates ( + id SERIAL PRIMARY KEY, + -- Root = 0, Int = 1, Other = 2.. priv_key_pem TEXT NOT NULL, certificate_pem TEXT NOT NULL, lookup_string TEXT UNIQUE, created_at TEXT, updated_at TEXT ); -CREATE TABLE IF NOT EXISTS notifications -( - id INTEGER PRIMARY KEY, + +ALTER SEQUENCE certificates_id_seq START 2 RESTART 2; + +CREATE TABLE IF NOT EXISTS notifications ( + id SERIAL PRIMARY KEY, package_id TEXT, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, code INTEGER NOT NULL, @@ -48,9 +50,9 @@ CREATE TABLE IF NOT EXISTS notifications message TEXT NOT NULL, data TEXT ); -CREATE TABLE IF NOT EXISTS cifs_shares -( - id INTEGER PRIMARY KEY, + +CREATE TABLE IF NOT EXISTS cifs_shares ( + id SERIAL PRIMARY KEY, hostname TEXT NOT NULL, path TEXT NOT NULL, username TEXT NOT NULL, diff --git a/backend/sqlx-data.json b/backend/sqlx-data.json index 5f8cba317..788610295 100644 --- a/backend/sqlx-data.json +++ b/backend/sqlx-data.json @@ -1,82 +1,106 @@ { - "db": "SQLite", - "10350f5a16f1b2a6ce91672ae5dc6acc46691bd8f901861545ec83c326a8ccef": { + "db": "PostgreSQL", + "094882d4d46d52e814f9aaf5fae172a5dd745b06cbde347f47b18e6498167269": { "describe": { "columns": [], "nullable": [], "parameters": { - "Right": 3 + "Left": [ + "Text", + "Text", + "Text" + ] } }, - "query": "INSERT INTO ssh_keys (fingerprint, openssh_pubkey, created_at) VALUES (?, ?, ?)" - }, - "118d59de5cf930d5a3b5667b2220e9a3d593bd84276beb2b76c93b2694b0fd72": { - "describe": { - "columns": [], - "nullable": [], - "parameters": { - "Right": 3 - } - }, - "query": "INSERT INTO session (id, user_agent, metadata) VALUES (?, ?, ?)" + "query": "UPDATE certificates SET priv_key_pem = $1, certificate_pem = $2, updated_at = now() WHERE lookup_string = $3" }, "165daa7d6a60cb42122373b2c5ac7d39399bcc99992f0002ee7bfef50a8daceb": { "describe": { "columns": [], "nullable": [], "parameters": { - "Right": 0 + "Left": [] } }, "query": "DELETE FROM certificates WHERE id = 0 OR id = 1;" }, - "177c4b9cc7901a3b906e5969b86b1c11e6acbfb8e86e98f197d7333030b17964": { + "1f7936d27d63f01118ecd6f824e8a79607ed2b6e6def23f3e2487466dd2ddfe1": { "describe": { "columns": [], "nullable": [], "parameters": { - "Right": 1 + "Left": [ + "Text", + "Text", + "Text" + ] } }, - "query": "DELETE FROM notifications WHERE id = ?" + "query": "INSERT INTO certificates (priv_key_pem, certificate_pem, lookup_string, created_at, updated_at) VALUES ($1, $2, $3, now(), now())" }, - "1b2242afa55e730b37b00929b656d80940b457ec86c234ddd0de917bd8872611": { + "21471490cdc3adb206274cc68e1ea745ffa5da4479478c1fd2158a45324b1930": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Left": [ + "Text" + ] + } + }, + "query": "DELETE FROM ssh_keys WHERE fingerprint = $1" + }, + "22613628ff50341fdc35366e194fdcd850118824763cfe0dfff68dadc72167e9": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Left": [ + "Text", + "Text", + "Bytea" + ] + } + }, + "query": "INSERT INTO tor (package, interface, key) VALUES ($1, $2, $3) ON CONFLICT (package, interface) DO UPDATE SET key = $3" + }, + "28ea34bbde836e0618c5fc9bb7c36e463c20c841a7d6a0eb15be0f24f4a928ec": { "describe": { "columns": [ { - "name": "id: u32", + "name": "hostname", "ordinal": 0, - "type_info": "Int64" + "type_info": "Text" + }, + { + "name": "path", + "ordinal": 1, + "type_info": "Text" + }, + { + "name": "username", + "ordinal": 2, + "type_info": "Text" + }, + { + "name": "password", + "ordinal": 3, + "type_info": "Text" } ], "nullable": [ - false + false, + false, + false, + true ], "parameters": { - "Right": 4 + "Left": [ + "Int4" + ] } }, - "query": "INSERT INTO cifs_shares (hostname, path, username, password) VALUES (?, ?, ?, ?) RETURNING id AS \"id: u32\"" - }, - "1eee1fdc793919c391008854407143d7a11b4668486c11a760b49af49992f9f8": { - "describe": { - "columns": [], - "nullable": [], - "parameters": { - "Right": 2 - } - }, - "query": "REPLACE INTO tor (package, interface, key) VALUES (?, 'main', ?)" - }, - "2932aa02735b6422fca4ba889abfb3de8598178d4690076dc278898753d9df62": { - "describe": { - "columns": [], - "nullable": [], - "parameters": { - "Right": 1 - } - }, - "query": "UPDATE session SET logged_out = CURRENT_TIMESTAMP WHERE id = ?" + "query": "SELECT hostname, path, username, password FROM cifs_shares WHERE id = $1" }, "3502e58f2ab48fb4566d21c920c096f81acfa3ff0d02f970626a4dcd67bac71d": { "describe": { @@ -84,35 +108,75 @@ { "name": "tor_key", "ordinal": 0, - "type_info": "Blob" + "type_info": "Bytea" } ], "nullable": [ false ], "parameters": { - "Right": 0 + "Left": [] } }, "query": "SELECT tor_key FROM account" }, - "3e57a0e52b69f33e9411c13b03a5d82c5856d63f0375eb4c23b255a09c54f8b1": { + "4099028a5c0de578255bf54a67cef6cb0f1e9a4e158260700f1639dd4b438997": { "describe": { "columns": [ { - "name": "key", + "name": "fingerprint", "ordinal": 0, - "type_info": "Blob" + "type_info": "Text" + }, + { + "name": "openssh_pubkey", + "ordinal": 1, + "type_info": "Text" + }, + { + "name": "created_at", + "ordinal": 2, + "type_info": "Text" } ], "nullable": [ + false, + false, false ], "parameters": { - "Right": 2 + "Left": [ + "Text" + ] } }, - "query": "SELECT key FROM tor WHERE package = ? AND interface = ?" + "query": "SELECT * FROM ssh_keys WHERE fingerprint = $1" + }, + "46815a4ac2c43e1dfbab3c0017ed09d5c833062e523205db4245a5218b2562b8": { + "describe": { + "columns": [ + { + "name": "priv_key_pem", + "ordinal": 0, + "type_info": "Text" + }, + { + "name": "certificate_pem", + "ordinal": 1, + "type_info": "Text" + } + ], + "nullable": [ + false, + false + ], + "parameters": { + "Left": [ + "Text" + ] + } + }, + "query": "SELECT priv_key_pem, certificate_pem FROM certificates WHERE lookup_string = $1" }, "4691e3a2ce80b59009ac17124f54f925f61dc5ea371903e62cdffa5d7b67ca96": { "describe": { @@ -125,17 +189,17 @@ { "name": "logged_in", "ordinal": 1, - "type_info": "Datetime" + "type_info": "Timestamp" }, { "name": "logged_out", "ordinal": 2, - "type_info": "Datetime" + "type_info": "Timestamp" }, { "name": "last_active", "ordinal": 3, - "type_info": "Datetime" + "type_info": "Timestamp" }, { "name": "user_agent", @@ -157,50 +221,40 @@ false ], "parameters": { - "Right": 0 + "Left": [] } }, "query": "SELECT * FROM session WHERE logged_out IS NULL OR logged_out > CURRENT_TIMESTAMP" }, - "530192a2a530ee6b92e5b98e1eb1bf6d1426c7b0cb2578593a367cb0bf2c3ca8": { + "4bcfbefb1eb3181343871a1cd7fc3afb81c2be5c681cfa8b4be0ce70610e9c3a": { "describe": { "columns": [], "nullable": [], "parameters": { - "Right": 3 + "Left": [ + "Text" + ] } }, - "query": "UPDATE certificates SET priv_key_pem = ?, certificate_pem = ?, updated_at = datetime('now') WHERE lookup_string = ?" + "query": "UPDATE session SET logged_out = CURRENT_TIMESTAMP WHERE id = $1" }, - "56b986f2a2b7091d9c3acdd78f75d9842242de1f4da8f3672f2793d9fb256928": { + "548448e8ed8bcdf9efdc813d65af2cc55064685293b936f0f09e07f91a328eb9": { "describe": { - "columns": [], - "nullable": [], + "columns": [ + { + "name": "setval", + "ordinal": 0, + "type_info": "Int8" + } + ], + "nullable": [ + null + ], "parameters": { - "Right": 1 + "Left": [] } }, - "query": "DELETE FROM tor WHERE package = ?" - }, - "5b114c450073f77f466c980a2541293f30087b57301c379630326e5e5c2fb792": { - "describe": { - "columns": [], - "nullable": [], - "parameters": { - "Right": 3 - } - }, - "query": "REPLACE INTO tor (package, interface, key) VALUES (?, ?, ?)" - }, - "5c47da44b9c84468e95a13fc47301989900f130b3b5899d1ee6664df3ed812ac": { - "describe": { - "columns": [], - "nullable": [], - "parameters": { - "Right": 2 - } - }, - "query": "INSERT INTO certificates (id, priv_key_pem, certificate_pem, lookup_string, created_at, updated_at) VALUES (0, ?, ?, NULL, datetime('now'), datetime('now'))" + "query": "SELECT setval('certificates_id_seq', GREATEST(MAX(id) + 1, nextval('certificates_id_seq') - 1)) FROM certificates" }, "629be61c3c341c131ddbbff0293a83dbc6afd07cae69d246987f62cf0cc35c2a": { "describe": { @@ -215,38 +269,75 @@ false ], "parameters": { - "Right": 0 + "Left": [] } }, "query": "SELECT password FROM account" }, - "63785dc5f193ea31e6f641a910c75857ccd288a3f6e9c4f704331531e4f0689f": { + "687688055e63d27123cdc89a5bbbd8361776290a9411d527eaf1fdb40bef399d": { + "describe": { + "columns": [ + { + "name": "key", + "ordinal": 0, + "type_info": "Bytea" + } + ], + "nullable": [ + false + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + } + }, + "query": "SELECT key FROM tor WHERE package = $1 AND interface = $2" + }, + "6c96d76bffcc5f03290d8d8544a58521345ed2a843a509b17bbcd6257bb81821": { + "describe": { + "columns": [ + { + "name": "priv_key_pem", + "ordinal": 0, + "type_info": "Text" + }, + { + "name": "certificate_pem", + "ordinal": 1, + "type_info": "Text" + } + ], + "nullable": [ + false, + false + ], + "parameters": { + "Left": [] + } + }, + "query": "SELECT priv_key_pem, certificate_pem FROM certificates WHERE id = 1;" + }, + "6d35ccf780fb2bb62586dd1d3df9c1550a41ee580dad3f49d35cb843ebef10ca": { "describe": { "columns": [], "nullable": [], "parameters": { - "Right": 1 + "Left": [ + "Text" + ] } }, - "query": "UPDATE session SET last_active = CURRENT_TIMESTAMP WHERE id = ? AND logged_out IS NULL OR logged_out > CURRENT_TIMESTAMP" + "query": "UPDATE session SET last_active = CURRENT_TIMESTAMP WHERE id = $1 AND logged_out IS NULL OR logged_out > CURRENT_TIMESTAMP" }, - "6440354d73a67c041ea29508b43b5f309d45837a44f1a562051ad540d894c7d6": { - "describe": { - "columns": [], - "nullable": [], - "parameters": { - "Right": 1 - } - }, - "query": "DELETE FROM ssh_keys WHERE fingerprint = ?" - }, - "65e6c3fbb138da5cf385af096fdd3c062b6e826e12a8a4b23e16fcc773004c29": { + "7b64f032d507e8ffe37c41f4c7ad514a66c421a11ab04c26d89a7aa8f6b67210": { "describe": { "columns": [ { "name": "id", "ordinal": 0, - "type_info": "Int64" + "type_info": "Int4" }, { "name": "package_id", @@ -256,12 +347,12 @@ { "name": "created_at", "ordinal": 2, - "type_info": "Datetime" + "type_info": "Timestamp" }, { "name": "code", "ordinal": 3, - "type_info": "Int64" + "type_info": "Int4" }, { "name": "level", @@ -295,18 +386,109 @@ true ], "parameters": { - "Right": 2 + "Left": [ + "Int4", + "Int8" + ] } }, - "query": "SELECT id, package_id, created_at, code, level, title, message, data FROM notifications WHERE id < ? ORDER BY id DESC LIMIT ?" + "query": "SELECT id, package_id, created_at, code, level, title, message, data FROM notifications WHERE id < $1 ORDER BY id DESC LIMIT $2" }, - "668f39c868f90cdbcc635858bac9e55ed73192ed2aec5c52dcfba9800a7a4a41": { + "7e0649d839927e57fa03ee51a2c9f96a8bdb0fc97ee8a3c6df1069e1e2b98576": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Left": [ + "Text" + ] + } + }, + "query": "DELETE FROM tor WHERE package = $1" + }, + "8951b9126fbf60dbb5997241e11e3526b70bccf3e407327917294a993bc17ed5": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Left": [ + "Text", + "Text", + "Bytea" + ] + } + }, + "query": "INSERT INTO tor (package, interface, key) VALUES ($1, $2, $3) ON CONFLICT (package, interface) DO NOTHING" + }, + "94d471bb374b4965c6cbedf8c17bbf6bea226d38efaf6559923c79a36d5ca08c": { "describe": { "columns": [ { - "name": "id: u32", + "name": "id", "ordinal": 0, - "type_info": "Int64" + "type_info": "Int4" + }, + { + "name": "package_id", + "ordinal": 1, + "type_info": "Text" + }, + { + "name": "created_at", + "ordinal": 2, + "type_info": "Timestamp" + }, + { + "name": "code", + "ordinal": 3, + "type_info": "Int4" + }, + { + "name": "level", + "ordinal": 4, + "type_info": "Text" + }, + { + "name": "title", + "ordinal": 5, + "type_info": "Text" + }, + { + "name": "message", + "ordinal": 6, + "type_info": "Text" + }, + { + "name": "data", + "ordinal": 7, + "type_info": "Text" + } + ], + "nullable": [ + false, + true, + false, + false, + false, + false, + false, + true + ], + "parameters": { + "Left": [ + "Int8" + ] + } + }, + "query": "SELECT id, package_id, created_at, code, level, title, message, data FROM notifications ORDER BY id DESC LIMIT $1" + }, + "95c4ab4c645f3302568c6ff13d85ab58252362694cf0f56999bf60194d20583a": { + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Int4" }, { "name": "hostname", @@ -337,142 +519,49 @@ true ], "parameters": { - "Right": 0 + "Left": [] } }, - "query": "SELECT id AS \"id: u32\", hostname, path, username, password FROM cifs_shares" + "query": "SELECT id, hostname, path, username, password FROM cifs_shares" }, - "6b9abc9e079cff975f8a7f07ff70548c7877ecae3be0d0f2d3f439a6713326c0": { + "a60d6e66719325b08dc4ecfacaf337527233c84eee758ac9be967906e5841d27": { "describe": { "columns": [], "nullable": [], "parameters": { - "Right": 1 + "Left": [ + "Int4" + ] } }, - "query": "DELETE FROM notifications WHERE id < ?" + "query": "DELETE FROM cifs_shares WHERE id = $1" }, - "6c96d76bffcc5f03290d8d8544a58521345ed2a843a509b17bbcd6257bb81821": { - "describe": { - "columns": [ - { - "name": "priv_key_pem", - "ordinal": 0, - "type_info": "Text" - }, - { - "name": "certificate_pem", - "ordinal": 1, - "type_info": "Text" - } - ], - "nullable": [ - false, - false - ], - "parameters": { - "Right": 0 - } - }, - "query": "SELECT priv_key_pem, certificate_pem FROM certificates WHERE id = 1;" - }, - "7d548d2472fa3707bd17364b4800e229b9c2b1c0a22e245bf4e635b9b16b8c24": { + "a645d636be810a4ba61dcadf22e90de6e9baf3614aa9e97f053ff480cb3118a2": { "describe": { "columns": [], "nullable": [], "parameters": { - "Right": 3 + "Left": [ + "Text", + "Bytea" + ] } }, - "query": "INSERT INTO certificates (priv_key_pem, certificate_pem, lookup_string, created_at, updated_at) VALUES (?, ?, ?, datetime('now'), datetime('now'))" + "query": "INSERT INTO tor (package, interface, key) VALUES ($1, 'main', $2) ON CONFLICT (package, interface) DO UPDATE SET key = $2" }, - "82a8fa7eae8a73b5345015c72af024b4f21489b1d9b42235398d7eb8977fb132": { + "a6645d91f76b3d5fac2191ea3bec5dab7d7d124715fde02e6a816fa5dbc7acf2": { "describe": { "columns": [], "nullable": [], "parameters": { - "Right": 1 + "Left": [ + "Int4", + "Text", + "Bytea" + ] } }, - "query": "UPDATE account SET password = ?" - }, - "8595651866e7db772260bd79e19d55b7271fd795b82a99821c935a9237c1aa16": { - "describe": { - "columns": [ - { - "name": "interface", - "ordinal": 0, - "type_info": "Text" - }, - { - "name": "key", - "ordinal": 1, - "type_info": "Blob" - } - ], - "nullable": [ - false, - false - ], - "parameters": { - "Right": 1 - } - }, - "query": "SELECT interface, key FROM tor WHERE package = ?" - }, - "9496e17a73672ac3675e02efa7c4bf8bd479b866c0d31fa1e3a85ef159310a57": { - "describe": { - "columns": [ - { - "name": "priv_key_pem", - "ordinal": 0, - "type_info": "Text" - }, - { - "name": "certificate_pem", - "ordinal": 1, - "type_info": "Text" - } - ], - "nullable": [ - false, - false - ], - "parameters": { - "Right": 1 - } - }, - "query": "SELECT priv_key_pem, certificate_pem FROM certificates WHERE lookup_string = ?" - }, - "9fcedab1ba34daa2c6ae97c5953c09821b35b55be75b0c66045ab31a2cf4553e": { - "describe": { - "columns": [], - "nullable": [], - "parameters": { - "Right": 3 - } - }, - "query": "REPLACE INTO account (id, password, tor_key) VALUES (?, ?, ?)" - }, - "a1cbaac36d8e14c8c3e7276237c4824bff18861f91b0b08aa5791704c492acb7": { - "describe": { - "columns": [], - "nullable": [], - "parameters": { - "Right": 2 - } - }, - "query": "INSERT INTO certificates (id, priv_key_pem, certificate_pem, lookup_string, created_at, updated_at) VALUES (1, ?, ?, NULL, datetime('now'), datetime('now'))" - }, - "a4e7162322b28508310b9de7ebc891e619b881ff6d3ea09eba13da39626ab12f": { - "describe": { - "columns": [], - "nullable": [], - "parameters": { - "Right": 5 - } - }, - "query": "UPDATE cifs_shares SET hostname = ?, path = ?, username = ?, password = ? WHERE id = ?" + "query": "INSERT INTO account (id, password, tor_key) VALUES ($1, $2, $3) ON CONFLICT (id) DO UPDATE SET password = $2, tor_key = $3" }, "a6b0c8909a3a5d6d9156aebfb359424e6b5a1d1402e028219e21726f1ebd282e": { "describe": { @@ -499,116 +588,39 @@ false ], "parameters": { - "Right": 0 + "Left": [] } }, "query": "SELECT fingerprint, openssh_pubkey, created_at FROM ssh_keys" }, - "abfdeea8cd10343b85f647d7abc5dc3bd0b5891101b143485938192ee3b8c907": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int64" - }, - { - "name": "package_id", - "ordinal": 1, - "type_info": "Text" - }, - { - "name": "created_at", - "ordinal": 2, - "type_info": "Datetime" - }, - { - "name": "code", - "ordinal": 3, - "type_info": "Int64" - }, - { - "name": "level", - "ordinal": 4, - "type_info": "Text" - }, - { - "name": "title", - "ordinal": 5, - "type_info": "Text" - }, - { - "name": "message", - "ordinal": 6, - "type_info": "Text" - }, - { - "name": "data", - "ordinal": 7, - "type_info": "Text" - } - ], - "nullable": [ - false, - true, - false, - false, - false, - false, - false, - true - ], - "parameters": { - "Right": 1 - } - }, - "query": "SELECT id, package_id, created_at, code, level, title, message, data FROM notifications ORDER BY id DESC LIMIT ?" - }, - "b376d9e77e0861a9af2d1081ca48d14e83abc5a1546213d15bb570972c403beb": { + "b1147beaaabbed89f2ab8c1e13ec4393a9a8fde2833cf096af766a979d94dee6": { "describe": { "columns": [], "nullable": [], "parameters": { - "Right": 0 + "Left": [ + "Text", + "Text", + "Text", + "Text", + "Int4" + ] } }, - "query": "-- Add migration script here\nCREATE TABLE IF NOT EXISTS tor\n(\n package TEXT NOT NULL,\n interface TEXT NOT NULL,\n key BLOB NOT NULL CHECK (length(key) = 64),\n PRIMARY KEY (package, interface)\n);\nCREATE TABLE IF NOT EXISTS session\n(\n id TEXT NOT NULL PRIMARY KEY,\n logged_in TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\n logged_out TIMESTAMP,\n last_active TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\n user_agent TEXT,\n metadata TEXT NOT NULL DEFAULT 'null'\n);\nCREATE TABLE IF NOT EXISTS account\n(\n id INTEGER PRIMARY KEY CHECK (id = 0),\n password TEXT NOT NULL,\n tor_key BLOB NOT NULL CHECK (length(tor_key) = 64)\n);\nCREATE TABLE IF NOT EXISTS ssh_keys\n(\n fingerprint TEXT NOT NULL,\n openssh_pubkey TEXT NOT NULL,\n created_at TEXT NOT NULL,\n PRIMARY KEY (fingerprint)\n);\nCREATE TABLE IF NOT EXISTS certificates\n(\n id INTEGER PRIMARY KEY, -- Root = 0, Int = 1, Other = 2..\n priv_key_pem TEXT NOT NULL,\n certificate_pem TEXT NOT NULL,\n lookup_string TEXT UNIQUE,\n created_at TEXT,\n updated_at TEXT\n);\nCREATE TABLE IF NOT EXISTS notifications\n(\n id INTEGER PRIMARY KEY,\n package_id TEXT,\n created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\n code INTEGER NOT NULL,\n level TEXT NOT NULL,\n title TEXT NOT NULL,\n message TEXT NOT NULL,\n data TEXT\n);\nCREATE TABLE IF NOT EXISTS cifs_shares\n(\n id INTEGER PRIMARY KEY,\n hostname TEXT NOT NULL,\n path TEXT NOT NULL,\n username TEXT NOT NULL,\n password TEXT\n);" + "query": "UPDATE cifs_shares SET hostname = $1, path = $2, username = $3, password = $4 WHERE id = $5" }, - "cc33fe2958fe7caeac6999a217f918a68b45ad596664170b4d07671c6ea49566": { + "cec8112be0ebc02ef7e651631be09efe26d1677b5b8aa95ceb3a92aff1afdbcc": { "describe": { - "columns": [ - { - "name": "hostname", - "ordinal": 0, - "type_info": "Text" - }, - { - "name": "path", - "ordinal": 1, - "type_info": "Text" - }, - { - "name": "username", - "ordinal": 2, - "type_info": "Text" - }, - { - "name": "password", - "ordinal": 3, - "type_info": "Text" - } - ], - "nullable": [ - false, - false, - false, - true - ], + "columns": [], + "nullable": [], "parameters": { - "Right": 1 + "Left": [ + "Text", + "Text" + ] } }, - "query": "SELECT hostname, path, username, password FROM cifs_shares WHERE id = ?" + "query": "INSERT INTO certificates (id, priv_key_pem, certificate_pem, lookup_string, created_at, updated_at) VALUES (1, $1, $2, NULL, now(), now())" }, "d5117054072476377f3c4f040ea429d4c9b2cf534e76f35c80a2bf60e8599cca": { "describe": { @@ -623,60 +635,139 @@ false ], "parameters": { - "Right": 0 + "Left": [] } }, "query": "SELECT openssh_pubkey FROM ssh_keys" }, - "d54bd5b53f8c760e1f8cde604aa8b1bdc66e4e025a636bc44ffbcd788b5168fd": { + "da71f94b29798d1738d2b10b9a721ea72db8cfb362e7181c8226d9297507c62b": { "describe": { "columns": [], "nullable": [], "parameters": { - "Right": 6 + "Left": [ + "Text", + "Int4", + "Text", + "Text", + "Text", + "Text" + ] } }, - "query": "INSERT INTO notifications (package_id, code, level, title, message, data) VALUES (?, ?, ?, ?, ?, ?)" + "query": "INSERT INTO notifications (package_id, code, level, title, message, data) VALUES ($1, $2, $3, $4, $5, $6)" }, - "d79d608ceb862c15b741a6040044c6dd54a837a3a0c5594d15a6041c7bc68ea8": { + "df4428ccb891bd791824bcd7990550cc9651e1cfaab1db33833ff7959d113b2c": { "describe": { "columns": [], "nullable": [], "parameters": { - "Right": 3 + "Left": [ + "Text", + "Text" + ] } }, - "query": "INSERT OR IGNORE INTO tor (package, interface, key) VALUES (?, ?, ?)" + "query": "INSERT INTO certificates (id, priv_key_pem, certificate_pem, lookup_string, created_at, updated_at) VALUES (0, $1, $2, NULL, now(), now())" }, - "de2a5e90798d606047ab8180c044baac05469c0cdf151316bd58ee8c7196fdef": { + "e185203cf84e43b801dfb23b4159e34aeaef1154dcd3d6811ab504915497ccf7": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Left": [ + "Int4" + ] + } + }, + "query": "DELETE FROM notifications WHERE id = $1" + }, + "e25e53c45c5a494a45cdb4d145de507df6f322ac6706e71b86895f1c64195f41": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Left": [ + "Text" + ] + } + }, + "query": "UPDATE account SET password = $1" + }, + "e5843c5b0e7819b29aa1abf2266799bd4f82e761837b526a0972c3d4439a264d": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Left": [ + "Text", + "Text", + "Text" + ] + } + }, + "query": "INSERT INTO session (id, user_agent, metadata) VALUES ($1, $2, $3)" + }, + "e85749336fce4afaf16627bee8cfcb70be6f189ea7d1f05f9a26bead4be11839": { "describe": { "columns": [ { - "name": "fingerprint", + "name": "interface", "ordinal": 0, "type_info": "Text" }, { - "name": "openssh_pubkey", + "name": "key", "ordinal": 1, - "type_info": "Text" - }, - { - "name": "created_at", - "ordinal": 2, - "type_info": "Text" + "type_info": "Bytea" } ], "nullable": [ - false, false, false ], "parameters": { - "Right": 1 + "Left": [ + "Text" + ] } }, - "query": "SELECT * FROM ssh_keys WHERE fingerprint = ?" + "query": "SELECT interface, key FROM tor WHERE package = $1" + }, + "eb750adaa305bdbf3c5b70aaf59139c7b7569602adb58f2d6b3a94da4f167b0a": { + "describe": { + "columns": [], + "nullable": [], + "parameters": { + "Left": [ + "Int4" + ] + } + }, + "query": "DELETE FROM notifications WHERE id < $1" + }, + "ecc765d8205c0876956f95f76944ac6a5f34dd820c4073b7728c7067aab9fded": { + "describe": { + "columns": [ + { + "name": "id", + "ordinal": 0, + "type_info": "Int4" + } + ], + "nullable": [ + false + ], + "parameters": { + "Left": [ + "Text", + "Text", + "Text", + "Text" + ] + } + }, + "query": "INSERT INTO cifs_shares (hostname, path, username, password) VALUES ($1, $2, $3, $4) RETURNING id" }, "ed848affa5bf92997cd441e3a50b3616b6724df3884bd9d199b3225e0bea8a54": { "describe": { @@ -697,19 +788,23 @@ false ], "parameters": { - "Right": 0 + "Left": [] } }, "query": "SELECT priv_key_pem, certificate_pem FROM certificates WHERE id = 0;" }, - "f63c8c5a8754b34a49ef5d67802fa2b72aa409bbec92ecc6901492092974b71a": { + "f6d1c5ef0f9d9577bea8382318967b9deb46da75788c7fe6082b43821c22d556": { "describe": { "columns": [], "nullable": [], "parameters": { - "Right": 1 + "Left": [ + "Text", + "Text", + "Text" + ] } }, - "query": "DELETE FROM cifs_shares WHERE id = ?" + "query": "INSERT INTO ssh_keys (fingerprint, openssh_pubkey, created_at) VALUES ($1, $2, $3)" } } \ No newline at end of file diff --git a/backend/src/auth.rs b/backend/src/auth.rs index 000b35e32..462119098 100644 --- a/backend/src/auth.rs +++ b/backend/src/auth.rs @@ -10,7 +10,7 @@ use rpc_toolkit::command_helpers::prelude::{RequestParts, ResponseParts}; use rpc_toolkit::yajrc::RpcError; use serde::{Deserialize, Serialize}; use serde_json::Value; -use sqlx::{Executor, Sqlite}; +use sqlx::{Executor, Postgres}; use tracing::instrument; use crate::context::{CliContext, RpcContext}; @@ -87,7 +87,7 @@ pub fn check_password(hash: &str, password: &str) -> Result<(), Error> { pub async fn check_password_against_db(secrets: &mut Ex, password: &str) -> Result<(), Error> where - for<'a> &'a mut Ex: Executor<'a, Database = Sqlite>, + for<'a> &'a mut Ex: Executor<'a, Database = Postgres>, { let pw_hash = sqlx::query!("SELECT password FROM account") .fetch_one(secrets) @@ -124,7 +124,7 @@ pub async fn login( let metadata = serde_json::to_string(&metadata).with_kind(crate::ErrorKind::Database)?; let hash_token_hashed = hash_token.hashed(); sqlx::query!( - "INSERT INTO session (id, user_agent, metadata) VALUES (?, ?, ?)", + "INSERT INTO session (id, user_agent, metadata) VALUES ($1, $2, $3)", hash_token_hashed, user_agent, metadata, @@ -328,7 +328,7 @@ pub async fn set_password( password: &str, ) -> Result<(), Error> where - for<'a> &'a mut Ex: Executor<'a, Database = Sqlite>, + for<'a> &'a mut Ex: Executor<'a, Database = Postgres>, { let password = argon2::hash_encoded( password.as_bytes(), @@ -337,7 +337,7 @@ where ) .with_kind(crate::ErrorKind::PasswordHashGeneration)?; - sqlx::query!("UPDATE account SET password = ?", password,) + sqlx::query!("UPDATE account SET password = $1", password,) .execute(secrets) .await?; diff --git a/backend/src/backup/mod.rs b/backend/src/backup/mod.rs index 3cc3f5bd8..7e7a62b36 100644 --- a/backend/src/backup/mod.rs +++ b/backend/src/backup/mod.rs @@ -8,7 +8,7 @@ use patch_db::{DbHandle, HasModel, LockType}; use reqwest::Url; use rpc_toolkit::command; use serde::{Deserialize, Serialize}; -use sqlx::{Executor, Sqlite}; +use sqlx::{Executor, Postgres}; use tokio::fs::File; use tokio::io::AsyncWriteExt; use tracing::instrument; @@ -195,7 +195,7 @@ impl BackupActions { volumes: &Volumes, ) -> Result<(), Error> where - for<'a> &'a mut Ex: Executor<'a, Database = Sqlite>, + for<'a> &'a mut Ex: Executor<'a, Database = Postgres>, { let mut volumes = volumes.clone(); volumes.insert(VolumeId::Backup, Volume::Backup { readonly: true }); @@ -231,7 +231,7 @@ impl BackupActions { ) })?; sqlx::query!( - "REPLACE INTO tor (package, interface, key) VALUES (?, ?, ?)", + "INSERT INTO tor (package, interface, key) VALUES ($1, $2, $3) ON CONFLICT (package, interface) DO UPDATE SET key = $3", **pkg_id, *iface, key_vec, diff --git a/backend/src/backup/restore.rs b/backend/src/backup/restore.rs index 93bc8c96d..9390283ee 100644 --- a/backend/src/backup/restore.rs +++ b/backend/src/backup/restore.rs @@ -221,7 +221,7 @@ pub async fn recover_full_embassy( let key_vec = os_backup.tor_key.as_bytes().to_vec(); let secret_store = ctx.secret_store().await?; sqlx::query!( - "REPLACE INTO account (id, password, tor_key) VALUES (?, ?, ?)", + "INSERT INTO account (id, password, tor_key) VALUES ($1, $2, $3) ON CONFLICT (id) DO UPDATE SET password = $2, tor_key = $3", 0, password, key_vec, diff --git a/backend/src/backup/target/cifs.rs b/backend/src/backup/target/cifs.rs index 856c7baf6..3c683ad1f 100644 --- a/backend/src/backup/target/cifs.rs +++ b/backend/src/backup/target/cifs.rs @@ -4,7 +4,7 @@ use color_eyre::eyre::eyre; use futures::TryStreamExt; use rpc_toolkit::command; use serde::{Deserialize, Serialize}; -use sqlx::{Executor, Sqlite}; +use sqlx::{Executor, Postgres}; use super::{BackupTarget, BackupTargetId}; use crate::context::RpcContext; @@ -49,8 +49,8 @@ pub async fn add( let embassy_os = recovery_info(&guard).await?; guard.unmount().await?; let path_string = Path::new("/").join(&cifs.path).display().to_string(); - let id: u32 = sqlx::query!( - "INSERT INTO cifs_shares (hostname, path, username, password) VALUES (?, ?, ?, ?) RETURNING id AS \"id: u32\"", + let id: i32 = sqlx::query!( + "INSERT INTO cifs_shares (hostname, path, username, password) VALUES ($1, $2, $3, $4) RETURNING id", cifs.hostname, path_string, cifs.username, @@ -98,7 +98,7 @@ pub async fn update( guard.unmount().await?; let path_string = Path::new("/").join(&cifs.path).display().to_string(); if sqlx::query!( - "UPDATE cifs_shares SET hostname = ?, path = ?, username = ?, password = ? WHERE id = ?", + "UPDATE cifs_shares SET hostname = $1, path = $2, username = $3, password = $4 WHERE id = $5", cifs.hostname, path_string, cifs.username, @@ -137,7 +137,7 @@ pub async fn remove(#[context] ctx: RpcContext, #[arg] id: BackupTargetId) -> Re crate::ErrorKind::NotFound, )); }; - if sqlx::query!("DELETE FROM cifs_shares WHERE id = ?", id) + if sqlx::query!("DELETE FROM cifs_shares WHERE id = $1", id) .execute(&ctx.secret_store) .await? .rows_affected() @@ -151,12 +151,12 @@ pub async fn remove(#[context] ctx: RpcContext, #[arg] id: BackupTargetId) -> Re Ok(()) } -pub async fn load(secrets: &mut Ex, id: u32) -> Result +pub async fn load(secrets: &mut Ex, id: i32) -> Result where - for<'a> &'a mut Ex: Executor<'a, Database = Sqlite>, + for<'a> &'a mut Ex: Executor<'a, Database = Postgres>, { let record = sqlx::query!( - "SELECT hostname, path, username, password FROM cifs_shares WHERE id = ?", + "SELECT hostname, path, username, password FROM cifs_shares WHERE id = $1", id ) .fetch_one(secrets) @@ -170,14 +170,13 @@ where }) } -pub async fn list(secrets: &mut Ex) -> Result, Error> +pub async fn list(secrets: &mut Ex) -> Result, Error> where - for<'a> &'a mut Ex: Executor<'a, Database = Sqlite>, + for<'a> &'a mut Ex: Executor<'a, Database = Postgres>, { - let mut records = sqlx::query!( - "SELECT id AS \"id: u32\", hostname, path, username, password FROM cifs_shares" - ) - .fetch_many(secrets); + let mut records = + sqlx::query!("SELECT id, hostname, path, username, password FROM cifs_shares") + .fetch_many(secrets); let mut cifs = Vec::new(); while let Some(query_result) = records.try_next().await? { diff --git a/backend/src/backup/target/mod.rs b/backend/src/backup/target/mod.rs index b1f53f51f..6a9c7a9e1 100644 --- a/backend/src/backup/target/mod.rs +++ b/backend/src/backup/target/mod.rs @@ -10,7 +10,7 @@ use digest::OutputSizeUser; use rpc_toolkit::command; use serde::{Deserialize, Serialize}; use sha2::Sha256; -use sqlx::{Executor, Sqlite}; +use sqlx::{Executor, Postgres}; use tracing::instrument; use self::cifs::CifsBackupTarget; @@ -45,12 +45,12 @@ pub enum BackupTarget { #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] pub enum BackupTargetId { Disk { logicalname: PathBuf }, - Cifs { id: u32 }, + Cifs { id: i32 }, } impl BackupTargetId { pub async fn load(self, secrets: &mut Ex) -> Result where - for<'a> &'a mut Ex: Executor<'a, Database = Sqlite>, + for<'a> &'a mut Ex: Executor<'a, Database = Postgres>, { Ok(match self { BackupTargetId::Disk { logicalname } => { diff --git a/backend/src/config/spec.rs b/backend/src/config/spec.rs index 6156ea802..31228572e 100644 --- a/backend/src/config/spec.rs +++ b/backend/src/config/spec.rs @@ -18,7 +18,7 @@ use regex::Regex; use serde::de::{MapAccess, Visitor}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde_json::{Number, Value}; -use sqlx::SqlitePool; +use sqlx::PgPool; use super::util::{self, CharSet, NumRange, UniqueBy, STATIC_NULL}; use super::{Config, MatchError, NoMatchWithPath, TimeoutError, TypeOf}; @@ -2050,7 +2050,7 @@ impl TorKeyPointer { async fn deref( &self, source_package: &PackageId, - secrets: &SqlitePool, + secrets: &PgPool, ) -> Result { if &self.package_id != source_package { return Err(ConfigurationError::PermissionDenied( @@ -2058,7 +2058,7 @@ impl TorKeyPointer { )); } let x = sqlx::query!( - "SELECT key FROM tor WHERE package = ? AND interface = ?", + "SELECT key FROM tor WHERE package = $1 AND interface = $2", *self.package_id, *self.interface ) diff --git a/backend/src/context/rpc.rs b/backend/src/context/rpc.rs index 508cdd4a3..208d8f4e2 100644 --- a/backend/src/context/rpc.rs +++ b/backend/src/context/rpc.rs @@ -4,7 +4,6 @@ use std::ops::Deref; use std::path::{Path, PathBuf}; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; -use std::time::Duration; use bollard::Docker; use helpers::to_tmp_path; @@ -14,8 +13,8 @@ use reqwest::Url; use rpc_toolkit::url::Host; use rpc_toolkit::Context; use serde::Deserialize; -use sqlx::sqlite::SqliteConnectOptions; -use sqlx::SqlitePool; +use sqlx::postgres::PgConnectOptions; +use sqlx::PgPool; use tokio::fs::File; use tokio::process::Command; use tokio::sync::{broadcast, oneshot, Mutex, RwLock}; @@ -24,6 +23,7 @@ use tracing::instrument; use crate::core::rpc_continuations::{RequestGuid, RestHandler, RpcContinuation}; use crate::db::model::{Database, InstalledPackageDataEntry, PackageDataEntry}; use crate::hostname::{derive_hostname, derive_id, get_product_key}; +use crate::init::{init_postgres, pgloader}; use crate::install::cleanup::{cleanup_failed, uninstall, CleanupFailedReceipts}; use crate::manager::ManagerMap; use crate::middleware::auth::HashSessionToken; @@ -71,7 +71,7 @@ impl RpcContextConfig { .as_deref() .unwrap_or_else(|| Path::new("/embassy-data")) } - pub async fn db(&self, secret_store: &SqlitePool, product_key: &str) -> Result { + pub async fn db(&self, secret_store: &PgPool, product_key: &str) -> Result { let sid = derive_id(product_key); let hostname = derive_hostname(&sid); let db_path = self.datadir().join("main").join("embassy.db"); @@ -94,18 +94,19 @@ impl RpcContextConfig { Ok(db) } #[instrument] - pub async fn secret_store(&self) -> Result { - let secret_store = SqlitePool::connect_with( - SqliteConnectOptions::new() - .filename(self.datadir().join("main").join("secrets.db")) - .create_if_missing(true) - .busy_timeout(Duration::from_secs(30)), - ) - .await?; + pub async fn secret_store(&self) -> Result { + init_postgres(self.datadir()).await?; + let secret_store = + PgPool::connect_with(PgConnectOptions::new().database("secrets").username("root")) + .await?; sqlx::migrate!() .run(&secret_store) .await .with_kind(crate::ErrorKind::Database)?; + let old_db_path = self.datadir().join("main/secrets.db"); + if tokio::fs::metadata(&old_db_path).await.is_ok() { + pgloader(&old_db_path).await?; + } Ok(secret_store) } } @@ -118,7 +119,7 @@ pub struct RpcContextSeed { pub datadir: PathBuf, pub disk_guid: Arc, pub db: PatchDb, - pub secret_store: SqlitePool, + pub secret_store: PgPool, pub docker: Docker, pub net_controller: NetController, pub managers: ManagerMap, @@ -214,7 +215,7 @@ impl RpcContext { ))); let (shutdown, _) = tokio::sync::broadcast::channel(1); let secret_store = base.secret_store().await?; - tracing::info!("Opened Sqlite DB"); + tracing::info!("Opened Pg DB"); let db = base.db(&secret_store, &get_product_key().await?).await?; tracing::info!("Opened PatchDB"); let docker = Docker::connect_with_unix_defaults()?; diff --git a/backend/src/context/setup.rs b/backend/src/context/setup.rs index 8fd118783..14eeab11b 100644 --- a/backend/src/context/setup.rs +++ b/backend/src/context/setup.rs @@ -9,9 +9,10 @@ use patch_db::PatchDb; use rpc_toolkit::yajrc::RpcError; use rpc_toolkit::Context; use serde::{Deserialize, Serialize}; -use sqlx::sqlite::SqliteConnectOptions; -use sqlx::SqlitePool; +use sqlx::postgres::PgConnectOptions; +use sqlx::PgPool; use tokio::fs::File; +use tokio::process::Command; use tokio::sync::broadcast::Sender; use tokio::sync::RwLock; use tracing::instrument; @@ -19,10 +20,11 @@ use url::Host; use crate::db::model::Database; use crate::hostname::{derive_hostname, derive_id, get_product_key}; +use crate::init::{init_postgres, pgloader}; use crate::net::tor::os_key; use crate::setup::{password_hash, RecoveryStatus}; use crate::util::io::from_yaml_async_reader; -use crate::util::AsyncFileExt; +use crate::util::{AsyncFileExt, Invoke}; use crate::{Error, ResultExt}; #[derive(Clone, Serialize, Deserialize)] @@ -93,7 +95,7 @@ impl SetupContext { }))) } #[instrument(skip(self))] - pub async fn db(&self, secret_store: &SqlitePool) -> Result { + pub async fn db(&self, secret_store: &PgPool) -> Result { let db_path = self.datadir.join("main").join("embassy.db"); let db = PatchDb::open(&db_path) .await @@ -117,18 +119,19 @@ impl SetupContext { Ok(db) } #[instrument(skip(self))] - pub async fn secret_store(&self) -> Result { - let secret_store = SqlitePool::connect_with( - SqliteConnectOptions::new() - .filename(self.datadir.join("main").join("secrets.db")) - .create_if_missing(true) - .busy_timeout(Duration::from_secs(30)), - ) - .await?; + pub async fn secret_store(&self) -> Result { + init_postgres(&self.datadir).await?; + let secret_store = + PgPool::connect_with(PgConnectOptions::new().database("secrets").username("root")) + .await?; sqlx::migrate!() .run(&secret_store) .await .with_kind(crate::ErrorKind::Database)?; + let old_db_path = self.datadir.join("main/secrets.db"); + if tokio::fs::metadata(&old_db_path).await.is_ok() { + pgloader(&old_db_path).await?; + } Ok(secret_store) } #[instrument(skip(self))] diff --git a/backend/src/disk/mount/util.rs b/backend/src/disk/mount/util.rs index a9ebc69f1..f1194c53c 100644 --- a/backend/src/disk/mount/util.rs +++ b/backend/src/disk/mount/util.rs @@ -48,13 +48,15 @@ pub async fn unmount>(mountpoint: P) -> Result<(), Error> { .arg(mountpoint.as_ref()) .invoke(crate::ErrorKind::Filesystem) .await?; - tokio::fs::remove_dir_all(mountpoint.as_ref()) - .await - .with_ctx(|_| { - ( - crate::ErrorKind::Filesystem, - format!("rm {}", mountpoint.as_ref().display()), - ) - })?; + match tokio::fs::remove_dir(mountpoint.as_ref()).await { + Err(e) if e.raw_os_error() == Some(39) => Ok(()), // directory not empty + a => a, + } + .with_ctx(|_| { + ( + crate::ErrorKind::Filesystem, + format!("rm {}", mountpoint.as_ref().display()), + ) + })?; Ok(()) } diff --git a/backend/src/init.rs b/backend/src/init.rs index 5c61311d3..a5d12c411 100644 --- a/backend/src/init.rs +++ b/backend/src/init.rs @@ -1,10 +1,14 @@ +use std::path::Path; +use std::process::Stdio; use std::time::Duration; +use color_eyre::eyre::eyre; use patch_db::{DbHandle, LockReceipt, LockType}; use tokio::process::Command; use crate::context::rpc::RpcContextConfig; use crate::db::model::ServerStatus; +use crate::disk::mount::util::unmount; use crate::install::PKG_DOCKER_DIR; use crate::util::Invoke; use crate::Error; @@ -67,10 +71,91 @@ impl InitReceipts { } } +pub async fn pgloader(old_db_path: impl AsRef) -> Result<(), Error> { + tokio::fs::write( + "/etc/embassy/migrate.load", + format!( + include_str!("migrate.load"), + sqlite_path = old_db_path.as_ref().display() + ), + ) + .await?; + tracing::info!("Running pgloader"); + let out = Command::new("pgloader") + .arg("-v") + .arg("/etc/embassy/migrate.load") + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .output() + .await?; + let stdout = String::from_utf8(out.stdout)?; + for line in stdout.lines() { + tracing::debug!("pgloader: {}", line); + } + let stderr = String::from_utf8(out.stderr)?; + for line in stderr.lines() { + tracing::debug!("pgloader err: {}", line); + } + tracing::debug!("pgloader exited with code {:?}", out.status); + if let Some(err) = stdout.lines().chain(stderr.lines()).find_map(|l| { + if l.split_ascii_whitespace() + .any(|word| word == "ERROR" || word == "FATAL") + { + Some(l) + } else { + None + } + }) { + return Err(Error::new( + eyre!("pgloader error: {}", err), + crate::ErrorKind::Database, + )); + } + tokio::fs::rename( + old_db_path.as_ref(), + old_db_path.as_ref().with_extension("bak"), + ) + .await?; + Ok(()) +} + +// must be idempotent +pub async fn init_postgres(datadir: impl AsRef) -> Result<(), Error> { + let db_dir = datadir.as_ref().join("main/postgresql"); + let is_mountpoint = || async { + Ok::<_, Error>( + tokio::process::Command::new("mountpoint") + .arg("/var/lib/postgresql") + .stdout(std::process::Stdio::null()) + .stderr(std::process::Stdio::null()) + .status() + .await? + .success(), + ) + }; + if tokio::fs::metadata(&db_dir).await.is_err() { + Command::new("cp") + .arg("-ra") + .arg("/var/lib/postgresql") + .arg(&db_dir) + .invoke(crate::ErrorKind::Filesystem) + .await?; + } + if !is_mountpoint().await? { + crate::disk::mount::util::bind(&db_dir, "/var/lib/postgresql", false).await?; + } + Command::new("systemctl") + .arg("start") + .arg("postgresql") + .invoke(crate::ErrorKind::Database) + .await?; + Ok(()) +} + pub async fn init(cfg: &RpcContextConfig, product_key: &str) -> Result<(), Error> { let should_rebuild = tokio::fs::metadata(SYSTEM_REBUILD_PATH).await.is_ok(); let secret_store = cfg.secret_store().await?; - let log_dir = cfg.datadir().join("main").join("logs"); + let log_dir = cfg.datadir().join("main/logs"); if tokio::fs::metadata(&log_dir).await.is_err() { tokio::fs::create_dir_all(&log_dir).await?; } @@ -92,7 +177,7 @@ pub async fn init(cfg: &RpcContextConfig, product_key: &str) -> Result<(), Error tokio::fs::remove_dir_all(&tmp_docker).await?; } Command::new("cp") - .arg("-r") + .arg("-ra") .arg("/var/lib/docker") .arg(&tmp_docker) .invoke(crate::ErrorKind::Filesystem) diff --git a/backend/src/install/cleanup.rs b/backend/src/install/cleanup.rs index 10ff1562a..b61766854 100644 --- a/backend/src/install/cleanup.rs +++ b/backend/src/install/cleanup.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use bollard::image::ListImagesOptions; use patch_db::{DbHandle, LockReceipt, LockTargetId, LockType, PatchDbHandle, Verifier}; -use sqlx::{Executor, Sqlite}; +use sqlx::{Executor, Postgres}; use tracing::instrument; use super::{PKG_ARCHIVE_DIR, PKG_DOCKER_DIR}; @@ -337,7 +337,7 @@ pub async fn uninstall( id: &PackageId, ) -> Result<(), Error> where - for<'a> &'a mut Ex: Executor<'a, Database = Sqlite>, + for<'a> &'a mut Ex: Executor<'a, Database = Postgres>, { let mut tx = db.begin().await?; let receipts = UninstallReceipts::new(&mut tx, id).await?; @@ -383,10 +383,10 @@ where #[instrument(skip(secrets))] pub async fn remove_tor_keys(secrets: &mut Ex, id: &PackageId) -> Result<(), Error> where - for<'a> &'a mut Ex: Executor<'a, Database = Sqlite>, + for<'a> &'a mut Ex: Executor<'a, Database = Postgres>, { let id_str = id.as_str(); - sqlx::query!("DELETE FROM tor WHERE package = ?", id_str) + sqlx::query!("DELETE FROM tor WHERE package = $1", id_str) .execute(secrets) .await?; Ok(()) diff --git a/backend/src/lib.rs b/backend/src/lib.rs index cef4164bb..714a7fd30 100644 --- a/backend/src/lib.rs +++ b/backend/src/lib.rs @@ -1,4 +1,4 @@ -pub const DEFAULT_MARKETPLACE: &str = "https://marketplace.start9.com"; +pub const DEFAULT_MARKETPLACE: &str = "https://registry.start9.com"; pub const BUFFER_SIZE: usize = 1024; pub const HOST_IP: [u8; 4] = [172, 18, 0, 1]; pub const TARGET: &str = current_platform::CURRENT_PLATFORM; diff --git a/backend/src/manager/mod.rs b/backend/src/manager/mod.rs index 282830218..017fec6e3 100644 --- a/backend/src/manager/mod.rs +++ b/backend/src/manager/mod.rs @@ -12,7 +12,7 @@ use color_eyre::eyre::eyre; use nix::sys::signal::Signal; use num_enum::TryFromPrimitive; use patch_db::DbHandle; -use sqlx::{Executor, Sqlite}; +use sqlx::{Executor, Postgres}; use tokio::sync::watch::error::RecvError; use tokio::sync::watch::{channel, Receiver, Sender}; use tokio::sync::{Notify, RwLock}; @@ -47,7 +47,7 @@ impl ManagerMap { secrets: &mut Ex, ) -> Result<(), Error> where - for<'a> &'a mut Ex: Executor<'a, Database = Sqlite>, + for<'a> &'a mut Ex: Executor<'a, Database = Postgres>, { let mut res = BTreeMap::new(); for package in crate::db::DatabaseModel::new() diff --git a/backend/src/middleware/auth.rs b/backend/src/middleware/auth.rs index c8c16635d..83fed8eb8 100644 --- a/backend/src/middleware/auth.rs +++ b/backend/src/middleware/auth.rs @@ -41,7 +41,7 @@ impl HasLoggedOutSessions { for session in logged_out_sessions { let session = session.as_logout_session_id(); sqlx::query!( - "UPDATE session SET logged_out = CURRENT_TIMESTAMP WHERE id = ?", + "UPDATE session SET logged_out = CURRENT_TIMESTAMP WHERE id = $1", session ) .execute(&mut sqlx_conn) @@ -68,7 +68,7 @@ impl HasValidSession { pub async fn from_session(session: &HashSessionToken, ctx: &RpcContext) -> Result { let session_hash = session.hashed(); - let session = sqlx::query!("UPDATE session SET last_active = CURRENT_TIMESTAMP WHERE id = ? AND logged_out IS NULL OR logged_out > CURRENT_TIMESTAMP", session_hash) + let session = sqlx::query!("UPDATE session SET last_active = CURRENT_TIMESTAMP WHERE id = $1 AND logged_out IS NULL OR logged_out > CURRENT_TIMESTAMP", session_hash) .execute(&mut ctx.secret_store.acquire().await?) .await?; if session.rows_affected() == 0 { diff --git a/backend/src/migrate.load b/backend/src/migrate.load new file mode 100644 index 000000000..e36d9698a --- /dev/null +++ b/backend/src/migrate.load @@ -0,0 +1,7 @@ +load database + from sqlite://{sqlite_path} + into postgresql://root@unix:/var/run/postgresql:5432/secrets + + with include no drop, truncate, reset sequences, data only + + excluding table names like '_sqlx_migrations'; diff --git a/backend/src/net/interface.rs b/backend/src/net/interface.rs index 371fc4cdf..e59d4c2ce 100644 --- a/backend/src/net/interface.rs +++ b/backend/src/net/interface.rs @@ -6,7 +6,7 @@ use indexmap::IndexSet; use itertools::Either; pub use models::InterfaceId; use serde::{Deserialize, Deserializer, Serialize}; -use sqlx::{Executor, Sqlite}; +use sqlx::{Executor, Postgres}; use torut::onion::TorSecretKeyV3; use tracing::instrument; @@ -39,7 +39,7 @@ impl Interfaces { package_id: &PackageId, ) -> Result where - for<'a> &'a mut Ex: Executor<'a, Database = Sqlite>, + for<'a> &'a mut Ex: Executor<'a, Database = Postgres>, { let mut interface_addresses = InterfaceAddressMap(BTreeMap::new()); for (id, iface) in &self.0 { @@ -51,7 +51,7 @@ impl Interfaces { let key = TorSecretKeyV3::generate(); let key_vec = key.as_bytes().to_vec(); sqlx::query!( - "INSERT OR IGNORE INTO tor (package, interface, key) VALUES (?, ?, ?)", + "INSERT INTO tor (package, interface, key) VALUES ($1, $2, $3) ON CONFLICT (package, interface) DO NOTHING", **package_id, **id, key_vec, @@ -59,7 +59,7 @@ impl Interfaces { .execute(&mut *secrets) .await?; let key_row = sqlx::query!( - "SELECT key FROM tor WHERE package = ? AND interface = ?", + "SELECT key FROM tor WHERE package = $1 AND interface = $2", **package_id, **id, ) @@ -89,10 +89,10 @@ impl Interfaces { package_id: &PackageId, ) -> Result, Error> where - for<'a> &'a mut Ex: Executor<'a, Database = Sqlite>, + for<'a> &'a mut Ex: Executor<'a, Database = Postgres>, { Ok(sqlx::query!( - "SELECT interface, key FROM tor WHERE package = ?", + "SELECT interface, key FROM tor WHERE package = $1", **package_id ) .fetch_many(secrets) diff --git a/backend/src/net/mod.rs b/backend/src/net/mod.rs index 793111173..112823157 100644 --- a/backend/src/net/mod.rs +++ b/backend/src/net/mod.rs @@ -4,7 +4,7 @@ use std::path::PathBuf; use openssl::pkey::{PKey, Private}; use openssl::x509::X509; use rpc_toolkit::command; -use sqlx::SqlitePool; +use sqlx::PgPool; use torut::onion::{OnionAddressV3, TorSecretKeyV3}; use tracing::instrument; @@ -56,7 +56,7 @@ impl NetController { embassyd_tor_key: TorSecretKeyV3, tor_control: SocketAddr, dns_bind: &[SocketAddr], - db: SqlitePool, + db: PgPool, import_root_ca: Option<(PKey, X509)>, ) -> Result { let ssl = match import_root_ca { diff --git a/backend/src/net/ssl.rs b/backend/src/net/ssl.rs index b2c0a1fd1..3a56399da 100644 --- a/backend/src/net/ssl.rs +++ b/backend/src/net/ssl.rs @@ -11,7 +11,7 @@ use openssl::nid::Nid; use openssl::pkey::{PKey, Private}; use openssl::x509::{X509Builder, X509Extension, X509NameBuilder, X509}; use openssl::*; -use sqlx::SqlitePool; +use sqlx::PgPool; use tokio::process::Command; use tokio::sync::Mutex; use tracing::instrument; @@ -33,17 +33,17 @@ pub struct SslManager { #[derive(Debug)] struct SslStore { - secret_store: SqlitePool, + secret_store: PgPool, } impl SslStore { - fn new(db: SqlitePool) -> Result { + fn new(db: PgPool) -> Result { Ok(SslStore { secret_store: db }) } #[instrument(skip(self))] async fn save_root_certificate(&self, key: &PKey, cert: &X509) -> Result<(), Error> { let key_str = String::from_utf8(key.private_key_to_pem_pkcs8()?)?; let cert_str = String::from_utf8(cert.to_pem()?)?; - let _n = sqlx::query!("INSERT INTO certificates (id, priv_key_pem, certificate_pem, lookup_string, created_at, updated_at) VALUES (0, ?, ?, NULL, datetime('now'), datetime('now'))", key_str, cert_str).execute(&self.secret_store).await?; + let _n = sqlx::query!("INSERT INTO certificates (id, priv_key_pem, certificate_pem, lookup_string, created_at, updated_at) VALUES (0, $1, $2, NULL, now(), now())", key_str, cert_str).execute(&self.secret_store).await?; Ok(()) } #[instrument(skip(self))] @@ -69,7 +69,7 @@ impl SslStore { ) -> Result<(), Error> { let key_str = String::from_utf8(key.private_key_to_pem_pkcs8()?)?; let cert_str = String::from_utf8(cert.to_pem()?)?; - let _n = sqlx::query!("INSERT INTO certificates (id, priv_key_pem, certificate_pem, lookup_string, created_at, updated_at) VALUES (1, ?, ?, NULL, datetime('now'), datetime('now'))", key_str, cert_str).execute(&self.secret_store).await?; + let _n = sqlx::query!("INSERT INTO certificates (id, priv_key_pem, certificate_pem, lookup_string, created_at, updated_at) VALUES (1, $1, $2, NULL, now(), now())", key_str, cert_str).execute(&self.secret_store).await?; Ok(()) } async fn load_intermediate_certificate(&self) -> Result, X509)>, Error> { @@ -108,7 +108,7 @@ impl SslStore { ) -> Result<(), Error> { let key_str = String::from_utf8(key.private_key_to_pem_pkcs8()?)?; let cert_str = String::from_utf8(cert.to_pem()?)?; - let _n = sqlx::query!("INSERT INTO certificates (priv_key_pem, certificate_pem, lookup_string, created_at, updated_at) VALUES (?, ?, ?, datetime('now'), datetime('now'))", key_str, cert_str, lookup_string).execute(&self.secret_store).await?; + let _n = sqlx::query!("INSERT INTO certificates (priv_key_pem, certificate_pem, lookup_string, created_at, updated_at) VALUES ($1, $2, $3, now(), now())", key_str, cert_str, lookup_string).execute(&self.secret_store).await?; Ok(()) } async fn load_certificate( @@ -116,7 +116,7 @@ impl SslStore { lookup_string: &str, ) -> Result, X509)>, Error> { let m_row = sqlx::query!( - "SELECT priv_key_pem, certificate_pem FROM certificates WHERE lookup_string = ?", + "SELECT priv_key_pem, certificate_pem FROM certificates WHERE lookup_string = $1", lookup_string ) .fetch_optional(&self.secret_store) @@ -139,7 +139,8 @@ impl SslStore { ) -> Result<(), Error> { let key_str = String::from_utf8(key.private_key_to_pem_pkcs8()?)?; let cert_str = String::from_utf8(cert.to_pem()?)?; - let n = sqlx::query!("UPDATE certificates SET priv_key_pem = ?, certificate_pem = ?, updated_at = datetime('now') WHERE lookup_string = ?", key_str, cert_str, lookup_string).execute(&self.secret_store).await?; + let n = sqlx::query!("UPDATE certificates SET priv_key_pem = $1, certificate_pem = $2, updated_at = now() WHERE lookup_string = $3", key_str, cert_str, lookup_string) + .execute(&self.secret_store).await?; if n.rows_affected() == 0 { return Err(Error::new( eyre!( @@ -161,7 +162,7 @@ lazy_static::lazy_static! { impl SslManager { #[instrument(skip(db))] - pub async fn init(db: SqlitePool) -> Result { + pub async fn init(db: PgPool) -> Result { let store = SslStore::new(db)?; let (root_key, root_cert) = match store.load_root_certificate().await? { None => { @@ -204,6 +205,10 @@ impl SslManager { } Some((key, cert)) => Ok((key, cert)), }?; + + sqlx::query!("SELECT setval('certificates_id_seq', GREATEST(MAX(id) + 1, nextval('certificates_id_seq') - 1)) FROM certificates") + .fetch_one(&store.secret_store).await?; + Ok(SslManager { store, root_cert, @@ -221,7 +226,7 @@ impl SslManager { // the intermediate certificate #[instrument(skip(db))] pub async fn import_root_ca( - db: SqlitePool, + db: PgPool, root_key: PKey, root_cert: X509, ) -> Result { @@ -508,10 +513,11 @@ fn make_leaf_cert( #[tokio::test] async fn ca_details_persist() -> Result<(), Error> { - let pool = sqlx::Pool::::connect("sqlite::memory:").await?; - sqlx::query_file!("migrations/20210629193146_Init.sql") - .execute(&pool) - .await?; + let pool = sqlx::Pool::::connect("postgres::memory:").await?; + sqlx::migrate!() + .run(&pool) + .await + .with_kind(crate::ErrorKind::Database)?; let mgr = SslManager::init(pool.clone()).await?; let root_cert0 = mgr.root_cert; let int_key0 = mgr.int_key; @@ -532,10 +538,11 @@ async fn ca_details_persist() -> Result<(), Error> { #[tokio::test] async fn certificate_details_persist() -> Result<(), Error> { - let pool = sqlx::Pool::::connect("sqlite::memory:").await?; - sqlx::query_file!("migrations/20210629193146_Init.sql") - .execute(&pool) - .await?; + let pool = sqlx::Pool::::connect("postgres::memory:").await?; + sqlx::migrate!() + .run(&pool) + .await + .with_kind(crate::ErrorKind::Database)?; let mgr = SslManager::init(pool.clone()).await?; let package_id = "bitcoind".parse().unwrap(); let (key0, cert_chain0) = mgr.certificate_for("start9", &package_id).await?; diff --git a/backend/src/net/tor.rs b/backend/src/net/tor.rs index a37ce4649..ae66d3004 100644 --- a/backend/src/net/tor.rs +++ b/backend/src/net/tor.rs @@ -9,7 +9,7 @@ use futures::FutureExt; use reqwest::Client; use rpc_toolkit::command; use serde_json::json; -use sqlx::{Executor, Sqlite}; +use sqlx::{Executor, Postgres}; use tokio::net::TcpStream; use tokio::sync::Mutex; use torut::control::{AsyncEvent, AuthenticatedConn, ConnError}; @@ -60,7 +60,7 @@ pub async fn list_services( #[instrument(skip(secrets))] pub async fn os_key(secrets: &mut Ex) -> Result where - for<'a> &'a mut Ex: Executor<'a, Database = Sqlite>, + for<'a> &'a mut Ex: Executor<'a, Database = Postgres>, { let key = sqlx::query!("SELECT tor_key FROM account") .fetch_one(secrets) diff --git a/backend/src/notifications.rs b/backend/src/notifications.rs index 07a633259..a239da0e5 100644 --- a/backend/src/notifications.rs +++ b/backend/src/notifications.rs @@ -6,7 +6,7 @@ use chrono::{DateTime, Utc}; use color_eyre::eyre::eyre; use patch_db::{DbHandle, LockType}; use rpc_toolkit::command; -use sqlx::SqlitePool; +use sqlx::PgPool; use tokio::sync::Mutex; use tracing::instrument; @@ -27,7 +27,7 @@ pub async fn notification() -> Result<(), Error> { #[instrument(skip(ctx))] pub async fn list( #[context] ctx: RpcContext, - #[arg] before: Option, + #[arg] before: Option, #[arg] limit: Option, ) -> Result>, Error> { let limit = limit.unwrap_or(40); @@ -39,8 +39,8 @@ pub async fn list( .unread_notification_count(); model.lock(&mut handle, LockType::Write).await?; let records = sqlx::query!( - "SELECT id, package_id, created_at, code, level, title, message, data FROM notifications ORDER BY id DESC LIMIT ?", - limit + "SELECT id, package_id, created_at, code, level, title, message, data FROM notifications ORDER BY id DESC LIMIT $1", + limit as i64 ).fetch_all(&ctx.secret_store).await?; let notifs = records .into_iter() @@ -80,9 +80,9 @@ pub async fn list( } Some(before) => { let records = sqlx::query!( - "SELECT id, package_id, created_at, code, level, title, message, data FROM notifications WHERE id < ? ORDER BY id DESC LIMIT ?", + "SELECT id, package_id, created_at, code, level, title, message, data FROM notifications WHERE id < $1 ORDER BY id DESC LIMIT $2", before, - limit + limit as i64 ).fetch_all(&ctx.secret_store).await?; let res = records .into_iter() @@ -122,16 +122,16 @@ pub async fn list( } #[command(display(display_none))] -pub async fn delete(#[context] ctx: RpcContext, #[arg] id: u32) -> Result<(), Error> { - sqlx::query!("DELETE FROM notifications WHERE id = ?", id) +pub async fn delete(#[context] ctx: RpcContext, #[arg] id: i32) -> Result<(), Error> { + sqlx::query!("DELETE FROM notifications WHERE id = $1", id) .execute(&ctx.secret_store) .await?; Ok(()) } #[command(rename = "delete-before", display(display_none))] -pub async fn delete_before(#[context] ctx: RpcContext, #[arg] before: u32) -> Result<(), Error> { - sqlx::query!("DELETE FROM notifications WHERE id < ?", before) +pub async fn delete_before(#[context] ctx: RpcContext, #[arg] before: i32) -> Result<(), Error> { + sqlx::query!("DELETE FROM notifications WHERE id < $1", before) .execute(&ctx.secret_store) .await?; Ok(()) @@ -218,22 +218,22 @@ pub struct Notification { pub trait NotificationType: serde::Serialize + for<'de> serde::Deserialize<'de> + std::fmt::Debug { - const CODE: u32; + const CODE: i32; } impl NotificationType for () { - const CODE: u32 = 0; + const CODE: i32 = 0; } impl NotificationType for BackupReport { - const CODE: u32 = 1; + const CODE: i32 = 1; } pub struct NotificationManager { - sqlite: SqlitePool, + sqlite: PgPool, cache: Mutex, NotificationLevel, String), i64>>, } impl NotificationManager { - pub fn new(sqlite: SqlitePool) -> Self { + pub fn new(sqlite: PgPool) -> Self { NotificationManager { sqlite, cache: Mutex::new(HashMap::new()), @@ -267,9 +267,9 @@ impl NotificationManager { let sql_data = serde_json::to_string(&subtype).with_kind(crate::ErrorKind::Serialization)?; sqlx::query!( - "INSERT INTO notifications (package_id, code, level, title, message, data) VALUES (?, ?, ?, ?, ?, ?)", + "INSERT INTO notifications (package_id, code, level, title, message, data) VALUES ($1, $2, $3, $4, $5, $6)", sql_package_id, - sql_code, + sql_code as i32, sql_level, title, message, diff --git a/backend/src/setup.rs b/backend/src/setup.rs index 135188a30..4ad8c7da2 100644 --- a/backend/src/setup.rs +++ b/backend/src/setup.rs @@ -17,7 +17,7 @@ use rpc_toolkit::command; use rpc_toolkit::yajrc::RpcError; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; -use sqlx::{Connection, Executor, Sqlite}; +use sqlx::{Connection, Executor, Postgres}; use tokio::fs::File; use tokio::io::AsyncWriteExt; use torut::onion::{OnionAddressV3, TorSecretKeyV3}; @@ -52,7 +52,7 @@ use crate::{ensure_code, Error, ErrorKind, ResultExt}; #[instrument(skip(secrets))] pub async fn password_hash(secrets: &mut Ex) -> Result where - for<'a> &'a mut Ex: Executor<'a, Database = Sqlite>, + for<'a> &'a mut Ex: Executor<'a, Database = Postgres>, { let password = sqlx::query!("SELECT password FROM account") .fetch_one(secrets) @@ -448,7 +448,7 @@ async fn fresh_setup( let key_vec = tor_key.as_bytes().to_vec(); let sqlite_pool = ctx.secret_store().await?; sqlx::query!( - "REPLACE INTO account (id, password, tor_key) VALUES (?, ?, ?)", + "INSERT INTO account (id, password, tor_key) VALUES ($1, $2, $3) ON CONFLICT (id) DO UPDATE SET password = $2, tor_key = $3", 0, password, key_vec, @@ -696,7 +696,7 @@ async fn recover_v2( .with_kind(crate::ErrorKind::PasswordHashGeneration)?; let sqlite_pool = ctx.secret_store().await?; sqlx::query!( - "REPLACE INTO account (id, password, tor_key) VALUES (?, ?, ?)", + "INSERT INTO account (id, password, tor_key) VALUES ($1, $2, $3) ON CONFLICT (id) DO UPDATE SET password = $2, tor_key = $3", 0, password, key_vec @@ -790,7 +790,7 @@ async fn recover_v2( ); let key_vec = key_vec[32..].to_vec(); sqlx::query!( - "REPLACE INTO tor (package, interface, key) VALUES (?, 'main', ?)", + "INSERT INTO tor (package, interface, key) VALUES ($1, 'main', $2) ON CONFLICT (package, interface) DO UPDATE SET key = $2", *dst_id, key_vec, ) diff --git a/backend/src/ssh.rs b/backend/src/ssh.rs index 02294a90b..c9b02a88f 100644 --- a/backend/src/ssh.rs +++ b/backend/src/ssh.rs @@ -4,7 +4,7 @@ use chrono::Utc; use clap::ArgMatches; use color_eyre::eyre::eyre; use rpc_toolkit::command; -use sqlx::{Pool, Sqlite}; +use sqlx::{Pool, Postgres}; use tracing::instrument; use crate::context::RpcContext; @@ -61,7 +61,7 @@ pub async fn add(#[context] ctx: RpcContext, #[arg] key: PubKey) -> Result Result R let pool = &ctx.secret_store; // check if fingerprint is in DB // if in DB, remove it from DB - let n = sqlx::query!("DELETE FROM ssh_keys WHERE fingerprint = ?", fingerprint) + let n = sqlx::query!("DELETE FROM ssh_keys WHERE fingerprint = $1", fingerprint) .execute(pool) .await? .rows_affected(); @@ -172,7 +172,10 @@ pub async fn list( } #[instrument(skip(pool, dest))] -pub async fn sync_keys_from_db>(pool: &Pool, dest: P) -> Result<(), Error> { +pub async fn sync_keys_from_db>( + pool: &Pool, + dest: P, +) -> Result<(), Error> { let dest = dest.as_ref(); let keys = sqlx::query!("SELECT openssh_pubkey FROM ssh_keys") .fetch_all(pool) diff --git a/backend/src/system.rs b/backend/src/system.rs index 0e5fb4910..43d048a64 100644 --- a/backend/src/system.rs +++ b/backend/src/system.rs @@ -112,7 +112,7 @@ pub async fn kernel_logs_nofollow( _ctx: (), (limit, cursor, before, _): (Option, Option, bool, bool), ) -> Result { - fetch_logs(LogSource::Service(SYSTEMD_UNIT), limit, cursor, before).await + fetch_logs(LogSource::Kernel, limit, cursor, before).await } #[command(rpc_only, rename = "follow", display(display_none))] @@ -120,7 +120,7 @@ pub async fn kernel_logs_follow( #[context] ctx: RpcContext, #[parent_data] (limit, _, _, _): (Option, Option, bool, bool), ) -> Result { - follow_logs(ctx, LogSource::Service(SYSTEMD_UNIT), limit).await + follow_logs(ctx, LogSource::Kernel, limit).await } #[derive(Serialize, Deserialize)] diff --git a/backend/update-sqlx-data.sh b/backend/update-sqlx-data.sh new file mode 100755 index 000000000..46dc26a0e --- /dev/null +++ b/backend/update-sqlx-data.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +TMP_DIR=$(mktemp -d) +mkdir $TMP_DIR/pgdata +docker run -d --rm --name=tmp_postgres -e POSTGRES_PASSWORD=password -v $TMP_DIR/pgdata:/var/lib/postgresql/data postgres + +( + set -e + ctr=0 + while ! docker exec tmp_postgres psql -U postgres || [ $ctr -lt 5 ]; do + ctr=$[ctr + 1] + sleep 1; + done + + PG_IP=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' tmp_postgres) + + DATABASE_URL=postgres://postgres:password@$PG_IP/postgres cargo sqlx migrate run + DATABASE_URL=postgres://postgres:password@$PG_IP/postgres cargo sqlx prepare -- --lib --profile=test +) + +docker stop tmp_postgres +sudo rm -rf $TMP_DIR diff --git a/build/initialization.sh b/build/initialization.sh index 2ef0de94d..00a51da31 100755 --- a/build/initialization.sh +++ b/build/initialization.sh @@ -13,6 +13,12 @@ fi passwd -l start9 +while ! ping -q -w 1 -c 1 `ip r | grep default | cut -d ' ' -f 3` > /dev/null; do + >&2 echo "Waiting for internet connection..." + sleep 1 +done +echo "Connected to network" + # change timezone timedatectl set-timezone Etc/UTC @@ -45,7 +51,9 @@ apt-get install -y \ network-manager \ vim \ jq \ - ncdu + ncdu \ + postgresql \ + pgloader # Setup repository from The Guardian Project and install latest stable Tor daemon touch /etc/apt/sources.list.d/tor.list @@ -64,6 +72,10 @@ systemctl start systemd-resolved apt-get remove --purge openresolv dhcpcd5 -y systemctl disable wpa_supplicant.service +sudo -u postgres createuser root +sudo -u postgres createdb secrets -O root +systemctl disable postgresql.service + ln -rsf /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf systemctl disable bluetooth.service