3 Commits

Author SHA1 Message Date
58bd07d556 chore: remove unnecessary step
All checks were successful
Create and publish a Docker image / build-and-push-image (push) Successful in 1m29s
Default docker image in Gitea runner now ships with Docker
2023-12-19 20:09:05 +01:00
ad014dd1ae chore: simplify Dockerfile 2023-12-19 20:09:05 +01:00
60a81f66a8 feat: Enable Docker deployment and CD
All checks were successful
Create and publish a Docker image / build-and-push-image (push) Successful in 8m1s
Closes #8, partially addresses #6
2023-11-26 04:22:56 +01:00
17 changed files with 319 additions and 479 deletions

View File

@@ -1,32 +1,3 @@
# Include any files or directories that you don't want to be copied to your /assets
# container here (e.g., local build artifacts, temporary files, etc.). /.sqlx
# /.env.example
# For more help, visit the .dockerignore file reference guide at
# https://docs.docker.com/go/build-context-dockerignore/
**/.DS_Store
**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/charts
**/docker-compose*
**/compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/secrets.dev.yaml
**/values.dev.yaml
/bin
/target
LICENSE
README.md

View File

@@ -1 +1,5 @@
DISCORD_TOKEN=changeme DISCORD_TOKEN=changeme
# Only useful when developing locally. Do not change it when deploying
# with Docker.
DATABASE_URL=sqlite:p4bl0t.db

View File

@@ -2,7 +2,7 @@ name: Create and publish a Docker image
on: on:
push: push:
branches: ['main', 'feature/cd'] branches: ['main', 'feature/docker']
env: env:
REGISTRY: labs.phundrak.com REGISTRY: labs.phundrak.com

1
.gitignore vendored
View File

@@ -1,3 +1,4 @@
/target /target
/.env /.env
*.db *.db
/.sqlx/

View File

@@ -1,12 +0,0 @@
{
"db_name": "SQLite",
"query": "\nINSERT INTO guild_log_channels (guild_id, channel_id)\nVALUES ( ?1, ?2 )",
"describe": {
"columns": [],
"parameters": {
"Right": 2
},
"nullable": []
},
"hash": "5b44991d1514160fa00572e398f0577ad44f839a0470f9eeb89da8b5e77f0e03"
}

View File

@@ -1,20 +0,0 @@
{
"db_name": "SQLite",
"query": "\nSELECT channel_id\nFROM guild_log_channels\nWHERE guild_id = ?1",
"describe": {
"columns": [
{
"name": "channel_id",
"ordinal": 0,
"type_info": "Int64"
}
],
"parameters": {
"Right": 1
},
"nullable": [
false
]
},
"hash": "8444f7b7452a5ace6352aef943274f8a345a958257d896c7658b7700557959ab"
}

View File

@@ -1,12 +0,0 @@
{
"db_name": "SQLite",
"query": "\nDELETE FROM guild_log_channels\nWHERE guild_id = ?1 AND channel_id = ?2",
"describe": {
"columns": [],
"parameters": {
"Right": 2
},
"nullable": []
},
"hash": "d6e9f422d6ae29a00658f55165018119d1e13d407266440415dfcc17a97ba00e"
}

494
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -10,12 +10,12 @@ homepage = "https://github.com/phundrak/p4bl0t"
repository = "https://github.com/phundrak/p4bl0t" repository = "https://github.com/phundrak/p4bl0t"
keywords = ["discord", "bot", "logging"] keywords = ["discord", "bot", "logging"]
publish = false publish = false
build = "build.rs"
[dependencies] [dependencies]
color-eyre = "0.6.2" color-eyre = "0.6.2"
dotenvy = "0.15.7"
poise = { version = "0.5.7" } poise = { version = "0.5.7" }
sqlx = { version = "0.7.3", features = ["sqlite", "tls-rustls", "runtime-tokio-rustls"] } sqlx = { version = "0.7.2", features = ["sqlite", "tls-rustls", "runtime-tokio-rustls"] }
tokio = { version = "1.34.0", features = ["macros", "rt-multi-thread"] } tokio = { version = "1.34.0", features = ["macros", "rt-multi-thread"] }
tracing = "0.1.40" tracing = "0.1.40"
tracing-subscriber = "0.3.18" tracing-subscriber = "0.3.18"

