From 6ff3746c5944ad04223fbe82d73fc613b21b3626 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?E=CC=81ric=20Gaspar?= <46165813+ericgaspar@users.noreply.github.com> Date: Sun, 14 Jan 2024 12:26:30 +0100 Subject: [PATCH 01/12] v2 --- conf/app.src | 7 --- conf/nginx.conf | 2 +- conf/php-fpm.conf | 2 +- manifest.toml | 64 +++++++++++++++++++++++++ scripts/_common.sh | 2 +- scripts/backup | 20 ++++---- scripts/change_url | 66 +++++++++++++------------- scripts/install | 116 ++++++++++++++++++++++----------------------- scripts/remove | 28 +++++------ scripts/restore | 44 ++++++++--------- scripts/upgrade | 80 +++++++++++++++---------------- 11 files changed, 245 insertions(+), 186 deletions(-) delete mode 100644 conf/app.src create mode 100644 manifest.toml diff --git a/conf/app.src b/conf/app.src deleted file mode 100644 index 2bf7c30..0000000 --- a/conf/app.src +++ /dev/null @@ -1,7 +0,0 @@ -SOURCE_URL=https://github.com/YunoHost-Apps/digitools_ynh/releases/download/0.2.4/digitools-v0.2.4.zip -SOURCE_SUM=efab6ba1cfed4216add53dcaae58ac0c56d27cbb5ae4072bea479cb616e5e2da -SOURCE_SUM_PRG=sha256sum -SOURCE_FORMAT=zip -SOURCE_IN_SUBDIR=true -#SOURCE_FILENAME= -SOURCE_EXTRACT=true diff --git a/conf/nginx.conf b/conf/nginx.conf index 4ac1997..04337be 100644 --- a/conf/nginx.conf +++ b/conf/nginx.conf @@ -2,7 +2,7 @@ location __PATH__/ { # Path to source - alias __FINALPATH__/ ; + alias __INSTALL_DIR__/ ; ### Example PHP configuration (remove it if not used) index index.html index.php; diff --git a/conf/php-fpm.conf b/conf/php-fpm.conf index 230b4ea..24a64f9 100644 --- a/conf/php-fpm.conf +++ b/conf/php-fpm.conf @@ -358,7 +358,7 @@ request_terminate_timeout = 1d ; Chdir to this directory at the start. ; Note: relative path can be used. ; Default Value: current directory or / when chroot -chdir = __FINALPATH__ +chdir = __INSTALL_DIR__ ; Redirect worker stdout and stderr into main error log. If not set, stdout and ; stderr will be redirected to /dev/null according to FastCGI specs. diff --git a/manifest.toml b/manifest.toml new file mode 100644 index 0000000..e245f21 --- /dev/null +++ b/manifest.toml @@ -0,0 +1,64 @@ +packaging_format = 2 + +id = "digitools" +name = "Digitools" +description.en = "Simple and useful tools for the classroom." +description.fr = "Des outils simples et utiles pour la classe." + +version = "0.2.4~ynh1" + +maintainers = ["Pierre-Amiel Giraud"] + +[upstream] +license = "GPL-3.0-only" +website = "https://ladigitale.dev/" +demo = "https://ladigitale.dev/digitools/" +admindoc = "https://codeberg.org/ladigitale/digitools/src/branch/main/README.md" +userdoc = "https://ladigitale.dev/blog/digitools-des-outils-simples-et-utiles-pour-la-classe" +code = "https://codeberg.org/ladigitale/digitools" +cpe = "???" # 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) +fund = "???" # 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. + +[integration] +yunohost = ">= 4.3.0" +architectures = "all" # FIXME: can be replaced by a list of supported archs using the dpkg --print-architecture nomenclature (amd64/i386/armhf/arm64), for example: ["amd64", "i386"] +multi_instance = true +ldap = "?" # FIXME: replace with true, false, or "not_relevant". Not to confuse with the "sso" key : the "ldap" key corresponds to wether or not a user *can* login on the app using its YunoHost credentials. +sso = "?" # FIXME: replace with true, false, or "not_relevant". Not to confuse with the "ldap" key : the "sso" key corresponds to wether or not a user is *automatically logged-in* on the app when logged-in on the YunoHost portal. +disk = "50M" # FIXME: replace with an **estimate** minimum disk requirement. e.g. 20M, 400M, 1G, ... +ram.build = "50M" # FIXME: replace with an **estimate** minimum ram requirement. e.g. 50M, 400M, 1G, ... +ram.runtime = "50M" # FIXME: replace with an **estimate** minimum ram requirement. e.g. 50M, 400M, 1G, ... + +[install] + [install.domain] + # this is a generic question - ask strings are automatically handled by Yunohost's core + type = "domain" + + [install.path] + # this is a generic question - ask strings are automatically handled by Yunohost's core + type = "path" + default = "/digitools" + + [install.init_main_permission] + help.en = "If not public, you won’t be able to share cards (for instance) with your public, unless they are users of your Yunohost instance." + help.fr = "Si l’application n’est pas publique, vous ne pourrez pas partager les cartes (par exemple) avec vos élèves, sauf s’ils sont tous utilisateurs de votre instance Yunohost." + type = "group" + default = "visitors" + +[resources] + [resources.sources.main] + url = "https://github.com/YunoHost-Apps/digitools_ynh/releases/download/0.2.4/digitools-v0.2.4.zip" + sha256 = "efab6ba1cfed4216add53dcaae58ac0c56d27cbb5ae4072bea479cb616e5e2da" + + + [resources.system_user] + + [resources.install_dir] + + [resources.data_dir] + + [resources.permissions] + main.url = "/" + + [resources.database] + type = "mysql" diff --git a/scripts/_common.sh b/scripts/_common.sh index 5007fea..db33a7d 100644 --- a/scripts/_common.sh +++ b/scripts/_common.sh @@ -5,7 +5,7 @@ #================================================= # dependencies used by the app -pkg_dependencies="php$YNH_DEFAULT_PHP_VERSION-sqlite3" +#REMOVEME? pkg_dependencies="php$YNH_DEFAULT_PHP_VERSION-sqlite3" #================================================= # PERSONAL HELPERS diff --git a/scripts/backup b/scripts/backup index 1c7b676..f3ede60 100644 --- a/scripts/backup +++ b/scripts/backup @@ -14,25 +14,25 @@ source /usr/share/yunohost/helpers # MANAGE SCRIPT FAILURE #================================================= -ynh_clean_setup () { +#REMOVEME? ynh_clean_setup () { ### Remove this function if there's nothing to clean before calling the remove script. true } # Exit if an error occurs during the execution of the script -ynh_abort_if_errors +#REMOVEME? ynh_abort_if_errors #================================================= # LOAD SETTINGS #================================================= -ynh_print_info --message="Loading installation settings..." +#REMOVEME? ynh_print_info --message="Loading installation settings..." -app=$YNH_APP_INSTANCE_NAME +#REMOVEME? app=$YNH_APP_INSTANCE_NAME -final_path=$(ynh_app_setting_get --app=$app --key=final_path) -domain=$(ynh_app_setting_get --app=$app --key=domain) -db_name=$(ynh_app_setting_get --app=$app --key=db_name) -phpversion=$(ynh_app_setting_get --app=$app --key=phpversion) -datadir=$(ynh_app_setting_get --app=$app --key=datadir) +#REMOVEME? #REMOVEME? install_dir=$(ynh_app_setting_get --app=$app --key=install_dir) +#REMOVEME? domain=$(ynh_app_setting_get --app=$app --key=domain) +#REMOVEME? db_name=$(ynh_app_setting_get --app=$app --key=db_name) +#REMOVEME? phpversion=$(ynh_app_setting_get --app=$app --key=phpversion) +#REMOVEME? data_dir=$(ynh_app_setting_get --app=$app --key=data_dir) #================================================= # DECLARE DATA AND CONF FILES TO BACKUP @@ -48,7 +48,7 @@ ynh_print_info --message="Declaring files to be backed up..." # BACKUP THE APP MAIN DIR #================================================= -ynh_backup --src_path="$final_path" +ynh_backup --src_path="$install_dir" #================================================= # BACKUP THE NGINX CONFIGURATION diff --git a/scripts/change_url b/scripts/change_url index 5c958cc..f42dbab 100644 --- a/scripts/change_url +++ b/scripts/change_url @@ -13,58 +13,58 @@ source /usr/share/yunohost/helpers # RETRIEVE ARGUMENTS #================================================= -old_domain=$YNH_APP_OLD_DOMAIN -old_path=$YNH_APP_OLD_PATH +#REMOVEME? old_domain=$YNH_APP_OLD_DOMAIN +#REMOVEME? old_path=$YNH_APP_OLD_PATH -new_domain=$YNH_APP_NEW_DOMAIN -new_path=$YNH_APP_NEW_PATH +#REMOVEME? new_domain=$YNH_APP_NEW_DOMAIN +#REMOVEME? new_path=$YNH_APP_NEW_PATH -app=$YNH_APP_INSTANCE_NAME +#REMOVEME? app=$YNH_APP_INSTANCE_NAME #================================================= # LOAD SETTINGS #================================================= -ynh_script_progression --message="Loading installation settings..." --time --weight=1 +#REMOVEME? ynh_script_progression --message="Loading installation settings..." --time --weight=1 -# Needed for helper "ynh_add_nginx_config" -final_path=$(ynh_app_setting_get --app=$app --key=final_path) +#REMOVEME? # Needed for helper "ynh_add_nginx_config" +#REMOVEME? #REMOVEME? install_dir=$(ynh_app_setting_get --app=$app --key=install_dir) # Add settings here as needed by your application -#db_name=$(ynh_app_setting_get --app=$app --key=db_name) +#REMOVEME? #db_name=$(ynh_app_setting_get --app=$app --key=db_name) #db_user=$db_name -#db_pwd=$(ynh_app_setting_get --app=$app --key=db_pwd) +#REMOVEME? #db_pwd=$(ynh_app_setting_get --app=$app --key=db_pwd) #================================================= # BACKUP BEFORE CHANGE URL THEN ACTIVE TRAP #================================================= -ynh_script_progression --message="Backing up the app before changing its URL (may take a while)..." --time --weight=1 +#REMOVEME? ynh_script_progression --message="Backing up the app before changing its URL (may take a while)..." --time --weight=1 # Backup the current version of the app -ynh_backup_before_upgrade -ynh_clean_setup () { +#REMOVEME? ynh_backup_before_upgrade +#REMOVEME? ynh_clean_setup () { # Remove the new domain config file, the remove script won't do it as it doesn't know yet its location. - ynh_secure_remove --file="/etc/nginx/conf.d/$new_domain.d/$app.conf" +#REMOVEME? ynh_secure_remove --file="/etc/nginx/conf.d/$new_domain.d/$app.conf" # Restore it if the upgrade fails - ynh_restore_upgradebackup +#REMOVEME? ynh_restore_upgradebackup } # Exit if an error occurs during the execution of the script -ynh_abort_if_errors +#REMOVEME? ynh_abort_if_errors #================================================= # CHECK WHICH PARTS SHOULD BE CHANGED #================================================= -change_domain=0 -if [ "$old_domain" != "$new_domain" ] +#REMOVEME? change_domain=0 +#REMOVEME? if [ "$old_domain" != "$new_domain" ] then - change_domain=1 + #REMOVEME? change_domain=1 fi -change_path=0 -if [ "$old_path" != "$new_path" ] +#REMOVEME? change_path=0 +#REMOVEME? if [ "$old_path" != "$new_path" ] then - change_path=1 + #REMOVEME? change_path=1 fi #================================================= @@ -74,28 +74,30 @@ fi #================================================= ynh_script_progression --message="Updating NGINX web server configuration..." --time --weight=1 -nginx_conf_path=/etc/nginx/conf.d/$old_domain.d/$app.conf +ynh_change_url_nginx_config + +#REMOVEME? nginx_conf_path=/etc/nginx/conf.d/$old_domain.d/$app.conf # Change the path in the NGINX config file if [ $change_path -eq 1 ] then # Make a backup of the original NGINX config file if modified - ynh_backup_if_checksum_is_different --file="$nginx_conf_path" +#REMOVEME? ynh_backup_if_checksum_is_different --file="$nginx_conf_path" # Set global variables for NGINX helper - domain="$old_domain" - path_url="$new_path" +#REMOVEME? domain="$old_domain" +#REMOVEME? path="$new_path" # Create a dedicated NGINX config - ynh_add_nginx_config +#REMOVEME? ynh_add_nginx_config fi # Change the domain for NGINX if [ $change_domain -eq 1 ] then # Delete file checksum for the old conf file location - ynh_delete_file_checksum --file="$nginx_conf_path" - mv $nginx_conf_path /etc/nginx/conf.d/$new_domain.d/$app.conf +#REMOVEME? ynh_delete_file_checksum --file="$nginx_conf_path" +#REMOVEME? mv $nginx_conf_path /etc/nginx/conf.d/$new_domain.d/$app.conf # Store file checksum for the new config file location - ynh_store_file_checksum --file="/etc/nginx/conf.d/$new_domain.d/$app.conf" +#REMOVEME? ynh_store_file_checksum --file="/etc/nginx/conf.d/$new_domain.d/$app.conf" fi #================================================= @@ -109,9 +111,9 @@ fi #================================================= # RELOAD NGINX #================================================= -ynh_script_progression --message="Reloading NGINX web server..." --time --weight=1 +#REMOVEME? ynh_script_progression --message="Reloading NGINX web server..." --time --weight=1 -ynh_systemd_action --service_name=nginx --action=reload +#REMOVEME? #REMOVEME? ynh_systemd_action --service_name=nginx --action=reload #================================================= # END OF SCRIPT diff --git a/scripts/install b/scripts/install index 0988cfe..25dbcdd 100644 --- a/scripts/install +++ b/scripts/install @@ -13,35 +13,35 @@ source /usr/share/yunohost/helpers # MANAGE SCRIPT FAILURE #================================================= -ynh_clean_setup () { +#REMOVEME? ynh_clean_setup () { ### Remove this function if there's nothing to clean before calling the remove script. true } # Exit if an error occurs during the execution of the script -ynh_abort_if_errors +#REMOVEME? ynh_abort_if_errors #================================================= # RETRIEVE ARGUMENTS FROM THE MANIFEST #================================================= -domain=$YNH_APP_ARG_DOMAIN -path_url=$YNH_APP_ARG_PATH -#admin=$YNH_APP_ARG_ADMIN -is_public=$YNH_APP_ARG_IS_PUBLIC -#language=$YNH_APP_ARG_LANGUAGE -#password=$YNH_APP_ARG_PASSWORD +#REMOVEME? domain=$YNH_APP_ARG_DOMAIN +#REMOVEME? path=$YNH_APP_ARG_PATH +#REMOVEME? #admin=$YNH_APP_ARG_ADMIN +#REMOVEME? is_public=$YNH_APP_ARG_IS_PUBLIC +#REMOVEME? #language=$YNH_APP_ARG_LANGUAGE +#REMOVEME? #password=$YNH_APP_ARG_PASSWORD ### If it's a multi-instance app, meaning it can be installed several times independently ### The id of the app as stated in the manifest is available as $YNH_APP_ID ### The instance number is available as $YNH_APP_INSTANCE_NUMBER (equals "1", "2"...) -### The app instance name is available as $YNH_APP_INSTANCE_NAME -### - the first time the app is installed, YNH_APP_INSTANCE_NAME = ynhexample -### - the second time the app is installed, YNH_APP_INSTANCE_NAME = ynhexample__2 +#REMOVEME? ### The app instance name is available as $YNH_APP_INSTANCE_NAME +#REMOVEME? ### - the first time the app is installed, YNH_APP_INSTANCE_NAME = ynhexample +#REMOVEME? ### - the second time the app is installed, YNH_APP_INSTANCE_NAME = ynhexample__2 ### - ynhexample__{N} for the subsequent installations, with N=3,4... ### The app instance name is probably what interests you most, since this is ### guaranteed to be unique. This is a good unique identifier to define installation path, ### db names... -app=$YNH_APP_INSTANCE_NAME +#REMOVEME? app=$YNH_APP_INSTANCE_NAME #================================================= # CHECK IF THE APP CAN BE INSTALLED WITH THESE ARGS @@ -54,24 +54,24 @@ app=$YNH_APP_INSTANCE_NAME ### Use the execution time, given by --time, to estimate the weight of a step. ### A common way to do it is to set a weight equal to the execution time in second +1. ### The execution time is given for the duration since the previous call. So the weight should be applied to this previous call. -ynh_script_progression --message="Validating installation parameters..." --weight=1 +#REMOVEME? ynh_script_progression --message="Validating installation parameters..." --weight=1 ### If the app uses NGINX as web server (written in HTML/PHP in most cases), the final path should be "/var/www/$app". ### If the app provides an internal web server (or uses another application server such as uWSGI), the final path should be "/opt/yunohost/$app" -final_path=/var/www/$app -test ! -e "$final_path" || ynh_die --message="This path already contains a folder" +#REMOVEME? install_dir=/var/www/$app +#REMOVEME? test ! -e "$install_dir" || ynh_die --message="This path already contains a folder" # Register (book) web path -ynh_webpath_register --app=$app --domain=$domain --path_url=$path_url +#REMOVEME? ynh_webpath_register --app=$app --domain=$domain --path=$path #================================================= # STORE SETTINGS FROM MANIFEST #================================================= -ynh_script_progression --message="Storing installation settings..." --weight=1 +#REMOVEME? ynh_script_progression --message="Storing installation settings..." --weight=1 -ynh_app_setting_set --app=$app --key=domain --value=$domain -ynh_app_setting_set --app=$app --key=path --value=$path_url -#ynh_app_setting_set --app=$app --key=admin --value=$admin +#REMOVEME? ynh_app_setting_set --app=$app --key=domain --value=$domain +#REMOVEME? ynh_app_setting_set --app=$app --key=path --value=$path +#REMOVEME? #ynh_app_setting_set --app=$app --key=admin --value=$admin #ynh_app_setting_set --app=$app --key=language --value=$language #================================================= @@ -79,16 +79,16 @@ ynh_app_setting_set --app=$app --key=path --value=$path_url #================================================= # FIND AND OPEN A PORT #================================================= -#ynh_script_progression --message="Finding an available port..." --weight=1 +#REMOVEME? #ynh_script_progression --message="Finding an available port..." --weight=1 ### Use these lines if you have to open a port for the application -### `ynh_find_port` will find the first available port starting from the given port. +#REMOVEME? ### `ynh_find_port` will find the first available port starting from the given port. ### If you're not using these lines: ### - Remove the section "CLOSE A PORT" in the remove script # Find an available port -#port=$(ynh_find_port --port=8095) -#ynh_app_setting_set --app=$app --key=port --value=$port +#REMOVEME? #port=$(ynh_find_port --port=8095) +#REMOVEME? #ynh_app_setting_set --app=$app --key=port --value=$port # Optional: Expose this port publicly # (N.B.: you only need to do this if the app actually needs to expose the port publicly. @@ -101,9 +101,9 @@ ynh_app_setting_set --app=$app --key=path --value=$path_url #================================================= # INSTALL DEPENDENCIES #================================================= -ynh_script_progression --message="Installing dependencies..." --weight=1 +#REMOVEME? ynh_script_progression --message="Installing dependencies..." --weight=1 -### `ynh_install_app_dependencies` allows you to add any "apt" dependencies to the package. +#REMOVEME? ### `ynh_install_app_dependencies` allows you to add any "apt" dependencies to the package. ### Those deb packages will be installed as dependencies of this package. ### If you're not using this helper: ### - Remove the section "REMOVE DEPENDENCIES" in the remove script @@ -111,23 +111,23 @@ ynh_script_progression --message="Installing dependencies..." --weight=1 ### - As well as the section "REINSTALL DEPENDENCIES" in the restore script ### - And the section "UPGRADE DEPENDENCIES" in the upgrade script -ynh_install_app_dependencies $pkg_dependencies +#REMOVEME? ynh_install_app_dependencies $pkg_dependencies #================================================= # CREATE DEDICATED USER #================================================= -ynh_script_progression --message="Configuring system user..." --weight=1 +#REMOVEME? ynh_script_progression --message="Configuring system user..." --weight=1 # Create a system user -ynh_system_user_create --username=$app --home_dir="$final_path" +#REMOVEME? ynh_system_user_create --username=$app --home_dir="$install_dir" #================================================= # CREATE A MYSQL DATABASE #================================================= -#ynh_script_progression --message="Creating a MySQL database..." --time --weight=1 +#REMOVEME? #ynh_script_progression --message="Creating a MySQL database..." --time --weight=1 ### Use these lines if you need a database for the application. -### `ynh_mysql_setup_db` will create a database, an associated user and a ramdom password. +#REMOVEME? ### `ynh_mysql_setup_db` will create a database, an associated user and a ramdom password. ### The password will be stored as 'mysqlpwd' into the app settings, ### and will be available as $db_pwd ### If you're not using these lines: @@ -137,8 +137,8 @@ ynh_system_user_create --username=$app --home_dir="$final_path" #db_name=$(ynh_sanitize_dbid --db_name=$app) #db_user=$db_name -#ynh_app_setting_set --app=$app --key=db_name --value=$db_name -#ynh_mysql_setup_db --db_user=$db_user --db_name=$db_name +#REMOVEME? #ynh_app_setting_set --app=$app --key=db_name --value=$db_name +#REMOVEME? #ynh_mysql_setup_db --db_user=$db_user --db_name=$db_name #================================================= # DOWNLOAD, CHECK AND UNPACK SOURCE @@ -149,9 +149,9 @@ ynh_script_progression --message="Setting up source files..." --weight=1 ### downloaded from an upstream source, like a git repository. ### `ynh_setup_source` use the file conf/app.src -ynh_app_setting_set --app=$app --key=final_path --value=$final_path +#REMOVEME? ynh_app_setting_set --app=$app --key=install_dir --value=$install_dir # Download, check integrity, uncompress and patch the source from app.src -ynh_setup_source --dest_dir="$final_path" +ynh_setup_source --dest_dir="$install_dir" # FIXME: this should be managed by the core in the future # Here, as a packager, you may have to tweak the ownerhsip/permissions @@ -159,9 +159,9 @@ ynh_setup_source --dest_dir="$final_path" # files in some cases. # But FOR THE LOVE OF GOD, do not allow r/x for "others" on the entire folder - # this will be treated as a security issue. -chmod 750 "$final_path" -chmod -R o-rwx "$final_path" -chown -R $app:www-data "$final_path" +chmod 750 "$install_dir" +chmod -R o-rwx "$install_dir" +chown -R $app:www-data "$install_dir" #================================================= # NGINX CONFIGURATION @@ -211,10 +211,10 @@ ynh_add_fpm_config ### - Remove the section "RESTORE THE DATA DIRECTORY" in the restore script ### - As well as the section "REMOVE DATA DIR" in the remove script -#datadir=/home/yunohost.app/$app -#ynh_app_setting_set --app=$app --key=datadir --value=$datadir +#data_dir=/home/yunohost.app/$app +#REMOVEME? #ynh_app_setting_set --app=$app --key=data_dir --value=$data_dir -#mkdir -p $datadir +#mkdir -p $data_dir # FIXME: this should be managed by the core in the future # Here, as a packager, you may have to tweak the ownerhsip/permissions @@ -222,9 +222,9 @@ ynh_add_fpm_config # files in some cases. # But FOR THE LOVE OF GOD, do not allow r/x for "others" on the entire folder - # this will be treated as a security issue. -#chmod 750 "$datadir" -#chmod -R o-rwx "$datadir" -#chown -R $app:www-data "$datadir" +#chmod 750 "$data_dir" +#chmod -R o-rwx "$data_dir" +#chown -R $app:www-data "$data_dir" #================================================= # ADD A CONFIGURATION @@ -243,20 +243,20 @@ ynh_add_fpm_config ### ### Check the documentation of `ynh_add_config` for more info. -#ynh_add_config --template="some_config_file" --destination="$final_path/some_config_file" +#ynh_add_config --template="some_config_file" --destination="$install_dir/some_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 "$final_path/some_config_file" -#chown $app:$app "$final_path/some_config_file" +#chmod 400 "$install_dir/some_config_file" +#chown $app:$app "$install_dir/some_config_file" ### 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="$final_path/some_config_file" -### ynh_store_file_checksum --file="$final_path/some_config_file" +### 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" #================================================= # SETUP SYSTEMD @@ -289,14 +289,14 @@ ynh_add_fpm_config # Set the app as temporarily public for curl call #ynh_script_progression --message="Configuring SSOwat..." --time --weight=1 # Making the app public for curl -#ynh_permission_update --permission="main" --add="visitors" +#REMOVEME? #ynh_permission_update --permission="main" --add="visitors" # Installation with curl #ynh_script_progression --message="Finalizing installation..." --time --weight=1 #ynh_local_curl "/INSTALL_PATH" "key1=value1" "key2=value2" "key3=value3" # Remove the public access -#ynh_permission_update --permission="main" --remove="visitors" +#REMOVEME? #ynh_permission_update --permission="main" --remove="visitors" #================================================= # GENERIC FINALIZATION @@ -375,33 +375,33 @@ ynh_add_fpm_config #================================================= # SETUP SSOWAT #================================================= -ynh_script_progression --message="Configuring permissions..." --weight=1 +#REMOVEME? ynh_script_progression --message="Configuring permissions..." --weight=1 # Make app public if necessary -if [ $is_public -eq 1 ] +#REMOVEME? if [ $is_public -eq 1 ] then # Everyone can access the app. # The "main" permission is automatically created before the install script. - ynh_permission_update --permission="main" --add="visitors" +#REMOVEME? ynh_permission_update --permission="main" --add="visitors" fi ### N.B. : the following extra permissions only make sense if your app ### does have for example an admin interface or an API. # Only the admin can access the admin panel of the app (if the app has an admin panel) -#ynh_permission_create --permission="admin" --url="/admin" --allowed=$admin +#REMOVEME? #ynh_permission_create --permission="admin" --url="/admin" --allowed=$admin # Everyone can access the API part # We don't want to display the tile in the SSO so we put --show_tile="false" # And we don't want the YunoHost admin to be able to remove visitors group to this permission, so we put --protected="true" -#ynh_permission_create --permission="api" --url="/api" --allowed="visitors" --show_tile="false" --protected="true" +#REMOVEME? #ynh_permission_create --permission="api" --url="/api" --allowed="visitors" --show_tile="false" --protected="true" #================================================= # RELOAD NGINX #================================================= -ynh_script_progression --message="Reloading NGINX web server..." --weight=1 +#REMOVEME? ynh_script_progression --message="Reloading NGINX web server..." --weight=1 -ynh_systemd_action --service_name=nginx --action=reload +#REMOVEME? ynh_systemd_action --service_name=nginx --action=reload #================================================= # END OF SCRIPT diff --git a/scripts/remove b/scripts/remove index 2019d36..63f006f 100644 --- a/scripts/remove +++ b/scripts/remove @@ -12,26 +12,26 @@ source /usr/share/yunohost/helpers #================================================= # LOAD SETTINGS #================================================= -ynh_script_progression --message="Loading installation settings..." --weight=1 +#REMOVEME? ynh_script_progression --message="Loading installation settings..." --weight=1 -app=$YNH_APP_INSTANCE_NAME +#REMOVEME? app=$YNH_APP_INSTANCE_NAME -domain=$(ynh_app_setting_get --app=$app --key=domain) -port=$(ynh_app_setting_get --app=$app --key=port) -db_name=$(ynh_app_setting_get --app=$app --key=db_name) -db_user=$db_name -final_path=$(ynh_app_setting_get --app=$app --key=final_path) -datadir=$(ynh_app_setting_get --app=$app --key=datadir) +#REMOVEME? domain=$(ynh_app_setting_get --app=$app --key=domain) +#REMOVEME? port=$(ynh_app_setting_get --app=$app --key=port) +#REMOVEME? db_name=$(ynh_app_setting_get --app=$app --key=db_name) +#REMOVEME? db_user=$db_name +#REMOVEME? #REMOVEME? install_dir=$(ynh_app_setting_get --app=$app --key=install_dir) +#REMOVEME? data_dir=$(ynh_app_setting_get --app=$app --key=data_dir) #================================================= # STANDARD REMOVE #================================================= # REMOVE APP MAIN DIR #================================================= -ynh_script_progression --message="Removing app main directory..." --weight=1 +#REMOVEME? ynh_script_progression --message="Removing app main directory..." --weight=1 # Remove the app directory securely -ynh_secure_remove --file="$final_path" +#REMOVEME? ynh_secure_remove --file="$install_dir" #================================================= # REMOVE NGINX CONFIGURATION @@ -52,10 +52,10 @@ ynh_remove_fpm_config #================================================= # REMOVE DEPENDENCIES #================================================= -ynh_script_progression --message="Removing dependencies..." --weight=1 +#REMOVEME? ynh_script_progression --message="Removing dependencies..." --weight=1 # Remove metapackage and its dependencies -ynh_remove_app_dependencies +#REMOVEME? ynh_remove_app_dependencies #================================================= # REMOVE FAIL2BAN CONFIGURATION @@ -86,10 +86,10 @@ ynh_secure_remove --file="/var/log/$app" #================================================= # REMOVE DEDICATED USER #================================================= -ynh_script_progression --message="Removing the dedicated system user..." --weight=1 +#REMOVEME? ynh_script_progression --message="Removing the dedicated system user..." --weight=1 # Delete a system user -ynh_system_user_delete --username=$app +#REMOVEME? ynh_system_user_delete --username=$app #================================================= # END OF SCRIPT diff --git a/scripts/restore b/scripts/restore index 2c60b67..095af0d 100644 --- a/scripts/restore +++ b/scripts/restore @@ -14,35 +14,35 @@ source /usr/share/yunohost/helpers # MANAGE SCRIPT FAILURE #================================================= -ynh_clean_setup () { +#REMOVEME? ynh_clean_setup () { #### Remove this function if there's nothing to clean before calling the remove script. true } # Exit if an error occurs during the execution of the script -ynh_abort_if_errors +#REMOVEME? ynh_abort_if_errors #================================================= # LOAD SETTINGS #================================================= -ynh_script_progression --message="Loading installation settings..." --weight=1 +#REMOVEME? ynh_script_progression --message="Loading installation settings..." --weight=1 -app=$YNH_APP_INSTANCE_NAME +#REMOVEME? app=$YNH_APP_INSTANCE_NAME -domain=$(ynh_app_setting_get --app=$app --key=domain) -path_url=$(ynh_app_setting_get --app=$app --key=path) -final_path=$(ynh_app_setting_get --app=$app --key=final_path) -db_name=$(ynh_app_setting_get --app=$app --key=db_name) -db_user=$db_name -phpversion=$(ynh_app_setting_get --app=$app --key=phpversion) -datadir=$(ynh_app_setting_get --app=$app --key=datadir) +#REMOVEME? domain=$(ynh_app_setting_get --app=$app --key=domain) +#REMOVEME? path=$(ynh_app_setting_get --app=$app --key=path) +#REMOVEME? #REMOVEME? install_dir=$(ynh_app_setting_get --app=$app --key=install_dir) +#REMOVEME? db_name=$(ynh_app_setting_get --app=$app --key=db_name) +#REMOVEME? db_user=$db_name +#REMOVEME? phpversion=$(ynh_app_setting_get --app=$app --key=phpversion) +#REMOVEME? data_dir=$(ynh_app_setting_get --app=$app --key=data_dir) #================================================= # CHECK IF THE APP CAN BE RESTORED #================================================= -ynh_script_progression --message="Validating restoration parameters..." --weight=1 +#REMOVEME? ynh_script_progression --message="Validating restoration parameters..." --weight=1 -test ! -d $final_path \ - || ynh_die --message="There is already a directory: $final_path " +#REMOVEME? test ! -d $install_dir \ + || ynh_die --message="There is already a directory: $install_dir " #================================================= # STANDARD RESTORATION STEPS @@ -56,17 +56,17 @@ ynh_restore_file --origin_path="/etc/nginx/conf.d/$domain.d/$app.conf" #================================================= # RECREATE THE DEDICATED USER #================================================= -ynh_script_progression --message="Recreating the dedicated system user..." --weight=1 +#REMOVEME? ynh_script_progression --message="Recreating the dedicated system user..." --weight=1 # Create the dedicated user (if not existing) -ynh_system_user_create --username=$app --home_dir="$final_path" +#REMOVEME? ynh_system_user_create --username=$app --home_dir="$install_dir" #================================================= # RESTORE THE APP MAIN DIR #================================================= ynh_script_progression --message="Restoring the app main directory..." --weight=1 -ynh_restore_file --origin_path="$final_path" +ynh_restore_file --origin_path="$install_dir" # FIXME: this should be managed by the core in the future # Here, as a packager, you may have to tweak the ownerhsip/permissions @@ -74,9 +74,9 @@ ynh_restore_file --origin_path="$final_path" # files in some cases. # But FOR THE LOVE OF GOD, do not allow r/x for "others" on the entire folder - # this will be treated as a security issue. -chmod 750 "$final_path" -chmod -R o-rwx "$final_path" -chown -R $app:www-data "$final_path" +chmod 750 "$install_dir" +chmod -R o-rwx "$install_dir" +chown -R $app:www-data "$install_dir" #================================================= # RESTORE THE PHP-FPM CONFIGURATION @@ -99,10 +99,10 @@ ynh_restore_file --origin_path="/etc/php/$phpversion/fpm/pool.d/$app.conf" #================================================= # REINSTALL DEPENDENCIES #================================================= -ynh_script_progression --message="Reinstalling dependencies..." --weight=1 +#REMOVEME? ynh_script_progression --message="Reinstalling dependencies..." --weight=1 # Define and install dependencies -ynh_install_app_dependencies $pkg_dependencies +#REMOVEME? ynh_install_app_dependencies $pkg_dependencies #================================================= # RESTORE VARIOUS FILES diff --git a/scripts/upgrade b/scripts/upgrade index 0905b70..311e9d8 100644 --- a/scripts/upgrade +++ b/scripts/upgrade @@ -12,16 +12,16 @@ source /usr/share/yunohost/helpers #================================================= # LOAD SETTINGS #================================================= -ynh_script_progression --message="Loading installation settings..." --weight=1 +#REMOVEME? ynh_script_progression --message="Loading installation settings..." --weight=1 -app=$YNH_APP_INSTANCE_NAME +#REMOVEME? app=$YNH_APP_INSTANCE_NAME -domain=$(ynh_app_setting_get --app=$app --key=domain) -path_url=$(ynh_app_setting_get --app=$app --key=path) -admin=$(ynh_app_setting_get --app=$app --key=admin) -final_path=$(ynh_app_setting_get --app=$app --key=final_path) -#language=$(ynh_app_setting_get --app=$app --key=language) -db_name=$(ynh_app_setting_get --app=$app --key=db_name) +#REMOVEME? domain=$(ynh_app_setting_get --app=$app --key=domain) +#REMOVEME? path=$(ynh_app_setting_get --app=$app --key=path) +#REMOVEME? admin=$(ynh_app_setting_get --app=$app --key=admin) +#REMOVEME? #REMOVEME? install_dir=$(ynh_app_setting_get --app=$app --key=install_dir) +#REMOVEME? #language=$(ynh_app_setting_get --app=$app --key=language) +#REMOVEME? db_name=$(ynh_app_setting_get --app=$app --key=db_name) #================================================= # CHECK VERSION @@ -38,16 +38,16 @@ upgrade_type=$(ynh_check_app_version_changed) #================================================= # BACKUP BEFORE UPGRADE THEN ACTIVE TRAP #================================================= -ynh_script_progression --message="Backing up the app before upgrading (may take a while)..." --weight=1 +#REMOVEME? ynh_script_progression --message="Backing up the app before upgrading (may take a while)..." --weight=1 # Backup the current version of the app -ynh_backup_before_upgrade -ynh_clean_setup () { +#REMOVEME? ynh_backup_before_upgrade +#REMOVEME? ynh_clean_setup () { # Restore it if the upgrade fails - ynh_restore_upgradebackup +#REMOVEME? ynh_restore_upgradebackup } # Exit if an error occurs during the execution of the script -ynh_abort_if_errors +#REMOVEME? ynh_abort_if_errors #================================================= # STANDARD UPGRADE STEPS @@ -65,42 +65,42 @@ ynh_script_progression --message="Ensuring downward compatibility..." --weight=1 # If db_name doesn't exist, create it #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 +#REMOVEME? # ynh_app_setting_set --app=$app --key=db_name --value=$db_name #fi -# If final_path doesn't exist, create it -#if [ -z "$final_path" ]; then -# final_path=/var/www/$app -# ynh_app_setting_set --app=$app --key=final_path --value=$final_path +# If install_dir doesn't exist, create it +#if [ -z "$install_dir" ]; then +# install_dir=/var/www/$app +#REMOVEME? # ynh_app_setting_set --app=$app --key=install_dir --value=$install_dir #fi ### If nobody installed your app before 4.1, ### then you may safely remove these lines # Cleaning legacy permissions -if ynh_legacy_permissions_exists; then - ynh_legacy_permissions_delete_all +#REMOVEME? if ynh_legacy_permissions_exists; then +#REMOVEME? ynh_legacy_permissions_delete_all ynh_app_setting_delete --app=$app --key=is_public fi -if ! ynh_permission_exists --permission="admin"; then +#REMOVEME? if ! ynh_permission_exists --permission="admin"; then # Create the required permissions - ynh_permission_create --permission="admin" --url="/admin" --allowed=$admin +#REMOVEME? ynh_permission_create --permission="admin" --url="/admin" --allowed=$admin fi # Create a permission if needed -if ! ynh_permission_exists --permission="api"; then - ynh_permission_create --permission="api" --url="/api" --allowed="visitors" --show_tile="false" --protected="true" +#REMOVEME? if ! ynh_permission_exists --permission="api"; then +#REMOVEME? ynh_permission_create --permission="api" --url="/api" --allowed="visitors" --show_tile="false" --protected="true" fi #================================================= # CREATE DEDICATED USER #================================================= -ynh_script_progression --message="Making sure dedicated system user exists..." --weight=1 +#REMOVEME? ynh_script_progression --message="Making sure dedicated system user exists..." --weight=1 # Create a dedicated user (if not existing) -ynh_system_user_create --username=$app --home_dir="$final_path" +#REMOVEME? ynh_system_user_create --username=$app --home_dir="$install_dir" #================================================= # DOWNLOAD, CHECK AND UNPACK SOURCE @@ -111,28 +111,28 @@ then ynh_script_progression --message="Upgrading source files..." --weight=1 # Test because file doesn’t exist at first install. It is created at first page opening. - if test -f "$final_path/inc/digiwords.db" + if test -f "$install_dir/inc/digiwords.db" then # Create a temporary directory tmpdir="$(mktemp -d)" # Backup the inc/digiwords.db file to the temp dir - cp -ar "$final_path/inc/digiwords.db" "$tmpdir/digiwords.db" + cp -ar "$install_dir/inc/digiwords.db" "$tmpdir/digiwords.db" # Remove the app directory securely - ynh_secure_remove --file=$final_path +#REMOVEME? ynh_secure_remove --file=$install_dir # Download, check integrity, uncompress and patch the source from app.src - ynh_setup_source --dest_dir="$final_path" + ynh_setup_source --dest_dir="$install_dir" - # Copy digiwords.db back to the final_path - cp -ar "$tmpdir/digiwords.db" "$final_path/inc/digiwords.db" + # Copy digiwords.db back to the install_dir + cp -ar "$tmpdir/digiwords.db" "$install_dir/inc/digiwords.db" # Remove the tmp directory securely ynh_secure_remove --file="$tmpdir" else # Download, check integrity, uncompress and patch the source from app.src - ynh_setup_source --dest_dir="$final_path" + ynh_setup_source --dest_dir="$install_dir" fi fi @@ -142,9 +142,9 @@ fi # files in some cases. # But FOR THE LOVE OF GOD, do not allow r/x for "others" on the entire folder - # this will be treated as a security issue. -chmod 750 "$final_path" -chmod -R o-rwx "$final_path" -chown -R $app:www-data "$final_path" +chmod 750 "$install_dir" +chmod -R o-rwx "$install_dir" +chown -R $app:www-data "$install_dir" #================================================= # NGINX CONFIGURATION @@ -157,9 +157,9 @@ ynh_add_nginx_config #================================================= # UPGRADE DEPENDENCIES #================================================= -ynh_script_progression --message="Upgrading dependencies..." --weight=1 +#REMOVEME? ynh_script_progression --message="Upgrading dependencies..." --weight=1 -ynh_install_app_dependencies $pkg_dependencies +#REMOVEME? ynh_install_app_dependencies $pkg_dependencies #================================================= # PHP-FPM CONFIGURATION @@ -186,9 +186,9 @@ ynh_add_fpm_config #================================================= # RELOAD NGINX #================================================= -ynh_script_progression --message="Reloading NGINX web server..." --weight=1 +#REMOVEME? ynh_script_progression --message="Reloading NGINX web server..." --weight=1 -ynh_systemd_action --service_name=nginx --action=reload +#REMOVEME? ynh_systemd_action --service_name=nginx --action=reload #================================================= # END OF SCRIPT From e7d2a4ba5bf398381e4e16b2a1ad2bcee318f703 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?E=CC=81ric=20Gaspar?= <46165813+ericgaspar@users.noreply.github.com> Date: Sun, 14 Jan 2024 12:34:04 +0100 Subject: [PATCH 02/12] v2 --- check_process | 34 ---- conf/extra_php-fpm.conf | 4 + conf/nginx.conf | 6 +- conf/php-fpm.conf | 431 ---------------------------------------- manifest.json | 56 ------ manifest.toml | 32 +-- scripts/_common.sh | 3 - scripts/backup | 46 ----- scripts/change_url | 97 --------- scripts/install | 375 ---------------------------------- scripts/remove | 74 +------ scripts/restore | 88 +------- scripts/upgrade | 136 +------------ tests.toml | 5 + 14 files changed, 33 insertions(+), 1354 deletions(-) delete mode 100644 check_process create mode 100644 conf/extra_php-fpm.conf delete mode 100644 conf/php-fpm.conf delete mode 100644 manifest.json create mode 100644 tests.toml diff --git a/check_process b/check_process deleted file mode 100644 index 575e9b7..0000000 --- a/check_process +++ /dev/null @@ -1,34 +0,0 @@ -# See here for more information -# https://github.com/YunoHost/package_check#syntax-check_process-file - -# Move this file from check_process.default to check_process when you have filled it. - -;; Test complet - ; Manifest - domain="domain.tld" - path="/path" - # admin="john" - language="fr" - is_public=1 - # password="1Strong-Password" - # port="666" - ; Checks - pkg_linter=1 - setup_sub_dir=1 - setup_root=1 - setup_nourl=0 - setup_private=1 - setup_public=1 - upgrade=1 - upgrade=1 from_commit=8091fdc9c9283ce29e7fde5fcc4fc77b8738ce58 - backup_restore=1 - multi_instance=1 - port_already_use=0 - change_url=1 -;;; Options -Email= -Notification=none -;;; Upgrade options - ; commit=8091fdc9c9283ce29e7fde5fcc4fc77b8738ce58 - ;name=Upgrade from 0.2.3. - ;manifest_arg=domain=DOMAIN&path=PATH&language=fr&is_public=1& diff --git a/conf/extra_php-fpm.conf b/conf/extra_php-fpm.conf new file mode 100644 index 0000000..700c37c --- /dev/null +++ b/conf/extra_php-fpm.conf @@ -0,0 +1,4 @@ +; Additional php.ini defines, specific to this pool of workers. + +php_admin_value[upload_max_filesize] = 50M +php_admin_value[post_max_size] = 50M diff --git a/conf/nginx.conf b/conf/nginx.conf index 04337be..7088431 100644 --- a/conf/nginx.conf +++ b/conf/nginx.conf @@ -2,13 +2,12 @@ location __PATH__/ { # Path to source - alias __INSTALL_DIR__/ ; + alias __INSTALL_DIR__/; -### Example PHP configuration (remove it if not used) index index.html index.php; # Common parameter to increase upload size limit in conjunction with dedicated php-fpm file - #client_max_body_size 50M; + client_max_body_size 50M; try_files $uri $uri/ index.php; location ~ [^/]\.php(/|$) { @@ -21,7 +20,6 @@ location __PATH__/ { fastcgi_param PATH_INFO $fastcgi_path_info; fastcgi_param SCRIPT_FILENAME $request_filename; } -### End of PHP configuration part # Include SSOWAT user panel. include conf.d/yunohost_panel.conf.inc; diff --git a/conf/php-fpm.conf b/conf/php-fpm.conf deleted file mode 100644 index 24a64f9..0000000 --- a/conf/php-fpm.conf +++ /dev/null @@ -1,431 +0,0 @@ -; Start a new pool named 'www'. -; the variable $pool can be used in any directive and will be replaced by the -; pool name ('www' here) -[__NAMETOCHANGE__] - -; Per pool prefix -; It only applies on the following directives: -; - 'access.log' -; - 'slowlog' -; - 'listen' (unixsocket) -; - 'chroot' -; - 'chdir' -; - 'php_values' -; - 'php_admin_values' -; When not set, the global prefix (or /usr) applies instead. -; Note: This directive can also be relative to the global prefix. -; Default Value: none -;prefix = /path/to/pools/$pool - -; Unix user/group of processes -; Note: The user is mandatory. If the group is not set, the default user's group -; will be used. -user = __USER__ -group = __USER__ - -; The address on which to accept FastCGI requests. -; Valid syntaxes are: -; 'ip.add.re.ss:port' - to listen on a TCP socket to a specific IPv4 address on -; a specific port; -; '[ip:6:addr:ess]:port' - to listen on a TCP socket to a specific IPv6 address on -; a specific port; -; 'port' - to listen on a TCP socket to all addresses -; (IPv6 and IPv4-mapped) on a specific port; -; '/path/to/unix/socket' - to listen on a unix socket. -; Note: This value is mandatory. -listen = /var/run/php/php__PHPVERSION__-fpm-__NAMETOCHANGE__.sock - -; Set listen(2) backlog. -; Default Value: 511 (-1 on FreeBSD and OpenBSD) -;listen.backlog = 511 - -; Set permissions for unix socket, if one is used. In Linux, read/write -; permissions must be set in order to allow connections from a web server. Many -; BSD-derived systems allow connections regardless of permissions. -; Default Values: user and group are set as the running user -; mode is set to 0660 -listen.owner = www-data -listen.group = www-data -;listen.mode = 0660 -; When POSIX Access Control Lists are supported you can set them using -; these options, value is a comma separated list of user/group names. -; When set, listen.owner and listen.group are ignored -;listen.acl_users = -;listen.acl_groups = - -; List of addresses (IPv4/IPv6) of FastCGI clients which are allowed to connect. -; Equivalent to the FCGI_WEB_SERVER_ADDRS environment variable in the original -; PHP FCGI (5.2.2+). Makes sense only with a tcp listening socket. Each address -; must be separated by a comma. If this value is left blank, connections will be -; accepted from any ip address. -; Default Value: any -;listen.allowed_clients = 127.0.0.1 - -; Specify the nice(2) priority to apply to the pool processes (only if set) -; The value can vary from -19 (highest priority) to 20 (lower priority) -; Note: - It will only work if the FPM master process is launched as root -; - The pool processes will inherit the master process priority -; unless it specified otherwise -; Default Value: no set -; process.priority = -19 - -; Set the process dumpable flag (PR_SET_DUMPABLE prctl) even if the process user -; or group is differrent than the master process user. It allows to create process -; core dump and ptrace the process for the pool user. -; Default Value: no -; process.dumpable = yes - -; Choose how the process manager will control the number of child processes. -; Possible Values: -; static - a fixed number (pm.max_children) of child processes; -; dynamic - the number of child processes are set dynamically based on the -; following directives. With this process management, there will be -; always at least 1 children. -; pm.max_children - the maximum number of children that can -; be alive at the same time. -; pm.start_servers - the number of children created on startup. -; pm.min_spare_servers - the minimum number of children in 'idle' -; state (waiting to process). If the number -; of 'idle' processes is less than this -; number then some children will be created. -; pm.max_spare_servers - the maximum number of children in 'idle' -; state (waiting to process). If the number -; of 'idle' processes is greater than this -; number then some children will be killed. -; ondemand - no children are created at startup. Children will be forked when -; new requests will connect. The following parameter are used: -; pm.max_children - the maximum number of children that -; can be alive at the same time. -; pm.process_idle_timeout - The number of seconds after which -; an idle process will be killed. -; Note: This value is mandatory. -pm = dynamic - -; The number of child processes to be created when pm is set to 'static' and the -; maximum number of child processes when pm is set to 'dynamic' or 'ondemand'. -; This value sets the limit on the number of simultaneous requests that will be -; served. Equivalent to the ApacheMaxClients directive with mpm_prefork. -; Equivalent to the PHP_FCGI_CHILDREN environment variable in the original PHP -; CGI. The below defaults are based on a server without much resources. Don't -; forget to tweak pm.* to fit your needs. -; Note: Used when pm is set to 'static', 'dynamic' or 'ondemand' -; Note: This value is mandatory. -pm.max_children = 5 - -; The number of child processes created on startup. -; Note: Used only when pm is set to 'dynamic' -; Default Value: min_spare_servers + (max_spare_servers - min_spare_servers) / 2 -pm.start_servers = 2 - -; The desired minimum number of idle server processes. -; Note: Used only when pm is set to 'dynamic' -; Note: Mandatory when pm is set to 'dynamic' -pm.min_spare_servers = 1 - -; The desired maximum number of idle server processes. -; Note: Used only when pm is set to 'dynamic' -; Note: Mandatory when pm is set to 'dynamic' -pm.max_spare_servers = 3 - -; The number of seconds after which an idle process will be killed. -; Note: Used only when pm is set to 'ondemand' -; Default Value: 10s -;pm.process_idle_timeout = 10s; - -; The number of requests each child process should execute before respawning. -; This can be useful to work around memory leaks in 3rd party libraries. For -; endless request processing specify '0'. Equivalent to PHP_FCGI_MAX_REQUESTS. -; Default Value: 0 -;pm.max_requests = 500 - -; The URI to view the FPM status page. If this value is not set, no URI will be -; recognized as a status page. It shows the following informations: -; pool - the name of the pool; -; process manager - static, dynamic or ondemand; -; start time - the date and time FPM has started; -; start since - number of seconds since FPM has started; -; accepted conn - the number of request accepted by the pool; -; listen queue - the number of request in the queue of pending -; connections (see backlog in listen(2)); -; max listen queue - the maximum number of requests in the queue -; of pending connections since FPM has started; -; listen queue len - the size of the socket queue of pending connections; -; idle processes - the number of idle processes; -; active processes - the number of active processes; -; total processes - the number of idle + active processes; -; max active processes - the maximum number of active processes since FPM -; has started; -; max children reached - number of times, the process limit has been reached, -; when pm tries to start more children (works only for -; pm 'dynamic' and 'ondemand'); -; Value are updated in real time. -; Example output: -; pool: www -; process manager: static -; start time: 01/Jul/2011:17:53:49 +0200 -; start since: 62636 -; accepted conn: 190460 -; listen queue: 0 -; max listen queue: 1 -; listen queue len: 42 -; idle processes: 4 -; active processes: 11 -; total processes: 15 -; max active processes: 12 -; max children reached: 0 -; -; By default the status page output is formatted as text/plain. Passing either -; 'html', 'xml' or 'json' in the query string will return the corresponding -; output syntax. Example: -; http://www.foo.bar/status -; http://www.foo.bar/status?json -; http://www.foo.bar/status?html -; http://www.foo.bar/status?xml -; -; By default the status page only outputs short status. Passing 'full' in the -; query string will also return status for each pool process. -; Example: -; http://www.foo.bar/status?full -; http://www.foo.bar/status?json&full -; http://www.foo.bar/status?html&full -; http://www.foo.bar/status?xml&full -; The Full status returns for each process: -; pid - the PID of the process; -; state - the state of the process (Idle, Running, ...); -; start time - the date and time the process has started; -; start since - the number of seconds since the process has started; -; requests - the number of requests the process has served; -; request duration - the duration in µs of the requests; -; request method - the request method (GET, POST, ...); -; request URI - the request URI with the query string; -; content length - the content length of the request (only with POST); -; user - the user (PHP_AUTH_USER) (or '-' if not set); -; script - the main script called (or '-' if not set); -; last request cpu - the %cpu the last request consumed -; it's always 0 if the process is not in Idle state -; because CPU calculation is done when the request -; processing has terminated; -; last request memory - the max amount of memory the last request consumed -; it's always 0 if the process is not in Idle state -; because memory calculation is done when the request -; processing has terminated; -; If the process is in Idle state, then informations are related to the -; last request the process has served. Otherwise informations are related to -; the current request being served. -; Example output: -; ************************ -; pid: 31330 -; state: Running -; start time: 01/Jul/2011:17:53:49 +0200 -; start since: 63087 -; requests: 12808 -; request duration: 1250261 -; request method: GET -; request URI: /test_mem.php?N=10000 -; content length: 0 -; user: - -; script: /home/fat/web/docs/php/test_mem.php -; last request cpu: 0.00 -; last request memory: 0 -; -; Note: There is a real-time FPM status monitoring sample web page available -; It's available in: /usr/share/php/7.0/fpm/status.html -; -; Note: The value must start with a leading slash (/). The value can be -; anything, but it may not be a good idea to use the .php extension or it -; may conflict with a real PHP file. -; Default Value: not set -;pm.status_path = /status - -; The ping URI to call the monitoring page of FPM. If this value is not set, no -; URI will be recognized as a ping page. This could be used to test from outside -; that FPM is alive and responding, or to -; - create a graph of FPM availability (rrd or such); -; - remove a server from a group if it is not responding (load balancing); -; - trigger alerts for the operating team (24/7). -; Note: The value must start with a leading slash (/). The value can be -; anything, but it may not be a good idea to use the .php extension or it -; may conflict with a real PHP file. -; Default Value: not set -;ping.path = /ping - -; This directive may be used to customize the response of a ping request. The -; response is formatted as text/plain with a 200 response code. -; Default Value: pong -;ping.response = pong - -; The access log file -; Default: not set -;access.log = log/$pool.access.log - -; The access log format. -; The following syntax is allowed -; %%: the '%' character -; %C: %CPU used by the request -; it can accept the following format: -; - %{user}C for user CPU only -; - %{system}C for system CPU only -; - %{total}C for user + system CPU (default) -; %d: time taken to serve the request -; it can accept the following format: -; - %{seconds}d (default) -; - %{miliseconds}d -; - %{mili}d -; - %{microseconds}d -; - %{micro}d -; %e: an environment variable (same as $_ENV or $_SERVER) -; it must be associated with embraces to specify the name of the env -; variable. Some exemples: -; - server specifics like: %{REQUEST_METHOD}e or %{SERVER_PROTOCOL}e -; - HTTP headers like: %{HTTP_HOST}e or %{HTTP_USER_AGENT}e -; %f: script filename -; %l: content-length of the request (for POST request only) -; %m: request method -; %M: peak of memory allocated by PHP -; it can accept the following format: -; - %{bytes}M (default) -; - %{kilobytes}M -; - %{kilo}M -; - %{megabytes}M -; - %{mega}M -; %n: pool name -; %o: output header -; it must be associated with embraces to specify the name of the header: -; - %{Content-Type}o -; - %{X-Powered-By}o -; - %{Transfert-Encoding}o -; - .... -; %p: PID of the child that serviced the request -; %P: PID of the parent of the child that serviced the request -; %q: the query string -; %Q: the '?' character if query string exists -; %r: the request URI (without the query string, see %q and %Q) -; %R: remote IP address -; %s: status (response code) -; %t: server time the request was received -; it can accept a strftime(3) format: -; %d/%b/%Y:%H:%M:%S %z (default) -; The strftime(3) format must be encapsuled in a %{}t tag -; e.g. for a ISO8601 formatted timestring, use: %{%Y-%m-%dT%H:%M:%S%z}t -; %T: time the log has been written (the request has finished) -; it can accept a strftime(3) format: -; %d/%b/%Y:%H:%M:%S %z (default) -; The strftime(3) format must be encapsuled in a %{}t tag -; e.g. for a ISO8601 formatted timestring, use: %{%Y-%m-%dT%H:%M:%S%z}t -; %u: remote user -; -; Default: "%R - %u %t \"%m %r\" %s" -;access.format = "%R - %u %t \"%m %r%Q%q\" %s %f %{mili}d %{kilo}M %C%%" - -; The log file for slow requests -; Default Value: not set -; Note: slowlog is mandatory if request_slowlog_timeout is set -;slowlog = log/$pool.log.slow - -; The timeout for serving a single request after which a PHP backtrace will be -; dumped to the 'slowlog' file. A value of '0s' means 'off'. -; Available units: s(econds)(default), m(inutes), h(ours), or d(ays) -; Default Value: 0 -;request_slowlog_timeout = 0 - -; The timeout for serving a single request after which the worker process will -; be killed. This option should be used when the 'max_execution_time' ini option -; does not stop script execution for some reason. A value of '0' means 'off'. -; Available units: s(econds)(default), m(inutes), h(ours), or d(ays) -; Default Value: 0 -request_terminate_timeout = 1d - -; Set open file descriptor rlimit. -; Default Value: system defined value -;rlimit_files = 1024 - -; Set max core size rlimit. -; Possible Values: 'unlimited' or an integer greater or equal to 0 -; Default Value: system defined value -;rlimit_core = 0 - -; Chroot to this directory at the start. This value must be defined as an -; absolute path. When this value is not set, chroot is not used. -; Note: you can prefix with '$prefix' to chroot to the pool prefix or one -; of its subdirectories. If the pool prefix is not set, the global prefix -; will be used instead. -; Note: chrooting is a great security feature and should be used whenever -; possible. However, all PHP paths will be relative to the chroot -; (error_log, sessions.save_path, ...). -; Default Value: not set -;chroot = - -; Chdir to this directory at the start. -; Note: relative path can be used. -; Default Value: current directory or / when chroot -chdir = __INSTALL_DIR__ - -; Redirect worker stdout and stderr into main error log. If not set, stdout and -; stderr will be redirected to /dev/null according to FastCGI specs. -; Note: on highloaded environement, this can cause some delay in the page -; process time (several ms). -; Default Value: no -;catch_workers_output = yes - -; Clear environment in FPM workers -; Prevents arbitrary environment variables from reaching FPM worker processes -; by clearing the environment in workers before env vars specified in this -; pool configuration are added. -; Setting to "no" will make all environment variables available to PHP code -; via getenv(), $_ENV and $_SERVER. -; Default Value: yes -;clear_env = no - -; Limits the extensions of the main script FPM will allow to parse. This can -; prevent configuration mistakes on the web server side. You should only limit -; FPM to .php extensions to prevent malicious users to use other extensions to -; execute php code. -; Note: set an empty value to allow all extensions. -; Default Value: .php -;security.limit_extensions = .php .php3 .php4 .php5 .php7 - -; Pass environment variables like LD_LIBRARY_PATH. All $VARIABLEs are taken from -; the current environment. -; Default Value: clean env -;env[HOSTNAME] = $HOSTNAME -;env[PATH] = /usr/local/bin:/usr/bin:/bin -;env[TMP] = /tmp -;env[TMPDIR] = /tmp -;env[TEMP] = /tmp - -; Additional php.ini defines, specific to this pool of workers. These settings -; overwrite the values previously defined in the php.ini. The directives are the -; same as the PHP SAPI: -; php_value/php_flag - you can set classic ini defines which can -; be overwritten from PHP call 'ini_set'. -; php_admin_value/php_admin_flag - these directives won't be overwritten by -; PHP call 'ini_set' -; For php_*flag, valid values are on, off, 1, 0, true, false, yes or no. - -; Defining 'extension' will load the corresponding shared extension from -; extension_dir. Defining 'disable_functions' or 'disable_classes' will not -; overwrite previously defined php.ini values, but will append the new value -; instead. - -; Note: path INI options can be relative and will be expanded with the prefix -; (pool, global or /usr) - -; Default Value: nothing is defined by default except the values in php.ini and -; specified at startup with the -d argument -;php_admin_value[sendmail_path] = /usr/sbin/sendmail -t -i -f www@my.domain.com -;php_flag[display_errors] = off -;php_admin_value[error_log] = /var/log/fpm-php.www.log -;php_admin_flag[log_errors] = on -;php_admin_value[memory_limit] = 32M - -; Common values to change to increase file upload limit -; php_admin_value[upload_max_filesize] = 50M -; php_admin_value[post_max_size] = 50M -; php_admin_flag[mail.add_x_header] = Off - -; Other common parameters -; php_admin_value[max_execution_time] = 600 -; php_admin_value[max_input_time] = 300 -; php_admin_value[memory_limit] = 256M -; php_admin_flag[short_open_tag] = On - diff --git a/manifest.json b/manifest.json deleted file mode 100644 index 5fe390b..0000000 --- a/manifest.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "name": "Digitools", - "id": "digitools", - "packaging_format": 1, - "description": { - "en": "Simple and useful tools for the classroom.", - "fr": "Des outils simples et utiles pour la classe." - }, - "version": "0.2.4~ynh1", - "url": "https://ladigitale.dev/", - "upstream": { - "license": "GPL-3.0-only", - "website": "https://ladigitale.dev/", - "demo": "https://ladigitale.dev/digitools/", - "admindoc": "https://codeberg.org/ladigitale/digitools/src/branch/main/README.md", - "userdoc": "https://ladigitale.dev/blog/digitools-des-outils-simples-et-utiles-pour-la-classe", - "code": "https://codeberg.org/ladigitale/digitools" - }, - "license": "GPL-3.0-only", - "maintainer": { - "name": "Pierre-Amiel Giraud", - "email": "xxxxx" - }, - "requirements": { - "yunohost": ">= 4.3.0" - }, - "multi_instance": true, - "services": [ - "nginx", - "php8.0-fpm" - ], - "arguments": { - "install" : [ - { - "name": "domain", - "type": "domain" - }, - { - "name": "path", - "type": "path", - "example": "/example", - "default": "/digitools" - }, - { - "name": "is_public", - "type": "boolean", - "help": { - "en": "If not public, you won’t be able to share cards (for instance) with your public, unless they are users of your Yunohost instance.", - "fr": "Si l’application n’est pas publique, vous ne pourrez pas partager les cartes (par exemple) avec vos élèves, sauf s’ils sont tous utilisateurs de votre instance Yunohost." - }, - "default": true - } - ] - } -} - diff --git a/manifest.toml b/manifest.toml index e245f21..d471890 100644 --- a/manifest.toml +++ b/manifest.toml @@ -5,7 +5,7 @@ name = "Digitools" description.en = "Simple and useful tools for the classroom." description.fr = "Des outils simples et utiles pour la classe." -version = "0.2.4~ynh1" +version = "0.3.7~ynh1" maintainers = ["Pierre-Amiel Giraud"] @@ -16,39 +16,36 @@ demo = "https://ladigitale.dev/digitools/" admindoc = "https://codeberg.org/ladigitale/digitools/src/branch/main/README.md" userdoc = "https://ladigitale.dev/blog/digitools-des-outils-simples-et-utiles-pour-la-classe" code = "https://codeberg.org/ladigitale/digitools" -cpe = "???" # 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) -fund = "???" # 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. [integration] -yunohost = ">= 4.3.0" -architectures = "all" # FIXME: can be replaced by a list of supported archs using the dpkg --print-architecture nomenclature (amd64/i386/armhf/arm64), for example: ["amd64", "i386"] +yunohost = ">= 11.2" +architectures = "all" multi_instance = true -ldap = "?" # FIXME: replace with true, false, or "not_relevant". Not to confuse with the "sso" key : the "ldap" key corresponds to wether or not a user *can* login on the app using its YunoHost credentials. -sso = "?" # FIXME: replace with true, false, or "not_relevant". Not to confuse with the "ldap" key : the "sso" key corresponds to wether or not a user is *automatically logged-in* on the app when logged-in on the YunoHost portal. -disk = "50M" # FIXME: replace with an **estimate** minimum disk requirement. e.g. 20M, 400M, 1G, ... -ram.build = "50M" # FIXME: replace with an **estimate** minimum ram requirement. e.g. 50M, 400M, 1G, ... -ram.runtime = "50M" # FIXME: replace with an **estimate** minimum ram requirement. e.g. 50M, 400M, 1G, ... + +ldap = false + +sso = false + +disk = "50M" +ram.build = "50M" +ram.runtime = "50M" [install] [install.domain] - # this is a generic question - ask strings are automatically handled by Yunohost's core type = "domain" [install.path] - # this is a generic question - ask strings are automatically handled by Yunohost's core type = "path" default = "/digitools" [install.init_main_permission] - help.en = "If not public, you won’t be able to share cards (for instance) with your public, unless they are users of your Yunohost instance." - help.fr = "Si l’application n’est pas publique, vous ne pourrez pas partager les cartes (par exemple) avec vos élèves, sauf s’ils sont tous utilisateurs de votre instance Yunohost." type = "group" default = "visitors" [resources] [resources.sources.main] - url = "https://github.com/YunoHost-Apps/digitools_ynh/releases/download/0.2.4/digitools-v0.2.4.zip" - sha256 = "efab6ba1cfed4216add53dcaae58ac0c56d27cbb5ae4072bea479cb616e5e2da" + url = "https://codeberg.org/ladigitale/digitools/releases/download/0.3.7/digitools-v0.3.7.zip" + sha256 = "75f56c0987795d5f89bc10033a461a30166aac42f21a0169bbe33d2c304702eb" [resources.system_user] @@ -60,5 +57,8 @@ ram.runtime = "50M" # FIXME: replace with an **estimate** minimum ram requiremen [resources.permissions] main.url = "/" + [resources.database] + type = "mariadb-server, php8.2-sqlite3" + [resources.database] type = "mysql" diff --git a/scripts/_common.sh b/scripts/_common.sh index db33a7d..7ba8d71 100644 --- a/scripts/_common.sh +++ b/scripts/_common.sh @@ -4,9 +4,6 @@ # COMMON VARIABLES #================================================= -# dependencies used by the app -#REMOVEME? pkg_dependencies="php$YNH_DEFAULT_PHP_VERSION-sqlite3" - #================================================= # PERSONAL HELPERS #================================================= diff --git a/scripts/backup b/scripts/backup index f3ede60..cc362c0 100644 --- a/scripts/backup +++ b/scripts/backup @@ -10,40 +10,11 @@ source ../settings/scripts/_common.sh source /usr/share/yunohost/helpers -#================================================= -# MANAGE SCRIPT FAILURE -#================================================= - -#REMOVEME? ynh_clean_setup () { - ### Remove this function if there's nothing to clean before calling the remove script. - true -} -# Exit if an error occurs during the execution of the script -#REMOVEME? ynh_abort_if_errors - -#================================================= -# LOAD SETTINGS -#================================================= -#REMOVEME? ynh_print_info --message="Loading installation settings..." - -#REMOVEME? app=$YNH_APP_INSTANCE_NAME - -#REMOVEME? #REMOVEME? install_dir=$(ynh_app_setting_get --app=$app --key=install_dir) -#REMOVEME? domain=$(ynh_app_setting_get --app=$app --key=domain) -#REMOVEME? db_name=$(ynh_app_setting_get --app=$app --key=db_name) -#REMOVEME? phpversion=$(ynh_app_setting_get --app=$app --key=phpversion) -#REMOVEME? data_dir=$(ynh_app_setting_get --app=$app --key=data_dir) - #================================================= # DECLARE DATA AND CONF FILES TO BACKUP #================================================= ynh_print_info --message="Declaring files to be backed up..." -### 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. - #================================================= # BACKUP THE APP MAIN DIR #================================================= @@ -62,23 +33,6 @@ ynh_backup --src_path="/etc/nginx/conf.d/$domain.d/$app.conf" ynh_backup --src_path="/etc/php/$phpversion/fpm/pool.d/$app.conf" -#================================================= -# BACKUP FAIL2BAN CONFIGURATION -#================================================= - -#ynh_backup --src_path="/etc/fail2ban/jail.d/$app.conf" -#ynh_backup --src_path="/etc/fail2ban/filter.d/$app.conf" - -#================================================= -# SPECIFIC BACKUP -#================================================= -# BACKUP VARIOUS FILES -#================================================= - -#ynh_backup --src_path="/etc/cron.d/$app" - -#ynh_backup --src_path="/etc/$app/" - #================================================= # END OF SCRIPT #================================================= diff --git a/scripts/change_url b/scripts/change_url index f42dbab..bd1543f 100644 --- a/scripts/change_url +++ b/scripts/change_url @@ -9,64 +9,6 @@ source _common.sh source /usr/share/yunohost/helpers -#================================================= -# RETRIEVE ARGUMENTS -#================================================= - -#REMOVEME? old_domain=$YNH_APP_OLD_DOMAIN -#REMOVEME? old_path=$YNH_APP_OLD_PATH - -#REMOVEME? new_domain=$YNH_APP_NEW_DOMAIN -#REMOVEME? new_path=$YNH_APP_NEW_PATH - -#REMOVEME? app=$YNH_APP_INSTANCE_NAME - -#================================================= -# LOAD SETTINGS -#================================================= -#REMOVEME? ynh_script_progression --message="Loading installation settings..." --time --weight=1 - -#REMOVEME? # Needed for helper "ynh_add_nginx_config" -#REMOVEME? #REMOVEME? install_dir=$(ynh_app_setting_get --app=$app --key=install_dir) - -# Add settings here as needed by your application -#REMOVEME? #db_name=$(ynh_app_setting_get --app=$app --key=db_name) -#db_user=$db_name -#REMOVEME? #db_pwd=$(ynh_app_setting_get --app=$app --key=db_pwd) - -#================================================= -# BACKUP BEFORE CHANGE URL THEN ACTIVE TRAP -#================================================= -#REMOVEME? ynh_script_progression --message="Backing up the app before changing its URL (may take a while)..." --time --weight=1 - -# Backup the current version of the app -#REMOVEME? ynh_backup_before_upgrade -#REMOVEME? ynh_clean_setup () { - # Remove the new domain config file, the remove script won't do it as it doesn't know yet its location. -#REMOVEME? ynh_secure_remove --file="/etc/nginx/conf.d/$new_domain.d/$app.conf" - - # Restore it if the upgrade fails -#REMOVEME? ynh_restore_upgradebackup -} -# Exit if an error occurs during the execution of the script -#REMOVEME? ynh_abort_if_errors - -#================================================= -# CHECK WHICH PARTS SHOULD BE CHANGED -#================================================= - -#REMOVEME? change_domain=0 -#REMOVEME? if [ "$old_domain" != "$new_domain" ] -then - #REMOVEME? change_domain=1 -fi - -#REMOVEME? change_path=0 -#REMOVEME? if [ "$old_path" != "$new_path" ] -then - #REMOVEME? change_path=1 -fi - #================================================= # STANDARD MODIFICATIONS #================================================= @@ -76,45 +18,6 @@ ynh_script_progression --message="Updating NGINX web server configuration..." -- ynh_change_url_nginx_config -#REMOVEME? nginx_conf_path=/etc/nginx/conf.d/$old_domain.d/$app.conf - -# Change the path in the NGINX config file -if [ $change_path -eq 1 ] -then - # Make a backup of the original NGINX config file if modified -#REMOVEME? ynh_backup_if_checksum_is_different --file="$nginx_conf_path" - # Set global variables for NGINX helper -#REMOVEME? domain="$old_domain" -#REMOVEME? path="$new_path" - # Create a dedicated NGINX config -#REMOVEME? ynh_add_nginx_config -fi - -# Change the domain for NGINX -if [ $change_domain -eq 1 ] -then - # Delete file checksum for the old conf file location -#REMOVEME? ynh_delete_file_checksum --file="$nginx_conf_path" -#REMOVEME? mv $nginx_conf_path /etc/nginx/conf.d/$new_domain.d/$app.conf - # Store file checksum for the new config file location -#REMOVEME? ynh_store_file_checksum --file="/etc/nginx/conf.d/$new_domain.d/$app.conf" -fi - -#================================================= -# SPECIFIC MODIFICATIONS -#================================================= -# ... -#================================================= - -#================================================= -# GENERIC FINALISATION -#================================================= -# RELOAD NGINX -#================================================= -#REMOVEME? ynh_script_progression --message="Reloading NGINX web server..." --time --weight=1 - -#REMOVEME? #REMOVEME? ynh_systemd_action --service_name=nginx --action=reload - #================================================= # END OF SCRIPT #================================================= diff --git a/scripts/install b/scripts/install index 25dbcdd..ae8982d 100644 --- a/scripts/install +++ b/scripts/install @@ -9,157 +9,13 @@ source _common.sh source /usr/share/yunohost/helpers -#================================================= -# MANAGE SCRIPT FAILURE -#================================================= - -#REMOVEME? ynh_clean_setup () { - ### Remove this function if there's nothing to clean before calling the remove script. - true -} -# Exit if an error occurs during the execution of the script -#REMOVEME? ynh_abort_if_errors - -#================================================= -# RETRIEVE ARGUMENTS FROM THE MANIFEST -#================================================= - -#REMOVEME? domain=$YNH_APP_ARG_DOMAIN -#REMOVEME? path=$YNH_APP_ARG_PATH -#REMOVEME? #admin=$YNH_APP_ARG_ADMIN -#REMOVEME? is_public=$YNH_APP_ARG_IS_PUBLIC -#REMOVEME? #language=$YNH_APP_ARG_LANGUAGE -#REMOVEME? #password=$YNH_APP_ARG_PASSWORD - -### If it's a multi-instance app, meaning it can be installed several times independently -### The id of the app as stated in the manifest is available as $YNH_APP_ID -### The instance number is available as $YNH_APP_INSTANCE_NUMBER (equals "1", "2"...) -#REMOVEME? ### The app instance name is available as $YNH_APP_INSTANCE_NAME -#REMOVEME? ### - the first time the app is installed, YNH_APP_INSTANCE_NAME = ynhexample -#REMOVEME? ### - the second time the app is installed, YNH_APP_INSTANCE_NAME = ynhexample__2 -### - ynhexample__{N} for the subsequent installations, with N=3,4... -### The app instance name is probably what interests you most, since this is -### guaranteed to be unique. This is a good unique identifier to define installation path, -### db names... -#REMOVEME? app=$YNH_APP_INSTANCE_NAME - -#================================================= -# CHECK IF THE APP CAN BE INSTALLED WITH THESE ARGS -#================================================= -### About --weight and --time -### ynh_script_progression will show to your final users the progression of each scripts. -### In order to do that, --weight will represent the relative time of execution compared to the other steps in the script. -### --time is a packager option, it will show you the execution time since the previous call. -### This option should be removed before releasing your app. -### Use the execution time, given by --time, to estimate the weight of a step. -### A common way to do it is to set a weight equal to the execution time in second +1. -### The execution time is given for the duration since the previous call. So the weight should be applied to this previous call. -#REMOVEME? ynh_script_progression --message="Validating installation parameters..." --weight=1 - -### If the app uses NGINX as web server (written in HTML/PHP in most cases), the final path should be "/var/www/$app". -### If the app provides an internal web server (or uses another application server such as uWSGI), the final path should be "/opt/yunohost/$app" -#REMOVEME? install_dir=/var/www/$app -#REMOVEME? test ! -e "$install_dir" || ynh_die --message="This path already contains a folder" - -# Register (book) web path -#REMOVEME? ynh_webpath_register --app=$app --domain=$domain --path=$path - -#================================================= -# STORE SETTINGS FROM MANIFEST -#================================================= -#REMOVEME? ynh_script_progression --message="Storing installation settings..." --weight=1 - -#REMOVEME? ynh_app_setting_set --app=$app --key=domain --value=$domain -#REMOVEME? ynh_app_setting_set --app=$app --key=path --value=$path -#REMOVEME? #ynh_app_setting_set --app=$app --key=admin --value=$admin -#ynh_app_setting_set --app=$app --key=language --value=$language - -#================================================= -# STANDARD MODIFICATIONS -#================================================= -# FIND AND OPEN A PORT -#================================================= -#REMOVEME? #ynh_script_progression --message="Finding an available port..." --weight=1 - -### Use these lines if you have to open a port for the application -#REMOVEME? ### `ynh_find_port` will find the first available port starting from the given port. -### If you're not using these lines: -### - Remove the section "CLOSE A PORT" in the remove script - -# Find an available port -#REMOVEME? #port=$(ynh_find_port --port=8095) -#REMOVEME? #ynh_app_setting_set --app=$app --key=port --value=$port - -# Optional: Expose this port publicly -# (N.B.: you only need to do this if the app actually needs to expose the port publicly. -# If you do this and the app doesn't actually need you are CREATING SECURITY HOLES IN THE SERVER !) - -# Open the port -# ynh_script_progression --message="Configuring firewall..." --time --weight=1 -# ynh_exec_warn_less yunohost firewall allow --no-upnp TCP $port - -#================================================= -# INSTALL DEPENDENCIES -#================================================= -#REMOVEME? ynh_script_progression --message="Installing dependencies..." --weight=1 - -#REMOVEME? ### `ynh_install_app_dependencies` allows you to add any "apt" dependencies to the package. -### Those deb packages will be installed as dependencies of this package. -### If you're not using this helper: -### - Remove the section "REMOVE DEPENDENCIES" in the remove script -### - Remove the variable "pkg_dependencies" in _common.sh -### - As well as the section "REINSTALL DEPENDENCIES" in the restore script -### - And the section "UPGRADE DEPENDENCIES" in the upgrade script - -#REMOVEME? ynh_install_app_dependencies $pkg_dependencies - -#================================================= -# CREATE DEDICATED USER -#================================================= -#REMOVEME? ynh_script_progression --message="Configuring system user..." --weight=1 - -# Create a system user -#REMOVEME? ynh_system_user_create --username=$app --home_dir="$install_dir" - -#================================================= -# CREATE A MYSQL DATABASE -#================================================= -#REMOVEME? #ynh_script_progression --message="Creating a MySQL database..." --time --weight=1 - -### Use these lines if you need a database for the application. -#REMOVEME? ### `ynh_mysql_setup_db` will create a database, an associated user and a ramdom password. -### The password will be stored as 'mysqlpwd' into the app settings, -### and will be available as $db_pwd -### If you're not using these lines: -### - Remove the section "BACKUP THE MYSQL DATABASE" in the backup script -### - Remove also the section "REMOVE THE MYSQL DATABASE" in the remove script -### - As well as the section "RESTORE THE MYSQL DATABASE" in the restore script - -#db_name=$(ynh_sanitize_dbid --db_name=$app) -#db_user=$db_name -#REMOVEME? #ynh_app_setting_set --app=$app --key=db_name --value=$db_name -#REMOVEME? #ynh_mysql_setup_db --db_user=$db_user --db_name=$db_name - #================================================= # DOWNLOAD, CHECK AND UNPACK SOURCE #================================================= ynh_script_progression --message="Setting up source files..." --weight=1 -### `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 - -#REMOVEME? ynh_app_setting_set --app=$app --key=install_dir --value=$install_dir -# Download, check integrity, uncompress and patch the source from app.src ynh_setup_source --dest_dir="$install_dir" -# FIXME: this should be managed by the core in the future -# Here, as a packager, you may have to tweak the ownerhsip/permissions -# such that the appropriate users (e.g. maybe www-data) can access -# files in some cases. -# But FOR THE LOVE OF GOD, do not allow r/x for "others" on the entire folder - -# this will be treated as a security issue. -chmod 750 "$install_dir" chmod -R o-rwx "$install_dir" chown -R $app:www-data "$install_dir" @@ -168,241 +24,10 @@ chown -R $app:www-data "$install_dir" #================================================= ynh_script_progression --message="Configuring NGINX web server..." --weight=1 -### `ynh_add_nginx_config` will use the file conf/nginx.conf - -# Create a dedicated NGINX config ynh_add_nginx_config -#================================================= -# PHP-FPM CONFIGURATION -#================================================= -ynh_script_progression --message="Configuring PHP-FPM..." --weight=1 - -### `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 - -# Create a dedicated PHP-FPM config ynh_add_fpm_config -#================================================= -# SPECIFIC SETUP -#================================================= -# ... -#================================================= - -#================================================= -# CREATE DATA DIRECTORY -#================================================= -#ynh_script_progression --message="Creating a data directory..." --time --weight=1 - -### Use these lines if you need to create a directory to store "persistent files" for the application. -### Usually this directory is used to store uploaded files or any file that won't be updated during -### an upgrade and that won't be deleted during app removal unless "--purge" option is used. -### If you're not using these lines: -### - Remove the section "BACKUP THE DATA DIR" in the backup script -### - Remove the section "RESTORE THE DATA DIRECTORY" in the restore script -### - As well as the section "REMOVE DATA DIR" in the remove script - -#data_dir=/home/yunohost.app/$app -#REMOVEME? #ynh_app_setting_set --app=$app --key=data_dir --value=$data_dir - -#mkdir -p $data_dir - -# FIXME: this should be managed by the core in the future -# Here, as a packager, you may have to tweak the ownerhsip/permissions -# such that the appropriate users (e.g. maybe www-data) can access -# files in some cases. -# But FOR THE LOVE OF GOD, do not allow r/x for "others" on the entire folder - -# this will be treated as a security issue. -#chmod 750 "$data_dir" -#chmod -R o-rwx "$data_dir" -#chown -R $app:www-data "$data_dir" - -#================================================= -# ADD A CONFIGURATION -#================================================= -#ynh_script_progression --message="Adding a configuration file..." --time --weight=1 - -### You can add specific configuration files. -### -### Typically, put your template conf file in ../conf/your_config_file -### The template may contain strings such as __FOO__ or __FOO_BAR__, -### which will automatically be replaced by the values of $foo and $foo_bar -### -### ynh_add_config will also keep track of the config file's checksum, -### which later during upgrade may allow to automatically backup the config file -### if it's found that the file was manually modified -### -### Check the documentation of `ynh_add_config` for more info. - -#ynh_add_config --template="some_config_file" --destination="$install_dir/some_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/some_config_file" -#chown $app:$app "$install_dir/some_config_file" - -### 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" - -#================================================= -# SETUP SYSTEMD -#================================================= -#ynh_script_progression --message="Configuring a systemd service..." --time --weight=1 - -### `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 - -# Create a dedicated systemd config -#ynh_add_systemd_config - -#================================================= -# SETUP APPLICATION WITH CURL -#================================================= - -### 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. - -# Set the app as temporarily public for curl call -#ynh_script_progression --message="Configuring SSOwat..." --time --weight=1 -# Making the app public for curl -#REMOVEME? #ynh_permission_update --permission="main" --add="visitors" - -# Installation with curl -#ynh_script_progression --message="Finalizing installation..." --time --weight=1 -#ynh_local_curl "/INSTALL_PATH" "key1=value1" "key2=value2" "key3=value3" - -# Remove the public access -#REMOVEME? #ynh_permission_update --permission="main" --remove="visitors" - -#================================================= -# GENERIC FINALIZATION -#================================================= -# SETUP LOGROTATE -#================================================= -#ynh_script_progression --message="Configuring log rotation..." --time --weight=1 - -### `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 - -# Use logrotate to manage application logfile(s) -#ynh_use_logrotate - -#================================================= -# INTEGRATE SERVICE IN YUNOHOST -#================================================= -#ynh_script_progression --message="Integrating service in YunoHost..." --time --weight=1 - -### `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 - -#yunohost service add $app --description="A short description of the app" --log="/var/log/$app/$app.log" - -### Additional options starting with 3.8: -### -### --needs_exposed_ports "$port" a list of ports that needs to be publicly exposed -### which will then be checked by YunoHost's diagnosis system -### (N.B. DO NOT USE THIS is the port is only internal!!!) -### -### --test_status "some command" a custom command to check the status of the service -### (only relevant if 'systemctl status' doesn't do a good job) -### -### --test_conf "some command" some command similar to "nginx -t" that validates the conf of the service -### -### Re-calling 'yunohost service add' during the upgrade script is the right way -### 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) - -#================================================= -# START SYSTEMD SERVICE -#================================================= -#ynh_script_progression --message="Starting a systemd service..." --time --weight=1 - -### `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 - -# Start a systemd service -#ynh_systemd_action --service_name=$app --action="start" --log_path="/var/log/$app/$app.log" - -#================================================= -# SETUP FAIL2BAN -#================================================= -#ynh_script_progression --message="Configuring Fail2Ban..." --time --weight=1 - -# Create a dedicated Fail2Ban config -#ynh_add_fail2ban_config --logpath="/var/log/nginx/${domain}-error.log" --failregex="Regex to match into the log for a failed login" - -#================================================= -# SETUP SSOWAT -#================================================= -#REMOVEME? ynh_script_progression --message="Configuring permissions..." --weight=1 - -# Make app public if necessary -#REMOVEME? if [ $is_public -eq 1 ] -then - # Everyone can access the app. - # The "main" permission is automatically created before the install script. -#REMOVEME? ynh_permission_update --permission="main" --add="visitors" -fi - -### N.B. : the following extra permissions only make sense if your app -### does have for example an admin interface or an API. - -# Only the admin can access the admin panel of the app (if the app has an admin panel) -#REMOVEME? #ynh_permission_create --permission="admin" --url="/admin" --allowed=$admin - -# Everyone can access the API part -# We don't want to display the tile in the SSO so we put --show_tile="false" -# And we don't want the YunoHost admin to be able to remove visitors group to this permission, so we put --protected="true" -#REMOVEME? #ynh_permission_create --permission="api" --url="/api" --allowed="visitors" --show_tile="false" --protected="true" - -#================================================= -# RELOAD NGINX -#================================================= -#REMOVEME? ynh_script_progression --message="Reloading NGINX web server..." --weight=1 - -#REMOVEME? ynh_systemd_action --service_name=nginx --action=reload - #================================================= # END OF SCRIPT #================================================= diff --git a/scripts/remove b/scripts/remove index 63f006f..5a4ee64 100644 --- a/scripts/remove +++ b/scripts/remove @@ -1,4 +1,4 @@ - #!/bin/bash +#!/bin/bash #================================================= # GENERIC START @@ -9,30 +9,6 @@ source _common.sh source /usr/share/yunohost/helpers -#================================================= -# LOAD SETTINGS -#================================================= -#REMOVEME? ynh_script_progression --message="Loading installation settings..." --weight=1 - -#REMOVEME? app=$YNH_APP_INSTANCE_NAME - -#REMOVEME? domain=$(ynh_app_setting_get --app=$app --key=domain) -#REMOVEME? port=$(ynh_app_setting_get --app=$app --key=port) -#REMOVEME? db_name=$(ynh_app_setting_get --app=$app --key=db_name) -#REMOVEME? db_user=$db_name -#REMOVEME? #REMOVEME? install_dir=$(ynh_app_setting_get --app=$app --key=install_dir) -#REMOVEME? data_dir=$(ynh_app_setting_get --app=$app --key=data_dir) - -#================================================= -# STANDARD REMOVE -#================================================= -# REMOVE APP MAIN DIR -#================================================= -#REMOVEME? ynh_script_progression --message="Removing app main directory..." --weight=1 - -# Remove the app directory securely -#REMOVEME? ynh_secure_remove --file="$install_dir" - #================================================= # REMOVE NGINX CONFIGURATION #================================================= @@ -41,56 +17,8 @@ ynh_script_progression --message="Removing NGINX web server configuration..." -- # Remove the dedicated NGINX config ynh_remove_nginx_config -#================================================= -# REMOVE PHP-FPM CONFIGURATION -#================================================= -ynh_script_progression --message="Removing PHP-FPM configuration..." --weight=1 - -# Remove the dedicated PHP-FPM config ynh_remove_fpm_config -#================================================= -# REMOVE DEPENDENCIES -#================================================= -#REMOVEME? ynh_script_progression --message="Removing dependencies..." --weight=1 - -# Remove metapackage and its dependencies -#REMOVEME? ynh_remove_app_dependencies - -#================================================= -# REMOVE FAIL2BAN CONFIGURATION -#================================================= -#ynh_script_progression --message="Removing Fail2Ban configuration..." --weight=1 - -# Remove the dedicated Fail2Ban config -#ynh_remove_fail2ban_config - -#================================================= -# SPECIFIC REMOVE -#================================================= -# REMOVE VARIOUS FILES -#================================================= -ynh_script_progression --message="Removing various files..." --weight=1 - -# Remove a cron file -#ynh_secure_remove --file="/etc/cron.d/$app" - -# Remove a directory securely -#ynh_secure_remove --file="/etc/$app" - -# Remove the log files -ynh_secure_remove --file="/var/log/$app" - -#================================================= -# GENERIC FINALIZATION -#================================================= -# REMOVE DEDICATED USER -#================================================= -#REMOVEME? ynh_script_progression --message="Removing the dedicated system user..." --weight=1 - -# Delete a system user -#REMOVEME? ynh_system_user_delete --username=$app - #================================================= # END OF SCRIPT #================================================= diff --git a/scripts/restore b/scripts/restore index 095af0d..6603226 100644 --- a/scripts/restore +++ b/scripts/restore @@ -10,57 +10,6 @@ source ../settings/scripts/_common.sh source /usr/share/yunohost/helpers -#================================================= -# MANAGE SCRIPT FAILURE -#================================================= - -#REMOVEME? ynh_clean_setup () { - #### Remove this function if there's nothing to clean before calling the remove script. - true -} -# Exit if an error occurs during the execution of the script -#REMOVEME? ynh_abort_if_errors - -#================================================= -# LOAD SETTINGS -#================================================= -#REMOVEME? ynh_script_progression --message="Loading installation settings..." --weight=1 - -#REMOVEME? app=$YNH_APP_INSTANCE_NAME - -#REMOVEME? domain=$(ynh_app_setting_get --app=$app --key=domain) -#REMOVEME? path=$(ynh_app_setting_get --app=$app --key=path) -#REMOVEME? #REMOVEME? install_dir=$(ynh_app_setting_get --app=$app --key=install_dir) -#REMOVEME? db_name=$(ynh_app_setting_get --app=$app --key=db_name) -#REMOVEME? db_user=$db_name -#REMOVEME? phpversion=$(ynh_app_setting_get --app=$app --key=phpversion) -#REMOVEME? data_dir=$(ynh_app_setting_get --app=$app --key=data_dir) - -#================================================= -# CHECK IF THE APP CAN BE RESTORED -#================================================= -#REMOVEME? ynh_script_progression --message="Validating restoration parameters..." --weight=1 - -#REMOVEME? test ! -d $install_dir \ - || ynh_die --message="There is already a directory: $install_dir " - -#================================================= -# STANDARD RESTORATION STEPS -#================================================= -# RESTORE THE NGINX CONFIGURATION -#================================================= -ynh_script_progression --message="Restoring the NGINX web server configuration..." --weight=1 - -ynh_restore_file --origin_path="/etc/nginx/conf.d/$domain.d/$app.conf" - -#================================================= -# RECREATE THE DEDICATED USER -#================================================= -#REMOVEME? ynh_script_progression --message="Recreating the dedicated system user..." --weight=1 - -# Create the dedicated user (if not existing) -#REMOVEME? ynh_system_user_create --username=$app --home_dir="$install_dir" - #================================================= # RESTORE THE APP MAIN DIR #================================================= @@ -68,13 +17,6 @@ ynh_script_progression --message="Restoring the app main directory..." --weight= ynh_restore_file --origin_path="$install_dir" -# FIXME: this should be managed by the core in the future -# Here, as a packager, you may have to tweak the ownerhsip/permissions -# such that the appropriate users (e.g. maybe www-data) can access -# files in some cases. -# But FOR THE LOVE OF GOD, do not allow r/x for "others" on the entire folder - -# this will be treated as a security issue. -chmod 750 "$install_dir" chmod -R o-rwx "$install_dir" chown -R $app:www-data "$install_dir" @@ -83,36 +25,10 @@ chown -R $app:www-data "$install_dir" #================================================= ynh_script_progression --message="Restoring the PHP-FPM configuration..." --weight=1 +ynh_restore_file --origin_path="/etc/nginx/conf.d/$domain.d/$app.conf" + ynh_restore_file --origin_path="/etc/php/$phpversion/fpm/pool.d/$app.conf" -#================================================= -# RESTORE FAIL2BAN CONFIGURATION -#================================================= -#ynh_script_progression --message="Restoring the Fail2Ban configuration..." --time --weight=1 - -#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 - -#================================================= -# SPECIFIC RESTORATION -#================================================= -# REINSTALL DEPENDENCIES -#================================================= -#REMOVEME? ynh_script_progression --message="Reinstalling dependencies..." --weight=1 - -# Define and install dependencies -#REMOVEME? ynh_install_app_dependencies $pkg_dependencies - -#================================================= -# RESTORE VARIOUS FILES -#================================================= -#ynh_script_progression --message="Restoring various files..." --time --weight=1 - -#ynh_restore_file --origin_path="/etc/cron.d/$app" - -#ynh_restore_file --origin_path="/etc/$app/" - #================================================= # GENERIC FINALIZATION #================================================= diff --git a/scripts/upgrade b/scripts/upgrade index 311e9d8..9520ef9 100644 --- a/scripts/upgrade +++ b/scripts/upgrade @@ -9,99 +9,12 @@ source _common.sh source /usr/share/yunohost/helpers -#================================================= -# LOAD SETTINGS -#================================================= -#REMOVEME? ynh_script_progression --message="Loading installation settings..." --weight=1 - -#REMOVEME? app=$YNH_APP_INSTANCE_NAME - -#REMOVEME? domain=$(ynh_app_setting_get --app=$app --key=domain) -#REMOVEME? path=$(ynh_app_setting_get --app=$app --key=path) -#REMOVEME? admin=$(ynh_app_setting_get --app=$app --key=admin) -#REMOVEME? #REMOVEME? install_dir=$(ynh_app_setting_get --app=$app --key=install_dir) -#REMOVEME? #language=$(ynh_app_setting_get --app=$app --key=language) -#REMOVEME? db_name=$(ynh_app_setting_get --app=$app --key=db_name) - #================================================= # CHECK VERSION #================================================= -### This helper will compare the version of the currently installed app and the version of the upstream package. -### $upgrade_type can have 2 different values -### - UPGRADE_APP if the upstream app version has changed -### - 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. upgrade_type=$(ynh_check_app_version_changed) -#================================================= -# BACKUP BEFORE UPGRADE THEN ACTIVE TRAP -#================================================= -#REMOVEME? ynh_script_progression --message="Backing up the app before upgrading (may take a while)..." --weight=1 - -# Backup the current version of the app -#REMOVEME? ynh_backup_before_upgrade -#REMOVEME? ynh_clean_setup () { - # Restore it if the upgrade fails -#REMOVEME? ynh_restore_upgradebackup -} -# Exit if an error occurs during the execution of the script -#REMOVEME? ynh_abort_if_errors - -#================================================= -# STANDARD UPGRADE STEPS -#================================================= -# ENSURE DOWNWARD COMPATIBILITY -#================================================= -ynh_script_progression --message="Ensuring downward compatibility..." --weight=1 - -# -# N.B. : the followings setting migrations snippets are provided as *EXAMPLES* -# of what you may want to do in some cases (e.g. a setting was not defined on -# some legacy installs and you therefore want to initiaze stuff during upgrade) -# - -# If db_name doesn't exist, create it -#if [ -z "$db_name" ]; then -# db_name=$(ynh_sanitize_dbid --db_name=$app) -#REMOVEME? # 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 -# install_dir=/var/www/$app -#REMOVEME? # ynh_app_setting_set --app=$app --key=install_dir --value=$install_dir -#fi - -### If nobody installed your app before 4.1, -### then you may safely remove these lines - -# Cleaning legacy permissions -#REMOVEME? if ynh_legacy_permissions_exists; then -#REMOVEME? ynh_legacy_permissions_delete_all - - ynh_app_setting_delete --app=$app --key=is_public -fi - -#REMOVEME? if ! ynh_permission_exists --permission="admin"; then - # Create the required permissions -#REMOVEME? ynh_permission_create --permission="admin" --url="/admin" --allowed=$admin -fi - -# Create a permission if needed -#REMOVEME? if ! ynh_permission_exists --permission="api"; then -#REMOVEME? ynh_permission_create --permission="api" --url="/api" --allowed="visitors" --show_tile="false" --protected="true" -fi - -#================================================= -# CREATE DEDICATED USER -#================================================= -#REMOVEME? ynh_script_progression --message="Making sure dedicated system user exists..." --weight=1 - -# Create a dedicated user (if not existing) -#REMOVEME? ynh_system_user_create --username=$app --home_dir="$install_dir" - #================================================= # DOWNLOAD, CHECK AND UNPACK SOURCE #================================================= @@ -118,12 +31,9 @@ then # Backup the inc/digiwords.db file to the temp dir cp -ar "$install_dir/inc/digiwords.db" "$tmpdir/digiwords.db" - - # Remove the app directory securely -#REMOVEME? ynh_secure_remove --file=$install_dir - + # Download, check integrity, uncompress and patch the source from app.src - ynh_setup_source --dest_dir="$install_dir" + ynh_setup_source --dest_dir="$install_dir" --full_replace = 1 # Copy digiwords.db back to the install_dir cp -ar "$tmpdir/digiwords.db" "$install_dir/inc/digiwords.db" @@ -132,17 +42,10 @@ then ynh_secure_remove --file="$tmpdir" else # Download, check integrity, uncompress and patch the source from app.src - ynh_setup_source --dest_dir="$install_dir" + ynh_setup_source --dest_dir="$install_dir" --full_replace = 1 fi fi -# FIXME: this should be managed by the core in the future -# Here, as a packager, you may have to tweak the ownerhsip/permissions -# such that the appropriate users (e.g. maybe www-data) can access -# files in some cases. -# But FOR THE LOVE OF GOD, do not allow r/x for "others" on the entire folder - -# this will be treated as a security issue. -chmod 750 "$install_dir" chmod -R o-rwx "$install_dir" chown -R $app:www-data "$install_dir" @@ -154,42 +57,9 @@ ynh_script_progression --message="Upgrading NGINX web server configuration..." - # Create a dedicated NGINX config ynh_add_nginx_config -#================================================= -# UPGRADE DEPENDENCIES -#================================================= -#REMOVEME? ynh_script_progression --message="Upgrading dependencies..." --weight=1 - -#REMOVEME? ynh_install_app_dependencies $pkg_dependencies - -#================================================= -# PHP-FPM CONFIGURATION -#================================================= -ynh_script_progression --message="Upgrading PHP-FPM configuration..." --weight=1 - # Create a dedicated PHP-FPM config ynh_add_fpm_config -#================================================= -# SPECIFIC UPGRADE -#================================================= - -#================================================= -# GENERIC FINALIZATION -#================================================= -# UPGRADE FAIL2BAN -#================================================= -#ynh_script_progression --message="Reconfiguring Fail2Ban..." --time --weight=1 - -# Create a dedicated Fail2Ban config -#ynh_add_fail2ban_config --logpath="/var/log/nginx/${domain}-error.log" --failregex="Regex to match into the log for a failed login" - -#================================================= -# RELOAD NGINX -#================================================= -#REMOVEME? ynh_script_progression --message="Reloading NGINX web server..." --weight=1 - -#REMOVEME? ynh_systemd_action --service_name=nginx --action=reload - #================================================= # END OF SCRIPT #================================================= diff --git a/tests.toml b/tests.toml new file mode 100644 index 0000000..94abb79 --- /dev/null +++ b/tests.toml @@ -0,0 +1,5 @@ +#:schema https://raw.githubusercontent.com/YunoHost/apps/master/schemas/tests.v1.schema.json + +test_format = 1.0 + +[default] From 694f93bbf0bf8a12d629ea9084899d063ec888f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?E=CC=81ric=20Gaspar?= <46165813+ericgaspar@users.noreply.github.com> Date: Sun, 14 Jan 2024 12:34:27 +0100 Subject: [PATCH 03/12] Update manifest.toml --- manifest.toml | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/manifest.toml b/manifest.toml index d471890..f3871c3 100644 --- a/manifest.toml +++ b/manifest.toml @@ -52,13 +52,8 @@ ram.runtime = "50M" [resources.install_dir] - [resources.data_dir] - [resources.permissions] main.url = "/" [resources.database] - type = "mariadb-server, php8.2-sqlite3" - - [resources.database] - type = "mysql" + type = "php8.2-sqlite3" From 0bf5c61c27ae4b0f2d4bf95c4d246539866182a8 Mon Sep 17 00:00:00 2001 From: yunohost-bot Date: Sun, 14 Jan 2024 11:34:31 +0000 Subject: [PATCH 04/12] Auto-update README --- README.md | 2 +- README_fr.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fdc08ec..0462b16 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ If you don't have YunoHost, please consult [the guide](https://yunohost.org/#/in Simple and useful tools for the classroom. -**Shipped version:** 0.2.4~ynh1 +**Shipped version:** 0.3.7~ynh1 **Demo:** https://ladigitale.dev/digitools/ ## Documentation and resources diff --git a/README_fr.md b/README_fr.md index 9091818..912c4d5 100644 --- a/README_fr.md +++ b/README_fr.md @@ -18,7 +18,7 @@ Si vous n’avez pas YunoHost, regardez [ici](https://yunohost.org/#/install) po Des outils simples et utiles pour la classe. -**Version incluse :** 0.2.4~ynh1 +**Version incluse :** 0.3.7~ynh1 **Démo :** https://ladigitale.dev/digitools/ ## Documentations et ressources From 570e534b708f25da66b9c03a4dbdeec459aaf2c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?E=CC=81ric=20Gaspar?= <46165813+ericgaspar@users.noreply.github.com> Date: Sun, 14 Jan 2024 12:41:38 +0100 Subject: [PATCH 05/12] fix --- scripts/restore | 6 +++--- scripts/upgrade | 36 +++++------------------------------- 2 files changed, 8 insertions(+), 34 deletions(-) diff --git a/scripts/restore b/scripts/restore index 6603226..f66332a 100644 --- a/scripts/restore +++ b/scripts/restore @@ -18,12 +18,12 @@ ynh_script_progression --message="Restoring the app main directory..." --weight= ynh_restore_file --origin_path="$install_dir" chmod -R o-rwx "$install_dir" -chown -R $app:www-data "$install_dir" +chown -R "$app:www-data" "$install_dir" #================================================= -# RESTORE THE PHP-FPM CONFIGURATION +# RESTORE SYSTEM CONFIGURATIONS #================================================= -ynh_script_progression --message="Restoring the PHP-FPM configuration..." --weight=1 +ynh_script_progression --message="Restoring system configurations related to $app..." --weight=1 ynh_restore_file --origin_path="/etc/nginx/conf.d/$domain.d/$app.conf" diff --git a/scripts/upgrade b/scripts/upgrade index 9520ef9..c0e9955 100644 --- a/scripts/upgrade +++ b/scripts/upgrade @@ -18,46 +18,20 @@ upgrade_type=$(ynh_check_app_version_changed) #================================================= # DOWNLOAD, CHECK AND UNPACK SOURCE #================================================= +ynh_script_progression --message="Upgrading source files..." --weight=1 -if [ "$upgrade_type" == "UPGRADE_APP" ] -then - ynh_script_progression --message="Upgrading source files..." --weight=1 - - # Test because file doesn’t exist at first install. It is created at first page opening. - if test -f "$install_dir/inc/digiwords.db" - then - # Create a temporary directory - tmpdir="$(mktemp -d)" - - # Backup the inc/digiwords.db file to the temp dir - cp -ar "$install_dir/inc/digiwords.db" "$tmpdir/digiwords.db" - - # Download, check integrity, uncompress and patch the source from app.src - ynh_setup_source --dest_dir="$install_dir" --full_replace = 1 - - # Copy digiwords.db back to the install_dir - cp -ar "$tmpdir/digiwords.db" "$install_dir/inc/digiwords.db" - - # Remove the tmp directory securely - ynh_secure_remove --file="$tmpdir" - else - # Download, check integrity, uncompress and patch the source from app.src - ynh_setup_source --dest_dir="$install_dir" --full_replace = 1 - fi -fi +ynh_setup_source --dest_dir="$install_dir" --keep="inc/digiwords.db" chmod -R o-rwx "$install_dir" -chown -R $app:www-data "$install_dir" +chown -R "$app:www-data" "$install_dir" #================================================= -# NGINX CONFIGURATION +# REAPPLY SYSTEM CONFIGURATIONS #================================================= -ynh_script_progression --message="Upgrading NGINX web server configuration..." --weight=1 +ynh_script_progression --message="Upgrading system configurations related to $app..." --weight=1 -# Create a dedicated NGINX config ynh_add_nginx_config -# Create a dedicated PHP-FPM config ynh_add_fpm_config #================================================= From 80d2a9e42a1998f88355bf83c8b83272667cefe1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?E=CC=81ric=20Gaspar?= <46165813+ericgaspar@users.noreply.github.com> Date: Sun, 14 Jan 2024 12:45:22 +0100 Subject: [PATCH 06/12] Update manifest.toml --- manifest.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/manifest.toml b/manifest.toml index f3871c3..d84b7d4 100644 --- a/manifest.toml +++ b/manifest.toml @@ -55,5 +55,5 @@ ram.runtime = "50M" [resources.permissions] main.url = "/" - [resources.database] - type = "php8.2-sqlite3" + [resources.apt] + packages = "php8.2-sqlite3" From d1221d9ab29a18cf2a73428e770490dabc99579d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?E=CC=81ric=20Gaspar?= <46165813+ericgaspar@users.noreply.github.com> Date: Sun, 14 Jan 2024 12:48:32 +0100 Subject: [PATCH 07/12] Create DESCRIPTION.md --- doc/DESCRIPTION.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/DESCRIPTION.md diff --git a/doc/DESCRIPTION.md b/doc/DESCRIPTION.md new file mode 100644 index 0000000..b3bf2cb --- /dev/null +++ b/doc/DESCRIPTION.md @@ -0,0 +1 @@ +Simple and useful tools for the classroom \ No newline at end of file From cf508f7e66b3a16b09e88d1b61d41d5da17ae853 Mon Sep 17 00:00:00 2001 From: yunohost-bot Date: Sun, 14 Jan 2024 11:48:37 +0000 Subject: [PATCH 08/12] Auto-update README --- README.md | 2 +- README_fr.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0462b16..c952179 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ If you don't have YunoHost, please consult [the guide](https://yunohost.org/#/in ## Overview -Simple and useful tools for the classroom. +Simple and useful tools for the classroom **Shipped version:** 0.3.7~ynh1 diff --git a/README_fr.md b/README_fr.md index 912c4d5..a311f1c 100644 --- a/README_fr.md +++ b/README_fr.md @@ -16,7 +16,7 @@ Si vous n’avez pas YunoHost, regardez [ici](https://yunohost.org/#/install) po ## Vue d’ensemble -Des outils simples et utiles pour la classe. +Simple and useful tools for the classroom **Version incluse :** 0.3.7~ynh1 From 6a3b71fa4113871a209487b838aedd3960559d40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?E=CC=81ric=20Gaspar?= <46165813+ericgaspar@users.noreply.github.com> Date: Sun, 14 Jan 2024 12:48:40 +0100 Subject: [PATCH 09/12] Update manifest.toml --- manifest.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/manifest.toml b/manifest.toml index d84b7d4..f35cfea 100644 --- a/manifest.toml +++ b/manifest.toml @@ -2,8 +2,8 @@ packaging_format = 2 id = "digitools" name = "Digitools" -description.en = "Simple and useful tools for the classroom." -description.fr = "Des outils simples et utiles pour la classe." +description.en = "Simple and useful tools for the classroom" +description.fr = "Des outils simples et utiles pour la classe" version = "0.3.7~ynh1" From cd42c1637fa21212989f0a5e9ce7717035b12898 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?E=CC=81ric=20Gaspar?= <46165813+ericgaspar@users.noreply.github.com> Date: Sun, 14 Jan 2024 12:49:27 +0100 Subject: [PATCH 10/12] Update manifest.toml --- manifest.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifest.toml b/manifest.toml index f35cfea..d309a7f 100644 --- a/manifest.toml +++ b/manifest.toml @@ -27,7 +27,7 @@ ldap = false sso = false disk = "50M" -ram.build = "50M" +ram.build = "100M" ram.runtime = "50M" [install] From 14c24e08cac9c4631d0db90942d393f85f3e88fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?E=CC=81ric=20Gaspar?= <46165813+ericgaspar@users.noreply.github.com> Date: Sun, 14 Jan 2024 12:55:59 +0100 Subject: [PATCH 11/12] Create screenshot.jpg --- doc/screenshots/screenshot.jpg | Bin 0 -> 50919 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 doc/screenshots/screenshot.jpg diff --git a/doc/screenshots/screenshot.jpg b/doc/screenshots/screenshot.jpg new file mode 100644 index 0000000000000000000000000000000000000000..05c5406a5ab44d7913e5795ebba71c437bd79909 GIT binary patch literal 50919 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 literal 0 HcmV?d00001 From 58598d1a676ab613350ec631410fd90a305503eb Mon Sep 17 00:00:00 2001 From: yunohost-bot Date: Sun, 14 Jan 2024 11:56:03 +0000 Subject: [PATCH 12/12] Auto-update README --- README.md | 5 +++++ README_fr.md | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/README.md b/README.md index c952179..649a491 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,11 @@ Simple and useful tools for the classroom **Shipped version:** 0.3.7~ynh1 **Demo:** https://ladigitale.dev/digitools/ + +## Screenshots + +![Screenshot of Digitools](./doc/screenshots/screenshot.jpg) + ## Documentation and resources * Official app website: diff --git a/README_fr.md b/README_fr.md index a311f1c..44f1000 100644 --- a/README_fr.md +++ b/README_fr.md @@ -21,6 +21,11 @@ Simple and useful tools for the classroom **Version incluse :** 0.3.7~ynh1 **Démo :** https://ladigitale.dev/digitools/ + +## Captures d’écran + +![Capture d’écran de Digitools](./doc/screenshots/screenshot.jpg) + ## Documentations et ressources * Site officiel de l’app :