From f170915328c051e574a5da4af0397e465b961e59 Mon Sep 17 00:00:00 2001 From: Matt Scott Date: Sun, 5 Dec 2021 23:28:38 -0500 Subject: [PATCH 1/5] Working on RC1 of the Docker overhaul. Current progress is a working local dev environment with auto-reload. --- configs/development.py | 24 ++--- configs/docker_config.py | 22 ++++- docker-compose-test.yml | 34 ------- docker-test/Dockerfile | 34 ------- docker-test/Dockerfile.pdns | 13 --- docker-test/env | 5 - docker-test/pdns.sqlite.sql | 92 ------------------- docker-test/start.sh | 24 ----- docker-test/wait-for-pdns.sh | 22 ----- docker/{ => alpine}/Dockerfile | 2 +- .../alpine/docker-compose.yml | 4 +- docker/{ => alpine}/entrypoint.sh | 0 docker/debian/Dockerfile | 58 ++++++++++++ docker/debian/entrypoint.sh | 19 ++++ docker/debian/pdns-check.sh | 31 +++++++ docker/debian/pdns-schema.sql | 89 ++++++++++++++++++ docker/stacks/local/docker-compose.yml | 47 ++++++++++ docker/stacks/local/env-pda | 18 ++++ docker/stacks/local/env-pdns | 20 ++++ powerdnsadmin/default_config.py | 4 +- requirements.txt | 1 + run.py | 2 +- 22 files changed, 322 insertions(+), 243 deletions(-) delete mode 100644 docker-compose-test.yml delete mode 100644 docker-test/Dockerfile delete mode 100644 docker-test/Dockerfile.pdns delete mode 100644 docker-test/env delete mode 100644 docker-test/pdns.sqlite.sql delete mode 100644 docker-test/start.sh delete mode 100644 docker-test/wait-for-pdns.sh rename docker/{ => alpine}/Dockerfile (99%) rename docker-compose.yml => docker/alpine/docker-compose.yml (89%) rename docker/{ => alpine}/entrypoint.sh (100%) create mode 100644 docker/debian/Dockerfile create mode 100755 docker/debian/entrypoint.sh create mode 100644 docker/debian/pdns-check.sh create mode 100644 docker/debian/pdns-schema.sql create mode 100644 docker/stacks/local/docker-compose.yml create mode 100644 docker/stacks/local/env-pda create mode 100644 docker/stacks/local/env-pdns diff --git a/configs/development.py b/configs/development.py index 2c2e63d..f2a5199 100644 --- a/configs/development.py +++ b/configs/development.py @@ -1,31 +1,33 @@ import os -#import urllib.parse +import urllib.parse + basedir = os.path.abspath(os.path.dirname(__file__)) ### BASIC APP CONFIG +DEBUG_MODE = True SALT = '$2b$12$yLUMTIfl21FKJQpTkRQXCu' SECRET_KEY = 'e951e5a1f4b94151b360f47edf596dd2' BIND_ADDRESS = '0.0.0.0' -PORT = 9191 +PORT = 80 OFFLINE_MODE = False ### DATABASE CONFIG SQLA_DB_USER = 'pda' -SQLA_DB_PASSWORD = 'changeme' -SQLA_DB_HOST = '127.0.0.1' +SQLA_DB_PASSWORD = 'qHZDZ26fPqqCfKZtWkQ9' +SQLA_DB_HOST = 'mysql' SQLA_DB_NAME = 'pda' SQLALCHEMY_TRACK_MODIFICATIONS = True ### DATABASE - MySQL -#SQLALCHEMY_DATABASE_URI = 'mysql://{}:{}@{}/{}'.format( -# urllib.parse.quote_plus(SQLA_DB_USER), -# urllib.parse.quote_plus(SQLA_DB_PASSWORD), -# SQLA_DB_HOST, -# SQLA_DB_NAME -#) +SQLALCHEMY_DATABASE_URI = 'mysql://{}:{}@{}/{}'.format( + urllib.parse.quote_plus(SQLA_DB_USER), + urllib.parse.quote_plus(SQLA_DB_PASSWORD), + SQLA_DB_HOST, + SQLA_DB_NAME +) ### DATABASE - SQLite -SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'pdns.db') +#SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'pdns.db') ### SMTP config # MAIL_SERVER = 'localhost' diff --git a/configs/docker_config.py b/configs/docker_config.py index 6666fc2..91f0f10 100644 --- a/configs/docker_config.py +++ b/configs/docker_config.py @@ -1,7 +1,25 @@ -# Defaults for Docker image +import urllib.parse +DEBUG_MODE = False BIND_ADDRESS = '0.0.0.0' PORT = 80 -SQLALCHEMY_DATABASE_URI = 'sqlite:////data/powerdns-admin.db' + +#SQLALCHEMY_DATABASE_URI = 'sqlite:////data/powerdns-admin.db' + +### DATABASE CONFIG +SQLA_DB_USER = 'pda' +SQLA_DB_PASSWORD = 'qHZDZ26fPqqCfKZtWkQ9' +SQLA_DB_HOST = 'mysql' +SQLA_DB_NAME = 'pda' +SQLALCHEMY_TRACK_MODIFICATIONS = True + +### DATABASE - MySQL +SQLALCHEMY_DATABASE_URI = 'mysql://{}:{}@{}/{}'.format( + urllib.parse.quote_plus(SQLA_DB_USER), + urllib.parse.quote_plus(SQLA_DB_PASSWORD), + SQLA_DB_HOST, + SQLA_DB_NAME +) + legal_envvars = ( 'SECRET_KEY', diff --git a/docker-compose-test.yml b/docker-compose-test.yml deleted file mode 100644 index 7dcf4a0..0000000 --- a/docker-compose-test.yml +++ /dev/null @@ -1,34 +0,0 @@ -version: "2.1" - -services: - powerdns-admin: - build: - context: . - dockerfile: docker-test/Dockerfile - image: powerdns-admin-test - container_name: powerdns-admin-test - ports: - - "9191:80" - networks: - - default - env_file: - - ./docker-test/env - depends_on: - - pdns-server - - pdns-server: - build: - context: . - dockerfile: docker-test/Dockerfile.pdns - image: pdns-server-test - ports: - - "5053:53" - - "5053:53/udp" - - "8081:8081" - networks: - - default - env_file: - - ./docker-test/env - -networks: - default: diff --git a/docker-test/Dockerfile b/docker-test/Dockerfile deleted file mode 100644 index 577e120..0000000 --- a/docker-test/Dockerfile +++ /dev/null @@ -1,34 +0,0 @@ -FROM debian:stretch-slim -LABEL maintainer="k@ndk.name" - -ENV LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8 LANGUAGE=en_US.UTF-8 - -RUN apt-get update -y \ - && apt-get install -y --no-install-recommends apt-transport-https locales locales-all python3-pip python3-setuptools python3-dev curl libsasl2-dev libldap2-dev libssl-dev libxml2-dev libxslt1-dev libxmlsec1-dev libffi-dev build-essential libmariadb-dev-compat \ - && curl -sL https://deb.nodesource.com/setup_10.x | bash - \ - && curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \ - && echo "deb https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list \ - && apt-get update -y \ - && apt-get install -y nodejs yarn \ - && apt-get clean -y \ - && rm -rf /var/lib/apt/lists/* - -# We copy just the requirements.txt first to leverage Docker cache -COPY ./requirements.txt /app/requirements.txt - -WORKDIR /app -RUN pip3 install --upgrade pip -RUN pip3 install -r requirements.txt - -COPY . /app -COPY ./docker/entrypoint.sh /usr/local/bin/ -RUN chmod +x /usr/local/bin/entrypoint.sh - -ENV FLASK_APP=powerdnsadmin/__init__.py -RUN yarn install --pure-lockfile --production \ - && yarn cache clean \ - && flask assets build - -COPY ./docker-test/wait-for-pdns.sh /opt -RUN chmod u+x /opt/wait-for-pdns.sh -CMD ["/opt/wait-for-pdns.sh", "/usr/local/bin/pytest","--capture=no","-vv"] diff --git a/docker-test/Dockerfile.pdns b/docker-test/Dockerfile.pdns deleted file mode 100644 index c234c98..0000000 --- a/docker-test/Dockerfile.pdns +++ /dev/null @@ -1,13 +0,0 @@ -FROM ubuntu:latest - -RUN apt-get update && apt-get install -y pdns-backend-sqlite3 pdns-server sqlite3 - -COPY ./docker-test/pdns.sqlite.sql /data/pdns.sql -ADD ./docker-test/start.sh /data/ - -RUN rm -f /etc/powerdns/pdns.d/pdns.simplebind.conf -RUN rm -f /etc/powerdns/pdns.d/bind.conf - -RUN chmod +x /data/start.sh && mkdir -p /var/empty/var/run - -CMD /data/start.sh diff --git a/docker-test/env b/docker-test/env deleted file mode 100644 index 30d9bb3..0000000 --- a/docker-test/env +++ /dev/null @@ -1,5 +0,0 @@ -PDNS_PROTO=http -PDNS_PORT=8081 -PDNS_HOST=pdns-server -PDNS_API_KEY=changeme -PDNS_WEBSERVER_ALLOW_FROM=0.0.0.0/0 \ No newline at end of file diff --git a/docker-test/pdns.sqlite.sql b/docker-test/pdns.sqlite.sql deleted file mode 100644 index 4748a8d..0000000 --- a/docker-test/pdns.sqlite.sql +++ /dev/null @@ -1,92 +0,0 @@ -PRAGMA foreign_keys = 1; - -CREATE TABLE domains ( - id INTEGER PRIMARY KEY, - name VARCHAR(255) NOT NULL COLLATE NOCASE, - master VARCHAR(128) DEFAULT NULL, - last_check INTEGER DEFAULT NULL, - type VARCHAR(6) NOT NULL, - notified_serial INTEGER DEFAULT NULL, - account VARCHAR(40) DEFAULT NULL -); - -CREATE UNIQUE INDEX name_index ON domains(name); - - -CREATE TABLE records ( - id INTEGER PRIMARY KEY, - domain_id INTEGER DEFAULT NULL, - name VARCHAR(255) DEFAULT NULL, - type VARCHAR(10) DEFAULT NULL, - content VARCHAR(65535) DEFAULT NULL, - ttl INTEGER DEFAULT NULL, - prio INTEGER DEFAULT NULL, - change_date INTEGER DEFAULT NULL, - disabled BOOLEAN DEFAULT 0, - ordername VARCHAR(255), - auth BOOL DEFAULT 1, - FOREIGN KEY(domain_id) REFERENCES domains(id) ON DELETE CASCADE ON UPDATE CASCADE -); - -CREATE INDEX rec_name_index ON records(name); -CREATE INDEX nametype_index ON records(name,type); -CREATE INDEX domain_id ON records(domain_id); -CREATE INDEX orderindex ON records(ordername); - - -CREATE TABLE supermasters ( - ip VARCHAR(64) NOT NULL, - nameserver VARCHAR(255) NOT NULL COLLATE NOCASE, - account VARCHAR(40) NOT NULL -); - -CREATE UNIQUE INDEX ip_nameserver_pk ON supermasters(ip, nameserver); - - -CREATE TABLE comments ( - id INTEGER PRIMARY KEY, - domain_id INTEGER NOT NULL, - name VARCHAR(255) NOT NULL, - type VARCHAR(10) NOT NULL, - modified_at INT NOT NULL, - account VARCHAR(40) DEFAULT NULL, - comment VARCHAR(65535) NOT NULL, - FOREIGN KEY(domain_id) REFERENCES domains(id) ON DELETE CASCADE ON UPDATE CASCADE -); - -CREATE INDEX comments_domain_id_index ON comments (domain_id); -CREATE INDEX comments_nametype_index ON comments (name, type); -CREATE INDEX comments_order_idx ON comments (domain_id, modified_at); - - -CREATE TABLE domainmetadata ( - id INTEGER PRIMARY KEY, - domain_id INT NOT NULL, - kind VARCHAR(32) COLLATE NOCASE, - content TEXT, - FOREIGN KEY(domain_id) REFERENCES domains(id) ON DELETE CASCADE ON UPDATE CASCADE -); - -CREATE INDEX domainmetaidindex ON domainmetadata(domain_id); - - -CREATE TABLE cryptokeys ( - id INTEGER PRIMARY KEY, - domain_id INT NOT NULL, - flags INT NOT NULL, - active BOOL, - content TEXT, - FOREIGN KEY(domain_id) REFERENCES domains(id) ON DELETE CASCADE ON UPDATE CASCADE -); - -CREATE INDEX domainidindex ON cryptokeys(domain_id); - - -CREATE TABLE tsigkeys ( - id INTEGER PRIMARY KEY, - name VARCHAR(255) COLLATE NOCASE, - algorithm VARCHAR(50) COLLATE NOCASE, - secret VARCHAR(255) -); - -CREATE UNIQUE INDEX namealgoindex ON tsigkeys(name, algorithm); diff --git a/docker-test/start.sh b/docker-test/start.sh deleted file mode 100644 index 9a66017..0000000 --- a/docker-test/start.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env sh - -if [ -z ${PDNS_API_KEY+x} ]; then - API_KEY=changeme -fi - -if [ -z ${PDNS_PORT+x} ]; then - WEB_PORT=8081 -fi - -# Import schema structure -if [ -e "/data/pdns.sql" ]; then - rm /data/pdns.db - cat /data/pdns.sql | sqlite3 /data/pdns.db - rm /data/pdns.sql - echo "Imported schema structure" -fi - -chown -R pdns:pdns /data/ - -/usr/sbin/pdns_server \ - --launch=gsqlite3 --gsqlite3-database=/data/pdns.db \ - --webserver=yes --webserver-address=0.0.0.0 --webserver-port=${PDNS_PORT} \ - --api=yes --api-key=$PDNS_API_KEY --webserver-allow-from=${PDNS_WEBSERVER_ALLOW_FROM} diff --git a/docker-test/wait-for-pdns.sh b/docker-test/wait-for-pdns.sh deleted file mode 100644 index d4c301b..0000000 --- a/docker-test/wait-for-pdns.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/sh - -set -e - -CMD="$1" -shift -CMD_ARGS="$@" - -LOOPS=10 -until curl -H "X-API-Key: ${PDNS_API_KEY}" "${PDNS_PROTO}://${PDNS_HOST}:${PDNS_PORT}/api/v1/servers"; do - >&2 echo "PDNS is unavailable - sleeping" - sleep 1 - if [ $LOOPS -eq 10 ] - then - break - fi -done - -sleep 5 - ->&2 echo "PDNS is up - executing command" -exec $CMD $CMD_ARGS diff --git a/docker/Dockerfile b/docker/alpine/Dockerfile similarity index 99% rename from docker/Dockerfile rename to docker/alpine/Dockerfile index 5296e02..5eb5496 100644 --- a/docker/Dockerfile +++ b/docker/alpine/Dockerfile @@ -33,7 +33,7 @@ RUN pip install --upgrade pip && \ pip install -r requirements.txt # Add sources -COPY . /build +COPY .. /build # Prepare assets RUN yarn install --pure-lockfile --production && \ diff --git a/docker-compose.yml b/docker/alpine/docker-compose.yml similarity index 89% rename from docker-compose.yml rename to docker/alpine/docker-compose.yml index e18d683..8e55cd5 100644 --- a/docker-compose.yml +++ b/docker/alpine/docker-compose.yml @@ -1,9 +1,9 @@ -version: "3" +version: "3.3" services: app: image: ngoduykhanh/powerdns-admin:latest - container_name: powerdns_admin + container_name: powerdns-admin ports: - "9191:80" logging: diff --git a/docker/entrypoint.sh b/docker/alpine/entrypoint.sh similarity index 100% rename from docker/entrypoint.sh rename to docker/alpine/entrypoint.sh diff --git a/docker/debian/Dockerfile b/docker/debian/Dockerfile new file mode 100644 index 0000000..9ef611f --- /dev/null +++ b/docker/debian/Dockerfile @@ -0,0 +1,58 @@ +FROM debian:11.1-slim +LABEL maintainer="matt@azorian.solutions" + +# Set additional container environment variables +ENV LC_ALL=en_US.UTF-8 \ + LANG=en_US.UTF-8 \ + LANGUAGE=en_US.UTF-8 \ + FLASK_APP=${FLASK_APP:-/app/run.py} + +# Perform the following tasks: +# - Install required APT packages per the required dependencies +# - Download and execute NodeSource setup package +# - Download and add Yarn public GPG key to APT +# - Add Yarn repository to the APT configuration +# - Update APT cache +# - Install NodeJS and Yarn APT packages +# - Clean APT caches and dynamic lists +# - Upgrade Python pip package using pip3 +RUN apt update -y \ + && apt install -y --no-install-recommends apt-transport-https locales locales-all python3-pip python3-setuptools \ + python3-dev curl libsasl2-dev libldap2-dev libssl-dev libxml2-dev libxmlsec1 libxslt1-dev libxmlsec1-dev xmlsec1 \ + libffi-dev build-essential libmariadb-dev-compat pkg-config \ + && curl -sL https://deb.nodesource.com/setup_14.x | bash - \ + && curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \ + && echo "deb https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list \ + && apt update -y \ + && apt install -y nodejs yarn \ + && apt clean -y \ + && rm -rf /var/lib/apt/lists/* \ + && pip3 install --upgrade pip + +# We copy just the requirements.txt first to leverage Docker cache +COPY /requirements.txt /app/requirements.txt + +# Change the working directory which may affect the build contexts of following commands +WORKDIR /app + +# Install the required Python pip packages as defined in the ./requirements.txt file +RUN pip3 install -r requirements.txt + +# Copy all project files into the container's /app directory +# FIXME: This is too broad and will almost guarentee constant image re-builds that can be skipped in dev environment +COPY . /app + +# Perform the following tasks: +# - Copy additional automation scripts to their appropriate locations +# - Set permissions on automation scripts +# - Install Yarn and clean the Yarn cache +# - Build Flask assets +RUN cp /app/docker/debian/entrypoint.sh /usr/local/bin/entrypoint.sh \ + && cp /app/docker/debian/pdns-check.sh /srv/ \ + && chmod +x /usr/local/bin/entrypoint.sh /srv/pdns-check.sh \ + && yarn install --pure-lockfile --production \ + && yarn cache clean \ + && flask assets build + +# Set the default command of the container to the PDNS check script to prevent premature execution 乁༼ ☯‿☯༽ㄏ +CMD ["/srv/pdns-check.sh", "/usr/local/bin/entrypoint.sh", "/app/run.py"] diff --git a/docker/debian/entrypoint.sh b/docker/debian/entrypoint.sh new file mode 100755 index 0000000..98d647b --- /dev/null +++ b/docker/debian/entrypoint.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +set -eo pipefail + +GUNICORN_TIMEOUT="${GUNICORN_TIMEOUT:-120}" +GUNICORN_WORKERS="${GUNICORN_WORKERS:-4}" +GUNICORN_LOGLEVEL="${GUNICORN_LOGLEVEL:-info}" +BIND_ADDRESS="${BIND_ADDRESS:-0.0.0.0:80}" +GUNICORN_ARGS="-t ${GUNICORN_TIMEOUT} --workers ${GUNICORN_WORKERS} --log-level ${GUNICORN_LOGLEVEL} --bind ${BIND_ADDRESS}" + +cd /app + +/bin/sh -c "flask db upgrade" + +if [ "$1" = "gunicorn" ]; then + exec "$@" $GUNICORN_ARGS +else + exec "$@" +fi diff --git a/docker/debian/pdns-check.sh b/docker/debian/pdns-check.sh new file mode 100644 index 0000000..e557a74 --- /dev/null +++ b/docker/debian/pdns-check.sh @@ -0,0 +1,31 @@ +#!/bin/sh +set -e + +# How many seconds to wait after a connection attempt failure before trying again +FAIL_DELAY=3 + +# How many seconds to wait after a successful connection attempt before proceeding to the next step +SUCCESS_DELAY=3 + +# How many connection attempts should be made before halting execution +MAX_ATTEMPTS=10 + +API_URI="${PDNS_PROTO}://${PDNS_HOST}:${PDNS_PORT}/api/v1/servers" +API_AUTH_HEADER="X-API-Key: ${PDNS_API_KEY}" +CMD="$1" +shift +CMD_ARGS="$@" + +until curl -H "${API_AUTH_HEADER}" "${API_URI}"; do + >&2 echo "\nPowerDNS Authoritative server API not online yet. Waiting for ${FAIL_DELAY} seconds..." + sleep $FAIL_DELAY + if [ $MAX_ATTEMPTS -eq 10 ] + then + break + fi +done + +sleep $SUCCESS_DELAY + +>&2 echo "PowerDNS Authoritative server API is online. Proceeding with next script execution..." +exec $CMD $CMD_ARGS diff --git a/docker/debian/pdns-schema.sql b/docker/debian/pdns-schema.sql new file mode 100644 index 0000000..9b1bdd1 --- /dev/null +++ b/docker/debian/pdns-schema.sql @@ -0,0 +1,89 @@ +CREATE TABLE domains ( + id INT AUTO_INCREMENT, + name VARCHAR(255) NOT NULL, + master VARCHAR(128) DEFAULT NULL, + last_check INT DEFAULT NULL, + type VARCHAR(6) NOT NULL, + notified_serial INT UNSIGNED DEFAULT NULL, + account VARCHAR(40) CHARACTER SET 'utf8' DEFAULT NULL, + PRIMARY KEY (id) +) Engine=InnoDB CHARACTER SET 'latin1'; + +CREATE UNIQUE INDEX name_index ON domains(name); + + +CREATE TABLE records ( + id BIGINT AUTO_INCREMENT, + domain_id INT DEFAULT NULL, + name VARCHAR(255) DEFAULT NULL, + type VARCHAR(10) DEFAULT NULL, + content VARCHAR(64000) DEFAULT NULL, + ttl INT DEFAULT NULL, + prio INT DEFAULT NULL, + disabled TINYINT(1) DEFAULT 0, + ordername VARCHAR(255) BINARY DEFAULT NULL, + auth TINYINT(1) DEFAULT 1, + PRIMARY KEY (id) +) Engine=InnoDB CHARACTER SET 'latin1'; + +CREATE INDEX nametype_index ON records(name,type); +CREATE INDEX domain_id ON records(domain_id); +CREATE INDEX ordername ON records (ordername); + + +CREATE TABLE supermasters ( + ip VARCHAR(64) NOT NULL, + nameserver VARCHAR(255) NOT NULL, + account VARCHAR(40) CHARACTER SET 'utf8' NOT NULL, + PRIMARY KEY (ip, nameserver) +) Engine=InnoDB CHARACTER SET 'latin1'; + + +CREATE TABLE comments ( + id INT AUTO_INCREMENT, + domain_id INT NOT NULL, + name VARCHAR(255) NOT NULL, + type VARCHAR(10) NOT NULL, + modified_at INT NOT NULL, + account VARCHAR(40) CHARACTER SET 'utf8' DEFAULT NULL, + comment TEXT CHARACTER SET 'utf8' NOT NULL, + PRIMARY KEY (id) +) Engine=InnoDB CHARACTER SET 'latin1'; + +CREATE INDEX comments_name_type_idx ON comments (name, type); +CREATE INDEX comments_order_idx ON comments (domain_id, modified_at); + + +CREATE TABLE domainmetadata ( + id INT AUTO_INCREMENT, + domain_id INT NOT NULL, + kind VARCHAR(32), + content TEXT, + PRIMARY KEY (id) +) Engine=InnoDB CHARACTER SET 'latin1'; + +CREATE INDEX domainmetadata_idx ON domainmetadata (domain_id, kind); + + +CREATE TABLE cryptokeys ( + id INT AUTO_INCREMENT, + domain_id INT NOT NULL, + flags INT NOT NULL, + active BOOL, + published BOOL DEFAULT 1, + content TEXT, + PRIMARY KEY(id) +) Engine=InnoDB CHARACTER SET 'latin1'; + +CREATE INDEX domainidindex ON cryptokeys(domain_id); + + +CREATE TABLE tsigkeys ( + id INT AUTO_INCREMENT, + name VARCHAR(255), + algorithm VARCHAR(50), + secret VARCHAR(255), + PRIMARY KEY (id) +) Engine=InnoDB CHARACTER SET 'latin1'; + +CREATE UNIQUE INDEX namealgoindex ON tsigkeys(name, algorithm); \ No newline at end of file diff --git a/docker/stacks/local/docker-compose.yml b/docker/stacks/local/docker-compose.yml new file mode 100644 index 0000000..991c5c9 --- /dev/null +++ b/docker/stacks/local/docker-compose.yml @@ -0,0 +1,47 @@ +version: "3.3" + +services: + + mysql: + image: mariadb:10.6 + restart: unless-stopped + environment: + MYSQL_ROOT_PASSWORD: qHZDZ26fPqqCfKZtWkQ9 + MYSQL_DATABASE: pda + MYSQL_USER: pda + MYSQL_PASSWORD: qHZDZ26fPqqCfKZtWkQ9 + + phpmyadmin: + image: phpmyadmin/phpmyadmin:5.1 + depends_on: ['mysql'] + restart: unless-stopped + environment: + - PMA_HOST=mysql + - PMA_PORT=3306 + #- PMA_ARBITRARY=1 + ports: + - 8082:80 + + pdns: + image: azoriansolutions/powerdns-nameserver:4.5.2-alpine-3.14-mysql + depends_on: ['mysql'] + restart: unless-stopped + env_file: ['./env-pdns'] + ports: + - "5053:53/tcp" + - "5053:53/udp" + - "8081:8081/tcp" + + powerdns-admin: + build: + context: ../../../ + dockerfile: ./docker/debian/Dockerfile + image: powerdns-admin-test + depends_on: ['pdns'] + restart: unless-stopped + container_name: powerdns-admin-test + env_file: ['./env-pda'] + ports: + - "8080:80" + volumes: + - ../../../powerdnsadmin:/app/powerdnsadmin diff --git a/docker/stacks/local/env-pda b/docker/stacks/local/env-pda new file mode 100644 index 0000000..7fb49c1 --- /dev/null +++ b/docker/stacks/local/env-pda @@ -0,0 +1,18 @@ +BIND_ADDRESS=0.0.0.0 +PDNS_API_KEY=scjfNRN4Ch2DBuhk24vr +PDNS_VERSION=4.5.2 +PDNS_PROTO=http +PDNS_HOST=pdns +PDNS_PORT=8081 +PDNS_WEBSERVER_ALLOW_FROM=0.0.0.0/0 +SQLA_DB_HOST=mysql +SQLA_DB_NAME=pda +SQLA_DB_USER=root +SQLA_DB_PASSWORD=qHZDZ26fPqqCfKZtWkQ9 +SQLA_DB_PORT=3306 +ADMIN_USER=nocadmin +ADMIN_USER_PASSWORD=clink henchmen mete coracle uphold +FLASK_ENV=development +FLASK_CONF=/app/configs/development.py +FLASK_APP=/app/run.py +FLASK_DEBUG=true \ No newline at end of file diff --git a/docker/stacks/local/env-pdns b/docker/stacks/local/env-pdns new file mode 100644 index 0000000..a8364f4 --- /dev/null +++ b/docker/stacks/local/env-pdns @@ -0,0 +1,20 @@ + AS_MYSQL_CHECK_RETRY=10 + AS_MYSQL_CHECK_INTERVAL=4 + PDNS_launch=gmysql + PDNS_gmysql_host=mysql + PDNS_gmysql_port=3306 + PDNS_gmysql_user=root + PDNS_gmysql_password=qHZDZ26fPqqCfKZtWkQ9 + PDNS_gmysql_dbname=pda + PDNS_gmysql_dnssec=yes + PDNS_default_soa_content=pdns noc.@ 0 10800 3600 604800 3600 + PDNS_default_ttl=3600 + PDNS_local_address=0.0.0.0 + PDNS_local_port=53 + PDNS_api=yes + PDNS_api_key=scjfNRN4Ch2DBuhk24vr + PDNS_webserver=yes + PDNS_webserver_address=0.0.0.0 + PDNS_webserver_allow_from=0.0.0.0/0 + PDNS_any_to_tcp=yes + PDNS_master=no \ No newline at end of file diff --git a/powerdnsadmin/default_config.py b/powerdnsadmin/default_config.py index 16b8161..361c6d0 100644 --- a/powerdnsadmin/default_config.py +++ b/powerdnsadmin/default_config.py @@ -13,8 +13,8 @@ FILESYSTEM_SESSIONS_ENABLED = False ### DATABASE CONFIG SQLA_DB_USER = 'pda' -SQLA_DB_PASSWORD = 'changeme' -SQLA_DB_HOST = '127.0.0.1' +SQLA_DB_PASSWORD = 'qHZDZ26fPqqCfKZtWkQ9' +SQLA_DB_HOST = 'mysql' SQLA_DB_NAME = 'pda' SQLALCHEMY_TRACK_MODIFICATIONS = True diff --git a/requirements.txt b/requirements.txt index 5e97a12..142bf15 100644 --- a/requirements.txt +++ b/requirements.txt @@ -28,3 +28,4 @@ PyYAML==5.4 Flask-SSLify==0.1.5 Flask-Mail==0.9.1 flask-session==0.3.2 +xmlsec==1.3.12 \ No newline at end of file diff --git a/run.py b/run.py index 5512724..7118ccf 100755 --- a/run.py +++ b/run.py @@ -3,4 +3,4 @@ from powerdnsadmin import create_app if __name__ == '__main__': app = create_app() - app.run(debug = True, host=app.config.get('BIND_ADDRESS', '127.0.0.1'), port=app.config.get('PORT', '9191')) + app.run(debug=app.config.get('DEBUG_MODE', False), host=app.config.get('BIND_ADDRESS', '127.0.0.1'), port=app.config.get('PORT', '80')) From cd16a39ec0a4019a21ed93380302a8d335f609f8 Mon Sep 17 00:00:00 2001 From: Matt Scott Date: Mon, 6 Dec 2021 22:44:11 -0500 Subject: [PATCH 2/5] Working on RC1 of the Docker overhaul. Current progress is a working local dev environment with auto-reload. --- bin/build.sh | 58 ++++++ configs/development.py | 2 +- docker/alpine/Dockerfile | 131 +++++------- docker/alpine/docker-compose.yml | 18 -- docker/alpine/entrypoint.sh | 17 -- docker/debian/Dockerfile | 46 +++-- docker/debian/entrypoint.sh | 19 -- docker/debian/pdns-check.sh | 31 --- docker/repo.cfg | 1 + docker/shared/entrypoint.sh | 186 ++++++++++++++++++ .../pdns-schema-mysql.sql} | 0 docker/shared/pdns-schema-sqlite.sql | 92 +++++++++ docker/stacks/local/docker-compose.yml | 47 ----- docker/stacks/local/env-pda | 18 -- docker/stacks/local/env-pdns | 20 -- docker/stacks/local/mysql/docker-compose.yml | 78 ++++++++ run.py | 6 +- 17 files changed, 506 insertions(+), 264 deletions(-) create mode 100755 bin/build.sh delete mode 100644 docker/alpine/docker-compose.yml delete mode 100755 docker/alpine/entrypoint.sh delete mode 100755 docker/debian/entrypoint.sh delete mode 100644 docker/debian/pdns-check.sh create mode 100644 docker/repo.cfg create mode 100755 docker/shared/entrypoint.sh rename docker/{debian/pdns-schema.sql => shared/pdns-schema-mysql.sql} (100%) create mode 100644 docker/shared/pdns-schema-sqlite.sql delete mode 100644 docker/stacks/local/docker-compose.yml delete mode 100644 docker/stacks/local/env-pda delete mode 100644 docker/stacks/local/env-pdns create mode 100644 docker/stacks/local/mysql/docker-compose.yml diff --git a/bin/build.sh b/bin/build.sh new file mode 100755 index 0000000..6d34553 --- /dev/null +++ b/bin/build.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env sh + +script_path=$(readlink -f "$0") +bin_path=$(dirname "$script_path") +app_path=$(dirname "$bin_path") +app_version=0.2.4 +docker_path=$app_path/docker +docker_repo_config_path=$docker_path/repo.cfg +docker_repo= +docker_distro=alpine +docker_distro_tag=3.14 +docker_tag=$app_version-$docker_distro-$docker_distro_tag-mysql + +if [ ! -z "$1" ]; +then + docker_tag=$1 +fi + +if [ ! -z "$2" ]; +then + app_version=$2 +fi + +if [ ! -z "$3" ]; +then + docker_distro=$3 +fi + +if [ ! -z "$4" ]; +then + docker_distro_tag=$4 +fi + +if [ -f "$docker_repo_config_path" ]; +then + docker_repo=`cat $docker_repo_config_path` + + if [ -f "$docker_path/$docker_distro/Dockerfile-$docker_tag" ]; + then + docker_file=$docker_path/$docker_distro/Dockerfile-$docker_tag + elif [ -f "$docker_path/$docker_distro/Dockerfile-$docker_distro-$docker_distro_tag" ]; + then + docker_file=$docker_path/$docker_distro/Dockerfile-$docker_distro-$docker_distro_tag + elif [ -f "$docker_path/$docker_distro/Dockerfile-$docker_distro" ]; + then + docker_file=$docker_path/$docker_distro/Dockerfile-$docker_distro + else + docker_file=$docker_path/$docker_distro/Dockerfile + fi + + docker build --force-rm -f $docker_file -t $docker_repo:$docker_tag --build-arg DISTRO=$docker_distro --build-arg DISTRO_TAG=$docker_distro_tag $app_path + +else + echo "Could not load Docker registry path from $docker_repo_config_path" + exit 1 +fi + +exit \ No newline at end of file diff --git a/configs/development.py b/configs/development.py index f2a5199..1f37f46 100644 --- a/configs/development.py +++ b/configs/development.py @@ -27,7 +27,7 @@ SQLALCHEMY_DATABASE_URI = 'mysql://{}:{}@{}/{}'.format( ) ### DATABASE - SQLite -#SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'pdns.db') +SQLALCHEMY_DATABASE_URI = 'sqlite:////srv/app/pdns.db' ### SMTP config # MAIL_SERVER = 'localhost' diff --git a/docker/alpine/Dockerfile b/docker/alpine/Dockerfile index 5eb5496..d802e49 100644 --- a/docker/alpine/Dockerfile +++ b/docker/alpine/Dockerfile @@ -1,95 +1,68 @@ -FROM alpine:3.13 AS builder -LABEL maintainer="k@ndk.name" +ARG DISTRO=alpine +ARG DISTRO_TAG=3.14 -ARG BUILD_DEPENDENCIES="build-base \ - libffi-dev \ - libxml2-dev \ - mariadb-connector-c-dev \ - openldap-dev \ - python3-dev \ - xmlsec-dev \ - yarn \ - cargo" +FROM ${DISTRO}:${DISTRO_TAG} +LABEL maintainer="matt@azorian.solutions" +# Set additional container environment variables ENV LC_ALL=en_US.UTF-8 \ LANG=en_US.UTF-8 \ LANGUAGE=en_US.UTF-8 \ - FLASK_APP=/build/powerdnsadmin/__init__.py + PDA_PORT=${PDA_PORT:-80} \ + PDA_UID=${PDA_UID:-1000} \ + PDA_USER=${PDA_USER:-pda} \ + PDA_GID=${PDA_GID:-1000} \ + PDA_GROUP=${PDA_GROUP:-pda} \ + FLASK_APP=${FLASK_APP:-/srv/app/run.py} -# Get dependencies -# py3-pip should not belong to BUILD_DEPENDENCIES. Otherwise, when we remove -# them with "apk del" at the end of build stage, the python requests module -# will be removed as well - (Tested with alpine:3.12 and python 3.8.5). -RUN apk add --no-cache ${BUILD_DEPENDENCIES} && \ - apk add --no-cache py3-pip +# Perform the following tasks: +# - Install required APK packages per the required dependencies +# - Upgrade Python pip package using pip3 +RUN apk update \ + && apk add --no-cache curl build-base tzdata libcap libffi-dev libxml2-dev xmlsec-dev mariadb-connector-c-dev \ + openldap-dev python3-dev py3-pip py3-gunicorn py3-psycopg2 yarn cargo mysql-client postgresql-client sqlite \ + && pip install --upgrade pip -WORKDIR /build +# Copy Python pip requirements file for early installation which leverages the image caching mechanisms +COPY /requirements.txt /srv/app/requirements.txt -# We copy just the requirements.txt first to leverage Docker cache -COPY ./requirements.txt /build/requirements.txt +# Change the working directory which may affect the build contexts of following commands +WORKDIR /srv/app -# Get application dependencies -RUN pip install --upgrade pip && \ - pip install -r requirements.txt +# Install the required Python pip packages as defined in the ./requirements.txt file +RUN pip install -r requirements.txt -# Add sources -COPY .. /build +# Copy all project files into the container's /app directory +# FIXME: This is too broad and will almost guarentee constant image re-builds that can be skipped in dev environment +COPY . /srv/app -# Prepare assets -RUN yarn install --pure-lockfile --production && \ - yarn cache clean && \ - sed -i -r -e "s|'cssmin',\s?'cssrewrite'|'cssmin'|g" /build/powerdnsadmin/assets.py && \ - flask assets build +# Perform the following tasks: +# - Create system group and user as configured in the PDA_GROUP and PDA_USER environment variables +# - Copy additional automation scripts to their appropriate locations +# - Set permissions on automation scripts +# - Install Yarn and clean the Yarn cache +# - Build Flask assets +# - Apply netcaps and cleanup APK packages +RUN addgroup -S --gid $PDA_GID $PDA_GROUP \ + && adduser -S -D --uid $PDA_UID -G $PDA_GROUP $PDA_USER \ + # XXX: Is this setcap still needed? + && setcap cap_net_bind_service=+ep $(readlink -f /usr/bin/python3) \ + && apk del libcap \ + && yarn install --pure-lockfile --production \ + && yarn cache clean \ + && flask assets build -RUN mv /build/powerdnsadmin/static /tmp/static && \ - mkdir /build/powerdnsadmin/static && \ - cp -r /tmp/static/generated /build/powerdnsadmin/static && \ - cp -r /tmp/static/assets /build/powerdnsadmin/static && \ - cp -r /tmp/static/img /build/powerdnsadmin/static && \ - find /tmp/static/node_modules -name 'fonts' -exec cp -r {} /build/powerdnsadmin/static \; && \ - find /tmp/static/node_modules/icheck/skins/square -name '*.png' -exec cp {} /build/powerdnsadmin/static/generated \; +# Set the system user and group for the primary container process +USER "${PDA_USER}:${PDA_GROUP}" -RUN { \ - echo "from flask_assets import Environment"; \ - echo "assets = Environment()"; \ - echo "assets.register('js_login', 'generated/login.js')"; \ - echo "assets.register('js_validation', 'generated/validation.js')"; \ - echo "assets.register('css_login', 'generated/login.css')"; \ - echo "assets.register('js_main', 'generated/main.js')"; \ - echo "assets.register('css_main', 'generated/main.css')"; \ - } > /build/powerdnsadmin/assets.py +# Define the app directory as a volume +VOLUME /srv/app -# Move application -RUN mkdir -p /app && \ - cp -r /build/migrations/ /build/powerdnsadmin/ /build/run.py /app && \ - mkdir -p /app/configs && \ - cp -r /build/configs/docker_config.py /app/configs +# Expose the configured service port +EXPOSE 80/TCP -# Build image -FROM alpine:3.13 +# Set the entrypoint script of the container +ENTRYPOINT ["sh", "/srv/app/docker/shared/entrypoint.sh"] -ENV FLASK_APP=/app/powerdnsadmin/__init__.py \ - USER=pda - -RUN apk add --no-cache mariadb-connector-c postgresql-client py3-gunicorn py3-psycopg2 xmlsec tzdata libcap && \ - addgroup -S ${USER} && \ - adduser -S -D -G ${USER} ${USER} && \ - mkdir /data && \ - chown ${USER}:${USER} /data && \ - setcap cap_net_bind_service=+ep $(readlink -f /usr/bin/python3) && \ - apk del libcap - -COPY --from=builder /usr/bin/flask /usr/bin/ -COPY --from=builder /usr/lib/python3.8/site-packages /usr/lib/python3.8/site-packages/ -COPY --from=builder --chown=root:${USER} /app /app/ -COPY ./docker/entrypoint.sh /usr/bin/ - -WORKDIR /app -RUN chown ${USER}:${USER} ./configs /app && \ - cat ./powerdnsadmin/default_config.py ./configs/docker_config.py > ./powerdnsadmin/docker_config.py - -EXPOSE 80/tcp -USER ${USER} -HEALTHCHECK CMD ["wget","--output-document=-","--quiet","--tries=1","http://127.0.0.1/"] -ENTRYPOINT ["entrypoint.sh"] -CMD ["gunicorn","powerdnsadmin:create_app()"] +# Set the default command of the container to be ran following the entrypoint script +CMD ["sh", "-c", "/srv/app/run.py"] diff --git a/docker/alpine/docker-compose.yml b/docker/alpine/docker-compose.yml deleted file mode 100644 index 8e55cd5..0000000 --- a/docker/alpine/docker-compose.yml +++ /dev/null @@ -1,18 +0,0 @@ -version: "3.3" - -services: - app: - image: ngoduykhanh/powerdns-admin:latest - container_name: powerdns-admin - ports: - - "9191:80" - logging: - driver: json-file - options: - max-size: 50m - environment: - - SQLALCHEMY_DATABASE_URI=mysql://pda:changeme@host.docker.internal/pda - - GUNICORN_TIMEOUT=60 - - GUNICORN_WORKERS=2 - - GUNICORN_LOGLEVEL=DEBUG - - OFFLINE_MODE=False # True for offline, False for external resources diff --git a/docker/alpine/entrypoint.sh b/docker/alpine/entrypoint.sh deleted file mode 100755 index b1d0c24..0000000 --- a/docker/alpine/entrypoint.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/sh -set -euo pipefail -cd /app - -GUNICORN_TIMEOUT="${GUNICORN_TIMEOUT:-120}" -GUNICORN_WORKERS="${GUNICORN_WORKERS:-4}" -GUNICORN_LOGLEVEL="${GUNICORN_LOGLEVEL:-info}" -BIND_ADDRESS="${BIND_ADDRESS:-0.0.0.0:80}" - -GUNICORN_ARGS="-t ${GUNICORN_TIMEOUT} --workers ${GUNICORN_WORKERS} --bind ${BIND_ADDRESS} --log-level ${GUNICORN_LOGLEVEL}" -if [ "$1" == gunicorn ]; then - /bin/sh -c "flask db upgrade" - exec "$@" $GUNICORN_ARGS - -else - exec "$@" -fi diff --git a/docker/debian/Dockerfile b/docker/debian/Dockerfile index 9ef611f..afbb196 100644 --- a/docker/debian/Dockerfile +++ b/docker/debian/Dockerfile @@ -1,11 +1,19 @@ -FROM debian:11.1-slim +ARG DISTRO=debian +ARG DISTRO_TAG=11.1-slim + +FROM ${DISTRO}:${DISTRO_TAG} LABEL maintainer="matt@azorian.solutions" # Set additional container environment variables ENV LC_ALL=en_US.UTF-8 \ LANG=en_US.UTF-8 \ LANGUAGE=en_US.UTF-8 \ - FLASK_APP=${FLASK_APP:-/app/run.py} + PDA_PORT=${PDA_PORT:-80} \ + PDA_UID=${PDA_UID:-1000} \ + PDA_USER=${PDA_USER:-pda} \ + PDA_GID=${PDA_GID:-1000} \ + PDA_GROUP=${PDA_GROUP:-pda} \ + FLASK_APP=${FLASK_APP:-/srv/app/run.py} # Perform the following tasks: # - Install required APT packages per the required dependencies @@ -19,7 +27,7 @@ ENV LC_ALL=en_US.UTF-8 \ RUN apt update -y \ && apt install -y --no-install-recommends apt-transport-https locales locales-all python3-pip python3-setuptools \ python3-dev curl libsasl2-dev libldap2-dev libssl-dev libxml2-dev libxmlsec1 libxslt1-dev libxmlsec1-dev xmlsec1 \ - libffi-dev build-essential libmariadb-dev-compat pkg-config \ + libffi-dev build-essential libmariadb-dev-compat pkg-config mysql-client postgresql-client sqlite3 \ && curl -sL https://deb.nodesource.com/setup_14.x | bash - \ && curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \ && echo "deb https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list \ @@ -29,30 +37,42 @@ RUN apt update -y \ && rm -rf /var/lib/apt/lists/* \ && pip3 install --upgrade pip -# We copy just the requirements.txt first to leverage Docker cache -COPY /requirements.txt /app/requirements.txt +# Copy Python pip requirements file for early installation which leverages the image caching mechanisms +COPY /requirements.txt /srv/app/requirements.txt # Change the working directory which may affect the build contexts of following commands -WORKDIR /app +WORKDIR /srv/app # Install the required Python pip packages as defined in the ./requirements.txt file RUN pip3 install -r requirements.txt -# Copy all project files into the container's /app directory +# Copy all project files into the container's /srv/app directory # FIXME: This is too broad and will almost guarentee constant image re-builds that can be skipped in dev environment -COPY . /app +COPY . /srv/app # Perform the following tasks: # - Copy additional automation scripts to their appropriate locations # - Set permissions on automation scripts # - Install Yarn and clean the Yarn cache # - Build Flask assets -RUN cp /app/docker/debian/entrypoint.sh /usr/local/bin/entrypoint.sh \ - && cp /app/docker/debian/pdns-check.sh /srv/ \ - && chmod +x /usr/local/bin/entrypoint.sh /srv/pdns-check.sh \ +RUN addgroup --system --gid $PDA_GID $PDA_GROUP \ + && adduser --system --disabled-login --no-create-home --home /tmp --shell /bin/false --uid $PDA_UID \ + --ingroup $PDA_GROUP $PDA_USER 2>/dev/null \ && yarn install --pure-lockfile --production \ && yarn cache clean \ && flask assets build -# Set the default command of the container to the PDNS check script to prevent premature execution 乁༼ ☯‿☯༽ㄏ -CMD ["/srv/pdns-check.sh", "/usr/local/bin/entrypoint.sh", "/app/run.py"] +# Set the system user and group for the primary container process +USER "${PDA_USER}:${PDA_GROUP}" + +# Define the app directory as a volume +VOLUME /srv/app + +# Expose the configured service port +EXPOSE 80/TCP + +# Set the entrypoint script of the container +ENTRYPOINT ["sh", "/srv/app/docker/shared/entrypoint.sh"] + +# Set the default command of the container to be ran following the entrypoint script +CMD ["sh", "-c", "/srv/app/run.py"] diff --git a/docker/debian/entrypoint.sh b/docker/debian/entrypoint.sh deleted file mode 100755 index 98d647b..0000000 --- a/docker/debian/entrypoint.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash - -set -eo pipefail - -GUNICORN_TIMEOUT="${GUNICORN_TIMEOUT:-120}" -GUNICORN_WORKERS="${GUNICORN_WORKERS:-4}" -GUNICORN_LOGLEVEL="${GUNICORN_LOGLEVEL:-info}" -BIND_ADDRESS="${BIND_ADDRESS:-0.0.0.0:80}" -GUNICORN_ARGS="-t ${GUNICORN_TIMEOUT} --workers ${GUNICORN_WORKERS} --log-level ${GUNICORN_LOGLEVEL} --bind ${BIND_ADDRESS}" - -cd /app - -/bin/sh -c "flask db upgrade" - -if [ "$1" = "gunicorn" ]; then - exec "$@" $GUNICORN_ARGS -else - exec "$@" -fi diff --git a/docker/debian/pdns-check.sh b/docker/debian/pdns-check.sh deleted file mode 100644 index e557a74..0000000 --- a/docker/debian/pdns-check.sh +++ /dev/null @@ -1,31 +0,0 @@ -#!/bin/sh -set -e - -# How many seconds to wait after a connection attempt failure before trying again -FAIL_DELAY=3 - -# How many seconds to wait after a successful connection attempt before proceeding to the next step -SUCCESS_DELAY=3 - -# How many connection attempts should be made before halting execution -MAX_ATTEMPTS=10 - -API_URI="${PDNS_PROTO}://${PDNS_HOST}:${PDNS_PORT}/api/v1/servers" -API_AUTH_HEADER="X-API-Key: ${PDNS_API_KEY}" -CMD="$1" -shift -CMD_ARGS="$@" - -until curl -H "${API_AUTH_HEADER}" "${API_URI}"; do - >&2 echo "\nPowerDNS Authoritative server API not online yet. Waiting for ${FAIL_DELAY} seconds..." - sleep $FAIL_DELAY - if [ $MAX_ATTEMPTS -eq 10 ] - then - break - fi -done - -sleep $SUCCESS_DELAY - ->&2 echo "PowerDNS Authoritative server API is online. Proceeding with next script execution..." -exec $CMD $CMD_ARGS diff --git a/docker/repo.cfg b/docker/repo.cfg new file mode 100644 index 0000000..a3b049e --- /dev/null +++ b/docker/repo.cfg @@ -0,0 +1 @@ +azoriansolutions/powerdns-admin \ No newline at end of file diff --git a/docker/shared/entrypoint.sh b/docker/shared/entrypoint.sh new file mode 100755 index 0000000..04252c0 --- /dev/null +++ b/docker/shared/entrypoint.sh @@ -0,0 +1,186 @@ +#!/bin/sh +set -e + +script_path=$(readlink -f "$0") +app_path=$(dirname $(dirname $(dirname "$script_path"))) +docker_path=$app_path/docker +first_run_path=$docker_path/.firstrun + +######################################################################################################################## +# START CONFIGURATION +######################################################################################################################## + +# How many seconds to wait after a connection attempt failure before trying again +PDA_CHECK_MYSQL_FAIL_DELAY=${PDA_CHECK_MYSQL_FAIL_DELAY:-2} + +# How many seconds to wait after a successful connection attempt before proceeding to the next step +PDA_CHECK_MYSQL_SUCCESS_DELAY=${PDA_CHECK_MYSQL_SUCCESS_DELAY:-0} + +# How many MySQL connection attempts should be made before halting container execution +PDA_CHECK_MYSQL_ATTEMPTS=${PDA_CHECK_MYSQL_ATTEMPTS:-30} + +# How many seconds to wait after a connection attempt failure before trying again +PDA_CHECK_API_FAIL_DELAY=${PDA_CHECK_API_FAIL_DELAY:-2} + +# How many seconds to wait after a successful connection attempt before proceeding to the next step +PDA_CHECK_API_SUCCESS_DELAY=${PDA_CHECK_API_SUCCESS_DELAY:-0} + +# How many API connection attempts should be made before halting container execution +PDA_CHECK_API_ATTEMPTS=${PDA_CHECK_API_ATTEMPTS:-15} + +PDA_GUNICORN_BIND_ADDRESS="${PDA_GUNICORN_BIND_ADDRESS:-0.0.0.0:80}" + +PDA_GUNICORN_TIMEOUT="${PDA_GUNICORN_TIMEOUT:-120}" + +PDA_GUNICORN_WORKERS="${PDA_GUNICORN_WORKERS:-4}" + +PDA_GUNICORN_LOGLEVEL="${PDA_GUNICORN_LOGLEVEL:-info}" + +######################################################################################################################## +# END CONFIGURATION +######################################################################################################################## + +API_URI="${PDA_PDNS_PROTO:-http}://${PDA_PDNS_HOST:-127.0.0.1}:${PDA_PDNS_PORT:-8081}/api/v1/servers" +API_AUTH_HEADER="X-API-Key: ${PDA_PDNS_API_KEY}" +GUNICORN_ARGS="-t ${PDA_GUNICORN_TIMEOUT} --workers ${PDA_GUNICORN_WORKERS} --log-level ${PDA_GUNICORN_LOGLEVEL} \ +--bind ${PDA_GUNICORN_BIND_ADDRESS}" + +convert_file_vars() { + for line in $(env); do + if [[ $line == PDA_* ]] || [[ $line == AS_* ]]; then + if [[ $line =~ ^.*_FILE ]]; then + local INDEX=$(echo $line | grep -aob '=' | grep -oE '[0-9]+') + local LEN=$(echo $line | wc -c) + local NAME_END_INDEX=$(($INDEX - 5)) + local NAME_FULL=$(echo $line | cut -c1-$INDEX) + local NAME=$(echo $line | cut -c1-$NAME_END_INDEX) + INDEX=$(($INDEX + 2)) + local VALUE=$(echo $line | cut -c$INDEX-$LEN) + local FILE_VALUE=$(cat $VALUE) + export $NAME=$FILE_VALUE + unset $NAME_FULL + fi + fi + done +} + +verify_mysql_ready() { + local host=$1 + local port=$2 + local retry_executed=1 + + while ! nc -z $host $port >& /dev/null; do + # The last connection test to the MySQL server failed at this point + + # If the remaining retry counter falls to zero, exit the connection test cycle + if [ $retry_executed -ge $PDA_CHECK_MYSQL_ATTEMPTS ]; then + echo "The maximum number ($PDA_CHECK_MYSQL_ATTEMPTS) of TCP connection tests have been executed without success. This container will now exit." + exit 1 + else + echo "MySQL server is offline. The TCP connection test cycle number $retry_executed has failed to $host:$port. Waiting for $PDA_CHECK_MYSQL_FAIL_DELAY seconds..." + fi + + # Delay execution for the configured MySQL fail delay + sleep $PDA_CHECK_MYSQL_FAIL_DELAY + + # Increment the retry execution counter + retry_executed=$((retry_executed + 1)) + done + + echo "MySQL server is online after $retry_executed check(s). Delaying execution for $PDA_CHECK_MYSQL_SUCCESS_DELAY seconds..." + + sleep $PDA_CHECK_MYSQL_SUCCESS_DELAY +} + +verify_api_ready() { + local retry_executed=1 + + while [ $(curl -s -o /dev/null -w "%{http_code}" -H "$2" $1) -ne "200" ]; do + # The last connection test to the API server failed at this point + + # If the remaining retry counter falls to zero, exit the connection test cycle + if [ $retry_executed -ge $PDA_CHECK_API_ATTEMPTS ]; then + echo "The maximum number ($PDA_CHECK_API_ATTEMPTS) of API server HTTP connection tests have been executed without success. This container will now exit." + exit 1 + else + echo "PowerDNS API server is offline. The HTTP connection test cycle number $retry_executed has failed to $1. Waiting for $PDA_CHECK_API_FAIL_DELAY seconds..." + fi + + # Delay execution for the configured API fail delay + sleep $PDA_CHECK_API_FAIL_DELAY + + # Increment the retry execution counter + retry_executed=$((retry_executed + 1)) + done + + echo "PowerDNS API server is online after $retry_executed check(s). Delaying execution for ${PDA_CHECK_API_SUCCESS_DELAY} seconds..." + + sleep $PDA_CHECK_API_SUCCESS_DELAY +} + +first_run() { + # Determine if the first run file exists and abort if so + if [ -f "$first_run_path" ]; then + echo "First run actions aborting as a first run file exists at $first_run_path" + return + fi + + # Assume MySQL database if the SQLA_DB_HOST environment variable has been set, otherwise assume SQLite + if [ -n "$SQLA_DB_HOST" ]; then + table=tsigkeys + sql_exists=$(printf 'SHOW TABLES LIKE "%s"' "$table") + if [[ -z $(mysql -h $SQLA_DB_HOST -u $SQLA_DB_USER -p$SQLA_DB_PASSWORD -e "$sql_exists" $SQLA_DB_NAME) ]]; then + echo "The table $table does not exist so the base pdns schema will be installed." + mysql_result=$(mysql -h $SQLA_DB_HOST -u $SQLA_DB_USER -p$SQLA_DB_PASSWORD $SQLA_DB_NAME < /srv/app/docker/shared/pdns-schema-mysql.sql) + else + echo "The table $table does exist so no further action will be taken." + fi + fi + + if [ ! -f "/srv/app/pdns.db" ]; then + cat /srv/app/docker/shared/pdns-schema-sqlite.sql | sqlite3 /srv/app/pdns.db + fi + + touch $first_run_path; +} + +# If the command starts with an option, prepend the appropriate command name +if [ "${1:0:1}" = '-' ]; then + set -- "gunicorn" "$@" +fi + +# Automatically convert any environment variables that are prefixed with "PDA_" or "AS_" and suffixed with "_FILE" +convert_file_vars + +# Verify that the configured MySQL server is ready for connections +if [ -n "$SQLA_DB_HOST" ]; then + verify_mysql_ready "${SQLA_DB_HOST}" "${SQLA_DB_PORT:-3306}" +fi + +# Execute first run tasks +# - Setup the base PowerDNS Authoritative server database schema if this is the first run +first_run + +# Verify that the configured PowerDNS name server API is ready for connections +if [ -n "$PDA_PDNS_HOST" ]; then + verify_api_ready "${API_URI}" "${API_AUTH_HEADER}" +fi + +echo "Proceeding with app initialization..." +cd /srv/app + +# Execute any pending database upgrades via Flask +flask db upgrade + +if [ $FLASK_ENV = "development" ]; then + yarn install --pure-lockfile --${FLASK_ENV} + yarn cache clean + flask assets build +fi + +# Determine if the gunicorn command is being used and append program arguments if so. Otherwise, execute normally +if [ "$1" = "gunicorn" ]; then + exec "$@" $GUNICORN_ARGS +else + exec "$@" +fi diff --git a/docker/debian/pdns-schema.sql b/docker/shared/pdns-schema-mysql.sql similarity index 100% rename from docker/debian/pdns-schema.sql rename to docker/shared/pdns-schema-mysql.sql diff --git a/docker/shared/pdns-schema-sqlite.sql b/docker/shared/pdns-schema-sqlite.sql new file mode 100644 index 0000000..c9e12a1 --- /dev/null +++ b/docker/shared/pdns-schema-sqlite.sql @@ -0,0 +1,92 @@ +PRAGMA foreign_keys = 1; + +CREATE TABLE domains ( + id INTEGER PRIMARY KEY, + name VARCHAR(255) NOT NULL COLLATE NOCASE, + master VARCHAR(128) DEFAULT NULL, + last_check INTEGER DEFAULT NULL, + type VARCHAR(6) NOT NULL, + notified_serial INTEGER DEFAULT NULL, + account VARCHAR(40) DEFAULT NULL +); + +CREATE UNIQUE INDEX name_index ON domains(name); + + +CREATE TABLE records ( + id INTEGER PRIMARY KEY, + domain_id INTEGER DEFAULT NULL, + name VARCHAR(255) DEFAULT NULL, + type VARCHAR(10) DEFAULT NULL, + content VARCHAR(65535) DEFAULT NULL, + ttl INTEGER DEFAULT NULL, + prio INTEGER DEFAULT NULL, + change_date INTEGER DEFAULT NULL, + disabled BOOLEAN DEFAULT 0, + ordername VARCHAR(255), + auth BOOL DEFAULT 1, + FOREIGN KEY(domain_id) REFERENCES domains(id) ON DELETE CASCADE ON UPDATE CASCADE +); + +CREATE INDEX rec_name_index ON records(name); +CREATE INDEX nametype_index ON records(name,type); +CREATE INDEX domain_id ON records(domain_id); +CREATE INDEX orderindex ON records(ordername); + + +CREATE TABLE supermasters ( + ip VARCHAR(64) NOT NULL, + nameserver VARCHAR(255) NOT NULL COLLATE NOCASE, + account VARCHAR(40) NOT NULL +); + +CREATE UNIQUE INDEX ip_nameserver_pk ON supermasters(ip, nameserver); + + +CREATE TABLE comments ( + id INTEGER PRIMARY KEY, + domain_id INTEGER NOT NULL, + name VARCHAR(255) NOT NULL, + type VARCHAR(10) NOT NULL, + modified_at INT NOT NULL, + account VARCHAR(40) DEFAULT NULL, + comment VARCHAR(65535) NOT NULL, + FOREIGN KEY(domain_id) REFERENCES domains(id) ON DELETE CASCADE ON UPDATE CASCADE +); + +CREATE INDEX comments_domain_id_index ON comments (domain_id); +CREATE INDEX comments_nametype_index ON comments (name, type); +CREATE INDEX comments_order_idx ON comments (domain_id, modified_at); + + +CREATE TABLE domainmetadata ( + id INTEGER PRIMARY KEY, + domain_id INT NOT NULL, + kind VARCHAR(32) COLLATE NOCASE, + content TEXT, + FOREIGN KEY(domain_id) REFERENCES domains(id) ON DELETE CASCADE ON UPDATE CASCADE +); + +CREATE INDEX domainmetaidindex ON domainmetadata(domain_id); + + +CREATE TABLE cryptokeys ( + id INTEGER PRIMARY KEY, + domain_id INT NOT NULL, + flags INT NOT NULL, + active BOOL, + content TEXT, + FOREIGN KEY(domain_id) REFERENCES domains(id) ON DELETE CASCADE ON UPDATE CASCADE +); + +CREATE INDEX domainidindex ON cryptokeys(domain_id); + + +CREATE TABLE tsigkeys ( + id INTEGER PRIMARY KEY, + name VARCHAR(255) COLLATE NOCASE, + algorithm VARCHAR(50) COLLATE NOCASE, + secret VARCHAR(255) +); + +CREATE UNIQUE INDEX namealgoindex ON tsigkeys(name, algorithm); \ No newline at end of file diff --git a/docker/stacks/local/docker-compose.yml b/docker/stacks/local/docker-compose.yml deleted file mode 100644 index 991c5c9..0000000 --- a/docker/stacks/local/docker-compose.yml +++ /dev/null @@ -1,47 +0,0 @@ -version: "3.3" - -services: - - mysql: - image: mariadb:10.6 - restart: unless-stopped - environment: - MYSQL_ROOT_PASSWORD: qHZDZ26fPqqCfKZtWkQ9 - MYSQL_DATABASE: pda - MYSQL_USER: pda - MYSQL_PASSWORD: qHZDZ26fPqqCfKZtWkQ9 - - phpmyadmin: - image: phpmyadmin/phpmyadmin:5.1 - depends_on: ['mysql'] - restart: unless-stopped - environment: - - PMA_HOST=mysql - - PMA_PORT=3306 - #- PMA_ARBITRARY=1 - ports: - - 8082:80 - - pdns: - image: azoriansolutions/powerdns-nameserver:4.5.2-alpine-3.14-mysql - depends_on: ['mysql'] - restart: unless-stopped - env_file: ['./env-pdns'] - ports: - - "5053:53/tcp" - - "5053:53/udp" - - "8081:8081/tcp" - - powerdns-admin: - build: - context: ../../../ - dockerfile: ./docker/debian/Dockerfile - image: powerdns-admin-test - depends_on: ['pdns'] - restart: unless-stopped - container_name: powerdns-admin-test - env_file: ['./env-pda'] - ports: - - "8080:80" - volumes: - - ../../../powerdnsadmin:/app/powerdnsadmin diff --git a/docker/stacks/local/env-pda b/docker/stacks/local/env-pda deleted file mode 100644 index 7fb49c1..0000000 --- a/docker/stacks/local/env-pda +++ /dev/null @@ -1,18 +0,0 @@ -BIND_ADDRESS=0.0.0.0 -PDNS_API_KEY=scjfNRN4Ch2DBuhk24vr -PDNS_VERSION=4.5.2 -PDNS_PROTO=http -PDNS_HOST=pdns -PDNS_PORT=8081 -PDNS_WEBSERVER_ALLOW_FROM=0.0.0.0/0 -SQLA_DB_HOST=mysql -SQLA_DB_NAME=pda -SQLA_DB_USER=root -SQLA_DB_PASSWORD=qHZDZ26fPqqCfKZtWkQ9 -SQLA_DB_PORT=3306 -ADMIN_USER=nocadmin -ADMIN_USER_PASSWORD=clink henchmen mete coracle uphold -FLASK_ENV=development -FLASK_CONF=/app/configs/development.py -FLASK_APP=/app/run.py -FLASK_DEBUG=true \ No newline at end of file diff --git a/docker/stacks/local/env-pdns b/docker/stacks/local/env-pdns deleted file mode 100644 index a8364f4..0000000 --- a/docker/stacks/local/env-pdns +++ /dev/null @@ -1,20 +0,0 @@ - AS_MYSQL_CHECK_RETRY=10 - AS_MYSQL_CHECK_INTERVAL=4 - PDNS_launch=gmysql - PDNS_gmysql_host=mysql - PDNS_gmysql_port=3306 - PDNS_gmysql_user=root - PDNS_gmysql_password=qHZDZ26fPqqCfKZtWkQ9 - PDNS_gmysql_dbname=pda - PDNS_gmysql_dnssec=yes - PDNS_default_soa_content=pdns noc.@ 0 10800 3600 604800 3600 - PDNS_default_ttl=3600 - PDNS_local_address=0.0.0.0 - PDNS_local_port=53 - PDNS_api=yes - PDNS_api_key=scjfNRN4Ch2DBuhk24vr - PDNS_webserver=yes - PDNS_webserver_address=0.0.0.0 - PDNS_webserver_allow_from=0.0.0.0/0 - PDNS_any_to_tcp=yes - PDNS_master=no \ No newline at end of file diff --git a/docker/stacks/local/mysql/docker-compose.yml b/docker/stacks/local/mysql/docker-compose.yml new file mode 100644 index 0000000..65a5574 --- /dev/null +++ b/docker/stacks/local/mysql/docker-compose.yml @@ -0,0 +1,78 @@ +version: "3.3" + +services: + + mysql: + image: mariadb:10.6 + restart: unless-stopped + environment: + - MYSQL_ROOT_PASSWORD=qHZDZ26fPqqCfKZtWkQ9 + - MYSQL_DATABASE=pda + - MYSQL_USER=pda + - MYSQL_PASSWORD=qHZDZ26fPqqCfKZtWkQ9 + + phpmyadmin: + image: phpmyadmin/phpmyadmin:5.1 + depends_on: ['mysql'] + restart: unless-stopped + environment: + - PMA_HOST=mysql + - PMA_PORT=3306 + ports: + - 8082:80 + + pdns: + image: azoriansolutions/powerdns-nameserver:4.5.2-alpine-3.14-mysql + depends_on: ['powerdns-admin'] + restart: unless-stopped + environment: + - AS_MYSQL_CHECK_RETRY=10 + - AS_MYSQL_CHECK_INTERVAL=4 + - PDNS_launch=gmysql + - PDNS_gmysql_host=mysql + - PDNS_gmysql_port=3306 + - PDNS_gmysql_user=root + - PDNS_gmysql_password=qHZDZ26fPqqCfKZtWkQ9 + - PDNS_gmysql_dbname=pda + - PDNS_gmysql_dnssec=yes + - PDNS_default_soa_content=pdns noc.@ 0 10800 3600 604800 3600 + - PDNS_default_ttl=3600 + - PDNS_local_address=0.0.0.0 + - PDNS_local_port=53 + - PDNS_api=yes + - PDNS_api_key=scjfNRN4Ch2DBuhk24vr + - PDNS_webserver=yes + - PDNS_webserver_address=0.0.0.0 + - PDNS_webserver_allow_from=0.0.0.0/0 + - PDNS_any_to_tcp=yes + - PDNS_master=no + ports: + - "5053:53/tcp" + - "5053:53/udp" + - "8081:8081/tcp" + + powerdns-admin: + image: azoriansolutions/powerdns-admin:alpine + depends_on: ['mysql'] + restart: unless-stopped + environment: + - PDA_BUILD_ON_STARTUP=1 + - PDA_PORT=80 + - PDA_PDNS_API_KEY=scjfNRN4Ch2DBuhk24vr + - PDA_PDNS_VERSION=4.5.2 + - PDA_PDNS_PROTO=http + - PDA_PDNS_HOST=pdns + - PDA_PDNS_PORT=8081 + - SQLA_DB_HOST=mysql + - SQLA_DB_NAME=pda + - SQLA_DB_USER=root + - SQLA_DB_PASSWORD=qHZDZ26fPqqCfKZtWkQ9 + - SQLA_DB_PORT=3306 + - ADMIN_USER=nocadmin + - ADMIN_USER_PASSWORD=clink henchmen mete coracle uphold + - FLASK_ENV=development + - FLASK_CONF=/srv/app/configs/development.py + ports: + - "8080:80" + volumes: + - /home/matt/Projects/azorian-solutions-llc/projects/docker-images/powerdns-admin/AzorianSolutions/PowerDNS-Admin:/srv/app \ No newline at end of file diff --git a/run.py b/run.py index 7118ccf..b9e8e02 100755 --- a/run.py +++ b/run.py @@ -3,4 +3,8 @@ from powerdnsadmin import create_app if __name__ == '__main__': app = create_app() - app.run(debug=app.config.get('DEBUG_MODE', False), host=app.config.get('BIND_ADDRESS', '127.0.0.1'), port=app.config.get('PORT', '80')) + # TODO: Update the following to source the debug mode setting from either the application configuration or the + # environment variable FLASK_DEBUG. I believe support for the environment variable is already built-in to Flask + # so perhaps this just needs to negate the use of the debug flag if the environment variable is present + app.run(debug=app.config.get('DEBUG_MODE', False), host=app.config.get('BIND_ADDRESS', '0.0.0.0'), + port=app.config.get('PORT', '80')) From 84677e7ccf5c536cb3f3c35e9365ebd705989adf Mon Sep 17 00:00:00 2001 From: Matt Scott Date: Wed, 8 Dec 2021 03:52:06 -0500 Subject: [PATCH 3/5] Wrapping up Docker overhaul. This consequently resulted in the configuration system getting some modifications as well. Here is a brief on the changes; - Added environment variable / setting key tracking files at powerdnsadmin/env/*.env that serve for automatic type conversion when loading app settings from environment variables. Also used for manual change tracking during project cleanup. - Updated the app config loading process to make use of a config utility that loads app config settings from environment variables replacing the legacy implementation - Cleaned up the configs directory files and moved the default configuration file from powerdnsadmin/default_config.py to configs/default.py. - Removed all current defaults from the default config file at configs/default.py as a move toward standardizing on Docker deployments for all environments - Started the migration of some legacy app settings / environment variables into the new PDA_ namespace. Added backwards compatibility to specific locations that were changed such as the main run.py file. - Overhauled some of the documentation, I specifically updated primary README.md file and also added docs/docker.md and docs/settings.md - Completely overhauled the Docker implementation to include two production ready images based on Alpine 3.14 and Debian 11.1-slim. - Separated Docker image templating files from deployment files within the docker directory. - Added Docker image building script at docker/bin/build-image and a release building script at docker/bin/create-release - Added shared Docker image asset files to docker/shared directory such as entrypoint script and auto-init database schemas - Docker container has option to automatically initialize chosen database on first run - Docker container has option to wait for MySQL server to be online before starting - Docker container has option to wait for PDNS API server authorization test to pass before starting - Docker container running Flask in development environment will automatically rebuild flask assets during startup if feature enabled - Docker container will automatically run Python test unit during startup if feature enabled - Dramatically improved the build effeciency, maintainability, and startup time of the Docker images --- .dockerignore | 1 + .gitignore | 1 + README.md | 45 +- yarn.lock => _yarn.lock | 0 configs/default.py | 0 configs/development.py | 75 ++- configs/docker_config.py | 133 ------ docker/alpine/Dockerfile | 8 +- bin/build.sh => docker/bin/build-image | 21 +- docker/bin/create-release | 1 + docker/debian/Dockerfile | 9 +- docker/repo.cfg | 2 +- docker/shared/entrypoint | 238 ++++++++++ docker/shared/entrypoint.sh | 186 -------- docker/stacks/local/mysql/docker-compose.yml | 77 ++-- docker/stacks/local/sqlite/docker-compose.yml | 93 ++++ .../production/mysql/docker-compose.yml | 40 ++ .../production/sqlite/docker-compose.yml | 26 ++ docs/docker.md | 142 ++++++ docs/settings.md | 426 ++++++++++++++++++ package.json | 1 + powerdnsadmin/__init__.py | 68 ++- powerdnsadmin/default_config.py | 34 -- powerdnsadmin/env/boolean.env | 26 ++ powerdnsadmin/env/default.env | 31 ++ powerdnsadmin/env/discovered.env | 75 +++ powerdnsadmin/env/integer.env | 8 + powerdnsadmin/env/legacy.env | 61 +++ powerdnsadmin/lib/config_util.py | 55 +++ run.py | 10 +- 30 files changed, 1402 insertions(+), 491 deletions(-) rename yarn.lock => _yarn.lock (100%) create mode 100644 configs/default.py delete mode 100644 configs/docker_config.py rename bin/build.sh => docker/bin/build-image (82%) create mode 100644 docker/bin/create-release create mode 100755 docker/shared/entrypoint delete mode 100755 docker/shared/entrypoint.sh create mode 100644 docker/stacks/local/sqlite/docker-compose.yml create mode 100644 docker/stacks/production/mysql/docker-compose.yml create mode 100644 docker/stacks/production/sqlite/docker-compose.yml create mode 100644 docs/docker.md create mode 100644 docs/settings.md delete mode 100644 powerdnsadmin/default_config.py create mode 100644 powerdnsadmin/env/boolean.env create mode 100644 powerdnsadmin/env/default.env create mode 100644 powerdnsadmin/env/discovered.env create mode 100644 powerdnsadmin/env/integer.env create mode 100644 powerdnsadmin/env/legacy.env create mode 100644 powerdnsadmin/lib/config_util.py diff --git a/.dockerignore b/.dockerignore index 2efd33b..de79042 100644 --- a/.dockerignore +++ b/.dockerignore @@ -97,6 +97,7 @@ npm-debug.log Dockerfile* docker-compose* .dockerignore +docker/.autoinit # Git .git diff --git a/.gitignore b/.gitignore index 4a62dbb..4b46523 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ nosetests.xml flask config.py configs/production.py +docker/.autoinit logfile.log log.txt pdns.db diff --git a/README.md b/README.md index e924bb0..d58b9ef 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,18 @@ # PowerDNS-Admin -A PowerDNS web interface with advanced features. [![Language grade: Python](https://img.shields.io/lgtm/grade/python/g/ngoduykhanh/PowerDNS-Admin.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/ngoduykhanh/PowerDNS-Admin/context:python) [![Language grade: JavaScript](https://img.shields.io/lgtm/grade/javascript/g/ngoduykhanh/PowerDNS-Admin.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/ngoduykhanh/PowerDNS-Admin/context:javascript) +The PowerDNS-Admin is a simple web GUI for managing zone configurations of a PowerDNS Authoritative server. + +The PowerDNS-Admin app does NOT modify the PowerDNS Authoritative server database directly. Instead, it communicates with the PDNS server via the built-in HTTP API. + +The app does have a database for identity management, access control, and caching which can be hosted in either MySQL or SQLite. + +- [PowerDNS-Admin GitHub](https://github.com/ngoduykhanh/PowerDNS-Admin) +- [PowerDNS-Admin Settings](https://github.com/ngoduykhanh/PowerDNS-Admin/blob/master/docs/settings.md) +- [PowerDNS-Admin Wiki](https://github.com/ngoduykhanh/PowerDNS-Admin/wiki) + #### Features: - Multiple domain management - Domain template @@ -19,38 +28,18 @@ A PowerDNS web interface with advanced features. - Limited API for manipulating zones and records - Full IDN/Punycode support -## Running PowerDNS-Admin -There are several ways to run PowerDNS-Admin. The easiest way is to use Docker. -If you are looking to install and run PowerDNS-Admin directly onto your system check out the [Wiki](https://github.com/ngoduykhanh/PowerDNS-Admin/wiki#installation-guides) for ways to do that. +## Deploying PowerDNS-Admin +There are multiple ways to run the PowerDNS-Admin app. The recommended method is to use the official [Docker images](https://github.com/ngoduykhanh/PowerDNS-Admin/blob/master/docs/docker.md). -### Docker -This are two options to run PowerDNS-Admin using Docker. -To get started as quickly as possible try option 1. If you want to make modifications to the configuration option 2 may be cleaner. +If you would like to run PowerDNS-Admin directly on your machine or VM, check out the [Wiki](https://github.com/ngoduykhanh/PowerDNS-Admin/wiki#installation-guides) for additional information. -#### Option 1: From Docker Hub -The easiest is to just run the latest Docker image from Docker Hub: -``` -$ docker run -d \ - -e SECRET_KEY='a-very-secret-key' \ - -v pda-data:/data \ - -p 9191:80 \ - ngoduykhanh/powerdns-admin:latest -``` -This creates a volume called `pda-data` to persist the SQLite database with the configuration. +Once you have deployed the app through one of the supported methods, You should be able to access the PowerDNS-Admin app by pointing your browser to http://localhost:8080. -#### Option 2: Using docker-compose -1. Update the configuration - Edit the `docker-compose.yml` file to update the database connection string in `SQLALCHEMY_DATABASE_URI`. - Other environment variables are mentioned in the [legal_envvars](https://github.com/ngoduykhanh/PowerDNS-Admin/blob/master/configs/docker_config.py#L5-L46). - To use the Docker secrets feature it is possible to append `_FILE` to the environment variables and point to a file with the values stored in it. - Make sure to set the environment variable `SECRET_KEY` to a long random string (https://flask.palletsprojects.com/en/1.1.x/config/#SECRET_KEY) +## Configuring PowerDNS-Admin -2. Start docker container - ``` - $ docker-compose up - ``` +The app has a [plethora of settings](https://github.com/ngoduykhanh/PowerDNS-Admin/blob/master/docs/settings.md) that may be configured through a number of methods. Check out the settings documentation [here](https://github.com/ngoduykhanh/PowerDNS-Admin/blob/master/docs/settings.md). -You can then access PowerDNS-Admin by pointing your browser to http://localhost:9191. +[PowerDNS Admin Settings](https://github.com/ngoduykhanh/PowerDNS-Admin/blob/master/docs/settings.md) ## Screenshots ![dashboard](https://user-images.githubusercontent.com/6447444/44068603-0d2d81f6-9fa5-11e8-83af-14e2ad79e370.png) diff --git a/yarn.lock b/_yarn.lock similarity index 100% rename from yarn.lock rename to _yarn.lock diff --git a/configs/default.py b/configs/default.py new file mode 100644 index 0000000..e69de29 diff --git a/configs/development.py b/configs/development.py index 1f37f46..5a93b40 100644 --- a/configs/development.py +++ b/configs/development.py @@ -1,35 +1,35 @@ -import os -import urllib.parse - -basedir = os.path.abspath(os.path.dirname(__file__)) - -### BASIC APP CONFIG -DEBUG_MODE = True +######################################## +# Core App Config +######################################## +PDA_BIND_ADDRESS = '0.0.0.0' +PDA_BIND_PORT = 80 +PDA_DEBUG = True SALT = '$2b$12$yLUMTIfl21FKJQpTkRQXCu' SECRET_KEY = 'e951e5a1f4b94151b360f47edf596dd2' -BIND_ADDRESS = '0.0.0.0' -PORT = 80 +SIGNUP_ENABLED = True +HSTS_ENABLED = False OFFLINE_MODE = False +FILESYSTEM_SESSIONS_ENABLED = False -### DATABASE CONFIG +######################################## +# MySQL Database Config +######################################## +SQLA_DB_HOST = 'mysql' +SQLA_DB_PORT = 3306 SQLA_DB_USER = 'pda' SQLA_DB_PASSWORD = 'qHZDZ26fPqqCfKZtWkQ9' -SQLA_DB_HOST = 'mysql' SQLA_DB_NAME = 'pda' SQLALCHEMY_TRACK_MODIFICATIONS = True -### DATABASE - MySQL -SQLALCHEMY_DATABASE_URI = 'mysql://{}:{}@{}/{}'.format( - urllib.parse.quote_plus(SQLA_DB_USER), - urllib.parse.quote_plus(SQLA_DB_PASSWORD), - SQLA_DB_HOST, - SQLA_DB_NAME -) +######################################## +# SQLite Database Config +######################################## +# Uncomment the following line to use SQLite instead of MySQL +# SQLALCHEMY_DATABASE_URI = 'sqlite:////srv/app/pdns.db' -### DATABASE - SQLite -SQLALCHEMY_DATABASE_URI = 'sqlite:////srv/app/pdns.db' - -### SMTP config +######################################## +# SMTP Config +######################################## # MAIL_SERVER = 'localhost' # MAIL_PORT = 25 # MAIL_DEBUG = False @@ -39,28 +39,27 @@ SQLALCHEMY_DATABASE_URI = 'sqlite:////srv/app/pdns.db' # MAIL_PASSWORD = None # MAIL_DEFAULT_SENDER = ('PowerDNS-Admin', 'noreply@domain.ltd') -# SAML Authnetication +######################################## +# SAML Authentication Config +######################################## SAML_ENABLED = False # SAML_DEBUG = True # SAML_PATH = os.path.join(os.path.dirname(__file__), 'saml') -# ##Example for ADFS Metadata-URL +# ** Example for ADFS Metadata-URL # SAML_METADATA_URL = 'https:///FederationMetadata/2007-06/FederationMetadata.xml' -# #Cache Lifetime in Seconds +# ** Cache Lifetime in Seconds # SAML_METADATA_CACHE_LIFETIME = 1 - -# # SAML SSO binding format to use -# ## Default: library default (urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect) +# ** SAML SSO binding format to use +# *** Default: library default (urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect) # #SAML_IDP_SSO_BINDING = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST' - -# ## EntityID of the IdP to use. Only needed if more than one IdP is -# ## in the SAML_METADATA_URL -# ### Default: First (only) IdP in the SAML_METADATA_URL -# ### Example: https://idp.example.edu/idp -# #SAML_IDP_ENTITY_ID = 'https://idp.example.edu/idp' -# ## NameID format to request -# ### Default: The SAML NameID Format in the metadata if present, -# ### otherwise urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified -# ### Example: urn:oid:0.9.2342.19200300.100.1.1 +# ** EntityID of the IdP to use. Only needed if more than one IdP is in the SAML_METADATA_URL +# *** Default: First (only) IdP in the SAML_METADATA_URL +# *** Example: https://idp.example.edu/idp +# SAML_IDP_ENTITY_ID = 'https://idp.example.edu/idp' +# ** NameID format to request +# *** Default: The SAML NameID Format in the metadata if present, +# *** otherwise urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified +# *** Example: urn:oid:0.9.2342.19200300.100.1.1 # #SAML_NAMEID_FORMAT = 'urn:oid:0.9.2342.19200300.100.1.1' # Following parameter defines RequestedAttributes section in SAML metadata diff --git a/configs/docker_config.py b/configs/docker_config.py deleted file mode 100644 index 91f0f10..0000000 --- a/configs/docker_config.py +++ /dev/null @@ -1,133 +0,0 @@ -import urllib.parse -DEBUG_MODE = False -BIND_ADDRESS = '0.0.0.0' -PORT = 80 - -#SQLALCHEMY_DATABASE_URI = 'sqlite:////data/powerdns-admin.db' - -### DATABASE CONFIG -SQLA_DB_USER = 'pda' -SQLA_DB_PASSWORD = 'qHZDZ26fPqqCfKZtWkQ9' -SQLA_DB_HOST = 'mysql' -SQLA_DB_NAME = 'pda' -SQLALCHEMY_TRACK_MODIFICATIONS = True - -### DATABASE - MySQL -SQLALCHEMY_DATABASE_URI = 'mysql://{}:{}@{}/{}'.format( - urllib.parse.quote_plus(SQLA_DB_USER), - urllib.parse.quote_plus(SQLA_DB_PASSWORD), - SQLA_DB_HOST, - SQLA_DB_NAME -) - - -legal_envvars = ( - 'SECRET_KEY', - 'OIDC_OAUTH_API_URL', - 'OIDC_OAUTH_TOKEN_URL', - 'OIDC_OAUTH_AUTHORIZE_URL', - 'BIND_ADDRESS', - 'PORT', - 'LOG_LEVEL', - 'SALT', - 'SQLALCHEMY_TRACK_MODIFICATIONS', - 'SQLALCHEMY_DATABASE_URI', - 'MAIL_SERVER', - 'MAIL_PORT', - 'MAIL_DEBUG', - 'MAIL_USE_TLS', - 'MAIL_USE_SSL', - 'MAIL_USERNAME', - 'MAIL_PASSWORD', - 'MAIL_DEFAULT_SENDER', - 'SAML_ENABLED', - 'SAML_DEBUG', - 'SAML_PATH', - 'SAML_METADATA_URL', - 'SAML_METADATA_CACHE_LIFETIME', - 'SAML_IDP_SSO_BINDING', - 'SAML_IDP_ENTITY_ID', - 'SAML_NAMEID_FORMAT', - 'SAML_ATTRIBUTE_EMAIL', - 'SAML_ATTRIBUTE_GIVENNAME', - 'SAML_ATTRIBUTE_SURNAME', - 'SAML_ATTRIBUTE_NAME', - 'SAML_ATTRIBUTE_USERNAME', - 'SAML_ATTRIBUTE_ADMIN', - 'SAML_ATTRIBUTE_GROUP', - 'SAML_GROUP_ADMIN_NAME', - 'SAML_GROUP_TO_ACCOUNT_MAPPING', - 'SAML_ATTRIBUTE_ACCOUNT', - 'SAML_SP_ENTITY_ID', - 'SAML_SP_CONTACT_NAME', - 'SAML_SP_CONTACT_MAIL', - 'SAML_SIGN_REQUEST', - 'SAML_WANT_MESSAGE_SIGNED', - 'SAML_LOGOUT', - 'SAML_LOGOUT_URL', - 'SAML_ASSERTION_ENCRYPTED', - 'OFFLINE_MODE', - 'REMOTE_USER_LOGOUT_URL', - 'REMOTE_USER_COOKIES', - 'SIGNUP_ENABLED', - 'LOCAL_DB_ENABLED', - 'LDAP_ENABLED', - 'SAML_CERT', - 'SAML_KEY', - 'FILESYSTEM_SESSIONS_ENABLED' -) - -legal_envvars_int = ('PORT', 'MAIL_PORT', 'SAML_METADATA_CACHE_LIFETIME') - -legal_envvars_bool = ( - 'SQLALCHEMY_TRACK_MODIFICATIONS', - 'HSTS_ENABLED', - 'MAIL_DEBUG', - 'MAIL_USE_TLS', - 'MAIL_USE_SSL', - 'SAML_ENABLED', - 'SAML_DEBUG', - 'SAML_SIGN_REQUEST', - 'SAML_WANT_MESSAGE_SIGNED', - 'SAML_LOGOUT', - 'SAML_ASSERTION_ENCRYPTED', - 'OFFLINE_MODE', - 'REMOTE_USER_ENABLED', - 'SIGNUP_ENABLED', - 'LOCAL_DB_ENABLED', - 'LDAP_ENABLED', - 'FILESYSTEM_SESSIONS_ENABLED' -) - -# import everything from environment variables -import os -import sys - - -def str2bool(v): - return v.lower() in ("true", "yes", "1") - - -for v in legal_envvars: - - ret = None - # _FILE suffix will allow to read value from file, usefull for Docker's - # secrets feature - if v + '_FILE' in os.environ: - if v in os.environ: - raise AttributeError( - "Both {} and {} are set but are exclusive.".format( - v, v + '_FILE')) - with open(os.environ[v + '_FILE']) as f: - ret = f.read() - f.close() - - elif v in os.environ: - ret = os.environ[v] - - if ret is not None: - if v in legal_envvars_bool: - ret = str2bool(ret) - if v in legal_envvars_int: - ret = int(ret) - sys.modules[__name__].__dict__[v] = ret diff --git a/docker/alpine/Dockerfile b/docker/alpine/Dockerfile index d802e49..eede6a9 100644 --- a/docker/alpine/Dockerfile +++ b/docker/alpine/Dockerfile @@ -8,7 +8,6 @@ LABEL maintainer="matt@azorian.solutions" ENV LC_ALL=en_US.UTF-8 \ LANG=en_US.UTF-8 \ LANGUAGE=en_US.UTF-8 \ - PDA_PORT=${PDA_PORT:-80} \ PDA_UID=${PDA_UID:-1000} \ PDA_USER=${PDA_USER:-pda} \ PDA_GID=${PDA_GID:-1000} \ @@ -19,7 +18,7 @@ ENV LC_ALL=en_US.UTF-8 \ # - Install required APK packages per the required dependencies # - Upgrade Python pip package using pip3 RUN apk update \ - && apk add --no-cache curl build-base tzdata libcap libffi-dev libxml2-dev xmlsec-dev mariadb-connector-c-dev \ + && apk add --no-cache bash grep curl build-base tzdata libcap libffi-dev libxml2-dev xmlsec-dev mariadb-connector-c-dev \ openldap-dev python3-dev py3-pip py3-gunicorn py3-psycopg2 yarn cargo mysql-client postgresql-client sqlite \ && pip install --upgrade pip @@ -33,7 +32,6 @@ WORKDIR /srv/app RUN pip install -r requirements.txt # Copy all project files into the container's /app directory -# FIXME: This is too broad and will almost guarentee constant image re-builds that can be skipped in dev environment COPY . /srv/app # Perform the following tasks: @@ -62,7 +60,7 @@ VOLUME /srv/app EXPOSE 80/TCP # Set the entrypoint script of the container -ENTRYPOINT ["sh", "/srv/app/docker/shared/entrypoint.sh"] +ENTRYPOINT ["/bin/bash", "/srv/app/docker/shared/entrypoint"] # Set the default command of the container to be ran following the entrypoint script -CMD ["sh", "-c", "/srv/app/run.py"] +CMD ["/bin/sh", "-c", "/srv/app/run.py"] diff --git a/bin/build.sh b/docker/bin/build-image similarity index 82% rename from bin/build.sh rename to docker/bin/build-image index 6d34553..e3e25ef 100755 --- a/bin/build.sh +++ b/docker/bin/build-image @@ -1,34 +1,27 @@ -#!/usr/bin/env sh +#!/bin/bash script_path=$(readlink -f "$0") bin_path=$(dirname "$script_path") -app_path=$(dirname "$bin_path") -app_version=0.2.4 +app_path=$(dirname $(dirname "$bin_path")) docker_path=$app_path/docker docker_repo_config_path=$docker_path/repo.cfg -docker_repo= docker_distro=alpine docker_distro_tag=3.14 -docker_tag=$app_version-$docker_distro-$docker_distro_tag-mysql +docker_tag=latest if [ ! -z "$1" ]; then - docker_tag=$1 + docker_distro=$1 fi if [ ! -z "$2" ]; then - app_version=$2 + docker_distro_tag=$2 fi if [ ! -z "$3" ]; then - docker_distro=$3 -fi - -if [ ! -z "$4" ]; -then - docker_distro_tag=$4 + docker_tag=$3 fi if [ -f "$docker_repo_config_path" ]; @@ -54,5 +47,3 @@ else echo "Could not load Docker registry path from $docker_repo_config_path" exit 1 fi - -exit \ No newline at end of file diff --git a/docker/bin/create-release b/docker/bin/create-release new file mode 100644 index 0000000..a9bf588 --- /dev/null +++ b/docker/bin/create-release @@ -0,0 +1 @@ +#!/bin/bash diff --git a/docker/debian/Dockerfile b/docker/debian/Dockerfile index afbb196..1fe6f94 100644 --- a/docker/debian/Dockerfile +++ b/docker/debian/Dockerfile @@ -8,7 +8,6 @@ LABEL maintainer="matt@azorian.solutions" ENV LC_ALL=en_US.UTF-8 \ LANG=en_US.UTF-8 \ LANGUAGE=en_US.UTF-8 \ - PDA_PORT=${PDA_PORT:-80} \ PDA_UID=${PDA_UID:-1000} \ PDA_USER=${PDA_USER:-pda} \ PDA_GID=${PDA_GID:-1000} \ @@ -27,7 +26,8 @@ ENV LC_ALL=en_US.UTF-8 \ RUN apt update -y \ && apt install -y --no-install-recommends apt-transport-https locales locales-all python3-pip python3-setuptools \ python3-dev curl libsasl2-dev libldap2-dev libssl-dev libxml2-dev libxmlsec1 libxslt1-dev libxmlsec1-dev xmlsec1 \ - libffi-dev build-essential libmariadb-dev-compat pkg-config mysql-client postgresql-client sqlite3 \ + libffi-dev build-essential libmariadb-dev-compat pkg-config default-mysql-client postgresql-client sqlite3 \ + netcat-openbsd \ && curl -sL https://deb.nodesource.com/setup_14.x | bash - \ && curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \ && echo "deb https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list \ @@ -47,7 +47,6 @@ WORKDIR /srv/app RUN pip3 install -r requirements.txt # Copy all project files into the container's /srv/app directory -# FIXME: This is too broad and will almost guarentee constant image re-builds that can be skipped in dev environment COPY . /srv/app # Perform the following tasks: @@ -72,7 +71,7 @@ VOLUME /srv/app EXPOSE 80/TCP # Set the entrypoint script of the container -ENTRYPOINT ["sh", "/srv/app/docker/shared/entrypoint.sh"] +ENTRYPOINT ["/bin/bash", "/srv/app/docker/shared/entrypoint"] # Set the default command of the container to be ran following the entrypoint script -CMD ["sh", "-c", "/srv/app/run.py"] +CMD ["/bin/sh", "-c", "/srv/app/run.py"] diff --git a/docker/repo.cfg b/docker/repo.cfg index a3b049e..9289d7d 100644 --- a/docker/repo.cfg +++ b/docker/repo.cfg @@ -1 +1 @@ -azoriansolutions/powerdns-admin \ No newline at end of file +ngoduykhanh/powerdns-admin \ No newline at end of file diff --git a/docker/shared/entrypoint b/docker/shared/entrypoint new file mode 100755 index 0000000..c68f540 --- /dev/null +++ b/docker/shared/entrypoint @@ -0,0 +1,238 @@ +#!/bin/bash + +script_path=$(readlink -f "$0") +app_path=$(dirname $(dirname $(dirname "$script_path"))) +docker_path=$app_path/docker +auto_init_path=$docker_path/.autoinit + +######################################################################################################################## +# START CONFIGURATION +######################################################################################################################## + +# The container interface IP mask to bind both gunicorn and the built-in web server to +PDA_BIND_ADDRESS=${PDA_BIND_ADDRESS:-0.0.0.0} + +# The container port to bind both gunicorn and the built-in web server to +PDA_BIND_PORT=${PDA_BIND_PORT:-80} + +# The debug mode setting of the app +PDA_DEBUG=${PDA_DEBUG:-false} + +# Whether to automatically convert environment variables beginning with PDA_ and ending with _FILE +PDA_LOAD_ENV_FILES=${PDA_LOAD_ENV_FILES:-true} + +# Whether the automatic first-time initialization feature is enabled +PDA_AUTOINIT_ENABLED=${PDA_AUTOINIT_ENABLED:-false} + +# Whether to force execution of the first-time initialization feature +PDA_AUTOINIT_FORCE=${PDA_AUTOINIT_FORCE:-false} + +# Whether to delay container startup until the configured MySQL server is online +PDA_CHECK_MYSQL_ENABLED=${PDA_CHECK_MYSQL_ENABLED:-false} + +# How many seconds to wait after a MySQL connection attempt failure before trying again +PDA_CHECK_MYSQL_FAIL_DELAY=${PDA_CHECK_MYSQL_FAIL_DELAY:-2} + +# How many seconds to wait after a successful MySQL connection attempt before proceeding to the next step +PDA_CHECK_MYSQL_SUCCESS_DELAY=${PDA_CHECK_MYSQL_SUCCESS_DELAY:-0} + +# How many MySQL connection attempts should be made before halting container execution +PDA_CHECK_MYSQL_ATTEMPTS=${PDA_CHECK_MYSQL_ATTEMPTS:-30} + +# Whether to delay container startup until the configured PDNS API key passes authorization to the server +PDA_CHECK_API_ENABLED=${PDA_CHECK_API_ENABLED:-false} + +# How many seconds to wait after an API connection attempt failure before trying again +PDA_CHECK_API_FAIL_DELAY=${PDA_CHECK_API_FAIL_DELAY:-2} + +# How many seconds to wait after an API successful connection attempt before proceeding to the next step +PDA_CHECK_API_SUCCESS_DELAY=${PDA_CHECK_API_SUCCESS_DELAY:-0} + +# How many API connection attempts should be made before halting container execution +PDA_CHECK_API_ATTEMPTS=${PDA_CHECK_API_ATTEMPTS:-15} + +# Whether to run the app's Python test unit before starting the primary container process +PDA_CHECK_PYTEST_ENABLED=${PDA_CHECK_PYTEST_ENABLED:-false} + +# Whether to enable the use of gunicorn +PDA_GUNICORN_ENABLED=${PDA_GUNICORN_ENABLED:-false} + +# The request timeout in seconds for a gunicorn worker +PDA_GUNICORN_TIMEOUT=${PDA_GUNICORN_TIMEOUT:-120} + +# The total number of gunicorn worker threads to spawn +PDA_GUNICORN_WORKERS=${PDA_GUNICORN_WORKERS:-4} + +# The log output level of the gunicorn process +PDA_GUNICORN_LOGLEVEL=${PDA_GUNICORN_LOGLEVEL:-info} + +# The additional gunicorn arguments that will be appended to the startup command when gunicorn is enabled +PDA_GUNICORN_ARGS=${PDA_GUNICORN_ARGS:-"-t ${PDA_GUNICORN_TIMEOUT} -w ${PDA_GUNICORN_WORKERS} \ +--log-level ${PDA_GUNICORN_LOGLEVEL} --bind ${PDA_BIND_ADDRESS}:${PDA_BIND_PORT}"} + +# The API key for the associated PowerDNS Authoritative server API +PDA_PDNS_API_KEY=${PDA_PDNS_API_KEY:-CHANGE_ME!} + +# The API version of the associated PowerDNS Authoritative server API +PDA_PDNS_API_VERSION=${PDA_PDNS_API_VERSION:-4.5.2} + +# The HTTP protocol for the associated PowerDNS Authoritative server API +PDA_PDNS_API_PROTO=${PDA_PDNS_API_PROTO:-http} + +# The hostname or IP address of the associated PowerDNS Authoritative server API +PDA_PDNS_API_HOST=${PDA_PDNS_API_HOST:-127.0.0.1} + +# The port of the associated PowerDNS Authoritative server API +PDA_PDNS_API_PORT=${PDA_PDNS_API_PORT:-8081} + +# The username to use for automatic admin account setup on startup +PDA_ADMIN_USER_USERNAME=${PDA_ADMIN_USER_USERNAME:-} + +# The password to use for automatic admin account setup on startup +PDA_ADMIN_USER_PASSWORD=${PDA_ADMIN_USER_PASSWORD:-} + +######################################################################################################################## +# END CONFIGURATION +######################################################################################################################## + +convert_file_vars() { + for line in $(env); do + len=$(($(echo $line | wc -c) - 1)) + key=$(echo $line | cut -c1-$(echo $line | grep -aob '=' | grep -oE '[0-9]+')) + value=$(echo $line | cut -c$(($(echo $line | grep -aob '=' | grep -oE '[0-9]+') + 2))-$len) + if [[ $key == PDA_* && $key == *_FILE && -n "$value" && -f "$value" ]]; then + len=$(($(echo $key | wc -c) - 1)) + new_key=${key:0:$((len - 5))} + file_value=$(cat "$value") + export "$new_key"="$file_value" + unset "$key" + fi + done +} + +auto_init() { + # Determine if the first run file exists and abort if so + if ([ ! -n "$PDA_AUTOINIT_FORCE" ] || [ ! "$PDA_AUTOINIT_FORCE" = "true" ]) && [ -f "$auto_init_path" ]; then + echo "Automatic initialization will abort as a first run file exists at $auto_init_path" + return + fi + + # Assume MySQL database if the SQLA_DB_HOST environment variable has been set, otherwise assume SQLite + if [ -n "$PDAC_SQLA_DB_HOST" ] && [ -n "$PDAC_SQLA_DB_USER" ] && [ -n "$PDAC_SQLA_DB_PASSWORD" ] \ + && [ -n "$PDAC_SQLA_DB_PASSWORD" ] && [ -n "$PDAC_SQLA_DB_NAME" ]; then + table=tsigkeys + sql=$(printf "SHOW TABLES LIKE '%s'" "$table") + if [[ -z $(mysql -h $PDAC_SQLA_DB_HOST -u $PDAC_SQLA_DB_USER -p$PDAC_SQLA_DB_PASSWORD -e "$sql" $PDAC_SQLA_DB_NAME) ]]; then + echo "The table $table does not exist so the base pdns schema will be installed." + mysql_result=$(mysql -h $PDAC_SQLA_DB_HOST -u $PDAC_SQLA_DB_USER -p$PDAC_SQLA_DB_PASSWORD $PDAC_SQLA_DB_NAME < /srv/app/docker/shared/pdns-schema-mysql.sql) + else + echo "The table $table does exist so no further action will be taken." + fi + fi + + if [ ! -f "/srv/app/pdns.db" ]; then + cat /srv/app/docker/shared/pdns-schema-sqlite.sql | sqlite3 /srv/app/pdns.db + fi + + touch $auto_init_path; +} + +verify_mysql_ready() { + local host=$1 + local port=$2 + local retry_executed=1 + + while ! nc -z $host $port >& /dev/null; do + # The last connection test to the MySQL server failed at this point + + # If the remaining retry counter falls to zero, exit the connection test cycle + if [ $retry_executed -ge $PDA_CHECK_MYSQL_ATTEMPTS ]; then + echo "The maximum number ($PDA_CHECK_MYSQL_ATTEMPTS) of TCP connection tests have been executed without success. This container will now exit." + exit 1 + else + echo "MySQL server is offline. The TCP connection test cycle number $retry_executed has failed to $host:$port. Waiting for $PDA_CHECK_MYSQL_FAIL_DELAY seconds..." + fi + + # Delay execution for the configured MySQL fail delay + sleep $PDA_CHECK_MYSQL_FAIL_DELAY + + # Increment the retry execution counter + retry_executed=$((retry_executed + 1)) + done + + echo "MySQL server is online after $retry_executed check(s). Delaying execution for $PDA_CHECK_MYSQL_SUCCESS_DELAY seconds..." + + sleep $PDA_CHECK_MYSQL_SUCCESS_DELAY +} + +verify_api_ready() { + local retry_executed=1 + local api_uri="$PDA_PDNS_PROTO://$PDA_PDNS_HOST:$PDA_PDNS_PORT/api/v1/servers" + local auth_header="X-API-Key: $PDA_PDNS_API_KEY" + + while [ $(curl -s -o /dev/null -w "%{http_code}" -H "$auth_header" "$api_uri") -ne "200" ]; do + # The last connection test to the API server failed at this point + + # If the remaining retry counter falls to zero, exit the connection test cycle + if [ $retry_executed -ge $PDA_CHECK_API_ATTEMPTS ]; then + echo "The maximum number ($PDA_CHECK_API_ATTEMPTS) of API server HTTP connection tests have been executed without success. This container will now exit." + exit 1 + else + echo "PowerDNS API server is offline. The HTTP connection test cycle number $retry_executed has failed to $api_uri. Waiting for $PDA_CHECK_API_FAIL_DELAY seconds..." + fi + + # Delay execution for the configured API fail delay + sleep $PDA_CHECK_API_FAIL_DELAY + + # Increment the retry execution counter + retry_executed=$((retry_executed + 1)) + done + + echo "PowerDNS API server is online after $retry_executed check(s). Delaying execution for ${PDA_CHECK_API_SUCCESS_DELAY} seconds..." + + sleep $PDA_CHECK_API_SUCCESS_DELAY +} + +# Automatically load any environment variables that begin with "PDA_" and end with "_FILE" if this feature is enabled +if [ "$PDA_LOAD_ENV_FILES" = "true" ]; then + convert_file_vars +fi + +# Verify that the configured MySQL server is ready for connections +if [ "$PDA_CHECK_MYSQL_ENABLED" = "true" ] && [ -n "$PDAC_SQLA_DB_HOST" ]; then + verify_mysql_ready "$PDAC_SQLA_DB_HOST" "${PDAC_SQLA_DB_PORT:-3306}" +fi + +# Execute first-run automatic initialization +# - Setup the base PowerDNS Authoritative server database schema if this is the first run if this feature is enabled +if [ "$PDA_AUTOINIT_ENABLED" = "true" ] || [ "$PDA_AUTOINIT_FORCE" = "true" ]; then + auto_init +fi + +# Verify that the configured PowerDNS name server API is ready for connections if this feature is enabled +if [ "$PDA_CHECK_API_ENABLED" = "true" ] && [ -n "$PDA_PDNS_HOST" ]; then + verify_api_ready +fi + +echo "Proceeding with app initialization..." +cd /srv/app + +# Execute any pending database upgrades via Flask +flask db upgrade + +if [ "$FLASK_ENV" = "development" ]; then + flask assets build +fi + +# Run Python test units if this feature is enabled +if [ "$PDA_CHECK_PYTEST_ENABLED" = "true" ]; then + pytest +fi + +# If gunicorn is configured for use then execute the gunicorn command with the configuration arguments and container +# command appended. Otherwise, just execute the container command. +if [ "$PDA_GUNICORN_ENABLED" = "true" ]; then + exec "gunicorn" $PDA_GUNICORN_ARGS "$@" +else + exec "$@" +fi diff --git a/docker/shared/entrypoint.sh b/docker/shared/entrypoint.sh deleted file mode 100755 index 04252c0..0000000 --- a/docker/shared/entrypoint.sh +++ /dev/null @@ -1,186 +0,0 @@ -#!/bin/sh -set -e - -script_path=$(readlink -f "$0") -app_path=$(dirname $(dirname $(dirname "$script_path"))) -docker_path=$app_path/docker -first_run_path=$docker_path/.firstrun - -######################################################################################################################## -# START CONFIGURATION -######################################################################################################################## - -# How many seconds to wait after a connection attempt failure before trying again -PDA_CHECK_MYSQL_FAIL_DELAY=${PDA_CHECK_MYSQL_FAIL_DELAY:-2} - -# How many seconds to wait after a successful connection attempt before proceeding to the next step -PDA_CHECK_MYSQL_SUCCESS_DELAY=${PDA_CHECK_MYSQL_SUCCESS_DELAY:-0} - -# How many MySQL connection attempts should be made before halting container execution -PDA_CHECK_MYSQL_ATTEMPTS=${PDA_CHECK_MYSQL_ATTEMPTS:-30} - -# How many seconds to wait after a connection attempt failure before trying again -PDA_CHECK_API_FAIL_DELAY=${PDA_CHECK_API_FAIL_DELAY:-2} - -# How many seconds to wait after a successful connection attempt before proceeding to the next step -PDA_CHECK_API_SUCCESS_DELAY=${PDA_CHECK_API_SUCCESS_DELAY:-0} - -# How many API connection attempts should be made before halting container execution -PDA_CHECK_API_ATTEMPTS=${PDA_CHECK_API_ATTEMPTS:-15} - -PDA_GUNICORN_BIND_ADDRESS="${PDA_GUNICORN_BIND_ADDRESS:-0.0.0.0:80}" - -PDA_GUNICORN_TIMEOUT="${PDA_GUNICORN_TIMEOUT:-120}" - -PDA_GUNICORN_WORKERS="${PDA_GUNICORN_WORKERS:-4}" - -PDA_GUNICORN_LOGLEVEL="${PDA_GUNICORN_LOGLEVEL:-info}" - -######################################################################################################################## -# END CONFIGURATION -######################################################################################################################## - -API_URI="${PDA_PDNS_PROTO:-http}://${PDA_PDNS_HOST:-127.0.0.1}:${PDA_PDNS_PORT:-8081}/api/v1/servers" -API_AUTH_HEADER="X-API-Key: ${PDA_PDNS_API_KEY}" -GUNICORN_ARGS="-t ${PDA_GUNICORN_TIMEOUT} --workers ${PDA_GUNICORN_WORKERS} --log-level ${PDA_GUNICORN_LOGLEVEL} \ ---bind ${PDA_GUNICORN_BIND_ADDRESS}" - -convert_file_vars() { - for line in $(env); do - if [[ $line == PDA_* ]] || [[ $line == AS_* ]]; then - if [[ $line =~ ^.*_FILE ]]; then - local INDEX=$(echo $line | grep -aob '=' | grep -oE '[0-9]+') - local LEN=$(echo $line | wc -c) - local NAME_END_INDEX=$(($INDEX - 5)) - local NAME_FULL=$(echo $line | cut -c1-$INDEX) - local NAME=$(echo $line | cut -c1-$NAME_END_INDEX) - INDEX=$(($INDEX + 2)) - local VALUE=$(echo $line | cut -c$INDEX-$LEN) - local FILE_VALUE=$(cat $VALUE) - export $NAME=$FILE_VALUE - unset $NAME_FULL - fi - fi - done -} - -verify_mysql_ready() { - local host=$1 - local port=$2 - local retry_executed=1 - - while ! nc -z $host $port >& /dev/null; do - # The last connection test to the MySQL server failed at this point - - # If the remaining retry counter falls to zero, exit the connection test cycle - if [ $retry_executed -ge $PDA_CHECK_MYSQL_ATTEMPTS ]; then - echo "The maximum number ($PDA_CHECK_MYSQL_ATTEMPTS) of TCP connection tests have been executed without success. This container will now exit." - exit 1 - else - echo "MySQL server is offline. The TCP connection test cycle number $retry_executed has failed to $host:$port. Waiting for $PDA_CHECK_MYSQL_FAIL_DELAY seconds..." - fi - - # Delay execution for the configured MySQL fail delay - sleep $PDA_CHECK_MYSQL_FAIL_DELAY - - # Increment the retry execution counter - retry_executed=$((retry_executed + 1)) - done - - echo "MySQL server is online after $retry_executed check(s). Delaying execution for $PDA_CHECK_MYSQL_SUCCESS_DELAY seconds..." - - sleep $PDA_CHECK_MYSQL_SUCCESS_DELAY -} - -verify_api_ready() { - local retry_executed=1 - - while [ $(curl -s -o /dev/null -w "%{http_code}" -H "$2" $1) -ne "200" ]; do - # The last connection test to the API server failed at this point - - # If the remaining retry counter falls to zero, exit the connection test cycle - if [ $retry_executed -ge $PDA_CHECK_API_ATTEMPTS ]; then - echo "The maximum number ($PDA_CHECK_API_ATTEMPTS) of API server HTTP connection tests have been executed without success. This container will now exit." - exit 1 - else - echo "PowerDNS API server is offline. The HTTP connection test cycle number $retry_executed has failed to $1. Waiting for $PDA_CHECK_API_FAIL_DELAY seconds..." - fi - - # Delay execution for the configured API fail delay - sleep $PDA_CHECK_API_FAIL_DELAY - - # Increment the retry execution counter - retry_executed=$((retry_executed + 1)) - done - - echo "PowerDNS API server is online after $retry_executed check(s). Delaying execution for ${PDA_CHECK_API_SUCCESS_DELAY} seconds..." - - sleep $PDA_CHECK_API_SUCCESS_DELAY -} - -first_run() { - # Determine if the first run file exists and abort if so - if [ -f "$first_run_path" ]; then - echo "First run actions aborting as a first run file exists at $first_run_path" - return - fi - - # Assume MySQL database if the SQLA_DB_HOST environment variable has been set, otherwise assume SQLite - if [ -n "$SQLA_DB_HOST" ]; then - table=tsigkeys - sql_exists=$(printf 'SHOW TABLES LIKE "%s"' "$table") - if [[ -z $(mysql -h $SQLA_DB_HOST -u $SQLA_DB_USER -p$SQLA_DB_PASSWORD -e "$sql_exists" $SQLA_DB_NAME) ]]; then - echo "The table $table does not exist so the base pdns schema will be installed." - mysql_result=$(mysql -h $SQLA_DB_HOST -u $SQLA_DB_USER -p$SQLA_DB_PASSWORD $SQLA_DB_NAME < /srv/app/docker/shared/pdns-schema-mysql.sql) - else - echo "The table $table does exist so no further action will be taken." - fi - fi - - if [ ! -f "/srv/app/pdns.db" ]; then - cat /srv/app/docker/shared/pdns-schema-sqlite.sql | sqlite3 /srv/app/pdns.db - fi - - touch $first_run_path; -} - -# If the command starts with an option, prepend the appropriate command name -if [ "${1:0:1}" = '-' ]; then - set -- "gunicorn" "$@" -fi - -# Automatically convert any environment variables that are prefixed with "PDA_" or "AS_" and suffixed with "_FILE" -convert_file_vars - -# Verify that the configured MySQL server is ready for connections -if [ -n "$SQLA_DB_HOST" ]; then - verify_mysql_ready "${SQLA_DB_HOST}" "${SQLA_DB_PORT:-3306}" -fi - -# Execute first run tasks -# - Setup the base PowerDNS Authoritative server database schema if this is the first run -first_run - -# Verify that the configured PowerDNS name server API is ready for connections -if [ -n "$PDA_PDNS_HOST" ]; then - verify_api_ready "${API_URI}" "${API_AUTH_HEADER}" -fi - -echo "Proceeding with app initialization..." -cd /srv/app - -# Execute any pending database upgrades via Flask -flask db upgrade - -if [ $FLASK_ENV = "development" ]; then - yarn install --pure-lockfile --${FLASK_ENV} - yarn cache clean - flask assets build -fi - -# Determine if the gunicorn command is being used and append program arguments if so. Otherwise, execute normally -if [ "$1" = "gunicorn" ]; then - exec "$@" $GUNICORN_ARGS -else - exec "$@" -fi diff --git a/docker/stacks/local/mysql/docker-compose.yml b/docker/stacks/local/mysql/docker-compose.yml index 65a5574..b8d0e5f 100644 --- a/docker/stacks/local/mysql/docker-compose.yml +++ b/docker/stacks/local/mysql/docker-compose.yml @@ -21,6 +21,55 @@ services: ports: - 8082:80 + powerdns-admin: + image: ngoduykhanh/powerdns-admin:latest + depends_on: [ 'mysql' ] + restart: unless-stopped + environment: + - PDA_BIND_ADDRESS=0.0.0.0 + - PDA_BIND_PORT=80 + - PDA_DEBUG=true + - PDA_LOAD_ENV_FILES=true + - PDA_AUTOINIT_ENABLED=true + - PDA_AUTOINIT_FORCE=false + - PDA_CHECK_MYSQL_ENABLED=true + - PDA_CHECK_MYSQL_FAIL_DELAY=2 + - PDA_CHECK_MYSQL_SUCCESS_DELAY=0 + - PDA_CHECK_MYSQL_ATTEMPTS=30 + - PDA_CHECK_API_ENABLED=true + - PDA_CHECK_API_FAIL_DELAY=2 + - PDA_CHECK_API_SUCCESS_DELAY=0 + - PDA_CHECK_API_ATTEMPTS=15 + - PDA_GUNICORN_ENABLED=false + - PDA_GUNICORN_TIMEOUT=2 + - PDA_GUNICORN_WORKERS=2 + - PDA_GUNICORN_LOGLEVEL=debug + - PDA_PDNS_API_KEY=scjfNRN4Ch2DBuhk24vr + - PDA_PDNS_VERSION=4.5.2 + - PDA_PDNS_PROTO=http + - PDA_PDNS_HOST=pdns + - PDA_PDNS_PORT=8081 + - PDA_ADMIN_USER_USERNAME=nocadmin + - PDA_ADMIN_USER_PASSWORD=clink henchmen mete coracle uphold + - PDAC_SALT=ntR8BmMQb7MKTJ5YrgQwZr3pxU3uLG + - PDAC_SECRET_KEY=eJ92QLuennVStHTj5FVwxz7m4r3Ng3 + - PDAC_SIGNUP_ENABLED=true + - PDAC_HSTS_ENABLED=false + - PDAC_OFFLINE_MODE=false + - PDAC_FILESYSTEM_SESSIONS_ENABLED=false + - PDAC_SQLA_DB_HOST=mysql + - PDAC_SQLA_DB_NAME=pda + - PDAC_SQLA_DB_USER=root + - PDAC_SQLA_DB_PASSWORD=qHZDZ26fPqqCfKZtWkQ9 + - PDAC_SQLA_DB_PORT=3306 + - PDAC_SAML_ENABLED=false + - SQLALCHEMY_TRACK_MODIFICATIONS=true + - FLASK_ENV=development + ports: + - "8080:80" + volumes: + - /PATH/TO/YOUR/GIT/PROJECT/ROOT:/srv/app + pdns: image: azoriansolutions/powerdns-nameserver:4.5.2-alpine-3.14-mysql depends_on: ['powerdns-admin'] @@ -49,30 +98,4 @@ services: ports: - "5053:53/tcp" - "5053:53/udp" - - "8081:8081/tcp" - - powerdns-admin: - image: azoriansolutions/powerdns-admin:alpine - depends_on: ['mysql'] - restart: unless-stopped - environment: - - PDA_BUILD_ON_STARTUP=1 - - PDA_PORT=80 - - PDA_PDNS_API_KEY=scjfNRN4Ch2DBuhk24vr - - PDA_PDNS_VERSION=4.5.2 - - PDA_PDNS_PROTO=http - - PDA_PDNS_HOST=pdns - - PDA_PDNS_PORT=8081 - - SQLA_DB_HOST=mysql - - SQLA_DB_NAME=pda - - SQLA_DB_USER=root - - SQLA_DB_PASSWORD=qHZDZ26fPqqCfKZtWkQ9 - - SQLA_DB_PORT=3306 - - ADMIN_USER=nocadmin - - ADMIN_USER_PASSWORD=clink henchmen mete coracle uphold - - FLASK_ENV=development - - FLASK_CONF=/srv/app/configs/development.py - ports: - - "8080:80" - volumes: - - /home/matt/Projects/azorian-solutions-llc/projects/docker-images/powerdns-admin/AzorianSolutions/PowerDNS-Admin:/srv/app \ No newline at end of file + - "8081:8081/tcp" \ No newline at end of file diff --git a/docker/stacks/local/sqlite/docker-compose.yml b/docker/stacks/local/sqlite/docker-compose.yml new file mode 100644 index 0000000..cd49977 --- /dev/null +++ b/docker/stacks/local/sqlite/docker-compose.yml @@ -0,0 +1,93 @@ +version: "3.3" + +services: + + mysql: + image: mariadb:10.6 + restart: unless-stopped + environment: + - MYSQL_ROOT_PASSWORD=qHZDZ26fPqqCfKZtWkQ9 + - MYSQL_DATABASE=pda + - MYSQL_USER=pda + - MYSQL_PASSWORD=qHZDZ26fPqqCfKZtWkQ9 + + phpmyadmin: + image: phpmyadmin/phpmyadmin:5.1 + depends_on: ['mysql'] + restart: unless-stopped + environment: + - PMA_HOST=mysql + - PMA_PORT=3306 + ports: + - 8082:80 + + powerdns-admin: + image: ngoduykhanh/powerdns-admin:latest + depends_on: [ 'mysql' ] + restart: unless-stopped + environment: + - PDA_BIND_ADDRESS=0.0.0.0 + - PDA_BIND_PORT=80 + - PDA_DEBUG=true + - PDA_LOAD_ENV_FILES=true + - PDA_AUTOINIT_ENABLED=true + - PDA_AUTOINIT_FORCE=false + - PDA_CHECK_API_ENABLED=true + - PDA_CHECK_API_FAIL_DELAY=2 + - PDA_CHECK_API_SUCCESS_DELAY=0 + - PDA_CHECK_API_ATTEMPTS=15 + - PDA_GUNICORN_ENABLED=false + - PDA_GUNICORN_TIMEOUT=2 + - PDA_GUNICORN_WORKERS=2 + - PDA_GUNICORN_LOGLEVEL=debug + - PDA_PDNS_API_KEY=scjfNRN4Ch2DBuhk24vr + - PDA_PDNS_VERSION=4.5.2 + - PDA_PDNS_PROTO=http + - PDA_PDNS_HOST=pdns + - PDA_PDNS_PORT=8081 + - PDA_ADMIN_USER_USERNAME=nocadmin + - PDA_ADMIN_USER_PASSWORD=clink henchmen mete coracle uphold + - PDAC_SALT=ntR8BmMQb7MKTJ5YrgQwZr3pxU3uLG + - PDAC_SECRET_KEY=eJ92QLuennVStHTj5FVwxz7m4r3Ng3 + - PDAC_SIGNUP_ENABLED=true + - PDAC_HSTS_ENABLED=false + - PDAC_OFFLINE_MODE=false + - PDAC_FILESYSTEM_SESSIONS_ENABLED=false + - PDAC_SQLALCHEMY_DATABASE_URI=sqlite:////srv/app/pdns.db + - PDAC_SAML_ENABLED=false + - SQLALCHEMY_TRACK_MODIFICATIONS=true + - FLASK_ENV=development + ports: + - "8080:80" + volumes: + - /PATH/TO/YOUR/GIT/PROJECT/ROOT:/srv/app + + pdns: + image: azoriansolutions/powerdns-nameserver:4.5.2-alpine-3.14-mysql + depends_on: ['powerdns-admin'] + restart: unless-stopped + environment: + - AS_MYSQL_CHECK_RETRY=10 + - AS_MYSQL_CHECK_INTERVAL=4 + - PDNS_launch=gmysql + - PDNS_gmysql_host=mysql + - PDNS_gmysql_port=3306 + - PDNS_gmysql_user=root + - PDNS_gmysql_password=qHZDZ26fPqqCfKZtWkQ9 + - PDNS_gmysql_dbname=pda + - PDNS_gmysql_dnssec=yes + - PDNS_default_soa_content=pdns noc.@ 0 10800 3600 604800 3600 + - PDNS_default_ttl=3600 + - PDNS_local_address=0.0.0.0 + - PDNS_local_port=53 + - PDNS_api=yes + - PDNS_api_key=scjfNRN4Ch2DBuhk24vr + - PDNS_webserver=yes + - PDNS_webserver_address=0.0.0.0 + - PDNS_webserver_allow_from=0.0.0.0/0 + - PDNS_any_to_tcp=yes + - PDNS_master=no + ports: + - "5053:53/tcp" + - "5053:53/udp" + - "8081:8081/tcp" \ No newline at end of file diff --git a/docker/stacks/production/mysql/docker-compose.yml b/docker/stacks/production/mysql/docker-compose.yml new file mode 100644 index 0000000..1beb727 --- /dev/null +++ b/docker/stacks/production/mysql/docker-compose.yml @@ -0,0 +1,40 @@ +version: "3.3" +services: + mysql: + image: mariadb:10.6 + restart: unless-stopped + environment: + - MYSQL_ROOT_PASSWORD=qHZDZ26fPqqCfKZtWkQ9 + - MYSQL_DATABASE=pda + - MYSQL_USER=pda + - MYSQL_PASSWORD=qHZDZ26fPqqCfKZtWkQ9 + powerdns-admin: + image: ngoduykhanh/powerdns-admin:0.2.4 + depends_on: ['mysql'] + restart: unless-stopped + environment: + - PDA_CHECK_MYSQL_ENABLED=true + - PDA_CHECK_API_ENABLED=true + - PDA_GUNICORN_ENABLED=true + - PDA_GUNICORN_TIMEOUT=2 + - PDA_GUNICORN_WORKERS=10 + - PDA_GUNICORN_LOGLEVEL=info + - PDA_PDNS_API_KEY=YOUR-PDNS-API-SERVER-API-KEY + - PDA_PDNS_VERSION=YOUR-PDNS-API-SERVER-VERSION + - PDA_PDNS_PROTO=YOUR-PDNS-API-SERVER-HTTP-PROTOCOL + - PDA_PDNS_HOST=YOUR-PDNS-API-SERVER-HOSTNAME + - PDA_PDNS_PORT=YOUR-PDNS-API-SERVER-PORT + - PDAC_SALT=ntR8BmMQb7MKTJ5YrgQwZr3pxU3uLG + - PDAC_SECRET_KEY=eJ92QLuennVStHTj5FVwxz7m4r3Ng3 + - PDAC_SIGNUP_ENABLED=false + - PDAC_HSTS_ENABLED=true + - PDAC_SQLA_DB_HOST=mysql + - PDAC_SQLA_DB_NAME=pda + - PDAC_SQLA_DB_USER=root + - PDAC_SQLA_DB_PASSWORD=qHZDZ26fPqqCfKZtWkQ9 + - PDAC_SQLA_DB_PORT=3306 + - PDAC_SAML_ENABLED=false + - SQLALCHEMY_TRACK_MODIFICATIONS=false + - FLASK_ENV=production + ports: + - "8080:80" \ No newline at end of file diff --git a/docker/stacks/production/sqlite/docker-compose.yml b/docker/stacks/production/sqlite/docker-compose.yml new file mode 100644 index 0000000..f846f20 --- /dev/null +++ b/docker/stacks/production/sqlite/docker-compose.yml @@ -0,0 +1,26 @@ +version: "3.3" +services: + powerdns-admin: + image: ngoduykhanh/powerdns-admin:0.2.4 + restart: unless-stopped + environment: + - PDA_CHECK_API_ENABLED=true + - PDA_GUNICORN_ENABLED=true + - PDA_GUNICORN_TIMEOUT=2 + - PDA_GUNICORN_WORKERS=10 + - PDA_GUNICORN_LOGLEVEL=info + - PDA_PDNS_API_KEY=YOUR-PDNS-API-SERVER-API-KEY + - PDA_PDNS_VERSION=YOUR-PDNS-API-SERVER-VERSION + - PDA_PDNS_PROTO=YOUR-PDNS-API-SERVER-HTTP-PROTOCOL + - PDA_PDNS_HOST=YOUR-PDNS-API-SERVER-HOSTNAME + - PDA_PDNS_PORT=YOUR-PDNS-API-SERVER-PORT + - PDAC_SALT=ntR8BmMQb7MKTJ5YrgQwZr3pxU3uLG + - PDAC_SECRET_KEY=eJ92QLuennVStHTj5FVwxz7m4r3Ng3 + - PDAC_SIGNUP_ENABLED=false + - PDAC_HSTS_ENABLED=true + - PDAC_SQLALCHEMY_DATABASE_URI=sqlite:////srv/app/pdns.db + - PDAC_SAML_ENABLED=false + - SQLALCHEMY_TRACK_MODIFICATIONS=false + - FLASK_ENV=production + ports: + - "8080:80" \ No newline at end of file diff --git a/docs/docker.md b/docs/docker.md new file mode 100644 index 0000000..65f62ed --- /dev/null +++ b/docs/docker.md @@ -0,0 +1,142 @@ +# PowerDNS-Admin packaged by [Azorian Solutions](https://azorian.solutions) + +The PowerDNS-Admin is a simple web GUI for managing zone configurations of a PowerDNS Authoritative server. + +The PowerDNS-Admin app does NOT modify the PowerDNS Authoritative server database directly. Instead, it communicates with the PDNS server via the built-in HTTP API. + +The app does have a database for identity management, access control, and caching which can be hosted in either MySQL or SQLite. + +- [PowerDNS-Admin GitHub](https://github.com/ngoduykhanh/PowerDNS-Admin) +- [PowerDNS-Admin Settings](https://github.com/ngoduykhanh/PowerDNS-Admin/blob/master/docs/settings.md) +- [PowerDNS-Admin Wiki](https://github.com/ngoduykhanh/PowerDNS-Admin/wiki) + +## Quick reference + +- **Maintained by:** [Matt Scott](https://github.com/AzorianSolutions) +- **Github:** [https://github.com/AzorianSolutions](https://github.com/AzorianSolutions) +- **Website:** [https://azorian.solutions](https://azorian.solutions) + +## TL;DR + + docker run -d -p 8080:80 -e PDAC_SIGNUP_ENABLED=true ngoduykhanh/powerdns-nameserver + +## [Azorian Solutions](https://azorian.solutions) Docker image strategy + +The goal of creating this image and others alike is to provide a fairly uniform and turn-key implementation for a chosen set of products and solutions. By compiling the server binaries from source code, a greater chain of security is maintained by eliminating unnecessary trusts. This approach also helps assure support of specific features that may otherwise vary from distribution to distribution. A secondary goal of creating this image and others alike is to provide at least two Linux distribution options for any supported product or solution which is why you will often see tags for both Alpine Linux and Debian Linux. + +All documentation will be written with the assumption that you are already reasonably familiar with this ecosystem. This includes container concepts, the Docker ecosystem, and more specifically the product or solution that you are deploying. Simply put, I won't be fluffing the docs with content explaining every detail of what is presented. + +## Supported tags + +\* denotes an image that is planned but has not yet been released. + +### Alpine Linux + +- main, latest +- 0.2.4-alpine, alpine, 0.2.4 + +### Debian Linux + +- 0.2.4-debian, debian + +## Deploying this image + +### App configuration + +Configuration of the PowerDNS Admin app may be achieved through a mixture of two approaches. With either approach you choose, you will need to be aware of the various settings available for the app. + +[PowerDNS Admin Settings](https://github.com/ngoduykhanh/PowerDNS-Admin/blob/master/docs/settings.md) + +#### Approach #1 + +You may pass app settings as environment variables to the container. These environment variables will be automatically inserted into the app.config object. Any environment variable that begins with PDA_ or PDAC_ and is entirely capitalized, will be inserted into the app.config object. + +**One key difference** between the two variable prefixes is that variables prefixed with PDAC_ will have the PDAC_ prefix removed before being inserted into the app.config object. + +For example, say you pass the environment variable PDA_BIND_ADDRESS with the value 0.0.0.0 to the container. + + PDA_BIND_ADDRESS=0.0.0.0 + +This will create a key named PDA_BIND_ADDRESS in app.config with the value of the environment variable value of 0.0.0.0. + +Similar to how secrets work for Docker Swarm containers, you may append _FILE to your environment variables to automatically load the contents of the given file path into an environment variable. + +**One key difference** here from Docker Swarm secrets is that you provide an absolute file path to any path of our choosing. Also, once the contents of the file are loaded, an environment variable of the same name without the trailing _FILE is created with the contents of the loaded file as the value. The original environment variable with the trailing _FILE is removed. + +The following example illustrates a scenario where you want to set the PDA_PDNS_API_KEY app setting in a secure fashion. You have the secret API key stored at /run/secrets/PDA_PDNS_API_KEY. + + PDA_PDNS_API_KEY_FILE=/run/secrets/PDA_PDNS_API_KEY + +This would be the equivalent of running: + + export PDA_PDNS_API_KEY=CONTENTS_OF_SECRET_FILE + +#### Approach #2 + +With this approach, you may create traditional PowerDNS Admin config files and map them to an accessible location inside the container. Then you can pass an environment variable to indicate where to load the file from. + +For example, say you have mapped a python configuration file to /srv/app/configs/my-config.py in the container. You would add the following environment variable to your deployment configuration: + + FLASK_CONF=/srv/app/configs/my-config.py + +**!!! NOTICE !!!** - There is a very specific order in which different sources are loaded. Take careful notice to avoid confusion! + +- The config file **/srv/app/configs/default.py** is loaded. +- The **FLASK_CONF** environment variable is loaded if configured. +- Any instance specific configuration provided via code instantiation is loaded. +- Settings are loaded individually from any environment variables present. +- If all required MySQL settings are present in the app.config object and no SQLALCHEMY_DATABASE_URI app setting has been configured, the app.config setting will be automatically created from the provided MySQL settings. + - Otherwise, the SQLALCHEMY_DATABASE_URI app setting is set to "sqlite:////srv/app/pdns.db" + +### Deploy with Docker Run + +To run a simple container on Docker with this image, execute the following Docker command; + + docker run -d -p 8080:80 -e PDAC_SIGNUP_ENABLED=true ngoduykhanh/powerdns-nameserver + +### Deploy with Docker Compose + +To run this image using Docker Compose, create a YAML file in a place of your choosing and add the following contents to it; + + version: "3.3" + services: + powerdns-admin: + image: ngoduykhanh/powerdns-admin + restart: unless-stopped + environment: + - PDAC_SALT=ntR8BmMQb7MKTJ5YrgQwZr3pxU3uLG + - PDAC_SECRET_KEY=eJ92QLuennVStHTj5FVwxz7m4r3Ng3 + ports: + - "8080:80" + +Then execute the following Docker Compose command; + + docker-compose -f /path/to/yaml/file.yml up + +**!!! NOTICE !!!** - Make sure to set the environment variable `PDAC_SECRET_KEY` to a long random string ([see here for additional information](https://flask.palletsprojects.com/en/1.1.x/config/#SECRET_KEY)) + +## Building this image + +If you want to build this image yourself, you can easily do so using the **docker/bin/build-image** command I have included. + +The build-image command has the following parameter format; + + build-image IMAGE_TAG_NAME APP_VERSION DISTRO_NAME DISTRO_TAG + +So for example, to build the PowerDNS Admin app version 0.2.3 on Alpine Linux 3.14, you would execute the following shell command: + + build-image 0.2.3-alpine-3.14 0.2.3 alpine 3.14 + +The build-image command assumes the following parameter defaults; + +- Image Tag Name: latest +- App Version: 0.2.4 +- Distro Name: alpine +- Distro Tag: 3.14 + +This means that running the build-image command with no parameters would be the equivalent of executing the following shell command: + + build-image latest 0.2.4 alpine 3.14 + +When the image is tagged during compilation, the repository portion of the image tag is derived from the contents of the docker/repo.cfg file and the tag from the first parameter provided to the build-image command. + diff --git a/docs/settings.md b/docs/settings.md new file mode 100644 index 0000000..5a935f2 --- /dev/null +++ b/docs/settings.md @@ -0,0 +1,426 @@ +# PowerDNS-Admin Settings + +The app has a plethora of settings that may be configured through a number of methods. Unfortunately, at the current state the app does not use a completely uniform naming scheme across configuration methods. + +Don't worry though! A strong standard is quickly being implemented with full backwards compatibility so this problem should disappear quickly. + +Let's break the application settings keys down into two primary categories, docker only and app settings. Below you will find a detailed description of each setting and how to use it. + +## Docker Only Settings + +\+ denotes a setting that is planned but not yet implemented. + +#### PDA_UID = 1000 + +This setting controls what user ID is used for various container setup processes during image building. This setting is useful if you're mounting a host path over the /srv/app volume and need to match your host file system permissions. + +#### PDA_USER = pda + +This setting controls what username is used for various container setup processes during image building. This setting is useful if you're mounting a host path over the /srv/app volume and need to match your host file system permissions. + +#### PDA_GID = 1000 + +This setting controls what group ID is used for various container setup processes during image building. This setting is useful if you're mounting a host path over the /srv/app volume and need to match your host file system permissions. + +#### PDA_GROUP = pda + +This setting controls what group name is used for various container setup processes during image building. This setting is useful if you're mounting a host path over the /srv/app volume and need to match your host file system permissions. + +## App Settings +\* denotes a legacy setting key that will be deprecated. + +\+ denotes a setting that is planned but not yet implemented. + +**!COMPUTED!** denotes a default setting value that is computed dynamically at the time it is set. + +#### PDA_BIND_ADDRESS = 0.0.0.0 +The container interface IP mask to bind both gunicorn and the built-in web server to. + +#### PDA_BIND_PORT = 80 +The container port to bind both gunicorn and the built-in web server to. + +#### PDA_DEBUG = false +The debug mode setting of the app. + +#### PDA_LOAD_ENV_FILES = true +Whether to automatically convert environment variables beginning with PDA_ and ending with _FILE. + +#### PDA_AUTOINIT_ENABLED = true +Whether the automatic first-time initialization feature is enabled. + +#### PDA_AUTOINIT_FORCE = false +Whether to force execution of the first-time initialization feature. + +#### PDA_CHECK_MYSQL_ENABLED = false +Whether to delay container startup until the configured MySQL server is online. + +#### PDA_CHECK_MYSQL_FAIL_DELAY = 2 +How many seconds to wait after a MySQL connection attempt failure before trying again. + +#### PDA_CHECK_MYSQL_SUCCESS_DELAY = 0 +How many seconds to wait after a successful MySQL connection attempt before proceeding to the next step. + +#### PDA_CHECK_MYSQL_ATTEMPTS = 30 +How many MySQL connection attempts should be made before halting container execution. + +#### PDA_CHECK_API_ENABLED = false +Whether to delay container startup until the configured PDNS API key passes authorization to the server. + +#### PDA_CHECK_API_FAIL_DELAY = 2 +How many seconds to wait after an API connection attempt failure before trying again. + +#### PDA_CHECK_API_SUCCESS_DELAY = 0 +How many seconds to wait after an API successful connection attempt before proceeding to the next step. + +#### PDA_CHECK_API_ATTEMPTS = 15 +How many API connection attempts should be made before halting container execution. + +#### PDA_CHECK_PYTEST_ENABLED = false +Whether to run the app's Python test unit before starting the primary container process. + +#### PDA_GUNICORN_ENABLED = false +Whether to enable the use of gunicorn. + +#### PDA_GUNICORN_TIMEOUT = 120 +The request timeout in seconds for a gunicorn worker. + +#### PDA_GUNICORN_WORKERS = 4 +The total number of gunicorn worker threads to spawn. + +#### PDA_GUNICORN_LOGLEVEL = info +The log output level of the gunicorn process. + +#### PDA_GUNICORN_ARGS = !COMPUTED! +The additional gunicorn arguments that will be appended to the startup command when gunicorn is enabled. + +#### PDA_PDNS_API_KEY = CHANGE_ME! +The API key for the associated PowerDNS Authoritative server API. + +#### PDA_PDNS_API_VERSION = 4.5.2 +The API version of the associated PowerDNS Authoritative server API. + +#### PDA_PDNS_API_PROTO = http +The HTTP protocol for the associated PowerDNS Authoritative server API. + +#### PDA_PDNS_API_HOST = 127.0.0.1 +The hostname or IP address of the associated PowerDNS Authoritative server API. + +#### PDA_PDNS_API_PORT = 8081 +The port of the associated PowerDNS Authoritative server API. + +#### + PDA_ADMIN_USER_USERNAME +The username to use for automatic admin account setup on startup. + +#### + PDA_ADMIN_USER_PASSWORD +The password to use for automatic admin account setup on startup. + +#### * FLASK_ENV = production +The Flask app environment classification. + +**!!! NOTICE !!!** - Setting this to development carries implicit behaviors such as automatically setting the FLASK_DEBUG setting to true. + +#### * FLASK_DEBUG = false +The Flask app debug mode setting. + +**!!! NOTICE !!!** - Enabling debug mode will cause the debugger to run during app initialization. + +#### * FLASK_APP = /srv/app/run.py +The Flask Python file that should be treated as the main app file. + +#### * FLASK_CONF +The Flask Python config file path that should be loaded during app initialization. + +#### * PDNS_ADMIN_LOG_LEVEL = WARNING +The log output level that the app logging mechanism will use. + +#### * LOG_LEVEL = ? +TODO: Find out where this is used and what it's default value is + +#### * BIND_ADDRESS = 127.0.0.1 +Refer to the PDA_BIND_ADDRESS setting documentation. + +#### * PORT = 9191 +Refer to the PDA_BIND_PORT setting documentation. + +#### * DEBUG_MODE = false +Refer to the PDA_DEBUG setting documentation. + +#### * SALT = ? +TODO: Find out where this is used and what it's default value is + +TODO: Reassign to PDA_ scoped key + +#### * SECRET_KEY = ? +This should be set to a long, random string. [[see here for additional information]](https://flask.palletsprojects.com/en/1.1.x/config/#SECRET_KEY) + +TODO: Reassign to PDA_ scoped key + +#### * SIGNUP_ENABLED = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * HSTS_ENABLED = false +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * OFFLINE_MODE = false +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * FILE_SYSTEM_SESSIONS_ENABLED = false +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * LOCAL_DB_ENABLED = false +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * LDAP_ENABLED = false +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SQLA_DB_USER = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SQLA_DB_PASSWORD = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SQLA_DB_HOST = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SQLA_DB_PORT = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SQLA_DB_NAME = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SQLALCHEMY_DATABASE_URI = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SQLALCHEMY_TRACK_MODIFICATIONS = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * MAIL_SERVER = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * MAIL_PORT = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * MAIL_DEBUG = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * MAIL_USE_TLS = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * MAIL_USE_SSL = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * MAIL_USERNAME = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * MAIL_PASSWORD = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * MAIL_DEFAULT_SENDER = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * REMOTE_USER_LOGOUT_URL = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * REMOTE_USER_COOKIES = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * OIDC_OAUTH_API_URL = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * OIDC_OAUTH_TOKEN_URL = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * OIDC_OAUTH_AUTHORIZE_URL = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SAML_ENABLED = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SAML_DEBUG = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SAML_PATH = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SAML_ASSERTION_ENCRYPTED = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SAML_METADATA_URL = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SAML_METADATA_CACHE_LIFETIME = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SAML_IDP_SSO_BINDING = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SAML_IDP_ENTITY_ID = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SAML_NAMEID_FORMAT = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SAML_ATTRIBUTE_EMAIL = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SAML_ATTRIBUTE_GIVENNAME = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SAML_ATTRIBUTE_SURNAME = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SAML_ATTRIBUTE_NAME = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SAML_ATTRIBUTE_USERNAME = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SAML_ATTRIBUTE_ADMIN = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SAML_ATTRIBUTE_GROUP = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SAML_CERT = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SAML_KEY = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SAML_GROUP_ADMIN_NAME = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SAML_GROUP_TO_ACCOUNT_MAPPING = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SAML_ATTRIBUTE_ACCOUNT = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SAML_SP_ENTITY_ID = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SAML_SP_CONTACT_NAME = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SAML_SP_CONTACT_MAIL = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SAML_SIGN_REQUEST = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SAML_WANT_MESSAGE_SIGNED = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SAML_LOGOUT = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key + +#### * SAML_LOGOUT_URL = ? +TODO: Complete the setting description + +TODO: Reassign to PDA_ scoped key diff --git a/package.json b/package.json index 76982c8..69a984b 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "bootstrap-datepicker": "^1.8.0", "bootstrap-validator": "^0.11.9", "datatables.net-plugins": "^1.10.19", + "font-awesome": "^4.7.0", "icheck": "^1.0.2", "jquery-slimscroll": "^1.3.8", "jquery-ui-dist": "^1.12.1", diff --git a/powerdnsadmin/__init__.py b/powerdnsadmin/__init__.py index 98690c2..fe3a068 100755 --- a/powerdnsadmin/__init__.py +++ b/powerdnsadmin/__init__.py @@ -9,11 +9,17 @@ from flask_session import Session from .lib import utils -def create_app(config=None): +def create_app(config=None, other=''): + import urllib from . import models, routes, services from .assets import assets + from .lib import config_util app = Flask(__name__) + ######################################## + # LOGGING SETUP + ######################################## + # Read log level from environment variable log_level_name = os.environ.get('PDNS_ADMIN_LOG_LEVEL', 'WARNING') log_level = logging.getLevelName(log_level_name.upper()) @@ -23,17 +29,18 @@ def create_app(config=None): format= "[%(asctime)s] [%(filename)s:%(lineno)d] %(levelname)s - %(message)s") - # If we use Docker + Gunicorn, adjust the - # log handler - if "GUNICORN_LOGLEVEL" in os.environ: + # Reconfigure the log handler if gunicorn is being ran in Docker + if "PDA_GUNICORN_ENABLED" in os.environ and bool(os.environ['PDA_GUNICORN_ENABLED']): gunicorn_logger = logging.getLogger("gunicorn.error") app.logger.handlers = gunicorn_logger.handlers app.logger.setLevel(gunicorn_logger.level) - # Proxy + # Proxy Support app.wsgi_app = ProxyFix(app.wsgi_app) - # CSRF protection + ######################################## + # CSRF Protection Setup + ######################################## csrf = SeaSurf(app) csrf.exempt(routes.index.dyndns_checkip) csrf.exempt(routes.index.dyndns_update) @@ -56,38 +63,71 @@ def create_app(config=None): csrf.exempt(routes.api.api_add_account_user) csrf.exempt(routes.api.api_remove_account_user) - # Load config from env variables if using docker - if os.path.exists(os.path.join(app.root_path, 'docker_config.py')): - app.config.from_object('powerdnsadmin.docker_config') - else: - # Load default configuration - app.config.from_object('powerdnsadmin.default_config') + ######################################## + # CONFIGURATION SETUP + ######################################## - # Load config file from FLASK_CONF env variable + # Load default configuration + app.config.from_object('configs.default') + + # Load config file from path given in FLASK_CONF env variable if 'FLASK_CONF' in os.environ: app.config.from_envvar('FLASK_CONF') - # Load app sepecified configuration + # Load instance specific configuration if config is not None: if isinstance(config, dict): app.config.update(config) elif config.endswith('.py'): app.config.from_pyfile(config) + # Load configuration from environment variables + config_util.load_config_from_env(app) + + # If no SQLA DB URI is set but SQLA MySQL settings are present, then create DB URI setting + if 'SQLALCHEMY_DATABASE_URI' not in app.config and 'SQLA_DB_USER' in app.config \ + and 'SQLA_DB_PASSWORD' in app.config and 'SQLA_DB_HOST' in app.config and 'SQLA_DB_PORT' in app.config \ + and 'SQLA_DB_NAME' in app.config: + app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql://{}:{}@{}:{}/{}'.format( + urllib.parse.quote_plus(app.config.get('SQLA_DB_USER')), + urllib.parse.quote_plus(app.config.get('SQLA_DB_PASSWORD')), + app.config.get('SQLA_DB_HOST'), + app.config.get('SQLA_DB_PORT'), + app.config.get('SQLA_DB_NAME') + ) + else: + app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////srv/app/pdns.db' + + ######################################## + # SSL SETUP + ######################################## + # HSTS if app.config.get('HSTS_ENABLED'): from flask_sslify import SSLify _sslify = SSLify(app) # lgtm [py/unused-local-variable] + ######################################## + # SESSION SETUP + ######################################## + # Load Flask-Session if app.config.get('FILESYSTEM_SESSIONS_ENABLED'): app.config['SESSION_TYPE'] = 'filesystem' sess = Session() sess.init_app(app) + ######################################## + # MAIL SETUP + ######################################## + # SMTP app.mail = Mail(app) + ######################################## + # FLASK APP SETUP + ######################################## + # Load app's components assets.init_app(app) models.init_app(app) diff --git a/powerdnsadmin/default_config.py b/powerdnsadmin/default_config.py deleted file mode 100644 index 361c6d0..0000000 --- a/powerdnsadmin/default_config.py +++ /dev/null @@ -1,34 +0,0 @@ -import os -import urllib.parse -basedir = os.path.abspath(os.path.dirname(__file__)) - -### BASIC APP CONFIG -SALT = '$2b$12$yLUMTIfl21FKJQpTkRQXCu' -SECRET_KEY = 'e951e5a1f4b94151b360f47edf596dd2' -BIND_ADDRESS = '0.0.0.0' -PORT = 9191 -HSTS_ENABLED = False -OFFLINE_MODE = False -FILESYSTEM_SESSIONS_ENABLED = False - -### DATABASE CONFIG -SQLA_DB_USER = 'pda' -SQLA_DB_PASSWORD = 'qHZDZ26fPqqCfKZtWkQ9' -SQLA_DB_HOST = 'mysql' -SQLA_DB_NAME = 'pda' -SQLALCHEMY_TRACK_MODIFICATIONS = True - -### DATABASE - MySQL -SQLALCHEMY_DATABASE_URI = 'mysql://{}:{}@{}/{}'.format( - urllib.parse.quote_plus(SQLA_DB_USER), - urllib.parse.quote_plus(SQLA_DB_PASSWORD), - SQLA_DB_HOST, - SQLA_DB_NAME -) - -### DATABASE - SQLite -# SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'pdns.db') - -# SAML Authnetication -SAML_ENABLED = False -SAML_ASSERTION_ENCRYPTED = True diff --git a/powerdnsadmin/env/boolean.env b/powerdnsadmin/env/boolean.env new file mode 100644 index 0000000..73a3bf6 --- /dev/null +++ b/powerdnsadmin/env/boolean.env @@ -0,0 +1,26 @@ +PDA_DEBUG +PDA_GUNICORN_ENABLED +PDA_LOAD_ENV_FILES +PDA_AUTOINIT_ENABLED +PDA_AUTOINIT_FORCE +PDA_CHECK_MYSQL_ENABLED +PDA_CHECK_API_ENABLED +PDA_CHECK_PYTEST_ENABLED +DEBUG_MODE +SIGNUP_ENABLED +HSTS_ENABLED +OFFLINE_MODE +FILESYSTEM_SESSIONS_ENABLED +SQLALCHEMY_TRACK_MODIFICATIONS +LOCAL_DB_ENABLED +REMOTE_USER_ENABLED +LDAP_ENABLED +MAIL_DEBUG +MAIL_USE_TLS +MAIL_USE_SSL +SAML_ENABLED +SAML_DEBUG +SAML_SIGN_REQUEST +SAML_WANT_MESSAGE_SIGNED +SAML_LOGOUT +SAML_ASSERTION_ENCRYPTED \ No newline at end of file diff --git a/powerdnsadmin/env/default.env b/powerdnsadmin/env/default.env new file mode 100644 index 0000000..2e3a7a9 --- /dev/null +++ b/powerdnsadmin/env/default.env @@ -0,0 +1,31 @@ +PDA_UID +PDA_USER +PDA_GID +PDA_GROUP +PDA_BIND_ADDRESS +PDA_BIND_PORT +PDA_DEBUG +PDA_LOAD_ENV_FILES +PDA_AUTOINIT_ENABLED +PDA_AUTOINIT_FORCE +PDA_CHECK_MYSQL_ENABLED +PDA_CHECK_MYSQL_FAIL_DELAY +PDA_CHECK_MYSQL_SUCCESS_DELAY +PDA_CHECK_MYSQL_ATTEMPTS +PDA_CHECK_API_ENABLED +PDA_CHECK_API_FAIL_DELAY +PDA_CHECK_API_SUCCESS_DELAY +PDA_CHECK_API_ATTEMPTS +PDA_CHECK_PYTEST_ENABLED +PDA_GUNICORN_ENABLED +PDA_GUNICORN_TIMEOUT +PDA_GUNICORN_WORKERS +PDA_GUNICORN_LOGLEVEL +PDA_GUNICORN_ARGS +PDA_PDNS_API_KEY +PDA_PDNS_API_VERSION +PDA_PDNS_API_PROTO +PDA_PDNS_API_HOST +PDA_PDNS_API_PORT +PDA_ADMIN_USER_USERNAME +PDA_ADMIN_USER_PASSWORD \ No newline at end of file diff --git a/powerdnsadmin/env/discovered.env b/powerdnsadmin/env/discovered.env new file mode 100644 index 0000000..97ff0f6 --- /dev/null +++ b/powerdnsadmin/env/discovered.env @@ -0,0 +1,75 @@ +LC_ALL +LANG +LANGUAGE + +SERVER_NAME +BIND_ADDRESS +PORT +DEBUG_MODE +SALT +SECRET_KEY +SESSION_TYPE +PDNS_ADMIN_LOG_LEVEL + +OFFLINE_MODE +FILESYSTEM_SESSIONS_ENABLED +HSTS_ENABLED + +GUNICORN_LOGLEVEL +GUNICORN_WORKERS +GUNICORN_TIMEOUT +GUNICORN_ARGS + +FLASK_APP +FLASK_ENV +FLASK_CONF + +SQLA_DB_HOST +SQLA_DB_PORT +SQLA_DB_USER +SQLA_DB_PASSWORD +SQLA_DB_NAME +SQLALCHEMY_TRACK_MODIFICATIONS +SQLALCHEMY_DATABASE_URI + +MAIL_SERVER +MAIL_PORT +MAIL_DEBUG +MAIL_USE_TLS +MAIL_USE_SSL +MAIL_USERNAME +MAIL_PASSWORD +MAIL_DEFAULT_SENDER + +SAML_ENABLED +SAML_DEBUG +SAML_PATH +SAML_METADATA_URL +SAML_METADATA_CACHE_LIFETIME +SAML_IDP_SSO_BINDING +SAML_IDP_ENTITY_ID +SAML_NAMEID_FORMAT +SAML_SP_REQUESTED_ATTRIBUTES +SAML_ASSERTION_ENCRYPTED + +SAML_ATTRIBUTE_EMAIL +SAML_ATTRIBUTE_GIVENNAME +SAML_ATTRIBUTE_SURNAME +SAML_ATTRIBUTE_USERNAME +SAML_ATTRIBUTE_ADMIN +SAML_ATTRIBUTE_ACCOUNT + +SAML_SP_ENTITY_ID +SAML_SP_CONTACT_NAME +SAML_SP_CONTACT_MAIL + +SAML_CERT_FILE +SAML_CERT_KEY +SAML_SIGN_REQUEST + +SAML_LOGOUT +SAML_LOGOUT_URL + +REMOTE_USER_ENABLED +REMOTE_USER_LOGOUT_URL +REMOTE_USER_COOKIES diff --git a/powerdnsadmin/env/integer.env b/powerdnsadmin/env/integer.env new file mode 100644 index 0000000..9665a41 --- /dev/null +++ b/powerdnsadmin/env/integer.env @@ -0,0 +1,8 @@ +PDA_UID +PDA_GID +PDA_BIND_PORT +PDA_PDNS_PORT +PORT +SQLA_DB_PORT +MAIL_PORT +SAML_METADATA_CACHE_LIFETIME \ No newline at end of file diff --git a/powerdnsadmin/env/legacy.env b/powerdnsadmin/env/legacy.env new file mode 100644 index 0000000..181110f --- /dev/null +++ b/powerdnsadmin/env/legacy.env @@ -0,0 +1,61 @@ +PDNS_ADMIN_LOG_LEVEL +LOG_LEVEL +BIND_ADDRESS +PORT +DEBUG_MODE +SALT +SECRET_KEY +SIGNUP_ENABLED +HSTS_ENABLED +OFFLINE_MODE +FILESYSTEM_SESSIONS_ENABLED +LOCAL_DB_ENABLED +LDAP_ENABLED +SQLA_DB_USER +SQLA_DB_PASSWORD +SQLA_DB_HOST +SQLA_DB_PORT +SQLA_DB_NAME +SQLALCHEMY_DATABASE_URI +SQLALCHEMY_TRACK_MODIFICATIONS +MAIL_SERVER +MAIL_PORT +MAIL_DEBUG +MAIL_USE_TLS +MAIL_USE_SSL +MAIL_USERNAME +MAIL_PASSWORD +MAIL_DEFAULT_SENDER +REMOTE_USER_LOGOUT_URL +REMOTE_USER_COOKIES +OIDC_OAUTH_API_URL +OIDC_OAUTH_TOKEN_URL +OIDC_OAUTH_AUTHORIZE_URL +SAML_ENABLED +SAML_DEBUG +SAML_PATH +SAML_ASSERTION_ENCRYPTED +SAML_METADATA_URL +SAML_METADATA_CACHE_LIFETIME +SAML_IDP_SSO_BINDING +SAML_IDP_ENTITY_ID +SAML_NAMEID_FORMAT +SAML_ATTRIBUTE_EMAIL +SAML_ATTRIBUTE_GIVENNAME +SAML_ATTRIBUTE_SURNAME +SAML_ATTRIBUTE_NAME +SAML_ATTRIBUTE_USERNAME +SAML_ATTRIBUTE_ADMIN +SAML_ATTRIBUTE_GROUP +SAML_CERT +SAML_KEY +SAML_GROUP_ADMIN_NAME +SAML_GROUP_TO_ACCOUNT_MAPPING +SAML_ATTRIBUTE_ACCOUNT +SAML_SP_ENTITY_ID +SAML_SP_CONTACT_NAME +SAML_SP_CONTACT_MAIL +SAML_SIGN_REQUEST +SAML_WANT_MESSAGE_SIGNED +SAML_LOGOUT +SAML_LOGOUT_URL \ No newline at end of file diff --git a/powerdnsadmin/lib/config_util.py b/powerdnsadmin/lib/config_util.py new file mode 100644 index 0000000..942f9b2 --- /dev/null +++ b/powerdnsadmin/lib/config_util.py @@ -0,0 +1,55 @@ +import os + + +def str2bool(v): + return v.lower() in ("true", "yes", "1") + + +def load_env_file(file_name='legacy'): + f = open('/srv/app/powerdnsadmin/env/' + file_name + '.env', 'r') + lines = f.read().splitlines() + f.close() + return lines + + +def load_config_from_env(app): + legacy_vars = load_env_file('legacy') + boolean_vars = load_env_file('boolean') + integer_vars = load_env_file('integer') + config = {} + + for key in os.environ: + config_key = None + val = None + + if (key.startswith('PDA_') or key in legacy_vars) and not key.endswith('_FILE'): + config_key = key + val = os.environ[key] + + if key.startswith('PDAC_') and not key.endswith('_FILE'): + config_key = key[5:] + val = os.environ[key] + + if key.endswith('_FILE'): + config_key = key[:-5] + + if key.startswith('PDAC_'): + config_key = config_key[5:] + + if key.startswith('PDAC_') or config_key in legacy_vars: + with open(os.environ[key]) as f: + val = f.read() + f.close() + + if val is not None: + if config_key in boolean_vars: + val = str2bool(val) + if config_key in integer_vars: + val = int(val) + + if config_key is not None: + config[config_key] = val + + if isinstance(config, dict): + app.config.update(config) + diff --git a/run.py b/run.py index b9e8e02..1fafc94 100755 --- a/run.py +++ b/run.py @@ -3,8 +3,8 @@ from powerdnsadmin import create_app if __name__ == '__main__': app = create_app() - # TODO: Update the following to source the debug mode setting from either the application configuration or the - # environment variable FLASK_DEBUG. I believe support for the environment variable is already built-in to Flask - # so perhaps this just needs to negate the use of the debug flag if the environment variable is present - app.run(debug=app.config.get('DEBUG_MODE', False), host=app.config.get('BIND_ADDRESS', '0.0.0.0'), - port=app.config.get('PORT', '80')) + app.run( + host=app.config.get('BIND_ADDRESS', app.config.get('PDA_BIND_ADDRESS', '0.0.0.0')), + port=app.config.get('PORT', app.config.get('PDA_BIND_PORT', 80)), + debug=app.config.get('DEBUG_MODE', app.config.get('PDA_DEBUG', False)) + ) From 16b37969ffc0bfc1bfa8a60fb76868a3e7d27f33 Mon Sep 17 00:00:00 2001 From: Matt Scott Date: Wed, 8 Dec 2021 04:08:55 -0500 Subject: [PATCH 4/5] Removed old yarn lock file. Added latest docker app setting to local docker compose stacks. --- _yarn.lock | 1151 ----------------- docker/stacks/local/mysql/docker-compose.yml | 1 + docker/stacks/local/sqlite/docker-compose.yml | 1 + 3 files changed, 2 insertions(+), 1151 deletions(-) delete mode 100644 _yarn.lock diff --git a/_yarn.lock b/_yarn.lock deleted file mode 100644 index e3b95c5..0000000 --- a/_yarn.lock +++ /dev/null @@ -1,1151 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -JSONStream@^1.0.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.3.tgz#27b4b8fbbfeab4e71bcf551e7f27be8d952239bf" - dependencies: - jsonparse "^1.2.0" - through ">=2.2.7 <3" - -acorn-node@^1.2.0, acorn-node@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/acorn-node/-/acorn-node-1.3.0.tgz#5f86d73346743810ef1269b901dbcbded020861b" - dependencies: - acorn "^5.4.1" - xtend "^4.0.1" - -acorn@^4.0.3: - version "4.0.13" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.13.tgz#105495ae5361d697bd195c825192e1ad7f253787" - -acorn@^5.4.1: - version "5.6.2" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.6.2.tgz#b1da1d7be2ac1b4a327fb9eab851702c5045b4e7" - -admin-lte@2.4.9: - version "2.4.9" - resolved "https://registry.yarnpkg.com/admin-lte/-/admin-lte-2.4.9.tgz#effc04dbb587ccb7d674d4cf663cc141f925ed73" - integrity sha512-+u3zt5sWPPT8HmBvRg4F8IGZPaE5wD0K10+IjXoynfe/jEUrMMj+4eA1LGH9fMK6QulmFr1NCtc1Tk+mTgC24A== - dependencies: - bootstrap "^3.4.0" - bootstrap-colorpicker "^2.5.3" - bootstrap-datepicker "^1.8.0" - bootstrap-daterangepicker "^2.1.25" - bootstrap-slider "^9.8.0" - bootstrap-timepicker "^0.5.2" - chart.js "1.0.*" - ckeditor "^4.11.2" - datatables.net "^1.10.19" - datatables.net-bs "^1.10.19" - fastclick "^1.0.6" - flot "^0.8.3" - font-awesome "^4.7.0" - fullcalendar "^3.10.0" - inputmask "^3.3.7" - ion-rangeslider "^2.3.0" - ionicons "^3.0.0" - jquery "^3.2.1" - jquery-knob "^1.2.11" - jquery-sparkline "^2.4.0" - jquery-ui "^1.12.1" - jvectormap "^1.2.2" - moment "^2.24.0" - morris.js "^0.5.0" - pace "0.0.4" - raphael "^2.2.7" - select2 "^4.0.3" - slimscroll "^0.9.1" - -almond@~0.3.1: - version "0.3.3" - resolved "https://registry.yarnpkg.com/almond/-/almond-0.3.3.tgz#a0e7c95ac7624d6417b4494b1e68bff693168a20" - -array-filter@~0.0.0: - version "0.0.1" - resolved "https://registry.yarnpkg.com/array-filter/-/array-filter-0.0.1.tgz#7da8cf2e26628ed732803581fd21f67cacd2eeec" - -array-map@~0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/array-map/-/array-map-0.0.0.tgz#88a2bab73d1cf7bcd5c1b118a003f66f665fa662" - -array-reduce@~0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/array-reduce/-/array-reduce-0.0.0.tgz#173899d3ffd1c7d9383e4479525dbe278cab5f2b" - -asn1.js@^4.0.0: - version "4.10.1" - resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0" - dependencies: - bn.js "^4.0.0" - inherits "^2.0.1" - minimalistic-assert "^1.0.0" - -assert@^1.4.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/assert/-/assert-1.4.1.tgz#99912d591836b5a6f5b345c0f07eefc08fc65d91" - dependencies: - util "0.10.3" - -astw@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/astw/-/astw-2.2.0.tgz#7bd41784d32493987aeb239b6b4e1c57a873b917" - dependencies: - acorn "^4.0.3" - -balanced-match@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" - -base64-js@^1.0.2: - version "1.3.0" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.0.tgz#cab1e6118f051095e58b5281aea8c1cd22bfc0e3" - -bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.11.9: - version "4.12.0" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" - integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== - -bootstrap-colorpicker@^2.5.3: - version "2.5.3" - resolved "https://registry.yarnpkg.com/bootstrap-colorpicker/-/bootstrap-colorpicker-2.5.3.tgz#b50aff8590fbaa6b5aa63a5624e4213f1659a49d" - integrity sha512-xdllX8LSMvKULs3b8JrgRXTvyvjkSMHHHVuHjjN5FNMqr6kRe5NPiMHFmeAFjlgDF73MspikudLuEwR28LbzLw== - dependencies: - jquery ">=1.10" - -bootstrap-datepicker@^1.8.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/bootstrap-datepicker/-/bootstrap-datepicker-1.8.0.tgz#c63513931e6f09f16ae9f11b62f32d950df3958e" - integrity sha512-213St/G8KT3mjs4qu4qwww74KWysMaIeqgq5OhrboZjIjemIpyuxlSo9FNNI5+KzpkkxkRRba+oewiRGV42B1A== - dependencies: - jquery ">=1.7.1 <4.0.0" - -bootstrap-daterangepicker@^2.1.25: - version "2.1.30" - resolved "https://registry.yarnpkg.com/bootstrap-daterangepicker/-/bootstrap-daterangepicker-2.1.30.tgz#f893dbfff5a4d7dfaab75460e8ea6969bb89689a" - dependencies: - jquery ">=1.10" - moment "^2.9.0" - -bootstrap-slider@^9.8.0: - version "9.10.0" - resolved "https://registry.yarnpkg.com/bootstrap-slider/-/bootstrap-slider-9.10.0.tgz#1103d6bc00cfbfa8cfc9a2599ab518c55643da3f" - -bootstrap-timepicker@^0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/bootstrap-timepicker/-/bootstrap-timepicker-0.5.2.tgz#10ed9f2a2f0b8ccaefcde0fcf6a0738b919a3835" - -bootstrap-validator@^0.11.9: - version "0.11.9" - resolved "https://registry.yarnpkg.com/bootstrap-validator/-/bootstrap-validator-0.11.9.tgz#fb7058eef53623e78f5aa7967026f98f875a9404" - -bootstrap@^3.4.0, bootstrap@^3.4.1: - version "3.4.1" - resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-3.4.1.tgz#c3a347d419e289ad11f4033e3c4132b87c081d72" - integrity sha512-yN5oZVmRCwe5aKwzRj6736nSmKDX7pLYwsXiCj/EYmo16hODaBiT4En5btW/jhBF/seV+XMx3aYwukYC3A49DA== - -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -brorand@^1.0.1, brorand@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" - integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= - -browser-pack@^6.0.1: - version "6.1.0" - resolved "https://registry.yarnpkg.com/browser-pack/-/browser-pack-6.1.0.tgz#c34ba10d0b9ce162b5af227c7131c92c2ecd5774" - dependencies: - JSONStream "^1.0.3" - combine-source-map "~0.8.0" - defined "^1.0.0" - safe-buffer "^5.1.1" - through2 "^2.0.0" - umd "^3.0.0" - -browser-resolve@^1.11.0, browser-resolve@^1.7.0: - version "1.11.2" - resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-1.11.2.tgz#8ff09b0a2c421718a1051c260b32e48f442938ce" - dependencies: - resolve "1.1.7" - -browserify-aes@^1.0.0, browserify-aes@^1.0.4: - version "1.2.0" - resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" - dependencies: - buffer-xor "^1.0.3" - cipher-base "^1.0.0" - create-hash "^1.1.0" - evp_bytestokey "^1.0.3" - inherits "^2.0.1" - safe-buffer "^5.0.1" - -browserify-cipher@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.1.tgz#8d6474c1b870bfdabcd3bcfcc1934a10e94f15f0" - dependencies: - browserify-aes "^1.0.4" - browserify-des "^1.0.0" - evp_bytestokey "^1.0.0" - -browserify-des@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.1.tgz#3343124db6d7ad53e26a8826318712bdc8450f9c" - dependencies: - cipher-base "^1.0.1" - des.js "^1.0.0" - inherits "^2.0.1" - -browserify-rsa@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.0.1.tgz#21e0abfaf6f2029cf2fafb133567a701d4135524" - dependencies: - bn.js "^4.1.0" - randombytes "^2.0.1" - -browserify-sign@^4.0.0: - version "4.0.4" - resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.0.4.tgz#aa4eb68e5d7b658baa6bf6a57e630cbd7a93d298" - dependencies: - bn.js "^4.1.1" - browserify-rsa "^4.0.0" - create-hash "^1.1.0" - create-hmac "^1.1.2" - elliptic "^6.0.0" - inherits "^2.0.1" - parse-asn1 "^5.0.0" - -browserify-zlib@~0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz#2869459d9aa3be245fe8fe2ca1f46e2e7f54d73f" - dependencies: - pako "~1.0.5" - -browserify@>=3.46.0: - version "16.2.2" - resolved "https://registry.yarnpkg.com/browserify/-/browserify-16.2.2.tgz#4b1f66ba0e54fa39dbc5aa4be9629142143d91b0" - dependencies: - JSONStream "^1.0.3" - assert "^1.4.0" - browser-pack "^6.0.1" - browser-resolve "^1.11.0" - browserify-zlib "~0.2.0" - buffer "^5.0.2" - cached-path-relative "^1.0.0" - concat-stream "^1.6.0" - console-browserify "^1.1.0" - constants-browserify "~1.0.0" - crypto-browserify "^3.0.0" - defined "^1.0.0" - deps-sort "^2.0.0" - domain-browser "^1.2.0" - duplexer2 "~0.1.2" - events "^2.0.0" - glob "^7.1.0" - has "^1.0.0" - htmlescape "^1.1.0" - https-browserify "^1.0.0" - inherits "~2.0.1" - insert-module-globals "^7.0.0" - labeled-stream-splicer "^2.0.0" - mkdirp "^0.5.0" - module-deps "^6.0.0" - os-browserify "~0.3.0" - parents "^1.0.1" - path-browserify "~0.0.0" - process "~0.11.0" - punycode "^1.3.2" - querystring-es3 "~0.2.0" - read-only-stream "^2.0.0" - readable-stream "^2.0.2" - resolve "^1.1.4" - shasum "^1.0.0" - shell-quote "^1.6.1" - stream-browserify "^2.0.0" - stream-http "^2.0.0" - string_decoder "^1.1.1" - subarg "^1.0.0" - syntax-error "^1.1.1" - through2 "^2.0.0" - timers-browserify "^1.0.1" - tty-browserify "0.0.1" - url "~0.11.0" - util "~0.10.1" - vm-browserify "^1.0.0" - xtend "^4.0.0" - -buffer-from@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.0.tgz#87fcaa3a298358e0ade6e442cfce840740d1ad04" - -buffer-xor@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" - -buffer@^5.0.2: - version "5.1.0" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.1.0.tgz#c913e43678c7cb7c8bd16afbcddb6c5505e8f9fe" - dependencies: - base64-js "^1.0.2" - ieee754 "^1.1.4" - -builtin-status-codes@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" - -cached-path-relative@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/cached-path-relative/-/cached-path-relative-1.0.2.tgz#a13df4196d26776220cc3356eb147a52dba2c6db" - integrity sha512-5r2GqsoEb4qMTTN9J+WzXfjov+hjxT+j3u5K+kIVNIwAd99DLCJE9pBIMP1qVeybV6JiijL385Oz0DcYxfbOIg== - -charm@~0.1.0: - version "0.1.2" - resolved "https://registry.yarnpkg.com/charm/-/charm-0.1.2.tgz#06c21eed1a1b06aeb67553cdc53e23274bac2296" - -chart.js@1.0.*: - version "1.0.2" - resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-1.0.2.tgz#ad57d2229cfd8ccf5955147e8121b4911e69dfe7" - -cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" - dependencies: - inherits "^2.0.1" - safe-buffer "^5.0.1" - -ckeditor@^4.11.2: - version "4.11.3" - resolved "https://registry.yarnpkg.com/ckeditor/-/ckeditor-4.11.3.tgz#91f66d7ddb5bff3874514fe539779686874ed655" - integrity sha512-v6G+v16WAcukKuQH6m+trA9RCOQntFM2nxPO/GFiLYsdD/5IbZAZ2n7GwMxQmxDXpx4AixpnMWF+yAE1t9wq6Q== - -classie@>=0.0.1: - version "1.0.0" - resolved "https://registry.yarnpkg.com/classie/-/classie-1.0.0.tgz#fc9b29b47e64e374a2062fb624d05a61cd703ab2" - -combine-source-map@^0.8.0, combine-source-map@~0.8.0: - version "0.8.0" - resolved "https://registry.yarnpkg.com/combine-source-map/-/combine-source-map-0.8.0.tgz#a58d0df042c186fcf822a8e8015f5450d2d79a8b" - dependencies: - convert-source-map "~1.1.0" - inline-source-map "~0.6.0" - lodash.memoize "~3.0.3" - source-map "~0.5.3" - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - -concat-stream@^1.6.0, concat-stream@^1.6.1, concat-stream@~1.6.0: - version "1.6.2" - resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" - dependencies: - buffer-from "^1.0.0" - inherits "^2.0.3" - readable-stream "^2.2.2" - typedarray "^0.0.6" - -console-browserify@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.1.0.tgz#f0241c45730a9fc6323b206dbf38edc741d0bb10" - dependencies: - date-now "^0.1.4" - -constants-browserify@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" - -convert-source-map@~1.1.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.1.3.tgz#4829c877e9fe49b3161f3bf3673888e204699860" - -core-util-is@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" - -create-ecdh@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.3.tgz#c9111b6f33045c4697f144787f9254cdc77c45ff" - dependencies: - bn.js "^4.1.0" - elliptic "^6.0.0" - -create-hash@^1.1.0, create-hash@^1.1.2: - version "1.2.0" - resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" - dependencies: - cipher-base "^1.0.1" - inherits "^2.0.1" - md5.js "^1.3.4" - ripemd160 "^2.0.1" - sha.js "^2.4.0" - -create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: - version "1.1.7" - resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" - dependencies: - cipher-base "^1.0.3" - create-hash "^1.1.0" - inherits "^2.0.1" - ripemd160 "^2.0.0" - safe-buffer "^5.0.1" - sha.js "^2.4.8" - -crypto-browserify@^3.0.0: - version "3.12.0" - resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" - dependencies: - browserify-cipher "^1.0.0" - browserify-sign "^4.0.0" - create-ecdh "^4.0.0" - create-hash "^1.1.0" - create-hmac "^1.1.0" - diffie-hellman "^5.0.0" - inherits "^2.0.1" - pbkdf2 "^3.0.3" - public-encrypt "^4.0.0" - randombytes "^2.0.0" - randomfill "^1.0.3" - -datatables.net-bs@^1.10.19: - version "1.10.19" - resolved "https://registry.yarnpkg.com/datatables.net-bs/-/datatables.net-bs-1.10.19.tgz#08763b4e4d0cef1a427d019dc15e717c7ed67a4d" - integrity sha512-5gxoI2n+duZP06+4xVC2TtH6zcY369/TRKTZ1DdSgDcDUl4OYQsrXCuaLJmbVzna/5Y5lrMmK7CxgvYgIynICA== - dependencies: - datatables.net "1.10.19" - jquery ">=1.7" - -datatables.net-plugins@^1.10.19: - version "1.10.20" - resolved "https://registry.yarnpkg.com/datatables.net-plugins/-/datatables.net-plugins-1.10.20.tgz#c89f6bed3fa7e6605cbeaa35d60f223659e84c8c" - integrity sha512-rnhNmRHe9UEzvM7gtjBay1QodkWUmxLUhHNbmJMYhhUggjtm+BRSlE0PRilkeUkwckpNWzq+0fPd7/i0fpQgzA== - -datatables.net@1.10.19, datatables.net@^1.10.19: - version "1.10.19" - resolved "https://registry.yarnpkg.com/datatables.net/-/datatables.net-1.10.19.tgz#97a1ed41c85e62d61040603481b59790a172dd1f" - integrity sha512-+ljXcI6Pj3PTGy5pesp3E5Dr3x3AV45EZe0o1r0gKENN2gafBKXodVnk2ypKwl2tTmivjxbkiqoWnipTefyBTA== - dependencies: - jquery ">=1.7" - -date-now@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b" - -defined@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" - -deps-sort@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/deps-sort/-/deps-sort-2.0.0.tgz#091724902e84658260eb910748cccd1af6e21fb5" - dependencies: - JSONStream "^1.0.3" - shasum "^1.0.0" - subarg "^1.0.0" - through2 "^2.0.0" - -des.js@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.0.tgz#c074d2e2aa6a8a9a07dbd61f9a15c2cd83ec8ecc" - dependencies: - inherits "^2.0.1" - minimalistic-assert "^1.0.0" - -detective@^5.0.2: - version "5.1.0" - resolved "https://registry.yarnpkg.com/detective/-/detective-5.1.0.tgz#7a20d89236d7b331ccea65832e7123b5551bb7cb" - dependencies: - acorn-node "^1.3.0" - defined "^1.0.0" - minimist "^1.1.1" - -diffie-hellman@^5.0.0: - version "5.0.3" - resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" - dependencies: - bn.js "^4.1.0" - miller-rabin "^4.0.0" - randombytes "^2.0.0" - -domain-browser@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" - -domhelper@~0.9.0: - version "0.9.1" - resolved "https://registry.yarnpkg.com/domhelper/-/domhelper-0.9.1.tgz#26554e5bac2c9e9585dca500978df5067d64bd00" - dependencies: - browserify ">=3.46.0" - classie ">=0.0.1" - util-extend "^1.0.1" - -duplexer2@^0.1.2, duplexer2@~0.1.0, duplexer2@~0.1.2: - version "0.1.4" - resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" - dependencies: - readable-stream "^2.0.2" - -elliptic@^6.0.0: - version "6.5.4" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" - integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== - dependencies: - bn.js "^4.11.9" - brorand "^1.1.0" - hash.js "^1.0.0" - hmac-drbg "^1.0.1" - inherits "^2.0.4" - minimalistic-assert "^1.0.1" - minimalistic-crypto-utils "^1.0.1" - -eve-raphael@0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/eve-raphael/-/eve-raphael-0.5.0.tgz#17c754b792beef3fa6684d79cf5a47c63c4cda30" - -events@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/events/-/events-2.1.0.tgz#2a9a1e18e6106e0e812aa9ebd4a819b3c29c0ba5" - -evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" - dependencies: - md5.js "^1.3.4" - safe-buffer "^5.1.1" - -fastclick@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/fastclick/-/fastclick-1.0.6.tgz#161625b27b1a5806405936bda9a2c1926d06be6a" - -flot@^0.8.3: - version "0.8.3" - resolved "https://registry.yarnpkg.com/flot/-/flot-0.8.3.tgz#6d9b93c7aa2cfb30dfa1af9c1ec4c94070b1217f" - integrity sha512-xg2otcTJDvS+ERK+my4wxG/ASq90QURXtoM4LhacCq0jQW2jbyjdttbRNqU2cPykrpMvJ6b2uSp6SAgYAzj9tQ== - -font-awesome@^4.7.0: - version "4.7.0" - resolved "https://registry.yarnpkg.com/font-awesome/-/font-awesome-4.7.0.tgz#8fa8cf0411a1a31afd07b06d2902bb9fc815a133" - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - -fullcalendar@^3.10.0: - version "3.10.0" - resolved "https://registry.yarnpkg.com/fullcalendar/-/fullcalendar-3.10.0.tgz#cc5e87d518fd6550e142816a31dd191664847919" - integrity sha512-0OtsHhmdYhtFmQwXzyo8VqHzYgamg+zVOoytv5N13gI+iF6CGjevpCi/yBaQs0O4wY3OAp8I688IxdNYe0iAvw== - -function-bind@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" - -glob@^7.1.0: - version "7.1.2" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -has@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" - dependencies: - function-bind "^1.1.1" - -hash-base@^3.0.0: - version "3.0.4" - resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.0.4.tgz#5fc8686847ecd73499403319a6b0a3f3f6ae4918" - dependencies: - inherits "^2.0.1" - safe-buffer "^5.0.1" - -hash.js@^1.0.0, hash.js@^1.0.3: - version "1.1.7" - resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" - integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== - dependencies: - inherits "^2.0.3" - minimalistic-assert "^1.0.1" - -hmac-drbg@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" - integrity sha1-0nRXAQJabHdabFRXk+1QL8DGSaE= - dependencies: - hash.js "^1.0.3" - minimalistic-assert "^1.0.0" - minimalistic-crypto-utils "^1.0.1" - -htmlescape@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/htmlescape/-/htmlescape-1.1.1.tgz#3a03edc2214bca3b66424a3e7959349509cb0351" - -https-browserify@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" - -icheck@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/icheck/-/icheck-1.0.2.tgz#06d08da3d47ae448c153b2639b86e9ad7fdf7128" - -ieee754@^1.1.4: - version "1.1.12" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.12.tgz#50bf24e5b9c8bb98af4964c941cdb0918da7b60b" - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -inherits@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" - -inherits@2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - -inline-source-map@~0.6.0: - version "0.6.2" - resolved "https://registry.yarnpkg.com/inline-source-map/-/inline-source-map-0.6.2.tgz#f9393471c18a79d1724f863fa38b586370ade2a5" - dependencies: - source-map "~0.5.3" - -inputmask@^3.3.7: - version "3.3.11" - resolved "https://registry.yarnpkg.com/inputmask/-/inputmask-3.3.11.tgz#1421c94ae28c3dcd1b4d26337b508bb34998e2d8" - -insert-module-globals@^7.0.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/insert-module-globals/-/insert-module-globals-7.1.0.tgz#dbb3cea71d3a43d5a07ef0310fe5f078aa4dbf35" - dependencies: - JSONStream "^1.0.3" - combine-source-map "^0.8.0" - concat-stream "^1.6.1" - is-buffer "^1.1.0" - lexical-scope "^1.2.0" - path-is-absolute "^1.0.1" - process "~0.11.0" - through2 "^2.0.0" - xtend "^4.0.0" - -ion-rangeslider@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/ion-rangeslider/-/ion-rangeslider-2.3.0.tgz#7957ce2e78acfc956b8c43009373da91f743347e" - integrity sha512-7TtH9/X4Aq/xCzboWxjwlv20gVqR90Ysc3aehMlTuH2/ULaSxpB80hq+yvD1N0FwWbPDtxQpjQrz/iX+LWXKmg== - dependencies: - jquery ">=1.8" - -ionicons@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/ionicons/-/ionicons-3.0.0.tgz#40b8daf4fd7a31150bd002160f66496e22a98c3c" - -is-buffer@^1.1.0: - version "1.1.6" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" - -isarray@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.4.tgz#38e7bcbb0f3ba1b7933c86ba1894ddfc3781bbb7" - -isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - -jquery-knob@^1.2.11: - version "1.2.11" - resolved "https://registry.yarnpkg.com/jquery-knob/-/jquery-knob-1.2.11.tgz#f37c39dbc1c7a6a6c12cdb2ed4f6bffb683f10d6" - -jquery-mousewheel@~3.1.13: - version "3.1.13" - resolved "https://registry.yarnpkg.com/jquery-mousewheel/-/jquery-mousewheel-3.1.13.tgz#06f0335f16e353a695e7206bf50503cb523a6ee5" - -jquery-slimscroll@^1.3.8: - version "1.3.8" - resolved "https://registry.yarnpkg.com/jquery-slimscroll/-/jquery-slimscroll-1.3.8.tgz#8481c44e7a47687653908a28f7f70aed64c84e36" - dependencies: - jquery ">= 1.7" - -jquery-sparkline@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/jquery-sparkline/-/jquery-sparkline-2.4.0.tgz#1be8b7b704dd3857152708aefb1d4a4b3a69fb33" - -jquery-ui-dist@^1.12.1: - version "1.12.1" - resolved "https://registry.yarnpkg.com/jquery-ui-dist/-/jquery-ui-dist-1.12.1.tgz#5c0815d3cc6f90ff5faaf5b268a6e23b4ca904fa" - -jquery-ui@^1.12.1: - version "1.12.1" - resolved "https://registry.yarnpkg.com/jquery-ui/-/jquery-ui-1.12.1.tgz#bcb4045c8dd0539c134bc1488cdd3e768a7a9e51" - -jquery.quicksearch@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/jquery.quicksearch/-/jquery.quicksearch-2.4.0.tgz#240c9f435e936c63bf8fbba355144ffdddf9ea26" - integrity sha512-20FJSCW3kTawO6Jvy/6MtUCURvgUZFqRUOAGTxH/VaPlwLG4kba82sKaM3ghTi1DsmSZrM2BvrwLUwNWmwDXiw== - dependencies: - jquery ">=1.8" - -"jquery@>= 1.7", "jquery@>= 1.7.1", jquery@>=1.10, jquery@>=1.5, jquery@>=1.7, "jquery@>=1.7.1 <4.0.0", jquery@>=1.8, jquery@^3.2.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.3.1.tgz#958ce29e81c9790f31be7792df5d4d95fc57fbca" - -json-stable-stringify@~0.0.0: - version "0.0.1" - resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-0.0.1.tgz#611c23e814db375527df851193db59dd2af27f45" - dependencies: - jsonify "~0.0.0" - -jsonify@~0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" - -jsonparse@^1.2.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" - -jtimeout@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/jtimeout/-/jtimeout-3.1.0.tgz#4cd65b28eff8b9f8c61d08889a9ac3abdf5d9893" - integrity sha512-xA2TlImMGj4c0yAiM9BUq+8aAFVYVYUX2tkcC8u8das9qoZSs13SxhVcfWqI4cHOsv3huj2D0VRNHeVCLO3mVQ== - dependencies: - jquery ">=1.7.1 <4.0.0" - -jvectormap@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/jvectormap/-/jvectormap-1.2.2.tgz#2e4408b24a60473ff106c1e7243e375ae5ca85da" - dependencies: - jquery ">=1.5" - -labeled-stream-splicer@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/labeled-stream-splicer/-/labeled-stream-splicer-2.0.1.tgz#9cffa32fd99e1612fd1d86a8db962416d5292926" - dependencies: - inherits "^2.0.1" - isarray "^2.0.4" - stream-splicer "^2.0.0" - -lexical-scope@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/lexical-scope/-/lexical-scope-1.2.0.tgz#fcea5edc704a4b3a8796cdca419c3a0afaf22df4" - dependencies: - astw "^2.0.0" - -lodash.memoize@~3.0.3: - version "3.0.4" - resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-3.0.4.tgz#2dcbd2c287cbc0a55cc42328bd0c736150d53e3f" - -md5.js@^1.3.4: - version "1.3.4" - resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.4.tgz#e9bdbde94a20a5ac18b04340fc5764d5b09d901d" - dependencies: - hash-base "^3.0.0" - inherits "^2.0.1" - -miller-rabin@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" - dependencies: - bn.js "^4.0.0" - brorand "^1.0.1" - -minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" - integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== - -minimalistic-crypto-utils@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" - integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= - -minimatch@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" - dependencies: - brace-expansion "^1.1.7" - -minimist@0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" - -minimist@^1.1.0, minimist@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" - -mkdirp@^0.5.0: - version "0.5.1" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" - dependencies: - minimist "0.0.8" - -module-deps@^6.0.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/module-deps/-/module-deps-6.1.0.tgz#d1e1efc481c6886269f7112c52c3236188e16479" - dependencies: - JSONStream "^1.0.3" - browser-resolve "^1.7.0" - cached-path-relative "^1.0.0" - concat-stream "~1.6.0" - defined "^1.0.0" - detective "^5.0.2" - duplexer2 "^0.1.2" - inherits "^2.0.1" - parents "^1.0.0" - readable-stream "^2.0.2" - resolve "^1.4.0" - stream-combiner2 "^1.1.1" - subarg "^1.0.0" - through2 "^2.0.0" - xtend "^4.0.0" - -moment@^2.24.0: - version "2.24.0" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b" - integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg== - -moment@^2.9.0: - version "2.22.2" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66" - -morris.js@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/morris.js/-/morris.js-0.5.0.tgz#725767135cfae059aae75999bb2ce6a1c5d1b44b" - -multiselect@^0.9.12: - version "0.9.12" - resolved "https://registry.yarnpkg.com/multiselect/-/multiselect-0.9.12.tgz#d15536e986dd6a0029b160d6613bcedf81e4c7ed" - dependencies: - jquery ">= 1.7.1" - -once@^1.3.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - dependencies: - wrappy "1" - -os-browserify@~0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" - -pace@0.0.4: - version "0.0.4" - resolved "https://registry.yarnpkg.com/pace/-/pace-0.0.4.tgz#d66405d5f5bc12d25441a6e26c878dbc69e77a77" - dependencies: - charm "~0.1.0" - -pako@~1.0.5: - version "1.0.6" - resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.6.tgz#0101211baa70c4bca4a0f63f2206e97b7dfaf258" - -parents@^1.0.0, parents@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/parents/-/parents-1.0.1.tgz#fedd4d2bf193a77745fe71e371d73c3307d9c751" - dependencies: - path-platform "~0.11.15" - -parse-asn1@^5.0.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.1.tgz#f6bf293818332bd0dab54efb16087724745e6ca8" - dependencies: - asn1.js "^4.0.0" - browserify-aes "^1.0.0" - create-hash "^1.1.0" - evp_bytestokey "^1.0.0" - pbkdf2 "^3.0.3" - -path-browserify@~0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.0.tgz#a0b870729aae214005b7d5032ec2cbbb0fb4451a" - -path-is-absolute@^1.0.0, path-is-absolute@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - -path-parse@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1" - -path-platform@~0.11.15: - version "0.11.15" - resolved "https://registry.yarnpkg.com/path-platform/-/path-platform-0.11.15.tgz#e864217f74c36850f0852b78dc7bf7d4a5721bf2" - -pbkdf2@^3.0.3: - version "3.0.16" - resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.16.tgz#7404208ec6b01b62d85bf83853a8064f8d9c2a5c" - dependencies: - create-hash "^1.1.2" - create-hmac "^1.1.4" - ripemd160 "^2.0.1" - safe-buffer "^5.0.1" - sha.js "^2.4.8" - -process-nextick-args@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" - -process@~0.11.0: - version "0.11.10" - resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" - -public-encrypt@^4.0.0: - version "4.0.2" - resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.2.tgz#46eb9107206bf73489f8b85b69d91334c6610994" - dependencies: - bn.js "^4.1.0" - browserify-rsa "^4.0.0" - create-hash "^1.1.0" - parse-asn1 "^5.0.0" - randombytes "^2.0.1" - -punycode@1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" - -punycode@^1.3.2: - version "1.4.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" - -querystring-es3@~0.2.0: - version "0.2.1" - resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" - -querystring@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" - -randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5: - version "2.0.6" - resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.0.6.tgz#d302c522948588848a8d300c932b44c24231da80" - dependencies: - safe-buffer "^5.1.0" - -randomfill@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/randomfill/-/randomfill-1.0.4.tgz#c92196fc86ab42be983f1bf31778224931d61458" - dependencies: - randombytes "^2.0.5" - safe-buffer "^5.1.0" - -raphael@^2.2.7: - version "2.2.7" - resolved "https://registry.yarnpkg.com/raphael/-/raphael-2.2.7.tgz#231b19141f8d086986d8faceb66f8b562ee2c810" - dependencies: - eve-raphael "0.5.0" - -read-only-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/read-only-stream/-/read-only-stream-2.0.0.tgz#2724fd6a8113d73764ac288d4386270c1dbf17f0" - dependencies: - readable-stream "^2.0.2" - -readable-stream@^2.0.2, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.6: - version "2.3.6" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" - -resolve@1.1.7: - version "1.1.7" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" - -resolve@^1.1.4, resolve@^1.4.0: - version "1.7.1" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.7.1.tgz#aadd656374fd298aee895bc026b8297418677fd3" - dependencies: - path-parse "^1.0.5" - -ripemd160@^2.0.0, ripemd160@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" - dependencies: - hash-base "^3.0.0" - inherits "^2.0.1" - -safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - -select2@^4.0.3: - version "4.0.5" - resolved "https://registry.yarnpkg.com/select2/-/select2-4.0.5.tgz#7aac50692561985b34d3b82ec55e226f8960d40a" - dependencies: - almond "~0.3.1" - jquery-mousewheel "~3.1.13" - -sha.js@^2.4.0, sha.js@^2.4.8, sha.js@~2.4.4: - version "2.4.11" - resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" - dependencies: - inherits "^2.0.1" - safe-buffer "^5.0.1" - -shasum@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/shasum/-/shasum-1.0.2.tgz#e7012310d8f417f4deb5712150e5678b87ae565f" - dependencies: - json-stable-stringify "~0.0.0" - sha.js "~2.4.4" - -shell-quote@^1.6.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.6.1.tgz#f4781949cce402697127430ea3b3c5476f481767" - dependencies: - array-filter "~0.0.0" - array-map "~0.0.0" - array-reduce "~0.0.0" - jsonify "~0.0.0" - -slimscroll@^0.9.1: - version "0.9.1" - resolved "https://registry.yarnpkg.com/slimscroll/-/slimscroll-0.9.1.tgz#f675cdc601d80ada20f16004d227d156fd1187b2" - dependencies: - browserify ">=3.46.0" - classie ">=0.0.1" - domhelper "~0.9.0" - util-extend "^1.0.1" - -source-map@~0.5.3: - version "0.5.7" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" - -stream-browserify@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.1.tgz#66266ee5f9bdb9940a4e4514cafb43bb71e5c9db" - dependencies: - inherits "~2.0.1" - readable-stream "^2.0.2" - -stream-combiner2@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/stream-combiner2/-/stream-combiner2-1.1.1.tgz#fb4d8a1420ea362764e21ad4780397bebcb41cbe" - dependencies: - duplexer2 "~0.1.0" - readable-stream "^2.0.2" - -stream-http@^2.0.0: - version "2.8.3" - resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.3.tgz#b2d242469288a5a27ec4fe8933acf623de6514fc" - dependencies: - builtin-status-codes "^3.0.0" - inherits "^2.0.1" - readable-stream "^2.3.6" - to-arraybuffer "^1.0.0" - xtend "^4.0.0" - -stream-splicer@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/stream-splicer/-/stream-splicer-2.0.0.tgz#1b63be438a133e4b671cc1935197600175910d83" - dependencies: - inherits "^2.0.1" - readable-stream "^2.0.2" - -string_decoder@^1.1.1, string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - dependencies: - safe-buffer "~5.1.0" - -subarg@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/subarg/-/subarg-1.0.0.tgz#f62cf17581e996b48fc965699f54c06ae268b8d2" - dependencies: - minimist "^1.1.0" - -syntax-error@^1.1.1: - version "1.4.0" - resolved "https://registry.yarnpkg.com/syntax-error/-/syntax-error-1.4.0.tgz#2d9d4ff5c064acb711594a3e3b95054ad51d907c" - dependencies: - acorn-node "^1.2.0" - -through2@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.3.tgz#0004569b37c7c74ba39c43f3ced78d1ad94140be" - dependencies: - readable-stream "^2.1.5" - xtend "~4.0.1" - -"through@>=2.2.7 <3": - version "2.3.8" - resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" - -timers-browserify@^1.0.1: - version "1.4.2" - resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-1.4.2.tgz#c9c58b575be8407375cb5e2462dacee74359f41d" - dependencies: - process "~0.11.0" - -to-arraybuffer@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" - -tty-browserify@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.1.tgz#3f05251ee17904dfd0677546670db9651682b811" - -typedarray@^0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" - -umd@^3.0.0: - version "3.0.3" - resolved "https://registry.yarnpkg.com/umd/-/umd-3.0.3.tgz#aa9fe653c42b9097678489c01000acb69f0b26cf" - -url@~0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" - dependencies: - punycode "1.3.2" - querystring "0.2.0" - -util-deprecate@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - -util-extend@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/util-extend/-/util-extend-1.0.3.tgz#a7c216d267545169637b3b6edc6ca9119e2ff93f" - -util@0.10.3: - version "0.10.3" - resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" - dependencies: - inherits "2.0.1" - -util@~0.10.1: - version "0.10.4" - resolved "https://registry.yarnpkg.com/util/-/util-0.10.4.tgz#3aa0125bfe668a4672de58857d3ace27ecb76901" - dependencies: - inherits "2.0.3" - -vm-browserify@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.0.1.tgz#a15d7762c4c48fa6bf9f3309a21340f00ed23063" - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - -xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" diff --git a/docker/stacks/local/mysql/docker-compose.yml b/docker/stacks/local/mysql/docker-compose.yml index b8d0e5f..9f8b9f0 100644 --- a/docker/stacks/local/mysql/docker-compose.yml +++ b/docker/stacks/local/mysql/docker-compose.yml @@ -40,6 +40,7 @@ services: - PDA_CHECK_API_FAIL_DELAY=2 - PDA_CHECK_API_SUCCESS_DELAY=0 - PDA_CHECK_API_ATTEMPTS=15 + - PDA_CHECK_PYTEST_ENABLED=false - PDA_GUNICORN_ENABLED=false - PDA_GUNICORN_TIMEOUT=2 - PDA_GUNICORN_WORKERS=2 diff --git a/docker/stacks/local/sqlite/docker-compose.yml b/docker/stacks/local/sqlite/docker-compose.yml index cd49977..4d668b3 100644 --- a/docker/stacks/local/sqlite/docker-compose.yml +++ b/docker/stacks/local/sqlite/docker-compose.yml @@ -36,6 +36,7 @@ services: - PDA_CHECK_API_FAIL_DELAY=2 - PDA_CHECK_API_SUCCESS_DELAY=0 - PDA_CHECK_API_ATTEMPTS=15 + - PDA_CHECK_PYTEST_ENABLED=false - PDA_GUNICORN_ENABLED=false - PDA_GUNICORN_TIMEOUT=2 - PDA_GUNICORN_WORKERS=2 From 843930e44dc8dfdaa3d05c252ec81f635102704f Mon Sep 17 00:00:00 2001 From: Matt Scott Date: Wed, 8 Dec 2021 11:56:56 -0500 Subject: [PATCH 5/5] Added additional Docker specific setting named PDA_LOCAL_PATH. This setting is used by local deployment Docker files to determine the host machine path to the project root. Updated project README.md to remove redundant documentation links. Updating the settings.md documentation to reflect the aforementioned setting. --- README.md | 4 +--- docker/stacks/local/mysql/docker-compose.yml | 4 +++- docker/stacks/local/sqlite/docker-compose.yml | 4 +++- docs/settings.md | 4 ++++ 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index d58b9ef..ac74da2 100644 --- a/README.md +++ b/README.md @@ -37,9 +37,7 @@ Once you have deployed the app through one of the supported methods, You should ## Configuring PowerDNS-Admin -The app has a [plethora of settings](https://github.com/ngoduykhanh/PowerDNS-Admin/blob/master/docs/settings.md) that may be configured through a number of methods. Check out the settings documentation [here](https://github.com/ngoduykhanh/PowerDNS-Admin/blob/master/docs/settings.md). - -[PowerDNS Admin Settings](https://github.com/ngoduykhanh/PowerDNS-Admin/blob/master/docs/settings.md) +The app has a plethora of settings that may be configured through a number of methods. Check out the settings documentation [here](https://github.com/ngoduykhanh/PowerDNS-Admin/blob/master/docs/settings.md). ## Screenshots ![dashboard](https://user-images.githubusercontent.com/6447444/44068603-0d2d81f6-9fa5-11e8-83af-14e2ad79e370.png) diff --git a/docker/stacks/local/mysql/docker-compose.yml b/docker/stacks/local/mysql/docker-compose.yml index 9f8b9f0..4f5fe7a 100644 --- a/docker/stacks/local/mysql/docker-compose.yml +++ b/docker/stacks/local/mysql/docker-compose.yml @@ -69,7 +69,9 @@ services: ports: - "8080:80" volumes: - - /PATH/TO/YOUR/GIT/PROJECT/ROOT:/srv/app + - type: bind + source: ${PDA_LOCAL_PATH} + target: /srv/app pdns: image: azoriansolutions/powerdns-nameserver:4.5.2-alpine-3.14-mysql diff --git a/docker/stacks/local/sqlite/docker-compose.yml b/docker/stacks/local/sqlite/docker-compose.yml index 4d668b3..ce91a9d 100644 --- a/docker/stacks/local/sqlite/docker-compose.yml +++ b/docker/stacks/local/sqlite/docker-compose.yml @@ -61,7 +61,9 @@ services: ports: - "8080:80" volumes: - - /PATH/TO/YOUR/GIT/PROJECT/ROOT:/srv/app + - type: bind + source: ${PDA_LOCAL_PATH} + target: /srv/app pdns: image: azoriansolutions/powerdns-nameserver:4.5.2-alpine-3.14-mysql diff --git a/docs/settings.md b/docs/settings.md index 5a935f2..e79a86d 100644 --- a/docs/settings.md +++ b/docs/settings.md @@ -26,6 +26,10 @@ This setting controls what group ID is used for various container setup processe This setting controls what group name is used for various container setup processes during image building. This setting is useful if you're mounting a host path over the /srv/app volume and need to match your host file system permissions. +#### PDA_LOCAL_PATH + +This setting controls with path on the Docker host machine will be used as the project root for local development. + ## App Settings \* denotes a legacy setting key that will be deprecated.