diff --git a/flake.nix b/flake.nix index 493e4ee..815d91f 100644 --- a/flake.nix +++ b/flake.nix @@ -15,7 +15,7 @@ nixpkgs, flake-utils, fenix, - crane + crane, }: flake-utils.lib.eachDefaultSystem ( @@ -30,12 +30,14 @@ strictDeps = true; nativeBuildInputs = with pkgs; [pkg-config cmake]; - buildInputs = with pkgs; [ - openssl - zlib-ng - ] ++ lib.optionals stdenv.isDarwin [ - libiconv - ]; + buildInputs = with pkgs; + [ + openssl + zlib-ng + ] + ++ lib.optionals stdenv.isDarwin [ + libiconv + ]; }; in with pkgs; { @@ -47,9 +49,66 @@ ]; }; formatter = pkgs.alejandra; - packages.default = craneLib.buildPackage (commonArgs // { - cargoArtifacts = craneLib.buildDepsOnly commonArgs; - }); + packages.default = craneLib.buildPackage (commonArgs + // { + cargoArtifacts = craneLib.buildDepsOnly commonArgs; + }); + nixosModules.default = with lib; { config, ... }: { + options.services.smo-server = { + enable = mkEnableOption "a game server for Super Mario Odyssey Online"; + user = mkOption { + type = lib.types.string; + description = "The user to start the server with"; + }; + enableFaker = mkOption { + type = lib.types.bool; + default = false; + example = true; + description = "Whether to enable the test bot for solo development."; + }; + tcpPort = mkOption { + type = lib.types.port; + default = 1027; + description = "The TCP port to host the server on"; + }; + udpPort = mkOption { + type = lib.types.port; + default = 1027; + description = "The UDP port to host the server on"; + }; + proximity = { + type = lib.types.attrs; + port = { + type = lib.types.port; + example = 4433; + description = "The UDP port to host the proximity chat server on"; + }; + certPath = { + type = lib.types.path; + example = "cert.pem"; + description = "The certificate used for encrypting the WebTransport stream"; + }; + keyPath = { + type = lib.types.path; + example = "cert.pem"; + description = "The certificate used for encrypting the WebTransport stream"; + }; + }; + }; + config = mkIf config.services.smo-server.enable { + systemd.services.smo-server = with services.smo-server; { + wantedBy = ["multi-user.target"]; + after = ["network.target"]; + description = "Start smo-server"; + serviceConfig = { + WorkingDirectory = "${packages.default.outPath}"; + Type = "simple"; + ExecStart = let proxRes = 5; + in ''${packages.default.outPath}/bin/smo-server -t ${tcpPort} -u ${udpPort} ${proxRes}''; + }; + }; + }; + }; } ); } diff --git a/src/main.rs b/src/main.rs index 6cd4017..19c17f1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -39,17 +39,18 @@ pub fn manager() -> &'static Address { } #[derive(clap::Parser)] +#[command(about = "A game server for Super Mario Odyssey Online")] struct Arguments { #[arg(short, long, default_value_t = 1027)] tcp_port: u16, #[arg(short, long, default_value_t = 1027)] udp_port: u16, - #[arg(short, long, default_value_t = 4433)] - prox_port: u16, - #[arg(long, default_value = "./cert.pem")] - prox_cert: PathBuf, - #[arg(long, default_value = "./key.pem")] - prox_key: PathBuf, + #[arg(short, long, requires = "prox_cert", requires = "prox_key")] + prox_port: Option, + #[arg(long)] + prox_cert: Option, + #[arg(long)] + prox_key: Option, #[arg(long)] enable_faker: bool, } diff --git a/src/server/mod.rs b/src/server/mod.rs index 86da247..6ddffee 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -22,33 +22,44 @@ fn listeners() -> &'static RwLock>> &LISTENERS } -pub async fn web_main(port: u16, cert: PathBuf, key: PathBuf) -> Address { +pub async fn web_main(port: Option, cert: Option, key: Option) -> Address { let span = info_span!("prox"); - info!(parent: &span, "reading pems from {{ cert: {cert:?}, key: {key:?} }}"); - let identity = Identity::load_pemfiles(cert, key).await.expect("failed to create identity from proximity pems"); - let config = ServerConfig::builder().with_bind_default(port).with_identity(identity).build(); - let endpoint = Endpoint::server(config).expect("failed to build proximity endpoint"); + let endpoint = if let Some(port) = port { + let (cert, key) = (cert.unwrap(), key.unwrap()); // guarded and enforced to be fine by clap + info!(parent: &span, "reading pems from {{ cert: {cert:?}, key: {key:?} }}"); + let identity = Identity::load_pemfiles(cert, key).await.expect("failed to create identity from proximity pems"); + let config = ServerConfig::builder().with_bind_default(port).with_identity(identity).build(); + let endpoint = Endpoint::server(config).expect("failed to build proximity endpoint"); - info!("listening on webtransport port {port}"); + info!(parent: &span, "listening on webtransport port {port}"); + Some(endpoint) + } else { + info!(parent: &span, "no port specified, not starting server"); + + None + }; let manager = xtra::spawn_tokio( Manager { players: HashMap::new(), next_id: 0, }, - Mailbox::bounded(8), + Mailbox::bounded(32), ); - tokio::spawn({ - let manager = manager.clone(); - async move { - loop { - let connection = endpoint.accept().await; - ProximityPlayer::spawn(connection, manager.clone()); + if let Some(endpoint) = endpoint { + tokio::spawn({ + let manager = manager.clone(); + async move { + loop { + let connection = endpoint.accept().await; + + ProximityPlayer::spawn(connection, manager.clone()); + } } - } - .instrument(span) - }); + .instrument(span) + }); + } manager } diff --git a/src/server/prox.rs b/src/server/prox.rs index 79d24ea..b0be8a7 100644 --- a/src/server/prox.rs +++ b/src/server/prox.rs @@ -33,7 +33,6 @@ impl ProximityPlayer { ); let (mut send, mut recv) = connection.accept_bi().await.expect("failed to start channel"); - trace!("getting uuid and name"); let mut id = UuidString::new_zeroed(); recv.read_exact(id.as_mut_bytes()).await.expect("failed to read uuid"); let mut name = String::new_zeroed(); @@ -46,7 +45,7 @@ impl ProximityPlayer { send.write_u8(state.len() as u8).await.expect("failed to write length"); for player in state.values() { - trace!("sending player {player:?}"); + trace!(parent: &span, "sending player {player:?}"); send.write_all(player.as_bytes()).await.expect("failed to write player"); } @@ -78,7 +77,6 @@ impl ProximityPlayer { loop { match packet::Event::deserialize(&mut recv).await { Ok(event) => { - info!("deserialized event: {event:?}"); let _ = address.send(event).detach().await; } Err(error) => { @@ -359,7 +357,6 @@ impl Handler for ProximityPlayer { type Return = (); async fn handle(&mut self, message: ChangedStage, ctx: &mut xtra::Context) -> Self::Return { - warn!("todo: implement changed stage"); let event = packet::Packet { kind: packet::Kind::StageChanged, data: packet::StageChanged { @@ -381,7 +378,6 @@ pub mod packet { use anyhow::bail; use newtype_enum::newtype_enum; use tokio::io::{AsyncReadExt, AsyncWriteExt}; - use tracing::info; use wtransport::{RecvStream, SendStream}; use zerocopy::{FromZeros, Immutable, IntoBytes}; use Event_variants::{Answer, Candidate, Offer, TargetChanged}; @@ -478,8 +474,6 @@ pub mod packet { Ok(()) } - info!("writing event: {self:?}"); - match self { Self::Offer(offer) => { send.write_u8(Kind::Offer as u8).await?; @@ -504,7 +498,6 @@ pub mod packet { pub async fn deserialize(recv: &mut RecvStream) -> anyhow::Result { async fn read_string(recv: &mut RecvStream) -> anyhow::Result { let size = recv.read_u32_le().await?; - info!("string size: {size}"); let mut data = vec![0; size as usize]; recv.read_exact(&mut data).await?; Ok(StdString::from_utf8(data)?) @@ -512,12 +505,10 @@ pub mod packet { async fn read_fixed_string(recv: &mut RecvStream) -> anyhow::Result> { let mut str = String::new_zeroed(); recv.read_exact(str.as_mut_bytes()).await?; - info!("read id: {str}"); Ok(str) } let kind = recv.read_u8().await?; - info!("reading kind: {kind}"); let event = match kind { 0 => Self::Offer(Offer { id: read_fixed_string(recv).await?,