feat: initial server config & Dockerfile

This commit is contained in:
Ryan Walters
2025-09-16 22:13:35 -05:00
parent 7ede82cc5d
commit f69a5c7d52
9 changed files with 2389 additions and 42 deletions

16
.dockerignore Normal file
View File

@@ -0,0 +1,16 @@
# Build artifacts
/target
/dist
/emsdk
*.exe
/pacman/assets
/assets
# Development files
/.git
/*.md
/Justfile
/bacon.toml
/rust-toolchain.toml
/rustfmt.toml

3
.gitignore vendored
View File

@@ -23,3 +23,6 @@ flamegraph.svg
# Logs # Logs
*.log *.log
# Sensitive
*.env

2245
Cargo.lock generated
View File

File diff suppressed because it is too large Load Diff

View File

@@ -14,15 +14,6 @@ categories = ["games", "emulators"]
publish = false publish = false
[workspace.dependencies] [workspace.dependencies]
# Common dependencies that might be shared across crates
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tokio = { version = "1.0", features = ["full"] }
axum = "0.7"
tower = "0.4"
tower-http = { version = "0.5", features = ["cors", "trace"] }
tracing = "0.1"
tracing-subscriber = "0.3"
# Release profile for profiling (essentially the default 'release' profile with debug enabled) # Release profile for profiling (essentially the default 'release' profile with debug enabled)

View File

@@ -15,3 +15,23 @@ publish.workspace = true
default-run = "pacman-server" default-run = "pacman-server"
[dependencies] [dependencies]
axum = "0.8"
tokio = { version = "1", features = ["full"] }
oauth2 = "5"
reqwest = { version = "0.12", features = ["json"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
chrono = { version = "0.4", features = ["serde"] }
sqlx = { version = "0.8", features = [
"runtime-tokio-rustls",
"postgres",
"chrono",
] }
figment = { version = "0.10", features = ["env"] }
dotenvy = "0.15"
# Validation
# validator = { version = "0.16", features = ["derive"] }
# JWT for internal sessions
# jsonwebtoken = "8.3"

46
pacman-server/Dockerfile Normal file
View File

@@ -0,0 +1,46 @@
ARG RUST_VERSION=1.89.0
FROM lukemathwalker/cargo-chef:latest-rust-${RUST_VERSION} AS chef
WORKDIR /app
# -- Planner stage --
FROM chef AS planner
COPY . .
RUN cargo chef prepare --bin pacman-server --recipe-path recipe.json
# -- Builder stage --
FROM chef AS builder
COPY --from=planner /app/recipe.json recipe.json
RUN cargo chef cook --release --bin pacman-server --recipe-path recipe.json
# Copy the source code AFTER, so that dependencies are already cached
COPY . .
# Install build dependencies, then build the server
RUN apt-get update && apt-get install -y pkg-config libssl-dev && rm -rf /var/lib/apt/lists/*
RUN cargo build --package pacman-server --release --bin pacman-server
# -- Runtime stage --
FROM debian:bookworm-slim AS runtime
WORKDIR /app
COPY --from=builder /app/target/release/pacman-server /usr/local/bin/pacman-server
# Install runtime dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \
tzdata \
&& rm -rf /var/lib/apt/lists/*
ARG TZ=Etc/UTC
ENV TZ=${TZ}
# Optional build-time environment variable for embedding the Git commit SHA
ARG RAILWAY_GIT_COMMIT_SHA
ENV RAILWAY_GIT_COMMIT_SHA=${RAILWAY_GIT_COMMIT_SHA}
# Specify PORT at build-time or run-time, default to 3000
ARG PORT=3000
ENV PORT=${PORT}
EXPOSE ${PORT}
CMD ["sh", "-c", "exec /usr/local/bin/pacman-server"]

View File

@@ -0,0 +1,67 @@
use figment::{providers::Env, value::UncasedStr, Figment};
use serde::{Deserialize, Deserializer};
#[derive(Debug, Deserialize)]
pub struct Config {
// Database URL
pub database_url: String,
// Discord Credentials
#[serde(deserialize_with = "deserialize_string_from_any")]
pub discord_client_id: String,
pub discord_client_secret: String,
// GitHub Credentials
#[serde(deserialize_with = "deserialize_string_from_any")]
pub github_client_id: String,
pub github_client_secret: String,
// S3 Credentials
pub s3_access_key: String,
pub s3_secret_access_key: String,
pub s3_endpoint: String,
pub s3_region: String,
// Server Details
#[serde(default = "default_port")]
pub port: u16,
#[serde(default = "default_host")]
pub host: std::net::IpAddr,
#[serde(default = "default_shutdown_timeout")]
pub shutdown_timeout_seconds: u32,
}
fn default_host() -> std::net::IpAddr {
"0.0.0.0".parse().unwrap()
}
fn default_port() -> u16 {
3000
}
fn default_shutdown_timeout() -> u32 {
5
}
fn deserialize_string_from_any<'de, D>(deserializer: D) -> Result<String, D::Error>
where
D: Deserializer<'de>,
{
use serde_json::Value;
let value = Value::deserialize(deserializer)?;
match value {
Value::String(s) => Ok(s),
Value::Number(n) => Ok(n.to_string()),
_ => Err(serde::de::Error::custom("Expected string or number")),
}
}
pub fn load_config() -> Config {
Figment::new()
.merge(Env::raw().map(|key| {
if key == UncasedStr::new("RAILWAY_DEPLOYMENT_DRAINING_SECONDS") {
"SHUTDOWN_TIMEOUT".into()
} else {
key.into()
}
}))
.extract()
.expect("Failed to load config")
}

1
pacman-server/src/lib.rs Normal file
View File

@@ -0,0 +1 @@

View File

@@ -1,3 +1,23 @@
fn main() { use axum::Router;
println!("Hello, world!");
use crate::config::Config;
mod config;
#[tokio::main]
async fn main() {
// Load environment variables
#[cfg(debug_assertions)]
dotenvy::from_path(format!("{}.env", env!("CARGO_MANIFEST_DIR"))).ok();
#[cfg(not(debug_assertions))]
dotenvy::dotenv().ok();
// Load configuration
let config: Config = config::load_config();
let app = Router::new().fallback(|| async { "Hello, World!" });
let addr = std::net::SocketAddr::new(config.host, config.port);
let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
axum::serve(listener, app).await.unwrap();
} }