feat: reorganize with remote

This commit is contained in:
Jet Pham 2026-03-10 19:43:24 -07:00 committed by Jet
parent a74e5753fa
commit dc7b8cbadd
28 changed files with 622 additions and 3024 deletions

File diff suppressed because it is too large Load diff

View file

@ -6,9 +6,11 @@ edition = "2021"
[dependencies]
anyhow = "1.0"
axum = "0.8"
noisebell-common = { path = "../noisebell-common" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serenity = { version = "0.12", default-features = false, features = ["client", "gateway", "model", "rustls_backend"] }
tokio = { version = "1", features = ["rt-multi-thread", "macros", "net", "sync", "signal"] }
tokio = { version = "1", features = ["rt-multi-thread", "macros", "net", "sync", "signal", "time"] }
tower-http = { version = "0.6", features = ["trace"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }

View file

@ -1,64 +0,0 @@
{
"nodes": {
"crane": {
"locked": {
"lastModified": 1773115265,
"narHash": "sha256-5fDkKTYEgue2klksd52WvcXfZdY1EIlbk0QggAwpFog=",
"owner": "ipetkov",
"repo": "crane",
"rev": "27711550d109bf6236478dc9f53b9e29c1a374c5",
"type": "github"
},
"original": {
"owner": "ipetkov",
"repo": "crane",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1772963539,
"narHash": "sha256-9jVDGZnvCckTGdYT53d/EfznygLskyLQXYwJLKMPsZs=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "9dcb002ca1690658be4a04645215baea8b95f31d",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"crane": "crane",
"nixpkgs": "nixpkgs",
"rust-overlay": "rust-overlay"
}
},
"rust-overlay": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1773115373,
"narHash": "sha256-bfK9FJFcQth6f3ydYggS5m0z2NRGF/PY6Y2XgZDJ6pg=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "1924b4672a2b8e4aee6e6652ec2e59a8d3c5648e",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

View file

@ -1,47 +0,0 @@
{
description = "Noisebell - Discord bot";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
crane.url = "github:ipetkov/crane";
rust-overlay = {
url = "github:oxalica/rust-overlay";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = { self, nixpkgs, crane, rust-overlay }:
let
system = "x86_64-linux";
pkgs = import nixpkgs {
inherit system;
overlays = [ rust-overlay.overlays.default ];
};
rustToolchain = pkgs.rust-bin.stable.latest.default;
craneLib = (crane.mkLib pkgs).overrideToolchain rustToolchain;
src = craneLib.cleanCargoSource ./.;
commonArgs = {
inherit src;
strictDeps = true;
doCheck = false;
};
cargoArtifacts = craneLib.buildDepsOnly commonArgs;
noisebell-discord = craneLib.buildPackage (commonArgs // {
inherit cargoArtifacts;
});
in
{
packages.${system}.default = noisebell-discord;
nixosModules.default = import ./module.nix self;
devShells.${system}.default = craneLib.devShell {
packages = [ pkgs.rust-analyzer ];
};
};
}

View file

@ -1,9 +1,9 @@
self:
pkg:
{ config, lib, ... }:
let
cfg = config.services.noisebell-discord;
bin = "${self.packages.x86_64-linux.default}/bin/noisebell-discord";
bin = "${pkg}/bin/noisebell-discord";
in
{
options.services.noisebell-discord = {

View file

@ -1,34 +1,22 @@
use std::sync::Arc;
use std::time::Duration;
use anyhow::{Context, Result};
use axum::extract::State;
use axum::http::{HeaderMap, StatusCode};
use axum::routing::post;
use axum::routing::{get, post};
use axum::{Json, Router};
use serde::Deserialize;
use serenity::all::{ChannelId, Colour, CreateEmbed, CreateMessage, GatewayIntents, Http};
use tracing::{error, info};
#[derive(Deserialize)]
struct WebhookPayload {
status: String,
timestamp: u64,
}
use noisebell_common::{validate_bearer, WebhookPayload};
use serenity::all::{ChannelId, Colour, CreateEmbed, CreateMessage, GatewayIntents};
use tower_http::trace::TraceLayer;
use tracing::{error, info, Level};
struct AppState {
http: Arc<Http>,
http: Arc<serenity::all::Http>,
channel_id: ChannelId,
webhook_secret: String,
}
fn validate_bearer(headers: &HeaderMap, expected: &str) -> bool {
headers
.get("authorization")
.and_then(|v| v.to_str().ok())
.map(|v| v.strip_prefix("Bearer ").unwrap_or("") == expected)
.unwrap_or(false)
}
fn build_embed(status: &str, timestamp: u64) -> CreateEmbed {
let (colour, title, description) = match status {
"open" => (Colour::from_rgb(87, 242, 135), "Door is open", "The door at Noisebridge is open."),
@ -98,12 +86,12 @@ async fn main() -> Result<()> {
info!(port, channel_id, "starting noisebell-discord");
let intents = GatewayIntents::empty();
let mut client = serenity::Client::builder(&discord_token, intents)
let mut initial_client = serenity::Client::builder(&discord_token, intents)
.event_handler(Handler)
.await
.context("failed to create Discord client")?;
let http = client.http.clone();
let http = initial_client.http.clone();
let app_state = Arc::new(AppState {
http,
@ -112,7 +100,13 @@ async fn main() -> Result<()> {
});
let app = Router::new()
.route("/health", get(|| async { StatusCode::OK }))
.route("/webhook", post(post_webhook))
.layer(
TraceLayer::new_for_http()
.make_span_with(tower_http::trace::DefaultMakeSpan::new().level(Level::INFO))
.on_response(tower_http::trace::DefaultOnResponse::new().level(Level::INFO)),
)
.with_state(app_state);
let listener = tokio::net::TcpListener::bind(("0.0.0.0", port))
@ -121,21 +115,39 @@ async fn main() -> Result<()> {
info!(port, "webhook listener ready");
// Gateway reconnect loop — the Http client for sending messages is independent
let token_for_gateway = discord_token.clone();
tokio::spawn(async move {
if let Err(e) = initial_client.start().await {
error!(error = %e, "Discord gateway disconnected");
}
loop {
tokio::time::sleep(Duration::from_secs(5)).await;
info!("reconnecting to Discord gateway");
match serenity::Client::builder(&token_for_gateway, GatewayIntents::empty())
.event_handler(Handler)
.await
{
Ok(mut client) => {
if let Err(e) = client.start().await {
error!(error = %e, "Discord gateway disconnected");
}
}
Err(e) => {
error!(error = %e, "failed to create Discord client");
}
}
}
});
let mut sigterm = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
.context("failed to register SIGTERM handler")?;
tokio::select! {
result = client.start() => {
if let Err(e) = result {
error!(error = %e, "Discord client error");
}
}
result = axum::serve(listener, app).with_graceful_shutdown(async move { sigterm.recv().await; }) => {
if let Err(e) = result {
error!(error = %e, "HTTP server error");
}
}
}
axum::serve(listener, app)
.with_graceful_shutdown(async move {
sigterm.recv().await;
})
.await
.context("server error")?;
info!("shutdown complete");
Ok(())