updated README and a bunch of other stuff

This commit is contained in:
Sebastian Moser
2026-11-04 20:24:14 +01:00
parent 6eedb1826a
commit 9707286375
46 changed files with 3725 additions and 172 deletions

View File

@@ -0,0 +1,29 @@
cargo-features = ["profile-rustflags"]
[package]
name = "mize-part-ppc-server"
version = "0.1.0"
edition = "2024"
[lib]
name = "mize_part_ppc_server"
crate-type = ["cdylib", "rlib"]
[dependencies]
mize = { workspace = true }
axum = "0.8.9"
axum-oidc = "=1.0.0-dev-1"
openidconnect = { version = "4", features = ["reqwest", "rustls-tls"], default-features = false }
tower = "0.5.3"
tower-sessions = "0.15.0"
tower-http = { version = "0.7.1", features = ["cors", "trace"] }
serde_json = "1.0"
tokio = { version = "1", features = ["rt-multi-thread", "net", "macros"] }
tracing = "0.1"
dioxus = { version = "0.7", default-features = false, features = ["ssr", "macro", "html", "signals"] }
[profile.dev]
rustflags = ["-C", "prefer-dynamic", "-C", "link-args=-Wl,--export-dynamic"]
[profile.release]
rustflags = ["-C", "prefer-dynamic"]

176
parts/ppc.server/src/lib.rs Normal file
View File

