Privacy policy: the web UI exposes a
/privacypage that renders aprivacy.mdfile in the repo root. Update that markdown with your actual policy before deploying.Terms of service: a similar
/termspage readsterms.mdin the repository root. Create or edit that file to communicate any legal or usage terms.Changelog: the UI now exposes a
/changelogpage that displays the contents ofchangelog.mdwith version filtering. The previous dialog component has been removed, so updating the page is the only way to display changelog content now.
A Discord bot by RustedAperture.
NOTE: This bot is self-hosted. It supports multiple guilds but requires you to run it.
- Create a
.envfile in the project root (or set environment variables):
DISCORD_TOKEN=
DISCORD_CLIENT_ID=
DEFAULT_GUILD_ID= # optional
NEXT_PUBLIC_SITE_URL=http://localhost:3000
(You can also provide these via Docker environment variables; legacy config.json support remains temporarily but is deprecated.)
- Install deps and run in dev mode:
npm install
# run the bot backend
npm run dev
# run the web UI (in a separate shell)
npm run dev:web- Use
/setupin a guild channel to configure the bot. The web UI will be available athttp://localhost:3000whennpm run dev:webis running.
We provide a production-ready multi-stage Dockerfile and a GitHub Action that can push multi-arch images to GHCR.
- Build locally (tested):
- Build bot-only image (uses `libsql` now via `@libsql/client`)
docker buildx build --target bot -t petbot-bot:local .
# Build web-only image
docker buildx build --target web -t petbot-web:local .- Run with mounted data & config (web UI exposed on port 3000) using docker-compose:
# start both services (uses local images if tagged appropriately)
docker compose up --buildOr run a single service directly:
# Run bot container (internal API on 3001)
docker run --rm --name petbot-bot -v "$(pwd)/data":/home/node/app/data -e DISCORD_TOKEN="$DISCORD_TOKEN" petbot-bot:local
# Run web container (serves UI on 3000)
docker run --rm --name petbot-web -p 3000:3000 -v "$(pwd)/data":/home/node/app/data -e NEXT_PUBLIC_SITE_URL="http://localhost:3000" petbot-web:localAfter the containers start, open http://localhost:3000 to access the web UI.
The image's
entrypointscript ensuresdata/permissions and will warn if required environment variables are not set.
We publish multi-arch images to GHCR. Example (replace <owner>):
docker pull ghcr.io/<owner>/petbot:latestOr pull a semver-tagged release:
docker pull ghcr.io/<owner>/petbot:1.2.3If you want to run on Unraid, map ./data and set the required environment variables (do not commit secrets). Legacy config.json mounting is supported temporarily but using env vars is recommended.
- CI switched from
pnpmtonpm(usesnpm cifor reproducible installs). - Publish workflow:
.github/workflows/publish-ghcr.ymlbuilds and pushes multi-arch images (amd64 & arm64) to GHCR. - Token: workflow prefers a
CR_PATrepo secret (falls back toGITHUB_TOKEN). If you want an explicit PAT create one with Packages: write and add it asCR_PATin repo secrets.
This project uses TypeScript and compiles to dist/ using tsc (the Dockerfile copies compiled files into the runtime image). Alternatives are possible (e.g., bundling with esbuild/tsup) but dist/ is simple and reliable for Node services.
If you prefer a single-file bundle for smaller images, I can convert the build to esbuild and update the Dockerfile.
To enable "Sign in with Discord" for the web UI you must set the following environment variables for the apps/web Next app (development: use your shell or .env):
DISCORD_CLIENT_ID— OAuth application client idDISCORD_CLIENT_SECRET— OAuth application client secretNEXT_PUBLIC_SITE_URL— public URL for the site (defaults tohttp://localhost:3000in dev)
Security: the OAuth flow now generates a cryptographically-random state value and stores it in a short-lived HttpOnly cookie; the callback validates that state to protect against CSRF/session-fixation.
Register a Discord OAuth application and configure its redirect URI to: https://<your-site>/api/auth/discord/callback (or http://localhost:3000/api/auth/discord/callback for local dev).
- The web UI is served by Next.js at port
3000when running vianpm run dev:webor the container. - If native DB modules fail, ensure you build the image on the target architecture or use multi-arch images (we publish
linux/amd64andlinux/arm64). The Dockerfile useslibsql(@libsql/client) for the embedded DB. - The Dockerfile installs build deps in the builder stage so native modules are compiled for the image.
- If your database was created by a pre‑v8.2.4 release that used Sequelize/Umzug, first start that older release (for example
v8.2.3) so its legacy Umzug migrations run and copy per‑action default image fields into the newdefault_imagesJSON map. The migration named11_migrate_legacy_defaultsis part of the legacy Sequelize/Umzug migrations and is not present in the consolidated Drizzle migration set. After the legacy step has run (only required for existing Sequelize databases), upgrade to v8.2.4+ and run Drizzle migrations withnpx drizzle-kit migrate(fresh installs can skip the legacy step).
- Important: versions before
v8.2.4used Sequelize + Umzug migrations. The project now uses a single consolidated Drizzle SQL migration for fresh installs. - If you have an existing database managed by an older release, first start the bot once at
v8.2.3(or the latest pre-migration release) so Sequelize/Umzug can run legacy migrations against your DB.- Example (checkout and run the older release locally):
git checkout v8.2.3 npm ci npm run build && npm run start # allows Umzug/Sequelize migrations to run once
- Example (checkout and run the older release locally):
- After the legacy migrations have been applied, switch to
v8.2.3(or later) and start normally. Fresh installs can skip the v8.2.2 step — Drizzle's single migration will create the correct schema. - To manually apply the consolidated Drizzle migration on a fresh environment, use:
npx drizzle-kit migrate
The bot validates user-provided image URLs to reduce SSRF and network-probing risks.
Key behaviors:
- Only
httpandhttpsschemes are accepted. - Hosts that resolve to private or loopback addresses (e.g.,
127.0.0.0/8,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16, IPv6::1,fe80::/10,fc00::/7) are blocked by default. - Requests use a short timeout (5s) and do not follow redirects.
- Optionally restrict allowed hosts with
ALLOWED_IMAGE_HOSTS(a comma-separated list of hostnames). Subdomains are allowed (e.g.,cdn.example.commatchesexample.com).
HTTP API access (internal-only by default)
- The bot's HTTP API (default port
3001) is intended to be accessed only by the local Next.js frontend—it is bound to localhost by default so external callers cannot reach it. - Environment variables:
HTTP_HOST— host/interface the API server binds to (defaults to127.0.0.1).HTTP_PORT— port the API listens on (defaults to3001).INTERNAL_API_SECRET— optional secret; when set, callers must includex-internal-api-key: <secret>on requests.
- Recommended deployment: expose only port
3000(Next.js) to the public internet and keep the bot API internal/private.
Security guidance:
- Do not bind the API to a public interface in production unless you have a strong reason and you protect it (e.g., with
INTERNAL_API_SECRETand firewall rules). - If running in containers, use a private Docker network or host networking so only the Next.js container can reach the bot API.
Examples (do not commit these files/values into source control):
- Docker run:
docker run -e ALLOWED_IMAGE_HOSTS='example.com,images.example.net' ...- Docker Compose override (create
docker-compose.override.ymland keep it out of version control):
services:
petbot:
environment:
- ALLOWED_IMAGE_HOSTS=example.com,images.example.netIf you want stricter controls, consider enforcing an allowlist in production and/or adding monitoring for blocked attempts.
If you want, I can add a short Usage section with example docker-compose.yml for Unraid and commit it to this PR.