diff --git a/README.md b/README.md index 128203b..40b9152 100644 --- a/README.md +++ b/README.md @@ -41,25 +41,42 @@ Output example: ## Configure -Let's assume that the kickstart instance is named `kickstart1`. +Let's assume that the mattermost instance is named `kickstart1`. Launch `configure-module`, by setting the following parameters: -- ``: -- ``: -- ... +- `host`: a fully qualified domain name for the application +- `http2https`: enable or disable HTTP to HTTPS redirection (true/false) +- `lets_encrypt`: enable or disable Let's Encrypt certificate (true/false) + Example: - api-cli run module/kickstart1/configure-module --data '{}' +``` +api-cli run configure-module --agent module/kickstart1 --data - <80/tcp 80b8de25945f-infra +d8df02bf6f4a docker.io/library/mariadb:10.11.5 --character-set-s... 9 minutes ago Up 9 minutes 127.0.0.1:20015->80/tcp mariadb-app +9e58e5bd676f docker.io/library/nginx:stable-alpine3.17 nginx -g daemon o... 9 minutes ago Up 9 minutes 127.0.0.1:20015->80/tcp kickstart-app +``` + +you can see what environment variable is inside the container +``` +podman exec kickstart-app env +PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin +TERM=xterm +PKG_RELEASE=1 +MARIADB_DB_HOST=127.0.0.1 +MARIADB_DB_NAME=kickstart +MARIADB_IMAGE=docker.io/mariadb:10.11.5 +MARIADB_DB_TYPE=mysql +container=podman +NGINX_VERSION=1.24.0 +NJS_VERSION=0.7.12 +MARIADB_DB_USER=kickstart +MARIADB_DB_PASSWORD=kickstart +MARIADB_DB_PORT=3306 +HOME=/root +``` + +you can run a shell inside the container + +``` +podman exec -ti kickstart-app sh +/ # +``` ## Testing Test the module using the `test-module.sh` script: diff --git a/build-images.sh b/build-images.sh index 013671d..59304a4 100644 --- a/build-images.sh +++ b/build-images.sh @@ -35,11 +35,17 @@ buildah run \ buildah add "${container}" imageroot /imageroot buildah add "${container}" ui/dist /ui # Setup the entrypoint, ask to reserve one TCP port with the label and set a rootless container +# Select you image(s) with the label org.nethserver.images +# ghcr.io/xxxxx is the GitHub container registry or your own registry or docker.io for Docker Hub +# The image tag is set to latest by default, but can be overridden with the IMAGETAG environment variable +# --label="org.nethserver.images=docker.io/mariadb:10.11.5 docker.io/roundcube/roundcubemail:1.6.4-apache" +# rootfull=0 === rootless container +# tcp-ports-demand=1 number of tcp Port to reserve , 1 is the minimum, can be udp or tcp buildah config --entrypoint=/ \ --label="org.nethserver.authorizations=traefik@node:routeadm" \ --label="org.nethserver.tcp-ports-demand=1" \ --label="org.nethserver.rootfull=0" \ - --label="org.nethserver.images=docker.io/jmalloc/echo-server:latest" \ + --label="org.nethserver.images=docker.io/mariadb:10.11.5 docker.io/nginx:stable-alpine3.17" \ "${container}" # Commit the image buildah commit "${container}" "${repobase}/${reponame}" diff --git a/imageroot/actions/clone-module/50traefik b/imageroot/actions/clone-module/50traefik new file mode 120000 index 0000000..f3e62de --- /dev/null +++ b/imageroot/actions/clone-module/50traefik @@ -0,0 +1 @@ +../restore-module/50traefik \ No newline at end of file diff --git a/imageroot/actions/configure-module/01Hostname_validation b/imageroot/actions/configure-module/01Hostname_validation new file mode 100755 index 0000000..4399c84 --- /dev/null +++ b/imageroot/actions/configure-module/01Hostname_validation @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 + +# +# Copyright (C) 2023 Nethesis S.r.l. +# SPDX-License-Identifier: GPL-3.0-or-later +# + +import os +import sys +import json +import agent +import urllib.request +import urllib.error +import ssl + +agent.set_weight(os.path.basename(__file__), 0) # Validation step, no task progress at all + +# retrieve json data +data = json.load(sys.stdin) + +# Setup default values +host = data.get("host") +# do not test if it is the same host +oldHost = os.environ.get('TRAEFIK_HOST','') + +if host != oldHost and agent.http_route_in_use(domain=host): + agent.set_status('validation-failed') + json.dump([{'field':'host','parameter':'host','value':host,'error':'domain_already_used_in_traefik'}],fp=sys.stdout) + sys.exit(2) diff --git a/imageroot/actions/configure-module/10configure_environment_vars b/imageroot/actions/configure-module/10configure_environment_vars new file mode 100755 index 0000000..f0589aa --- /dev/null +++ b/imageroot/actions/configure-module/10configure_environment_vars @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 + +# +# Copyright (C) 2022 Nethesis S.r.l. +# SPDX-License-Identifier: GPL-3.0-or-later +# + +import json +import sys +import agent + +# Try to parse the stdin as JSON. +# If parsing fails, output everything to stderr +data = json.load(sys.stdin) + +#This is specific to you module, so you need to change it accordingly. +# we read a json stdin {"vars1":true, "var2":"foo", "vars3": 3} and we writ it to .config/state/environment +# Upper case to set environment variable and minor case to read from stdin + +# this values must exists in the json stdin +# agent.set_env("MAIL_SERVER", data["mail_server"]) +# agent.set_env("LDAP_DOMAIN", data["ldap_domain"]) + +# you can add default values for the following variables +# agent.set_env("MAIL_DOMAIN",data.get("mail_domain","")) +# agent.set_env("WOWORKERSCOUNT",data.get("workers_count",3)) +# agent.set_env("AUXILIARYACCOUNT",data.get("auxiliary_account",True)) +# agent.set_env("ACTIVESYNC",data.get("activesync",False)) + + +# Make sure everything is saved inside the environment file +# just before starting systemd unit +#agent.dump_env() diff --git a/imageroot/actions/configure-module/20configure b/imageroot/actions/configure-module/20configure deleted file mode 100755 index 55976a8..0000000 --- a/imageroot/actions/configure-module/20configure +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env python3 - -# -# Copyright (C) 2023 Nethesis S.r.l. -# SPDX-License-Identifier: GPL-3.0-or-later -# - -import json -import sys -import agent -import agent.tasks -import os - -request = json.load(sys.stdin) - -# TODO: do something with the request payload. For example configure a -# custom route in our web router. Here the path is hardcoded as -# 'kickstart'. - -# Configure Traefik to route "/kickstart" path requests to the kickstart service -response = agent.tasks.run( - agent_id=agent.resolve_agent_id('traefik@node'), - action='set-route', - data={ - 'instance': os.environ['MODULE_ID'], - 'url': 'http://127.0.0.1:' + os.environ["TCP_PORT"], - 'http2https': False, - 'lets_encrypt': False, - 'path': '/kickstart', - }, -) - -# Check if traefik configuration has been successfull -agent.assert_exp(response['exit_code'] == 0) diff --git a/imageroot/actions/configure-module/20configure_traefik b/imageroot/actions/configure-module/20configure_traefik new file mode 100755 index 0000000..9412f9e --- /dev/null +++ b/imageroot/actions/configure-module/20configure_traefik @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 + +# +# Copyright (C) 2022 Nethesis S.r.l. +# SPDX-License-Identifier: GPL-3.0-or-later +# + +import json +import sys +import agent +import agent.tasks +import os + +# Try to parse the stdin as JSON. +# If parsing fails, output everything to stderr +data = json.load(sys.stdin) + +# Setup default values +host = data.get("host") +h2hs = data.get("http2https", True) +le = data.get("lets_encrypt", False) + +# Talk with agent using file descriptor. +# Setup configuration from user input. +agent.set_env("TRAEFIK_HOST", host) +agent.set_env("TRAEFIK_HTTP2HTTPS", h2hs) +agent.set_env("TRAEFIK_LETS_ENCRYPT", le) + +# Make sure everything is saved inside the environment file +# just before starting systemd unit +agent.dump_env() + +# Find default traefik instance for current node +default_traefik_id = agent.resolve_agent_id('traefik@node') +if default_traefik_id is None: + sys.exit(2) + +response = agent.tasks.run( + agent_id=agent.resolve_agent_id('traefik@node'), + action='set-route', + data={ + 'instance': os.environ['MODULE_ID'], + 'url': 'http://127.0.0.1:' + os.environ["TCP_PORT"], + 'host': host, + 'http2https': h2hs, + 'lets_encrypt': le + }, +) + +# Check if traefik configuration has been successfull +agent.assert_exp(response['exit_code'] == 0) diff --git a/imageroot/actions/configure-module/80start_services b/imageroot/actions/configure-module/80start_services index 8aed691..ba46344 100755 --- a/imageroot/actions/configure-module/80start_services +++ b/imageroot/actions/configure-module/80start_services @@ -4,6 +4,10 @@ # Copyright (C) 2023 Nethesis S.r.l. # SPDX-License-Identifier: GPL-3.0-or-later # +set -e + +# Redirect any output to the journal (stderr) +exec 1>&2 # If the control reaches this step, the service can be enabled and started diff --git a/imageroot/actions/configure-module/validate-input.json b/imageroot/actions/configure-module/validate-input.json index e441099..f51502d 100644 --- a/imageroot/actions/configure-module/validate-input.json +++ b/imageroot/actions/configure-module/validate-input.json @@ -1,8 +1,36 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "configure-module input", - "$id": "http://schema.nethserver.org/kickstart/configure-module-input.json", + "title": "Configure kickstart", + "$id": "http://nethserver.org/json-schema/task/input/kickstart/configure-module", "description": "Configure kickstart", - "examples": [], - "type": "object" + "examples": [ + { + "host": "kickstart.domain.org", + "http2https": true, + "lets_encrypt": true + } + ], + "type": "object", + "required": [ + "host", + "http2https", + "lets_encrypt" + ], + "properties": { + "host": { + "type": "string", + "description": "Host name for the application, like 'kickstart.domain.org'", + "format": "idn-hostname" + }, + "lets_encrypt": { + "type": "boolean", + "title": "Let's Encrypt certificate", + "description": "Request a valid Let's Encrypt certificate." + }, + "http2https": { + "type": "boolean", + "title": "HTTP to HTTPS redirection", + "description": "Redirect all the HTTP requests to HTTPS" + } + } } \ No newline at end of file diff --git a/imageroot/actions/get-configuration/20read b/imageroot/actions/get-configuration/20read index 3a65b00..abb0f7b 100755 --- a/imageroot/actions/get-configuration/20read +++ b/imageroot/actions/get-configuration/20read @@ -1,14 +1,26 @@ #!/usr/bin/env python3 # -# Copyright (C) 2023 Nethesis S.r.l. +# Copyright (C) 2022 Nethesis S.r.l. # SPDX-License-Identifier: GPL-3.0-or-later # -import json +# +# Read configuration +# + +import os import sys +import json import agent +# Prepare return variable config = {} +# Read current configuration from the environment file +config["host"] = os.getenv("TRAEFIK_HOST","") +config["http2https"] = os.getenv("TRAEFIK_HTTP2HTTPS") == "True" +config["lets_encrypt"] = os.getenv("TRAEFIK_LETS_ENCRYPT") == "True" + +# Dump the configuration to stdout json.dump(config, fp=sys.stdout) diff --git a/imageroot/actions/get-configuration/validate-output.json b/imageroot/actions/get-configuration/validate-output.json index 8188b8c..04e246c 100644 --- a/imageroot/actions/get-configuration/validate-output.json +++ b/imageroot/actions/get-configuration/validate-output.json @@ -1,8 +1,36 @@ { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "get-configuration output", - "$id": "http://schema.nethserver.org/kickstart/get-configuration-output.json", - "description": "Get kickstart configuration", - "examples": [], - "type": "object" + "title": "Get kickstart settings", + "$id": "http://nethserver.org/json-schema/task/input/kickstart/get-configuration", + "description": "Get kickstart settings", + "examples": [ + { + "host": "kickstart.domain.org", + "http2https": true, + "lets_encrypt": true + } + ], + "type": "object", + "required": [ + "host", + "http2https", + "lets_encrypt" + ], + "properties": { + "host": { + "type": "string", + "description": "Host name for the application, like 'kickstart.domain.org'", + "format": "idn-hostname" + }, + "lets_encrypt": { + "type": "boolean", + "title": "Let's Encrypt certificate", + "description": "Request a valid Let's Encrypt certificate." + }, + "http2https": { + "type": "boolean", + "title": "HTTP to HTTPS redirection", + "description": "Redirect all the HTTP requests to HTTPS" + } + } } \ No newline at end of file diff --git a/imageroot/actions/restore-module/06copyenv b/imageroot/actions/restore-module/06copyenv new file mode 100755 index 0000000..1ac5e48 --- /dev/null +++ b/imageroot/actions/restore-module/06copyenv @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 + +# +# Copyright (C) 2022 Nethesis S.r.l. +# SPDX-License-Identifier: GPL-3.0-or-later +# + +import sys +import json +import agent + +request = json.load(sys.stdin) + +original_environment = request['environment'] + +for evar in [ + "TRAEFIK_HOST", + "TRAEFIK_HTTP2HTTPS", + "TRAEFIK_LETS_ENCRYPT", + ]: + agent.set_env(evar, original_environment[evar]) + +agent.dump_env() diff --git a/imageroot/actions/restore-module/50traefik b/imageroot/actions/restore-module/50traefik new file mode 100755 index 0000000..e6a6fdd --- /dev/null +++ b/imageroot/actions/restore-module/50traefik @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 + +# +# Copyright (C) 2023 Nethesis S.r.l. +# SPDX-License-Identifier: GPL-3.0-or-later +# + +import sys +import json +import agent +import os + +request = json.load(sys.stdin) +renv = request['environment'] + +configure_retval = agent.tasks.run(agent_id=os.environ['AGENT_ID'], action='configure-module', data={ + "lets_encrypt": renv["TRAEFIK_LETS_ENCRYPT"], + "host": renv["TRAEFIK_HOST"], + "http2https": renv["TRAEFIK_HTTP2HTTPS"], +}) +agent.assert_exp(configure_retval['exit_code'] == 0, "The configure-module subtask failed!") diff --git a/imageroot/etc/state-include.conf b/imageroot/etc/state-include.conf new file mode 100644 index 0000000..33946d2 --- /dev/null +++ b/imageroot/etc/state-include.conf @@ -0,0 +1,9 @@ +# +# Roundcube state/backup include patterns for Restic +# Syntax reference: https://pkg.go.dev/path/filepath#Glob +# Restic --files-from: https://restic.readthedocs.io/en/stable/040_backup.html#including-files +# List here what you want to save during backup : volumes or file Path + + +#state/smarthost.env +#volumes/html diff --git a/imageroot/systemd/user/kickstart-app.service b/imageroot/systemd/user/kickstart-app.service new file mode 100644 index 0000000..eb711be --- /dev/null +++ b/imageroot/systemd/user/kickstart-app.service @@ -0,0 +1,42 @@ +# +# Copyright (C) 2022 Nethesis S.r.l. +# SPDX-License-Identifier: GPL-3.0-or-later +# + +[Unit] +Description=Podman kickstart-app.service +BindsTo=kickstart.service +After=kickstart.service mariadb-app.service + +[Service] +Environment=PODMAN_SYSTEMD_UNIT=%n +EnvironmentFile=%S/state/environment +EnvironmentFile=-%S/state/smarthost.env +WorkingDirectory=%S/state +Restart=always +TimeoutStopSec=70 +ExecStartPre=/bin/mkdir -p config +ExecStartPre=/bin/rm -f %t/kickstart-app.pid %t/kickstart-app.ctr-id +ExecStartPre=-runagent discover-smarthost +ExecStart=/usr/bin/podman run --conmon-pidfile %t/kickstart-app.pid \ + --cidfile %t/kickstart-app.ctr-id --cgroups=no-conmon \ + --pod-id-file %t/kickstart.pod-id --replace -d --name kickstart-app \ + --volume html:/var/www/html/:Z \ + --volume ./config:/tmp:Z \ + --env=MARIADB_* \ + --env MARIADB_DB_TYPE=mysql \ + --env MARIADB_DB_HOST=127.0.0.1 \ + --env MARIADB_DB_PORT=3306 \ + --env MARIADB_DB_USER=kickstart \ + --env MARIADB_DB_PASSWORD=kickstart \ + --env MARIADB_DB_NAME=kickstart \ + ${NGINX_IMAGE} +ExecStop=/usr/bin/podman stop --ignore --cidfile %t/kickstart-app.ctr-id -t 10 +ExecReload=/usr/bin/podman kill -s HUP kickstart-app +SyslogIdentifier=%u +ExecStopPost=/usr/bin/podman rm --ignore -f --cidfile %t/kickstart-app.ctr-id +PIDFile=%t/kickstart-app.pid +Type=forking + +[Install] +WantedBy=default.target diff --git a/imageroot/systemd/user/kickstart.service b/imageroot/systemd/user/kickstart.service index dfcdce8..2e19fad 100644 --- a/imageroot/systemd/user/kickstart.service +++ b/imageroot/systemd/user/kickstart.service @@ -1,5 +1,5 @@ # -# Copyright (C) 2023 Nethesis S.r.l. +# Copyright (C) 2022 Nethesis S.r.l. # SPDX-License-Identifier: GPL-3.0-or-later # @@ -7,27 +7,26 @@ # This systemd unit starts a kickstart instance using Podman. # Most parts of this file come from podman-generate-systemd. # + [Unit] -Description=kickstart server +Description=Podman kickstart.service +Requires=mariadb-app.service kickstart-app.service +Before=mariadb-app.service kickstart-app.service [Service] Environment=PODMAN_SYSTEMD_UNIT=%n -EnvironmentFile=%S/state/environment -WorkingDirectory=%S/state +EnvironmentFile=-%S/state/environment Restart=always -ExecStartPre=/bin/rm -f %t/kickstart.pid %t/kickstart.ctr-id -ExecStartPre=-runagent discover-smarthost -ExecStart=/usr/bin/podman run \ - --detach \ - --conmon-pidfile=%t/kickstart.pid \ - --cidfile=%t/kickstart.ctr-id \ - --cgroups=no-conmon \ - --replace --name=%N \ - --publish=127.0.0.1:${TCP_PORT}:8080 \ - --env-file=smarthost.env \ - ${ECHO_SERVER_IMAGE} -ExecStop=/usr/bin/podman stop --ignore --cidfile %t/kickstart.ctr-id -t 10 -ExecStopPost=/usr/bin/podman rm --ignore -f --cidfile %t/kickstart.ctr-id +TimeoutStopSec=70 +ExecStartPre=/bin/rm -f %t/kickstart.pid %t/kickstart.pod-id +ExecStartPre=/usr/bin/podman pod create --infra-conmon-pidfile %t/kickstart.pid \ + --pod-id-file %t/kickstart.pod-id \ + --name kickstart \ + --publish 127.0.0.1:${TCP_PORT}:80 \ + --replace +ExecStart=/usr/bin/podman pod start --pod-id-file %t/kickstart.pod-id +ExecStop=/usr/bin/podman pod stop --ignore --pod-id-file %t/kickstart.pod-id -t 10 +ExecStopPost=/usr/bin/podman pod rm --ignore -f --pod-id-file %t/kickstart.pod-id PIDFile=%t/kickstart.pid Type=forking diff --git a/imageroot/systemd/user/mariadb-app.service b/imageroot/systemd/user/mariadb-app.service new file mode 100644 index 0000000..d4212a1 --- /dev/null +++ b/imageroot/systemd/user/mariadb-app.service @@ -0,0 +1,40 @@ +# +# Copyright (C) 2022 Nethesis S.r.l. +# SPDX-License-Identifier: GPL-3.0-or-later +# + +[Unit] +Description=Podman mariadb-app.service +BindsTo=kickstart.service +After=kickstart.service + +[Service] +Environment=PODMAN_SYSTEMD_UNIT=%n +EnvironmentFile=%S/state/environment +# EnvironmentFile=%S/state/secrets/passwords.secret +Restart=always +TimeoutStopSec=70 +ExecStartPre=/bin/rm -f %t/mariadb-app.pid %t/mariadb-app.ctr-id +ExecStart=/usr/bin/podman run --conmon-pidfile %t/mariadb-app.pid \ + --cidfile %t/mariadb-app.ctr-id --cgroups=no-conmon \ + --pod-id-file %t/kickstart.pod-id --replace -d --name mariadb-app \ + --env-file=%S/state/environment \ + --volume mysql-data:/var/lib/mysql/:Z \ + --env MARIADB_ROOT_PASSWORD=Nethesis,1234 \ + --env MARIADB_DATABASE=kickstart \ + --env MARIADB_USER=kickstart \ + --env MARIADB_PASSWORD=kickstart \ + --env MARIADB_AUTO_UPGRADE=1 \ + ${MARIADB_IMAGE} \ + --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci +ExecStartPost=/usr/bin/podman exec mariadb-app /bin/bash -c 'printf "[client] \npassword=Nethesis,1234" > /root/.my.cnf' +ExecStartPost=/usr/bin/podman exec mariadb-app /bin/bash -c "while ! mysqladmin ping -h localhost -P 3306 -u root; do sleep 1; done" +ExecStop=/usr/bin/podman stop --ignore --cidfile %t/mariadb-app.ctr-id -t 10 +ExecStopPost=/usr/bin/podman rm --ignore -f --cidfile %t/mariadb-app.ctr-id +ExecReload=/usr/bin/podman kill -s HUP mariadb-app +SyslogIdentifier=%u +PIDFile=%t/mariadb-app.pid +Type=forking + +[Install] +WantedBy=default.target diff --git a/imageroot/update-module.d/20restart b/imageroot/update-module.d/20restart new file mode 100755 index 0000000..ae0f791 --- /dev/null +++ b/imageroot/update-module.d/20restart @@ -0,0 +1,15 @@ +#!/bin/bash + +# +# Copyright (C) 2023 Nethesis S.r.l. +# SPDX-License-Identifier: GPL-3.0-or-later +# + +# If the control reaches this step, the service can be enabled and started + +set -e + +# Redirect any output to the journal (stderr) +exec 1>&2 + +systemctl --user try-restart kickstart.service diff --git a/package.json b/package.json new file mode 100644 index 0000000..da87c17 --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "@nethserver/ns8-ui-lib": "^0.1.27" + } +} diff --git a/ui/public/i18n/en/translation.json b/ui/public/i18n/en/translation.json index 0a04330..ead6f9c 100644 --- a/ui/public/i18n/en/translation.json +++ b/ui/public/i18n/en/translation.json @@ -24,7 +24,15 @@ "title": "Settings", "configure_instance": "Configure {instance}", "save": "Save", - "test_field": "Test field" + "kickstart_fqdn": "kickstart hostname (FQDN)", + "lets_encrypt": "Request LE Certificate", + "http_to_https": "Force HTTPS", + "enabled": "Enabled", + "disabled": "Disabled", + "advanced": "Advanced", + "configuring": "Configuring", + "instance_configuration": "Configure kickstart", + "domain_already_used_in_traefik": "Domain already used in traefik" }, "about": { "title": "About" diff --git a/ui/src/views/Settings.vue b/ui/src/views/Settings.vue index 10dd8c2..5ae59f5 100644 --- a/ui/src/views/Settings.vue +++ b/ui/src/views/Settings.vue @@ -1,5 +1,5 @@