@@ -0,0 +1,176 @@
mod pages;
use std::str::FromStr;
use std::thread;
use axum::{
Json, Router,
error_handling::HandleErrorLayer,
http::Uri,
response::IntoResponse,
routing::{any, get},
};
use axum_oidc::handle_oidc_redirect;
use axum_oidc::{
EmptyAdditionalClaims, OidcAuthLayer, OidcClaims, OidcClient, OidcLoginLayer,
OidcRpInitiatedLogout, error::MiddlewareError,
};
use mize::prelude::*;
use openidconnect::Scope;
use openidconnect::{ClientId, ClientSecret, IssuerUrl};
use serde_json::{Value, json};
use tokio::runtime::Runtime;
use tower::ServiceBuilder;
use tower_sessions::{
Expiry, MemoryStore, SessionManagerLayer,
cookie::{SameSite, time::Duration},
};
fn ensure_default(rel: &str, default: ItemData) -> MizeResult<()> {
let mut mize = get_global_mize()?;
let cur = mize
.cur_eval_part()?
.ok_or_else(|| mize_err!("no cur_eval_part"))?;
let full = format!("{}.{}", cur.dot(), rel);
if mize.get_config(&full).is_ok() {
return Ok(());
}
let segs: Vec<&str> = rel.split('.').filter(|s| !s.is_empty()).collect();
let last = *segs.last().ok_or_else(|| mize_err!("empty opt path"))?;
let mut parent = cur.clone();
for seg in &segs[..segs.len() - 1] {
parent = parent.join(seg)?;
}
let original = Some(cur);
mize.set_cur_eval_part(Some(parent))?;
opt(last).default_val(default);
mize.set_cur_eval_part(original)?;
Ok(())
}
#[unsafe(no_mangle)]
pub extern "C" fn part_ppc_server() -> MizeResult<()> {
ensure_default("url", "http://localhost:3000".into_item_data())?;
ensure_default("bind", "0.0.0.0:8010".into_item_data())?;
thread::spawn(|| {
let rt = match Runtime::new() {
Ok(rt) => rt,
Err(e) => {
eprintln!("ppc.server: failed to create runtime: {e}");
return;
}
};
if let Err(e) = rt.block_on(start_server()) {
e.log();
}
});
Ok(())
}
async fn start_server() -> MizeResult<()> {
let mut mize = get_global_mize()?;
let issuer = mize.get_config("auth.server.issuer")?.value_string()?;
let client_id = mize.get_config("auth.server.client_id")?.value_string()?;
let client_secret = mize.get_config("auth.server.client_secret")?.value_string()?;
let url = mize.get_config("ppc.server.url")?.value_string()?;
let bind = mize.get_config("ppc.server.bind")?.value_string()?;
let redirect_url = format!("{url}/oidc");
let session_store = MemoryStore::default();
let session_layer = SessionManagerLayer::new(session_store)
.with_secure(false)
.with_same_site(SameSite::Lax)
.with_expiry(Expiry::OnInactivity(Duration::seconds(120)));
let oidc_login_service = ServiceBuilder::new()
.layer(HandleErrorLayer::new(|e: MiddlewareError| async {
dbg!(&e);
e.into_response()
}))
.layer(OidcLoginLayer::<EmptyAdditionalClaims>::new());
let oidc_client = OidcClient::<EmptyAdditionalClaims>::builder()
.with_default_http_client()
.with_redirect_url(Uri::from_str(redirect_url.as_str())?)
.with_client_id(ClientId::new(client_id))
.add_scope(Scope::new("profile".into()))
.add_scope(Scope::new("email".into()))
.with_client_secret(ClientSecret::new(client_secret))
.discover(IssuerUrl::new(issuer).expect("Invalid IssuerUrl"))
.await
.map_err(|e| mize_err!("oidc discover failed: {:?}", e))?
.build();
let oidc_auth_service = ServiceBuilder::new()
.layer(HandleErrorLayer::new(|e: MiddlewareError| async {
dbg!(&e);
e.into_response()
}))
.layer(OidcAuthLayer::new(oidc_client));
let app = Router::new()
.route("/foo", get(authenticated))
.route("/logout", get(logout))
.layer(oidc_login_service)
.route("/bar", get(maybe_authenticated))
.route("/oidc", any(handle_oidc_redirect::<EmptyAdditionalClaims>))
.layer(oidc_auth_service)
.layer(session_layer)
.layer(tower_http::trace::TraceLayer::new_for_http())
.route("/", get(pages::main_page()))
.route("/mc", get(pages::mc_page()))
.route(
"/.well-known/matrix/client",
get(handle_matrix_client).layer(tower_http::cors::CorsLayer::permissive()),
)
.route("/.well-known/matrix/server", get(handle_matrix_server));
tracing::info!("ppc.server listening on http://{bind}");
let listener = tokio::net::TcpListener::bind(&bind)
.await
.map_err(|e| mize_err!("bind {bind}: {e}"))?;
axum::serve(listener, app.into_make_service())
.await
.map_err(|e| mize_err!("axum serve: {e}"))?;
Ok(())
}
async fn authenticated(claims: OidcClaims<EmptyAdditionalClaims>) -> impl IntoResponse {
format!("Hello {}", claims.subject().as_str())
}
async fn maybe_authenticated(
claims: Result<OidcClaims<EmptyAdditionalClaims>, axum_oidc::error::ExtractorError>,
) -> impl IntoResponse {
if let Ok(claims) = claims {
format!(
"Hello {}! You are already logged in from another Handler.",
claims.subject().as_str()
)
} else {
"Hello anon!".to_string()
}
}
async fn logout(logout: OidcRpInitiatedLogout) -> impl IntoResponse {
logout.with_post_logout_redirect(Uri::from_static("https://localhost:3000"))
}
async fn handle_matrix_client() -> Json<Value> {
Json(json!({
"m.homeserver": {
"base_url": "https://matrix.ppc.social"
}
}))
}
async fn handle_matrix_server() -> Json<Value> {
Json(json!({
"m.server": "matrix.ppc.social:443"
}))
}

View File

