From ca24d57fd0b331daa8445c8993bc62a13193cd53 Mon Sep 17 00:00:00 2001 From: ahmadk953 <103906421+ahmadk953@users.noreply.github.com> Date: Mon, 16 Jun 2025 21:43:06 -0400 Subject: [PATCH 1/8] feat: add pgbouncer --- .github/workflows/docker.yml | 63 ++++++++++ docker-compose.yml | 37 +++++- docker/pgbouncer/Dockerfile | 42 +++++++ docker/pgbouncer/README.md | 76 ++++++++++++ docker/pgbouncer/entrypoint.sh | 203 +++++++++++++++++++++++++++++++++ drizzle.config.ts | 8 +- generate-certs.sh | 18 ++- src/db/db.ts | 6 +- 8 files changed, 438 insertions(+), 15 deletions(-) create mode 100644 .github/workflows/docker.yml create mode 100644 docker/pgbouncer/Dockerfile create mode 100644 docker/pgbouncer/README.md create mode 100644 docker/pgbouncer/entrypoint.sh diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000..f30ed50 --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,63 @@ +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: true + 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 + env: + TAGS: ${{ steps.meta.outputs.tags }} + DIGEST: ${{ steps.build-and-push.outputs.digest }} + run: echo "${TAGS}" | xargs -I {} cosign sign --yes {}@${DIGEST} diff --git a/docker-compose.yml b/docker-compose.yml index b7cb6c4..fbaf1c0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -11,8 +11,6 @@ services: - ./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 @@ -24,7 +22,7 @@ services: timeout: 5s retries: 5 networks: - - backend + - services valkey: image: valkey/valkey:8-alpine @@ -51,12 +49,41 @@ services: timeout: 5s retries: 5 networks: - - backend + - services + + pgbouncer: + image: ghcr.io/ahmadk953/poixpixel-discord-bot-pgbouncer + container_name: pgbouncer + environment: + DB_USER: ${POSTGRES_USER} + DB_PASSWORD: ${POSTGRES_PASSWORD} + DB_HOST: postgres + # DB_NAME: ${POSTGRES_DB} + AUTH_TYPE: scram-sha-256 + POOL_MODE: transaction + ADMIN_USERS: ${POSTGRES_USER} + CLIENT_TLS_SSLMODE: require + CLIENT_TLS_CERT_FILE: /certs/server.crt + CLIENT_TLS_KEY_FILE: /certs/server.key + CLIENT_TLS_CA_FILE: /certs/ca.crt + SERVER_TLS_SSLMODE: require + ports: + - "5432:5432" + depends_on: + - postgres + volumes: + - ./certs/pgbouncer-server.crt:/certs/server.crt:ro + - ./certs/pgbouncer-server.key:/certs/server.key:ro + - ./certs/pgbouncer-ca.crt:/certs/ca.crt:ro + healthcheck: + test: ['CMD', 'pg_isready', '-h', 'localhost'] + networks: + - services volumes: postgres_data: valkey_data: networks: - backend: + services: driver: bridge diff --git a/docker/pgbouncer/Dockerfile b/docker/pgbouncer/Dockerfile new file mode 100644 index 0000000..7887f3e --- /dev/null +++ b/docker/pgbouncer/Dockerfile @@ -0,0 +1,42 @@ +# 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 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 + +RUN curl -Lo /pgbouncer.tar.gz https://pgbouncer.github.io/downloads/files/${PGBOUNCER_VERSION}/pgbouncer-${PGBOUNCER_VERSION}.tar.gz && \ + tar -xzf /pgbouncer.tar.gz && mv /pgbouncer-${PGBOUNCER_VERSION} /pgbouncer + +RUN cd /c-ares && ./configure && make && make install +RUN cd /pgbouncer && ./configure --with-cares && make && make install + +FROM alpine:3.22 + +COPY entrypoint.sh /entrypoint.sh + +RUN apk add --no-cache busybox libevent postgresql-client \ + && 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 /entrypoint.sh \ + && chmod +x /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"] \ No newline at end of file diff --git a/docker/pgbouncer/README.md b/docker/pgbouncer/README.md new file mode 100644 index 0000000..a899cc5 --- /dev/null +++ b/docker/pgbouncer/README.md @@ -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 with the exception of 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. diff --git a/docker/pgbouncer/entrypoint.sh b/docker/pgbouncer/entrypoint.sh new file mode 100644 index 0000000..64df010 --- /dev/null +++ b/docker/pgbouncer/entrypoint.sh @@ -0,0 +1,203 @@ +#!/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 +function 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. +function generate_userlist_if_needed() { + if [ -n "${DB_USER}" -a -n "${DB_PASSWORD}" -a -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$(echo -n "${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. +function generate_config_db_entry() { + printf "\ +${DB_NAME:-*} = host=${DB_HOST:?"Setup pgbouncer config error! You must set DB_HOST env"} \ +port=${DB_PORT:-5432} auth_user=${DB_USER:-postgres} +${CLIENT_ENCODING:+client_encoding = ${CLIENT_ENCODING}\n}\ +" >> "${PG_CONFIG_FILE}" +} + +# 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 read 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. + printf "\ +################## Auto generated ################## +[databases] +" > "${PG_CONFIG_FILE}" + + if [ -n "$DATABASE_URLS" ]; then + echo "$DATABASE_URLS" | tr , '\n' | while read 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 + + printf "\ +[pgbouncer] +listen_addr = ${LISTEN_ADDR:-0.0.0.0} +listen_port = ${LISTEN_PORT:-5432} +unix_socket_dir = ${UNIX_SOCKET_DIR} +user = pgbouncer +auth_file = ${_AUTH_FILE} +${AUTH_HBA_FILE:+auth_hba_file = ${AUTH_HBA_FILE}\n}\ +auth_type = ${AUTH_TYPE:-md5} +${AUTH_USER:+auth_user = ${AUTH_USER}\n}\ +${AUTH_QUERY:+auth_query = ${AUTH_QUERY}\n}\ +${AUTH_DBNAME:+auth_dbname = ${AUTH_DBNAME}\n}\ +${POOL_MODE:+pool_mode = ${POOL_MODE}\n}\ +${MAX_CLIENT_CONN:+max_client_conn = ${MAX_CLIENT_CONN}\n}\ +${POOL_SIZE:+pool_size = ${POOL_SIZE}\n}\ +${DEFAULT_POOL_SIZE:+default_pool_size = ${DEFAULT_POOL_SIZE}\n}\ +${MIN_POOL_SIZE:+min_pool_size = ${MIN_POOL_SIZE}\n}\ +${RESERVE_POOL_SIZE:+reserve_pool_size = ${RESERVE_POOL_SIZE}\n}\ +${RESERVE_POOL_TIMEOUT:+reserve_pool_timeout = ${RESERVE_POOL_TIMEOUT}\n}\ +${MAX_DB_CONNECTIONS:+max_db_connections = ${MAX_DB_CONNECTIONS}\n}\ +${MAX_USER_CONNECTIONS:+max_user_connections = ${MAX_USER_CONNECTIONS}\n}\ +${SERVER_ROUND_ROBIN:+server_round_robin = ${SERVER_ROUND_ROBIN}\n}\ +ignore_startup_parameters = ${IGNORE_STARTUP_PARAMETERS:-extra_float_digits} +${DISABLE_PQEXEC:+disable_pqexec = ${DISABLE_PQEXEC}\n}\ +${APPLICATION_NAME_ADD_HOST:+application_name_add_host = ${APPLICATION_NAME_ADD_HOST}\n}\ +${TIMEZONE:+timezone = ${TIMEZONE}\n}\ +${MAX_PREPARED_STATEMENTS:+max_prepared_statements = ${MAX_PREPARED_STATEMENTS}\n}\ + +# Log settings +${LOG_CONNECTIONS:+log_connections = ${LOG_CONNECTIONS}\n}\ +${LOG_DISCONNECTIONS:+log_disconnections = ${LOG_DISCONNECTIONS}\n}\ +${LOG_POOLER_ERRORS:+log_pooler_errors = ${LOG_POOLER_ERRORS}\n}\ +${LOG_STATS:+log_stats = ${LOG_STATS}\n}\ +${STATS_PERIOD:+stats_period = ${STATS_PERIOD}\n}\ +${VERBOSE:+verbose = ${VERBOSE}\n}\ +admin_users = ${ADMIN_USERS:-postgres} +${STATS_USERS:+stats_users = ${STATS_USERS}\n}\ + +# Connection sanity checks, timeouts +${SERVER_RESET_QUERY:+server_reset_query = ${SERVER_RESET_QUERY}\n}\ +${SERVER_RESET_QUERY_ALWAYS:+server_reset_query_always = ${SERVER_RESET_QUERY_ALWAYS}\n}\ +${SERVER_CHECK_DELAY:+server_check_delay = ${SERVER_CHECK_DELAY}\n}\ +${SERVER_CHECK_QUERY:+server_check_query = ${SERVER_CHECK_QUERY}\n}\ +${SERVER_LIFETIME:+server_lifetime = ${SERVER_LIFETIME}\n}\ +${SERVER_IDLE_TIMEOUT:+server_idle_timeout = ${SERVER_IDLE_TIMEOUT}\n}\ +${SERVER_CONNECT_TIMEOUT:+server_connect_timeout = ${SERVER_CONNECT_TIMEOUT}\n}\ +${SERVER_LOGIN_RETRY:+server_login_retry = ${SERVER_LOGIN_RETRY}\n}\ +${CLIENT_LOGIN_TIMEOUT:+client_login_timeout = ${CLIENT_LOGIN_TIMEOUT}\n}\ +${AUTODB_IDLE_TIMEOUT:+autodb_idle_timeout = ${AUTODB_IDLE_TIMEOUT}\n}\ +${DNS_MAX_TTL:+dns_max_ttl = ${DNS_MAX_TTL}\n}\ +${DNS_NXDOMAIN_TTL:+dns_nxdomain_ttl = ${DNS_NXDOMAIN_TTL}\n}\ + +# TLS settings +${CLIENT_TLS_SSLMODE:+client_tls_sslmode = ${CLIENT_TLS_SSLMODE}\n}\ +${CLIENT_TLS_KEY_FILE:+client_tls_key_file = ${CLIENT_TLS_KEY_FILE}\n}\ +${CLIENT_TLS_CERT_FILE:+client_tls_cert_file = ${CLIENT_TLS_CERT_FILE}\n}\ +${CLIENT_TLS_CA_FILE:+client_tls_ca_file = ${CLIENT_TLS_CA_FILE}\n}\ +${CLIENT_TLS_PROTOCOLS:+client_tls_protocols = ${CLIENT_TLS_PROTOCOLS}\n}\ +${CLIENT_TLS_CIPHERS:+client_tls_ciphers = ${CLIENT_TLS_CIPHERS}\n}\ +${CLIENT_TLS_ECDHCURVE:+client_tls_ecdhcurve = ${CLIENT_TLS_ECDHCURVE}\n}\ +${CLIENT_TLS_DHEPARAMS:+client_tls_dheparams = ${CLIENT_TLS_DHEPARAMS}\n}\ +${SERVER_TLS_SSLMODE:+server_tls_sslmode = ${SERVER_TLS_SSLMODE}\n}\ +${SERVER_TLS_CA_FILE:+server_tls_ca_file = ${SERVER_TLS_CA_FILE}\n}\ +${SERVER_TLS_KEY_FILE:+server_tls_key_file = ${SERVER_TLS_KEY_FILE}\n}\ +${SERVER_TLS_CERT_FILE:+server_tls_cert_file = ${SERVER_TLS_CERT_FILE}\n}\ +${SERVER_TLS_PROTOCOLS:+server_tls_protocols = ${SERVER_TLS_PROTOCOLS}\n}\ +${SERVER_TLS_CIPHERS:+server_tls_ciphers = ${SERVER_TLS_CIPHERS}\n}\ + +# Dangerous timeouts +${QUERY_TIMEOUT:+query_timeout = ${QUERY_TIMEOUT}\n}\ +${QUERY_WAIT_TIMEOUT:+query_wait_timeout = ${QUERY_WAIT_TIMEOUT}\n}\ +${CLIENT_IDLE_TIMEOUT:+client_idle_timeout = ${CLIENT_IDLE_TIMEOUT}\n}\ +${IDLE_TRANSACTION_TIMEOUT:+idle_transaction_timeout = ${IDLE_TRANSACTION_TIMEOUT}\n}\ +${PKT_BUF:+pkt_buf = ${PKT_BUF}\n}\ +${MAX_PACKET_SIZE:+max_packet_size = ${MAX_PACKET_SIZE}\n}\ +${LISTEN_BACKLOG:+listen_backlog = ${LISTEN_BACKLOG}\n}\ +${SBUF_LOOPCNT:+sbuf_loopcnt = ${SBUF_LOOPCNT}\n}\ +${SUSPEND_TIMEOUT:+suspend_timeout = ${SUSPEND_TIMEOUT}\n}\ +${TCP_DEFER_ACCEPT:+tcp_defer_accept = ${TCP_DEFER_ACCEPT}\n}\ +${TCP_KEEPALIVE:+tcp_keepalive = ${TCP_KEEPALIVE}\n}\ +${TCP_KEEPCNT:+tcp_keepcnt = ${TCP_KEEPCNT}\n}\ +${TCP_KEEPIDLE:+tcp_keepidle = ${TCP_KEEPIDLE}\n}\ +${TCP_KEEPINTVL:+tcp_keepintvl = ${TCP_KEEPINTVL}\n}\ +${TCP_USER_TIMEOUT:+tcp_user_timeout = ${TCP_USER_TIMEOUT}\n}\ +################## end file ################## +" >> "${PG_CONFIG_FILE}" + cat "${PG_CONFIG_FILE}" +fi + +echo "Starting $*..." +exec "$@" \ No newline at end of file diff --git a/drizzle.config.ts b/drizzle.config.ts index eb5083a..89cc62e 100644 --- a/drizzle.config.ts +++ b/drizzle.config.ts @@ -1,3 +1,5 @@ +/* eslint-disable */ + import fs from 'node:fs'; import path from 'node:path'; import { defineConfig } from 'drizzle-kit'; @@ -14,9 +16,9 @@ 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/pgbouncer-ca.crt')), + key: fs.readFileSync(path.resolve('./certs/pgbouncer-client.key')), + cert: fs.readFileSync(path.resolve('./certs/pgbouncer-server.crt')), }; } catch (error) { console.warn( diff --git a/generate-certs.sh b/generate-certs.sh index e025695..9b57d08 100755 --- a/generate-certs.sh +++ b/generate-certs.sh @@ -2,6 +2,7 @@ # Get the Effective User ID _uid="$(id -u)" +_gid="$(id -g)" # Create the certificates directory mkdir -p certs @@ -18,26 +19,35 @@ openssl req -new -x509 -days 365 -nodes \ -keyout certs/cache-server.key \ -subj "/CN=localhost" +# Genreate pgbouncer Certificates +openssl req -new -x509 -days 365 -nodes \ + -out certs/pgbouncer-server.crt \ + -keyout certs/pgbouncer-server.key \ + -subj "/CN=localhost" + # Get CA Certificates cp certs/psql-server.crt certs/psql-ca.crt cp certs/cache-server.crt certs/cache-ca.crt +cp certs/pgbouncer-server.crt certs/pgbouncer-ca.crt # Setup Permissions chmod 0600 certs/psql-server.key chmod 0600 certs/cache-server.key +chmod 0600 certs/pgbouncer-server.key # Assign Ownership sudo chown 70:70 certs/psql-*.* sudo chown 999:1000 certs/cache-*.* +sudo chown 1100:1100 certs/pgbouncer-*.* # Get Client Keys -sudo cp certs/psql-server.key certs/psql-client.key +sudo cp certs/pgbouncer-server.key certs/pgbouncer-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 +sudo chown $_uid:$_gid certs/pgbouncer-client.key +sudo chown $_uid:$_gid certs/cache-client.key # Change Client Key Permissions -sudo chmod +r certs/psql-client.key +sudo chmod +r certs/pgbouncer-client.key sudo chmod +r certs/cache-client.key diff --git a/src/db/db.ts b/src/db/db.ts index bc5005f..d4a0207 100644 --- a/src/db/db.ts +++ b/src/db/db.ts @@ -103,9 +103,9 @@ export async function initializeDatabaseConnection(): Promise { 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/pgbouncer-ca.crt')), + key: fs.readFileSync(path.resolve('./certs/pgbouncer-client.key')), + cert: fs.readFileSync(path.resolve('./certs/pgbouncer-server.crt')), }; } catch (error) { console.warn( From 04945bfebdb5ca21dd1028e700bcef6d0b802624 Mon Sep 17 00:00:00 2001 From: ahmadk953 <103906421+ahmadk953@users.noreply.github.com> Date: Mon, 16 Jun 2025 21:44:18 -0400 Subject: [PATCH 2/8] chore: update formating --- .lintstagedrc.mjs | 10 ++++++++-- docker-compose.yml | 4 ++-- src/util/helpers.ts | 7 ++++++- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/.lintstagedrc.mjs b/.lintstagedrc.mjs index 5d55c53..f0e6371 100644 --- a/.lintstagedrc.mjs +++ b/.lintstagedrc.mjs @@ -1,8 +1,14 @@ import path from 'path'; import process from 'process'; -const buildEslintCommand = (filenames) => - `eslint ${filenames.map((f) => path.relative(process.cwd(), f)).join(' ')}`; +const buildEslintCommand = (filenames) => { + // only lint files under src/ + const srcFiles = filenames.filter((f) => f.startsWith('src/')); + if (srcFiles.length === 0) return ''; + return `eslint ${srcFiles + .map((f) => path.relative(process.cwd(), f)) + .join(' ')}`; +}; const prettierCommand = 'prettier --write'; diff --git a/docker-compose.yml b/docker-compose.yml index fbaf1c0..60203ea 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -59,7 +59,7 @@ services: DB_PASSWORD: ${POSTGRES_PASSWORD} DB_HOST: postgres # DB_NAME: ${POSTGRES_DB} - AUTH_TYPE: scram-sha-256 + AUTH_TYPE: scram-sha-256 POOL_MODE: transaction ADMIN_USERS: ${POSTGRES_USER} CLIENT_TLS_SSLMODE: require @@ -68,7 +68,7 @@ services: CLIENT_TLS_CA_FILE: /certs/ca.crt SERVER_TLS_SSLMODE: require ports: - - "5432:5432" + - '5432:5432' depends_on: - postgres volumes: diff --git a/src/util/helpers.ts b/src/util/helpers.ts index 1301414..e1f7d95 100644 --- a/src/util/helpers.ts +++ b/src/util/helpers.ts @@ -67,7 +67,12 @@ export async function generateMemberBanner({ width, height, }: generateMemberBannerTypes): Promise { - const welcomeBackground = path.join(__dirname, 'assets', 'images', 'welcome-bg.png'); + const welcomeBackground = path.join( + __dirname, + 'assets', + 'images', + 'welcome-bg.png', + ); const canvas = Canvas.createCanvas(width, height); const context = canvas.getContext('2d'); const background = await Canvas.loadImage(welcomeBackground); From 9a4c794d51ad8a41b6f967f4b23f4421b773a5ef Mon Sep 17 00:00:00 2001 From: ahmadk953 <103906421+ahmadk953@users.noreply.github.com> Date: Tue, 17 Jun 2025 17:22:59 -0400 Subject: [PATCH 3/8] chore: fix typos --- docker/pgbouncer/README.md | 2 +- generate-certs.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/pgbouncer/README.md b/docker/pgbouncer/README.md index a899cc5..14d6aa2 100644 --- a/docker/pgbouncer/README.md +++ b/docker/pgbouncer/README.md @@ -66,7 +66,7 @@ docker run --rm \ -e DB_PASSWORD=pass \ -e DB_HOST=postgres-host \ -e DB_NAME=database \ - -v pgbouncer.ini:/etc/pgbouncer/pgbouncer.ini:ro + -v pgbouncer.ini:/etc/pgbouncer/pgbouncer.ini:ro \ -p 5432:5432 ghcr.io/ahmadk953/poixpixel-discord-bot-pgbouncer ``` diff --git a/generate-certs.sh b/generate-certs.sh index 9b57d08..eebdac0 100755 --- a/generate-certs.sh +++ b/generate-certs.sh @@ -19,7 +19,7 @@ openssl req -new -x509 -days 365 -nodes \ -keyout certs/cache-server.key \ -subj "/CN=localhost" -# Genreate pgbouncer Certificates +# Generate pgbouncer Certificates openssl req -new -x509 -days 365 -nodes \ -out certs/pgbouncer-server.crt \ -keyout certs/pgbouncer-server.key \ From 6865672d81f39cb8343cc024921898e0c6ffda25 Mon Sep 17 00:00:00 2001 From: ahmadk953 <103906421+ahmadk953@users.noreply.github.com> Date: Tue, 17 Jun 2025 20:24:57 -0400 Subject: [PATCH 4/8] chore: small script and file fixes --- .github/workflows/docker.yml | 4 +- .lintstagedrc.mjs | 10 +- docker-compose.yml | 29 ++- docker/pgbouncer/Dockerfile | 31 ++- docker/pgbouncer/README.md | 4 +- docker/pgbouncer/entrypoint.sh | 359 +++++++++++++++++++++++---------- drizzle.config.ts | 2 - generate-certs.sh | 8 +- src/util/helpers.ts | 4 +- 9 files changed, 310 insertions(+), 141 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index f30ed50..dcfb252 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -50,7 +50,7 @@ jobs: with: context: docker/pgbouncer file: docker/pgbouncer/Dockerfile - push: true + push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} cache-from: type=gha,scope=pgbouncer @@ -60,4 +60,4 @@ jobs: env: TAGS: ${{ steps.meta.outputs.tags }} DIGEST: ${{ steps.build-and-push.outputs.digest }} - run: echo "${TAGS}" | xargs -I {} cosign sign --yes {}@${DIGEST} + run: echo "${TAGS}" | xargs -I {} cosign sign --yes "{}@${DIGEST}" diff --git a/.lintstagedrc.mjs b/.lintstagedrc.mjs index f0e6371..879f1a3 100644 --- a/.lintstagedrc.mjs +++ b/.lintstagedrc.mjs @@ -2,9 +2,13 @@ import path from 'path'; import process from 'process'; const buildEslintCommand = (filenames) => { - // only lint files under src/ - const srcFiles = filenames.filter((f) => f.startsWith('src/')); - if (srcFiles.length === 0) return ''; + 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(' ')}`; diff --git a/docker-compose.yml b/docker-compose.yml index 60203ea..5edbaa8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -17,7 +17,11 @@ services: -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}'] + test: + [ + 'CMD-SHELL', + 'PGPASSWORD=${POSTGRES_PASSWORD} pg_isready -U ${POSTGRES_USER} -h localhost -p 5432 --db=${POSTGRES_DB}', + ] interval: 10s timeout: 5s retries: 5 @@ -44,7 +48,20 @@ services: --tls-key-file /certs/server.key --tls-ca-cert-file /certs/ca.crt healthcheck: - test: ['CMD', 'valkey-cli', '-a', '${VALKEY_PASSWORD}', 'ping'] + test: [ + 'CMD-SHELL', + 'valkey-cli + -a + ${VALKEY_PASSWORD} + --tls + --cacert + /certs/ca.crt + --cert + /certs/server.crt + --key + /certs/server.key + ping', + ] interval: 10s timeout: 5s retries: 5 @@ -58,7 +75,7 @@ services: DB_USER: ${POSTGRES_USER} DB_PASSWORD: ${POSTGRES_PASSWORD} DB_HOST: postgres - # DB_NAME: ${POSTGRES_DB} + AUTH_USER: ${POSTGRES_USER} AUTH_TYPE: scram-sha-256 POOL_MODE: transaction ADMIN_USERS: ${POSTGRES_USER} @@ -76,7 +93,11 @@ services: - ./certs/pgbouncer-server.key:/certs/server.key:ro - ./certs/pgbouncer-ca.crt:/certs/ca.crt:ro healthcheck: - test: ['CMD', 'pg_isready', '-h', 'localhost'] + test: + [ + 'CMD-SHELL', + 'PGPASSWORD=${POSTGRES_PASSWORD} pg_isready -U ${POSTGRES_USER} -h localhost -p 5432 --db=${POSTGRES_DB}', + ] networks: - services diff --git a/docker/pgbouncer/Dockerfile b/docker/pgbouncer/Dockerfile index 7887f3e..99b0502 100644 --- a/docker/pgbouncer/Dockerfile +++ b/docker/pgbouncer/Dockerfile @@ -11,27 +11,24 @@ 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 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 - -RUN curl -Lo /pgbouncer.tar.gz https://pgbouncer.github.io/downloads/files/${PGBOUNCER_VERSION}/pgbouncer-${PGBOUNCER_VERSION}.tar.gz && \ - tar -xzf /pgbouncer.tar.gz && mv /pgbouncer-${PGBOUNCER_VERSION} /pgbouncer - -RUN cd /c-ares && ./configure && make && make install -RUN cd /pgbouncer && ./configure --with-cares && make && make install +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 -COPY entrypoint.sh /entrypoint.sh - -RUN apk add --no-cache busybox libevent postgresql-client \ - && 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 /entrypoint.sh \ - && chmod +x /entrypoint.sh +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 diff --git a/docker/pgbouncer/README.md b/docker/pgbouncer/README.md index 14d6aa2..027c5bf 100644 --- a/docker/pgbouncer/README.md +++ b/docker/pgbouncer/README.md @@ -58,7 +58,7 @@ docker run --rm \ - **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 with the exception of 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). +- **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 \ @@ -67,7 +67,7 @@ docker run --rm \ -e DB_HOST=postgres-host \ -e DB_NAME=database \ -v pgbouncer.ini:/etc/pgbouncer/pgbouncer.ini:ro \ - -p 5432:5432 + -p 5432:5432 \ ghcr.io/ahmadk953/poixpixel-discord-bot-pgbouncer ``` diff --git a/docker/pgbouncer/entrypoint.sh b/docker/pgbouncer/entrypoint.sh index 64df010..c5d27b3 100644 --- a/docker/pgbouncer/entrypoint.sh +++ b/docker/pgbouncer/entrypoint.sh @@ -22,7 +22,7 @@ fi # Parameters: # - The url we should parse # Returns (sets variables): DB_USER, DB_PASSWORD, DB_HOST, DB_PORT, DB_NAME -function parse_url() { +parse_url() { # Thanks to https://stackoverflow.com/a/17287984/146289 # Allow to pass values like dj-database-url / django-environ accept @@ -39,10 +39,10 @@ function parse_url() { fi # extract the host -- updated - hostport=`echo $url | sed -e s,$userpass@,,g | cut -d/ -f1` - port=`echo $hostport | grep : | cut -d: -f2` + 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_HOST=$(echo $hostport | grep : | cut -d: -f1) DB_PORT="${port}" else DB_HOST="${hostport}" @@ -52,31 +52,42 @@ function parse_url() { } # Grabs variables set by `parse_url` and adds them to the userlist if not already set in there. -function generate_userlist_if_needed() { - if [ -n "${DB_USER}" -a -n "${DB_PASSWORD}" -a -e "${_AUTH_FILE}" ] && ! grep -q "^\"${DB_USER}\"" "${_AUTH_FILE}"; then - if [ "${AUTH_TYPE}" == "plain" ] || [ "${AUTH_TYPE}" == "scram-sha-256" ]; then +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$(echo -n "${DB_PASSWORD}${DB_USER}" | md5sum | cut -f 1 -d ' ')" + pass="md5$(printf '%s' "${DB_PASSWORD}${DB_USER}" | md5sum | cut -f 1 -d ' ')" fi - echo "\"${DB_USER}\" \"${pass}\"" >> "${_AUTH_FILE}" + 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. -function generate_config_db_entry() { - printf "\ -${DB_NAME:-*} = host=${DB_HOST:?"Setup pgbouncer config error! You must set DB_HOST env"} \ -port=${DB_PORT:-5432} auth_user=${DB_USER:-postgres} -${CLIENT_ENCODING:+client_encoding = ${CLIENT_ENCODING}\n}\ -" >> "${PG_CONFIG_FILE}" +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 read url; do + echo "${DATABASE_URLS}" | tr ',' '\n' | while IFS= read -r url; do parse_url "$url" generate_userlist_if_needed done @@ -87,19 +98,20 @@ else generate_userlist_if_needed fi -if [ ! -f "${PG_CONFIG_FILE}" ]; then +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. - printf "\ -################## Auto generated ################## -[databases] -" > "${PG_CONFIG_FILE}" + # 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 url; do + echo "$DATABASE_URLS" | tr , '\n' | while read -r url; do parse_url "$url" generate_config_db_entry done @@ -110,94 +122,231 @@ if [ ! -f "${PG_CONFIG_FILE}" ]; then generate_config_db_entry fi - printf "\ -[pgbouncer] -listen_addr = ${LISTEN_ADDR:-0.0.0.0} -listen_port = ${LISTEN_PORT:-5432} -unix_socket_dir = ${UNIX_SOCKET_DIR} -user = pgbouncer -auth_file = ${_AUTH_FILE} -${AUTH_HBA_FILE:+auth_hba_file = ${AUTH_HBA_FILE}\n}\ -auth_type = ${AUTH_TYPE:-md5} -${AUTH_USER:+auth_user = ${AUTH_USER}\n}\ -${AUTH_QUERY:+auth_query = ${AUTH_QUERY}\n}\ -${AUTH_DBNAME:+auth_dbname = ${AUTH_DBNAME}\n}\ -${POOL_MODE:+pool_mode = ${POOL_MODE}\n}\ -${MAX_CLIENT_CONN:+max_client_conn = ${MAX_CLIENT_CONN}\n}\ -${POOL_SIZE:+pool_size = ${POOL_SIZE}\n}\ -${DEFAULT_POOL_SIZE:+default_pool_size = ${DEFAULT_POOL_SIZE}\n}\ -${MIN_POOL_SIZE:+min_pool_size = ${MIN_POOL_SIZE}\n}\ -${RESERVE_POOL_SIZE:+reserve_pool_size = ${RESERVE_POOL_SIZE}\n}\ -${RESERVE_POOL_TIMEOUT:+reserve_pool_timeout = ${RESERVE_POOL_TIMEOUT}\n}\ -${MAX_DB_CONNECTIONS:+max_db_connections = ${MAX_DB_CONNECTIONS}\n}\ -${MAX_USER_CONNECTIONS:+max_user_connections = ${MAX_USER_CONNECTIONS}\n}\ -${SERVER_ROUND_ROBIN:+server_round_robin = ${SERVER_ROUND_ROBIN}\n}\ -ignore_startup_parameters = ${IGNORE_STARTUP_PARAMETERS:-extra_float_digits} -${DISABLE_PQEXEC:+disable_pqexec = ${DISABLE_PQEXEC}\n}\ -${APPLICATION_NAME_ADD_HOST:+application_name_add_host = ${APPLICATION_NAME_ADD_HOST}\n}\ -${TIMEZONE:+timezone = ${TIMEZONE}\n}\ -${MAX_PREPARED_STATEMENTS:+max_prepared_statements = ${MAX_PREPARED_STATEMENTS}\n}\ + # 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" -# Log settings -${LOG_CONNECTIONS:+log_connections = ${LOG_CONNECTIONS}\n}\ -${LOG_DISCONNECTIONS:+log_disconnections = ${LOG_DISCONNECTIONS}\n}\ -${LOG_POOLER_ERRORS:+log_pooler_errors = ${LOG_POOLER_ERRORS}\n}\ -${LOG_STATS:+log_stats = ${LOG_STATS}\n}\ -${STATS_PERIOD:+stats_period = ${STATS_PERIOD}\n}\ -${VERBOSE:+verbose = ${VERBOSE}\n}\ -admin_users = ${ADMIN_USERS:-postgres} -${STATS_USERS:+stats_users = ${STATS_USERS}\n}\ + # 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 -# Connection sanity checks, timeouts -${SERVER_RESET_QUERY:+server_reset_query = ${SERVER_RESET_QUERY}\n}\ -${SERVER_RESET_QUERY_ALWAYS:+server_reset_query_always = ${SERVER_RESET_QUERY_ALWAYS}\n}\ -${SERVER_CHECK_DELAY:+server_check_delay = ${SERVER_CHECK_DELAY}\n}\ -${SERVER_CHECK_QUERY:+server_check_query = ${SERVER_CHECK_QUERY}\n}\ -${SERVER_LIFETIME:+server_lifetime = ${SERVER_LIFETIME}\n}\ -${SERVER_IDLE_TIMEOUT:+server_idle_timeout = ${SERVER_IDLE_TIMEOUT}\n}\ -${SERVER_CONNECT_TIMEOUT:+server_connect_timeout = ${SERVER_CONNECT_TIMEOUT}\n}\ -${SERVER_LOGIN_RETRY:+server_login_retry = ${SERVER_LOGIN_RETRY}\n}\ -${CLIENT_LOGIN_TIMEOUT:+client_login_timeout = ${CLIENT_LOGIN_TIMEOUT}\n}\ -${AUTODB_IDLE_TIMEOUT:+autodb_idle_timeout = ${AUTODB_IDLE_TIMEOUT}\n}\ -${DNS_MAX_TTL:+dns_max_ttl = ${DNS_MAX_TTL}\n}\ -${DNS_NXDOMAIN_TTL:+dns_nxdomain_ttl = ${DNS_NXDOMAIN_TTL}\n}\ + # 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 -# TLS settings -${CLIENT_TLS_SSLMODE:+client_tls_sslmode = ${CLIENT_TLS_SSLMODE}\n}\ -${CLIENT_TLS_KEY_FILE:+client_tls_key_file = ${CLIENT_TLS_KEY_FILE}\n}\ -${CLIENT_TLS_CERT_FILE:+client_tls_cert_file = ${CLIENT_TLS_CERT_FILE}\n}\ -${CLIENT_TLS_CA_FILE:+client_tls_ca_file = ${CLIENT_TLS_CA_FILE}\n}\ -${CLIENT_TLS_PROTOCOLS:+client_tls_protocols = ${CLIENT_TLS_PROTOCOLS}\n}\ -${CLIENT_TLS_CIPHERS:+client_tls_ciphers = ${CLIENT_TLS_CIPHERS}\n}\ -${CLIENT_TLS_ECDHCURVE:+client_tls_ecdhcurve = ${CLIENT_TLS_ECDHCURVE}\n}\ -${CLIENT_TLS_DHEPARAMS:+client_tls_dheparams = ${CLIENT_TLS_DHEPARAMS}\n}\ -${SERVER_TLS_SSLMODE:+server_tls_sslmode = ${SERVER_TLS_SSLMODE}\n}\ -${SERVER_TLS_CA_FILE:+server_tls_ca_file = ${SERVER_TLS_CA_FILE}\n}\ -${SERVER_TLS_KEY_FILE:+server_tls_key_file = ${SERVER_TLS_KEY_FILE}\n}\ -${SERVER_TLS_CERT_FILE:+server_tls_cert_file = ${SERVER_TLS_CERT_FILE}\n}\ -${SERVER_TLS_PROTOCOLS:+server_tls_protocols = ${SERVER_TLS_PROTOCOLS}\n}\ -${SERVER_TLS_CIPHERS:+server_tls_ciphers = ${SERVER_TLS_CIPHERS}\n}\ + # 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 -# Dangerous timeouts -${QUERY_TIMEOUT:+query_timeout = ${QUERY_TIMEOUT}\n}\ -${QUERY_WAIT_TIMEOUT:+query_wait_timeout = ${QUERY_WAIT_TIMEOUT}\n}\ -${CLIENT_IDLE_TIMEOUT:+client_idle_timeout = ${CLIENT_IDLE_TIMEOUT}\n}\ -${IDLE_TRANSACTION_TIMEOUT:+idle_transaction_timeout = ${IDLE_TRANSACTION_TIMEOUT}\n}\ -${PKT_BUF:+pkt_buf = ${PKT_BUF}\n}\ -${MAX_PACKET_SIZE:+max_packet_size = ${MAX_PACKET_SIZE}\n}\ -${LISTEN_BACKLOG:+listen_backlog = ${LISTEN_BACKLOG}\n}\ -${SBUF_LOOPCNT:+sbuf_loopcnt = ${SBUF_LOOPCNT}\n}\ -${SUSPEND_TIMEOUT:+suspend_timeout = ${SUSPEND_TIMEOUT}\n}\ -${TCP_DEFER_ACCEPT:+tcp_defer_accept = ${TCP_DEFER_ACCEPT}\n}\ -${TCP_KEEPALIVE:+tcp_keepalive = ${TCP_KEEPALIVE}\n}\ -${TCP_KEEPCNT:+tcp_keepcnt = ${TCP_KEEPCNT}\n}\ -${TCP_KEEPIDLE:+tcp_keepidle = ${TCP_KEEPIDLE}\n}\ -${TCP_KEEPINTVL:+tcp_keepintvl = ${TCP_KEEPINTVL}\n}\ -${TCP_USER_TIMEOUT:+tcp_user_timeout = ${TCP_USER_TIMEOUT}\n}\ -################## end file ################## -" >> "${PG_CONFIG_FILE}" + # 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" cat "${PG_CONFIG_FILE}" fi echo "Starting $*..." -exec "$@" \ No newline at end of file +exec "$@" diff --git a/drizzle.config.ts b/drizzle.config.ts index 89cc62e..fc258df 100644 --- a/drizzle.config.ts +++ b/drizzle.config.ts @@ -1,5 +1,3 @@ -/* eslint-disable */ - import fs from 'node:fs'; import path from 'node:path'; import { defineConfig } from 'drizzle-kit'; diff --git a/generate-certs.sh b/generate-certs.sh index eebdac0..32ecd19 100755 --- a/generate-certs.sh +++ b/generate-certs.sh @@ -45,9 +45,9 @@ sudo cp certs/pgbouncer-server.key certs/pgbouncer-client.key sudo cp certs/cache-server.key certs/cache-client.key # Change Client Key Ownership -sudo chown $_uid:$_gid certs/pgbouncer-client.key -sudo chown $_uid:$_gid certs/cache-client.key +sudo chown "${_uid}:${_gid}" certs/pgbouncer-client.key +sudo chown "${_uid}:${_gid}" certs/cache-client.key # Change Client Key Permissions -sudo chmod +r certs/pgbouncer-client.key -sudo chmod +r certs/cache-client.key +sudo chmod 0600 certs/pgbouncer-client.key +sudo chmod 0600 certs/cache-client.key diff --git a/src/util/helpers.ts b/src/util/helpers.ts index e1f7d95..5b7b2e7 100644 --- a/src/util/helpers.ts +++ b/src/util/helpers.ts @@ -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 @@ -68,7 +68,7 @@ export async function generateMemberBanner({ height, }: generateMemberBannerTypes): Promise { const welcomeBackground = path.join( - __dirname, + PROJECT_ROOT, 'assets', 'images', 'welcome-bg.png', From 2a1bfa48175006c1e26e2bef7b090cc6b0f52d49 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 18 Jun 2025 21:37:31 +0000 Subject: [PATCH 5/8] chore(deps): bump pg from 8.16.0 to 8.16.1 Bumps [pg](https://github.com/brianc/node-postgres/tree/HEAD/packages/pg) from 8.16.0 to 8.16.1. - [Changelog](https://github.com/brianc/node-postgres/blob/master/CHANGELOG.md) - [Commits](https://github.com/brianc/node-postgres/commits/pg@8.16.1/packages/pg) --- updated-dependencies: - dependency-name: pg dependency-version: 8.16.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- package.json | 2 +- yarn.lock | 50 +++++++++++++++++++++++++------------------------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/package.json b/package.json index 11e287e..4cfd608 100644 --- a/package.json +++ b/package.json @@ -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.1" }, "devDependencies": { "@commitlint/cli": "^19.8.1", diff --git a/yarn.lock b/yarn.lock index 8b41cdc..5a76ca0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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.1": + version: 1.10.1 + resolution: "pg-protocol@npm:1.10.1" + checksum: 10c0/f684e4aaae52b296139b5ee573819e5eedbdd09c5dc4616a139da2034716f0bd83e30ab478ad688415e4bc77b5e59335f4e1b41b4890472541b12e217c375adb 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.1": + version: 8.16.1 + resolution: "pg@npm:8.16.1" 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.1" 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/f5053a37074a01cb889085b6242659784c0345bb10ca9d0e6d7ea1bbbd0d0e63b3e212c7fa3fc66d6838ce9f03eed3b9aff44dd3ffc5f1cbb1eee6468c2e2b15 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.1" prettier: "npm:3.5.3" ts-node: "npm:^10.9.2" ts-patch: "npm:^3.3.0" From a7b9134047305046524a3e45e96ec346eb2b3f0c Mon Sep 17 00:00:00 2001 From: ahmadk953 <103906421+ahmadk953@users.noreply.github.com> Date: Thu, 19 Jun 2025 17:55:52 -0400 Subject: [PATCH 6/8] chore: switched to redis and cleanup --- .github/workflows/docker.yml | 11 +-- .env.example => docker/.env.example | 1 - .../docker-compose.yml | 79 ++++++++----------- docker/pgbouncer/README.md | 16 ++-- docker/redis.conf | 7 ++ drizzle.config.ts | 4 +- generate-certs.sh | 66 ++++++---------- src/db/db.ts | 4 +- src/db/redis.ts | 6 +- 9 files changed, 84 insertions(+), 110 deletions(-) rename .env.example => docker/.env.example (73%) rename docker-compose.yml => docker/docker-compose.yml (54%) create mode 100644 docker/redis.conf diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index dcfb252..87a14f0 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -2,12 +2,12 @@ name: Docker Build and Push on: schedule: - - cron: '43 0 * * *' + - cron: "43 0 * * *" push: - branches: [ "main" ] - tags: [ 'v*.*.*' ] + branches: ["main"] + tags: ["v*.*.*"] pull_request: - branches: [ "main" ] + branches: ["main"] env: REGISTRY: ghcr.io @@ -56,7 +56,8 @@ jobs: cache-from: type=gha,scope=pgbouncer cache-to: type=gha,scope=pgbouncer,mode=max - - name: Sign the published Docker image for pgbouncer + - 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 }} diff --git a/.env.example b/docker/.env.example similarity index 73% rename from .env.example rename to docker/.env.example index 558a922..5298d68 100644 --- a/.env.example +++ b/docker/.env.example @@ -1,4 +1,3 @@ POSTGRES_USER=your_postgres_user POSTGRES_PASSWORD=your_postgres_password POSTGRES_DB=your_database_name -VALKEY_PASSWORD=your_valkey_password diff --git a/docker-compose.yml b/docker/docker-compose.yml similarity index 54% rename from docker-compose.yml rename to docker/docker-compose.yml index 5edbaa8..885ce32 100644 --- a/docker-compose.yml +++ b/docker/docker-compose.yml @@ -2,20 +2,20 @@ services: postgres: image: postgres:17-alpine container_name: postgres - restart: always + 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} - 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 command: > postgres -c ssl=on - -c ssl_cert_file=/var/lib/postgresql/server.crt - -c ssl_key_file=/var/lib/postgresql/server.key + -c ssl_cert_file=/var/lib/postgresql/cert.pem + -c ssl_key_file=/var/lib/postgresql/key.pem healthcheck: test: [ @@ -28,49 +28,44 @@ services: networks: - services - valkey: - image: valkey/valkey:8-alpine - container_name: valkey - restart: always + redis: + image: redis:8-alpine + container_name: redis + restart: unless-stopped 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 + - ../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: > - 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 + redis-server /usr/local/etc/redis/redis.conf healthcheck: - test: [ + test: + [ 'CMD-SHELL', - 'valkey-cli - -a - ${VALKEY_PASSWORD} - --tls - --cacert - /certs/ca.crt - --cert - /certs/server.crt - --key - /certs/server.key - ping', + '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} @@ -80,18 +75,11 @@ services: POOL_MODE: transaction ADMIN_USERS: ${POSTGRES_USER} CLIENT_TLS_SSLMODE: require - CLIENT_TLS_CERT_FILE: /certs/server.crt - CLIENT_TLS_KEY_FILE: /certs/server.key - CLIENT_TLS_CA_FILE: /certs/ca.crt + 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 - ports: - - '5432:5432' - depends_on: - - postgres - volumes: - - ./certs/pgbouncer-server.crt:/certs/server.crt:ro - - ./certs/pgbouncer-server.key:/certs/server.key:ro - - ./certs/pgbouncer-ca.crt:/certs/ca.crt:ro + SERVER_TLS_CA_FILE: /certs/ca.pem healthcheck: test: [ @@ -103,7 +91,6 @@ services: volumes: postgres_data: - valkey_data: networks: services: diff --git a/docker/pgbouncer/README.md b/docker/pgbouncer/README.md index 027c5bf..5e6ab87 100644 --- a/docker/pgbouncer/README.md +++ b/docker/pgbouncer/README.md @@ -1,19 +1,19 @@ -# Pgbouncer +# PgBouncer -Pgbouncer is a lightweight connection pooler for PostgreSQL that helps optimize database connections by reusing established sessions. +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. +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. +- **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: +To build the PgBouncer Docker image, run: ```sh docker build -t my-pgbouncer ./docker/pgbouncer @@ -58,7 +58,7 @@ docker run --rm \ - **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). +- **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 \ @@ -66,7 +66,7 @@ docker run --rm \ -e DB_PASSWORD=pass \ -e DB_HOST=postgres-host \ -e DB_NAME=database \ - -v pgbouncer.ini:/etc/pgbouncer/pgbouncer.ini:ro \ + -v PgBouncer.ini:/etc/PgBouncer/PgBouncer.ini:ro \ -p 5432:5432 \ ghcr.io/ahmadk953/poixpixel-discord-bot-pgbouncer ``` diff --git a/docker/redis.conf b/docker/redis.conf new file mode 100644 index 0000000..98416d0 --- /dev/null +++ b/docker/redis.conf @@ -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 diff --git a/drizzle.config.ts b/drizzle.config.ts index fc258df..3baff27 100644 --- a/drizzle.config.ts +++ b/drizzle.config.ts @@ -14,9 +14,7 @@ export default defineConfig({ ssl: (() => { try { return { - ca: fs.readFileSync(path.resolve('./certs/pgbouncer-ca.crt')), - key: fs.readFileSync(path.resolve('./certs/pgbouncer-client.key')), - cert: fs.readFileSync(path.resolve('./certs/pgbouncer-server.crt')), + ca: fs.readFileSync(path.resolve('./certs/rootCA.pem')), }; } catch (error) { console.warn( diff --git a/generate-certs.sh b/generate-certs.sh index 32ecd19..7f3f28e 100755 --- a/generate-certs.sh +++ b/generate-certs.sh @@ -1,53 +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 -# Generate pgbouncer Certificates -openssl req -new -x509 -days 365 -nodes \ - -out certs/pgbouncer-server.crt \ - -keyout certs/pgbouncer-server.key \ - -subj "/CN=localhost" +# Generate Cache Certificates +mkcert -key-file certs/cache-key.pem -cert-file certs/cache-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 -cp certs/pgbouncer-server.crt certs/pgbouncer-ca.crt +# 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/pgbouncer-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-*.* -sudo chown 1100:1100 certs/pgbouncer-*.* - -# Get Client Keys -sudo cp certs/pgbouncer-server.key certs/pgbouncer-client.key -sudo cp certs/cache-server.key certs/cache-client.key - -# Change Client Key Ownership -sudo chown "${_uid}:${_gid}" certs/pgbouncer-client.key -sudo chown "${_uid}:${_gid}" certs/cache-client.key - -# Change Client Key Permissions -sudo chmod 0600 certs/pgbouncer-client.key -sudo chmod 0600 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 diff --git a/src/db/db.ts b/src/db/db.ts index d4a0207..498662e 100644 --- a/src/db/db.ts +++ b/src/db/db.ts @@ -103,9 +103,7 @@ export async function initializeDatabaseConnection(): Promise { ssl: (() => { try { return { - ca: fs.readFileSync(path.resolve('./certs/pgbouncer-ca.crt')), - key: fs.readFileSync(path.resolve('./certs/pgbouncer-client.key')), - cert: fs.readFileSync(path.resolve('./certs/pgbouncer-server.crt')), + ca: fs.readFileSync(path.resolve('./certs/rootCA.pem')), }; } catch (error) { console.warn( diff --git a/src/db/redis.ts b/src/db/redis.ts index 8348190..23fab78 100644 --- a/src/db/redis.ts +++ b/src/db/redis.ts @@ -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( From 73d7b75544220bdecad5d32e7c5541d479ca1164 Mon Sep 17 00:00:00 2001 From: Ahmad <103906421+ahmadk953@users.noreply.github.com> Date: Thu, 19 Jun 2025 17:59:52 -0400 Subject: [PATCH 7/8] chore: update docker/pgbouncer/entrypoint.sh Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Ahmad <103906421+ahmadk953@users.noreply.github.com> --- docker/pgbouncer/entrypoint.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docker/pgbouncer/entrypoint.sh b/docker/pgbouncer/entrypoint.sh index c5d27b3..0f243f9 100644 --- a/docker/pgbouncer/entrypoint.sh +++ b/docker/pgbouncer/entrypoint.sh @@ -345,7 +345,9 @@ if [ ! -f "$PG_CONFIG_FILE" ]; then printf 'tcp_user_timeout = %s\n' "${TCP_USER_TIMEOUT}" >>"$PG_CONFIG_FILE" fi printf '\n################## end file ##################\n' >>"$PG_CONFIG_FILE" - cat "${PG_CONFIG_FILE}" + if [ "${DEBUG}" = "true" ]; then + cat "${PG_CONFIG_FILE}" + fi fi echo "Starting $*..." From df20712cf131c2a6b7c9e128a336f97c065b068d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 19 Jun 2025 22:06:20 +0000 Subject: [PATCH 8/8] chore(deps): bump pg from 8.16.1 to 8.16.2 Bumps [pg](https://github.com/brianc/node-postgres/tree/HEAD/packages/pg) from 8.16.1 to 8.16.2. - [Changelog](https://github.com/brianc/node-postgres/blob/master/CHANGELOG.md) - [Commits](https://github.com/brianc/node-postgres/commits/pg@8.16.2/packages/pg) --- updated-dependencies: - dependency-name: pg dependency-version: 8.16.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- package.json | 2 +- yarn.lock | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 4cfd608..b5ed2dd 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "discord.js": "^14.20.0", "drizzle-orm": "^0.44.2", "ioredis": "^5.6.1", - "pg": "^8.16.1" + "pg": "^8.16.2" }, "devDependencies": { "@commitlint/cli": "^19.8.1", diff --git a/yarn.lock b/yarn.lock index 5a76ca0..fad579b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4063,10 +4063,10 @@ __metadata: languageName: node linkType: hard -"pg-protocol@npm:^1.10.1": - version: 1.10.1 - resolution: "pg-protocol@npm:1.10.1" - checksum: 10c0/f684e4aaae52b296139b5ee573819e5eedbdd09c5dc4616a139da2034716f0bd83e30ab478ad688415e4bc77b5e59335f4e1b41b4890472541b12e217c375adb +"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.1": - version: 8.16.1 - resolution: "pg@npm:8.16.1" +"pg@npm:^8.16.2": + version: 8.16.2 + resolution: "pg@npm:8.16.2" dependencies: pg-cloudflare: "npm:^1.2.6" pg-connection-string: "npm:^2.9.1" pg-pool: "npm:^3.10.1" - pg-protocol: "npm:^1.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/f5053a37074a01cb889085b6242659784c0345bb10ca9d0e6d7ea1bbbd0d0e63b3e212c7fa3fc66d6838ce9f03eed3b9aff44dd3ffc5f1cbb1eee6468c2e2b15 + 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.1" + pg: "npm:^8.16.2" prettier: "npm:3.5.3" ts-node: "npm:^10.9.2" ts-patch: "npm:^3.3.0"