diff --git a/README.md b/README.md index 1dc8d7d..b4eb840 100644 --- a/README.md +++ b/README.md @@ -1,33 +1,30 @@ # ZeroNet for YunoHost -[![Integration level](https://dash.yunohost.org/integration/zeronet.svg)](https://dash.yunohost.org/appci/app/zeronet) ![](https://ci-apps.yunohost.org/ci/badges/zeronet.status.svg) ![](https://ci-apps.yunohost.org/ci/badges/zeronet.maintain.svg) + +[![Integration level](https://dash.yunohost.org/integration/zeronet.svg)](https://dash.yunohost.org/appci/app/zeronet) ![](https://ci-apps.yunohost.org/ci/badges/zeronet.status.svg) ![](https://ci-apps.yunohost.org/ci/badges/zeronet.maintain.svg) [![Install ZeroNet with YunoHost](https://install-app.yunohost.org/install-with-yunohost.png)](https://install-app.yunohost.org/?app=zeronet) *[Lire ce readme en français.](./README_fr.md)* -> *This package allows you to install REPLACEBYYOURAPP quickly and simply on a YunoHost server. +> *This package allows you to install ZeroNet quickly and simply on a YunoHost server. If you don't have YunoHost, please consult [the guide](https://yunohost.org/#/install) to learn how to install it.* ## Overview -eroNet allows you to publish static and dynamic websites on a distributed web platform. +ZeroNet allows you to publish static and dynamic websites on a distributed web platform using Bitcoin crypto and the BitTorrent network. **Shipped version:** 0.7.1 ## Screenshots -![](https://camo.githubusercontent.com/4629a7d44a828f5bb20cedd90522ae62f0947b35/68747470733a2f2f692e696d6775722e636f6d2f4836304f4148592e706e67) +![](https://camo.githubusercontent.com/7b79b9725df29fa5403ba490ff9870e0464a00e106bad5536867602625ca94cc/68747470733a2f2f692e696d6775722e636f6d2f4836304f4148592e706e67) -## Demo -* [Official demo](Link to a demo site for this app.) - -## Install +## Install instructions - This app can only be installed on root path, i.e you will need to use a dedicated domain name, e.g.`zeronet.domain.tld`. -- The app is installed in private mode only so unauthenticated users can not temper with your content. -- If you want to be a proper ZeroNet peer (serving your and other's content) you will want to open TCP port 15441 in your firewall and make sure requests to this port reach your instance [do i need to have a port opened](https://zeronet.readthedocs.io/en/latest/faq/#do-i-need-to-have-a-port-opened) -- Alternatively you can setup Tor which has to be done manually [how to use zeronet with Tor](https://zeronet.readthedocs.io/en/latest/faq/#how-to-use-zeronet-with-tor) +- You can setup Tor which has to be done manually till its implimented in the app.[how to use zeronet with Tor](https://zeronet.readthedocs.io/en/latest/faq/#how-to-use-zeronet-with-tor) + ## Multiple Instances -- Installing multiple instances of the app is *experimental*. Internally each instance it will use a unique port for both for ZeroNet UserInterface and FileServer. However, the tracker will see all instances as one peer, as they all use one and the same external IP address. +- Installing multiple instances of the app is *experimental*. Internally each instance will use an unique port for both for ZeroNet UserInterface and FileServer. However, the tracker will see all instances as one peer, as they all use one and the same external IP address. But you try it and explain your user case. - A solution that hasn't been confirmed yet is to use Tor instead. Each instance should then have a unique external address. ## Documentation @@ -39,23 +36,26 @@ eroNet allows you to publish static and dynamic websites on a distributed web pl #### Multi-user support -Are LDAP and HTTP auth supported? -Can the app be used by multiple users? +Are LDAP and HTTP auth supported? No + +#### Android app (Still in beta) + +[Android app](https://github.com/canewsin/zeronet_mobile) #### Supported architectures -* x86-64 - [![Build Status](https://ci-apps.yunohost.org/ci/logs/REPLACEBYYOURAPP%20%28Apps%29.svg)](https://ci-apps.yunohost.org/ci/apps/REPLACEBYYOURAPP/) -* ARMv8-A - [![Build Status](https://ci-apps-arm.yunohost.org/ci/logs/REPLACEBYYOURAPP%20%28Apps%29.svg)](https://ci-apps-arm.yunohost.org/ci/apps/REPLACEBYYOURAPP/) +* x86-64 - [![Build Status](https://ci-apps.yunohost.org/ci/logs/zeronet%20%28Apps%29.svg)](https://ci-apps.yunohost.org/ci/apps/zeronet/) +* ARMv8-A - [![Build Status](https://ci-apps-arm.yunohost.org/ci/logs/zeronet%20%28Apps%29.svg)](https://ci-apps-arm.yunohost.org/ci/apps/zeronet/) ## Limitations -* Any known limitations. +* App can not be used inside SSO. See [here](https://github.com/HelloZeroNet/ZeroNet/issues/2541) and [here](https://github.com/YunoHost/issues/issues/1580). ## Additional information -* Other info you would like to add about this app. +* Other info you would like to add about this Zeronet. -**More info on the documentation page:** +**More info on the documentation page:** https://yunohost.org/packaging_apps diff --git a/check_process b/check_process index 9f2a7ae..25c2bab 100644 --- a/check_process +++ b/check_process @@ -1,10 +1,9 @@ -;; Nom du test - auto_remove=1 -# Commentaire ignoré +;; Complete Test +# Comment ignored ; Manifest domain="sub.domain.tld" (DOMAIN) # path="/" (PATH) - admin="john" (USER) + password="seceret123" (USER) # language="fr" # is_public="Yes" (PUBLIC|public=Yes|private=No) # is_public=0 (PUBLIC|public=1|private=0) @@ -13,31 +12,18 @@ pkg_linter=1 setup_sub_dir=0 # setup_sub_dir=0 test disabled because app is always installed on / - setup_root=1 + setup_root=1 setup_nourl=0 setup_private=0 # setup_private=0 test disabled because app is always installed as private setup_public=0 upgrade=1 - backup_restore=1 - multi_instance=0 -# multi_instance=1 test disabled because it requires installation on sub-path which is not available. successfully conducted test manually - wrong_user=1 - wrong_path=0 - incorrect_path=0 -# corrupt_source=0 - fail_download_source=0 - port_already_use=1 (66) - final_path_already_use=1 -;;; Levels - Level 1=auto - Level 2=auto - Level 3=auto - Level 4=na -# level 4: the app does not directly use Yunohost user data - Level 5=auto - Level 6=auto - Level 7=auto - Level 8=0 - Level 9=0 - Level 10=0 + backup_restore=1 + multi_instance=q +;;; Options +Email= +Notification=none +;;; Upgrade options + ; commit=CommitHash + name=Name and date of the commit. + manifest_arg=domain=DOMAIN&path=PATH&admin=USER&is_public=1&password=pass& \ No newline at end of file diff --git a/conf/app.src b/conf/app.src index aa97514..0117e6c 100644 --- a/conf/app.src +++ b/conf/app.src @@ -1,3 +1,7 @@ SOURCE_URL=https://github.com/HelloZeroNet/ZeroNet/archive/v0.7.1.tar.gz SOURCE_SUM=78a27e1687d8e3699a854b77b516c95b30a8ba667f7ebbef0aabf7ec6ec7272d -SOURCE_VERSION=0.7.1 +SOURCE_SUM_PRG=sha256sum +SOURCE_FORMAT=tar.gz +SOURCE_IN_SUBDIR=true +SOURCE_FILENAME=0.7.1 +SOURCE_EXTRACT=true diff --git a/conf/nginx.conf b/conf/nginx.conf new file mode 100644 index 0000000..13a4178 --- /dev/null +++ b/conf/nginx.conf @@ -0,0 +1,9 @@ + +location / { + + proxy_pass http://127.0.0.1:__PORT__; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; +} \ No newline at end of file diff --git a/conf/nginx.conf.template b/conf/nginx.conf.template deleted file mode 100644 index c8fb620..0000000 --- a/conf/nginx.conf.template +++ /dev/null @@ -1,14 +0,0 @@ -location YNH_WWW_PATH { - - proxy_pass http://127.0.0.1:YNH_LOCAL_PORT; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header Accept 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'; - - # Include SSOWAT user panel. - include conf.d/yunohost_panel.conf.inc; -} diff --git a/conf/systemd.service b/conf/systemd.service new file mode 100644 index 0000000..5462b79 --- /dev/null +++ b/conf/systemd.service @@ -0,0 +1,15 @@ +[Unit] +Description=__APP__ service +After=network.target + +[Service] +User=__APP__ +Group=__APP__ +WorkingDirectory=__FINALPATH__ +ExecStart=/usr/bin/python3 __FINALPATH__/zeronet.py --ui_port __PORT__ --ui_host __DOMAIN__ --fileserver_port __FS_PORT__ --data_dir __DATADIR__/data --log_dir __DATADIR__/log --ui_password __PASSWORD__ +ExecReload=/bin/kill -HUP $MAINPID +KillMode=process +Restart=on-failure + +[Install] +WantedBy=multi-user.target diff --git a/conf/systemd.service.template b/conf/systemd.service.template deleted file mode 100644 index 4e6f674..0000000 --- a/conf/systemd.service.template +++ /dev/null @@ -1,14 +0,0 @@ -[Unit] -Description=ZeroNet for __USER__ -After=network.target - -[Service] -User=__USER__ -ExecStart=/usr/bin/python2 ./zeronet.py --ui_port __UI_PORT__ --ui_host __UI_HOST__ --fileserver_port __FS_PORT__ --data_dir __DATA_DIR__ --log_dir __LOG_DIR__ -WorkingDirectory=__WORKING_DIRECTORY__ -ExecReload=/bin/kill -HUP $MAINPID -KillMode=process -Restart=on-failure - -[Install] -WantedBy=multi-user.target diff --git a/issue_template.md b/issue_template.md new file mode 100644 index 0000000..653476a --- /dev/null +++ b/issue_template.md @@ -0,0 +1,55 @@ +--- +name: Bug report +about: When creating a bug report, please use the following template to provide all the relevant information and help debugging efficiently. + +--- + +**How to post a meaningful bug report** +1. *Read this whole template first.* +2. *Make sure you are on the right place:* + - *If you were performing an action on the app from the webadmin or the CLI (install, update, backup, restore, change_url...), you are on the right place!* + - *Otherwise, the issue may be due to the app itself. Refer to its documentation or repository for help.* + - *In doubt, ask here and we will figure it out together.* +3. *Delete these italic comments as you write over them below, and remove this guide.* +--- + +### Describe the bug + +*A clear and concise description of what the bug is.* + +### Context + +- Hardware: *VPS bought online / Old laptop or computer / Raspberry Pi at home / Internet Cube with VPN / Other ARM board / ...* +- YunoHost version: x.x.x +- I have access to my server: *Through SSH | through the webadmin | direct access via keyboard / screen | ...* +- Are you in a special context or did you perform some particular tweaking on your YunoHost instance ?: *no / yes* + - If yes, please explain: +- Using, or trying to install package version/branch: +- If upgrading, current package version: *can be found in the admin, or with `yunohost app info $app_id`* + +### Steps to reproduce + +- *If you performed a command from the CLI, the command itself is enough. For example:* + ```sh + sudo yunohost app install zeronet + ``` +- *If you used the webadmin, please perform the equivalent command from the CLI first.* +- *If the error occurs in your browser, explain what you did:* + 1. *Go to '...'* + 2. *Click on '...'* + 3. *Scroll down to '...'* + 4. *See error* + +### Expected behavior + +*A clear and concise description of what you expected to happen. You can remove this section if the command above is enough to understand your intent.* + +### Logs + +*When an operation fails, YunoHost provides a simple way to share the logs.* +- *In the webadmin, the error message contains a link to the relevant log page. On that page, you will be able to 'Share with Yunopaste'. If you missed it, the logs of previous operations are also available under Tools > Logs.* +- *In command line, the command to share the logs is displayed at the end of the operation and looks like `yunohost log display [log name] --share`. If you missed it, you can find the log ID of a previous operation using `yunohost log list`.* + +*After sharing the log, please copypaste directly the link provided by YunoHost (to help readability, no need to copypaste the entire content of the log here, just the link is enough...)* + +*If applicable and useful, add screenshots to help explain your problem.* diff --git a/manifest.json b/manifest.json index 7260872..71d64e2 100644 --- a/manifest.json +++ b/manifest.json @@ -32,14 +32,15 @@ "example": "zeronet.domain.tld" }, { - "name": "admin", - "type": "user", + "name": "password", + "type": "password", + "optional": true, "ask": { - "en": "Choose an admin user (the one who will be able to access the admin interface)", - "fr": "Choisissez l'administrateur (seul autorisé à accéder à l'interface d'administration)" + "en": "Set the password for the Zeronet.", + "fr": "Définissez le mot de passe pour votre Zeronet." }, - "example": "john doe" + "example": "myreallystrengthpassword" } ] } -} +} \ No newline at end of file diff --git a/pull_request_template.md b/pull_request_template.md new file mode 100644 index 0000000..824e95f --- /dev/null +++ b/pull_request_template.md @@ -0,0 +1,18 @@ +## Problem +- *Description of why you made this PR* + +## Solution +- *And how do you fix that problem* + +## PR Status +- [ ] Code finished. +- [ ] Tested with Package_check. +- [ ] Fix or enhancement tested. +- [ ] Upgrade from last version tested. +- [ ] Can be reviewed and tested. + +## Package_check results +--- +*If you have access to [App Continuous Integration for packagers](https://yunohost.org/#/packaging_apps_ci) you can provide a link to the package_check results like below, replacing '-NUM-' in this link by the PR number and USERNAME by your username on the ci-apps-dev. Or you provide a screenshot or a pastebin of the results* + +[![Build Status](https://ci-apps-dev.yunohost.org/jenkins/job/zeronet_ynh%20PR-NUM-%20(USERNAME)/badge/icon)](https://ci-apps-dev.yunohost.org/jenkins/job/zeronet_ynh%20PR-NUM-%20(USERNAME)/) diff --git a/scripts/_common.sh b/scripts/_common.sh new file mode 100644 index 0000000..55107c1 --- /dev/null +++ b/scripts/_common.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +#================================================= +# COMMON VARIABLES +#================================================= + +# dependencies used by the app +pkg_dependencies="python3-venv python3-pip" + +#================================================= +# PERSONAL HELPERS +#================================================= + +#================================================= +# EXPERIMENTAL HELPERS +#================================================= + +#================================================= +# FUTURE OFFICIAL HELPERS +#================================================= diff --git a/scripts/backup b/scripts/backup index 348f844..7ee7cd8 100755 --- a/scripts/backup +++ b/scripts/backup @@ -1,24 +1,63 @@ -#!/usr/bin/env bash +#!/bin/bash +#================================================= +# GENERIC START +#================================================= +# IMPORT GENERIC HELPERS +#================================================= + +#Keep this path for calling _common.sh inside the execution's context of backup and restore scripts +source ../settings/scripts/_common.sh source /usr/share/yunohost/helpers -main() { - ynh_abort_if_errors +#================================================= +# MANAGE SCRIPT FAILURE +#================================================= - local app=$YNH_APP_INSTANCE_NAME - local backup_dir=$YNH_APP_BACKUP_DIR - local deploy_path=$( ynh_app_setting_get $app deploy_path ) - local nginx_config_file=$( ynh_app_setting_get $app nginx_config_file ) - local systemd_service_name=$( ynh_app_setting_get $app systemd_service_name ) - local systemd_service_file=$( ynh_app_setting_get $app systemd_service_file ) - local user_zeronet_dir=$( ynh_app_setting_get $app user_zeronet_dir ) - - ynh_backup $deploy_path "sources" - - mkdir --parent ./conf - ynh_backup $nginx_config_file "conf/nginx.conf" - ynh_backup $systemd_service_file "conf/${systemd_service_name}" - ynh_backup $user_zeronet_dir "conf/.zeronet" +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 -main +#================================================= +# LOAD SETTINGS +#================================================= +ynh_print_info --message="Loading installation settings..." + +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) +datadir=$(ynh_app_setting_get --app=$app --key=datadir) + +#================================================= +# DECLARE DATA AND CONF FILES TO BACKUP +#================================================= +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="$datadir" + +#================================================= +# BACKUP THE NGINX CONFIGURATION +#================================================= + +ynh_backup --src_path="/etc/nginx/conf.d/$domain.d/$app.conf" + +#================================================= +# BACKUP SYSTEMD +#================================================= + +ynh_backup --src_path="/etc/systemd/system/$app.service" + +#================================================= +# END OF SCRIPT +#================================================= + +ynh_print_info --message="Backup script completed for $app. (YunoHost will then actually copy those files to the archive)." diff --git a/scripts/change_url b/scripts/change_url new file mode 100644 index 0000000..c77f72c --- /dev/null +++ b/scripts/change_url @@ -0,0 +1,129 @@ +#!/bin/bash + +#================================================= +# GENERIC STARTING +#================================================= +# IMPORT GENERIC HELPERS +#================================================= + +source _common.sh +source /usr/share/yunohost/helpers + +#================================================= +# RETRIEVE ARGUMENTS +#================================================= + +old_domain=$YNH_APP_OLD_DOMAIN +old_path=$YNH_APP_OLD_PATH + +new_domain=$YNH_APP_NEW_DOMAIN +new_path=$YNH_APP_NEW_PATH + +app=$YNH_APP_INSTANCE_NAME + +#================================================= +# LOAD SETTINGS +#================================================= +ynh_script_progression --message="Loading installation settings..." + +# Needed for helper "ynh_add_nginx_config" +final_path=$(ynh_app_setting_get --app=$app --key=final_path) + +#================================================= +# BACKUP BEFORE UPGRADE THEN ACTIVE TRAP +#================================================= +ynh_script_progression --message="Backing up the app before changing its URL (may take a while)..." + +# Backup the current version of the app +ynh_backup_before_upgrade +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" + + # restore it if the upgrade fails + ynh_restore_upgradebackup +} +# Exit if an error occurs during the execution of the script +ynh_abort_if_errors + +#================================================= +# CHECK WHICH PARTS SHOULD BE CHANGED +#================================================= + +change_domain=0 +if [ "$old_domain" != "$new_domain" ] +then + change_domain=1 +fi + +change_path=0 +if [ "$old_path" != "$new_path" ] +then + change_path=1 +fi + +#================================================= +# STANDARD MODIFICATIONS +#================================================= +# STOP SYSTEMD SERVICE +#================================================= +ynh_script_progression --message="Stopping a systemd service..." + +ynh_systemd_action --service_name=$app --action="start" --log_path="$datadir/log/debug-last.log" --line_match="Ui.UiServer Web interface" --timeout=120 + +#================================================= +# MODIFY URL IN NGINX CONF +#================================================= +ynh_script_progression --message="Updating NGINX web server configuration..." + +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" + # Set global variables for NGINX helper + domain="$old_domain" + path_url="$new_path" + # Create a dedicated NGINX config + 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 + # Store file checksum for the new config file location + ynh_store_file_checksum --file="/etc/nginx/conf.d/$new_domain.d/$app.conf" +fi + +#================================================= +# GENERIC FINALISATION +#================================================= +# INTEGRATE SERVICE IN YUNOHOST +#================================================= +ynh_script_progression --message="Integrating service in YunoHost..." + +ynh_replace_string --match_string="$old_domain" --replace_string="$new_domain" --target_file="/etc/systemd/system/${app}.service" + +#================================================= +# START SYSTEMD SERVICE +#================================================= + +ynh_systemd_action --service_name=$app --action="start" --log_path="$datadir/log/debug-last.log" + +#================================================= +# RELOAD NGINX +#================================================= +ynh_script_progression --message="Reloading NGINX web server..." + +ynh_systemd_action --service_name=nginx --action=reload + +#================================================= +# END OF SCRIPT +#================================================= + +ynh_script_progression --message="Change of URL completed for $app" diff --git a/scripts/functions.sh b/scripts/functions.sh deleted file mode 100644 index f975fa2..0000000 --- a/scripts/functions.sh +++ /dev/null @@ -1,65 +0,0 @@ -#!/usr/bin/env bash - -set -eu - -install_dependencies() { - apt-get install --quiet --yes python-msgpack python-gevent -} - -app_config_get() { - local app_config=$1 - local attribute=$2 - - cat ${app_config} \ - | grep ${attribute} \ - | cut --delimiter== --fields=2 -} - -make_deploy_path() { - local app=$1 - local source_version=$2 - - echo "/var/www/${app}-v${source_version}" # e.g. /var/www/zeronet__2-0.5.3 -} - -download_file() { - local url=$1 - local output_document=$2 - wget --no-verbose --output-document=${output_document} ${url} -} - -check_file_integrity() { - local file=$1 - local expected_checksum=$2 - - echo "${expected_checksum} ${file}" | sha256sum --check --status \ - || ynh_die "Corrupt source!" -} - -extract_archive() { - local src_file=$1 - local deploy_path=$2 - - mkdir --parents ${deploy_path} - tar --extract --file=${src_file} --directory=${deploy_path} --overwrite --strip-components 1 -} - -obtain_and_deploy_source() { - local app_config=$1 - local deploy_path=$2 - local symlink_to_deploy_path=$3 - local user=$4 - local src_url=$(app_config_get $app_config "SOURCE_URL") - local src_checksum=$(app_config_get $app_config "SOURCE_SUM") - local src_file="/tmp/source.tar.gz" - - download_file $src_url $src_file - check_file_integrity $src_file $src_checksum - extract_archive $src_file $deploy_path - - rm -f $symlink_to_deploy_path - ln --symbolic --force $deploy_path $symlink_to_deploy_path - - chown $user: -LR $symlink_to_deploy_path - chown $user: -h $symlink_to_deploy_path -} diff --git a/scripts/install b/scripts/install index 8ad4ff9..b594621 100755 --- a/scripts/install +++ b/scripts/install @@ -1,155 +1,161 @@ -#!/usr/bin/env bash +#!/bin/bash +#================================================= +# GENERIC START +#================================================= +# IMPORT GENERIC HELPERS +#================================================= + +source _common.sh source /usr/share/yunohost/helpers -source functions.sh -nginx_config_file() { - local app=$1 - local domain=$2 +#================================================= +# MANAGE SCRIPT FAILURE +#================================================= - mkdir --parents "/etc/nginx/conf.d/${domain}.d" - - echo "/etc/nginx/conf.d/${domain}.d/${app}.conf" +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 -update_nginx_configuration() { - local app=$1 - local config_template=$2 - local config_file=$3 - local path=$4 - local ui_port=$5 +#================================================= +# RETRIEVE ARGUMENTS FROM THE MANIFEST +#================================================= - cp $config_template $config_file +domain=$YNH_APP_ARG_DOMAIN +path_url="/" +password=$YNH_APP_ARG_PASSWORD - sed --in-place "s@YNH_WWW_PATH@${path}@g" ${config_file} - sed --in-place "s@YNH_LOCAL_PORT@${ui_port}/@g" ${config_file} +app=$YNH_APP_INSTANCE_NAME +datadir="/home/yunohost.app/${app}" +#================================================= +# CHECK IF THE APP CAN BE INSTALLED WITH THESE ARGS +#================================================= +ynh_script_progression --message="Validating installation parameters..." - service nginx reload -} +final_path=/var/www/$app +test ! -e "$final_path" || ynh_die --message="This path already contains a folder" -systemd_service_name() { - local app=$1 - local user=$2 +# Register (book) web path +ynh_webpath_register --app=$app --domain=$domain --path_url=$path_url - echo "ynh-${app}-${user}.service" -} +#================================================= +# STORE SETTINGS FROM MANIFEST +#================================================= +ynh_script_progression --message="Storing installation settings..." -# -# Creates service which runs zeronet on unique -# UserInterace and FileServer port for the user -# ZeroNet data is stored in the users home directory in ~/.zeronet -# -update_systemd_configuration() { - local app=$1 - local service_template=$2 - local service_name=$3 - local service_file=$4 - local user=$5 - local ui_port=$6 - local symlink_to_deploy_path=$7 - local zeronet_dir=$8 - local ui_host=$9 - local fs_port=${10} # only works with bash +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=datadir --value=$datadir +ynh_app_setting_set --app=$app --key=password --value=$password - # create zeronet user data location - local data_dir=$zeronet_dir/data - local log_dir=$zeronet_dir/log +#================================================= +# STANDARD MODIFICATIONS +#================================================= +# FIND AND OPEN A PORT +#================================================= +ynh_script_progression --message="Configuring firewall..." - mkdir -p $data_dir - mkdir -p $log_dir - chown -R $user: $zeronet_dir +# Find an available port +port=$(ynh_find_port --port=43110) +fs_port=$(ynh_find_port --port=15441) +ynh_app_setting_set --app=$app --key=port --value=$port +ynh_app_setting_set --app=$app --key=fs_port --value=$fs_port - # configure systemd service - cp $service_template $service_file +# Open the port +ynh_exec_warn_less yunohost firewall allow --no-upnp TCP $fs_port - sed --in-place "s@__USER__@$user@g" $service_file - sed --in-place "s@__UI_PORT__@$ui_port@g" $service_file - sed --in-place "s@__FS_PORT__@$fs_port@g" $service_file - sed --in-place "s@__UI_HOST__@$ui_host@g" $service_file - sed --in-place "s@__APP_NAME__@$app@g" $service_file - sed --in-place "s@__WORKING_DIRECTORY__@$symlink_to_deploy_path@g" $service_file - sed --in-place "s@__DATA_DIR__@$data_dir@g" $service_file - sed --in-place "s@__LOG_DIR__@$log_dir@g" $service_file +#================================================= +# INSTALL DEPENDENCIES +#================================================= +ynh_script_progression --message="Installing dependencies..." - systemctl daemon-reload - systemctl enable $service_name - systemctl start $service_name -} +ynh_install_app_dependencies $pkg_dependencies -# Source: https://github.com/YunoHost/yunohost/blob/901e3df9b604f542f2c460aad05bcc8efc9fd054/data/helpers.d/network -# -# Find a free port and return it -# -# example: port=$(ynh_find_port 8080) -# -# usage: ynh_find_port begin_port -# | arg: begin_port - port to start to search -ynh_find_port () { - port=$1 +pip3 install gevent-websocket msgpack-python gevent base58 merkletools rsa PySocks pyasn1 websocket_client gevent-ws coincurve maxminddb - while netcat -z 127.0.0.1 $port # Check if the port is free - do - port=$((port+1)) # Else, pass to next port - done - echo $port -} +#================================================= +# DOWNLOAD, CHECK AND UNPACK SOURCE +#================================================= +ynh_script_progression --message="Setting up source files..." -main() { - ynh_abort_if_errors +ynh_app_setting_set --app=$app --key=final_path --value=$final_path +# Download, check integrity, uncompress and patch the source from app.src +ynh_setup_source --dest_dir="$final_path" - local app=$YNH_APP_INSTANCE_NAME - local number=$YNH_APP_INSTANCE_NUMBER - local domain=$YNH_APP_ARG_DOMAIN - local user=$YNH_APP_ARG_ADMIN +#================================================= +# NGINX CONFIGURATION +#================================================= +ynh_script_progression --message="Configuring NGINX web server..." - local ui_port=$( ynh_find_port 43110 ) # the port zeronet ui listens on - local fs_port=$( ynh_find_port 15441 ) # the port zeronet fileserver listens on +# Create a dedicated NGINX config +ynh_add_nginx_config - local path="/" - local is_public=0 # by defaut access to zeronet is private - local nginx_config_template=../conf/nginx.conf.template - local systemd_service_template=../conf/systemd.service.template - local app_config=../conf/app.src - local systemd_service_dir=/etc/systemd/system +#================================================= +# CREATE DEDICATED USER +#================================================= +ynh_script_progression --message="Configuring system user..." - local url=$domain$path - local source_version=$( app_config_get $app_config "SOURCE_VERSION" ) - local systemd_service_name=$( systemd_service_name $app $user ) - local systemd_service_file="${systemd_service_dir}/${systemd_service_name}" - local nginx_config_file=$( nginx_config_file $app $domain ) - local user_zeronet_dir="/home/${user}/.zeronet" - local deploy_path=$( make_deploy_path $app $source_version ) - local symlink_to_deploy_path="/var/www/${app}" +# Create a system user +ynh_system_user_create --username=$app --home_dir=$datadir -s - ynh_webpath_available $domain $path - ynh_webpath_register $app $domain $path +#================================================= +# SETUP SYSTEMD +#================================================= +ynh_script_progression --message="Configuring a systemd service..." - ynh_user_exists $user +# Create a dedicated systemd config +ynh_add_systemd_config - ynh_app_setting_set $app is_public $is_public - ynh_app_setting_set $app domain $domain - ynh_app_setting_set $app path $path - ynh_app_setting_set $app url $url - ynh_app_setting_set $app admin $user - ynh_app_setting_set $app allowed_users $user - ynh_app_setting_set $app deploy_path $deploy_path - ynh_app_setting_set $app symlink_to_deploy_path $symlink_to_deploy_path - ynh_app_setting_set $app ui_port $ui_port - ynh_app_setting_set $app fs_port $fs_port - ynh_app_setting_set $app installed_version $source_version - ynh_app_setting_set $app nginx_config_file $nginx_config_file - ynh_app_setting_set $app user $user - ynh_app_setting_set $app user_zeronet_dir $user_zeronet_dir - ynh_app_setting_set $app systemd_service_name $systemd_service_name - ynh_app_setting_set $app systemd_service_file $systemd_service_file +#================================================= +# SETUP APPLICATION PERMISSIONS +#================================================= - install_dependencies +# Enable password authentication for Zeronet +mv $final_path/plugins/disabled-UiPassword $final_path/plugins/UiPassword - obtain_and_deploy_source $app_config $deploy_path $symlink_to_deploy_path $user +# Set right permissions +mkdir -p $datadir/data +mkdir -p $datadir/log +chown -R $app: $final_path +chown -R $app: $datadir - update_nginx_configuration $app $nginx_config_template $nginx_config_file $path $ui_port - update_systemd_configuration $app $systemd_service_template $systemd_service_name $systemd_service_file \ - $user $ui_port $symlink_to_deploy_path $user_zeronet_dir $domain $fs_port -} +#================================================= +# INTEGRATE SERVICE IN YUNOHOST +#================================================= +ynh_script_progression --message="Integrating service in YunoHost..." -main +ynh_add_systemd_config --service="$app" --template="systemd.service" --others_var="fs_port port domain datadir password" + +yunohost service add $app --description "$app service" --log "$datadir/log/debug-last.log" --needs_exposed_ports "$fs_port" + +#================================================= +# START SYSTEMD SERVICE +#================================================= +ynh_script_progression --message="Starting a systemd service..." + +# Start a systemd service +ynh_systemd_action --service_name=$app --action="start" --log_path="$datadir/log/debug-last.log" --line_match="Ui.UiServer Web interface" --timeout=120 + +#================================================= +# SETUP SSOWAT +#================================================= +ynh_script_progression --message="Configuring permissions..." + +# Allow the app to be public +ynh_permission_update --permission="main" --add="visitors" + +#================================================= +# RELOAD NGINX +#================================================= +ynh_script_progression --message="Reloading NGINX web server..." + +ynh_systemd_action --service_name=nginx --action=reload + +#================================================= +# END OF SCRIPT +#================================================= + +ynh_script_progression --message="Installation of $app completed" diff --git a/scripts/remove b/scripts/remove index fb79174..3e6e221 100755 --- a/scripts/remove +++ b/scripts/remove @@ -1,57 +1,96 @@ -#!/usr/bin/env bash -set -u +#!/bin/bash +#================================================= +# GENERIC START +#================================================= +# IMPORT GENERIC HELPERS +#================================================= + +source _common.sh source /usr/share/yunohost/helpers -remove_systemd_service_() { - local service_name=$1 - local service_file=$2 +#================================================= +# LOAD SETTINGS +#================================================= +ynh_script_progression --message="Loading installation settings..." - systemctl stop $service_name - systemctl disable $service_name - rm $service_file - systemctl daemon-reload - systemctl reset-failed -} +app=$YNH_APP_INSTANCE_NAME -remove_nginx_config() { - local nginx_config_file=$1 +domain=$(ynh_app_setting_get --app=$app --key=domain) +port=$(ynh_app_setting_get --app=$app --key=port) +fs_port=$(ynh_app_setting_get --app=$app --key=fs_port) +final_path=$(ynh_app_setting_get --app=$app --key=final_path) +datadir=$(ynh_app_setting_get --app=$app --key=datadir) - ynh_secure_remove $nginx_config_file - service nginx reload -} +#================================================= +# STANDARD REMOVE +#================================================= +# REMOVE SERVICE INTEGRATION IN YUNOHOST +#================================================= -remove_user_data() { - local user_zeronet_dir=$1 +# Remove the service from the list of services known by Yunohost (added from `yunohost service add`) +if ynh_exec_warn_less yunohost service status $app >/dev/null +then + ynh_script_progression --message="Removing $app service integration..." + yunohost service remove $app +fi - ynh_secure_remove $user_zeronet_dir -} +#================================================= +# STOP AND REMOVE SERVICE +#================================================= +ynh_script_progression --message="Stopping and removing the systemd service..." -remove_zeronet() { - local deploy_path=$1 - local symlink_to_deploy_path=$2 +# Remove the dedicated systemd config +ynh_remove_systemd_config - ynh_secure_remove $deploy_path - ynh_secure_remove $symlink_to_deploy_path -} +#================================================= +# REMOVE DEPENDENCIES +#================================================= +ynh_script_progression --message="Removing dependencies..." -main() { - local app=${YNH_APP_INSTANCE_NAME} - local domain=$( ynh_app_setting_get $app domain ) - local user=$( ynh_app_setting_get $app admin ) - local nginx_config_file=$( ynh_app_setting_get $app nginx_config_file ) - local systemd_service_name=$( ynh_app_setting_get $app systemd_service_name ) - local systemd_service_file=$( ynh_app_setting_get $app systemd_service_file ) - local deploy_path=$( ynh_app_setting_get $app deploy_path ) - local symlink_to_deploy_path=$( ynh_app_setting_get $app symlink_to_deploy_path ) - local user_zeronet_dir=$( ynh_app_setting_get $app user_zeronet_dir ) +# Remove metapackage and its dependencies +pip3 uninstall -y gevent-websocket msgpack-python gevent base58 merkletools rsa PySocks pyasn1 websocket_client gevent-ws coincurve maxminddb +ynh_remove_app_dependencies - if [ -n $domain ]; then - remove_nginx_config $nginx_config_file - remove_user_data $user_zeronet_dir - remove_systemd_service_ $systemd_service_name $systemd_service_file - remove_zeronet $deploy_path $symlink_to_deploy_path - fi -} +#================================================= +# REMOVE APP MAIN DIR +#================================================= +ynh_script_progression --message="Removing app main directory..." -main +# Remove the app directory securely +ynh_secure_remove --file="$final_path" +ynh_secure_remove --file="$datadir" + +#================================================= +# REMOVE NGINX CONFIGURATION +#================================================= +ynh_script_progression --message="Removing NGINX web server configuration..." + +# Remove the dedicated NGINX config +ynh_remove_nginx_config + +#================================================= +# CLOSE A PORT +#================================================= + +if yunohost firewall list | grep -q "\- $fs_port$" +then + ynh_script_progression --message="Closing port $port..." + ynh_exec_warn_less yunohost firewall disallow TCP $fs_port +fi + +#================================================= +# GENERIC FINALIZATION +#================================================= +# REMOVE DEDICATED USER +#================================================= +ynh_script_progression --message="Removing the dedicated system user..." + +# Delete a system user +ynh_system_user_delete --username=$app + +#================================================= +# END OF SCRIPT +#================================================= + +ynh_script_progression --message="Removal of $app completed" diff --git a/scripts/restore b/scripts/restore index 314471d..9ba2eaa 100755 --- a/scripts/restore +++ b/scripts/restore @@ -1,61 +1,124 @@ -#!/usr/bin/env bash +#!/bin/bash +#================================================= +# GENERIC START +#================================================= +# IMPORT GENERIC HELPERS +#================================================= + +source ../settings/scripts/_common.sh source /usr/share/yunohost/helpers -# TODO: enable with fix of https://github.com/YunoHost/yunohost/pull/246 -# source .hfunctions +#================================================= +# MANAGE SCRIPT FAILURE +#================================================= -# TODO: remove with fix of https://github.com/YunoHost/yunohost/pull/246 -install_dependencies() { - apt-get install --quiet --yes python-msgpack python-gevent +ynh_clean_setup () { + true } +# Exit if an error occurs during the execution of the script +ynh_abort_if_errors -is_app_restorable() { - local -r domain=$1 - local -r path=$2 - local -r deploy_path=$3 +#================================================= +# LOAD SETTINGS +#================================================= +ynh_script_progression --message="Loading installation settings..." - ynh_webpath_available $domain $path \ - || ynh_die "Path not available: ${domain}${path}" +app=$YNH_APP_INSTANCE_NAME - test ! -d $deploy_path \ - || ynh_die "There is already a directory: $deploy_path" -} +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) +port=$(ynh_app_setting_get --app=$app --key=port) +fs_port=$(ynh_app_setting_get --app=$app --key=fs_port) +datadir=$(ynh_app_setting_get --app=$app --key=datadir) -main() { - ynh_abort_if_errors +#================================================= +# CHECK IF THE APP CAN BE RESTORED +#================================================= +ynh_script_progression --message="Validating restoration parameters..." - local app=$YNH_APP_INSTANCE_NAME - local domain=$(ynh_app_setting_get $app domain) - local path=$(ynh_app_setting_get $app path) - local deploy_path=$(ynh_app_setting_get $app deploy_path) - local symlink_to_deploy_path=$(ynh_app_setting_get $app symlink_to_deploy_path) - local nginx_config_file=$( ynh_app_setting_get $app nginx_config_file ) - local systemd_service_name=$( ynh_app_setting_get $app systemd_service_name ) - local systemd_service_file=$( ynh_app_setting_get $app systemd_service_file ) - local user=$( ynh_app_setting_get $app user ) - local user_zeronet_dir=$( ynh_app_setting_get $app user_zeronet_dir ) +ynh_webpath_available --domain=$domain --path_url=$path_url \ + || ynh_die --message="Path not available: ${domain}${path_url}" +test ! -d $final_path \ + || ynh_die --message="There is already a directory: $final_path " - local url=$(ynh_app_setting_get $app url) +#================================================= +# STANDARD RESTORATION STEPS +#================================================= +# RESTORE THE NGINX CONFIGURATION +#================================================= - is_app_restorable $domain $path $deploy_path +ynh_restore_file --origin_path="/etc/nginx/conf.d/$domain.d/$app.conf" - install_dependencies +#================================================= +# RESTORE THE APP MAIN DIR +#================================================= +ynh_script_progression --message="Restoring the app main directory..." - cp -a "./sources" $deploy_path - ln --symbolic --force $deploy_path $symlink_to_deploy_path - chown $user: -LR $symlink_to_deploy_path - chown $user: -h $symlink_to_deploy_path +ynh_restore_file --origin_path="$final_path" +ynh_restore_file --origin_path="$datadir" - ynh_secure_remove $user_zeronet_dir - cp -aR "./conf/.zeronet" $user_zeronet_dir - chown -R $user: $user_zeronet_dir - - cp -a "./conf/nginx.conf" $nginx_config_file - service nginx reload - - cp -a "./conf/${systemd_service_name}" $systemd_service_file - systemctl restart $systemd_service_name -} +#================================================= +# RECREATE THE DEDICATED USER +#================================================= +ynh_script_progression --message="Recreating the dedicated system user..." -main +# Create the dedicated user (if not existing) +ynh_system_user_create --username=$app --home_dir=$datadir -s + +#================================================= +# RESTORE USER RIGHTS +#================================================= + +# Restore permissions on app files +chown -R $app: $final_path +chown -R $app: $datadir + +#================================================= +# SPECIFIC RESTORATION +#================================================= +# REINSTALL DEPENDENCIES +#================================================= +ynh_script_progression --message="Reinstalling dependencies..." + +# Define and install dependencies +ynh_install_app_dependencies $pkg_dependencies +pip3 install gevent-websocket msgpack-python gevent base58 merkletools rsa PySocks pyasn1 websocket_client gevent-ws coincurve maxminddb + +#================================================= +# RESTORE SYSTEMD +#================================================= +ynh_script_progression --message="Restoring the systemd configuration..." + +ynh_restore_file --origin_path="/etc/systemd/system/$app.service" +systemctl enable $app.service + +#================================================= +# INTEGRATE SERVICE IN YUNOHOST +#================================================= +ynh_script_progression --message="Integrating service in YunoHost..." + +yunohost service add $app --description "$app service" --log "$datadir/log/debug-last.log" --needs_exposed_ports "$fs_port" + +#================================================= +# START SYSTEMD SERVICE +#================================================= +ynh_script_progression --message="Starting a systemd service..." + +ynh_systemd_action --service_name=$app --action="start" --log_path="$datadir/log/debug-last.log" --line_match="Ui.UiServer Web interface" --timeout=120 + +#================================================= +# GENERIC FINALIZATION +#================================================= +# RELOAD NGINX +#================================================= +ynh_script_progression --message="Reloading NGINX web server..." + +ynh_systemd_action --service_name=nginx --action=reload + +#================================================= +# END OF SCRIPT +#================================================= + +ynh_script_progression --message="Restoration completed for $app" diff --git a/scripts/upgrade b/scripts/upgrade index 3b09cd2..e72b2a3 100755 --- a/scripts/upgrade +++ b/scripts/upgrade @@ -1,33 +1,152 @@ -#!/usr/bin/env bash +#!/bin/bash +#================================================= +# GENERIC START +#================================================= +# IMPORT GENERIC HELPERS +#================================================= + +source _common.sh source /usr/share/yunohost/helpers -source functions.sh -main() { - ynh_abort_if_errors +#================================================= +# LOAD SETTINGS +#================================================= +ynh_script_progression --message="Loading installation settings..." - local app=${YNH_APP_INSTANCE_NAME} - local user=$( ynh_app_setting_get $app user ) - local deploy_path=$( ynh_app_setting_get $app deploy_path ) - local symlink_to_deploy_path=$( ynh_app_setting_get $app symlink_to_deploy_path ) - local installed_version=$( ynh_app_setting_get $app installed_version ) - local systemd_service_name=$( ynh_app_setting_get $app systemd_service_name ) - local app_config=../conf/app.src - local source_version=$(app_config_get $app_config "SOURCE_VERSION") +app=$YNH_APP_INSTANCE_NAME - local old_deploy_path=$deploy_path - local new_deploy_path=$( make_deploy_path $app $source_version ) +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) +port=$(ynh_app_setting_get --app=$app --key=port) +fs_port=$(ynh_app_setting_get --app=$app --key=fs_port) +datadir=$(ynh_app_setting_get --app=$app --key=datadir) +password=$(ynh_app_setting_get --app=$app --key=password) - obtain_and_deploy_source $app_config $new_deploy_path $symlink_to_deploy_path $user +#================================================= +# CHECK VERSION +#================================================= - systemctl restart $systemd_service_name +upgrade_type=$(ynh_check_app_version_changed) - ynh_app_setting_set $app installed_version $source_version - ynh_app_setting_set $app deploy_path $new_deploy_path +#================================================= +# ENSURE DOWNWARD COMPATIBILITY +#================================================= +ynh_script_progression --message="Ensuring downward compatibility..." - if [ $new_deploy_path != $old_deploy_path ]; then - ynh_secure_remove $old_deploy_path - 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 +fi + +#================================================= +# BACKUP BEFORE UPGRADE THEN ACTIVE TRAP +#================================================= +ynh_script_progression --message="Backing up the app before upgrading (may take a while)..." + +# Backup the current version of the app +ynh_backup_before_upgrade +ynh_clean_setup () { + # restore it if the upgrade fails + ynh_restore_upgradebackup } +# Exit if an error occurs during the execution of the script +ynh_abort_if_errors -main +#================================================= +# STANDARD UPGRADE STEPS +#================================================= +# STOP SYSTEMD SERVICE +#================================================= +ynh_script_progression --message="Stopping a systemd service..." + +ynh_systemd_action --service_name=$app --action="stop" --log_path="$datadir/log/debug-last.log" + +#================================================= +# DOWNLOAD, CHECK AND UNPACK SOURCE +#================================================= + +if [ "$upgrade_type" == "UPGRADE_APP" ] +then + ynh_script_progression --message="Upgrading source files..." + + # Download, check integrity, uncompress and patch the source from app.src + ynh_secure_remove --file="$final_path" + ynh_setup_source --dest_dir="$final_path" +fi + +#================================================= +# NGINX CONFIGURATION +#================================================= +ynh_script_progression --message="Upgrading NGINX web server configuration..." + +# Create a dedicated NGINX config +ynh_add_nginx_config + +#================================================= +# UPGRADE DEPENDENCIES +#================================================= +ynh_script_progression --message="Upgrading dependencies..." + +ynh_install_app_dependencies $pkg_dependencies + +pip3 install gevent-websocket msgpack-python gevent base58 merkletools rsa PySocks pyasn1 websocket_client gevent-ws coincurve maxminddb + +#================================================= +# CREATE DEDICATED USER +#================================================= +ynh_script_progression --message="Making sure dedicated system user exists..." + +# Create a dedicated user (if not existing) +ynh_system_user_create --username=$app --home_dir=$datadir -s + +#================================================= +# SETUP SYSTEMD +#================================================= +ynh_script_progression --message="Upgrading systemd configuration..." + +# Create a dedicated systemd config +ynh_add_systemd_config + +#================================================= +# GENERIC FINALIZATION +#================================================= +# SECURE FILES AND DIRECTORIES +#================================================= + +# Enable password authentication for Zeronet +mv $final_path/plugins/disabled-UiPassword $final_path/plugins/UiPassword + +# Set permissions on app files +chown -R $app: $final_path +chown -R $app: $datadir + +#================================================= +# INTEGRATE SERVICE IN YUNOHOST +#================================================= +ynh_script_progression --message="Integrating service in YunoHost..." + +ynh_add_systemd_config --service="$app" --template="systemd.service" --others_var="fs_port port domain datadir password" +yunohost service add $app --description "$app service" --log "$datadir/log/debug-last.log" --needs_exposed_ports "$fs_port" + +#================================================= +# START SYSTEMD SERVICE +#================================================= +ynh_script_progression --message="Starting a systemd service..." + +ynh_systemd_action --service_name=$app --action="start" --log_path="$datadir/log/debug-last.log" --line_match="Ui.UiServer Web interface" --timeout=120 + +#================================================= +# RELOAD NGINX +#================================================= +ynh_script_progression --message="Reloading NGINX web server..." + +ynh_systemd_action --service_name=nginx --action=reload + +#================================================= +# END OF SCRIPT +#================================================= + +ynh_script_progression --message="Upgrade of $app completed"