From 8f74aa2c229cbb79ff651c56dcac75eb14bfc333 Mon Sep 17 00:00:00 2001 From: Alexandre Aubin Date: Thu, 9 Nov 2023 22:48:54 +0100 Subject: [PATCH] appgenerator: propagate changes to template files + rework preview/zipdownload with two submit buttons mapped to the same route --- tools/app_generator/app_generator.py | 306 +++--------- tools/app_generator/templates/_common.sh.j2 | 12 +- tools/app_generator/templates/backup.j2 | 86 ++-- tools/app_generator/templates/change_url.j2 | 28 +- tools/app_generator/templates/config.j2 | 109 ----- tools/app_generator/templates/index.html | 499 +------------------- tools/app_generator/templates/install.j2 | 196 +++----- tools/app_generator/templates/manifest.j2 | 158 +++---- tools/app_generator/templates/nginx.j2 | 35 ++ tools/app_generator/templates/remove.j2 | 41 +- tools/app_generator/templates/restore.j2 | 66 +-- tools/app_generator/templates/systemd.j2 | 54 +++ tools/app_generator/templates/upgrade.j2 | 90 ++-- 13 files changed, 459 insertions(+), 1221 deletions(-) delete mode 100644 tools/app_generator/templates/config.j2 create mode 100644 tools/app_generator/templates/nginx.j2 create mode 100644 tools/app_generator/templates/systemd.j2 diff --git a/tools/app_generator/app_generator.py b/tools/app_generator/app_generator.py index ccfbf151..810d79a0 100644 --- a/tools/app_generator/app_generator.py +++ b/tools/app_generator/app_generator.py @@ -1,4 +1,7 @@ #### Imports +from io import BytesIO +import re +import os import jinja2 as j2 from flask import ( Flask, @@ -301,16 +304,6 @@ class Resources(FlaskForm): default=False, ) - ## TODO - # These infos are used by https://github.com/YunoHost/apps/blob/master/tools/autoupdate_app_sources/autoupdate_app_sources.py - # to auto-update the previous asset urls and sha256sum + manifest version - # assuming the upstream's code repo is on github and relies on tags or releases - # See the 'sources' resource documentation for more details - - # autoupdate.strategy = "latest_github_tag" - - - apt_dependencies = StringField( "Dépendances à installer via apt (séparées par des virgules et/ou espaces)", render_kw={ @@ -375,11 +368,6 @@ class SpecificTechnology(FlaskForm): # PHP # - # TODO : add a tip about adding the PHP dependencies in the APT deps earlier - - # TODO : add a tip about the auto-prepared nginx config that will include the fastcgi snippet - - use_composer = BooleanField( "Utiliser composer", description="Composer est un gestionnaire de dépendance PHP utilisé par certaines apps", @@ -405,8 +393,6 @@ class SpecificTechnology(FlaskForm): # NodeJS / Python / Ruby / ... - # TODO : add a tip about the auto-prepared nginx config that will include a proxy_pass / reverse proxy to an auto-prepared systemd service - systemd_execstart = StringField( "Commande pour lancer le daemon de l'app (depuis le service systemd)", description="Corresponds to 'ExecStart' statement in systemd. You can use '__INSTALL_DIR__' to refer to the install directory, or '__APP__' to refer to the app id", @@ -443,7 +429,7 @@ class AppConfig(FlaskForm): class MoreAdvanced(FlaskForm): - supports_change_url = BooleanField( + enable_change_url = BooleanField( "Gérer le changement d'URL d'installation (script change_url)", default=True, render_kw={ @@ -462,7 +448,7 @@ class MoreAdvanced(FlaskForm): # custom_log_file = "/var/log/$app/$app.log" "/var/log/nginx/${domain}-error.log" use_fail2ban = BooleanField( "Protéger l'application des attaques par force brute (via fail2ban)", - default=True, + default=False, render_kw={ "title": "Si l'application genère des journaux (log) d'erreurs de connexion, cette option permet de bannir automatiquement les IP au bout d'un certain nombre d'essais de mot de passe. Recommandé." }, @@ -497,249 +483,97 @@ class MoreAdvanced(FlaskForm): class GeneratorForm( GeneralInfos, IntegrationInfos, UpstreamInfos, InstallQuestions, Resources, SpecificTechnology, AppConfig, MoreAdvanced ): + + class Meta: + csrf = False + generator_mode = SelectField( "Mode du générateur", description="En mode tutoriel, l'application générée contiendra des commentaires additionnels pour faciliter la compréhension. En version épurée, l'application générée ne contiendra que le minimum nécessaire.", - choices=[("false", "Version épurée"), ("true", "Version tutoriel")], + choices=[("simple", "Version épurée"), ("tutorial", "Version tutoriel")], default="true", validators=[DataRequired()], ) - submit = SubmitField("Soumettre") + submit_preview = SubmitField("Prévisualiser") + submit_download = SubmitField("Télécharger le .zip") #### Web pages @app.route("/", methods=["GET", "POST"]) def main_form_route(): - parameters = {} main_form = GeneratorForm() + app_files = [] if request.method == "POST": - result = request.form - results = dict(result) - # print("[DEBUG] This is a POST request") - # print(results) - for key, value in results.items(): - parameters[key] = value - parameters["preview"] = True - if main_form.validate_on_submit(): - parameters["invalid_form"] = False - print() - print("formulaire valide") - # print(main_form.data.items()) - for key, value in main_form.data.items(): - parameters[key] = value # TODO change the name + if not main_form.validate_on_submit(): + print("not validated?") + print(main_form.errors) - templates = ( - "templates/manifest.j2", - "templates/install.j2", - "templates/remove.j2", - "templates/backup.j2", - "templates/restore.j2", - "templates/upgrade.j2", - "templates/config.j2", - "templates/change_url.j2", - "templates/_common.sh.j2", - ) - markdown_to_html = dict() - for template in templates: - markdown_content, html_content = markdown_file_to_html_string(template) - template_key = template.split("templates/")[1].split(".j2")[ - 0 - ] # Let's retrieve what's the exact template used - markdown_to_html[template_key] = { - "markdown_content": markdown_content, - "html_content": html_content, - } - # print(markdown_to_html["markdown_content"]) - # print(markdown_to_html["html_content"]) - - ## Prepare the file contents for the download button - # print(markdown_to_html['manifest']['markdown_content']) - - # Workaround so /download_zip can access the content - FIXME ? - global template_manifest_content - global template_install_content - global template_remove_content - global template_backup_content - global template_restore_content - global template_upgrade_content - global template_config_content - global template_change_url_content - global template_common_sh_content - global custom_config_file - global nginx_config_file - global systemd_config_file - global cron_config_file - - template_manifest_content = render_template_string( - markdown_to_html["manifest"]["markdown_content"], - parameters=parameters, - main_form=main_form, - ) - - template_install_content = render_template_string( - markdown_to_html["install"]["markdown_content"], - parameters=parameters, - main_form=main_form, - markdown_to_html=markdown_to_html["install"], - ) - - template_remove_content = render_template_string( - markdown_to_html["remove"]["markdown_content"], - parameters=parameters, - main_form=main_form, - markdown_to_html=markdown_to_html["remove"], - ) - - template_backup_content = render_template_string( - markdown_to_html["backup"]["markdown_content"], - parameters=parameters, - main_form=main_form, - markdown_to_html=markdown_to_html["backup"], - ) - - template_restore_content = render_template_string( - markdown_to_html["restore"]["markdown_content"], - parameters=parameters, - main_form=main_form, - markdown_to_html=markdown_to_html["restore"], - ) - - template_upgrade_content = render_template_string( - markdown_to_html["upgrade"]["markdown_content"], - parameters=parameters, - main_form=main_form, - markdown_to_html=markdown_to_html["upgrade"], - ) - - template_config_content = render_template_string( - markdown_to_html["config"]["markdown_content"], - parameters=parameters, - main_form=main_form, - markdown_to_html=markdown_to_html["config"], - ) - - template_common_sh_content = render_template_string( - markdown_to_html["_common.sh"]["markdown_content"], - parameters=parameters, - main_form=main_form, - markdown_to_html=markdown_to_html["_common.sh"], - ) - - if parameters["supports_change_url"]: - template_change_url_content = render_template_string( - markdown_to_html["change_url"]["markdown_content"], - parameters=parameters, - main_form=main_form, - markdown_to_html=markdown_to_html["change_url"], - ) - else: - template_change_url_content = False - - print(parameters["custom_config_file"]) - print(parameters["use_custom_config_file"]) return render_template( - "index.html", - parameters=parameters, - main_form=main_form, - markdown_to_html=markdown_to_html, - template_manifest_content=template_manifest_content, - template_install_content=template_install_content, - template_remove_content=template_remove_content, - template_backup_content=template_backup_content, - template_restore_content=template_restore_content, - template_upgrade_content=template_upgrade_content, - template_config_content=template_config_content, - template_change_url_content=template_change_url_content, - template_common_sh_content=template_common_sh_content, - nginx_config_file=parameters["nginx_config_file"], - systemd_config_file=parameters["systemd_config_file"], - custom_config_file=parameters["custom_config_file"], - cron_config_file=parameters["cron_config_file"], + "index.html", main_form=main_form, generated_files={} ) - else: - print("[DEBUG] Formulaire invalide: ", main_form.errors) - parameters["preview"] = False - parameters["invalid_form"] = True - elif request.method == "GET": - parameters["preview"] = False + submit_mode = "preview" if main_form.submit_preview.data else "download" + + class AppFile: + def __init__(self, id_, destination_path=None): + self.id = id_ + self.destination_path = destination_path + self.content = None + + app_files = [ + AppFile("manifest", "manifest.toml"), + AppFile("_common.sh", "scripts/_common.sh"), + AppFile("install", "scripts/install"), + AppFile("remove", "scripts/remove"), + AppFile("backup", "scripts/backup"), + AppFile("restore", "scripts/restore"), + AppFile("upgrade", "scripts/upgrade"), + AppFile("nginx", "conf/nginx.conf"), + ] + + if main_form.enable_change_url: + app_files.append(AppFile("change_url", "scripts/change_url")) + + if main_form.main_technology not in ["none", "php"]: + app_files.append(AppFile("systemd", "conf/systemd.service")) + + if main_form.main_technology == "php": + app_files.append(AppFile("php", "conf/extra_php-fpm.conf")) + + template_dir = os.path.dirname(__file__) + "/templates/" + for app_file in app_files: + template = open(template_dir + app_file.id + ".j2").read() + app_file.content = render_template_string(template, data=dict(request.form)) + app_file.content = re.sub(r'\n\s+$', '\n', app_file.content, flags=re.M) + app_file.content = re.sub(r'\n{3,}', '\n\n', app_file.content, flags=re.M) + + # TODO + #if main_form.use_custom_config_file: + # app_files.append(AppFile("appconf", "conf/" + main_form.custom_config_file)) + # app_files[-1].content = main_form.custom_config_file_content + + # TODO : same for cron job + + if submit_mode == "download": + # Generate the zip file + f = BytesIO() + with zipfile.ZipFile(f, "w") as zf: + for app_file in app_files: + print(app_file.id) + zf.writestr(app_file.destination_path, app_file.content) + f.seek(0) + # Send the zip file to the user + return send_file(f, as_attachment=True, download_name=request.form["app_id"] + ".zip") return render_template( - "index.html", parameters=parameters, main_form=main_form + "index.html", main_form=main_form, generated_files=app_files ) -@app.route("/download_zip", methods=("GET", "POST")) -def telecharger_zip(): - # Retrieve arguments - print("Génération du .zip") - app_id = request.args.get("app_id") - print("Génération du .zip pour " + app_id) - - custom_config_file = parse.unquote(request.args.get("custom_config_file")) - custom_config_file_content = parse.unquote( - request.args.get("custom_config_file_content") - ) - systemd_config_file = parse.unquote(request.args.get("systemd_config_file")) - nginx_config_file = parse.unquote(request.args.get("nginx_config_file")) - cron_config_file = parse.unquote(request.args.get("cron_config_file")) - - global template_manifest_content - global template_install_content - global template_remove_content - global template_backup_content - global template_restore_content - global template_upgrade_content - global template_config_content - global template_change_url_content - global template_common_sh_content - - # global custom_config_file - - use_php = request.args.get("use_php") - print("PHP") - print(use_php) - php_config_file = parse.unquote(request.args.get("php_config_file")) - php_config_file_content = parse.unquote(request.args.get("php_config_file_content")) - - archive_name = ( - app_id + ".zip" - ) # Actually it's the javascript that decide of the filename… this is only an internal name - - # Generate the zip file (will be stored in the working directory) - with zipfile.ZipFile(archive_name, "w") as zf: - # Add text in directly in the ZIP, as a file - zf.writestr("manifest.toml", template_manifest_content) - zf.writestr("scripts/install", template_install_content) - zf.writestr("scripts/remove", template_remove_content) - zf.writestr("scripts/backup", template_backup_content) - zf.writestr("scripts/restore", template_restore_content) - zf.writestr("scripts/upgrade", template_upgrade_content) - zf.writestr("scripts/_common_sh", template_common_sh_content) - - if template_config_content: - zf.writestr("scripts/config", template_config_content) - if template_change_url_content: - zf.writestr("scripts/change_url", template_change_url_content) - if custom_config_file: - zf.writestr("conf/" + custom_config_file, custom_config_file_content) - if systemd_config_file: - zf.writestr("conf/systemd.service", systemd_config_file) - if nginx_config_file: - zf.writestr("conf/nginx.conf", nginx_config_file) - if cron_config_file: - zf.writestr("conf/task.conf", cron_config_file) - if use_php == "True": - zf.writestr("conf/" + php_config_file, php_config_file_content) - - # Send the zip file to the user - return send_file(archive_name, as_attachment=True) - - #### Running the web server if __name__ == "__main__": app.run(debug=True) diff --git a/tools/app_generator/templates/_common.sh.j2 b/tools/app_generator/templates/_common.sh.j2 index ea830fd7..eb200e90 100644 --- a/tools/app_generator/templates/_common.sh.j2 +++ b/tools/app_generator/templates/_common.sh.j2 @@ -4,17 +4,11 @@ # COMMON VARIABLES #================================================= -{% if parameters["use_nodejs"] -%} -nodejs_version={{ parameters["use_nodejs_version"]}} +{% if data.use_nodejs -%} +nodejs_version={{ data.nodejs_version }} {% endif -%} + #================================================= # PERSONAL HELPERS #================================================= -#================================================= -# EXPERIMENTAL HELPERS -#================================================= - -#================================================= -# FUTURE OFFICIAL HELPERS -#================================================= diff --git a/tools/app_generator/templates/backup.j2 b/tools/app_generator/templates/backup.j2 index 495cce94..db3c8be8 100644 --- a/tools/app_generator/templates/backup.j2 +++ b/tools/app_generator/templates/backup.j2 @@ -1,9 +1,9 @@ #!/bin/bash -#### App file generated with YoloGen, the YunoHost app generator, version {{ parameters['GENERATOR_VERSION'] }}. -{% if parameters["tutorial"] -%} # This is the tutorial version of the app. +#### App file generated with YoloGen, the YunoHost app generator, version {{ data.GENERATOR_VERSION }}. +{% if data.generator_mode == 'tutorial' -%} # This is the tutorial version of the app. # It contains extra commands to explain what should be done in case you want to adjust some part of the script. # Once you are done, you may remove them. -{% endif -%} +{% endif %} #================================================= # GENERIC START #================================================= @@ -19,91 +19,63 @@ source /usr/share/yunohost/helpers #================================================= ynh_print_info --message="Declaring files to be backed up..." -{% if parameters["tutorial"] -%} +{% if data.generator_mode == 'tutorial' -%} ### N.B. : the following 'ynh_backup' calls are only a *declaration* of what needs ### to be backuped and not an actual copy of any file. The actual backup that ### creates and fill the archive with the files happens in the core after this ### script is called. Hence ynh_backups calls takes basically 0 seconds to run. -{% endif -%} - -#================================================= -# BACKUP THE APP MAIN DIR -#================================================= +{% endif %} +{% if data.data_dir -%} ynh_backup --src_path="$install_dir" +{% endif %} -{% if parameters["data_dir"] -%} -#================================================= -# BACKUP THE DATA DIR -#================================================= - - {% if parameters["tutorial"] -%} +{% if data.data_dir -%} +{% if data.generator_mode == 'tutorial' -%} # The --is_big parameters ensure this folder is not included in the backup by default (taking less space), except if BACKUP_CORE_ONLY=0 is passed before the backup command. You might want to document that for your users. - {% endif -%} -# Only relevant if there is a "data_dir" resource for this app +{% endif %} ynh_backup --src_path="$data_dir" --is_big -{% endif -%} -#================================================= -# BACKUP THE NGINX CONFIGURATION -#================================================= +{% endif %} ynh_backup --src_path="/etc/nginx/conf.d/$domain.d/$app.conf" -{% if parameters["use_php"] -%} -#================================================= -# BACKUP THE PHP-FPM CONFIGURATION -#================================================= - +{% if data.main_technology == "php" -%} ynh_backup --src_path="/etc/php/$phpversion/fpm/pool.d/$app.conf" -{% endif -%} -{% if parameters["use_fail2ban"] -%} -#================================================= -# BACKUP FAIL2BAN CONFIGURATION -#================================================= +{% endif %} +{% if data.use_fail2ban -%} ynh_backup --src_path="/etc/fail2ban/jail.d/$app.conf" ynh_backup --src_path="/etc/fail2ban/filter.d/$app.conf" -{% endif -%} -{% if parameters["use_logrotate"] -%} -#================================================= -# SPECIFIC BACKUP -#================================================= -# BACKUP LOGROTATE -#================================================= +{% endif %} +{% if data.use_logrotate -%} ynh_backup --src_path="/etc/logrotate.d/$app" -{% endif -%} -{% if parameters["use_systemd_service"] -%} -#================================================= -# BACKUP SYSTEMD -#================================================= +{% endif %} +{% if data.main_technology not in ["php", "none"] -%} ynh_backup --src_path="/etc/systemd/system/$app.service" -{% endif -%} -#================================================= -# BACKUP VARIOUS FILES -#================================================= -{% if parameters["use_cron"] -%} +{% endif %} + +{% if data.use_cron -%} ynh_backup --src_path="/etc/cron.d/$app" -{% endif -%} -ynh_backup --src_path="/etc/$app/" +{% endif %} - -{% if parameters["use_db"] -%} +{% if data.database != 'false' -%} #================================================= # BACKUP THE DATABASE #================================================= -ynh_print_info --message="Backing up the {{ parameters['use_db'] }} database..." +ynh_print_info --message="Backing up the {{ data.database }} database..." ### (However, things like MySQL dumps *do* take some time to run, though the ### copy of the generated dump to the archive still happens later) -{% if parameters["use_db"] == 'mysql' -%} +{% if data.use_db == 'mysql' -%} ynh_mysql_dump_db --database="$db_name" > db.sql -{% elif parameters["use_db"] == 'postgresql' -%} +{% elif data.use_db == 'postgresql' -%} ynh_psql_dump_db --database="$db_name" > db.sql -{% endif -%} -{% endif -%} +{% endif %} +{% endif %} + #================================================= # END OF SCRIPT #================================================= diff --git a/tools/app_generator/templates/change_url.j2 b/tools/app_generator/templates/change_url.j2 index 92a1d85e..bfd0a14c 100644 --- a/tools/app_generator/templates/change_url.j2 +++ b/tools/app_generator/templates/change_url.j2 @@ -1,9 +1,11 @@ #!/bin/bash -#### App file generated with YoloGen, the YunoHost app generator, version {{ parameters['GENERATOR_VERSION'] }}. -{% if parameters["tutorial"] -%} # This is the tutorial version of the app. + +{% if data.generator_mode == 'tutorial' -%} +# This is the tutorial version of the app. # It contains extra commands to explain what should be done in case you want to adjust some part of the script. # Once you are done, you may remove them. -{% endif -%} +{% endif %} + #================================================= # GENERIC STARTING #================================================= @@ -13,20 +15,19 @@ source _common.sh source /usr/share/yunohost/helpers +{% if data.main_technology not in ["php", "none"] -%} #================================================= -# STANDARD MODIFICATIONS -#================================================= -{% if parameters["use_systemd_service"] -%} # STOP SYSTEMD SERVICE #================================================= -ynh_script_progression --message="Stopping a systemd service..." --weight=1 +ynh_script_progression --message="Stopping a systemd service..." ynh_systemd_action --service_name=$app --action="stop" --log_path="/var/log/$app/$app.log" -{% endif -%} +{% endif %} + #================================================= # MODIFY URL IN NGINX CONF #================================================= -ynh_script_progression --message="Updating NGINX web server configuration..." --weight=1 +ynh_script_progression --message="Updating NGINX web server configuration..." ynh_change_url_nginx_config @@ -36,16 +37,15 @@ ynh_change_url_nginx_config # ... #================================================= +{% if data.main_technology not in ["php", "none"] -%} #================================================= -# GENERIC FINALISATION -#================================================= -{% if parameters["use_systemd_service"] -%} # START SYSTEMD SERVICE #================================================= -ynh_script_progression --message="Starting a systemd service..." --weight=1 +ynh_script_progression --message="Starting a systemd service..." ynh_systemd_action --service_name=$app --action="start" --log_path="/var/log/$app/$app.log" -{% endif -%} +{% endif %} + #================================================= # END OF SCRIPT #================================================= diff --git a/tools/app_generator/templates/config.j2 b/tools/app_generator/templates/config.j2 deleted file mode 100644 index acc66a01..00000000 --- a/tools/app_generator/templates/config.j2 +++ /dev/null @@ -1,109 +0,0 @@ -#!/bin/bash -#### App file generated with YoloGen, the YunoHost app generator, version {{ parameters['GENERATOR_VERSION'] }}. -{% if parameters["tutorial"] -%} # This is the tutorial version of the app. -# It contains extra commands to explain what should be done in case you want to adjust some part of the script. -# Once you are done, you may remove them. -{% endif -%} - -{% if parameters["tutorial"] -%} -# In simple cases, you don't need a config script. - -# With a simple config_panel.toml, you can write in the app settings, in the -# upstream config file or replace complete files (logo ...) and restart services. - -# The config scripts allows you to go further, to handle specific cases -# (validation of several interdependent fields, specific getter/setter for a value, -# display dynamic informations or choices, pre-loading of config type .cube... ). -{% endif -%} -#================================================= -# GENERIC STARTING -#================================================= -# IMPORT GENERIC HELPERS -#================================================= - -source /usr/share/yunohost/helpers - -ynh_abort_if_errors - -#================================================= -# RETRIEVE ARGUMENTS -#================================================= - -install_dir=$(ynh_app_setting_get --app=$app --key=install_dir) - -#================================================= -# SPECIFIC GETTERS FOR TOML SHORT KEY -#================================================= - -get__amount() { - # Here we can imagine to have an API call to stripe to know the amount of donation during a month - local amount = 200 - - # It's possible to change some properties of the question by overriding it: - if [ $amount -gt 100 ] - then - cat << EOF -style: success -value: $amount -ask: - en: A lot of donation this month: **$amount €** -EOF - else - cat << EOF -style: danger -value: $amount -ask: - en: Not so much donation this month: $amount € -EOF - fi -} - -get__prices() { - local prices = "$(grep "DONATION\['" "$install_dir/settings.py" | sed -r "s@^DONATION\['([^']*)'\]\['([^']*)'\] = '([^']*)'@\1/\2/\3@g" | sed -z 's/\n/,/g;s/,$/\n/')" - if [ "$prices" == "," ]; - then - # Return YNH_NULL if you prefer to not return a value at all. - echo YNH_NULL - else - echo $prices - fi -} - - -#================================================= -# SPECIFIC VALIDATORS FOR TOML SHORT KEYS -#================================================= -validate__publishable_key() { - - # We can imagine here we test if the key is really a publisheable key - (is_secret_key $publishable_key) && - echo 'This key seems to be a secret key' -} - -#================================================= -# SPECIFIC SETTERS FOR TOML SHORT KEYS -#================================================= -set__prices() { - - #--------------------------------------------- - # IMPORTANT: setter are trigger only if a change is detected - #--------------------------------------------- - for price in $(echo $prices | sed "s/,/ /"); do - frequency=$(echo $price | cut -d/ -f1) - currency=$(echo $price | cut -d/ -f2) - price_id=$(echo $price | cut -d/ -f3) - sed "d/DONATION\['$frequency'\]\['$currency'\]" "$install_dir/settings.py" - - echo "DONATION['$frequency']['$currency'] = '$price_id'" >> "$install_dir/settings.py" - done - - #--------------------------------------------- - # IMPORTANT: to be able to upgrade properly, you have to saved the value in settings too - #--------------------------------------------- - ynh_app_setting_set $app prices $prices -} - -#================================================= -# GENERIC FINALIZATION -#================================================= -ynh_app_config_run $1 diff --git a/tools/app_generator/templates/index.html b/tools/app_generator/templates/index.html index ba22c9f1..10a14486 100644 --- a/tools/app_generator/templates/index.html +++ b/tools/app_generator/templates/index.html @@ -31,189 +31,14 @@ YunoHost app generator {% endblock %} {% block content %} - - - -

