From 9292145136c561ea4a97afd6aaa72661aa2a566f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20G=C3=B3mez=20Zarzosa?= <34076979+jorgegomzar@users.noreply.github.com> Date: Sun, 15 Feb 2026 08:27:27 +0100 Subject: [PATCH] fix(habitica): add user to mongodb replicaset to enable Dokploy DB backups (#574) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: use environment variables from configuration in habitica-server * feat: add users to mongodb replicaSet This commit adds admin and habitica users + enables replicaSet auth by introducing a keyfile. Dokploy DB backups require specifying an user and password, so this enables that feature for Habitica app. * chore: less retries * chore: do not start server until mongo is healthy * fix: ensure habitica user exists for habitica db * fix: remove BASE_URL to prevent infinite redirects * fix: add missing ADMIN_EMAIL --------- Co-authored-by: Jorge Gómez Zarzosa --- blueprints/habitica/docker-compose.yml | 75 ++++++++++++++++++++++---- blueprints/habitica/template.toml | 24 ++++++--- 2 files changed, 82 insertions(+), 17 deletions(-) diff --git a/blueprints/habitica/docker-compose.yml b/blueprints/habitica/docker-compose.yml index af226516..3990e571 100644 --- a/blueprints/habitica/docker-compose.yml +++ b/blueprints/habitica/docker-compose.yml @@ -1,13 +1,18 @@ -version: "3.8" - services: server: image: docker.io/awinterstein/habitica-server:latest restart: unless-stopped depends_on: - - mongo + mongo: + condition: service_healthy environment: - - NODE_DB_URI=mongodb://mongo/habitica + NODE_DB_URI: "mongodb://${MONGO_HABITICA_USER}:${MONGO_HABITICA_PASSWORD}@mongo/habitica?authSource=habitica" + INVITE_ONLY: "${INVITE_ONLY}" + EMAIL_SERVER_URL: "${EMAIL_SERVER_URL}" + EMAIL_SERVER_PORT: "${EMAIL_SERVER_PORT}" + EMAIL_SERVER_AUTH_USER: "${EMAIL_SERVER_AUTH_USER}" + EMAIL_SERVER_AUTH_PASSWORD: "${EMAIL_SERVER_AUTH_PASSWORD}" + ADMIN_EMAIL: "${ADMIN_EMAIL}" client: image: docker.io/awinterstein/habitica-client:latest @@ -20,12 +25,64 @@ services: mongo: image: docker.io/mongo:latest restart: unless-stopped - command: ["--replSet", "rs", "--bind_ip_all", "--port", "27017"] + command: > + bash -c " + echo \"${MONGO_KEYFILE_CONTENT}\" > /etc/mongo-keyfile && + chown 999:999 /etc/mongo-keyfile && + chmod 400 /etc/mongo-keyfile && + exec docker-entrypoint.sh mongod --replSet rs --bind_ip_all --port 27017 --keyFile /etc/mongo-keyfile + " + environment: + MONGO_INITDB_ROOT_USERNAME: ${MONGO_ADMIN_USER} + MONGO_INITDB_ROOT_PASSWORD: ${MONGO_ADMIN_PASSWORD} + MONGO_KEYFILE_CONTENT: ${MONGO_KEYFILE_CONTENT} + MONGO_HABITICA_USER: ${MONGO_HABITICA_USER} + MONGO_HABITICA_PASSWORD: ${MONGO_HABITICA_PASSWORD} + # --------------------------------------------------------- + # SMART HEALTHCHECK: Auto-fixes Hostname Mismatches for replicaSet + # --------------------------------------------------------- healthcheck: - test: echo "try { rs.status() } catch (err) { rs.initiate() }" | mongosh --port 27017 --quiet - interval: 10s - timeout: 30s - retries: 30 + test: | + mongosh --port 27017 --quiet -u "${MONGO_ADMIN_USER}" -p "${MONGO_ADMIN_PASSWORD}" --authenticationDatabase admin --eval " + try { + // 1. Hostname Fix + const config = rs.conf(); + const currentHost = require('os').hostname() + ':27017'; + if (config.members[0].host !== currentHost) { + config.members[0].host = currentHost; + rs.reconfig(config, { force: true }); + } + + // 2. User Creation Logic + const targetDb = db.getSiblingDB('habitica'); + const hUser = process.env.MONGO_HABITICA_USER; + const hPass = process.env.MONGO_HABITICA_PASSWORD; + + // We can only check/create users if we are Primary + if (rs.isMaster().ismaster) { + if (!targetDb.getUser(hUser)) { + print('Creating missing user ' + hUser + '...'); + targetDb.createUser({ user: hUser, pwd: hPass, roles: ['readWrite'] }); + } + // SUCCESS: User exists and we are Primary + quit(0); + } else { + // We are not Primary yet (still electing), so we cannot confirm user exists. + // Fail the check so the dependent app waits. + print('Waiting for Primary state...'); + quit(1); + } + } catch (err) { + // If not initialized, initiate and FAIL this check so we wait for the next cycle + try { + rs.initiate({ _id: 'rs', members: [{ _id: 0, host: require('os').hostname() + ':27017' }] }); + } catch (e) {} + quit(1); + } + " + interval: 5s + timeout: 10s + retries: 20 volumes: - habitica-mongo-data:/data/db diff --git a/blueprints/habitica/template.toml b/blueprints/habitica/template.toml index 3888ae24..a8cb5fc6 100644 --- a/blueprints/habitica/template.toml +++ b/blueprints/habitica/template.toml @@ -1,6 +1,9 @@ [variables] main_domain = "${domain}" mail_password = "${password:32}" +mongo_key = "${base64:756}" +mongo_admin_password = "${password}" +mongo_habitica_password = "${password}" [config] [[config.domains]] @@ -9,16 +12,21 @@ port = 80 host = "habitica.${main_domain}" [config.env] - -BASE_URL="https://habitica.${main_domain}" -INVITE_ONLY="false" -EMAIL_SERVER_URL="mail.example.com" -EMAIL_SERVER_PORT="587" -EMAIL_SERVER_AUTH_USER="mail_user" -EMAIL_SERVER_AUTH_PASSWORD="${mail_password}" +BASE_URL = "https://habitica.${main_domain}" +INVITE_ONLY = "false" +EMAIL_SERVER_URL = "mail.example.com" +EMAIL_SERVER_PORT = "587" +EMAIL_SERVER_AUTH_USER = "mail_user" +EMAIL_SERVER_AUTH_PASSWORD = "${mail_password}" +MONGO_KEYFILE_CONTENT = "${mongo_key}" +MONGO_ADMIN_USER = "admin" +MONGO_ADMIN_PASSWORD = "${mongo_admin_password}" +MONGO_HABITICA_USER = "habitica" +MONGO_HABITICA_PASSWORD = "${mongo_habitica_password}" +ADMIN_EMAIL = "no-reply@${main_domain}" [[config.mounts]] serviceName = "mongo" type = "volume" source = "habitica-mongo-data" -target = "/data/db" \ No newline at end of file +target = "/data/db"