mirror of
https://github.com/Mailu/Mailu.git
synced 2024-12-14 10:53:30 +02:00
Merge #2518
2518: Add dev runner for admin container r=mergify[bot] a=ghostwheel42 ## What type of PR? development feature ## What does this PR do? This adds a shell script (run_dev.sh) to run a live development environment in a container. Co-authored-by: Alexander Graf <ghostwheel42@users.noreply.github.com>
This commit is contained in:
commit
4563038b32
1
core/admin/.gitignore
vendored
1
core/admin/.gitignore
vendored
@ -2,3 +2,4 @@
|
|||||||
lib64
|
lib64
|
||||||
.vscode
|
.vscode
|
||||||
tags
|
tags
|
||||||
|
dev
|
||||||
|
@ -4,13 +4,19 @@ FROM node:16-alpine3.16
|
|||||||
|
|
||||||
WORKDIR /work
|
WORKDIR /work
|
||||||
|
|
||||||
COPY content/ ./
|
COPY package.json ./
|
||||||
|
COPY webpack.config.js ./
|
||||||
|
|
||||||
RUN set -euxo pipefail \
|
RUN set -euxo pipefail \
|
||||||
&& npm config set update-notifier false \
|
; npm config set update-notifier false \
|
||||||
&& npm install --no-audit --no-fund \
|
; npm install --no-audit --no-fund \
|
||||||
&& sed -i 's/#007bff/#55a5d9/' node_modules/admin-lte/build/scss/_bootstrap-variables.scss \
|
; sed -i 's/#007bff/#55a5d9/' node_modules/admin-lte/build/scss/_bootstrap-variables.scss \
|
||||||
&& for l in ca da de:de-DE en:en-GB es:es-ES eu fr:fr-FR he hu is it:it-IT ja nb_NO:no-NB nl:nl-NL pl pt:pt-PT ru sv:sv-SE zh; do \
|
; mkdir assets \
|
||||||
|
; for l in ca da de:de-DE en:en-GB es:es-ES eu fr:fr-FR he hu is it:it-IT ja nb_NO:no-NB nl:nl-NL pl pt:pt-PT ru sv:sv-SE zh; do \
|
||||||
cp node_modules/datatables.net-plugins/i18n/${l#*:}.json assets/${l%:*}.json; \
|
cp node_modules/datatables.net-plugins/i18n/${l#*:}.json assets/${l%:*}.json; \
|
||||||
done \
|
done
|
||||||
&& node_modules/.bin/webpack-cli --color
|
|
||||||
|
COPY assets/ ./assets/
|
||||||
|
|
||||||
|
RUN set -euxo pipefail \
|
||||||
|
; node_modules/.bin/webpack-cli --color
|
||||||
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 4.8 KiB |
@ -44,8 +44,10 @@ def create_app_from_config(config):
|
|||||||
# Initialize debugging tools
|
# Initialize debugging tools
|
||||||
if app.config.get("DEBUG"):
|
if app.config.get("DEBUG"):
|
||||||
debug.toolbar.init_app(app)
|
debug.toolbar.init_app(app)
|
||||||
# TODO: add a specific configuration variable for profiling
|
if app.config.get("DEBUG_PROFILER"):
|
||||||
# debug.profiler.init_app(app)
|
debug.profiler.init_app(app)
|
||||||
|
if assets := app.config.get('DEBUG_ASSETS'):
|
||||||
|
app.static_folder = assets
|
||||||
|
|
||||||
# Inject the default variables in the Jinja parser
|
# Inject the default variables in the Jinja parser
|
||||||
# TODO: move this to blueprints when needed
|
# TODO: move this to blueprints when needed
|
||||||
|
@ -11,8 +11,9 @@ DEFAULT_CONFIG = {
|
|||||||
'BABEL_DEFAULT_TIMEZONE': 'UTC',
|
'BABEL_DEFAULT_TIMEZONE': 'UTC',
|
||||||
'BOOTSTRAP_SERVE_LOCAL': True,
|
'BOOTSTRAP_SERVE_LOCAL': True,
|
||||||
'RATELIMIT_STORAGE_URL': '',
|
'RATELIMIT_STORAGE_URL': '',
|
||||||
'QUOTA_STORAGE_URL': '',
|
|
||||||
'DEBUG': False,
|
'DEBUG': False,
|
||||||
|
'DEBUG_PROFILER': False,
|
||||||
|
'DEBUG_ASSETS': '',
|
||||||
'DOMAIN_REGISTRATION': False,
|
'DOMAIN_REGISTRATION': False,
|
||||||
'TEMPLATES_AUTO_RELOAD': True,
|
'TEMPLATES_AUTO_RELOAD': True,
|
||||||
'MEMORY_SESSIONS': False,
|
'MEMORY_SESSIONS': False,
|
||||||
@ -149,8 +150,9 @@ class ConfigManager:
|
|||||||
template = self.DB_TEMPLATES[self.config['DB_FLAVOR']]
|
template = self.DB_TEMPLATES[self.config['DB_FLAVOR']]
|
||||||
self.config['SQLALCHEMY_DATABASE_URI'] = template.format(**self.config)
|
self.config['SQLALCHEMY_DATABASE_URI'] = template.format(**self.config)
|
||||||
|
|
||||||
self.config['RATELIMIT_STORAGE_URL'] = f'redis://{self.config["REDIS_ADDRESS"]}/2'
|
if not self.config.get('RATELIMIT_STORAGE_URL'):
|
||||||
self.config['QUOTA_STORAGE_URL'] = f'redis://{self.config["REDIS_ADDRESS"]}/1'
|
self.config['RATELIMIT_STORAGE_URL'] = f'redis://{self.config["REDIS_ADDRESS"]}/2'
|
||||||
|
|
||||||
self.config['SESSION_STORAGE_URL'] = f'redis://{self.config["REDIS_ADDRESS"]}/3'
|
self.config['SESSION_STORAGE_URL'] = f'redis://{self.config["REDIS_ADDRESS"]}/3'
|
||||||
self.config['SESSION_COOKIE_SAMESITE'] = 'Strict'
|
self.config['SESSION_COOKIE_SAMESITE'] = 'Strict'
|
||||||
self.config['SESSION_COOKIE_HTTPONLY'] = True
|
self.config['SESSION_COOKIE_HTTPONLY'] = True
|
||||||
@ -159,9 +161,9 @@ class ConfigManager:
|
|||||||
self.config['PERMANENT_SESSION_LIFETIME'] = int(self.config['PERMANENT_SESSION_LIFETIME'])
|
self.config['PERMANENT_SESSION_LIFETIME'] = int(self.config['PERMANENT_SESSION_LIFETIME'])
|
||||||
self.config['AUTH_RATELIMIT_IP_V4_MASK'] = int(self.config['AUTH_RATELIMIT_IP_V4_MASK'])
|
self.config['AUTH_RATELIMIT_IP_V4_MASK'] = int(self.config['AUTH_RATELIMIT_IP_V4_MASK'])
|
||||||
self.config['AUTH_RATELIMIT_IP_V6_MASK'] = int(self.config['AUTH_RATELIMIT_IP_V6_MASK'])
|
self.config['AUTH_RATELIMIT_IP_V6_MASK'] = int(self.config['AUTH_RATELIMIT_IP_V6_MASK'])
|
||||||
hostnames = [host.strip() for host in self.config['HOSTNAMES'].split(',')]
|
|
||||||
self.config['AUTH_RATELIMIT_EXEMPTION'] = set(ipaddress.ip_network(cidr, False) for cidr in (cidr.strip() for cidr in self.config['AUTH_RATELIMIT_EXEMPTION'].split(',')) if cidr)
|
self.config['AUTH_RATELIMIT_EXEMPTION'] = set(ipaddress.ip_network(cidr, False) for cidr in (cidr.strip() for cidr in self.config['AUTH_RATELIMIT_EXEMPTION'].split(',')) if cidr)
|
||||||
self.config['MESSAGE_RATELIMIT_EXEMPTION'] = set([s for s in self.config['MESSAGE_RATELIMIT_EXEMPTION'].lower().replace(' ', '').split(',') if s])
|
self.config['MESSAGE_RATELIMIT_EXEMPTION'] = set([s for s in self.config['MESSAGE_RATELIMIT_EXEMPTION'].lower().replace(' ', '').split(',') if s])
|
||||||
|
hostnames = [host.strip() for host in self.config['HOSTNAMES'].split(',')]
|
||||||
self.config['HOSTNAMES'] = ','.join(hostnames)
|
self.config['HOSTNAMES'] = ','.join(hostnames)
|
||||||
self.config['HOSTNAME'] = hostnames[0]
|
self.config['HOSTNAME'] = hostnames[0]
|
||||||
self.config['DEFAULT_SPAM_THRESHOLD'] = int(self.config['DEFAULT_SPAM_THRESHOLD'])
|
self.config['DEFAULT_SPAM_THRESHOLD'] = int(self.config['DEFAULT_SPAM_THRESHOLD'])
|
||||||
|
138
core/admin/run_dev.sh
Executable file
138
core/admin/run_dev.sh
Executable file
@ -0,0 +1,138 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
### CONFIG
|
||||||
|
|
||||||
|
DEV_NAME="${DEV_NAME:-mailu-dev}"
|
||||||
|
DEV_DB="${DEV_DB:-}"
|
||||||
|
DEV_PROFILER="${DEV_PROFILER:-false}"
|
||||||
|
DEV_LISTEN="${DEV_LISTEN:-127.0.0.1:8080}"
|
||||||
|
[[ "${DEV_LISTEN}" == *:* ]] || DEV_LISTEN="127.0.0.1:${DEV_LISTEN}"
|
||||||
|
DEV_ADMIN="${DEV_ADMIN:-admin@example.com}"
|
||||||
|
DEV_PASSWORD="${DEV_PASSWORD:-letmein}"
|
||||||
|
|
||||||
|
### MAIN
|
||||||
|
|
||||||
|
[[ -n "${DEV_DB}" ]] && {
|
||||||
|
[[ -f "${DEV_DB}" ]] || {
|
||||||
|
echo "Sorry, can't find DEV_DB: '${DEV_DB}'"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
DEV_DB="$(realpath "${DEV_DB}")"
|
||||||
|
}
|
||||||
|
|
||||||
|
docker="$(command -v podman || command -v docker || echo false)"
|
||||||
|
[[ "${docker}" == "false" ]] && {
|
||||||
|
echo "Sorry, you'll need podman or docker to run this."
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp="$(mktemp -d)"
|
||||||
|
[[ -n "${tmp}" && -d "${tmp}" ]] || {
|
||||||
|
echo "Sorry, can't create temporary folder."
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
trap "rm -rf '${tmp}'" INT TERM EXIT
|
||||||
|
|
||||||
|
admin="$(realpath "$(pwd)/${0%/*}")"
|
||||||
|
base="${admin}/../base"
|
||||||
|
assets="${admin}/assets"
|
||||||
|
|
||||||
|
cd "${tmp}"
|
||||||
|
|
||||||
|
# base
|
||||||
|
cp "${base}"/requirements-* .
|
||||||
|
cp -r "${base}"/libs .
|
||||||
|
sed -E '/^#/d;s:^FROM system$:FROM system AS base:' "${base}/Dockerfile" >Dockerfile
|
||||||
|
|
||||||
|
# assets
|
||||||
|
cp "${assets}/package.json" .
|
||||||
|
cp -r "${assets}/assets/" .
|
||||||
|
awk '/new compress/{f=1}!f{print}/}),/{f=0}' <"${assets}/webpack.config.js" >webpack.config.js
|
||||||
|
sed -E '/^#/d;s:^(FROM [^ ]+$):\1 AS assets:' "${assets}/Dockerfile" >>Dockerfile
|
||||||
|
|
||||||
|
# admin
|
||||||
|
sed -E '/^#/d;/^(COPY|EXPOSE|HEALTHCHECK|VOLUME|CMD) /d; s:^(.* )[^ ]*pybabel[^\\]*(.*):\1true \2:' "${admin}/Dockerfile" >>Dockerfile
|
||||||
|
|
||||||
|
# development
|
||||||
|
cat >>Dockerfile <<EOF
|
||||||
|
COPY --from=assets /work/static/ ./static/
|
||||||
|
|
||||||
|
RUN set -euxo pipefail \
|
||||||
|
; mkdir /data \
|
||||||
|
; ln -s /app/audit.py / \
|
||||||
|
; ln -s /app/start.py /
|
||||||
|
|
||||||
|
ENV \
|
||||||
|
FLASK_ENV="development" \
|
||||||
|
MEMORY_SESSIONS="true" \
|
||||||
|
RATELIMIT_STORAGE_URL="memory://" \
|
||||||
|
SESSION_COOKIE_SECURE="false" \
|
||||||
|
\
|
||||||
|
DEBUG="true" \
|
||||||
|
DEBUG_PROFILER="${DEV_PROFILER}" \
|
||||||
|
DEBUG_ASSETS="/app/static" \
|
||||||
|
DEBUG_TB_ENABLED="true" \
|
||||||
|
\
|
||||||
|
IMAP_ADDRESS="127.0.0.1" \
|
||||||
|
POP3_ADDRESS="127.0.0.1" \
|
||||||
|
AUTHSMTP_ADDRESS="127.0.0.1" \
|
||||||
|
SMTP_ADDRESS="127.0.0.1" \
|
||||||
|
REDIS_ADDRESS="127.0.0.1" \
|
||||||
|
WEBMAIL_ADDRESS="127.0.0.1"
|
||||||
|
|
||||||
|
CMD ["/bin/bash", "-c", "flask db upgrade &>/dev/null && flask mailu admin '${DEV_ADMIN/@*}' '${DEV_ADMIN#*@}' '${DEV_PASSWORD}' --mode ifmissing >/dev/null && flask run --host=0.0.0.0 --port=8080"]
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# build
|
||||||
|
chmod -R u+rwX,go+rX .
|
||||||
|
"${docker}" build --tag "${DEV_NAME}:latest" .
|
||||||
|
|
||||||
|
# gather volumes to map into container
|
||||||
|
volumes=()
|
||||||
|
|
||||||
|
[[ -n "${DEV_DB}" ]] && volumes+=( --volume "${DEV_DB}:/data/main.db" )
|
||||||
|
|
||||||
|
for vol in audit.py start.py mailu/ migrations/; do
|
||||||
|
volumes+=( --volume "${admin}/${vol}:/app/${vol}" )
|
||||||
|
done
|
||||||
|
|
||||||
|
for file in "${assets}/assets"/*; do
|
||||||
|
[[ ! -f "${file}" || "${file}" == */vendor.js ]] && continue
|
||||||
|
volumes+=( --volume "${file}:/app/static/${file/*\//}" )
|
||||||
|
done
|
||||||
|
|
||||||
|
# show configuration
|
||||||
|
cat <<EOF
|
||||||
|
|
||||||
|
=============================================================================
|
||||||
|
The "${DEV_NAME}" container was built using this configuration:
|
||||||
|
|
||||||
|
DEV_NAME="${DEV_NAME}"
|
||||||
|
DEV_DB="${DEV_DB}"
|
||||||
|
DEV_PROFILER="${DEV_PROFILER}"
|
||||||
|
DEV_LISTEN="${DEV_LISTEN}"
|
||||||
|
DEV_ADMIN="${DEV_ADMIN}"
|
||||||
|
DEV_PASSWORD="${DEV_PASSWORD}"
|
||||||
|
=============================================================================
|
||||||
|
|
||||||
|
=============================================================================
|
||||||
|
You can start the container later using this commandline:
|
||||||
|
|
||||||
|
${docker/*\/} run --rm -it --name "${DEV_NAME}" --publish ${DEV_LISTEN}:8080$(printf " %q" "${volumes[@]}") "${DEV_NAME}"
|
||||||
|
=============================================================================
|
||||||
|
|
||||||
|
=============================================================================
|
||||||
|
The Mailu UI can be found here: http://${DEV_LISTEN}/sso/login
|
||||||
|
EOF
|
||||||
|
[[ -z "${DEV_DB}" ]] && echo "You can log in with user ${DEV_ADMIN} and password ${DEV_PASSWORD}"
|
||||||
|
cat <<EOF
|
||||||
|
=============================================================================
|
||||||
|
|
||||||
|
Starting mailu dev environment...
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# run
|
||||||
|
"${docker}" run --rm -it --name "${DEV_NAME}" --publish "${DEV_LISTEN}:8080" "${volumes[@]}" "${DEV_NAME}"
|
||||||
|
|
@ -313,6 +313,48 @@ If git opens a editor for a commit message just save and exit as-is. If you have
|
|||||||
see above and do the complete procedure from ``git fetch`` onward again.
|
see above and do the complete procedure from ``git fetch`` onward again.
|
||||||
|
|
||||||
|
|
||||||
|
Web administration development
|
||||||
|
------------------------------
|
||||||
|
|
||||||
|
The administration web interface requires a proper dev environment that can easily
|
||||||
|
be setup using the ``run_dev.sh`` shell script. You need ``docker`` or ``podman``
|
||||||
|
to run it. It will create a local webserver listening at port 8080:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
cd core/admin
|
||||||
|
./run_dev.sh
|
||||||
|
pip install -r requirements.txt
|
||||||
|
[...]
|
||||||
|
=============================================================================
|
||||||
|
The "mailu-dev" container was built using this configuration:
|
||||||
|
|
||||||
|
DEV_NAME="mailu-dev"
|
||||||
|
DEV_DB=""
|
||||||
|
DEV_PROFILER="false"
|
||||||
|
DEV_LISTEN="127.0.0.1:8080"
|
||||||
|
DEV_ADMIN="admin@example.com"
|
||||||
|
DEV_PASSWORD="letmein"
|
||||||
|
=============================================================================
|
||||||
|
[...]
|
||||||
|
=============================================================================
|
||||||
|
The Mailu UI can be found here: http://127.0.0.1:8080/sso/login
|
||||||
|
You can log in with user admin@example.com and password letmein
|
||||||
|
=============================================================================
|
||||||
|
|
||||||
|
The container will use an empty database and a default user/password unless you
|
||||||
|
specify a database file to use by setting ``$DEV_DB``.
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
DEV_DB="/path/to/dev.db" ./run_dev.sh
|
||||||
|
|
||||||
|
Any change to the files will automatically restart the Web server and reload the files.
|
||||||
|
|
||||||
|
When using the development environment, a debugging toolbar is displayed on the right
|
||||||
|
side of the screen, where you can access query details, internal variables, etc.
|
||||||
|
|
||||||
|
|
||||||
Documentation
|
Documentation
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user