@@ -0,0 +1,186 @@
use axum::response::Html;
use dioxus::prelude::*;
const SITE_CSS: &str = r#"
*, *::before, *::after { box-sizing: border-box; }
html { color-scheme: light dark; }
body {
margin: 0;
padding: 0 1.25rem 2.5rem;
max-width: 60ch;
margin-inline: auto;
font-family: Georgia, "Times New Roman", serif;
font-size: 1.25rem;
line-height: 1.6;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
background-color: #F7F8F4;
color: #20251C;
word-wrap: break-word;
}
h1, h2 {
line-height: 1.2;
font-weight: 700;
color: #20251C;
}
h1 { margin: 2.5rem 0 1.5rem; font-size: 1.75rem; }
h2 { margin: 2.5rem 0 1rem; font-size: 1.35rem; }
p { margin: 0.85rem 0; }
.tagline { color: #66705E; font-style: italic; }
.meta { color: #66705E; font-size: 0.95rem; }
a {
color: #376777;
text-decoration-thickness: 1px;
text-underline-offset: 3px;
}
a:hover { color: #79500F; }
.accent { color: #79500F; }
.accent-bar {
display: inline-block;
width: 2.5rem;
height: 0.35rem;
background: #FEC251;
border-radius: 2px;
margin-bottom: 0.75rem;
}
@media (prefers-color-scheme: dark) {
body { background-color: #171D18; color: #F2F4EA; }
h1, h2 { color: #F2F4EA; }
.tagline, .meta { color: #AAB5A4; }
a { color: #B9E4EB; }
a:hover { color: #D7E47D; }
.accent { color: #D7E47D; }
.accent-bar { background: #D7E47D; }
}
"#;
const PLAUSIBLE_SRC: &str = "https://plausible.c2vi.dev/js/script.file-downloads.hash.outbound-links.pageview-props.revenue.tagged-events.js";
const PLAUSIBLE_BOOT: &str =
"window.plausible = window.plausible || function() { (window.plausible.q = window.plausible.q || []).push(arguments) }";
fn render_page(title: &str, body: Element) -> Html<String> {
let markup = dioxus::ssr::render_element(rsx! {
head {
meta { charset: "UTF-8" }
meta { name: "viewport", content: "width=device-width, initial-scale=1.0" }
title { "{title}" }
script {
defer: true,
"data-domain": "ppc.social",
src: PLAUSIBLE_SRC,
}
script { dangerous_inner_html: PLAUSIBLE_BOOT }
style { {SITE_CSS} }
}
body { {body} }
});
Html(format!("<!DOCTYPE html>\n<html lang=\"en\">\n{markup}\n</html>"))
}
#[component]
fn MainPageBody() -> Element {
rsx! {
div { class: "accent-bar" }
h1 { "People Corner" }
p { class: "tagline", "\"Cool People doing Cool things together\"" }
p {
"Read what this is all about: "
a { href: "https://wiki.ppc.social/u/c2vi/The-Idea-of-PPC", "About" }
}
p {
"There is a Discord Server: "
a { href: "https://discord.gg/cMKQNJMkRe", "https://discord.gg/cMKQNJMkRe" }
}
p {
"There is a "
a { href: "https://ppc.social/mc", "Minecraft server" }
"."
}
p {
"There is "
a { href: "https://wiki.ppc.social", "The PPC Wiki Projekt" }
"."
}
p {
"There is an "
a { href: "https://www.instagram.com/ppc.social/", "Instagram" }
" and a "
a { href: "https://www.youtube.com/@ppc.social", "Youtube Channel" }
}
p { class: "meta", "and that's about all for now..." }
}
}
#[component]
fn McPageBody() -> Element {
rsx! {
div { class: "accent-bar" }
h1 { "Welcome to the PPC Minecraft Cluster" }
h2 { "Address" }
p { class: "accent", "first.ppc.social" }
p { class: "meta", "Java 1.21.11 and Bedrock (bedrock not working rn)" }
h2 { "Discord" }
p {
a {
href: "https://discord.gg/cMKQNJMkRe",
target: "_blank",
"https://discord.gg/cMKQNJMkRe"
}
}
p { class: "meta", "come and say hi!" }
p { "See you ingame and invite all your friends to play as well!!" }
h2 { "SimpleVoicechat" }
p {
"This is "
span { class: "accent", "not required" }
", but makes playing soooo much more fun, so it's "
span { class: "accent", "highly encouraged" }
"."
}
p {
"Instructions can be found here:"
}
p {
a { href: "https://fabricmc.net/use/installer/", "Fabric" }
}
p {
a {
href: "https://modrepo.de/minecraft/voicechat/wiki/installation#fabric",
"SimpleVoiceChat"
}
}
}
}
pub fn main_page() -> Html<String> {
render_page("People Corner", rsx! { MainPageBody {} })
}
pub fn mc_page() -> Html<String> {
render_page("PPC Minecraft Server", rsx! { McPageBody {} })
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn main_page_renders() {
let html = main_page().0;
assert!(html.contains("<!DOCTYPE html>"));
assert!(html.contains("People Corner"));
assert!(html.contains("Cool People doing Cool things together"));
assert!(html.contains("#F7F8F4"));
assert!(html.contains("#376777"));
assert!(html.contains("https://wiki.ppc.social/u/c2vi/The-Idea-of-PPC"));
}
#[test]
fn mc_page_renders() {
let html = mc_page().0;
assert!(html.contains("PPC Minecraft Cluster"));
assert!(html.contains("first.ppc.social"));
assert!(html.contains("SimpleVoicechat"));
assert!(html.contains("https://fabricmc.net/use/installer/"));
}
}