From 5b3f8b89d50a78812ad1b3b2370a32ea5c71b3b4 Mon Sep 17 00:00:00 2001 From: eiiko6 Date: Thu, 26 Mar 2026 23:25:57 +0100 Subject: [PATCH] now using postgres --- flake.nix | 161 ++++++++++++++++++++++++++------------------- server/Cargo.lock | 1 - server/Cargo.toml | 2 +- server/src/main.rs | 78 +++++++++++----------- 4 files changed, 131 insertions(+), 111 deletions(-) diff --git a/flake.nix b/flake.nix index 6b5c877..8da855a 100644 --- a/flake.nix +++ b/flake.nix @@ -1,82 +1,107 @@ { - description = "Slimes Benchmark Server"; + description = "Slimes Benchmark Server"; - inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; - }; + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + }; - outputs = - { self, nixpkgs }: + outputs = + { self, nixpkgs }: + let + supportedSystems = [ + "x86_64-linux" + ]; + forAllSystems = nixpkgs.lib.genAttrs supportedSystems; + in + { + packages = forAllSystems ( + system: let - supportedSystems = [ - "x86_64-linux" - ]; - forAllSystems = nixpkgs.lib.genAttrs supportedSystems; + pkgs = import nixpkgs { inherit system; }; in { - packages = forAllSystems ( - system: - let - pkgs = import nixpkgs { inherit system; }; - in - { - default = pkgs.rustPlatform.buildRustPackage { - pname = "slimes-server"; - version = "0.1.0"; + default = pkgs.rustPlatform.buildRustPackage { + pname = "slimes-server"; + version = "0.1.0"; - src = ./server; + src = ./server; - cargoLock.lockFile = ./server/Cargo.lock; + cargoLock.lockFile = ./server/Cargo.lock; - nativeBuildInputs = [ pkgs.pkg-config ]; - buildInputs = [ pkgs.sqlite ]; + nativeBuildInputs = [ pkgs.pkg-config ]; + # buildInputs = [ pkgs.sqlite ]; - # doCheck = false; - }; - } - ); + # doCheck = false; + }; + } + ); - nixosModules.default = - { - config, - lib, - pkgs, - ... - }: - let - cfg = config.services.slimes-server; - in - { - options.services.slimes-server = { - enable = lib.mkEnableOption "Slimes Server"; - port = lib.mkOption { - type = lib.types.port; - default = 9003; - }; - databasePath = lib.mkOption { - type = lib.types.str; - default = "/var/lib/slimes-server/slimes.db"; - }; - }; + nixosModules.default = + { + config, + lib, + pkgs, + inputs, + ... + }: + let + cfg = config.services.slimes-server; + in + { + options.services.slimes-server = { + enable = lib.mkEnableOption "Slimes Server"; + port = lib.mkOption { + type = lib.types.port; + default = 9003; + }; + databaseUrl = lib.mkOption { + type = lib.types.str; + default = "postgres://slimes:secret@127.0.0.1:9005/slimes"; + }; + }; - config = lib.mkIf cfg.enable { - systemd.services.slimes-server = { - description = "Slimes Benchmark Server"; - after = [ "network.target" ]; - wantedBy = [ "multi-user.target" ]; - serviceConfig = { - ExecStart = "${ - self.packages.${pkgs.system}.default - }/bin/slimes-server --database-url ${cfg.databasePath} --port ${toString cfg.port}"; - Restart = "on-failure"; - StateDirectory = "slimes-server"; - DynamicUser = true; - ProtectSystem = "strict"; - ProtectHome = true; - NoNewPrivileges = true; - }; - }; - }; - }; + config = lib.mkIf cfg.enable { + systemd.services.slimes-server = { + description = "Slimes Benchmark Server"; + after = [ + "network.target" + "slimes-server-db-container.service" + ]; + requires = [ "slimes-server-db-container.service" ]; + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + ExecStart = "${ + self.packages.${pkgs.system}.default + }/bin/slimes-server --database-url ${cfg.databaseUrl} --port ${toString cfg.port}"; + Restart = "on-failure"; + # StateDirectory = "slimes-server"; + # DynamicUser = true; + ProtectSystem = "strict"; + ProtectHome = true; + NoNewPrivileges = true; + }; + }; + + systemd.services."slimes-server-db-container" = { + wantedBy = [ "multi-user.target" ]; + serviceConfig = { + StateDirectory = "slimes-server-db"; + ExecStart = '' + ${pkgs.podman}/bin/podman run \ + --name slimes-server-db \ + --replace \ + -p 127.0.0.1:9005:5432 \ + -e POSTGRES_PASSWORD=secret \ + -e POSTGRES_USER=slimes \ + -e POSTGRES_DB=slimes \ + -v /var/lib/slimes-server-db:/var/lib/postgresql/data \ + docker.io/library/postgres:15 + ''; + ExecStop = "${pkgs.podman}/bin/podman stop slimes-server-db"; + Restart = "always"; + }; + }; + }; }; + }; } diff --git a/server/Cargo.lock b/server/Cargo.lock index 3c15cfd..ba770ff 100644 --- a/server/Cargo.lock +++ b/server/Cargo.lock @@ -1042,7 +1042,6 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" dependencies = [ - "cc", "pkg-config", "vcpkg", ] diff --git a/server/Cargo.toml b/server/Cargo.toml index 0e0a413..2a61607 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -11,7 +11,7 @@ clap = { version = "4.6.0", features = ["derive"] } serde = { version = "1.0.228", features = ["derive"] } serde_json = "1.0.149" # slimes = { version = "1.0.0", path = ".." } -sqlx = { version = "0.8.6", features = ["chrono", "sqlite", "runtime-tokio"] } +sqlx = { version = "0.8.6", features = ["chrono", "runtime-tokio", "postgres"] } tokio = { version = "1.50.0", features = ["rt-multi-thread", "macros"] } tower-http = { version = "0.6.8", features = ["fs", "cors"] } tower_governor = "0.8.0" diff --git a/server/src/main.rs b/server/src/main.rs index ce004cd..51a8358 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, net::SocketAddr, sync::Arc, time::Duration}; +use std::{net::SocketAddr, sync::Arc, time::Duration}; use axum::{ Json, Router, @@ -8,15 +8,14 @@ use axum::{ }; use clap::Parser; use serde::{Deserialize, Serialize}; -use sqlx::{SqlitePool, sqlite::SqlitePoolOptions}; +use sqlx::{PgPool, postgres::PgPoolOptions}; use tower_governor::{GovernorLayer, governor::GovernorConfigBuilder}; use tower_http::cors::{Any, CorsLayer}; #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] struct Args { - /// Path to the SQLite database file - #[arg(short, long, default_value = "slimes.db")] + #[arg(short, long)] database_url: String, /// Port to listen on @@ -43,10 +42,10 @@ pub struct BenchmarkReport { #[derive(Debug, Serialize, Deserialize)] pub struct FullReport { #[serde(skip_deserializing)] - pub id: Option, + pub id: Option, pub mac_address: String, pub timestamp: String, - pub slimes: Option>>, + pub slimes: Option, pub benchmark: Option, pub client_version: String, pub signature: String, @@ -54,30 +53,31 @@ pub struct FullReport { #[derive(Deserialize)] pub struct Pagination { - pub limit: Option, - pub offset: Option, + pub limit: Option, + pub offset: Option, } pub struct AppState { - db: SqlitePool, + db: PgPool, } async fn submit( State(state): State>, Json(payload): Json, -) -> Result<(StatusCode, Json), (StatusCode, String)> { +) -> Result<(StatusCode, Json), (StatusCode, String)> { let score = payload .benchmark .as_ref() .map(|b| b.multi_thread.score) .unwrap_or(0); - let raw_json = serde_json::to_string(&payload) + let raw_json = serde_json::to_value(&payload) .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; - let result = sqlx::query( + let row: (i32,) = sqlx::query_as( "INSERT INTO reports (mac_address, score, timestamp, client_version, signature, data) - VALUES (?, ?, ?, ?, ?, ?)", + VALUES ($1, $2, $3, $4, $5, $6) + RETURNING id", ) .bind(&payload.mac_address) .bind(score as i64) @@ -85,16 +85,15 @@ async fn submit( .bind(&payload.client_version) .bind(&payload.signature) .bind(raw_json) - .execute(&state.db) + .fetch_one(&state.db) .await .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?; - let id = result.last_insert_rowid(); - Ok((StatusCode::CREATED, Json(id))) + Ok((StatusCode::CREATED, Json(row.0))) } -fn parse_report_row(id: i64, data: String) -> Option { - let mut report: FullReport = serde_json::from_str(&data).ok()?; +fn parse_report_row(id: i32, data: serde_json::Value) -> Option { + let mut report: FullReport = serde_json::from_value(data).ok()?; report.id = Some(id); Some(report) } @@ -106,13 +105,15 @@ async fn get_leaderboard( let limit = pagination.limit.unwrap_or(10); let offset = pagination.offset.unwrap_or(0); - let rows: Vec<(i64, String)> = sqlx::query_as( + let rows: Vec<(i32, serde_json::Value)> = sqlx::query_as( r#" - SELECT id, data FROM reports r - WHERE score = (SELECT MAX(score) FROM reports WHERE mac_address = r.mac_address) - GROUP BY mac_address + SELECT id, data FROM ( + SELECT DISTINCT ON (mac_address) id, data, score + FROM reports + ORDER BY mac_address, score DESC + ) sub ORDER BY score DESC - LIMIT ? OFFSET ? + LIMIT $1 OFFSET $2 "#, ) .bind(limit) @@ -131,14 +132,15 @@ async fn get_leaderboard( async fn get_report_by_id( State(state): State>, - Path(id): Path, + Path(id): Path, ) -> Result, (StatusCode, String)> { - let row: (i64, String) = sqlx::query_as("SELECT id, data FROM reports WHERE id = ?") - .bind(id) - .fetch_optional(&state.db) - .await - .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))? - .ok_or((StatusCode::NOT_FOUND, "Report not found".to_string()))?; + let row: (i32, serde_json::Value) = + sqlx::query_as("SELECT id, data FROM reports WHERE id = $1") + .bind(id) + .fetch_optional(&state.db) + .await + .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))? + .ok_or((StatusCode::NOT_FOUND, "Report not found".to_string()))?; let report = parse_report_row(row.0, row.1).ok_or(( StatusCode::INTERNAL_SERVER_ERROR, @@ -162,26 +164,20 @@ async fn main() -> anyhow::Result<()> { let subscriber = tracing_subscriber::FmtSubscriber::new(); tracing::subscriber::set_global_default(subscriber).unwrap(); - let path = &args.database_url; - if !path.is_empty() && !std::path::Path::new(path).exists() { - tracing::info!("Creating database file at {}", path); - std::fs::File::create(path)?; - } - - let pool = SqlitePoolOptions::new() + let pool = PgPoolOptions::new() .max_connections(5) - .connect(format!("sqlite:{}", &args.database_url).as_str()) + .connect(&args.database_url) .await?; sqlx::query( "CREATE TABLE IF NOT EXISTS reports ( - id INTEGER PRIMARY KEY AUTOINCREMENT, + id SERIAL PRIMARY KEY, mac_address TEXT NOT NULL, - score INTEGER NOT NULL, + score BIGINT NOT NULL, timestamp TEXT NOT NULL, client_version TEXT NOT NULL, signature TEXT, - data TEXT NOT NULL + data JSONB NOT NULL );", ) .execute(&pool)