View File

@@ -1,74 +1,30 @@
# syntax=docker/dockerfile:1
# Comments are provided throughout this file to help you get started.
# If you need more help, visit the Dockerfile reference guide at
# https://docs.docker.com/go/dockerfile-reference/
ARG RUST_VERSION=1.73.0 ARG RUST_VERSION=1.73.0
ARG APP_NAME=p4bl0t FROM rust:${RUST_VERSION}-slim-bullseye AS build
################################################################################ RUN --mount=type=cache,target=/usr/local/cargo/registry \
# xx is a helper for cross-compilation. cargo install sqlx-cli --no-default-features --features rustls,sqlite && \
# See https://github.com/tonistiigi/xx/ for more information. cp /usr/local/cargo/bin/sqlx /bin/sqlx
FROM --platform=$BUILDPLATFORM tonistiigi/xx:1.3.0 AS xx
ENV DATABASE_URL=sqlite:/var/p4bl0t.db
################################################################################
# Create a stage for building the application.
FROM --platform=$BUILDPLATFORM rust:${RUST_VERSION}-alpine AS build
ARG APP_NAME
WORKDIR /app WORKDIR /app
# Copy cross compilation utilities from the xx stage.
COPY --from=xx / /
# Install host build dependencies.
RUN apk add --no-cache clang lld musl-dev git file
# This is the architecture youre building for, which is passed in by the builder.
# Placing it here allows the previous steps to be cached across architectures.
ARG TARGETPLATFORM
# Install cross compilation build dependencies.
RUN xx-apk add --no-cache musl-dev gcc
# Build the application.
# Leverage a cache mount to /usr/local/cargo/registry/
# for downloaded dependencies, a cache mount to /usr/local/cargo/git/db
# for git repository dependencies, and a cache mount to /app/target/ for
# compiled dependencies which will speed up subsequent builds.
# Leverage a bind mount to the src directory to avoid having to copy the
# source code into the container. Once built, copy the executable to an
# output directory before the cache mounted /app/target is unmounted.
RUN --mount=type=bind,source=src,target=src \ RUN --mount=type=bind,source=src,target=src \
--mount=type=bind,source=Cargo.toml,target=Cargo.toml \ --mount=type=bind,source=Cargo.toml,target=Cargo.toml \
--mount=type=bind,source=Cargo.lock,target=Cargo.lock \ --mount=type=bind,source=Cargo.lock,target=Cargo.lock \
--mount=type=bind,source=build.rs,target=build.rs \
--mount=type=bind,source=.sqlx,target=.sqlx \
--mount=type=bind,source=migrations,target=migrations \ --mount=type=bind,source=migrations,target=migrations \
--mount=type=cache,target=/app/target/,id=rust-cache-${APP_NAME}-${TARGETPLATFORM} \ --mount=type=cache,target=/app/target/ \
--mount=type=cache,target=/usr/local/cargo/git/db \ --mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=/usr/local/cargo/registry/ \
<<EOF <<EOF
set -e set -e
xx-cargo build --locked --release --target-dir ./target sqlx database create
cp ./target/$(xx-cargo --print-target-triple)/debug/$APP_NAME /bin/server sqlx migrate run
xx-verify /bin/server cargo install --locked --path .
EOF EOF
################################################################################
# Create a new stage for running the application that contains the minimal
# runtime dependencies for the application. This often uses a different base
# image from the build stage where the necessary files are copied from the build
# stage.
#
# The example below uses the alpine image as the foundation for running the app.
# By specifying the "3.18" tag, it will use version 3.18 of alpine. If
# reproducability is important, consider using a digest
# (e.g., alpine@sha256:664888ac9cfd28068e062c991ebcff4b4c7307dc8dd4df9e728bedde5c449d91).
FROM alpine:3.19 AS final
# Create a non-privileged user that the app will run under. FROM debian:bullseye-slim AS final
# See https://docs.docker.com/go/dockerfile-user-best-practices/
RUN apt-get update && apt-get install -qqy ca-certificates
ARG UID=10001 ARG UID=10001
RUN adduser \ RUN adduser \
--disabled-password \ --disabled-password \
@@ -78,15 +34,12 @@ RUN adduser \
--no-create-home \ --no-create-home \
--uid "${UID}" \ --uid "${UID}" \
appuser appuser
WORKDIR /app
RUN chown -R appuser /app
USER appuser USER appuser
# Copy the executable from the "build" stage. ENV DATABASE_URL=sqlite:/var/p4bl0t.db
COPY --from=build /bin/server /bin/ ENV DISCORD_TOKEN=changeme
# Expose the port that the application listens on. COPY --from=build /usr/local/cargo/bin/p4bl0t /bin
# EXPOSE 8080 COPY --chown=appuser --from=build /var/p4bl0t.db /var/p4bl0t.db
# What the container should run when it is started. CMD [ "p4bl0t" ]
CMD ["/bin/server"]

