Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
name: Deploy

on:
push:
branches: [main, dev]

env:
CARGO_TERM_COLOR: always

jobs:
deploy:
name: Deploy to ${{ github.ref_name == 'main' && 'Production' || 'Development' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'

- name: Install pnpm
run: npm install -g pnpm

- name: Install dependencies
run: pnpm install

- name: Deploy Convex (Dev)
if: github.ref_name == 'dev'
run: npx convex deploy
env:
CONVEX_DEPLOY_KEY: ${{ secrets.CONVEX_DEPLOY_KEY_DEV }}

- name: Deploy Convex (Prod)
if: github.ref_name == 'main'
run: npx convex deploy
env:
CONVEX_DEPLOY_KEY: ${{ secrets.CONVEX_DEPLOY_KEY_PROD }}

- name: Setup Rust
uses: dtolnay/rust-toolchain@stable

- name: Cache Cargo
uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}

- name: Build register_commands
run: cargo build --release --bin register_commands

- name: Register Discord Commands (Dev)
if: github.ref_name == 'dev'
run: ./target/release/register_commands
env:
DISCORD_BOT_TOKEN: ${{ secrets.DISCORD_BOT_TOKEN_DEV }}
DISCORD_APPLICATION_ID: ${{ secrets.DISCORD_APPLICATION_ID_DEV }}

- name: Register Discord Commands (Prod)
if: github.ref_name == 'main'
run: ./target/release/register_commands
env:
DISCORD_BOT_TOKEN: ${{ secrets.DISCORD_BOT_TOKEN_PROD }}
DISCORD_APPLICATION_ID: ${{ secrets.DISCORD_APPLICATION_ID_PROD }}
28 changes: 3 additions & 25 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,39 +8,17 @@ COPY . .
RUN cargo chef prepare --recipe-path recipe.json

FROM chef AS builder
# Install build dependencies including perl for OpenSSL, Node.js for Convex
# Install build dependencies
RUN apt-get update && apt-get install -y \
pkg-config libssl-dev perl make gcc curl \
&& curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
&& apt install nodejs -y \
&& npm install -g pnpm \
pkg-config libssl-dev perl make gcc \
&& rm -rf /var/lib/apt/lists/*

# Cache dependencies
COPY --from=planner /usr/src/app/recipe.json recipe.json
RUN cargo chef cook --release --recipe-path recipe.json

# Copy source and install Node dependencies
# Copy source and build
COPY . .
RUN pnpm install

# Build the register_commands binary first
RUN cargo build --release --bin register_commands

# Deploy Convex functions using BuildKit secrets (never stored in image layers)
# Build with: --secret id=convex_key,env=CONVEX_DEPLOY_KEY
RUN --mount=type=secret,id=convex_key \
CONVEX_DEPLOY_KEY=$(cat /run/secrets/convex_key) npx convex deploy

# Register Discord commands using BuildKit secrets
# Build with: --secret id=discord_token,env=DISCORD_BOT_TOKEN --secret id=discord_app_id,env=DISCORD_APPLICATION_ID
RUN --mount=type=secret,id=discord_token \
--mount=type=secret,id=discord_app_id \
DISCORD_BOT_TOKEN=$(cat /run/secrets/discord_token) \
DISCORD_APPLICATION_ID=$(cat /run/secrets/discord_app_id) \
./target/release/register_commands

# Build the main application
RUN cargo build --release

# Runtime stage
Expand Down
10 changes: 2 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,15 +78,9 @@ cargo run

### Run with Docker

The Docker build uses BuildKit secrets to securely pass credentials without exposing them in image layers:

```bash
# Build with secrets (requires DOCKER_BUILDKIT=1)
DOCKER_BUILDKIT=1 docker build \
--secret id=convex_key,env=CONVEX_DEPLOY_KEY \
--secret id=discord_token,env=DISCORD_BOT_TOKEN \
--secret id=discord_app_id,env=DISCORD_APPLICATION_ID \
-t bytehub .
# Build the image
docker build -t bytehub .

# Run with runtime environment
docker run -p 3000:3000 --env-file .env bytehub
Expand Down
9 changes: 7 additions & 2 deletions src/discord/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,12 +124,17 @@ impl DiscordInterface for DiscordClient {
// @everyone role ID is the same as guild ID
let everyone_role_id: Id<RoleMarker> = Id::new(guild_id.get());

// Deny SEND_MESSAGES for @everyone (read-only announcements)
// Deny SEND_MESSAGES and thread permissions for @everyone (fully read-only)
let deny_perms = Permissions::SEND_MESSAGES
| Permissions::SEND_MESSAGES_IN_THREADS
| Permissions::CREATE_PUBLIC_THREADS
| Permissions::CREATE_PRIVATE_THREADS;

let overwrites = vec![PermissionOverwrite {
id: everyone_role_id.cast(),
kind: PermissionOverwriteType::Role,
allow: Permissions::VIEW_CHANNEL,
deny: Permissions::SEND_MESSAGES,
deny: deny_perms,
}];

let channel = self
Expand Down
11 changes: 11 additions & 0 deletions src/discord/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -600,7 +600,17 @@ pub async fn do_repair(state: &AppState, guild_id: &Option<String>) -> Result<St
};
let project_list = projects::list_projects_by_guild(&state.db, guild_id_str).await?;

const MAX_FORUM_REPAIRS: usize = 10; // Prevent excessive API calls

let mut forum_repair_count = 0;
for project in project_list.iter().filter(|p| p.is_approved) {
if forum_repair_count >= MAX_FORUM_REPAIRS {
repairs.push(format!(
"⚠️ Stopped after {} forum repairs to avoid rate limits. Run /repair again.",
MAX_FORUM_REPAIRS
));
break;
}
if !channel_exists(&channels, &project.forum_channel_id) {
if let Some(cat_id) = github_cat {
let name = project
Expand All @@ -619,6 +629,7 @@ pub async fn do_repair(state: &AppState, guild_id: &Option<String>) -> Result<St
)
.await?;
repairs.push(format!("✅ Recreated forum for `{}`", project.github_repo));
forum_repair_count += 1;
}
}
}
Expand Down