From ef4b1659e4e99a54b8d4547dd76c0b7eaf27b49c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?E=CC=81ric=20Gaspar?= <46165813+ericgaspar@users.noreply.github.com> Date: Wed, 27 Sep 2023 16:30:53 +0200 Subject: [PATCH] v2 --- .github/workflows/updater.sh | 154 ------------------- .github/workflows/updater.yml | 51 ------- conf/nginx.conf | 2 +- config_panel.toml | 66 ++++---- doc/{DISCLAIMER.md => ADMIN.md} | 0 doc/{DISCLAIMER_fr.md => ADMIN_fr.md} | 0 doc/DESCRIPTION.md | 8 +- doc/DESCRIPTION_fr.md | 8 +- doc/logo_sloth.png | Bin 35961 -> 0 bytes manifest.json | 109 -------------- manifest.toml | 37 ++--- scripts/_common.sh | 22 --- scripts/backup | 31 ---- scripts/install | 183 +++------------------- scripts/remove | 94 ------------ scripts/restore | 209 ++------------------------ scripts/upgrade | 89 ----------- tests.toml | 9 ++ 18 files changed, 86 insertions(+), 986 deletions(-) delete mode 100755 .github/workflows/updater.sh delete mode 100644 .github/workflows/updater.yml rename doc/{DISCLAIMER.md => ADMIN.md} (100%) rename doc/{DISCLAIMER_fr.md => ADMIN_fr.md} (100%) delete mode 100644 doc/logo_sloth.png delete mode 100644 manifest.json create mode 100644 tests.toml diff --git a/.github/workflows/updater.sh b/.github/workflows/updater.sh deleted file mode 100755 index 218e48e..0000000 --- a/.github/workflows/updater.sh +++ /dev/null @@ -1,154 +0,0 @@ -#!/bin/bash - -#================================================= -# PACKAGE UPDATING HELPER -#================================================= - -# This script is meant to be run by GitHub Actions -# The YunoHost-Apps organisation offers a template Action to run this script periodically -# Since each app is different, maintainers can adapt its contents so as to perform -# automatic actions when a new upstream release is detected. - -# Remove this exit command when you are ready to run this Action -# exit 1 - -#================================================= -# FETCHING LATEST RELEASE AND ITS ASSETS -#================================================= - -# Fetching information -current_version=$(jq -j '.version|split("~")[0]' manifest.json) -repo=$(jq -j '.upstream.code|split("https://github.com/")[1]' manifest.json) -# Some jq magic is needed, because the latest upstream release is not always the latest version (e.g. security patches for older versions) -version=$(curl --silent "https://api.github.com/repos/$repo/releases" | jq -r '.[] | select( .prerelease != true ) | .tag_name' | sort -V | tail -1) -assets=($(curl --silent "https://api.github.com/repos/$repo/releases" | jq -r '[ .[] | select(.tag_name=="'"$version"'").assets[].browser_download_url ] | join(" ") | @sh' | tr -d "'")) - -# Later down the script, we assume the version has only digits and dots -# Sometimes the release name starts with a "v", so let's filter it out. -# You may need more tweaks here if the upstream repository has different naming conventions. -if [[ ${version:0:1} == "v" || ${version:0:1} == "V" ]]; then - version=${version:1} -fi - -# Setting up the environment variables -echo "Current version: $current_version" -echo "Latest release from upstream: $version" -{ echo "VERSION=$version"; echo "REPO=$repo"; } >> "$GITHUB_ENV" -# For the time being, let's assume the script will fail -echo "PROCEED=false" >> "$GITHUB_ENV" - -# Proceed only if the retrieved version is greater than the current one -if ! dpkg --compare-versions "$current_version" "lt" "$version" ; then - echo "::warning ::No new version available" - exit 0 -# Proceed only if the retrieved version is not a release candidate -elif [[ "$version" == *"rc"* ]] ; then - echo "::warning ::No new version available" - exit 0 -# Proceed only if a PR for this new version does not already exist -elif git ls-remote -q --exit-code --heads https://github.com/"$GITHUB_REPOSITORY".git ci-auto-update-v"$version" ; then - echo "::warning ::A branch already exists for this update" - exit 0 -fi - -# Each release can hold multiple assets (e.g. binaries for different architectures, source code, etc.) -echo "${#assets[@]} available asset(s)" - -#================================================= -# UPDATE SOURCE FILES -#================================================= - -# Here we use the $assets variable to get the resources published in the upstream release. -# Here is an example for Grav, it has to be adapted in accordance with how the upstream releases look like. - -# Create the temporary directory -tempdir="$(mktemp -d)" - -# Download checksums.txt -checksum_file=https://github.com/"$repo"/releases/download/v"$version"/checksums.txt -echo "Downloading checksums file at" "$checksum_file" -curl --silent -4 -L "$checksum_file" -o "$tempdir/checksums.txt" - -# Let's loop over the array of assets URLs -for asset_url in "${assets[@]}"; do - -echo "Handling asset at $asset_url" - -# Assign the asset to a source file in conf/ directory -# Here we base the source file name upon a unique keyword in the assets url (admin vs. update) -# Leave $src empty to ignore the asset -case $asset_url in - *"linux_386"*) - src="i386" - ;; - *"linux_amd64"*) - src="x86-64" - ;; - *"linux_arm64"*) - src="arm64" - ;; - *"linux_armv6"*) - src="armv6" - ;; - *"linux_armv7"*) - src="armv7" - ;; - *) - src="" - ;; -esac - -# If $src is not empty, let's process the asset -if [ -n "$src" ]; then - -# Get checksum -filename=${asset_url##*/} -checksum=$(grep "$filename" "$tempdir/checksums.txt" | awk '{print $1;}') - -# Get extension -if [[ $filename == *.tar.gz ]]; then - extension=tar.gz -else - extension=${filename##*.} -fi - -# Rewrite source file -cat < "conf/$src.src" -SOURCE_URL=$asset_url -SOURCE_SUM=$checksum -SOURCE_SUM_PRG=sha256sum -SOURCE_FORMAT=$extension -SOURCE_EXTRACT=true -SOURCE_IN_SUBDIR=false -SOURCE_FILENAME=$filename -EOT -echo "... conf/$src.src updated" - -else -echo "... asset ignored" -fi - -done - -# Delete temporary directory -rm -rf "$tempdir" - -#================================================= -# SPECIFIC UPDATE STEPS -#================================================= - -# Any action on the app's source code can be done. -# The GitHub Action workflow takes care of committing all changes after this script ends. - -#================================================= -# GENERIC FINALIZATION -#================================================= - -# Replace new version in manifest -echo "$(jq -s --indent 4 ".[] | .version = \"$version~ynh1\"" manifest.json)" > manifest.json - -# No need to update the README, yunohost-bot takes care of it - -# The Action will proceed only if the PROCEED environment variable is set to true -echo "PROCEED=true" >> "$GITHUB_ENV" -exit 0 diff --git a/.github/workflows/updater.yml b/.github/workflows/updater.yml deleted file mode 100644 index ad9b07e..0000000 --- a/.github/workflows/updater.yml +++ /dev/null @@ -1,51 +0,0 @@ -# This workflow allows GitHub Actions to automagically update your app whenever a new upstream release is detected. -# You need to enable Actions in your repository settings, and fetch this Action from the YunoHost-Apps organization. -# This file should be enough by itself, but feel free to tune it to your needs. -# It calls updater.sh, which is where you should put the app-specific update steps. -name: Check for new upstream releases -on: - # Allow to manually trigger the workflow - workflow_dispatch: - # Run it every day at 6:00 UTC - schedule: - - cron: '0 6 * * *' -jobs: - updater: - runs-on: ubuntu-latest - steps: - - name: Fetch the source code - uses: actions/checkout@v3 - with: - token: ${{ secrets.GITHUB_TOKEN }} - ref: 'testing' - - name: Run the updater script - id: run_updater - run: | - # Setting up Git user - git config --global user.name 'yunohost-bot' - git config --global user.email 'yunohost-bot@users.noreply.github.com' - # Run the updater script - /bin/bash .github/workflows/updater.sh - - name: Commit changes - id: commit - if: ${{ env.PROCEED == 'true' }} - run: | - git commit -am "Upgrade to v$VERSION" - - name: Create Pull Request - id: cpr - if: ${{ env.PROCEED == 'true' }} - uses: peter-evans/create-pull-request@v4 - with: - token: ${{ secrets.GITHUB_TOKEN }} - commit-message: Update to version ${{ env.VERSION }} - committer: 'yunohost-bot ' - author: 'yunohost-bot ' - signoff: false - base: testing - branch: ci-auto-update-v${{ env.VERSION }} - delete-branch: true - title: 'Upgrade to version ${{ env.VERSION }}' - body: | - Upgrade to v${{ env.VERSION }} - Changelog: https://github.com/${{ env.REPO }}/releases/tag/v${{ env.VERSION }} - draft: false diff --git a/conf/nginx.conf b/conf/nginx.conf index d1d550c..f751f6d 100644 --- a/conf/nginx.conf +++ b/conf/nginx.conf @@ -24,7 +24,7 @@ location __PATH__/ { # media caching stuff # https://docs.gotosocial.org/en/latest/advanced/caching/assets-media/#nginx location /assets/ { - alias __FINAL_PATH__/web/assets/; + alias __INSTALL_DIR__/web/assets/; autoindex off; # 300 = 5 minutes more_set_headers "Cache-control: public, max-age=300"; diff --git a/config_panel.toml b/config_panel.toml index c1b6a61..a841e21 100644 --- a/config_panel.toml +++ b/config_panel.toml @@ -22,7 +22,7 @@ help = "Config pertaining to creation and maintenance of accounts on the server, [main.accounts.accounts_registration_open] ask.en = "Open registrations?" ask.fr = "Inscriptions ouvertes ?" -bind = "accounts-registration-open:__FINALPATH__/config.yaml" +bind = "accounts-registration-open:__INSTALL_DIR__/config.yaml" choices = ["true", "false"] default = "false" help.en = "Do we want people to be able to just submit sign up requests, or do we want invite only?" @@ -32,7 +32,7 @@ type = "select" [main.accounts.accounts_approval_required] ask.en = "Approval required?" ask.fr = "Validation requise ?" -bind = "accounts-approval-required:__FINALPATH__/config.yaml" +bind = "accounts-approval-required:__INSTALL_DIR__/config.yaml" choices = ["true", "false"] default = "true" help.en = "Do sign up requests require approval from an admin/moderator before an account can sign in/use the server?" @@ -42,7 +42,7 @@ type = "select" [main.accounts.accounts_reason_required] ask.en = "Reason required?" ask.fr = "Motif requis ?" -bind = "accounts-reason-required:__FINALPATH__/config.yaml" +bind = "accounts-reason-required:__INSTALL_DIR__/config.yaml" choices = ["true", "false"] default = "true" help.en = "Are sign up requests required to submit a reason for the request (eg., an explanation of why they want to join the instance)?" @@ -52,7 +52,7 @@ type = "select" [main.accounts.accounts_allow_custom_css] ask.en = "Allow custom CSS?" ask.fr = "Autoriser le CSS personnalisé ?" -bind = "accounts-allow-custom-css:__FINALPATH__/config.yaml" +bind = "accounts-allow-custom-css:__INSTALL_DIR__/config.yaml" choices = ["true", "false"] default = "false" help.en = """Allow accounts on this instance to set custom CSS for their profile pages and statuses.\ @@ -68,7 +68,7 @@ type = "select" [main.accounts.accounts_custom_css_length] ask.en = "Custom CSS length?" ask.fr = "Longueur du CSS personnalisé ?" -bind = "accounts-custom-css-length:__FINALPATH__/config.yaml" +bind = "accounts-custom-css-length:__INSTALL_DIR__/config.yaml" default = "10000" help.en = "If accounts-allow-custom-css is 'true', this is the permitted length in characters for CSS uploaded by accounts on this instance. No effect if accounts-allow-custom-css is 'false'. Default: 10000" help.fr = "Si accounts-allow-custom-css est 'true', il s'agit de la longueur autorisée en caractères pour les feuilles de style CSS qui sont fournies par les comptes sur cette instance. Aucun effet si accounts-allow-custom-css est 'false'. Valeur par défaut : 10000" @@ -86,7 +86,7 @@ help = "Config pertaining to user media uploads (videos, image, image descriptio [main.media.media_image_max_size] ask.en = "Maximum allowed image upload size in bytes." ask.fr = "Taille maximale autorisée pour le téléchargement d'images, en octets." -bind = "media-image-max-size:__FINALPATH__/config.yaml" +bind = "media-image-max-size:__INSTALL_DIR__/config.yaml" default = "2097152" help.en = "Default: 2097152 -- aka 2MB" help.fr = "Valeur par défaut : 2097152 (soit 2 Mo)" @@ -95,7 +95,7 @@ type = "number" [main.media.media_video_max_size] ask.en = "Maximum allowed video upload size in bytes." ask.fr = "Taille maximale autorisée pour le téléchargement de vidéos, en octets." -bind = "media-video-max-size:__FINALPATH__/config.yaml" +bind = "media-video-max-size:__INSTALL_DIR__/config.yaml" default = "10485760" help.en = "Default: 10485760 -- aka 10MB" help.fr = "Valeur par défaut : 10485760 (soit 10 Mo)" @@ -104,7 +104,7 @@ type = "number" [main.media.media_description_min_chars] ask.en = "Minimum amount of characters required as an image or video description." ask.fr = "Nombre minimum de caractères requis pour la description d'une image ou d'une vidéo." -bind = "media-description-min-chars:__FINALPATH__/config.yaml" +bind = "media-description-min-chars:__INSTALL_DIR__/config.yaml" default = "0" help.en = "Default: 0 (not required)" help.fr = "Valeur par défaut : 0 (non obligatoire)" @@ -113,7 +113,7 @@ type = "number" [main.media.media_description_max_chars] ask.en = "Maximum amount of characters permitted in an image or video description." ask.fr = "Nombre maximum de caractères requis pour la description d'une image ou d'une vidéo." -bind = "media-description-max-chars:__FINALPATH__/config.yaml" +bind = "media-description-max-chars:__INSTALL_DIR__/config.yaml" default = "500" help.en = "Default: 500" help.fr = "Valeur par défaut : 500" @@ -122,7 +122,7 @@ type = "number" [main.media.media_remote_cache_days] ask.en = "Number of days to cache media from remote instances before they are removed from the cache." ask.fr = "Nombre de jours de mise en cache des médias des instances distantes avant qu'ils ne soient retirés du cache." -bind = "media-remote-cache-days:__FINALPATH__/config.yaml" +bind = "media-remote-cache-days:__INSTALL_DIR__/config.yaml" default = "30" help.en = """Default: 30\ A job will run every day at midnight to clean up any remote media older than the given amount of days. \ @@ -139,7 +139,7 @@ type = "number" [main.media.media_emoji_local_max_size] ask.en = "Max size in bytes of emojis uploaded to this instance via the admin API." ask.fr = "Taille maximale en octets des emojis téléchargés vers cette instance via l'API d'administration." -bind = "media-emoji-local-max-size:__FINALPATH__/config.yaml" +bind = "media-emoji-local-max-size:__INSTALL_DIR__/config.yaml" default = "51200" help.en = """Default: 51200\ The default is the same as the Mastodon size limit for emojis (50kb), which allows for good interoperability.\ @@ -152,7 +152,7 @@ type = "number" [main.media.media_emoji_remote_max_size] ask.en = "Max size in bytes of emojis to download from other instances." ask.fr = "Taille maximale en octets des emojis téléchargeables à partir d'autres instances." -bind = "media-emoji-remote-max-size:__FINALPATH__/config.yaml" +bind = "media-emoji-remote-max-size:__INSTALL_DIR__/config.yaml" default = "102400" help.en = """Default: 102400\ By default this is 100kb, or twice the size of the default for media-emoji-local-max-size.\ @@ -174,7 +174,7 @@ help = "Config pertaining to the creation of statuses/posts, and permitted limit [main.statuses.statuses_max_chars] ask.en = "Maximum amount of characters permitted for a new status." ask.fr = "Nombre maximal de caractères autorisés pour un nouveau statut." -bind = "statuses-max-chars:__FINALPATH__/config.yaml" +bind = "statuses-max-chars:__INSTALL_DIR__/config.yaml" default = "5000" help.en = "Default: 5000. Note that going way higher than the default might break federation." help.fr = "Valeur par défaut : 5000. Notez que si vous dépassez la valeur par défaut, vous risquez de compromettre la fédération." @@ -183,7 +183,7 @@ type = "number" [main.statuses.statuses_cw_max_chars] ask.en = "Maximum amount of characters allowed in the CW/subject header of a status." ask.fr = "Nombre maximum de caractères autorisés dans l'en-tête CW/sujet d'un statut." -bind = "statuses-cw-max-chars:__FINALPATH__/config.yaml" +bind = "statuses-cw-max-chars:__INSTALL_DIR__/config.yaml" default = "100" help.en = "Default: 100. Note that going way higher than the default might break federation." help.fr = "Valeur par défaut : 100. Notez que si vous dépassez la valeur par défaut, vous risquez de compromettre la fédération." @@ -192,7 +192,7 @@ type = "number" [main.statuses.statuses_poll_max_options] ask.en = "Maximum amount of options to permit when creating a new poll." ask.fr = "Nombre maximum d'options autorisées lors de la création d'un nouveau sondage." -bind = "statuses-poll-max-options:__FINALPATH__/config.yaml" +bind = "statuses-poll-max-options:__INSTALL_DIR__/config.yaml" default = "6" help.en = "Default: 6. Note that going way higher than the default might break federation." help.fr = "Valeur par défaut : 6. Notez que si vous dépassez la valeur par défaut, vous risquez de compromettre la fédération." @@ -201,7 +201,7 @@ type = "number" [main.statuses.statuses_poll_option_max_chars] ask.en = "Maximum amount of characters to permit per poll option when creating a new poll." ask.fr = "Nombre maximal de caractères autorisés par option de sondage lors de la création d'un nouveau sondage." -bind = "statuses-poll-option-max-chars:__FINALPATH__/config.yaml" +bind = "statuses-poll-option-max-chars:__INSTALL_DIR__/config.yaml" default = "50" help.en = "Default: 50. Note that going way higher than the default might break federation." help.fr = "Valeur par défaut : 50. Notez que si vous dépassez la valeur par défaut, vous risquez de compromettre la fédération." @@ -210,7 +210,7 @@ type = "number" [main.statuses.statuses_media_max_files] ask.en = "Maximum amount of media files that can be attached to a new status." ask.fr = "Quantité maximale de fichiers multimédias qui peuvent être joints à un nouveau statut." -bind = "statuses-media-max-files:__FINALPATH__/config.yaml" +bind = "statuses-media-max-files:__INSTALL_DIR__/config.yaml" default = "6" help.en = "Default: 6. Note that going way higher than the default might break federation." help.fr = "Valeur par défaut : 6. Notez que si vous dépassez la valeur par défaut, vous risquez de compromettre la fédération." @@ -229,7 +229,7 @@ help = "Config pertaining to instance federation settings, pages to hide/expose, [main.instance.landing_page_user] ask.en = "Landing page user" ask.fr = "Utilisateurice en tant que page d'accueil" -bind = "landing-page-user:__FINALPATH__/config.yaml" +bind = "landing-page-user:__INSTALL_DIR__/config.yaml" help.en = "The user that will be shown instead of the landing page. if no user is set, the landing page will be shown." help.fr = "L'utilisateurice qui sera affiché-e à la place de la page d'accueil. Si le champ est laissé vide, la page d'accueil normale sera affichée." type = "string" @@ -237,7 +237,7 @@ type = "string" [main.instance.instance_expose_peers] ask.en = "API: Expose peers?" ask.fr = "API : Exposer les pairs ?" -bind = "instance-expose-peers:__FINALPATH__/config.yaml" +bind = "instance-expose-peers:__INSTALL_DIR__/config.yaml" choices = ["true", "false"] default = "false" help.en = "Allow unauthenticated users to make queries to /api/v1/instance/peers?filter=open in order to see a list of instances that this instance 'peers' with. Even if set to 'false', then authenticated users (members of the instance) will still be able to query the endpoint." @@ -247,7 +247,7 @@ type = "select" [main.instance.instance_expose_suspended] ask.en = "API: Expose suspended?" ask.fr = "API : Exposer les instances bloquées ?" -bind = "instance-expose-suspended:__FINALPATH__/config.yaml" +bind = "instance-expose-suspended:__INSTALL_DIR__/config.yaml" choices = ["true", "false"] default = "false" help.en = "Allow unauthenticated users to make queries to /api/v1/instance/peers?filter=suspended in order to see a list of instances that this instance blocks/suspends. This will also allow unauthenticated users to see the list through the web UI. Even if set to 'false', then authenticated users (members of the instance) will still be able to query the endpoint." @@ -257,7 +257,7 @@ type = "select" [main.instance.instance_expose_suspended_web] ask.en = "API: Expose suspended on Web (/about/suspended)?" ask.fr = "API : Exposer les instances bloquées sur le Web (/about/suspended) ?" -bind = "instance-expose-suspended-web:__FINALPATH__/config.yaml" +bind = "instance-expose-suspended-web:__INSTALL_DIR__/config.yaml" choices = ["true", "false"] default = "false" help.en = "Allow unauthenticated users to view /about/suspended, showing the HTML rendered list of instances that this instance blocks/suspends." @@ -267,7 +267,7 @@ type = "select" [main.instance.instance_expose_public_timeline] ask.en = "API: Expose public timeline?" ask.fr = "API : Exposer la timeline publique ?" -bind = "instance-expose-public-timeline:__FINALPATH__/config.yaml" +bind = "instance-expose-public-timeline:__INSTALL_DIR__/config.yaml" choices = ["true", "false"] default = "false" help.en = "Allow unauthenticated users to make queries to /api/v1/timelines/public in order to see a list of public posts on this server. Even if set to 'false', then authenticated users (members of the instance) will still be able to query the endpoint." @@ -277,7 +277,7 @@ type = "select" [main.instance.instance_deliver_to_shared_inboxes] ask.en = "Deliver to shared inboxes?" ask.fr = "Envoi en boites partagées ?" -bind = "instance-deliver-to-shared-inboxes:__FINALPATH__/config.yaml" +bind = "instance-deliver-to-shared-inboxes:__INSTALL_DIR__/config.yaml" choices = ["true", "false"] default = "true" help.en = """This flag tweaks whether GoToSocial will deliver ActivityPub messages to the shared inbox of a recipient, if one is available, instead of delivering each message to each actor who should receive a message individually.\ @@ -291,7 +291,7 @@ type = "select" [main.instance.instance_inject_mastodon_version] ask.en = "Inject Mastodon version?" ask.fr = "Injecter une version Mastodon ?" -bind = "instance-inject-mastodon-version:__FINALPATH__/config.yaml" +bind = "instance-inject-mastodon-version:__INSTALL_DIR__/config.yaml" choices = ["true", "false"] default = "false" help.en = """This flag will inject a Mastodon version into the version field that is included in /api/v1/instance.\ @@ -317,7 +317,7 @@ help = "Config for sending emails via an smtp server." [main.smtp.smtp_host] ask.en = "SMTP Server Hostname" ask.fr = "Nom d'hôte du serveur SMTP" -bind = "smtp-host:__FINALPATH__/config.yaml" +bind = "smtp-host:__INSTALL_DIR__/config.yaml" default = "localhost" help.en = "The hostname of the SMTP server you want to use. Examples: mail.example.org, localhost" help.fr = "Le nom d'hôte du serveur SMTP que vous souhaitez utiliser. Exemples: mail.example.org, localhost" @@ -326,7 +326,7 @@ type = "string" [main.smtp.smtp_port] ask.en = "SMTP Port" ask.fr = "Port SMTP" -bind = "smtp-port:__FINALPATH__/config.yaml" +bind = "smtp-port:__INSTALL_DIR__/config.yaml" default = "25" help.en = "Port to use to connect to the SMTP server" help.fr = "Port à utiliser pour se connecter au serveur SMTP" @@ -335,7 +335,7 @@ type = "number" [main.smtp.smtp_username] ask.en = "SMTP Username" ask.fr = "Nom d'utilisateur SMTP" -bind = "smtp-username:__FINALPATH__/config.yaml" +bind = "smtp-username:__INSTALL_DIR__/config.yaml" default = "" help.en = "Username to use when authenticating with the SMTP server" help.fr = "Nom d'utilisateur à utiliser lors de l'authentification avec le serveur SMTP" @@ -344,7 +344,7 @@ type = "string" [main.smtp.smtp_password] ask.en = "SMTP Password" ask.fr = "Mot de passe SMTP" -bind = "smtp-password:__FINALPATH__/config.yaml" +bind = "smtp-password:__INSTALL_DIR__/config.yaml" default = "" help.en = "Password to use when authenticating with the SMTP server" help.fr = "Mot de passe à utiliser lors de l'authentification avec le serveur SMTP" @@ -353,7 +353,7 @@ type = "password" [main.smtp.smtp_from] ask.en = "SMTP From Address" ask.fr = "Adresse d'expédition SMTP" -bind = "smtp-from:__FINALPATH__/config.yaml" +bind = "smtp-from:__INSTALL_DIR__/config.yaml" default = "GoToSocial@__DOMAIN__" help.en = "From address for sent emails" help.fr = "L'adresse utilisée pour les e-mails envoyés" @@ -362,7 +362,7 @@ type = "email" [main.smtp.smtp_disclose_recipients] ask.en = "SMTP Disclose Recipients" ask.fr = "SMTP Divulguer les destinataires" -bind = "smtp-disclose-recipients:__FINALPATH__/config.yaml" +bind = "smtp-disclose-recipients:__INSTALL_DIR__/config.yaml" choices = ["true", "false"] default = "false" help.en = """true: Disclose all recipients in the To field\ @@ -384,7 +384,7 @@ help = "Settings pertaining to... the cache" [main.cache.cache_memory_target] ask.en = "Value of the cache target" ask.fr = "Valeur du niveau de cache" -bind = "memory-target:__FINALPATH__/config.yaml" +bind = "memory-target:__INSTALL_DIR__/config.yaml" default = "100MiB" help.en = """Sets a target limit that the application will try to keep it's caches within.\ This is based on estimated sizes of in-memory objects, and so NOT AT ALL EXACT. @@ -407,7 +407,7 @@ help = "Settings pertaining to http timeouts, security, cookies, and more. ⚠ [main.advanced.advanced_cookies_samesite] ask.en = "Value of the SameSite attribute of cookies set by GoToSocial." ask.fr = "Valeur de l'attribut SameSite des cookies définis par GoToSocial." -bind = "advanced-cookies-samesite:__FINALPATH__/config.yaml" +bind = "advanced-cookies-samesite:__INSTALL_DIR__/config.yaml" choices = ["lax", "strict"] default = "lax" help.en = """Defaults to 'lax' to ensure that the OIDC flow does not break, which is fine in most cases.\ @@ -420,7 +420,7 @@ type = "select" [main.advanced.advanced_rate_limit_requests] ask.en = "Amount of requests to permit from a single IP address within a span of 5 minutes." ask.fr = "Nombre de requêtes autorisées à partir d'une seule adresse IP dans un délai de 5 minutes." -bind = "advanced-rate-limit-requests:__FINALPATH__/config.yaml" +bind = "advanced-rate-limit-requests:__INSTALL_DIR__/config.yaml" default = "300" help.en = """Default: 300\ If this amount is exceeded, a 429 HTTP error code will be returned.\ diff --git a/doc/DISCLAIMER.md b/doc/ADMIN.md similarity index 100% rename from doc/DISCLAIMER.md rename to doc/ADMIN.md diff --git a/doc/DISCLAIMER_fr.md b/doc/ADMIN_fr.md similarity index 100% rename from doc/DISCLAIMER_fr.md rename to doc/ADMIN_fr.md diff --git a/doc/DESCRIPTION.md b/doc/DESCRIPTION.md index a88cd39..00ac5ce 100644 --- a/doc/DESCRIPTION.md +++ b/doc/DESCRIPTION.md @@ -1,7 +1 @@ -GoToSocial is an [ActivityPub](https://activitypub.rocks/) social network server, written in Golang. - -With GoToSocial, you can keep in touch with your friends, post, read, and share images and articles. All without being tracked or advertised to! - -![GoToSocial's logo, a stylized sloth head](./doc/logo_sloth.png) - -Documentation is at [docs.gotosocial.org](https://docs.gotosocial.org). +GoToSocial is an [ActivityPub](https://activitypub.rocks/) social network server, written in Golang. With GoToSocial, you can keep in touch with your friends, post, read, and share images and articles. All without being tracked or advertised to! \ No newline at end of file diff --git a/doc/DESCRIPTION_fr.md b/doc/DESCRIPTION_fr.md index ca48934..513d564 100644 --- a/doc/DESCRIPTION_fr.md +++ b/doc/DESCRIPTION_fr.md @@ -1,7 +1 @@ -Un serveur de réseau social basé sur [ActivityPub](https://activitypub.rocks/) écrit en Golang. - -Avec GoToSocial, vous pouvez rester en contact avec vos amis, publier, lire et partager des images et des articles. Tout cela sans être pisté ni subir de publicité ! - -![Le logo de GoToSocial, une tête de paresseux stylisée](./doc/logo_sloth.png) - -Vous pouvez consulter la documentation à l'adresse : [docs.gotosocial.org](https://docs.gotosocial.org). +Un serveur de réseau social basé sur [ActivityPub](https://activitypub.rocks/) écrit en Golang. Avec GoToSocial, vous pouvez rester en contact avec vos amis, publier, lire et partager des images et des articles. Tout cela sans être pisté ni subir de publicité ! diff --git a/doc/logo_sloth.png b/doc/logo_sloth.png deleted file mode 100644 index 2e46dbe35d58dab6cb0ad6874d4db1fd30fbf011..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 35961 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 diff --git a/manifest.json b/manifest.json deleted file mode 100644 index 2bd1d0b..0000000 --- a/manifest.json +++ /dev/null @@ -1,109 +0,0 @@ -{ - "name": "GoToSocial", - "id": "gotosocial", - "packaging_format": 1, - "description": { - "en": "ActivityPub social network server", - "fr": "Serveur de réseau social basé sur ActivityPub" - }, - "version": "0.11.1~ynh4", - "url": "https://github.com/superseriousbusiness/gotosocial", - "upstream": { - "license": "AGPL-3.0-only", - "website": "https://gotosocial.org/", - "demo": "", - "admindoc": "https://docs.gotosocial.org/en/latest/", - "userdoc": "https://docs.gotosocial.org/en/latest/", - "code": "https://github.com/superseriousbusiness/gotosocial" - }, - "license": "AGPL-3.0-only", - "maintainer": { - "name": "OniriCorpe", - "email": "" - }, - "requirements": { - "yunohost": ">= 11.0.6" - }, - "multi_instance": true, - "services": [ - "nginx", - "postgresql" - ], - "arguments": { - "install": [ - { - "name": "domain", - "type": "domain" - }, - { - "name": "admin", - "type": "string", - "ask": { - "en": "The username of your admin account.", - "fr": "Le nom d'utilisateur de votre compte admin." - }, - "help": { - "en": "Must be in lower case and without special characters.", - "fr": "Doit être en minuscule et sans caractère special." - }, - "example": "johndoe" - }, - { - "name": "email", - "type": "string", - "ask": { - "en": "The email adress of your admin account.", - "fr": "L'adresse e-mail de votre compte admin." - }, - "example": "johndoe@example.com" - }, - { - "name": "password", - "type": "password", - "help": { - "en": "Must contain: upper case, lower case, number and special character.", - "fr": "Il doit contenir : majuscule, minuscule, chiffre et caractère spécial." - } - }, - { - "name": "accounts_registration_open", - "type": "boolean", - "ask": { - "en": "Open registration?", - "fr": "Inscriptions ouvertes ?" - }, - "help": { - "en": "Do you want people to be able to just submit sign up requests (true), or do you want invite only (false)?", - "fr": "Voulez-vous que les gens puissent envoyer des demandes d'inscription (true) ou voulez-vous que les inscriptions soient uniquement sur invitation (false) ?" - }, - "default": false - }, - { - "name": "accounts_approval_required", - "type": "boolean", - "ask": { - "en": "Registration approval?", - "fr": "Vérification manuelle des inscriptions ?" - }, - "help": { - "en": "Do sign up requests require approval from an admin/moderator before an account can sign in/use the server?", - "fr": "Les demandes d'inscription doivent-elles être approuvées par un-e administrateur-ice/modérateur-ice avant qu'un compte puisse se connecter et utiliser le serveur ?" - }, - "default": true - }, - { - "name": "accounts_reason_required", - "type": "boolean", - "ask": { - "en": "Request registration reason?", - "fr": "Demande de motif pour les inscriptions ?" - }, - "help": { - "en": "Are sign up requests required to submit a reason for the request (eg., an explanation of why they want to join the instance)?", - "fr": "Les demandes d'inscription doivent-elles être accompagnée d'un motif (par exemple, une explication de la raison pour laquelle la personne veut rejoindre l'instance) ?" - }, - "default": true - } - ] - } -} \ No newline at end of file diff --git a/manifest.toml b/manifest.toml index c8b46f3..20079ec 100644 --- a/manifest.toml +++ b/manifest.toml @@ -16,40 +16,25 @@ demo = "" admindoc = "https://docs.gotosocial.org/en/latest/" userdoc = "https://docs.gotosocial.org/en/latest/" code = "https://github.com/superseriousbusiness/gotosocial" -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 = ">= 11.0.6" -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" - full_domain = true [install.admin] - # this is a generic question - ask strings are automatically handled by Yunohost's core - help.en = "Must be in lower case and without special characters." - help.fr = "Doit être en minuscule et sans caractère special." - type = "string" - example = "johndoe" - - [install.email] - ask.en = "The email adress of your admin account." - ask.fr = "L'adresse e-mail de votre compte admin." - type = "string" - example = "johndoe@example.com" + type = "user" [install.password] - # this is a generic question - ask strings are automatically handled by Yunohost's core help.en = "Must contain: upper case, lower case, number and special character." help.fr = "Il doit contenir : majuscule, minuscule, chiffre et caractère spécial." type = "password" @@ -92,9 +77,10 @@ ram.runtime = "50M" # FIXME: replace with an **estimate** minimum ram requiremen amd64.url = "https://github.com/superseriousbusiness/gotosocial/releases/download/v0.11.1/gotosocial_0.11.1_linux_amd64.tar.gz" amd64.sha256 = "4e44a5cb4044a523c51b5d178dfd06efb077a17dac22a9a170d6204cb7aed407" - [resources.system_user] + [resources.ports] + [resources.install_dir] [resources.data_dir] @@ -102,5 +88,8 @@ ram.runtime = "50M" # FIXME: replace with an **estimate** minimum ram requiremen [resources.permissions] main.url = "/" + [resources.apt] + packages = "postgresql, postgresql-contrib" + [resources.database] type = "postgresql" diff --git a/scripts/_common.sh b/scripts/_common.sh index 6cf1f26..5aaeadf 100644 --- a/scripts/_common.sh +++ b/scripts/_common.sh @@ -4,32 +4,10 @@ # COMMON VARIABLES #================================================= -# dependencies used by the app -#REMOVEME? pkg_dependencies="postgresql postgresql-contrib" - #================================================= # PERSONAL HELPERS #================================================= -# custom function to detect armv6 and armv7 -# ($YNH_ARCH returns armhf for both...) -detect_arch(){ - local architecture - if [ -n "$(uname -m | grep arm64)" ] || [ -n "$(uname -m | grep aarch64)" ]; then - architecture="arm64" - elif [ -n "$(uname -m | grep 64)" ]; then - architecture="x86-64" - elif [ -n "$(uname -m | grep 86)" ]; then - architecture="i386" - elif [ -n "$(uname -m | grep armv6)" ]; then - architecture="armv6" - elif [ -n "$(uname -m | grep armv7)" ]; then - architecture="armv7" - else - architecture="unknown" - fi - echo $architecture -} # custom function to change bash bool 0/1 to false/true convert_bool(){ diff --git a/scripts/backup b/scripts/backup index 5faddb4..0db8849 100755 --- a/scripts/backup +++ b/scripts/backup @@ -10,42 +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? domain=$(ynh_app_setting_get --app="$app" --key=domain) - -#REMOVEME? db_name=$(ynh_app_setting_get --app="$app" --key=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) - #================================================= # 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 #================================================= diff --git a/scripts/install b/scripts/install index 4ddcfa3..cbd3835 100755 --- a/scripts/install +++ b/scripts/install @@ -9,41 +9,22 @@ 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? app="$YNH_APP_INSTANCE_NAME" - landing_page_user="" - -#REMOVEME? domain="$YNH_APP_ARG_DOMAIN" -path="/" - client_max_body_size="100M" -#REMOVEME? admin="$YNH_APP_ARG_ADMIN" -#REMOVEME? email="$YNH_APP_ARG_EMAIL" -#REMOVEME? password="$YNH_APP_ARG_PASSWORD" +email=$(ynh_user_get_info --username=$admin --key=mail) # Config stuff: cache_memory_target="100MiB" -#REMOVEME? accounts_registration_open=$(convert_bool "$YNH_APP_ARG_ACCOUNTS_REGISTRATION_OPEN") -#REMOVEME? accounts_approval_required=$(convert_bool "$YNH_APP_ARG_ACCOUNTS_APPROVAL_REQUIRED") -#REMOVEME? accounts_reason_required=$(convert_bool "$YNH_APP_ARG_ACCOUNTS_REASON_REQUIRED") +accounts_registration_open=$(convert_bool "$YNH_APP_ARG_ACCOUNTS_REGISTRATION_OPEN") +accounts_approval_required=$(convert_bool "$YNH_APP_ARG_ACCOUNTS_APPROVAL_REQUIRED") +accounts_reason_required=$(convert_bool "$YNH_APP_ARG_ACCOUNTS_REASON_REQUIRED") accounts_allow_custom_css="false" accounts_custom_css_length="10000" @@ -85,34 +66,15 @@ smtp_disclose_recipients="false" advanced_cookies_samesite="lax" advanced_rate_limit_requests="300" -#================================================= -# CHECK IF THE APP CAN BE INSTALLED WITH THESE ARGS -#================================================= - -#REMOVEME? ynh_script_progression --message="Validating installation parameters..." --weight=1 - -#REMOVEME? install_dir="/var/www/$app" -#REMOVEME? test ! -e "$install_dir" || ynh_die --message="This path already contains a folder" - -#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=install_dir --value="$install_dir" +ynh_script_progression --message="Storing installation settings..." --weight=1 ynh_app_setting_set --app="$app" --key=landing_page_user --value="$landing_page_user" - -#REMOVEME? ynh_app_setting_set --app="$app" --key=domain --value="$domain" -#REMOVEME? ynh_app_setting_set --app="$app" --key=path --value="$path" - ynh_app_setting_set --app="$app" --key=client_max_body_size --value="$client_max_body_size" -#REMOVEME? ynh_app_setting_set --app="$app" --key=admin --value="$admin" ynh_app_setting_set --app="$app" --key=email --value="$email" -#REMOVEME? ynh_app_setting_set --app="$app" --key=password --value="$password" ynh_app_setting_set --app="$app" --key=cache_memory_target --value="$cache_memory_target" @@ -151,77 +113,23 @@ ynh_app_setting_set --app="$app" --key=statuses_poll_option_max_chars --value="$ ynh_app_setting_set --app="$app" --key=statuses_media_max_files --value="$statuses_media_max_files" ynh_app_setting_set --app="$app" --key=smtp_host --value="$smtp_host" -#REMOVEME? ynh_app_setting_set --app="$app" --key=smtp_port --value="$smtp_port" +REMOVEME? ynh_app_setting_set --app="$app" --key=smtp_port --value="$smtp_port" ynh_app_setting_set --app="$app" --key=smtp_username --value="$smtp_username" -#REMOVEME? ynh_app_setting_set --app="$app" --key=smtp_password --value="$smtp_password" +REMOVEME? ynh_app_setting_set --app="$app" --key=smtp_password --value="$smtp_password" ynh_app_setting_set --app="$app" --key=smtp_from --value="$smtp_from" ynh_app_setting_set --app="$app" --key=smtp_disclose_recipients --value="$smtp_disclose_recipients" ynh_app_setting_set --app="$app" --key=advanced_cookies_samesite --value="$advanced_cookies_samesite" ynh_app_setting_set --app="$app" --key=advanced_rate_limit_requests --value="$advanced_rate_limit_requests" -#================================================= -# STANDARD MODIFICATIONS -#================================================= -# FIND AND OPEN A PORT -#================================================= -#REMOVEME? ynh_script_progression --message="Finding an available port..." --weight=1 - -# Find an available port -#REMOVEME? port=$(ynh_find_port --port=8095) -#REMOVEME? ynh_app_setting_set --app="$app" --key=port --value="$port" - -#================================================= -# INSTALL DEPENDENCIES -#================================================= -#REMOVEME? ynh_script_progression --message="Installing dependencies..." --weight=5 - -#REMOVEME? ynh_exec_warn_less 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 POSTGRESQL DATABASE -#================================================= -#REMOVEME? ynh_script_progression --message="Creating a PostgreSQL database..." --weight=5 - -#REMOVEME? db_name=$(ynh_sanitize_dbid --db_name="$app") -#REMOVEME? db_user="$db_name" -#REMOVEME? db_pwd=$(ynh_string_random --length=30) -#REMOVEME? ynh_app_setting_set --app="$app" --key=db_name --value="$db_name" -#REMOVEME? ynh_app_setting_set --app="$app" --key=db_user --value="$db_user" -#REMOVEME? ynh_app_setting_set --app="$app" --key=db_pwd --value="$db_pwd" -#REMOVEME? ynh_psql_test_if_first_run -#REMOVEME? ynh_psql_setup_db --db_user="$db_user" --db_name="$db_name" --db_pwd="$db_pwd" - #================================================= # 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 - -# detect_arch comes from _common.sh / personnal helpers -architecture=$(detect_arch) - # 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" @@ -233,68 +141,28 @@ ynh_script_progression --message="Configuring NGINX web server..." --weight=1 # Create a dedicated NGINX config for the main domain ynh_add_nginx_config -#================================================= -# CREATE DATA DIRECTORY -#================================================= -ynh_script_progression --message="Creating a data directory..." --weight=1 - -#REMOVEME? 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..." --weight=1 - -ynh_add_config --template="config.yaml" --destination="$install_dir/config.yaml" - -# 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/config.yaml" -chown "$app:$app" "$install_dir/config.yaml" - -#================================================= -# SETUP SYSTEMD -#================================================= -ynh_script_progression --message="Configuring a systemd service..." --weight=1 - # Create a dedicated systemd config ynh_add_systemd_config -#================================================= -# GENERIC FINALIZATION -#================================================= -# SETUP LOGROTATE -#================================================= -ynh_script_progression --message="Configuring log rotation..." --weight=1 - # Use logrotate to manage application logfile(s) ynh_use_logrotate -#================================================= -# SETUP FAIL2BAN -#================================================= -ynh_script_progression --message="Configuring fail2ban..." --weight=1 - # Create the logfile, required before configuring fail2ban touch "/var/log/${app}/${app}.log" # Create a dedicated Fail2Ban config ynh_add_fail2ban_config --logpath="/var/log/${app}/${app}.log" --failregex="statusCode=401 path=/auth/sign_in clientIP= .* msg=\"Unauthorized:" --max_retry=5 +#================================================= +# ADD A CONFIGURATION +#================================================= +ynh_script_progression --message="Adding a configuration file..." --weight=1 + +ynh_add_config --template="../conf/config.yaml" --destination="$install_dir/config.yaml" + +chmod 400 "$install_dir/config.yaml" +chown "$app:$app" "$install_dir/config.yaml" + #================================================= # INTEGRATE SERVICE IN YUNOHOST #================================================= @@ -320,22 +188,7 @@ ynh_exec_warn_less /var/www/"$app"/gotosocial --config-path "$install_dir/config ynh_script_progression --message="Starting a systemd service..." --weight=1 # Start a systemd service -ynh_systemd_action --service_name="$app" --action="start" --log_path="/var/log/$app/$app.log" - -#================================================= -# SETUP SSOWAT -#================================================= -#REMOVEME? ynh_script_progression --message="Configuring permissions..." --weight=1 - -# Everyone can access the app. -#REMOVEME? ynh_permission_update --permission="main" --add="visitors" - -#================================================= -# RELOAD NGINX -#================================================= -#REMOVEME? ynh_script_progression --message="Reloading NGINX web server..." --weight=1 - -#REMOVEME? ynh_systemd_action --service_name=nginx --action=reload +ynh_systemd_action --service_name="$app" --action="start" --log_path="systemd" #================================================= # END OF SCRIPT diff --git a/scripts/remove b/scripts/remove index d2b9e25..881a174 100755 --- a/scripts/remove +++ b/scripts/remove @@ -9,20 +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=$(ynh_app_setting_get --app="$app" --key=db_user) -#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 #================================================= @@ -36,100 +22,20 @@ then yunohost service remove "$app" fi -#================================================= -# STOP AND REMOVE SERVICE -#================================================= -ynh_script_progression --message="Stopping and removing the systemd service..." --weight=1 - # Remove the dedicated systemd config ynh_remove_systemd_config -#================================================= -# REMOVE THE POSTGRESQL DATABASE -#================================================= -#REMOVEME? ynh_script_progression --message="Removing the PostgreSQL database..." --weight=5 - -# Remove a database if it exists, along with the associated user -#REMOVEME? ynh_psql_remove_db --db_user="$db_user" --db_name="$db_name" - -#================================================= -# REMOVE DEPENDENCIES -#================================================= -#REMOVEME? ynh_script_progression --message="Removing dependencies..." --weight=5 - -# Remove metapackage and its dependencies -#REMOVEME? ynh_remove_app_dependencies - -#================================================= -# 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 APP DATA DIR -#================================================= -ynh_script_progression --message="Removing app data directory..." --weight=1 - -# Remove the app directory securely -#REMOVEME? ynh_secure_remove --file="$data_dir" - -#================================================= -# REMOVE NGINX CONFIGURATION -#================================================= -ynh_script_progression --message="Removing NGINX web server configuration..." --weight=1 - # Remove the dedicated NGINX config of the main domain ynh_remove_nginx_config -#================================================= -# REMOVE LOGROTATE CONFIGURATION -#================================================= -ynh_script_progression --message="Removing logrotate configuration..." --weight=1 - # Remove the app-specific logrotate config ynh_remove_logrotate -#================================================= -# REMOVE FAIL2BAN CONFIGURATION -#================================================= - -ynh_script_progression --message="Removing fail2ban configuration..." --weight=1 - ynh_remove_fail2ban_config -#================================================= -# CLOSE A PORT -#================================================= - -if yunohost firewall list | grep -q "\- $port$" -then - ynh_script_progression --message="Closing port $port..." --weight=1 - ynh_exec_warn_less yunohost firewall disallow TCP "$port" -fi - -#================================================= -# SPECIFIC REMOVE -#================================================= -# REMOVE VARIOUS FILES -#================================================= -ynh_script_progression --message="Removing various files..." --weight=1 - # 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 e41bce6..390b0e1 100755 --- a/scripts/restore +++ b/scripts/restore @@ -10,111 +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? #REMOVEME? install_dir=$(ynh_app_setting_get --app="$app" --key=install_dir) - -#REMOVEME? landing_page_user=$(ynh_app_setting_get --app="$app" --key=landing_page_user) - -#REMOVEME? domain=$(ynh_app_setting_get --app="$app" --key=domain) -#REMOVEME? port=$(ynh_app_setting_get --app="$app" --key=port) -#REMOVEME? path=$(ynh_app_setting_get --app="$app" --key=path) - -#REMOVEME? client_max_body_size=$(ynh_app_setting_get --app="$app" --key=client_max_body_size) - -#REMOVEME? db_name=$(ynh_app_setting_get --app="$app" --key=db_name) -#REMOVEME? db_user=$(ynh_app_setting_get --app="$app" --key=db_user) -#REMOVEME? db_pwd=$(ynh_app_setting_get --app="$app" --key=db_pwd) - -#REMOVEME? data_dir=$(ynh_app_setting_get --app="$app" --key=data_dir) - -#REMOVEME? cache_memory_target=$(ynh_app_setting_get --app="$app" --key=cache_memory_target) - -#REMOVEME? instance_expose_peers=$(ynh_app_setting_get --app="$app" --key=instance_expose_peers) -#REMOVEME? instance_expose_suspended=$(ynh_app_setting_get --app="$app" --key=instance_expose_suspended) -#REMOVEME? instance_expose_suspended_web=$(ynh_app_setting_get --app="$app" --key=instance_expose_suspended_web) -#REMOVEME? instance_expose_public_timeline=$(ynh_app_setting_get --app="$app" --key=instance_expose_public_timeline) -#REMOVEME? instance_deliver_to_shared_inboxes=$(ynh_app_setting_get --app="$app" --key=instance_deliver_to_shared_inboxes) -#REMOVEME? instance_inject_mastodon_version=$(ynh_app_setting_get --app="$app" --key=instance_inject_mastodon_version) - -#REMOVEME? accounts_registration_open=$(ynh_app_setting_get --app="$app" --key=accounts_registration_open) -#REMOVEME? accounts_approval_required=$(ynh_app_setting_get --app="$app" --key=accounts_approval_required) -#REMOVEME? accounts_reason_required=$(ynh_app_setting_get --app="$app" --key=accounts_reason_required) -#REMOVEME? accounts_allow_custom_css=$(ynh_app_setting_get --app="$app" --key=accounts_allow_custom_css) -#REMOVEME? accounts_custom_css_length=$(ynh_app_setting_get --app="$app" --key=accounts_custom_css_length) - -#REMOVEME? media_image_max_size=$(ynh_app_setting_get --app="$app" --key=media_image_max_size) -#REMOVEME? media_video_max_size=$(ynh_app_setting_get --app="$app" --key=media_video_max_size) -#REMOVEME? media_description_min_chars=$(ynh_app_setting_get --app="$app" --key=media_description_min_chars) -#REMOVEME? media_description_max_chars=$(ynh_app_setting_get --app="$app" --key=media_description_max_chars) -#REMOVEME? media_remote_cache_days=$(ynh_app_setting_get --app="$app" --key=media_remote_cache_days) -#REMOVEME? media_emoji_local_max_size=$(ynh_app_setting_get --app="$app" --key=media_emoji_local_max_size) -#REMOVEME? media_emoji_remote_max_size=$(ynh_app_setting_get --app="$app" --key=media_emoji_remote_max_size) - -#REMOVEME? storage_backend=$(ynh_app_setting_get --app="$app" --key=storage_backend) -#REMOVEME? storage_s3_endpoint=$(ynh_app_setting_get --app="$app" --key=storage_s3_endpoint) -#REMOVEME? storage_s3_proxy=$(ynh_app_setting_get --app="$app" --key=storage_s3_proxy) -#REMOVEME? storage_s3_access_key=$(ynh_app_setting_get --app="$app" --key=storage_s3_access_key) -#REMOVEME? storage_s3_secret_key=$(ynh_app_setting_get --app="$app" --key=storage_s3_secret_key) -#REMOVEME? storage_s3_bucket=$(ynh_app_setting_get --app="$app" --key=storage_s3_bucket) - -#REMOVEME? statuses_max_chars=$(ynh_app_setting_get --app="$app" --key=statuses_max_chars) -#REMOVEME? statuses_cw_max_chars=$(ynh_app_setting_get --app="$app" --key=statuses_cw_max_chars) -#REMOVEME? statuses_poll_max_options=$(ynh_app_setting_get --app="$app" --key=statuses_poll_max_options) -#REMOVEME? statuses_poll_option_max_chars=$(ynh_app_setting_get --app="$app" --key=statuses_poll_option_max_chars) -#REMOVEME? statuses_media_max_files=$(ynh_app_setting_get --app="$app" --key=statuses_media_max_files) - -#REMOVEME? smtp_host=$(ynh_app_setting_get --app="$app" --key=smtp_host) -#REMOVEME? smtp_port=$(ynh_app_setting_get --app="$app" --key=smtp_port) -#REMOVEME? smtp_username=$(ynh_app_setting_get --app="$app" --key=smtp_username) -#REMOVEME? smtp_password=$(ynh_app_setting_get --app="$app" --key=smtp_password) -#REMOVEME? smtp_from=$(ynh_app_setting_get --app="$app" --key=smtp_from) -#REMOVEME? smtp_disclose_recipients=$(ynh_app_setting_get --app="$app" --key=smtp_disclose_recipients) - -#REMOVEME? advanced_cookies_samesite=$(ynh_app_setting_get --app="$app" --key=advanced_cookies_samesite) -#REMOVEME? advanced_rate_limit_requests=$(ynh_app_setting_get --app="$app" --key=advanced_rate_limit_requests) - -#================================================= -# 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 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 #================================================= @@ -122,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" @@ -139,91 +27,36 @@ ynh_script_progression --message="Restoring the data directory..." --weight=5 ynh_restore_file --origin_path="$data_dir" --not_mandatory -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" -#================================================= -# SPECIFIC RESTORATION -#================================================= -# REINSTALL DEPENDENCIES -#================================================= -#REMOVEME? ynh_script_progression --message="Reinstalling dependencies..." --weight=5 - -# Define and install dependencies -#REMOVEME? ynh_exec_warn_less ynh_install_app_dependencies "$pkg_dependencies" - #================================================= # RESTORE THE POSTGRESQL DATABASE #================================================= -#REMOVEME? ynh_script_progression --message="Restoring the PostgreSQL database..." --weight=5 +ynh_script_progression --message="Restoring the PostgreSQL database..." --weight=5 -#REMOVEME? ynh_psql_test_if_first_run -#REMOVEME? ynh_psql_setup_db --db_user="$db_user" --db_name="$db_name" --db_pwd="$db_pwd" ynh_psql_execute_as_root --sql="CREATE EXTENSION IF NOT EXISTS unaccent;" --database="$db_name" ynh_psql_execute_as_root --sql="CREATE EXTENSION IF NOT EXISTS pg_trgm;" --database="$db_name" ynh_psql_execute_as_root --sql="CREATE EXTENSION IF NOT EXISTS citext;" --database="$db_name" ynh_psql_execute_as_root --sql="CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\";" --database="$db_name" ynh_psql_execute_file_as_root --file="./db.sql" --database="$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 - -# detect_arch comes from _common.sh / personnal helpers -architecture="$(detect_arch)" - -# compare if the system arch is different from the binary arch -# if so, download the correct binary -if [ "$architecture" != "$(file "$install_dir"/gotosocial | cut -d ',' -f 2 | tr -d ' ')" ] -then - ynh_script_progression --message="Migrating binary architecture..." - - # Download, check integrity, uncompress and patch the source from app.src - ynh_setup_source --dest_dir="$install_dir" --keep="config.yaml" -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" - -#================================================= -# RESTORE VARIOUS FILES -#================================================= - -mkdir -p "/var/log/$app" - #================================================= # RESTORE SYSTEMD #================================================= ynh_script_progression --message="Restoring the systemd configuration..." --weight=1 +ynh_restore_file --origin_path="/etc/nginx/conf.d/$domain.d/$app.conf" + ynh_restore_file --origin_path="/etc/systemd/system/$app.service" systemctl enable "$app.service" --quiet -#================================================= -# INTEGRATE SERVICE IN YUNOHOST -#================================================= -ynh_script_progression --message="Integrating service in YunoHost..." --weight=1 +mkdir -p "/var/log/$app" + +ynh_restore_file --origin_path="/etc/logrotate.d/$app" + +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 yunohost service add "$app" --description="Gotosocial server" --log="/var/log/$app/$app.log" @@ -234,28 +67,6 @@ ynh_script_progression --message="Starting a systemd service..." --weight=1 ynh_systemd_action --service_name="$app" --action="start" --log_path="/var/log/$app/$app.log" -#================================================= -# RESTORE THE LOGROTATE CONFIGURATION -#================================================= -ynh_script_progression --message="Restoring the logrotate configuration..." --weight=1 - -ynh_restore_file --origin_path="/etc/logrotate.d/$app" - -#================================================= -# RESTORE THE FAIL2BAN CONFIGURATION -#================================================= - -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 - -#================================================= -# GENERIC FINALIZATION -#================================================= -# RELOAD NGINX -#================================================= -ynh_script_progression --message="Reloading NGINX web server..." --weight=1 - ynh_systemd_action --service_name=nginx --action=reload #================================================= diff --git a/scripts/upgrade b/scripts/upgrade index 0a8e199..978cda8 100644 --- a/scripts/upgrade +++ b/scripts/upgrade @@ -9,101 +9,12 @@ source _common.sh source /usr/share/yunohost/helpers -#================================================= -# LOAD SETTINGS -#================================================= -#REMOVEME? ynh_script_progression --message="Loading installation settings..." - -#REMOVEME? app="$YNH_APP_INSTANCE_NAME" - -#REMOVEME? #REMOVEME? install_dir=$(ynh_app_setting_get --app="$app" --key=install_dir) - -#REMOVEME? landing_page_user=$(ynh_app_setting_get --app="$app" --key=landing_page_user) - -#REMOVEME? domain=$(ynh_app_setting_get --app="$app" --key=domain) -#REMOVEME? port=$(ynh_app_setting_get --app="$app" --key=port) -#REMOVEME? path=$(ynh_app_setting_get --app="$app" --key=path) - -#REMOVEME? client_max_body_size=$(ynh_app_setting_get --app="$app" --key=client_max_body_size) - -#REMOVEME? db_name=$(ynh_app_setting_get --app="$app" --key=db_name) -#REMOVEME? db_user=$(ynh_app_setting_get --app="$app" --key=db_user) -#REMOVEME? db_pwd=$(ynh_app_setting_get --app="$app" --key=db_pwd) - -#REMOVEME? data_dir=$(ynh_app_setting_get --app="$app" --key=data_dir) - -#REMOVEME? cache_memory_target=$(ynh_app_setting_get --app="$app" --key=cache_memory_target) - -#REMOVEME? instance_expose_peers=$(ynh_app_setting_get --app="$app" --key=instance_expose_peers) -#REMOVEME? instance_expose_suspended=$(ynh_app_setting_get --app="$app" --key=instance_expose_suspended) -#REMOVEME? instance_expose_suspended_web=$(ynh_app_setting_get --app="$app" --key=instance_expose_suspended_web) -#REMOVEME? instance_expose_public_timeline=$(ynh_app_setting_get --app="$app" --key=instance_expose_public_timeline) -#REMOVEME? instance_deliver_to_shared_inboxes=$(ynh_app_setting_get --app="$app" --key=instance_deliver_to_shared_inboxes) -#REMOVEME? instance_inject_mastodon_version=$(ynh_app_setting_get --app="$app" --key=instance_inject_mastodon_version) - -#REMOVEME? accounts_registration_open=$(ynh_app_setting_get --app="$app" --key=accounts_registration_open) -#REMOVEME? accounts_approval_required=$(ynh_app_setting_get --app="$app" --key=accounts_approval_required) -#REMOVEME? accounts_reason_required=$(ynh_app_setting_get --app="$app" --key=accounts_reason_required) -#REMOVEME? accounts_allow_custom_css=$(ynh_app_setting_get --app="$app" --key=accounts_allow_custom_css) -#REMOVEME? accounts_custom_css_length=$(ynh_app_setting_get --app="$app" --key=accounts_custom_css_length) - -#REMOVEME? media_image_max_size=$(ynh_app_setting_get --app="$app" --key=media_image_max_size) -#REMOVEME? media_video_max_size=$(ynh_app_setting_get --app="$app" --key=media_video_max_size) -#REMOVEME? media_description_min_chars=$(ynh_app_setting_get --app="$app" --key=media_description_min_chars) -#REMOVEME? media_description_max_chars=$(ynh_app_setting_get --app="$app" --key=media_description_max_chars) -#REMOVEME? media_remote_cache_days=$(ynh_app_setting_get --app="$app" --key=media_remote_cache_days) -#REMOVEME? media_emoji_local_max_size=$(ynh_app_setting_get --app="$app" --key=media_emoji_local_max_size) -#REMOVEME? media_emoji_remote_max_size=$(ynh_app_setting_get --app="$app" --key=media_emoji_remote_max_size) - -#REMOVEME? storage_backend=$(ynh_app_setting_get --app="$app" --key=storage_backend) -#REMOVEME? storage_s3_endpoint=$(ynh_app_setting_get --app="$app" --key=storage_s3_endpoint) -#REMOVEME? storage_s3_proxy=$(ynh_app_setting_get --app="$app" --key=storage_s3_proxy) -#REMOVEME? storage_s3_access_key=$(ynh_app_setting_get --app="$app" --key=storage_s3_access_key) -#REMOVEME? storage_s3_secret_key=$(ynh_app_setting_get --app="$app" --key=storage_s3_secret_key) -#REMOVEME? storage_s3_bucket=$(ynh_app_setting_get --app="$app" --key=storage_s3_bucket) - -#REMOVEME? statuses_max_chars=$(ynh_app_setting_get --app="$app" --key=statuses_max_chars) -#REMOVEME? statuses_cw_max_chars=$(ynh_app_setting_get --app="$app" --key=statuses_cw_max_chars) -#REMOVEME? statuses_poll_max_options=$(ynh_app_setting_get --app="$app" --key=statuses_poll_max_options) -#REMOVEME? statuses_poll_option_max_chars=$(ynh_app_setting_get --app="$app" --key=statuses_poll_option_max_chars) -#REMOVEME? statuses_media_max_files=$(ynh_app_setting_get --app="$app" --key=statuses_media_max_files) - -#REMOVEME? smtp_host=$(ynh_app_setting_get --app="$app" --key=smtp_host) -#REMOVEME? smtp_port=$(ynh_app_setting_get --app="$app" --key=smtp_port) -#REMOVEME? smtp_username=$(ynh_app_setting_get --app="$app" --key=smtp_username) -#REMOVEME? smtp_password=$(ynh_app_setting_get --app="$app" --key=smtp_password) -#REMOVEME? smtp_from=$(ynh_app_setting_get --app="$app" --key=smtp_from) -#REMOVEME? smtp_disclose_recipients=$(ynh_app_setting_get --app="$app" --key=smtp_disclose_recipients) - -#REMOVEME? advanced_cookies_samesite=$(ynh_app_setting_get --app="$app" --key=advanced_cookies_samesite) -#REMOVEME? advanced_rate_limit_requests=$(ynh_app_setting_get --app="$app" --key=advanced_rate_limit_requests) - #================================================= # 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)..." - -# 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 #================================================= diff --git a/tests.toml b/tests.toml new file mode 100644 index 0000000..9342b8b --- /dev/null +++ b/tests.toml @@ -0,0 +1,9 @@ +test_format = 1.0 + +[default] + + # ------------------------------- + # Commits to test upgrade from + # ------------------------------- + + test_upgrade_from.9a6d018337c7d83193282830ff9d9e9b0ae3a733.name = "Upgrade from 0.6.0~ynh1"