Formulaire de génération d'une application Yunohost

{{ main_form.hidden_tag() }} +
{{ wtf.form_errors(main_form, hiddens="only") }} +
{{ form_field(main_form.generator_mode) }} @@ -265,7 +90,6 @@ YunoHost app generator
-

4/8 - Questions à poser pendant l'installation

@@ -423,7 +247,7 @@ YunoHost app generator

8/8 - Options avancées

- {{ form_field(main_form.supports_change_url) }} + {{ form_field(main_form.enable_change_url) }} {{ form_field(main_form.use_logrotate) }} {{ form_field(main_form.use_fail2ban) }} {{ form_field(main_form.fail2ban_regex) }} @@ -434,317 +258,26 @@ YunoHost app generator
- {{ main_form.submit(class="btn btn-primary") }} + {{ main_form.submit_preview(class="btn btn-primary") }} + {{ main_form.submit_download(class="btn btn-primary") }}
- - {% if parameters['invalid_form'] %} -

Formulaire invalide, veuillez vérifier quel champ contient une erreur svp.

- {% endif %}
-{% if parameters['preview'] %} {# is defined #} -
- Afficher le code des fichiers principaux -

Voici le code de l'application {{parameters['app.name']}}

- - - - - - -

Manifeste (manifest.toml)

-
-
-
-{% set lines = template_manifest_content.splitlines() %}
-{% for line in lines %} {{ line }}
+{% if generated_files %}
+

Code généré

+{% for file in generated_files %} +
+
+

{{ file.destination_path }}

+
+
+
{{ file.content }}
+
+
{% endfor %} -
-
-
- - - - - - - - - -

Script d'installation (install)

-
-
-
-{% set lines = template_install_content.splitlines() %}
-{% for line in lines %} {{ line }}
-{% endfor %}
-
-
-
- - - - - - - - - -

Script de suppression (remove)

-
-
-
-{% set lines = template_remove_content.splitlines() %}
-{% for line in lines %} {{ line }}
-{% endfor %}
-
-
-
- - - - - - - - - -

Script de sauvegarde (backup)

-
-
-
-{% set lines = template_backup_content.splitlines() %}
-{% for line in lines %} {{ line }}
-{% endfor %}
-
-
-
- - - - - - - - - -

Script de restauration (restore)

-
-
-
-{% set lines = template_restore_content.splitlines() %}
-{% for line in lines %} {{ line }}
-{% endfor %}
-
-
-
- - - - - - - - - -

Script de mise à jour (upgrade)

-
-
-
-{% set lines = template_upgrade_content.splitlines() %}
-{% for line in lines %} {{ line }}
-{% endfor %}
-
-
-
- - - - - - - - - -

Script de configuration spéciale (config)

-
-
-
-{% set lines = template_config_content.splitlines() %}
-{% for line in lines %} {{ line }}
-{% endfor %}
-
-
-
- - - - - - - - - - {%- if template_change_url_content -%} -

Script de changement d'URL (change_url)

-
-
-
-{% set lines = template_change_url_content.splitlines() %}
-{% for line in lines %} {{ line }}
-{% endfor %}
-
-
-
- - - - - - - {% endif %} - - - {%- if systemd_config_file -%} -

Fichier de configuration du service SystemD (systemd.service)

-
-
-            
-            {{parameters['systemd_config_file']}}
-            
-        
-
- - - - - - - {% endif %} - - - {%- if nginx_config_file -%} -

Fichier de configuration de NGINX (nginx.conf)

-
-
-            
-            {{parameters['nginx_config_file']}}
-            
-        
-
- - - - - - - {% endif %} - - - {%- if custom_config_file -%} -

Fichier de configuration personnalisé ({{custom_config_file}})

-
-
-            
-            {{parameters['custom_config_file_content']}}
-            
-        
-
- - - - - - - {% endif %} - - - {%- if cron_config_file -%} -

Fichier de configuration de tâche CRON (task.cron)

-
-
-            
-            {{parameters['cron_config_file']}}
-            
-        
-
- - - - - - - {% endif %} - - -
- -

- -

- {% endif %}
diff --git a/tools/app_generator/templates/install.j2 b/tools/app_generator/templates/install.j2 index f6e07073..cef50dbd 100644 --- a/tools/app_generator/templates/install.j2 +++ b/tools/app_generator/templates/install.j2 @@ -1,9 +1,11 @@ #!/bin/bash -#### App file generated with YoloGen, the YunoHost app generator, version {{ parameters['GENERATOR_VERSION'] }}. -{% if parameters["tutorial"] -%} # This is the tutorial version of the app. + +{% if data.generator_mode == 'tutorial' -%} +# This is the tutorial version of the app. # It contains extra commands to explain what should be done in case you want to adjust some part of the script. # Once you are done, you may remove them. -{% endif -%} +{% endif %} + #================================================= # GENERIC START #================================================= @@ -12,7 +14,7 @@ source _common.sh source /usr/share/yunohost/helpers -{% if parameters["tutorial"] -%} +{% if data.generator_mode == 'tutorial' -%} # Install parameters are automatically saved as settings # # Settings are automatically loaded as bash variables @@ -33,121 +35,70 @@ source /usr/share/yunohost/helpers # $app is the app id (i.e. 'example' for first install, # or 'example__2', '__3', ... for multi-instance installs) # -{% endif -%} +{% endif %} #================================================= # INSTALL DEPENDENCIES #================================================= -ynh_script_progression --message="Installing dependencies..." --weight=10 -{% if parameters["use_nodejs_needs_yarn"] -%} +{% if data.main_technology == "nodejs" -%} +ynh_script_progression --message="Installing NodeJS..." --weight=10 + # Install Nodejs ynh_exec_warn_less ynh_install_nodejs --nodejs_version=$nodejs_version -{% endif -%} +ynh_use_nodejs +{% endif %} #================================================= # APP "BUILD" (DEPLOYING SOURCES, VENV, COMPILING ETC) #================================================= # DOWNLOAD, CHECK AND UNPACK SOURCE #================================================= -ynh_script_progression --message="Setting up source files..." --weight=1 +ynh_script_progression --message="Setting up source files..." -{% if parameters["tutorial"] -%} +{% if data.generator_mode == 'tutorial' -%} ### `ynh_setup_source` is used to install an app from a zip or tar.gz file, -### downloaded from an upstream source, like a git repository. -### `ynh_setup_source` use the file conf/app.src -{% endif -%} +### downloaded from an upstream source, as defined in the manifest.toml +{% endif %} -# Download, check integrity, uncompress and patch the source from app.src ynh_setup_source --dest_dir="$install_dir" -{% if parameters["tutorial"] -%} +{% if data.generator_mode == 'tutorial' -%} # $install_dir will automatically be initialized with some decent # permission by default ... however, you may need to recursively reapply # ownership to all files such as after the ynh_setup_source step -{% endif -%} -chown -R $app:www-data "$install_dir" {# TODO : we may need to allow people to configure this #} +{% endif %} +chown -R $app:www-data "$install_dir" #================================================= # SYSTEM CONFIGURATION #================================================= -ynh_script_progression --message="Adding system configurations related to $app..." --weight=1 -{% if use_php -%} +ynh_script_progression --message="Adding system configurations related to $app..." -{% if version == "tutorial" -%} -### `ynh_add_fpm_config` is used to set up a PHP config. -### You can remove it if your app doesn't use PHP. -### `ynh_add_fpm_config` will use the files conf/php-fpm.conf -### If you're not using these lines: -### - You can remove these files in conf/. -### - Remove the section "BACKUP THE PHP-FPM CONFIGURATION" in the backup script -### - Remove also the section "REMOVE PHP-FPM CONFIGURATION" in the remove script -### - As well as the section "RESTORE THE PHP-FPM CONFIGURATION" in the restore script -### with the reload at the end of the script. -### - And the section "PHP-FPM CONFIGURATION" in the upgrade script -### But wait : This should have been handled by our app generator ;) -{% endif -%} - -ynh_script_progression --message="Adding {{php_version}} configurations related to $app..." --weight=1 -# Create a dedicated PHP-FPM config using the conf/php-fpm.conf or conf/extra_php-fpm.conf +{% if data.main_technology == "php" -%} +# Create a dedicated PHP-FPM config +# conf/extra_php-fpm.conf will be appended to the auto-generated config, which will go in /etc/php/X.Y/fpm/pool.d/ ynh_add_fpm_config -{% endif -%} - - -{% if use_php -%} - - {% if version == "tutorial" -%} -### `ynh_add_fpm_config` is used to set up a PHP config. -### You can remove it if your app doesn't use PHP. -### `ynh_add_fpm_config` will use the files conf/php-fpm.conf -### If you're not using these lines: -### - You can remove these files in conf/. -### - Remove the section "BACKUP THE PHP-FPM CONFIGURATION" in the backup script -### - Remove also the section "REMOVE PHP-FPM CONFIGURATION" in the remove script -### - As well as the section "RESTORE THE PHP-FPM CONFIGURATION" in the restore script -### with the reload at the end of the script. -### - And the section "PHP-FPM CONFIGURATION" in the upgrade script - {% endif -%} - -ynh_script_progression --message="Adding {{php_version}} configurations related to $app..." --weight=1 -# Create a dedicated PHP-FPM config using the conf/php-fpm.conf or conf/extra_php-fpm.conf -ynh_add_fpm_config -{% endif -%} +{% endif %} # Create a dedicated NGINX config using the conf/nginx.conf template ynh_add_nginx_config -{% if parameters["use_systemd_service"] -%} - {% if version == "tutorial" -%} -### `ynh_systemd_config` is used to configure a systemd script for an app. -### It can be used for apps that use sysvinit (with adaptation) or systemd. -### Have a look at the app to be sure this app needs a systemd script. -### `ynh_systemd_config` will use the file conf/systemd.service -### If you're not using these lines: -### - You can remove those files in conf/. -### - Remove the section "BACKUP SYSTEMD" in the backup script -### - Remove also the section "STOP AND REMOVE SERVICE" in the remove script -### - As well as the section "RESTORE SYSTEMD" in the restore script -### - And the section "SETUP SYSTEMD" in the upgrade script - {% endif -%} - +{% if data.main_technology not in ["php", "none"] -%} +{% if data.generator_mode == 'tutorial' -%} +### `ynh_systemd_config` is used to configure a systemd script for an app, using the conf/systemd.service template +{% endif %} # Create a dedicated systemd config ynh_add_systemd_config - {% if version == "tutorial" -%} +{% if data.generator_mode == 'tutorial' -%} ### `yunohost service add` integrates a service in YunoHost. It then gets ### displayed in the admin interface and through the others `yunohost service` commands. -### (N.B.: this line only makes sense if the app adds a service to the system!) -### If you're not using these lines: -### - You can remove these files in conf/. -### - Remove the section "REMOVE SERVICE INTEGRATION IN YUNOHOST" in the remove script -### - As well as the section "INTEGRATE SERVICE IN YUNOHOST" in the restore script -### - And the section "INTEGRATE SERVICE IN YUNOHOST" in the upgrade script - {% endif -%} +{% endif %} -yunohost service add $app --description="{{ parameters['systemd_service_description'] }}" --log="/var/log/$app/$app.log" +yunohost service add $app --log="/var/log/$app/$app.log" - {% if version == "tutorial" -%} +{% if data.generator_mode == 'tutorial' -%} ### Additional options starting with 3.8: ### ### --needs_exposed_ports "$port" a list of ports that needs to be publicly exposed @@ -163,43 +114,35 @@ yunohost service add $app --description="{{ parameters['systemd_service_descript ### to proceed if you later realize that you need to enable some flags that ### weren't enabled on old installs (be careful it'll override the existing ### service though so you should re-provide all relevant flags when doing so) - -### `ynh_use_logrotate` is used to configure a logrotate configuration for the logs of this app. -### Use this helper only if there is effectively a log file for this app. -### If you're not using this helper: -### - Remove the section "BACKUP LOGROTATE" in the backup script -### - Remove also the section "REMOVE LOGROTATE CONFIGURATION" in the remove script -### - As well as the section "RESTORE THE LOGROTATE CONFIGURATION" in the restore script -### - And the section "SETUP LOGROTATE" in the upgrade script - {% endif -%} {% endif -%} +{% endif %} +{% if data.use_logrotate %} # Use logrotate to manage application logfile(s) ynh_use_logrotate +{% endif %} -{% if parameters["use_fail2ban"] -%} +{% if data.use_fail2ban -%} # Create a dedicated Fail2Ban config -ynh_add_fail2ban_config --logpath="/var/log/nginx/${domain}-error.log" --failregex="{{ parameters['fail2ban_regex'] }}" -{% endif -%} +ynh_add_fail2ban_config --logpath="/var/log/nginx/${domain}-error.log" --failregex="{{ data.fail2ban_regex }}" +{% endif %} -{% if parameters["use_cron"] -%} -#================================================= -# ADD A CRON JOB -#================================================= +{% if data.use_cron -%} +# Add cron job cron_path="/etc/cron.d/$app" ynh_add_config --template="../conf/task.cron" --destination="$cron_path" chown root: "$cron_path" chmod 644 "$cron_path" -{% endif -%} +{% endif %} #================================================= # APP INITIAL CONFIGURATION #================================================= # ADD A CONFIGURATION #================================================= -ynh_script_progression --message="Adding a configuration file..." --weight=1 +ynh_script_progression --message="Adding app's configuration file..." -{% if version == "tutorial" -%} +{% if data.generator_mode == 'tutorial' -%} ### You can add specific configuration files. ### ### Typically, put your template conf file in ../conf/your_config_file @@ -211,73 +154,60 @@ ynh_script_progression --message="Adding a configuration file..." --weight=1 ### if it's found that the file was manually modified ### ### Check the documentation of `ynh_add_config` for more info. -{% endif -%} +{% endif %} -ynh_add_config --template="{{ parameters['custom_config_file'] }}" --destination="$install_dir/{{ parameters['custom_config_file'] }}" +ynh_add_config --template="{{ data.custom_config_file }}" --destination="$install_dir/{{ data.custom_config_file }}" # FIXME: this should be handled by the core in the future # You may need to use chmod 600 instead of 400, # for example if the app is expected to be able to modify its own config -chmod 400 "$install_dir/{{ parameters['custom_config_file'] }}" -chown $app:$app "$install_dir/{{ parameters['custom_config_file'] }}" +chmod 400 "$install_dir/{{ data.custom_config_file }}" +chown $app:$app "$install_dir/{{ data.custom_config_file }}" +{% if data.generator_mode == 'tutorial' -%} ### For more complex cases where you want to replace stuff using regexes, ### you shoud rely on ynh_replace_string (which is basically a wrapper for sed) ### When doing so, you also need to manually call ynh_store_file_checksum ### ### ynh_replace_string --match_string="match_string" --replace_string="replace_string" --target_file="$install_dir/some_config_file" ### ynh_store_file_checksum --file="$install_dir/some_config_file" +{% endif %} -{% if parameters["use_nodejs_needs_yarn"] -%} +{% if data.install_snippet -%} #================================================= -# INSTALL YARN +# INSTALL APP #================================================= -ynh_script_progression --message="Installing yarn dependency..." --weight=15 +ynh_script_progression --message="Installing app..." --weight=5 pushd $install_dir - ynh_use_nodejs - ynh_exec_warn_less ynh_exec_as $app env $ynh_node_load_PATH yarn install +{{ data.install_snippet }} popd -{% endif -%} +{% endif %} #================================================= -# SETUP APPLICATION WITH CURL +# FINALIZE APP INSTALL WITH CURL #================================================= -{% if version == "tutorial" -%} +{% if data.generator_mode == 'tutorial' -%} ### Use these lines only if the app installation needs to be finalized through ### web forms. We generally don't want to ask the final user, ### so we're going to use curl to automatically fill the fields and submit the ### forms. -{% endif -%} +{% endif %} -#### TODO in Yunohost App Generator -# Installation with curl -ynh_script_progression --message="Finalizing installation..." --weight=1 -ynh_local_curl "/INSTALL_PATH" "key1=value1" "key2=value2" "key3=value3" +# REMOVEME? ynh_script_progression --message="Finalizing installation..." +# REMOVEME? ynh_local_curl "/INSTALL_PATH" "key1=value1" "key2=value2" "key3=value3" +{% if data.main_technology not in ["php", "none"] -%} #================================================= -# GENERIC FINALIZATION -#================================================= -{% if parameters["use_systemd_service"] -%} # START SYSTEMD SERVICE #================================================= -ynh_script_progression --message="Starting a systemd service..." --weight=1 - - -{% if version == "tutorial" -%} -### `ynh_systemd_action` is used to start a systemd service for an app. -### Only needed if you have configure a systemd service -### If you're not using these lines: -### - Remove the section "STOP SYSTEMD SERVICE" and "START SYSTEMD SERVICE" in the backup script -### - As well as the section "START SYSTEMD SERVICE" in the restore script -### - As well as the section"STOP SYSTEMD SERVICE" and "START SYSTEMD SERVICE" in the upgrade script -### - And the section "STOP SYSTEMD SERVICE" and "START SYSTEMD SERVICE" in the change_url script -{% endif -%} +ynh_script_progression --message="Starting app's systemd service..." # Start a systemd service ynh_systemd_action --service_name=$app --action="start" --log_path="/var/log/$app/$app.log" -{% endif -%} +{% endif %} + #================================================= # END OF SCRIPT #================================================= diff --git a/tools/app_generator/templates/manifest.j2 b/tools/app_generator/templates/manifest.j2 index 54b7bf44..244c035e 100644 --- a/tools/app_generator/templates/manifest.j2 +++ b/tools/app_generator/templates/manifest.j2 @@ -1,101 +1,93 @@ packaging_format = 2 -id = "{{ parameters['app_id'] }}" -name = "{{ parameters['app_name'] }}" +id = "{{ data.app_id }}" +name = "{{ data.app_name }}" -{% if parameters['tutorial'] -%} -{% endif -%} +description.en = "{{ data.description_en }}" +description.fr = "{{ data.description_fr }}" +version = "{{ data.version }}" -description.en = "{{ parameters['description_en'] }}" -description.fr = "{{ parameters['description_fr'] }}" - -version = "{{ parameters['version'] }}" - -maintainers = ["{{ parameters['maintainers'] }}"] +{% if data.maintainers -%} +maintainers = ["{{ data.maintainers }}"] +{%- endif %} [upstream] -# NB: Only the "license" key is mandatory. Remove entries for which there's no relevant data -license = "{{ parameters['license'] }}" -website = "{{ parameters['website'] }}" -demo = "{{ parameters['demo'] }}" -admindoc = "{{ parameters['admindoc'] }}" -userdoc = "{{ parameters['userdoc'] }}" -code = "{{ parameters['code'] }}" -# FIXME: optional but recommended if relevant, this is meant to contain the Common Platform Enumeration, which is sort of a standard id for applications defined by the NIST. In particular, YunoHost may use this is in the future to easily track CVE (=security reports) related to apps. The CPE may be obtained by searching here: https://nvd.nist.gov/products/cpe/search. For example, for Nextcloud, the CPE is 'cpe:2.3:a:nextcloud:nextcloud' (no need to include the version number) -cpe = "???" -# FIXME: optional but recommended (or remove if irrelevant / not applicable). This is meant to be an URL where people can financially support this app, especially when its development is based on volunteers and/or financed by its community. YunoHost may later advertise it in the webadmin. -fund = "???" - +license = "{{ data.license }}" +{% if data.website -%}website = "{{ data.website }}"{%- endif %} +{% if data.demo -%}demo = "{{ data.demo }}"{%- endif %} +{% if data.admindoc -%}admindoc = "{{ data.admindoc }}"{%- endif %} +{% if data.userdoc -%}userdoc = "{{ data.userdoc }}"{%- endif %} +{% if data.code -%}code = "{{ data.code }}"{%- endif %} [integration] -{% if parameters['yunohost_required_version'] -%} -yunohost = '>= {{ parameters['yunohost_required_version'] }}' -{% else -%} -yunohost = ">= 11.1.21" -{% endif -%} - -{% if parameters['tutorial'] -%} +yunohost = '>= {{ data.yunohost_required_version or '11.2'}}' +{% if data.generator_mode == "tutorial" -%} # List of supported archs using the dpkg --print-architecture nomenclature (amd64/i386/armhf/arm64), for example: ["amd64", "i386'] {% endif -%} -architectures = "{{ parameters['architectures'] }}" # TODO : handle the "all" option (no ["all"]) -multi_instance = {{% if parameters['multi_instance'] -%} true {% else -%} false {% endif -%} -ldap = "{{ parameters['ldap'] }}" -sso = "{{ parameters['sso'] }}" +architectures = "{{ data.architectures }}" # TODO : handle the "all" option (no ["all"]) +multi_instance = {% if data.multi_instance -%} true {% else -%} false {% endif -%} +ldap = "{{ data.ldap }}" # TODO : fixme, use actual booleans + handle the "not_relevant" value +sso = "{{ data.sso }}" # FIXME: replace with an **estimate** minimum disk and RAM requirements. e.g. 20M, 400M, 1G... You may have have a look at CI results disk = "50M" ram.build = "50M" ram.runtime = "50M" [install] + + {% if data.domain_and_path != "false" -%} [install.domain] - {% if parameters['tutorial'] -%} + {% if data.generator_mode == "tutorial" -%} # this is a generic question - ask strings are automatically handled by YunoHost's core {% endif -%} type = "domain" - {% if not parameters['use_whole_domain'] -%} + {% if data.domain_and_path != "full_domain" -%} [install.path] - {% if parameters['tutorial'] -%} + {% if data.generator_mode == "tutorial" -%} # this is a generic question - ask strings are automatically handled by YunoHost's core {% endif -%} type = "path" default = "/example" {% endif -%} + {% endif %} + {% if data.init_main_permission -%} [install.init_main_permission] + {% if data.generator_mode == "tutorial" -%} # this is a generic question - ask strings are automatically handled by YunoHost's core # This won't be saved as setting and will instead be used to initialize the SSOwat permission + {% endif -%} type = "group" - default = "{{ parameters['visibility'] }}" + default = "visitors" + {% endif %} + {% if data.language != "_" -%} [install.language] ask.en = "Choose the application language" ask.fr = "Choisissez la langue de l'application" type = "select" - choices = {{ parameters['language'] |safe }} - default = "{{ parameters['default_language'] }}" + choices = {{ data.language |safe }} + {% endif %} - [install.admin] - {% if parameters['tutorial'] -%} + {% if data.init_admin_permission -%} + [install.init_admin_permission] + {% if data.generator_mode == "tutorial" -%} # this is a generic question - ask strings are automatically handled by YunoHost's core - type = "user" + # This won't be saved as setting and will instead be used to initialize the SSOwat permission {% endif -%} + type = "group" + default = "admins" + {% endif %} - [install.password] - {% if parameters['tutorial'] -%} - # this is a generic question - ask strings are automatically handled by YunoHost's core - # Note that user-provided passwords questions are not automatically saved as setting - {% endif -%} - help.en = "Use the help field to add an information for the admin about this question." - help.fr = "Utilisez le champ aide pour ajouter une information à l'intention de l'administrateur à propos de cette question." - type = "password" [resources] - {% if parameters['tutorial'] -%} + {% if data.generator_mode == "tutorial" -%} # See the packaging documentation for the full set # of explanation regarding the behavior and properties for each of those - {% endif -%} + # https://yunohost.org/packaging_apps_resources + {% endif %} [resources.sources] @@ -103,77 +95,79 @@ ram.runtime = "50M" # This will pre-fetch the asset which can then be deployed during the install/upgrade scripts with : # ynh_setup_source --dest_dir="$install_dir" # You can also define other assets than "main" and add --source_id="foobar" in the previous command - url = "{{parameters['source_url']}}" - sha256 = "{{parameters['sha256sum']}}" + url = "{{data.source_url}}" + sha256 = "{{data.sha256sum}}" # These infos are used by https://github.com/YunoHost/apps/blob/master/tools/autoupdate_app_sources/autoupdate_app_sources.py # to auto-update the previous asset urls and sha256sum + manifest version # assuming the upstream's code repo is on github and relies on tags or releases # See the 'sources' resource documentation for more details - {% if parameters['auto_update'] -%} + {% if data.auto_update -%} autoupdate.strategy = "latest_github_tag" {% else -%} # autoupdate.strategy = "latest_github_tag" - {% endif -%} + {% endif %} + {% if data.system_user -%} [resources.system_user] - {% if parameters['tutorial'] -%} + {% if data.generator_mode == "tutorial" -%} # This will provision/deprovision a unix system user - {% endif -%} + {%- endif -%} + {%- endif %} + {% if data.install_dir -%} [resources.install_dir] - {% if parameters['tutorial'] -%} + {% if data.generator_mode == "tutorial" -%} # This will create/remove the install dir as /var/www/$app # and store the corresponding setting $install_dir - {% endif -%} + {%- endif -%} + {%- endif %} - {% if parameters['data_dir'] %} + {% if data.data_dir -%} [resources.data_dir] - {% if parameters['tutorial'] -%} + {% if data.generator_mode == "tutorial" -%} # This will create/remove the data dir as /home/yunohost.app/$app # and store the corresponding setting $data_dir - {% endif -%} - {% if parameters['data_subdirs'] -%} - subdirs = {{ parameters['data_subdirs'].split(",") |safe}} - {% endif -%} - {% endif -%} + {%- endif -%} + {%- endif %} [resources.permissions] - {% if parameters['tutorial'] -%} + {% if data.generator_mode == "tutorial" -%} # This will configure SSOwat permission for $domain/$path/ # The initial allowed group of user is configured via the init_main_permission question (public=visitors, private=all_users) {% endif -%} main.url = "/" + {% if data.main_technology not in ['none', 'php'] -%} [resources.ports] - {% if parameters['tutorial'] -%} + {% if data.generator_mode == "tutorial" -%} # This will pick a random port for reverse-proxying and store it as the $port setting {% endif -%} + {%- endif %} - {% if parameters['dependencies'] or parameters['use_nodejs_needs_yarn'] -%} + {%- if data.apt_dependencies or data.use_yarn -%} [resources.apt] - {% if parameters['tutorial'] -%} + {% if data.generator_mode == "tutorial" -%} # This will automatically install/uninstall the following apt packages # and implicitly define the $phpversion setting as 8.0 (if phpX.Y-foobar dependencies are listed) {% endif -%} - packages = "{{ parameters['dependencies'] }} {% if parameters['use_db'] != 'false' -%} {{ parameters['use_db'] }} {% endif -%} {% if parameters['use_python'] -%} python3-venv {% endif -%}" - {% endif -%} + packages = "{{ data.apt_dependencies }} {% if data.database == 'mysql' -%} mariadb-server {% elif data.database == 'postgresql' %} postgresql {% endif -%} {% if data.main_technology == 'python' -%} python3 python3-venv {% endif -%}" + {%- endif %} - {% if parameters['tutorial'] -%} - {% if parameters['use_nodejs_needs_yarn'] -%} + {%- if data.use_nodejs_needs_yarn -%} + {% if data.generator_mode == "tutorial" -%} # This will configure an extra repository to install yarn dependency + {% endif -%} extras.yarn.repo = "deb https://dl.yarnpkg.com/debian/ stable main" extras.yarn.key = "https://dl.yarnpkg.com/debian/pubkey.gpg" extras.yarn.packages = "yarn" - {% endif -%} - {% endif -%} + {%- endif %} - {% if parameters['use_db'] != 'false' -%} + {%- if data.database != 'false' -%} [resources.database] - {% if parameters['tutorial'] -%} + {% if data.generator_mode == "tutorial" -%} # This will automatically provision/deprovison a database and store the corresponding credentials in settings $db_user, $db_name, $db_pwd - {% endif -%} - type = "{{ parameters['use_db'] }}" {% endif -%} - + type = "{{ data.database }}" + {%- endif -%} diff --git a/tools/app_generator/templates/nginx.j2 b/tools/app_generator/templates/nginx.j2 new file mode 100644 index 00000000..66124e28 --- /dev/null +++ b/tools/app_generator/templates/nginx.j2 @@ -0,0 +1,35 @@ +#sub_path_only rewrite ^__PATH__$ __PATH__/ permanent; +location __PATH__/ { + +{% if data.main_technology in ["none", "php"] %} + # Path to source + alias __INSTALL_DIR__/; +{% endif %} + + client_max_body_size 10M; + +{% if data.main_technology == "php" %} + try_files $uri $uri/ index.php; + location ~ [^/]\.php(/|$) { + fastcgi_split_path_info ^(.+?\.php)(/.*)$; + fastcgi_pass unix:/var/run/php/php__PHPVERSION__-fpm-__NAME__.sock; + + fastcgi_index index.php; + include fastcgi_params; + fastcgi_param REMOTE_USER $remote_user; + fastcgi_param PATH_INFO $fastcgi_path_info; + fastcgi_param SCRIPT_FILENAME $request_filename; + } +{% elif data.main_technology not in ["php", "none"] %} + proxy_pass http://127.0.0.1:__PORT__; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "Upgrade"; + + # preserve client IP + proxy_set_header X-Forwarded-For $remote_addr; +{% endif %} + + # Include SSOWAT user panel's shortcut tile. + include conf.d/yunohost_panel.conf.inc; +} diff --git a/tools/app_generator/templates/remove.j2 b/tools/app_generator/templates/remove.j2 index c9cd966c..82c42369 100644 --- a/tools/app_generator/templates/remove.j2 +++ b/tools/app_generator/templates/remove.j2 @@ -1,9 +1,10 @@ #!/bin/bash -#### App file generated with YoloGen, the YunoHost app generator, version {{ parameters['GENERATOR_VERSION'] }}. -{% if parameters["tutorial"] -%} # This is the tutorial version of the app. + +{% if data.generator_mode == 'tutorial' -%} # This is the tutorial version of the app. # It contains extra commands to explain what should be done in case you want to adjust some part of the script. # Once you are done, you may remove them. -{% endif -%} +{% endif %} + #================================================= # GENERIC START #================================================= @@ -12,7 +13,7 @@ source _common.sh source /usr/share/yunohost/helpers -{% if parameters["tutorial"] -%} +{% if data.generator_mode == 'tutorial' -%} # Settings are automatically loaded as bash variables # in every app script context, therefore typically these will exist: # - $domain @@ -25,50 +26,48 @@ source /usr/share/yunohost/helpers # For remove operations : # - the core will deprovision every resource defined in the manifest **after** this script is ran # this includes removing the install directory, and data directory (if --purge was used) -{% endif -%} +{% endif %} #================================================= # REMOVE SYSTEM CONFIGURATIONS #================================================= # REMOVE SYSTEMD SERVICE #================================================= -ynh_script_progression --message="Removing system configurations related to $app..." --weight=1 +ynh_script_progression --message="Removing system configurations related to $app..." -{% if parameters["tutorial"] -%} +{% if data.generator_mode == 'tutorial' -%} # This should be a symetric version of what happens in the install script -{% endif -%} +{% endif %} -{% if parameters["use_systemd_service"] -%} +{% if data.main_technology not in ["php", "none"] -%} # Remove the service from the list of services known by YunoHost (added from `yunohost service add`) if ynh_exec_warn_less yunohost service status $app >/dev/null then - ynh_script_progression --message="Removing $app service integration..." --weight=1 + ynh_script_progression --message="Removing $app service integration..." yunohost service remove $app fi ynh_remove_systemd_config -{% endif -%} +{% endif %} ynh_remove_nginx_config -{% if parameters["use_php"] -%} +{% if data.main_technology == "php" -%} ynh_remove_fpm_config -{% endif -%} +{% endif %} -{% if parameters["use_logrotate"] -%} +{% if data.use_logrotate -%} ynh_remove_logrotate -{% endif -%} +{% endif %} -{% if parameters["use_fail2ban"] -%} +{% if data.use_fail2ban -%} ynh_remove_fail2ban_config -{% endif -%} +{% endif %} # Remove other various files specific to the app... such as : -{% if parameters["use_cron"] -%} +{% if data.use_cron -%} ynh_secure_remove --file="/etc/cron.d/$app" -{% endif -%} - -ynh_secure_remove --file="/etc/$app" +{% endif %} ynh_secure_remove --file="/var/log/$app" diff --git a/tools/app_generator/templates/restore.j2 b/tools/app_generator/templates/restore.j2 index 5ecef1f1..b039e8b1 100644 --- a/tools/app_generator/templates/restore.j2 +++ b/tools/app_generator/templates/restore.j2 @@ -1,9 +1,10 @@ #!/bin/bash -#### App file generated with YoloGen, the YunoHost app generator, version {{ parameters['GENERATOR_VERSION'] }}. -{% if parameters["tutorial"] -%} # This is the tutorial version of the app. + +{% if data.generator_mode == 'tutorial' -%} # This is the tutorial version of the app. # It contains extra commands to explain what should be done in case you want to adjust some part of the script. # Once you are done, you may remove them. -{% endif -%} +{% endif %} + #================================================= # GENERIC START #================================================= @@ -14,6 +15,7 @@ source ../settings/scripts/_common.sh source /usr/share/yunohost/helpers +{% if data.install_dir %} #================================================= # RESTORE THE APP MAIN DIR #================================================= @@ -21,14 +23,15 @@ ynh_script_progression --message="Restoring the app main directory..." --weight= ynh_restore_file --origin_path="$install_dir" -{% if parameters["tutorial"] -%} +{% if data.generator_mode == 'tutorial' -%} # $install_dir will automatically be initialized with some decent # permission by default ... however, you may need to recursively reapply # ownership to all files such as after the ynh_setup_source step -{% endif -%} +{% endif %} chown -R $app:www-data "$install_dir" +{% endif %} -{% if parameters["data_dir"] -%} +{% if data.data_dir -%} #================================================= # RESTORE THE DATA DIRECTORY #================================================= @@ -38,73 +41,72 @@ ynh_restore_file --origin_path="$data_dir" --not_mandatory # (Same as for install dir) chown -R $app:www-data "$data_dir" -{% endif -%} +{% endif %} -{% if parameters["use_db"] -%} +{% if data.database -%} #================================================= # RESTORE THE MYSQL DATABASE #================================================= - {% if parameters["use_db"] == 'mysql' -%} +{% if data.datase == 'mysql' -%} ynh_script_progression --message="Restoring the MySQL database..." --weight=1 ynh_mysql_connect_as --user=$db_user --password=$db_pwd --database=$db_name < ./db.sql - {% endif -%} +{% endif %} - {% if parameters["use_db"] == 'postgresql' -%} +{% if data.database == 'postgresql' -%} ynh_script_progression --message="Restoring the PostgreSQL database..." --weight=1 ynh_psql_connect_as --user=$db_user --password=$db_pwd --database=$db_name < ./db.sql - {% endif -%} -{% endif -%} +{% endif %} +{% endif %} + #================================================= # RESTORE SYSTEM CONFIGURATIONS #================================================= -# RESTORE THE PHP-FPM CONFIGURATION -#================================================= ynh_script_progression --message="Restoring system configurations related to $app..." --weight=1 # This should be a symetric version of what happens in the install script -{% if parameters["use_php"] -%} +{% if data.main_technology == "php" -%} ynh_restore_file --origin_path="/etc/php/$phpversion/fpm/pool.d/$app.conf" -{% endif -%} +{% endif %} ynh_restore_file --origin_path="/etc/nginx/conf.d/$domain.d/$app.conf" -{% if parameters["use_systemd_service"] -%} +{% if data.main_technology not in ["php", "none"] -%} ynh_restore_file --origin_path="/etc/systemd/system/$app.service" systemctl enable $app.service --quiet -yunohost service add $app --description="{{ parameters['systemd_service_description'] }}" --log="/var/log/$app/$app.log" -{% endif -%} +yunohost service add $app --log="/var/log/$app/$app.log" +{% endif %} -{% if parameters["use_logrotate"] -%} +{% if data.use_logrotate -%} ynh_restore_file --origin_path="/etc/logrotate.d/$app" -{% endif -%} -{% if parameters["use_fail2ban"] -%} +{% endif %} + +{% if data.use_fail2ban -%} ynh_restore_file --origin_path="/etc/fail2ban/jail.d/$app.conf" ynh_restore_file --origin_path="/etc/fail2ban/filter.d/$app.conf" ynh_systemd_action --action=restart --service_name=fail2ban -{% endif -%} +{% endif %} + # Other various files... - +{% if data.use_cron -%} ynh_restore_file --origin_path="/etc/cron.d/$app" -ynh_restore_file --origin_path="/etc/$app/" +{% endif %} -#================================================= -# GENERIC FINALIZATION #================================================= # RELOAD NGINX AND PHP-FPM OR THE APP SERVICE #================================================= ynh_script_progression --message="Reloading NGINX web server and $app's service..." --weight=1 # Typically you only have either $app or php-fpm but not both at the same time... -{% if parameters["use_systemd_service"] -%} +{% if data.main_technology not in ["php", "none"] -%} ynh_systemd_action --service_name=$app --action="start" --log_path="/var/log/$app/$app.log" -{% endif -%} -{% if parameters["use_php"] -%} +{% endif %} +{% if data.main_technology == "php" -%} ynh_systemd_action --service_name=php$phpversion-fpm --action=reload -{% endif -%} +{% endif %} ynh_systemd_action --service_name=nginx --action=reload diff --git a/tools/app_generator/templates/systemd.j2 b/tools/app_generator/templates/systemd.j2 new file mode 100644 index 00000000..ff53a24d --- /dev/null +++ b/tools/app_generator/templates/systemd.j2 @@ -0,0 +1,54 @@ +[Unit] +Description=Service for {{ data.app_name }} (__APP__) +After=network.target + +[Service] +Type=simple +User=__APP__ +Group=__APP__ +{% if data.custom_config_file == ".env" %} +EnvironmentFile=__INSTALL_DIR__/.env +{% endif %} +WorkingDirectory=__INSTALL_DIR__/ +ExecStart={{ data.systemd_execstart }} +StandardOutput=append:/var/log/__APP__/__APP__.log +StandardError=inherit +Restart=on-failure +RestartSec=10 + +# Sandboxing options to harden security +# Depending on specificities of your service/app, you may need to tweak these +# .. but this should be a good baseline +# Details for these options: https://www.freedesktop.org/software/systemd/man/systemd.exec.html +NoNewPrivileges=yes +PrivateTmp=yes +PrivateDevices=yes +RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 AF_NETLINK +RestrictNamespaces=yes +RestrictRealtime=yes +DevicePolicy=closed +ProtectClock=yes +ProtectHostname=yes +ProtectProc=invisible +ProtectSystem=full +ProtectControlGroups=yes +ProtectKernelModules=yes +ProtectKernelTunables=yes +LockPersonality=yes +SystemCallArchitectures=native +SystemCallFilter=~@clock @debug @module @mount @obsolete @reboot @setuid @swap @cpu-emulation @privileged + +# Denying access to capabilities that should not be relevant for webapps +# Doc: https://man7.org/linux/man-pages/man7/capabilities.7.html +CapabilityBoundingSet=~CAP_RAWIO CAP_MKNOD +CapabilityBoundingSet=~CAP_AUDIT_CONTROL CAP_AUDIT_READ CAP_AUDIT_WRITE +CapabilityBoundingSet=~CAP_SYS_BOOT CAP_SYS_TIME CAP_SYS_MODULE CAP_SYS_PACCT +CapabilityBoundingSet=~CAP_LEASE CAP_LINUX_IMMUTABLE CAP_IPC_LOCK +CapabilityBoundingSet=~CAP_BLOCK_SUSPEND CAP_WAKE_ALARM +CapabilityBoundingSet=~CAP_SYS_TTY_CONFIG +CapabilityBoundingSet=~CAP_MAC_ADMIN CAP_MAC_OVERRIDE +CapabilityBoundingSet=~CAP_NET_ADMIN CAP_NET_BROADCAST CAP_NET_RAW +CapabilityBoundingSet=~CAP_SYS_ADMIN CAP_SYS_PTRACE CAP_SYSLOG + +[Install] +WantedBy=multi-user.target diff --git a/tools/app_generator/templates/upgrade.j2 b/tools/app_generator/templates/upgrade.j2 index 4e001ca1..8802ab42 100644 --- a/tools/app_generator/templates/upgrade.j2 +++ b/tools/app_generator/templates/upgrade.j2 @@ -1,9 +1,10 @@ #!/bin/bash -#### App file generated with YoloGen, the YunoHost app generator, version {{ parameters['GENERATOR_VERSION'] }}. -{% if parameters["tutorial"] -%} # This is the tutorial version of the app. + +{% if data.generator_mode == 'tutorial' -%} # This is the tutorial version of the app. # It contains extra commands to explain what should be done in case you want to adjust some part of the script. # Once you are done, you may remove them. -{% endif -%} +{% endif %} + #================================================= # GENERIC START #================================================= @@ -13,7 +14,7 @@ source _common.sh source /usr/share/yunohost/helpers -{% if parameters["tutorial"] -%} +{% if data.generator_mode == 'tutorial' -%} # Settings are automatically loaded as bash variables # in every app script context, therefore typically these will exist: # - $domain @@ -33,7 +34,7 @@ source /usr/share/yunohost/helpers ### - UPGRADE_PACKAGE if only the YunoHost package has changed ### ynh_check_app_version_changed will stop the upgrade if the app is up to date. ### UPGRADE_APP should be used to upgrade the core app only if there's an upgrade to do. -{% endif -%} +{% endif %} upgrade_type=$(ynh_check_app_version_changed) #================================================= @@ -41,8 +42,8 @@ upgrade_type=$(ynh_check_app_version_changed) #================================================= # ENSURE DOWNWARD COMPATIBILITY #================================================= -{% if parameters["tutorial"] -%} -#ynh_script_progression --message="Ensuring downward compatibility..." --weight=1 +{% if data.generator_mode == 'tutorial' -%} +#ynh_script_progression --message="Ensuring downward compatibility..." # # N.B. : the followings setting migrations snippets are provided as *EXAMPLES* @@ -51,26 +52,27 @@ upgrade_type=$(ynh_check_app_version_changed) # # If db_name doesn't exist, create it -#if [ -z "$db_name" ]; then +#if [ -z "${db_name:-}" ]; then # db_name=$(ynh_sanitize_dbid --db_name=$app) # ynh_app_setting_set --app=$app --key=db_name --value=$db_name #fi # If install_dir doesn't exist, create it -#if [ -z "$install_dir" ]; then +#if [ -z "${install_dir:-}" ]; then # install_dir=/var/www/$app # ynh_app_setting_set --app=$app --key=install_dir --value=$install_dir #fi -{% endif -%} +{% endif %} -{% if parameters["use_systemd_service"] -%} +{% if data.main_technology not in ["php", "none"] -%} #================================================= # STOP SYSTEMD SERVICE #================================================= -ynh_script_progression --message="Stopping a systemd service..." --weight=1 +ynh_script_progression --message="Stopping a systemd service..." ynh_systemd_action --service_name=$app --action="stop" --log_path="/var/log/$app/$app.log" -{% endif -%} +{% endif %} + #================================================= # "REBUILD" THE APP (DEPLOY NEW SOURCES, RERUN NPM BUILD...) #================================================= @@ -79,100 +81,98 @@ ynh_systemd_action --service_name=$app --action="stop" --log_path="/var/log/$app if [ "$upgrade_type" == "UPGRADE_APP" ] then - ynh_script_progression --message="Upgrading source files..." --weight=1 + ynh_script_progression --message="Upgrading source files..." # Download, check integrity, uncompress and patch the source from app.src ynh_setup_source --dest_dir="$install_dir" fi -{% if parameters["tutorial"] -%} +{% if data.generator_mode == 'tutorial' -%} # $install_dir will automatically be initialized with some decent # permission by default ... however, you may need to recursively reapply # ownership to all files such as after the ynh_setup_source step -{% endif -%} +{% endif %} chown -R $app:www-data "$install_dir" #================================================= # REAPPLY SYSTEM CONFIGURATIONS #================================================= -ynh_script_progression --message="Upgrading system configurations related to $app..." --weight=1 +ynh_script_progression --message="Upgrading system configurations related to $app..." -{% if parameters["tutorial"] -%} +{% if data.generator_mode == 'tutorial' -%} # This should be a literal copypasta of what happened in the install's "System configuration" section -{% endif -%} +{% endif %} -{% if parameters["use_php"] -%} +{% if data.main_technology == "php" -%} ynh_add_fpm_config -{% endif -%} +{% endif %} ynh_add_nginx_config -{% if parameters["use_systemd_service"] -%} +{% if data.main_technology not in ["php", "none"] -%} ynh_add_systemd_config -yunohost service add $app --description="{{ parameters['systemd_service_description'] }}" --log="/var/log/$app/$app.log" -{% endif -%} +yunohost service add $app --log="/var/log/$app/$app.log" +{% endif %} -{% if parameters["use_logrotate"] -%} +{% if data.use_logrotate -%} ynh_use_logrotate --non-append -{% endif -%} +{% endif %} -{% if parameters["use_fail2ban"] -%} +{% if data.use_fail2ban -%} # Create a dedicated Fail2Ban config -ynh_add_fail2ban_config --logpath="/var/log/nginx/${domain}-error.log" --failregex="{{ parameters['fail2ban_regex'] }}" -{% endif -%} +ynh_add_fail2ban_config --logpath="/var/log/nginx/${domain}-error.log" --failregex="{{ data.fail2ban_regex }}" +{% endif %} -{% if parameters["use_cron"] -%} -#================================================= -# UPDATE THE CRON JOB -#================================================= +{% if data.use_cron -%} cron_path="/etc/cron.d/$app" ynh_add_config --template="../conf/task.cron" --destination="$cron_path" chown root: "$cron_path" chmod 644 "$cron_path" -{% endif -%} +{% endif %} #================================================= # RECONFIGURE THE APP (UPDATE CONF, APPLY MIGRATIONS...) #================================================= # UPDATE A CONFIG FILE #================================================= -ynh_script_progression --message="Updating a configuration file..." --weight=1 +ynh_script_progression --message="Updating a configuration file..." -{% if parameters["tutorial"] -%} +{% if data.generator_mode == 'tutorial' -%} ### Same as during install ### ### The file will automatically be backed-up if it's found to be manually modified (because ### ynh_add_config keeps track of the file's checksum) -{% endif -%} +{% endif %} -ynh_add_config --template="{{ parameters['custom_config_file'] }}" --destination="$install_dir/{{ parameters['custom_config_file'] }}" +ynh_add_config --template="{{ data.custom_config_file }}" --destination="$install_dir/{{ data.custom_config_file }}" -{% if parameters["tutorial"] -%} +{% if data.generator_mode == 'tutorial' -%} # FIXME: this should be handled by the core in the future # You may need to use chmod 600 instead of 400, # for example if the app is expected to be able to modify its own config -{% endif -%} +{% endif %} chmod 400 "$install_dir/some_config_file" chown $app:$app "$install_dir/some_config_file" -{% if parameters["tutorial"] -%} +{% if data.generator_mode == 'tutorial' -%} ### For more complex cases where you want to replace stuff using regexes, ### you shoud rely on ynh_replace_string (which is basically a wrapper for sed) ### When doing so, you also need to manually call ynh_store_file_checksum ### ### ynh_replace_string --match_string="match_string" --replace_string="replace_string" --target_file="$install_dir/some_config_file" ### ynh_store_file_checksum --file="$install_dir/some_config_file" -{% endif -%} +{% endif %} -{% if parameters["use_systemd_service"] -%} +{% if data.main_technology not in ["php", "none"] -%} #================================================= # START SYSTEMD SERVICE #================================================= -ynh_script_progression --message="Starting a systemd service..." --weight=1 +ynh_script_progression --message="Starting a systemd service..." ynh_systemd_action --service_name=$app --action="start" --log_path="/var/log/$app/$app.log" -{% endif -%} +{% endif %} + #================================================= # END OF SCRIPT #=================================================