switch to postgresql (#1763)

switch sqlx to postgresql
This commit is contained in:
Aiden McClelland
2022-09-01 10:32:01 -06:00
committed by GitHub
parent 705653465a
commit 76682ebef0
30 changed files with 800 additions and 537 deletions

View File

@@ -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)

98
backend/Cargo.lock generated
View File

@@ -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"

View File

@@ -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

View File

@@ -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,

File diff suppressed because it is too large Load Diff

View File

@@ -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<Ex>(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<Db: DbHandle, 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 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?;

View File

@@ -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,

View File

@@ -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,

View File

@@ -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<Ex>(secrets: &mut Ex, id: u32) -> Result<Cifs, Error>
pub async fn load<Ex>(secrets: &mut Ex, id: i32) -> Result<Cifs, Error>
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<Ex>(secrets: &mut Ex) -> Result<Vec<(u32, CifsBackupTarget)>, Error>
pub async fn list<Ex>(secrets: &mut Ex) -> Result<Vec<(i32, CifsBackupTarget)>, 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? {

View File

@@ -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<Ex>(self, secrets: &mut Ex) -> Result<BackupTargetFS, Error>
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 } => {

View File

@@ -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<Value, ConfigurationError> {
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
)

View File

@@ -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<PatchDb, Error> {
pub async fn db(&self, secret_store: &PgPool, product_key: &str) -> Result<PatchDb, Error> {
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<SqlitePool, Error> {
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<PgPool, Error> {
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<String>,
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()?;

View File

@@ -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<PatchDb, Error> {
pub async fn db(&self, secret_store: &PgPool) -> Result<PatchDb, Error> {
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<SqlitePool, Error> {
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<PgPool, Error> {
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))]

View File

@@ -48,13 +48,15 @@ pub async fn unmount<P: AsRef<Path>>(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(())
}

View File

@@ -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<Path>) -> 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<Path>) -> 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)

View File

@@ -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<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 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<Ex>(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(())

View File

@@ -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;

View File

@@ -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()

View File

@@ -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<Self, Error> {
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 {

7
backend/src/migrate.load Normal file
View File

@@ -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';

View File

@@ -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<InterfaceAddressMap, Error>
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<BTreeMap<InterfaceId, TorSecretKeyV3>, 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)

View File

@@ -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<Private>, X509)>,
) -> Result<Self, Error> {
let ssl = match import_root_ca {

View File

@@ -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<Self, Error> {
fn new(db: PgPool) -> Result<Self, Error> {
Ok(SslStore { secret_store: db })
}
#[instrument(skip(self))]
async fn save_root_certificate(&self, key: &PKey<Private>, 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<Option<(PKey<Private>, 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<Option<(PKey<Private>, 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<Self, Error> {
pub async fn init(db: PgPool) -> Result<Self, Error> {
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<Private>,
root_cert: X509,
) -> Result<Self, Error> {
@@ -508,10 +513,11 @@ fn make_leaf_cert(
#[tokio::test]
async fn ca_details_persist() -> Result<(), Error> {
let pool = sqlx::Pool::<sqlx::Sqlite>::connect("sqlite::memory:").await?;
sqlx::query_file!("migrations/20210629193146_Init.sql")
.execute(&pool)
.await?;
let pool = sqlx::Pool::<sqlx::Postgres>::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::<sqlx::Sqlite>::connect("sqlite::memory:").await?;
sqlx::query_file!("migrations/20210629193146_Init.sql")
.execute(&pool)
.await?;
let pool = sqlx::Pool::<sqlx::Postgres>::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?;

View File

@@ -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<Ex>(secrets: &mut Ex) -> Result<TorSecretKeyV3, Error>
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)

View File

@@ -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<u32>,
#[arg] before: Option<i32>,
#[arg] limit: Option<u32>,
) -> Result<WithRevision<Vec<Notification>>, 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<HashMap<(Option<PackageId>, 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,

View File

@@ -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<Ex>(secrets: &mut Ex) -> Result<String, Error>
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,
)

View File

@@ -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<SshKe
let pool = &ctx.secret_store;
// check fingerprint for duplicates
let fp = key.0.fingerprint_md5();
match sqlx::query!("SELECT * FROM ssh_keys WHERE fingerprint = ?", fp)
match sqlx::query!("SELECT * FROM ssh_keys WHERE fingerprint = $1", fp)
.fetch_optional(pool)
.await?
{
@@ -70,7 +70,7 @@ pub async fn add(#[context] ctx: RpcContext, #[arg] key: PubKey) -> Result<SshKe
let raw_key = format!("{}", key.0);
let created_at = Utc::now().to_rfc3339();
sqlx::query!(
"INSERT INTO ssh_keys (fingerprint, openssh_pubkey, created_at) VALUES (?, ?, ?)",
"INSERT INTO ssh_keys (fingerprint, openssh_pubkey, created_at) VALUES ($1, $2, $3)",
fp,
raw_key,
created_at
@@ -96,7 +96,7 @@ pub async fn delete(#[context] ctx: RpcContext, #[arg] fingerprint: String) -> 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<P: AsRef<Path>>(pool: &Pool<Sqlite>, dest: P) -> Result<(), Error> {
pub async fn sync_keys_from_db<P: AsRef<Path>>(
pool: &Pool<Postgres>,
dest: P,
) -> Result<(), Error> {
let dest = dest.as_ref();
let keys = sqlx::query!("SELECT openssh_pubkey FROM ssh_keys")
.fetch_all(pool)

View File

@@ -112,7 +112,7 @@ pub async fn kernel_logs_nofollow(
_ctx: (),
(limit, cursor, before, _): (Option<usize>, Option<String>, bool, bool),
) -> Result<LogResponse, Error> {
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<usize>, Option<String>, bool, bool),
) -> Result<LogFollowResponse, Error> {
follow_logs(ctx, LogSource::Service(SYSTEMD_UNIT), limit).await
follow_logs(ctx, LogSource::Kernel, limit).await
}
#[derive(Serialize, Deserialize)]

22
backend/update-sqlx-data.sh Executable file
View File

@@ -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

View File

@@ -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