From c8cb29ec5292b9390eaa7f5a8243bb07dc9d33f2 Mon Sep 17 00:00:00 2001 From: arx-ein Date: Wed, 11 Feb 2026 20:00:31 +0900 Subject: [PATCH 1/4] Migrate from Shuttle to Railway (code changes by Claude Code) - Remove shuttle-runtime and shuttle-serenity deps, replace with tokio::main - Replace SecretStore with std::env::var for config - Add Dockerfile (multi-stage build), railway.toml, and .dockerignore - Update GitHub Actions workflow to deploy via Railway CLI - Update deploy.sh to use railway up - Add DATABASE_PATH env var for configurable SQLite path Co-Authored-By: Claude Opus 4.6 --- .dockerignore | 9 +++++ .github/workflows/main.yml | 42 ++++++++++-------------- Cargo.toml | 4 +-- Dockerfile | 16 +++++++++ deploy.sh | 3 +- railway.toml | 6 ++++ src/main.rs | 67 +++++++++++++++++++------------------- 7 files changed, 84 insertions(+), 63 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 railway.toml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..f291f7b --- /dev/null +++ b/.dockerignore @@ -0,0 +1,9 @@ +target/ +.git/ +.github/ +*.md +.dockerignore +Dockerfile +railway.toml +deploy.sh +generate_entity.sh diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e4a6e00..9ed1a7b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,4 +1,4 @@ -name: Shuttle Deploy +name: Railway Deploy on: push: @@ -11,33 +11,25 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set commit metadata run: | echo "COMMIT_HASH=$(git rev-parse --short HEAD)" >> $GITHUB_ENV echo "COMMIT_DATE=$(git show -s --format=%ci HEAD)" >> $GITHUB_ENV - - uses: shuttle-hq/deploy-action@v2 - with: - shuttle-api-key: ${{ secrets.SHUTTLE_API_KEY }} - project-id: ${{ secrets.SHUTTLE_PROJECT_ID || 'proj_01JCJS9WKRQ1RC9MVZW6WDJK97' }} - extra-args: --allow-dirty - secrets: | - AI_API_KEY = '${{ secrets.AI_API_KEY }}' - GEMINI_API_KEY = '${{ secrets.GEMINI_API_KEY }}' - DISCORD_TOKEN = '${{ secrets.DISCORD_TOKEN }}' - DEBUG_ROOM_ID = '${{ secrets.DEBUG_ROOM_ID }}' - FREETALK1_ROOM_ID = '${{ secrets.FREETALK1_ROOM_ID }}' - FREETALK2_ROOM_ID = '${{ secrets.FREETALK2_ROOM_ID }}' - MADSISTERS_ROOM_ID = '${{ secrets.MADSISTERS_ROOM_ID }}' - SHYBOYS_ROOM_ID = '${{ secrets.SHYBOYS_ROOM_ID }}' - SHUTTLE_API_KEY = '${{ secrets.SHUTTLE_API_KEY }}' - EROGAKI_ROLE_ID = '${{ secrets.EROGAKI_ROLE_ID }}' - JAIL_MARK_ROLE_ID = '${{ secrets.JAIL_MARK_ROLE_ID }}' - JAIL_MAIN_ROLE_ID = '${{ secrets.JAIL_MAIN_ROLE_ID }}' - DISCORD_GUILD_ID = '${{ secrets.DISCORD_GUILD_ID }}' - COMMIT_HASH = '${{ env.COMMIT_HASH }}' - COMMIT_DATE = '${{ env.COMMIT_DATE }}' - DISABLED_COMMANDS = '${{ secrets.DISABLED_COMMANDS }}' - ROOMS_ID = '${{ secrets.ROOMS_ID }}' + - name: Install Railway CLI + run: npm install -g @railway/cli + + - name: Set variables + run: | + railway variables set \ + COMMIT_HASH='${{ env.COMMIT_HASH }}' \ + COMMIT_DATE='${{ env.COMMIT_DATE }}' + env: + RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }} + + - name: Deploy + run: railway up --detach + env: + RAILWAY_TOKEN: ${{ secrets.RAILWAY_TOKEN }} diff --git a/Cargo.toml b/Cargo.toml index b3f8c17..46f9ae6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,9 +18,7 @@ serenity = { version = "0.12.4", default-features = false, features = [ "model", "chrono", ] } -shuttle-runtime = "0.55.0" -shuttle-serenity = "0.55.0" -tokio = "1.45.1" +tokio = { version = "1.45.1", features = ["macros", "rt-multi-thread"] } tracing = "0.1.41" rand_distr = "0.5.1" strum = { version = "0.27.1", features = ["derive"] } diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..0481830 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,16 @@ +FROM rust:1.87-bookworm AS builder + +WORKDIR /app +COPY . . +RUN cargo build --release + +FROM debian:bookworm-slim + +RUN apt-get update && apt-get install -y \ + libssl3 \ + ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +COPY --from=builder /app/target/release/udamanami /usr/local/bin/udamanami + +CMD ["udamanami"] diff --git a/deploy.sh b/deploy.sh index cbbcd43..4463714 100755 --- a/deploy.sh +++ b/deploy.sh @@ -1,2 +1 @@ -cargo shuttle deploy --allow-dirty --no-test -idle-minutes 0 -cargo shuttle project statuscar +railway up --detach diff --git a/railway.toml b/railway.toml new file mode 100644 index 0000000..97ab113 --- /dev/null +++ b/railway.toml @@ -0,0 +1,6 @@ +[build] +dockerfilePath = "Dockerfile" + +[deploy] +restartPolicyType = "ON_FAILURE" +restartPolicyMaxRetries = 10 diff --git a/src/main.rs b/src/main.rs index 9a49e6b..d126280 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -use std::{convert::Into, str::FromStr}; +use std::{env, str::FromStr}; use anyhow::Context as _; @@ -6,27 +6,30 @@ use serenity::{ model::id::{ChannelId, GuildId, RoleId}, prelude::*, }; -use shuttle_runtime::SecretStore; use udamanami::ai; use udamanami::db::BotDatabase; use udamanami::Bot; -#[shuttle_runtime::main] -async fn serenity( - #[shuttle_runtime::Secrets] secrets: SecretStore, -) -> shuttle_serenity::ShuttleSerenity { - // Get the discord token set in `Secrets.toml` - let token = secrets - .get("DISCORD_TOKEN") - .context("'DISCORD_TOKEN' was not found")?; +fn env_var(key: &str) -> Option { + env::var(key).ok() +} + +fn env_var_required(key: &str) -> anyhow::Result { + env::var(key).with_context(|| format!("'{key}' was not found")) +} + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + // Get the discord token set in environment variables + let token = env_var_required("DISCORD_TOKEN")?; // Set gateway intents, which decides what events the bot will be notified about let intents = GatewayIntents::GUILD_MESSAGES | GatewayIntents::MESSAGE_CONTENT | GatewayIntents::DIRECT_MESSAGES; - let channel_ids_new = secrets.get("ROOMS_ID").map_or(vec![], |rooms| { + let channel_ids_new = env_var("ROOMS_ID").map_or(vec![], |rooms| { rooms .split(',') .filter_map(|id| ChannelId::from_str(id.trim()).ok()) @@ -35,12 +38,12 @@ async fn serenity( // ↓ここからのコードは将来的に取り除きたい let channel_ids_old: Vec = vec![ - secrets.get("FREETALK1_ROOM_ID"), - secrets.get("FREETALK2_ROOM_ID"), - secrets.get("MADSISTERS_ROOM_ID"), - secrets.get("SHYBOYS_ROOM_ID"), - secrets.get("DEBUG_ROOM_ID"), - secrets.get("HOSPITAL_ROOM_ID"), + env_var("FREETALK1_ROOM_ID"), + env_var("FREETALK2_ROOM_ID"), + env_var("MADSISTERS_ROOM_ID"), + env_var("SHYBOYS_ROOM_ID"), + env_var("DEBUG_ROOM_ID"), + env_var("HOSPITAL_ROOM_ID"), ] .into_iter() .filter_map(|id| id.and_then(|id| ChannelId::from_str(&id).ok())) @@ -53,13 +56,11 @@ async fn serenity( }; // ↑ここまで - let debug_channel_id = secrets - .get("DEBUG_ROOM_ID") + let debug_channel_id = env_var("DEBUG_ROOM_ID") .map(|id| ChannelId::from_str(&id).unwrap()) .unwrap_or_default(); - let disabled_commands = secrets - .get("DISABLED_COMMANDS") + let disabled_commands = env_var("DISABLED_COMMANDS") .map(|commands| { commands .split(',') @@ -72,28 +73,26 @@ async fn serenity( .map(|s| s.as_str()) .collect::>(); - let guild_id = secrets - .get("DISCORD_GUILD_ID") + let guild_id = env_var("DISCORD_GUILD_ID") .map(|id| GuildId::from_str(&id).unwrap()) .unwrap(); - let jail_mark_role_id = secrets - .get("JAIL_MARK_ROLE_ID") + let jail_mark_role_id = env_var("JAIL_MARK_ROLE_ID") .map(|id| RoleId::from_str(&id).unwrap()) .unwrap(); - let jail_main_role_id = secrets - .get("JAIL_MAIN_ROLE_ID") + let jail_main_role_id = env_var("JAIL_MAIN_ROLE_ID") .map(|id| RoleId::from_str(&id).unwrap()) .unwrap(); - let commit_hash = secrets.get("COMMIT_HASH"); + let commit_hash = env_var("COMMIT_HASH"); - let commit_date = secrets.get("COMMIT_DATE"); + let commit_date = env_var("COMMIT_DATE"); - let gemini = ai::GeminiAI::manami(&secrets.get("GEMINI_API_KEY").unwrap()); + let gemini = ai::GeminiAI::manami(&env_var_required("GEMINI_API_KEY")?); - let database = BotDatabase::new("./db.sqlite").await?; + let database_path = env_var("DATABASE_PATH").unwrap_or_else(|| "./db.sqlite".to_owned()); + let database = BotDatabase::new(&database_path).await?; let bot = Bot::new( channel_ids, @@ -109,10 +108,12 @@ async fn serenity( ) .await; - let client = Client::builder(&token, intents) + let mut client = Client::builder(&token, intents) .event_handler(bot) .await .expect("Err creating client"); - Ok(client.into()) + client.start().await?; + + Ok(()) } From 4da8c2335cefd65dc72bfb1d9ef79f27b68c0170 Mon Sep 17 00:00:00 2001 From: arx-ein Date: Thu, 12 Feb 2026 00:38:03 +0900 Subject: [PATCH 2/4] Bump GitHub Actions --- .github/workflows/ci.yml | 2 +- .github/workflows/main.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 63a8604..1d35219 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,7 +10,7 @@ jobs: ci: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - uses: dtolnay/rust-toolchain@nightly with: components: clippy diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9ed1a7b..1c03d5a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Set commit metadata run: | From d80b54bc9279d7a8a245351c68a322d544b47e70 Mon Sep 17 00:00:00 2001 From: arx-ein Date: Wed, 11 Feb 2026 20:55:19 +0900 Subject: [PATCH 3/4] Fix Dockerfile to make sure it builds & runs --- Dockerfile | 22 ++++++++++++++-------- src/main.rs | 4 ++-- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/Dockerfile b/Dockerfile index 0481830..3b3af27 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,16 +1,22 @@ -FROM rust:1.87-bookworm AS builder - +FROM lukemathwalker/cargo-chef:latest-rust-1 AS chef WORKDIR /app + +FROM chef AS planner COPY . . -RUN cargo build --release +RUN cargo chef prepare --recipe-path recipe.json -FROM debian:bookworm-slim +FROM chef AS builder +COPY --from=planner /app/recipe.json recipe.json +RUN --mount=type=cache,target=/usr/local/cargo/registry,sharing=locked \ + --mount=type=cache,target=/usr/local/cargo/git,sharing=locked \ + cargo chef cook --release --recipe-path recipe.json -RUN apt-get update && apt-get install -y \ - libssl3 \ - ca-certificates \ - && rm -rf /var/lib/apt/lists/* +COPY . . +RUN --mount=type=cache,target=/usr/local/cargo/registry,sharing=locked \ + --mount=type=cache,target=/usr/local/cargo/git,sharing=locked \ + cargo build --release +FROM debian:trixie-slim COPY --from=builder /app/target/release/udamanami /usr/local/bin/udamanami CMD ["udamanami"] diff --git a/src/main.rs b/src/main.rs index d126280..9b0d5f1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -79,11 +79,11 @@ async fn main() -> anyhow::Result<()> { let jail_mark_role_id = env_var("JAIL_MARK_ROLE_ID") .map(|id| RoleId::from_str(&id).unwrap()) - .unwrap(); + .unwrap_or_default(); let jail_main_role_id = env_var("JAIL_MAIN_ROLE_ID") .map(|id| RoleId::from_str(&id).unwrap()) - .unwrap(); + .unwrap_or_default(); let commit_hash = env_var("COMMIT_HASH"); From 688d9ce3bbfdfd7e46f68b43ecac9683684b5479 Mon Sep 17 00:00:00 2001 From: arx-ein Date: Thu, 12 Feb 2026 01:33:39 +0900 Subject: [PATCH 4/4] Remove cache mount to make it portable --- Dockerfile | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/Dockerfile b/Dockerfile index 3b3af27..0ad6bfc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,3 +1,5 @@ +# syntax=docker/dockerfile:1 + FROM lukemathwalker/cargo-chef:latest-rust-1 AS chef WORKDIR /app @@ -7,14 +9,10 @@ RUN cargo chef prepare --recipe-path recipe.json FROM chef AS builder COPY --from=planner /app/recipe.json recipe.json -RUN --mount=type=cache,target=/usr/local/cargo/registry,sharing=locked \ - --mount=type=cache,target=/usr/local/cargo/git,sharing=locked \ - cargo chef cook --release --recipe-path recipe.json +RUN cargo chef cook --release --recipe-path recipe.json COPY . . -RUN --mount=type=cache,target=/usr/local/cargo/registry,sharing=locked \ - --mount=type=cache,target=/usr/local/cargo/git,sharing=locked \ - cargo build --release +RUN cargo build --release FROM debian:trixie-slim COPY --from=builder /app/target/release/udamanami /usr/local/bin/udamanami