View File

@@ -3,24 +3,11 @@
p4bl0t is a simple logging bot for Discord written in Rust. p4bl0t is a simple logging bot for Discord written in Rust.
## Usage ## Usage
### Preparation
In order to run p4bl0t, you will need a Discord token with which your In order to run p4bl0t, head over to your [developer
bot will authenticate. Head over to your [developer
portal](https://discord.com/developers) on Discords website, and portal](https://discord.com/developers) on Discords website, and
create a bot there. You will be able to get the bots token there. create a bot there. Then, copy the `.env.example` file to a `.env`
file and fill in the details.
### Docker
The easiest way to run p4bl0t is using Docker. Copy
`docker-compose.example.yml` to `docker-compose.yml` and modify the
`DISCORD_TOKEN` variable.
Then, you can simply run
```sh
docker compose up # or docker-compose on some machines
```
### Building and running it yourself
Copy the `.env.example` file to a `.env` file and fill in the details.
```sh ```sh
cp .env.example .env cp .env.example .env
emacs .env emacs .env
@@ -40,6 +27,7 @@ cargo install sqlx-cli
Setup your SQLite database. Setup your SQLite database.
```sh ```sh
export DATABASE_URL=<your-database-url> # should be the same as in the .env file
sqlx database create sqlx database create
sqlx migrate run sqlx migrate run
``` ```

View File

@@ -1,5 +0,0 @@
// generated by `sqlx migrate build-script`
fn main() {
// trigger recompilation when a new migration is added
println!("cargo:rerun-if-changed=migrations");
}

View File

@@ -1,9 +0,0 @@
services:
p4bl0t:
build:
context: .
target: final
environment:
DISCORD_TOKEN: changeme
volumes:
- ./p4bl0t.db:/app/p4bl0t.db

8
docker-compose.yml Normal file
View File

@@ -0,0 +1,8 @@
version: '3'
services:
p4bl0t:
env_file: .env
build:
context: .
target: final

View File

@@ -1,8 +1,10 @@
#![allow(clippy::cast_possible_wrap, clippy::cast_sign_loss)] #![allow(clippy::cast_possible_wrap, clippy::cast_sign_loss)]
use std::env;
use poise::serenity_prelude::{ChannelId, GuildId}; use poise::serenity_prelude::{ChannelId, GuildId};
use sqlx::{migrate::MigrateDatabase, Sqlite, SqlitePool}; use sqlx::SqlitePool;
use tracing::{error, info, debug}; use tracing::{error, info};
pub type Result<T> = ::std::result::Result<T, sqlx::Error>; pub type Result<T> = ::std::result::Result<T, sqlx::Error>;
@@ -14,23 +16,20 @@ impl Database {
/// The Sqlite database should already exist and have its /// The Sqlite database should already exist and have its
/// migrations already executed. /// migrations already executed.
/// ///
/// # Panics
///
/// Panics if the environment variable `DATABASE_URL` is not set.
///
/// # Errors /// # Errors
/// ///
/// This function will return an error if the Sqlite pool fails to /// This function will return an error if the Sqlite pool fails to
/// create. /// create.
// TODO: Create the database if it doesnt exist already and run migrations
pub async fn new() -> Result<Self> { pub async fn new() -> Result<Self> {
let url = "sqlite:p4bl0t.db"; let db_url = env::var("DATABASE_URL")
if !Sqlite::database_exists(url).await? { .expect("Missing enviroment variable DATABASE_URL");
info!("Creating database"); info!("Connecting to database located at {db_url}");
Sqlite::create_database(url).await?; Ok(Self(SqlitePool::connect(&db_url).await?))
info!("Database created");
}
debug!("Getting pool connection");
let pool = SqlitePool::connect(url).await?;
info!("Running migrations");
sqlx::migrate!().run(&pool).await?;
debug!("Database initialized");
Ok(Self(pool))
} }
/// Return from database all channels registered as loggers for a /// Return from database all channels registered as loggers for a

View File

@@ -1,9 +1,10 @@
mod commands; mod commands;
pub mod error;
mod events; mod events;
pub mod utils; pub mod utils;
pub mod error;
use poise::FrameworkBuilder; use poise::FrameworkBuilder;
use tracing::info;
use utils::serenity; use utils::serenity;
use commands::logging; use commands::logging;
@@ -19,24 +20,30 @@ pub type Result = ::std::result::Result<(), Error>;
/// ///
/// Panics if the environment `DISCORD_TOKEN` is unavailable. /// Panics if the environment `DISCORD_TOKEN` is unavailable.
pub fn make_bot() -> FrameworkBuilder<BotData, Error> { pub fn make_bot() -> FrameworkBuilder<BotData, Error> {
poise::Framework::builder() match std::env::var("DISCORD_TOKEN") {
.options(poise::FrameworkOptions { Ok(token) => {
commands: vec![logging()], info!("Launching bot with token {token}");
event_handler: |ctx, event, framework, data| { poise::Framework::builder()
Box::pin(event_handler(ctx, event, framework, data)) .options(poise::FrameworkOptions {
}, commands: vec![logging()],
..Default::default() event_handler: |ctx, event, framework, data| {
}) Box::pin(event_handler(ctx, event, framework, data))
.token(std::env::var("DISCORD_TOKEN").expect("missing DISCORD_TOKEN")) },
.intents(serenity::GatewayIntents::non_privileged()) ..Default::default()
.setup(|ctx, _ready, framework| { })
Box::pin(async move { .token(token)
poise::builtins::register_globally( .intents(serenity::GatewayIntents::non_privileged())
ctx, .setup(|ctx, _ready, framework| {
&framework.options().commands, Box::pin(async move {
) poise::builtins::register_globally(
.await?; ctx,
Ok(BotData::new().await?) &framework.options().commands,
}) )
}) .await?;
Ok(BotData::new().await?)
})
})
}
Err(_) => panic!("DISCORD_TOKEN environment variable is missing."),
}
} }

View File

@@ -6,11 +6,18 @@ mod utils;
use std::error::Error; use std::error::Error;
use tracing::info;
#[tokio::main] #[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> { async fn main() -> Result<(), Box<dyn Error>> {
println!("Setting logging up");
utils::setup_logging(); utils::setup_logging();
info!("Setting up color_eyre");
color_eyre::install()?; color_eyre::install()?;
info!("Reading from dotenv");
let _ =
dotenvy::dotenv().map_err(|_| info!("No .env file found, skipping"));
info!("Launching bot");
let bot = discord::make_bot(); let bot = discord::make_bot();
bot.run().await?; bot.run().await?;