chore: merge branch main into achievement-fixes
Some checks failed
Commitlint / Run commitlint scanning (push) Has been cancelled

This commit is contained in:
Ahmad 2025-06-19 23:01:51 -04:00
commit 1eb068a634
No known key found for this signature in database
GPG key ID: 8FD8A93530D182BF
16 changed files with 714 additions and 135 deletions

64
.github/workflows/docker.yml vendored Normal file
View file

@ -0,0 +1,64 @@
name: Docker Build and Push
on:
schedule:
- cron: "43 0 * * *"
push:
branches: ["main"]
tags: ["v*.*.*"]
pull_request:
branches: ["main"]
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
pgbouncer:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install cosign
uses: sigstore/cosign-installer@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log into registry ${{ env.REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract Docker metadata for pgbouncer
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-pgbouncer
- name: Build and push Docker image for pgbouncer
id: build-and-push
uses: docker/build-push-action@v6
with:
context: docker/pgbouncer
file: docker/pgbouncer/Dockerfile
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha,scope=pgbouncer
cache-to: type=gha,scope=pgbouncer,mode=max
- name: Sign the published Docker image for PgBouncer
if: ${{ github.event_name != 'pull_request' }}
env:
TAGS: ${{ steps.meta.outputs.tags }}
DIGEST: ${{ steps.build-and-push.outputs.digest }}
run: echo "${TAGS}" | xargs -I {} cosign sign --yes "{}@${DIGEST}"

View file

@ -1,8 +1,18 @@
import path from 'path';
import process from 'process';
const buildEslintCommand = (filenames) =>
`eslint ${filenames.map((f) => path.relative(process.cwd(), f)).join(' ')}`;
const buildEslintCommand = (filenames) => {
const srcDir = path.resolve(process.cwd(), 'src');
const srcFiles = filenames.filter((f) => {
const absolute = path.resolve(process.cwd(), f);
const relativeToSrc = path.relative(srcDir, absolute);
return !relativeToSrc.startsWith('..');
});
if (srcFiles.length === 0) return [];
return `eslint ${srcFiles
.map((f) => path.relative(process.cwd(), f))
.join(' ')}`;
};
const prettierCommand = 'prettier --write';

View file

@ -1,62 +0,0 @@
services:
postgres:
image: postgres:17-alpine
container_name: postgres
restart: always
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
volumes:
- ./certs/psql-server.crt:/var/lib/postgresql/server.crt:ro
- ./certs/psql-server.key:/var/lib/postgresql/server.key:ro
- postgres_data:/var/lib/postgresql/data
ports:
- '5432:5432'
command: >
postgres
-c ssl=on
-c ssl_cert_file=/var/lib/postgresql/server.crt
-c ssl_key_file=/var/lib/postgresql/server.key
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U ${POSTGRES_USER}']
interval: 10s
timeout: 5s
retries: 5
networks:
- backend
valkey:
image: valkey/valkey:8-alpine
container_name: valkey
restart: always
ports:
- '6379:6379'
volumes:
- ./certs/cache-server.crt:/certs/server.crt:ro
- ./certs/cache-server.key:/certs/server.key:ro
- ./certs/cache-ca.crt:/certs/ca.crt:ro
- valkey_data:/data
command: >
valkey-server
--requirepass ${VALKEY_PASSWORD}
--tls-port 6379
--port 0
--tls-cert-file /certs/server.crt
--tls-key-file /certs/server.key
--tls-ca-cert-file /certs/ca.crt
healthcheck:
test: ['CMD', 'valkey-cli', '-a', '${VALKEY_PASSWORD}', 'ping']
interval: 10s
timeout: 5s
retries: 5
networks:
- backend
volumes:
postgres_data:
valkey_data:
networks:
backend:
driver: bridge

View file

@ -1,4 +1,3 @@
POSTGRES_USER=your_postgres_user
POSTGRES_PASSWORD=your_postgres_password
POSTGRES_DB=your_database_name
VALKEY_PASSWORD=your_valkey_password

97
docker/docker-compose.yml Normal file
View file

@ -0,0 +1,97 @@
services:
postgres:
image: postgres:17-alpine
container_name: postgres
restart: unless-stopped
volumes:
- ../certs/psql-cert.pem:/var/lib/postgresql/cert.pem:ro
- ../certs/psql-key.pem:/var/lib/postgresql/key.pem:ro
- postgres_data:/var/lib/postgresql/data
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
command: >
postgres
-c ssl=on
-c ssl_cert_file=/var/lib/postgresql/cert.pem
-c ssl_key_file=/var/lib/postgresql/key.pem
healthcheck:
test:
[
'CMD-SHELL',
'PGPASSWORD=${POSTGRES_PASSWORD} pg_isready -U ${POSTGRES_USER} -h localhost -p 5432 --db=${POSTGRES_DB}',
]
interval: 10s
timeout: 5s
retries: 5
networks:
- services
redis:
image: redis:8-alpine
container_name: redis
restart: unless-stopped
ports:
- '6379:6379'
volumes:
- ../certs/cache-cert.pem:/usr/local/etc/redis/cert.pem:ro
- ../certs/cache-key.pem:/usr/local/etc/redis/key.pem:ro
- ../certs/rootCA.pem:/usr/local/etc/redis/ca.pem:ro
- ./redis.conf:/usr/local/etc/redis/redis.conf:ro
command: >
redis-server /usr/local/etc/redis/redis.conf
healthcheck:
test:
[
'CMD-SHELL',
'redis-cli --tls --cacert /usr/local/etc/redis/ca.pem ping | grep PONG',
]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
networks:
- services
pgbouncer:
image: ghcr.io/ahmadk953/poixpixel-discord-bot-pgbouncer
container_name: pgbouncer
restart: unless-stopped
depends_on:
- postgres
ports:
- '5432:5432'
volumes:
- ../certs/pgbouncer-cert.pem:/certs/cert.pem:ro
- ../certs/pgbouncer-key.pem:/certs/key.pem:ro
- ../certs/rootCA.pem:/certs/ca.pem:ro
environment:
DB_USER: ${POSTGRES_USER}
DB_PASSWORD: ${POSTGRES_PASSWORD}
DB_HOST: postgres
AUTH_USER: ${POSTGRES_USER}
AUTH_TYPE: scram-sha-256
POOL_MODE: transaction
ADMIN_USERS: ${POSTGRES_USER}
CLIENT_TLS_SSLMODE: require
CLIENT_TLS_CERT_FILE: /certs/cert.pem
CLIENT_TLS_KEY_FILE: /certs/key.pem
CLIENT_TLS_CA_FILE: /certs/ca.pem
SERVER_TLS_SSLMODE: require
SERVER_TLS_CA_FILE: /certs/ca.pem
healthcheck:
test:
[
'CMD-SHELL',
'PGPASSWORD=${POSTGRES_PASSWORD} pg_isready -U ${POSTGRES_USER} -h localhost -p 5432 --db=${POSTGRES_DB}',
]
networks:
- services
volumes:
postgres_data:
networks:
services:
driver: bridge

View file

@ -0,0 +1,39 @@
# Based on https://raw.githubusercontent.com/edoburu/docker-pgbouncer/master/Dockerfile
FROM alpine:3.22 AS build
LABEL org.opencontainers.image.source=https://github.com/ahmadk953/poixpixel-discord-bot/tree/main/docker/pgbouncer
LABEL org.opencontainers.image.description="Docker image for pgbouncer with c-ares support"
LABEL org.opencontainers.image.licenses=Apache-2.0
ARG PGBOUNCER_VERSION=1.24.1
ARG C_ARES_VERSION=1.34.5
RUN apk add --no-cache autoconf autoconf-doc automake curl gcc git libc-dev libevent-dev libtool make openssl-dev pandoc pkgconfig
RUN set -eux; \
curl -Lo /c-ares.tar.gz https://github.com/c-ares/c-ares/releases/download/v${C_ARES_VERSION}/c-ares-${C_ARES_VERSION}.tar.gz && \
tar -xzf /c-ares.tar.gz && mv /c-ares-${C_ARES_VERSION} /c-ares && \
cd /c-ares && ./configure && make && make install && \
curl -Lo /pgbouncer.tar.gz https://pgbouncer.github.io/downloads/files/${PGBOUNCER_VERSION}/pgbouncer-${PGBOUNCER_VERSION}.tar.gz && \
tar -xzf /pgbouncer.tar.gz -C / && mv /pgbouncer-${PGBOUNCER_VERSION} /pgbouncer && \
cd /pgbouncer && ./configure --with-cares && make && make install
FROM alpine:3.22
RUN apk add --no-cache busybox libevent postgresql-client libssl3 \
&& mkdir -p /etc/pgbouncer /var/log/pgbouncer /var/run/pgbouncer \
&& touch /etc/pgbouncer/userlist.txt \
&& addgroup -S -g 1100 pgbouncer \
&& adduser -S -u 1100 -G pgbouncer pgbouncer \
&& chown -R pgbouncer:pgbouncer /etc/pgbouncer /var/log/pgbouncer /var/run/pgbouncer
COPY --chmod=+x entrypoint.sh /entrypoint.sh
COPY --from=build /usr/local/bin /usr/local/bin
COPY --from=build /usr/local/lib /usr/local/lib
COPY --from=build /pgbouncer/etc/pgbouncer.ini /etc/pgbouncer/pgbouncer.ini.example
COPY --from=build /pgbouncer/etc/userlist.txt /etc/pgbouncer/userlist.txt.example
EXPOSE 5432
USER pgbouncer
ENTRYPOINT ["/entrypoint.sh"]
CMD ["/usr/local/bin/pgbouncer", "/etc/pgbouncer/pgbouncer.ini"]

View file

@ -0,0 +1,76 @@
# PgBouncer
PgBouncer is a lightweight connection pooler for PostgreSQL that helps optimize database connections by reusing established sessions.
## Overview
This directory contains all the necessary files to build and run PgBouncer as part of the Poixpixel Discord Bot project. It is based on Alpine Linux and includes support for c-ares.
## Contents
- **Dockerfile**: Builds the PgBouncer image with c-ares support.
- **entrypoint.sh**: Generates and configures the PgBouncer configuration file at container startup.
## Building the Docker Image
To build the PgBouncer Docker image, run:
```sh
docker build -t my-pgbouncer ./docker/pgbouncer
```
## Running the Container
Run the container with your desired environment variables. For example:
```sh
docker run --rm \
-e DATABASE_URL="postgres://user:pass@postgres-host/database" \
-p 5432:5432 \
my-pgbouncer
```
Or, if you would like to use separate environment variables:
```sh
docker run --rm \
-e DB_USER=user \
-e DB_PASSWORD=pass \
-e DB_HOST=postgres-host \
-e DB_NAME=database \
-p 5432:5432 \
my-pgbouncer
```
You can also use the prebuilt image. For example:
```sh
docker run --rm \
-e DB_USER=user \
-e DB_PASSWORD=pass \
-e DB_HOST=postgres-host \
-e DB_NAME=database \
-p 5432:5432 \
ghcr.io/ahmadk953/poixpixel-discord-bot-pgbouncer
```
## Customizing Your Setup
- **Dockerfile**: Modify build arguments or dependencies as needed.
- **entrypoint.sh**: Adjust how the configuration file is generated and updated.
- **Environment Variables**: Almost all settings found in the `pgbouncer.ini` file can be set as environment variables, except for a few system-specific configuration options. For an example, check out [the example Docker compose file](../../docker-compose.yml). For all configuration options, check the [PgBouncer configuration documentation](https://www.pgbouncer.org/config.html).
- **Configuration File**: You can specify your own `pgbouncer.ini` file by mounting it as a volume like so:
```sh
docker run --rm \
-e DB_USER=user \
-e DB_PASSWORD=pass \
-e DB_HOST=postgres-host \
-e DB_NAME=database \
-v PgBouncer.ini:/etc/PgBouncer/PgBouncer.ini:ro \
-p 5432:5432 \
ghcr.io/ahmadk953/poixpixel-discord-bot-pgbouncer
```
## License
See the [LICENSE](../../LICENSE) file in the root of the project for licensing details.

View file

@ -0,0 +1,354 @@
#!/bin/sh
# Based on https://raw.githubusercontent.com/brainsam/pgbouncer/master/entrypoint.sh
# and https://raw.githubusercontent.com/edoburu/docker-pgbouncer/master/entrypoint.sh
set -e
# Here are some parameters. See all on
# https://pgbouncer.github.io/config.html
PG_CONFIG_DIR=/etc/pgbouncer
PG_CONFIG_FILE="${PG_CONFIG_DIR}/pgbouncer.ini"
_AUTH_FILE="${AUTH_FILE:-$PG_CONFIG_DIR/userlist.txt}"
# Workaround userlist.txt missing issue
# https://github.com/edoburu/docker-pgbouncer/issues/33
if [ ! -e "${_AUTH_FILE}" ]; then
touch "${_AUTH_FILE}"
fi
# Extract all info from a given URL. Sets variables because shell functions can't return multiple values.
#
# Parameters:
# - The url we should parse
# Returns (sets variables): DB_USER, DB_PASSWORD, DB_HOST, DB_PORT, DB_NAME
parse_url() {
# Thanks to https://stackoverflow.com/a/17287984/146289
# Allow to pass values like dj-database-url / django-environ accept
proto="$(echo $1 | grep :// | sed -e's,^\(.*://\).*,\1,g')"
url="$(echo $1 | sed -e s,$proto,,g)"
# extract the user and password (if any)
userpass="$(echo $url | grep @ | sed -r 's/^(.*)@([^@]*)$/\1/')"
DB_PASSWORD="$(echo $userpass | grep : | cut -d: -f2)"
if [ -n "${DB_PASSWORD}" ]; then
DB_USER="$(echo $userpass | grep : | cut -d: -f1)"
else
DB_USER="${userpass}"
fi
# extract the host -- updated
hostport=$(echo $url | sed -e s,$userpass@,,g | cut -d/ -f1)
port=$(echo $hostport | grep : | cut -d: -f2)
if [ -n "$port" ]; then
DB_HOST=$(echo $hostport | grep : | cut -d: -f1)
DB_PORT="${port}"
else
DB_HOST="${hostport}"
fi
DB_NAME="$(echo $url | grep / | cut -d/ -f2-)"
}
# Grabs variables set by `parse_url` and adds them to the userlist if not already set in there.
generate_userlist_if_needed() {
if [ -n "${DB_USER}" ] && [ -n "${DB_PASSWORD}" ] && [ -e "${_AUTH_FILE}" ] && ! grep -q "^\"${DB_USER}\"" "${_AUTH_FILE}"; then
if [ "${AUTH_TYPE}" = "plain" ] || [ "${AUTH_TYPE}" = "scram-sha-256" ]; then
pass="${DB_PASSWORD}"
else
pass="md5$(printf '%s' "${DB_PASSWORD}${DB_USER}" | md5sum | cut -f 1 -d ' ')"
fi
echo "\"${DB_USER}\" \"${pass}\"" >>"${_AUTH_FILE}"
echo "Wrote authentication credentials for '${DB_USER}' to ${_AUTH_FILE}"
fi
}
# Grabs variables set by `parse_url` and adds them to the PG config file as a database entry.
generate_config_db_entry() {
# Prepare values
dbname=${DB_NAME:-*}
host=${DB_HOST:?"Setup pgbouncer config error! You must set DB_HOST env"}
port=${DB_PORT:-5432}
auth_user=${DB_USER:-postgres}
# Print main entry
printf '%s = host=%s port=%s auth_user=%s\n' \
"$dbname" "$host" "$port" "$auth_user" \
>>"$PG_CONFIG_FILE"
# Optional client_encoding
if [ -n "$CLIENT_ENCODING" ]; then
printf 'client_encoding = %s\n' "$CLIENT_ENCODING" \
>>"$PG_CONFIG_FILE"
fi
}
# Write the password with MD5 encryption, to avoid printing it during startup.
# Notice that `docker inspect` will show unencrypted env variables.
if [ -n "${DATABASE_URLS}" ]; then
echo "${DATABASE_URLS}" | tr ',' '\n' | while IFS= read -r url; do
parse_url "$url"
generate_userlist_if_needed
done
else
if [ -n "${DATABASE_URL}" ]; then
parse_url "${DATABASE_URL}"
fi
generate_userlist_if_needed
fi
if [ ! -f "$PG_CONFIG_FILE" ]; then
echo "Creating pgbouncer config in ${PG_CONFIG_DIR}"
# Config file is in "ini" format. Section names are between "[" and "]".
# Lines starting with ";" or "#" are taken as comments and ignored.
# The characters ";" and "#" are not recognized when they appear later in the line.
# write static header
printf '%s\n%s\n' \
'################## Auto generated ##################' \
'[databases]' \
>"$PG_CONFIG_FILE"
if [ -n "$DATABASE_URLS" ]; then
echo "$DATABASE_URLS" | tr , '\n' | while read -r url; do
parse_url "$url"
generate_config_db_entry
done
else
if [ -n "$DATABASE_URL" ]; then
parse_url "$DATABASE_URL"
fi
generate_config_db_entry
fi
# write [pgbouncer] section with a constant format string
{
printf '%s\n' '[pgbouncer]'
printf 'listen_addr = %s\n' "${LISTEN_ADDR:-0.0.0.0}"
printf 'listen_port = %s\n' "${LISTEN_PORT:-5432}"
printf 'unix_socket_dir = %s\n' "${UNIX_SOCKET_DIR}"
printf 'user = %s\n' "pgbouncer"
printf 'auth_file = %s\n' "${_AUTH_FILE}"
} >>"$PG_CONFIG_FILE"
# now handle each optional setting in its own if-block:
if [ -n "${AUTH_HBA_FILE}" ]; then
printf 'auth_hba_file = %s\n' "${AUTH_HBA_FILE}" >>"$PG_CONFIG_FILE"
fi
if [ -n "${AUTH_TYPE}" ]; then
printf 'auth_type = %s\n' "${AUTH_TYPE}" >>"$PG_CONFIG_FILE"
fi
if [ -n "${AUTH_USER}" ]; then
printf 'auth_user = %s\n' "${AUTH_USER}" >>"$PG_CONFIG_FILE"
fi
if [ -n "${AUTH_QUERY}" ]; then
printf 'auth_query = %s\n' "${AUTH_QUERY}" >>"$PG_CONFIG_FILE"
fi
if [ -n "${AUTH_DBNAME}" ]; then
printf 'auth_dbname = %s\n' "${AUTH_DBNAME}" >>"$PG_CONFIG_FILE"
fi
if [ -n "${POOL_MODE}" ]; then
printf 'pool_mode = %s\n' "${POOL_MODE}" >>"$PG_CONFIG_FILE"
fi
if [ -n "${MAX_CLIENT_CONN}" ]; then
printf 'max_client_conn = %s\n' "${MAX_CLIENT_CONN}" >>"$PG_CONFIG_FILE"
fi
if [ -n "${POOL_SIZE}" ]; then
printf 'pool_size = %s\n' "${POOL_SIZE}" >>"$PG_CONFIG_FILE"
fi
if [ -n "${DEFAULT_POOL_SIZE}" ]; then
printf 'default_pool_size = %s\n' "${DEFAULT_POOL_SIZE}" >>"$PG_CONFIG_FILE"
fi
if [ -n "${MIN_POOL_SIZE}" ]; then
printf 'min_pool_size = %s\n' "${MIN_POOL_SIZE}" >>"$PG_CONFIG_FILE"
fi
if [ -n "${RESERVE_POOL_SIZE}" ]; then
printf 'reserve_pool_size = %s\n' "${RESERVE_POOL_SIZE}" >>"$PG_CONFIG_FILE"
fi
if [ -n "${RESERVE_POOL_TIMEOUT}" ]; then
printf 'reserve_pool_timeout = %s\n' "${RESERVE_POOL_TIMEOUT}" >>"$PG_CONFIG_FILE"
fi
if [ -n "${MAX_DB_CONNECTIONS}" ]; then
printf 'max_db_connections = %s\n' "${MAX_DB_CONNECTIONS}" >>"$PG_CONFIG_FILE"
fi
if [ -n "${MAX_USER_CONNECTIONS}" ]; then
printf 'max_user_connections = %s\n' "${MAX_USER_CONNECTIONS}" >>"$PG_CONFIG_FILE"
fi
if [ -n "${SERVER_ROUND_ROBIN}" ]; then
printf 'server_round_robin = %s\n' "${SERVER_ROUND_ROBIN}" >>"$PG_CONFIG_FILE"
fi
printf 'ignore_startup_parameters = %s\n' "${IGNORE_STARTUP_PARAMETERS:-extra_float_digits}" >>"$PG_CONFIG_FILE"
if [ -n "${DISABLE_PQEXEC}" ]; then
printf 'disable_pqexec = %s\n' "${DISABLE_PQEXEC}" >>"$PG_CONFIG_FILE"
fi
if [ -n "${APPLICATION_NAME_ADD_HOST}" ]; then
printf 'application_name_add_host = %s\n' "${APPLICATION_NAME_ADD_HOST}" >>"$PG_CONFIG_FILE"
fi
if [ -n "${TIMEZONE}" ]; then
printf 'timezone = %s\n' "${TIMEZONE}" >>"$PG_CONFIG_FILE"
fi
if [ -n "${MAX_PREPARED_STATEMENTS}" ]; then
printf 'max_prepared_statements = %s\n' "${MAX_PREPARED_STATEMENTS}" >>"$PG_CONFIG_FILE"
fi
# Log settings
if [ -n "${LOG_CONNECTIONS}" ]; then
printf 'log_connections = %s\n' "${LOG_CONNECTIONS}" >>"$PG_CONFIG_FILE"
fi
if [ -n "${LOG_DISCONNECTIONS}" ]; then
printf 'log_disconnections = %s\n' "${LOG_DISCONNECTIONS}" >>"$PG_CONFIG_FILE"
fi
if [ -n "${LOG_POOLER_ERRORS}" ]; then
printf 'log_pooler_errors = %s\n' "${LOG_POOLER_ERRORS}" >>"$PG_CONFIG_FILE"
fi
if [ -n "${LOG_STATS}" ]; then
printf 'log_stats = %s\n' "${LOG_STATS}" >>"$PG_CONFIG_FILE"
fi
if [ -n "${STATS_PERIOD}" ]; then
printf 'stats_period = %s\n' "${STATS_PERIOD}" >>"$PG_CONFIG_FILE"
fi
if [ -n "${VERBOSE}" ]; then
printf 'verbose = %s\n' "${VERBOSE}" >>"$PG_CONFIG_FILE"
fi
printf 'admin_users = %s\n' "${ADMIN_USERS:-postgres}" >>"$PG_CONFIG_FILE"
if [ -n "${STATS_USERS}" ]; then
printf 'stats_users = %s\n' "${STATS_USERS}" >>"$PG_CONFIG_FILE"
fi
# Connection sanity checks, timeouts
if [ -n "${SERVER_RESET_QUERY}" ]; then
printf 'server_reset_query = %s\n' "${SERVER_RESET_QUERY}" >>"$PG_CONFIG_FILE"
fi
if [ -n "${SERVER_RESET_QUERY_ALWAYS}" ]; then
printf 'server_reset_query_always = %s\n' "${SERVER_RESET_QUERY_ALWAYS}" >>"$PG_CONFIG_FILE"
fi
if [ -n "${SERVER_CHECK_DELAY}" ]; then
printf 'server_check_delay = %s\n' "${SERVER_CHECK_DELAY}" >>"$PG_CONFIG_FILE"
fi
if [ -n "${SERVER_CHECK_QUERY}" ]; then
printf 'server_check_query = %s\n' "${SERVER_CHECK_QUERY}" >>"$PG_CONFIG_FILE"
fi
if [ -n "${SERVER_LIFETIME}" ]; then
printf 'server_lifetime = %s\n' "${SERVER_LIFETIME}" >>"$PG_CONFIG_FILE"
fi
if [ -n "${SERVER_IDLE_TIMEOUT}" ]; then
printf 'server_idle_timeout = %s\n' "${SERVER_IDLE_TIMEOUT}" >>"$PG_CONFIG_FILE"
fi
if [ -n "${SERVER_CONNECT_TIMEOUT}" ]; then
printf 'server_connect_timeout = %s\n' "${SERVER_CONNECT_TIMEOUT}" >>"$PG_CONFIG_FILE"
fi
if [ -n "${SERVER_LOGIN_RETRY}" ]; then
printf 'server_login_retry = %s\n' "${SERVER_LOGIN_RETRY}" >>"$PG_CONFIG_FILE"
fi
if [ -n "${CLIENT_LOGIN_TIMEOUT}" ]; then
printf 'client_login_timeout = %s\n' "${CLIENT_LOGIN_TIMEOUT}" >>"$PG_CONFIG_FILE"
fi
if [ -n "${AUTODB_IDLE_TIMEOUT}" ]; then
printf 'autodb_idle_timeout = %s\n' "${AUTODB_IDLE_TIMEOUT}" >>"$PG_CONFIG_FILE"
fi
if [ -n "${DNS_MAX_TTL}" ]; then
printf 'dns_max_ttl = %s\n' "${DNS_MAX_TTL}" >>"$PG_CONFIG_FILE"
fi
if [ -n "${DNS_NXDOMAIN_TTL}" ]; then
printf 'dns_nxdomain_ttl = %s\n' "${DNS_NXDOMAIN_TTL}" >>"$PG_CONFIG_FILE"
fi
# TLS settings
if [ -n "${CLIENT_TLS_SSLMODE}" ]; then
printf 'client_tls_sslmode = %s\n' "${CLIENT_TLS_SSLMODE}" >>"$PG_CONFIG_FILE"
fi
if [ -n "${CLIENT_TLS_KEY_FILE}" ]; then
printf 'client_tls_key_file = %s\n' "${CLIENT_TLS_KEY_FILE}" >>"$PG_CONFIG_FILE"
fi
if [ -n "${CLIENT_TLS_CERT_FILE}" ]; then
printf 'client_tls_cert_file = %s\n' "${CLIENT_TLS_CERT_FILE}" >>"$PG_CONFIG_FILE"
fi
if [ -n "${CLIENT_TLS_CA_FILE}" ]; then
printf 'client_tls_ca_file = %s\n' "${CLIENT_TLS_CA_FILE}" >>"$PG_CONFIG_FILE"
fi
if [ -n "${CLIENT_TLS_PROTOCOLS}" ]; then
printf 'client_tls_protocols = %s\n' "${CLIENT_TLS_PROTOCOLS}" >>"$PG_CONFIG_FILE"
fi
if [ -n "${CLIENT_TLS_CIPHERS}" ]; then
printf 'client_tls_ciphers = %s\n' "${CLIENT_TLS_CIPHERS}" >>"$PG_CONFIG_FILE"
fi
if [ -n "${CLIENT_TLS_ECDHCURVE}" ]; then
printf 'client_tls_ecdhcurve = %s\n' "${CLIENT_TLS_ECDHCURVE}" >>"$PG_CONFIG_FILE"
fi
if [ -n "${CLIENT_TLS_DHEPARAMS}" ]; then
printf 'client_tls_dheparams = %s\n' "${CLIENT_TLS_DHEPARAMS}" >>"$PG_CONFIG_FILE"
fi
if [ -n "${SERVER_TLS_SSLMODE}" ]; then
printf 'server_tls_sslmode = %s\n' "${SERVER_TLS_SSLMODE}" >>"$PG_CONFIG_FILE"
fi
if [ -n "${SERVER_TLS_CA_FILE}" ]; then
printf 'server_tls_ca_file = %s\n' "${SERVER_TLS_CA_FILE}" >>"$PG_CONFIG_FILE"
fi
if [ -n "${SERVER_TLS_KEY_FILE}" ]; then
printf 'server_tls_key_file = %s\n' "${SERVER_TLS_KEY_FILE}" >>"$PG_CONFIG_FILE"
fi
if [ -n "${SERVER_TLS_CERT_FILE}" ]; then
printf 'server_tls_cert_file = %s\n' "${SERVER_TLS_CERT_FILE}" >>"$PG_CONFIG_FILE"
fi
if [ -n "${SERVER_TLS_PROTOCOLS}" ]; then
printf 'server_tls_protocols = %s\n' "${SERVER_TLS_PROTOCOLS}" >>"$PG_CONFIG_FILE"
fi
if [ -n "${SERVER_TLS_CIPHERS}" ]; then
printf 'server_tls_ciphers = %s\n' "${SERVER_TLS_CIPHERS}" >>"$PG_CONFIG_FILE"
fi
# Dangerous timeouts
if [ -n "${QUERY_TIMEOUT}" ]; then
printf 'query_timeout = %s\n' "${QUERY_TIMEOUT}" >>"$PG_CONFIG_FILE"
fi
if [ -n "${QUERY_WAIT_TIMEOUT}" ]; then
printf 'query_wait_timeout = %s\n' "${QUERY_WAIT_TIMEOUT}" >>"$PG_CONFIG_FILE"
fi
if [ -n "${CLIENT_IDLE_TIMEOUT}" ]; then
printf 'client_idle_timeout = %s\n' "${CLIENT_IDLE_TIMEOUT}" >>"$PG_CONFIG_FILE"
fi
if [ -n "${IDLE_TRANSACTION_TIMEOUT}" ]; then
printf 'idle_transaction_timeout = %s\n' "${IDLE_TRANSACTION_TIMEOUT}" >>"$PG_CONFIG_FILE"
fi
if [ -n "${PKT_BUF}" ]; then
printf 'pkt_buf = %s\n' "${PKT_BUF}" >>"$PG_CONFIG_FILE"
fi
if [ -n "${MAX_PACKET_SIZE}" ]; then
printf 'max_packet_size = %s\n' "${MAX_PACKET_SIZE}" >>"$PG_CONFIG_FILE"
fi
if [ -n "${LISTEN_BACKLOG}" ]; then
printf 'listen_backlog = %s\n' "${LISTEN_BACKLOG}" >>"$PG_CONFIG_FILE"
fi
if [ -n "${SBUF_LOOPCNT}" ]; then
printf 'sbuf_loopcnt = %s\n' "${SBUF_LOOPCNT}" >>"$PG_CONFIG_FILE"
fi
if [ -n "${SUSPEND_TIMEOUT}" ]; then
printf 'suspend_timeout = %s\n' "${SUSPEND_TIMEOUT}" >>"$PG_CONFIG_FILE"
fi
if [ -n "${TCP_DEFER_ACCEPT}" ]; then
printf 'tcp_defer_accept = %s\n' "${TCP_DEFER_ACCEPT}" >>"$PG_CONFIG_FILE"
fi
if [ -n "${TCP_KEEPALIVE}" ]; then
printf 'tcp_keepalive = %s\n' "${TCP_KEEPALIVE}" >>"$PG_CONFIG_FILE"
fi
if [ -n "${TCP_KEEPCNT}" ]; then
printf 'tcp_keepcnt = %s\n' "${TCP_KEEPCNT}" >>"$PG_CONFIG_FILE"
fi
if [ -n "${TCP_KEEPIDLE}" ]; then
printf 'tcp_keepidle = %s\n' "${TCP_KEEPIDLE}" >>"$PG_CONFIG_FILE"
fi
if [ -n "${TCP_KEEPINTVL}" ]; then
printf 'tcp_keepintvl = %s\n' "${TCP_KEEPINTVL}" >>"$PG_CONFIG_FILE"
fi
if [ -n "${TCP_USER_TIMEOUT}" ]; then
printf 'tcp_user_timeout = %s\n' "${TCP_USER_TIMEOUT}" >>"$PG_CONFIG_FILE"
fi
printf '\n################## end file ##################\n' >>"$PG_CONFIG_FILE"
if [ "${DEBUG}" = "true" ]; then
cat "${PG_CONFIG_FILE}"
fi
fi
echo "Starting $*..."
exec "$@"

7
docker/redis.conf Normal file
View file

@ -0,0 +1,7 @@
# redis.conf
port 0
tls-port 6379
tls-cert-file /usr/local/etc/redis/cert.pem
tls-key-file /usr/local/etc/redis/key.pem
tls-ca-cert-file /usr/local/etc/redis/ca.pem
tls-auth-clients no

View file

@ -14,9 +14,7 @@ export default defineConfig({
ssl: (() => {
try {
return {
ca: fs.readFileSync(path.resolve('./certs/psql-ca.crt')),
key: fs.readFileSync(path.resolve('./certs/psql-client.key')),
cert: fs.readFileSync(path.resolve('./certs/psql-server.crt')),
ca: fs.readFileSync(path.resolve('./certs/rootCA.pem')),
};
} catch (error) {
console.warn(

View file

@ -1,43 +1,37 @@
#!/bin/bash
# Get the Effective User ID
_uid="$(id -u)"
# Get the Group ID
_gid="$(id -g)"
# Create the certificates directory
mkdir -p certs
# Remove everything in the certs directory except for rootCA.pem and rootCA-key.pem
if [ -d certs ]; then
find certs -mindepth 1 ! -name 'rootCA.pem' ! -name 'rootCA-key.pem' -exec rm -rf {} +
else
mkdir certs
fi
# Generate PostgreSQL Certificates
openssl req -new -x509 -days 365 -nodes \
-out certs/psql-server.crt \
-keyout certs/psql-server.key \
-subj "/CN=localhost"
# Set CAROOT Environment Variable
CAROOT="$(pwd)/certs"
export CAROOT
# Generate Valkey Certificates
openssl req -new -x509 -days 365 -nodes \
-out certs/cache-server.crt \
-keyout certs/cache-server.key \
-subj "/CN=localhost"
# Generate postgres Certificates
mkcert -key-file certs/psql-key.pem -cert-file certs/psql-cert.pem localhost 127.0.0.1 ::1
# Get CA Certificates
cp certs/psql-server.crt certs/psql-ca.crt
cp certs/cache-server.crt certs/cache-ca.crt
# Generate Cache Certificates
mkcert -key-file certs/cache-key.pem -cert-file certs/cache-cert.pem localhost 127.0.0.1 ::1
# Generate PgBouncer Certificates
mkcert -key-file certs/pgbouncer-key.pem -cert-file certs/pgbouncer-cert.pem localhost 127.0.0.1 ::1
# Install the Root CA
mkcert -install
# Setup Permissions
chmod 0600 certs/psql-server.key
chmod 0600 certs/cache-server.key
chmod 0600 certs/psql-key.pem
chmod 0640 certs/pgbouncer-key.pem
chmod 0640 certs/cache-key.pem
# Assign Ownership
sudo chown 70:70 certs/psql-*.*
sudo chown 999:1000 certs/cache-*.*
# Get Client Keys
sudo cp certs/psql-server.key certs/psql-client.key
sudo cp certs/cache-server.key certs/cache-client.key
# Change Client Key Ownership
sudo chown $_uid:$_uid certs/psql-client.key
sudo chown $_uid:$_uid certs/cache-client.key
# Change Client Key Permissions
sudo chmod +r certs/psql-client.key
sudo chmod +r certs/cache-client.key
sudo chown 70:70 certs/psql-key.pem
sudo chown 1100:"${_gid}" certs/pgbouncer-key.pem
sudo chown 999:"${_gid}" certs/cache-key.pem

View file

@ -24,7 +24,7 @@
"discord.js": "^14.20.0",
"drizzle-orm": "^0.44.2",
"ioredis": "^5.6.1",
"pg": "^8.16.0"
"pg": "^8.16.2"
},
"devDependencies": {
"@commitlint/cli": "^19.8.1",

View file

@ -103,9 +103,7 @@ export async function initializeDatabaseConnection(): Promise<boolean> {
ssl: (() => {
try {
return {
ca: fs.readFileSync(path.resolve('./certs/psql-ca.crt')),
key: fs.readFileSync(path.resolve('./certs/psql-client.key')),
cert: fs.readFileSync(path.resolve('./certs/psql-server.crt')),
ca: fs.readFileSync(path.resolve('./certs/rootCA.pem')),
};
} catch (error) {
console.warn(

View file

@ -96,9 +96,9 @@ async function initializeRedisConnection() {
tls: (() => {
try {
return {
ca: fs.readFileSync(path.resolve('./certs/cache-ca.crt')),
key: fs.readFileSync(path.resolve('./certs/cache-client.key')),
cert: fs.readFileSync(path.resolve('./certs/cache-server.crt')),
ca: fs.readFileSync(path.resolve('./certs/rootCA.pem')),
key: fs.readFileSync(path.resolve('./certs/cache-key.pem')),
cert: fs.readFileSync(path.resolve('./certs/cache-cert.pem')),
};
} catch (error) {
console.warn(

View file

@ -19,7 +19,7 @@ import { moderationTable } from '@/db/schema.js';
import { db, getMember, handleDbError, updateMember } from '@/db/db.js';
import logAction from './logging/logAction.js';
const __dirname = path.resolve();
const PROJECT_ROOT = path.resolve();
/**
* Turns a duration string into milliseconds
@ -67,7 +67,12 @@ export async function generateMemberBanner({
width,
height,
}: generateMemberBannerTypes): Promise<AttachmentBuilder> {
const welcomeBackground = path.join(__dirname, 'assets', 'images', 'welcome-bg.png');
const welcomeBackground = path.join(
PROJECT_ROOT,
'assets',
'images',
'welcome-bg.png',
);
const canvas = Canvas.createCanvas(width, height);
const context = canvas.getContext('2d');
const background = await Canvas.loadImage(welcomeBackground);

View file

@ -4026,17 +4026,17 @@ __metadata:
languageName: node
linkType: hard
"pg-cloudflare@npm:^1.2.5":
version: 1.2.5
resolution: "pg-cloudflare@npm:1.2.5"
checksum: 10c0/48b9105ef027c7b3f57ef88ceaec3634cd82120059bd68273cce06989a1ec547e0b0fbb5d1afdd0711824f409c8b410f9bdec2f6c8034728992d3658c0b36f86
"pg-cloudflare@npm:^1.2.6":
version: 1.2.6
resolution: "pg-cloudflare@npm:1.2.6"
checksum: 10c0/db339518ed763982c45a94c96cedba1b25819fe32e9d3a4df6f82cd647c716ff9b5009f3c8b90f2940b0a1889387710ef764c9e3e7ddd8397d217309d663a2c8
languageName: node
linkType: hard
"pg-connection-string@npm:^2.9.0":
version: 2.9.0
resolution: "pg-connection-string@npm:2.9.0"
checksum: 10c0/7145d00688200685a9d9931a7fc8d61c75f348608626aef88080ece956ceb4ff1cbdee29c3284e41b7a3345bab0e4f50f9edc256e270bfa3a563af4ea78bb490
"pg-connection-string@npm:^2.9.1":
version: 2.9.1
resolution: "pg-connection-string@npm:2.9.1"
checksum: 10c0/9a646529bbc0843806fc5de98ce93735a4612b571f11867178a85665d11989a827e6fd157388ca0e34ec948098564fce836c178cfd499b9f0e8cd9972b8e2e5c
languageName: node
linkType: hard
@ -4047,12 +4047,12 @@ __metadata:
languageName: node
linkType: hard
"pg-pool@npm:^3.10.0":
version: 3.10.0
resolution: "pg-pool@npm:3.10.0"
"pg-pool@npm:^3.10.1":
version: 3.10.1
resolution: "pg-pool@npm:3.10.1"
peerDependencies:
pg: ">=8.0"
checksum: 10c0/b36162dc98c0ad88cd26f3d65f3e3932c3f870abe7a88905f16fc98282e8131692903e482720ebc9698cb08851c9b19242ff16a50af7f9434c8bb0b5d33a9a9a
checksum: 10c0/a00916b7df64226cc597fe769e3a757ff9b11562dc87ce5b0a54101a18c1fe282daaa2accaf27221e81e1e4cdf4da6a33dab09614734d32904d6c4e11c44a079
languageName: node
linkType: hard
@ -4063,10 +4063,10 @@ __metadata:
languageName: node
linkType: hard
"pg-protocol@npm:^1.10.0":
version: 1.10.0
resolution: "pg-protocol@npm:1.10.0"
checksum: 10c0/7d0d64fe9df50262d907fd476454e1e36f41f5f66044c3ba6aa773fb8add1d350a9c162306e5c33e99bdfbdcc1140dd4ca74f66eda41d0aaceb5853244dcdb65
"pg-protocol@npm:^1.10.2":
version: 1.10.2
resolution: "pg-protocol@npm:1.10.2"
checksum: 10c0/3f9b5aba3f356190738ea25ecded3cd033cd2218789acf9c67b75788932c4b594eeb7043481822b69eaae4d84401e00142a2ef156297a8347987a78a52afd50e
languageName: node
linkType: hard
@ -4083,14 +4083,14 @@ __metadata:
languageName: node
linkType: hard
"pg@npm:^8.16.0":
version: 8.16.0
resolution: "pg@npm:8.16.0"
"pg@npm:^8.16.2":
version: 8.16.2
resolution: "pg@npm:8.16.2"
dependencies:
pg-cloudflare: "npm:^1.2.5"
pg-connection-string: "npm:^2.9.0"
pg-pool: "npm:^3.10.0"
pg-protocol: "npm:^1.10.0"
pg-cloudflare: "npm:^1.2.6"
pg-connection-string: "npm:^2.9.1"
pg-pool: "npm:^3.10.1"
pg-protocol: "npm:^1.10.2"
pg-types: "npm:2.2.0"
pgpass: "npm:1.0.5"
peerDependencies:
@ -4101,7 +4101,7 @@ __metadata:
peerDependenciesMeta:
pg-native:
optional: true
checksum: 10c0/24542229c7e5cbf69d654de32e8cdc8302c73f1338e56728543cb16364fb319d5689e03fa704b69a208105c7065c867cfccb9dbccccea2020bb5c64ead785713
checksum: 10c0/e444103fda2fa236bb7951e534bbce52d81df8f0ca43b36cdd32da2e558946ae011fa6a0fcfea2e48d935addba821868e76d3f4f670b313f639a3d24fcd420af
languageName: node
linkType: hard
@ -4161,7 +4161,7 @@ __metadata:
husky: "npm:^9.1.7"
ioredis: "npm:^5.6.1"
lint-staged: "npm:^16.1.2"
pg: "npm:^8.16.0"
pg: "npm:^8.16.2"
prettier: "npm:3.5.3"
ts-node: "npm:^10.9.2"
ts-patch: "npm:^3.3.0"