Compare commits

..

No commits in common. "dev" and "debian/4.3.1.2" have entirely different histories.

408 changed files with 17630 additions and 39248 deletions

View file

@ -1,24 +0,0 @@
---
version: "2"
plugins:
duplication:
enabled: true
config:
languages:
python:
python_version: 3
shellcheck:
enabled: true
pep8:
enabled: true
fixme:
enabled: true
sonar-python:
enabled: true
config:
tests_patterns:
- bin/*
- data/**
- doc/*
- src/**
- tests/**

View file

@ -1,2 +1,2 @@
[report] [report]
omit=src/tests/*,src/vendor/*,/usr/lib/moulinette/yunohost/*,/usr/lib/python3/dist-packages/yunohost/tests/*,/usr/lib/python3/dist-packages/yunohost/vendor/* omit=src/yunohost/tests/*,src/yunohost/vendor/*,/usr/lib/moulinette/yunohost/*

View file

@ -1,30 +0,0 @@
name: Check / auto apply Black
on:
push:
branches: [ "dev" ]
jobs:
black:
name: Check / auto apply black
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Check files using the black formatter
uses: psf/black@stable
id: black
with:
options: "."
continue-on-error: true
- name: Create Pull Request
uses: peter-evans/create-pull-request@v6
with:
token: ${{ secrets.GITHUB_TOKEN }}
title: "Format Python code with Black"
commit-message: ":art: Format Python code with Black"
body: |
This pull request uses the [psf/black](https://github.com/psf/black) formatter.
base: ${{ github.head_ref }} # Creates pull request onto pull request or commit branch
branch: actions/black

View file

@ -1,42 +0,0 @@
name: "CodeQL"
on:
push:
branches: [ "dev" ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ "dev" ]
paths-ignore:
- 'src/tests/**'
schedule:
- cron: '43 12 * * 3'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'python' ]
steps:
- name: Checkout repository
uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
queries: security-extended,security-and-quality
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
with:
category: "/language:${{matrix.language}}"

View file

@ -1,39 +0,0 @@
# This workflow allows GitHub Actions to automagically update YunoHost NodeJS helper whenever a new release of n is detected.
name: Check for new n releases
on:
# Allow to manually trigger the workflow
workflow_dispatch:
# Run it every day at 5:00 UTC
schedule:
- cron: '0 5 * * *'
jobs:
updater:
runs-on: ubuntu-latest
steps:
- name: Fetch the source code
uses: actions/checkout@v4
- name: Run the updater script
id: run_updater
run: |
# Download n
wget https://raw.githubusercontent.com/tj/n/master/bin/n --output-document=helpers/vendor/n/n
echo "VERSION=$(sed -n 's/^VERSION=\"\(.*\)\"/\1/p' < helpers/vendor/n/n)" >> $GITHUB_ENV
- name: Create Pull Request
uses: peter-evans/create-pull-request@v6
id: cpr
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: Update n to ${{ env.VERSION }}
committer: 'yunohost-bot <yunohost-bot@users.noreply.github.com>'
author: 'yunohost-bot <yunohost-bot@users.noreply.github.com>'
signoff: false
base: dev
branch: ci-auto-update-n-${{ env.VERSION }}
delete-branch: true
title: 'Upgrade n to ${{ env.VERSION }}'
body: |
Upgrade `n` to ${{ env.VERSION }}
draft: false

11
.gitignore vendored
View file

@ -31,14 +31,7 @@ pip-log.txt
.mr.developer.cfg .mr.developer.cfg
# moulinette lib # moulinette lib
src/locales src/yunohost/locales
# Test # Test
src/tests/apps src/yunohost/tests/apps
# Tmp/local doc stuff
doc/bash-completion.sh
doc/bash_completion.d
doc/openapi.js
doc/openapi.json
doc/swagger

View file

@ -2,7 +2,7 @@
stages: stages:
- build - build
- install - install
- test - tests
- lint - lint
- doc - doc
- translation - translation
@ -13,43 +13,18 @@ default:
# All jobs are interruptible by default # All jobs are interruptible by default
interruptible: true interruptible: true
code_quality:
tags:
- docker
rules:
- if: $CI_COMMIT_TAG # Only for tags
code_quality_html:
extends: code_quality
variables:
REPORT_FORMAT: html
artifacts:
paths: [gl-code-quality-report.html]
rules:
- if: $CI_COMMIT_TAG # Only for tags
# see: https://docs.gitlab.com/ee/ci/yaml/#switch-between-branch-pipelines-and-merge-request-pipelines # see: https://docs.gitlab.com/ee/ci/yaml/#switch-between-branch-pipelines-and-merge-request-pipelines
workflow: workflow:
rules: rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event" # If we move to gitlab one day - if: $CI_PIPELINE_SOURCE == "merge_request_event" # If we move to gitlab one day
- if: $CI_PIPELINE_SOURCE == "external_pull_request_event" # For github PR - if: $CI_PIPELINE_SOURCE == "external_pull_request_event" # For github PR
- if: $CI_COMMIT_TAG # For tags - if: $CI_COMMIT_TAG # For tags
- if: $CI_COMMIT_REF_NAME == "ci-format-$CI_DEFAULT_BRANCH" # Ignore black formatting branch created by the CI
when: never
- if: $CI_COMMIT_REF_NAME == "actions/black" # Ignore black formatting branch created by the CI
when: never
- if: $CI_COMMIT_REF_NAME != $CI_DEFAULT_BRANCH && $CI_PIPELINE_SOURCE == "push" # If it's not the default branch and if it's a push, then do not trigger a build - if: $CI_COMMIT_REF_NAME != $CI_DEFAULT_BRANCH && $CI_PIPELINE_SOURCE == "push" # If it's not the default branch and if it's a push, then do not trigger a build
when: never when: never
- when: always - when: always
variables: variables:
GIT_CLONE_PATH: '$CI_BUILDS_DIR/$CI_COMMIT_SHA/$CI_JOB_ID' YNH_BUILD_DIR: "ynh-build"
YNH_SOURCE: "https://github.com/yunohost"
YNH_DEBIAN: "bullseye"
YNH_SKIP_DIAGNOSIS_DURING_UPGRADE: "true"
include: include:
- template: Code-Quality.gitlab-ci.yml
- local: .gitlab/ci/*.gitlab-ci.yml - local: .gitlab/ci/*.gitlab-ci.yml

View file

@ -1,22 +1,20 @@
.build-stage: .build-stage:
stage: build stage: build
image: "build-and-lint" image: "before-install"
variables: variables:
YNH_BUILD_DIR: "$GIT_CLONE_PATH/build" YNH_SOURCE: "https://github.com/yunohost"
before_script: before_script:
- mkdir -p $YNH_BUILD_DIR - mkdir -p $YNH_BUILD_DIR
artifacts: artifacts:
paths: paths:
- ./*.deb - $YNH_BUILD_DIR/*.deb
.build_script: &build_script .build_script: &build_script
- cd $YNH_BUILD_DIR/$PACKAGE - cd $YNH_BUILD_DIR/$PACKAGE
- VERSION=$(dpkg-parsechangelog -S Version 2>/dev/null) - VERSION=$(dpkg-parsechangelog -S Version 2>/dev/null)
- VERSION_TIMESTAMPED="${VERSION}+$(date +%Y%m%d%H%M)" - VERSION_NIGHTLY="${VERSION}+$(date +%Y%m%d%H%M)"
- dch --package "${PACKAGE}" --force-bad-version -v "${VERSION_TIMESTAMPED}" -D "unstable" --force-distribution "CI build." - dch --package "${PACKAGE}" --force-bad-version -v "${VERSION_NIGHTLY}" -D "unstable" --force-distribution "Daily build."
- debuild --no-lintian -us -uc - debuild --no-lintian -us -uc
- cp $YNH_BUILD_DIR/*.deb ${CI_PROJECT_DIR}/
- cd ${CI_PROJECT_DIR}
######################################## ########################################
# BUILD DEB # BUILD DEB
@ -31,16 +29,18 @@ build-yunohost:
- mkdir -p $YNH_BUILD_DIR/$PACKAGE - mkdir -p $YNH_BUILD_DIR/$PACKAGE
- cat archive.tar.gz | tar -xz -C $YNH_BUILD_DIR/$PACKAGE - cat archive.tar.gz | tar -xz -C $YNH_BUILD_DIR/$PACKAGE
- rm archive.tar.gz - rm archive.tar.gz
- DEBIAN_FRONTEND=noninteractive apt --assume-yes -o Dpkg::Options::="--force-confold" build-dep $YNH_BUILD_DIR/$PACKAGE - DEBIAN_FRONTEND=noninteractive apt --assume-yes -o Dpkg::Options::="--force-confold" build-dep $(pwd)/$YNH_BUILD_DIR/$PACKAGE
- *build_script - *build_script
build-ssowat: build-ssowat:
extends: .build-stage extends: .build-stage
variables: variables:
PACKAGE: "ssowat" PACKAGE: "ssowat"
script: script:
- git clone $YNH_SOURCE/$PACKAGE -b $CI_COMMIT_REF_NAME $YNH_BUILD_DIR/$PACKAGE --depth 1 || git clone $YNH_SOURCE/$PACKAGE -b $YNH_DEBIAN $YNH_BUILD_DIR/$PACKAGE --depth 1 || git clone $YNH_SOURCE/$PACKAGE $YNH_BUILD_DIR/$PACKAGE --depth 1 - DEBIAN_DEPENDS=$(cat debian/control | tr "," "\n" | grep -Po "ssowat \([>,=,<]+ .*\)" | grep -Po "[0-9\.]+")
- DEBIAN_FRONTEND=noninteractive apt --assume-yes -o Dpkg::Options::="--force-confold" build-dep $YNH_BUILD_DIR/$PACKAGE - git clone $YNH_SOURCE/$PACKAGE -b $CI_COMMIT_REF_NAME $YNH_BUILD_DIR/$PACKAGE --depth 1 || git clone $YNH_SOURCE/$PACKAGE -b $DEBIAN_DEPENDS $YNH_BUILD_DIR/$PACKAGE --depth 1 || git clone $YNH_SOURCE/$PACKAGE $YNH_BUILD_DIR/$PACKAGE --depth 1
- DEBIAN_FRONTEND=noninteractive apt --assume-yes -o Dpkg::Options::="--force-confold" build-dep $(pwd)/$YNH_BUILD_DIR/$PACKAGE
- *build_script - *build_script
build-moulinette: build-moulinette:
@ -48,6 +48,7 @@ build-moulinette:
variables: variables:
PACKAGE: "moulinette" PACKAGE: "moulinette"
script: script:
- git clone $YNH_SOURCE/$PACKAGE -b $CI_COMMIT_REF_NAME $YNH_BUILD_DIR/$PACKAGE --depth 1 || git clone $YNH_SOURCE/$PACKAGE -b $YNH_DEBIAN $YNH_BUILD_DIR/$PACKAGE --depth 1 || git clone $YNH_SOURCE/$PACKAGE $YNH_BUILD_DIR/$PACKAGE --depth 1 - DEBIAN_DEPENDS=$(cat debian/control | tr "," "\n" | grep -Po "moulinette \([>,=,<]+ .*\)" | grep -Po "[0-9\.]+")
- DEBIAN_FRONTEND=noninteractive apt --assume-yes -o Dpkg::Options::="--force-confold" build-dep $YNH_BUILD_DIR/$PACKAGE - git clone $YNH_SOURCE/$PACKAGE -b $CI_COMMIT_REF_NAME $YNH_BUILD_DIR/$PACKAGE --depth 1 || git clone $YNH_SOURCE/$PACKAGE -b $DEBIAN_DEPENDS $YNH_BUILD_DIR/$PACKAGE --depth 1 || git clone $YNH_SOURCE/$PACKAGE $YNH_BUILD_DIR/$PACKAGE --depth 1
- DEBIAN_FRONTEND=noninteractive apt --assume-yes -o Dpkg::Options::="--force-confold" build-dep $(pwd)/$YNH_BUILD_DIR/$PACKAGE
- *build_script - *build_script

View file

@ -4,28 +4,24 @@
generate-helpers-doc: generate-helpers-doc:
stage: doc stage: doc
image: "build-and-lint" image: "before-install"
needs: [] needs: []
before_script: before_script:
- apt-get update -y && apt-get install git hub -y
- git config --global user.email "yunohost@yunohost.org" - git config --global user.email "yunohost@yunohost.org"
- git config --global user.name "$GITHUB_USER" - git config --global user.name "$GITHUB_USER"
script: script:
- cd doc - cd doc
- python3 generate_helper_doc.py 2 - python3 generate_helper_doc.py
- python3 generate_helper_doc.py 2.1
- python3 generate_resource_doc.py > resources.md
- hub clone https://$GITHUB_TOKEN:x-oauth-basic@github.com/YunoHost/doc.git doc_repo - hub clone https://$GITHUB_TOKEN:x-oauth-basic@github.com/YunoHost/doc.git doc_repo
- cp helpers.v2.md doc_repo/pages/06.contribute/10.packaging_apps/20.scripts/10.helpers/packaging_app_scripts_helpers.md - cp helpers.md doc_repo/pages/04.contribute/04.packaging_apps/11.helpers/packaging_apps_helpers.md
- cp helpers.v2.1.md doc_repo/pages/06.contribute/10.packaging_apps/20.scripts/12.helpers21/packaging_app_scripts_helpers_v21.md
- cp resources.md doc_repo/pages/06.contribute/10.packaging_apps/10.manifest/10.appresources/packaging_app_manifest_resources.md
- cd doc_repo - cd doc_repo
# replace ${CI_COMMIT_REF_NAME} with ${CI_COMMIT_TAG} ? # replace ${CI_COMMIT_REF_NAME} with ${CI_COMMIT_TAG} ?
- hub checkout -b "${CI_COMMIT_REF_NAME}" - hub checkout -b "${CI_COMMIT_REF_NAME}"
- hub commit -am "[CI] Update app helpers/resources for ${CI_COMMIT_REF_NAME}" - hub commit -am "[CI] Helper for ${CI_COMMIT_REF_NAME}"
- hub pull-request -m "[CI] Update app helpers/resources for ${CI_COMMIT_REF_NAME}" -p # GITHUB_USER and GITHUB_TOKEN registered here https://gitlab.com/yunohost/yunohost/-/settings/ci_cd - hub pull-request -m "[CI] Helper for ${CI_COMMIT_REF_NAME}" -p # GITHUB_USER and GITHUB_TOKEN registered here https://gitlab.com/yunohost/yunohost/-/settings/ci_cd
artifacts: artifacts:
paths: paths:
- doc/helpers.md - doc/helpers.md
- doc/resources.md
only: only:
- tags - tags

View file

@ -14,14 +14,16 @@
upgrade: upgrade:
extends: .install-stage extends: .install-stage
image: "core-tests" image: "after-install"
script: script:
- DEBIAN_FRONTEND=noninteractive SUDO_FORCE_REMOVE=yes apt --assume-yes -o Dpkg::Options::="--force-confold" --allow-downgrades install ${CI_PROJECT_DIR}/*.deb - apt-get update -o Acquire::Retries=3
- DEBIAN_FRONTEND=noninteractive SUDO_FORCE_REMOVE=yes apt --assume-yes -o Dpkg::Options::="--force-confold" --allow-downgrades install ./$YNH_BUILD_DIR/*.deb
install-postinstall: install-postinstall:
extends: .install-stage extends: .install-stage
image: "before-install" image: "before-install"
script: script:
- DEBIAN_FRONTEND=noninteractive SUDO_FORCE_REMOVE=yes apt --assume-yes -o Dpkg::Options::="--force-confold" --allow-downgrades install ${CI_PROJECT_DIR}/*.deb - apt-get update -o Acquire::Retries=3
- yunohost tools postinstall -d domain.tld -u syssa -F 'Syssa Mine' -p the_password --ignore-dyndns --force-diskspace - DEBIAN_FRONTEND=noninteractive SUDO_FORCE_REMOVE=yes apt --assume-yes -o Dpkg::Options::="--force-confold" --allow-downgrades install ./$YNH_BUILD_DIR/*.deb
- yunohost tools postinstall -d domain.tld -p the_password --ignore-dyndns --force-diskspace

View file

@ -3,24 +3,55 @@
######################################## ########################################
# later we must fix lint and format-check jobs and remove "allow_failure" # later we must fix lint and format-check jobs and remove "allow_failure"
lint39: ---
lint37:
stage: lint stage: lint
image: "build-and-lint" image: "before-install"
needs: [] needs: []
allow_failure: true allow_failure: true
script: script:
- tox -e py39-lint - tox -e py37-lint
invalidcode39: invalidcode37:
stage: lint stage: lint
image: "build-and-lint" image: "before-install"
needs: [] needs: []
script: script:
- tox -e py39-invalidcode - tox -e py37-invalidcode
mypy: mypy:
stage: lint stage: lint
image: "build-and-lint" image: "before-install"
needs: [] needs: []
script: script:
- tox -e py39-mypy - tox -e py37-mypy
format-check:
stage: lint
image: "before-install"
allow_failure: true
needs: []
script:
- tox -e py37-black-check
format-run:
stage: lint
image: "before-install"
needs: []
before_script:
- apt-get update -y && apt-get install git hub -y
- git config --global user.email "yunohost@yunohost.org"
- git config --global user.name "$GITHUB_USER"
- hub clone --branch ${CI_COMMIT_REF_NAME} "https://$GITHUB_TOKEN:x-oauth-basic@github.com/YunoHost/yunohost.git" github_repo
- cd github_repo
script:
# create a local branch that will overwrite distant one
- git checkout -b "ci-format-${CI_COMMIT_REF_NAME}" --no-track
- tox -e py37-black-run
- '[ $(git diff | wc -l) != 0 ] || exit 0' # stop if there is nothing to commit
- git commit -am "[CI] Format code" || true
- git push -f origin "ci-format-${CI_COMMIT_REF_NAME}":"ci-format-${CI_COMMIT_REF_NAME}"
- hub pull-request -m "[CI] Format code" -b Yunohost:dev -p || true # GITHUB_USER and GITHUB_TOKEN registered here https://gitlab.com/yunohost/yunohost/-/settings/ci_cd
only:
refs:
- dev

View file

@ -1,16 +1,17 @@
.install_debs: &install_debs .install_debs: &install_debs
- DEBIAN_FRONTEND=noninteractive SUDO_FORCE_REMOVE=yes apt --assume-yes -o Dpkg::Options::="--force-confold" --allow-downgrades install ${CI_PROJECT_DIR}/*.deb - apt-get update -o Acquire::Retries=3
- DEBIAN_FRONTEND=noninteractive SUDO_FORCE_REMOVE=yes apt --assume-yes -o Dpkg::Options::="--force-confold" --allow-downgrades install ./$YNH_BUILD_DIR/*.deb
.test-stage: .test-stage:
stage: test stage: tests
image: "core-tests" image: "after-install"
variables: variables:
PYTEST_ADDOPTS: "--color=yes" PYTEST_ADDOPTS: "--color=yes"
before_script: before_script:
- *install_debs - *install_debs
cache: cache:
paths: paths:
- src/tests/apps - src/yunohost/tests/apps
key: "$CI_JOB_STAGE-$CI_COMMIT_REF_SLUG" key: "$CI_JOB_STAGE-$CI_COMMIT_REF_SLUG"
needs: needs:
- job: build-yunohost - job: build-yunohost
@ -21,21 +22,23 @@
artifacts: true artifacts: true
- job: upgrade - job: upgrade
######################################## ########################################
# TESTS # TESTS
######################################## ########################################
full-tests: full-tests:
stage: test stage: tests
image: "before-install" image: "before-install"
variables: variables:
PYTEST_ADDOPTS: "--color=yes" PYTEST_ADDOPTS: "--color=yes"
before_script: before_script:
- *install_debs - *install_debs
- pip install mock pip pyOpenSSL pytest pytest-cov pytest-mock pytest-sugar requests-mock "packaging<22" - yunohost tools postinstall -d domain.tld -p the_password --ignore-dyndns --force-diskspace
- yunohost tools postinstall -d domain.tld -u syssa -F 'Syssa Mine' -p the_password --ignore-dyndns --force-diskspace
script: script:
- python3 -m pytest --cov=yunohost tests/ src/tests/ --junitxml=report.xml - python3 -m pytest --cov=yunohost tests/ src/yunohost/tests/ data/hooks/diagnosis/ --junitxml=report.xml
- cd tests
- bash test_helpers.sh
needs: needs:
- job: build-yunohost - job: build-yunohost
artifacts: true artifacts: true
@ -43,158 +46,163 @@ full-tests:
artifacts: true artifacts: true
- job: build-moulinette - job: build-moulinette
artifacts: true artifacts: true
coverage: '/TOTAL.*\s+(\d+%)/'
artifacts: artifacts:
reports: reports:
junit: report.xml junit: report.xml
test-i18n-keys:
extends: .test-stage
script:
- python3 -m pytest tests/test_i18n_keys.py
only:
changes:
- locales/en.json
- src/yunohost/*.py
- data/hooks/diagnosis/*.py
test-translation-format-consistency:
extends: .test-stage
script:
- python3 -m pytest tests/test_translation_format_consistency.py
only:
changes:
- locales/*
test-actionmap: test-actionmap:
extends: .test-stage extends: .test-stage
script: script:
- python3 -m pytest tests/test_actionmap.py - python3 -m pytest tests/test_actionmap.py
only: only:
changes: changes:
- share/actionsmap.yml - data/actionsmap/*.yml
test-helpers2: test-helpers:
extends: .test-stage extends: .test-stage
script: script:
- cd tests - cd tests
- bash test_helpers.sh - bash test_helpers.sh
only:
test-helpers2.1: changes:
extends: .test-stage - data/helpers.d/*
script:
- cd tests
- bash test_helpers.sh 2.1
test-domains: test-domains:
extends: .test-stage extends: .test-stage
script: script:
- python3 -m pytest src/tests/test_domains.py - python3 -m pytest src/yunohost/tests/test_domains.py
only: only:
changes: changes:
- src/domain.py - src/yunohost/domain.py
test-dns: test-dns:
extends: .test-stage extends: .test-stage
script: script:
- python3 -m pytest src/tests/test_dns.py - python3 -m pytest src/yunohost/tests/test_dns.py
only: only:
changes: changes:
- src/dns.py - src/yunohost/dns.py
- src/utils/dns.py - src/yunohost/utils/dns.py
test-apps: test-apps:
extends: .test-stage extends: .test-stage
script: script:
- python3 -m pytest src/tests/test_apps.py - python3 -m pytest src/yunohost/tests/test_apps.py
only: only:
changes: changes:
- src/app.py - src/yunohost/app.py
test-appscatalog: test-appscatalog:
extends: .test-stage extends: .test-stage
script: script:
- python3 -m pytest src/tests/test_app_catalog.py - python3 -m pytest src/yunohost/tests/test_app_catalog.py
only: only:
changes: changes:
- src/app_calalog.py - src/yunohost/app_calalog.py
test-appurl: test-appurl:
extends: .test-stage extends: .test-stage
script: script:
- python3 -m pytest src/tests/test_appurl.py - python3 -m pytest src/yunohost/tests/test_appurl.py
only: only:
changes: changes:
- src/app.py - src/yunohost/app.py
test-questions: test-questions:
extends: .test-stage extends: .test-stage
script: script:
- python3 -m pytest src/tests/test_questions.py - python3 -m pytest src/yunohost/tests/test_questions.py
only: only:
changes: changes:
- src/utils/config.py - src/yunohost/utils/config.py
test-app-config: test-app-config:
extends: .test-stage extends: .test-stage
script: script:
- python3 -m pytest src/tests/test_app_config.py - python3 -m pytest src/yunohost/tests/test_app_config.py
only: only:
changes: changes:
- src/app.py - src/yunohost/app.py
- src/utils/config.py - src/yunohost/utils/config.py
test-app-resources:
extends: .test-stage
script:
- python3 -m pytest src/tests/test_app_resources.py
only:
changes:
- src/app.py
- src/utils/resources.py
test-changeurl: test-changeurl:
extends: .test-stage extends: .test-stage
script: script:
- python3 -m pytest src/tests/test_changeurl.py - python3 -m pytest src/yunohost/tests/test_changeurl.py
only: only:
changes: changes:
- src/app.py - src/yunohost/app.py
test-backuprestore: test-backuprestore:
extends: .test-stage extends: .test-stage
script: script:
- python3 -m pytest src/tests/test_backuprestore.py - python3 -m pytest src/yunohost/tests/test_backuprestore.py
only: only:
changes: changes:
- src/backup.py - src/yunohost/backup.py
test-permission: test-permission:
extends: .test-stage extends: .test-stage
script: script:
- python3 -m pytest src/tests/test_permission.py - python3 -m pytest src/yunohost/tests/test_permission.py
only: only:
changes: changes:
- src/permission.py - src/yunohost/permission.py
test-settings: test-settings:
extends: .test-stage extends: .test-stage
script: script:
- python3 -m pytest src/tests/test_settings.py - python3 -m pytest src/yunohost/tests/test_settings.py
only: only:
changes: changes:
- src/settings.py - src/yunohost/settings.py
test-user-group: test-user-group:
extends: .test-stage extends: .test-stage
script: script:
- python3 -m pytest src/tests/test_user-group.py - python3 -m pytest src/yunohost/tests/test_user-group.py
only: only:
changes: changes:
- src/user.py - src/yunohost/user.py
test-regenconf: test-regenconf:
extends: .test-stage extends: .test-stage
script: script:
- python3 -m pytest src/tests/test_regenconf.py - python3 -m pytest src/yunohost/tests/test_regenconf.py
only: only:
changes: changes:
- src/regenconf.py - src/yunohost/regenconf.py
test-service: test-service:
extends: .test-stage extends: .test-stage
script: script:
- python3 -m pytest src/tests/test_service.py - python3 -m pytest src/yunohost/tests/test_service.py
only: only:
changes: changes:
- src/service.py - src/yunohost/service.py
test-ldapauth: test-ldapauth:
extends: .test-stage extends: .test-stage
script: script:
- python3 -m pytest src/tests/test_ldapauth.py - python3 -m pytest src/yunohost/tests/test_ldapauth.py
only: only:
changes: changes:
- src/authenticators/*.py - src/yunohost/authenticators/*.py

View file

@ -1,34 +1,27 @@
######################################## ########################################
# TRANSLATION # TRANSLATION
######################################## ########################################
test-i18n-keys:
stage: translation
script:
- python3 maintenance/missing_i18n_keys.py --check
only:
changes:
- locales/en.json
- src/*.py
- src/diagnosers/*.py
autofix-translated-strings: autofix-translated-strings:
stage: translation stage: translation
image: "build-and-lint" image: "before-install"
needs: [] needs: []
before_script: before_script:
- apt-get update -y && apt-get install git hub -y
- git config --global user.email "yunohost@yunohost.org" - git config --global user.email "yunohost@yunohost.org"
- git config --global user.name "$GITHUB_USER" - git config --global user.name "$GITHUB_USER"
- hub clone --branch ${CI_COMMIT_REF_NAME} "https://$GITHUB_TOKEN:x-oauth-basic@github.com/YunoHost/yunohost.git" github_repo - git remote set-url origin https://$GITHUB_TOKEN:x-oauth-basic@github.com/YunoHost/yunohost.git
- cd github_repo
script: script:
- cd tests # Maybe move this script location to another folder?
# create a local branch that will overwrite distant one # create a local branch that will overwrite distant one
- git checkout -b "ci-autofix-translated-strings-${CI_COMMIT_REF_NAME}" --no-track - git checkout -b "ci-autofix-translated-strings-${CI_COMMIT_REF_NAME}" --no-track
- python3 maintenance/missing_i18n_keys.py --fix - python3 remove_stale_translated_strings.py
- python3 maintenance/autofix_locale_format.py - python3 autofix_locale_format.py
- '[ $(git diff --ignore-blank-lines --ignore-all-space --ignore-space-at-eol --ignore-cr-at-eol | wc -l) != 0 ] || exit 0' # stop if there is nothing to commit - python3 reformat_locales.py
- '[ $(git diff -w | wc -l) != 0 ] || exit 0' # stop if there is nothing to commit
- git commit -am "[CI] Reformat / remove stale translated strings" || true - git commit -am "[CI] Reformat / remove stale translated strings" || true
- git push -f origin "ci-autofix-translated-strings-${CI_COMMIT_REF_NAME}":"ci-remove-stale-translated-strings-${CI_COMMIT_REF_NAME}" - git push -f origin "HEAD":"ci-remove-stale-translated-strings-${CI_COMMIT_REF_NAME}"
- hub pull-request -m "[CI] Reformat / remove stale translated strings" -b Yunohost:$CI_COMMIT_REF_NAME -p || true # GITHUB_USER and GITHUB_TOKEN registered here https://gitlab.com/yunohost/yunohost/-/settings/ci_cd - hub pull-request -m "[CI] Reformat / remove stale translated strings" -b Yunohost:dev -p || true # GITHUB_USER and GITHUB_TOKEN registered here https://gitlab.com/yunohost/yunohost/-/settings/ci_cd
only: only:
variables: variables:
- $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH - $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH

101
CONTRIBUTORS.md Normal file
View file

@ -0,0 +1,101 @@
YunoHost core contributors
==========================
YunoHost is built and maintained by the YunoHost project community.
Everyone is encouraged to submit issues and changes, and to contribute in other ways -- see https://yunohost.org/contribute to find out how.
--
Initial YunoHost core was built by Kload & beudbeud, for YunoHost v2.
Most of code was written by Kload and jerome, with help of numerous contributors.
Translation is made by a bunch of lovely people all over the world.
We would like to thank anyone who ever helped the YunoHost project <3
YunoHost core Contributors
--------------------------
- Jérôme Lebleu
- Kload
- Laurent 'Bram' Peuch
- Julien 'ju' Malik
- opi
- Aleks
- Adrien 'beudbeud' Beudin
- M5oul
- Valentin 'zamentur' / 'ljf' Grimaud
- Jocelyn Delalande
- infertux
- Taziden
- ZeHiro
- Josue-T
- nahoj
- a1ex
- JimboJoe
- vetetix
- jellium
- Sebastien 'sebian' Badia
- lmangani
- Julien Vaubourg
- thardev
- zimo2001
YunoHost core Translators
-------------------------
If you want to help translation, please visit https://translate.yunohost.org/projects/yunohost/yunohost/
### Dutch
- DUBWiSE
- Jeroen Keerl
- marut
### English
- Bugsbane
- rokaz
### French
- aoz roon
- Genma
- Jean-Baptiste Holcroft
- Jean P.
- Jérôme Lebleu
- Lapineige
- paddy
### German
- david.bartke
- Fabian Gruber
- Felix Bartels
- Jeroen Keerl
- martin kistner
- Philip Gatzka
### Hindi
- Anmol
### Italian
- bricabrac
- Thomas Bille
### Portuguese
- Deleted User
- Trollken
### Spanish
- Juanu

View file

@ -5,12 +5,11 @@
<h1 align="center">YunoHost</h1> <h1 align="center">YunoHost</h1>
<div align="center"> <div align="center">
![Version](https://img.shields.io/github/v/tag/yunohost/yunohost?label=version&sort=semver) ![Version](https://img.shields.io/github/v/tag/yunohost/yunohost?label=version&sort=semver)
[![Pipeline status](https://gitlab.com/yunohost/yunohost/badges/dev/pipeline.svg)](https://gitlab.com/yunohost/yunohost/-/pipelines) [![Build status](https://shields.io/gitlab/pipeline/yunohost/yunohost/dev)](https://gitlab.com/yunohost/yunohost/-/pipelines)
![Test coverage](https://gitlab.com/yunohost/yunohost/badges/dev/coverage.svg) ![Test coverage](https://img.shields.io/gitlab/coverage/yunohost/yunohost/dev)
[![Project license](https://img.shields.io/gitlab/license/yunohost/yunohost)](https://github.com/YunoHost/yunohost/blob/dev/LICENSE) [![GitHub license](https://img.shields.io/github/license/YunoHost/yunohost)](https://github.com/YunoHost/yunohost/blob/dev/LICENSE)
[![CodeQL](https://github.com/yunohost/yunohost/workflows/CodeQL/badge.svg)](https://github.com/YunoHost/yunohost/security/code-scanning)
[![Mastodon Follow](https://img.shields.io/mastodon/follow/28084)](https://mastodon.social/@yunohost) [![Mastodon Follow](https://img.shields.io/mastodon/follow/28084)](https://mastodon.social/@yunohost)
</div> </div>
@ -19,49 +18,28 @@ YunoHost is an operating system aiming to simplify as much as possible the admin
This repository corresponds to the core code of YunoHost, mainly written in Python and Bash. This repository corresponds to the core code of YunoHost, mainly written in Python and Bash.
- [Project features](https://yunohost.org/whatsyunohost) - [Project features](https://yunohost.org/#/whatsyunohost)
- [Project website](https://yunohost.org) - [Project website](https://yunohost.org)
- [Install documentation](https://yunohost.org/install) - [Install documentation](https://yunohost.org/install)
- [Issue tracker](https://github.com/YunoHost/issues) - [Issue tracker](https://github.com/YunoHost/issues)
## Screenshots # Screenshots
Webadmin ([Yunohost-Admin](https://github.com/YunoHost/yunohost-admin)) | Single sign-on user portal ([SSOwat](https://github.com/YunoHost/ssowat)) Webadmin ([Yunohost-Admin](https://github.com/YunoHost/yunohost-admin)) | Single sign-on user portal ([SSOwat](https://github.com/YunoHost/ssowat))
--- | --- --- | ---
![Web admin insterface screenshot](https://raw.githubusercontent.com/YunoHost/doc/master/images/webadmin.png) | ![User portal screenshot](https://raw.githubusercontent.com/YunoHost/doc/master/images/user_panel.png) ![](https://raw.githubusercontent.com/YunoHost/doc/master/images/webadmin.png) | ![](https://raw.githubusercontent.com/YunoHost/doc/master/images/user_panel.png)
## Contributing ## Contributing
- You can learn how to get started with developing on YunoHost by reading [this piece of documentation](https://yunohost.org/dev). - You can learn how to get started with developing on YunoHost by reading [this piece of documentation](https://yunohost.org/dev).
- Come chat with us on the [dev chatroom](https://yunohost.org/chat_rooms)! - Come chat with us on the [dev chatroom](https://yunohost.org/#/chat_rooms) !
- You can help translate YunoHost on our [translation platform](https://translate.yunohost.org/engage/yunohost/?utm_source=widget). - You can help translate YunoHost on our [translation platform](https://translate.yunohost.org/engage/yunohost/?utm_source=widget)
<p align="center"> <p align="center">
<img alt="View of the translation rate for the different languages available in YunoHost" src="https://translate.yunohost.org/widgets/yunohost/-/core/horizontal-auto.svg" alt="Translation status" /> <img src="https://translate.yunohost.org/widgets/yunohost/-/core/horizontal-auto.svg" alt="Translation status" />
</p> </p>
## License ## License
As [other components of YunoHost](https://yunohost.org/faq), this repository is licensed under GNU AGPL v3. As [other components of YunoHost](https://yunohost.org/#/faq_en), this repository is licensed under GNU AGPL v3.
## They support us <3
We are thankful for our sponsors providing us with infrastructure and grants!
<div align="center">
<p style="margin-left:auto;margin-right:auto;">
<a style="padding: 5px;" href="https://nlnet.nl"><img alt="NLnet Foundation" src="https://user-images.githubusercontent.com/36127788/198088570-823c40bd-7ac3-44e3-a8ee-e7a9f14b47ac.png" width="150px"/></a>
<a style="padding: 5px;" href="https://www.ngi.eu"><img alt="Next Generation Internet" src="https://user-images.githubusercontent.com/36127788/198088663-daf587b9-fd09-4c00-aaf2-37c803939c94.png" width="130px"/></a>
<a style="padding: 5px;" href="https://www.codelutin.com"><img alt="Code Lutin" src="https://user-images.githubusercontent.com/36127788/198088737-d37b6674-379c-4be4-9d74-b93b6ad318d1.png" width="100px"/></a>
</p>
<p style="margin-left:auto;margin-right:auto;">
<a style="padding: 5px;" href="https://www.globenet.org"><img alt="Globenet" src="https://user-images.githubusercontent.com/36127788/198088794-751129ab-737d-4d99-9f35-5e01845dcdfe.png" width="150px"/></a>
<a style="padding: 5px;" href="https://www.gitoyen.net"><img alt="Gitoyen" src="https://user-images.githubusercontent.com/36127788/198088931-f16f4af4-57ae-42e9-8d42-fb3e2d8d7ee3.png" width="150px"/></a>
<a style="padding: 5px;" href="https://tetaneutral.net"><img alt="tetaneutral.net" src="https://user-images.githubusercontent.com/36127788/198088995-3ad9c34d-9807-4ead-934b-44df97d3c552.png" width="90px"/></a>
<a style="padding: 5px;" href="https://ldn-fai.net"><img alt="LDN (Lorraine Data Network)" src="https://user-images.githubusercontent.com/36127788/198089086-a4089d51-9173-4081-bd2e-fa1ac3378e49.png" width="120px"/></a>
<a style="padding: 5px;" href="https://www.nbs-system.com"><img alt="NBS System" src="https://user-images.githubusercontent.com/36127788/198089161-4cc0b7b7-bf56-4798-892e-a76112497921.png" width="130px"/></a>
</p>
</div>
This project was funded through the [NGI0 PET](https://nlnet.nl/PET) Fund, a fund established by NLnet with financial support from the European Commission's [Next Generation Internet](https://ngi.eu/) programme, under the aegis of DG Communications Networks, Content and Technology under grant agreement No 825310. If you're interested, [check out how to apply in this video](https://media.ccc.de/v/36c3-10795-ngi_zero_a_treasure_trove_of_it_innovation)!

View file

@ -1,78 +1,67 @@
#! /usr/bin/python3 #! /usr/bin/python3
# -*- coding: utf-8 -*-
import os import os
import sys import sys
import argparse import argparse
sys.path.insert(0, "/usr/lib/moulinette/")
import yunohost import yunohost
def _parse_cli_args(): def _parse_cli_args():
"""Parse additional arguments for the cli""" """Parse additional arguments for the cli"""
parser = argparse.ArgumentParser(add_help=False) parser = argparse.ArgumentParser(add_help=False)
parser.add_argument( parser.add_argument('--output-as',
"--output-as", choices=['json', 'plain', 'none'], default=None,
choices=["json", "plain", "none"], help="Output result in another format"
default=None,
help="Output result in another format",
) )
parser.add_argument( parser.add_argument('--debug',
"--debug", action='store_true', default=False,
action="store_true", help="Log and print debug messages"
default=False,
help="Log and print debug messages",
) )
parser.add_argument( parser.add_argument('--quiet',
"--quiet", action="store_true", default=False, help="Don't produce any output" action='store_true', default=False,
help="Don't produce any output"
) )
parser.add_argument( parser.add_argument('--timeout',
"--version", action="store_true", default=False, help="Display YunoHost packages versions (alias to 'yunohost tools versions')" type=int, default=None,
) help="Number of seconds before this command will timeout because it can't acquire the lock (meaning that another command is currently running), by default there is no timeout and the command will wait until it can get the lock"
parser.add_argument(
"--timeout",
type=int,
default=None,
help="Number of seconds before this command will timeout because it can't acquire the lock (meaning that another command is currently running), by default there is no timeout and the command will wait until it can get the lock",
) )
# deprecated arguments # deprecated arguments
parser.add_argument( parser.add_argument('--plain',
"--plain", action="store_true", default=False, help=argparse.SUPPRESS action='store_true', default=False, help=argparse.SUPPRESS
) )
parser.add_argument( parser.add_argument('--json',
"--json", action="store_true", default=False, help=argparse.SUPPRESS action='store_true', default=False, help=argparse.SUPPRESS
) )
opts, args = parser.parse_known_args() opts, args = parser.parse_known_args()
# output compatibility # output compatibility
if opts.plain: if opts.plain:
opts.output_as = "plain" opts.output_as = 'plain'
elif opts.json: elif opts.json:
opts.output_as = "json" opts.output_as = 'json'
return (parser, opts, args) return (parser, opts, args)
# Stupid PATH management because sometimes (e.g. some cron job) PATH is only /usr/bin:/bin ... # Stupid PATH management because sometimes (e.g. some cron job) PATH is only /usr/bin:/bin ...
default_path = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" default_path = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
if os.environ["PATH"] != default_path: if os.environ["PATH"] != default_path:
os.environ["PATH"] = default_path + ":" + os.environ["PATH"] os.environ["PATH"] = default_path + ":" + os.environ["PATH"]
# Main action ---------------------------------------------------------- # Main action ----------------------------------------------------------
if __name__ == "__main__": if __name__ == '__main__':
if os.geteuid() != 0: if os.geteuid() != 0:
sys.stderr.write( sys.stderr.write("\033[1;31mError:\033[0m yunohost command must be "
"\033[1;31mError:\033[0m yunohost command must be " "run as root or with sudo.\n")
"run as root or with sudo.\n"
)
sys.exit(1) sys.exit(1)
parser, opts, args = _parse_cli_args() parser, opts, args = _parse_cli_args()
if opts.version:
args = ["tools", "versions"]
# Execute the action # Execute the action
yunohost.cli( yunohost.cli(
debug=opts.debug, debug=opts.debug,
@ -80,5 +69,5 @@ if __name__ == "__main__":
output_as=opts.output_as, output_as=opts.output_as,
timeout=opts.timeout, timeout=opts.timeout,
args=args, args=args,
parser=parser, parser=parser
) )

View file

@ -1,52 +1,44 @@
#! /usr/bin/python3 #! /usr/bin/python3
# -*- coding: utf-8 -*-
import sys
import argparse import argparse
sys.path.insert(0, "/usr/lib/moulinette/")
import yunohost import yunohost
# Default server configuration # Default server configuration
DEFAULT_HOST = "localhost" DEFAULT_HOST = 'localhost'
DEFAULT_PORT = 6787 DEFAULT_PORT = 6787
def _parse_api_args(): def _parse_api_args():
"""Parse main arguments for the api""" """Parse main arguments for the api"""
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(add_help=False,
add_help=False,
description="Run the YunoHost API to manage your server.", description="Run the YunoHost API to manage your server.",
) )
srv_group = parser.add_argument_group("server configuration") srv_group = parser.add_argument_group('server configuration')
srv_group.add_argument( srv_group.add_argument('-h', '--host',
"-h", action='store', default=DEFAULT_HOST,
"--host",
action="store",
default=DEFAULT_HOST,
help="Host to listen on (default: %s)" % DEFAULT_HOST, help="Host to listen on (default: %s)" % DEFAULT_HOST,
) )
srv_group.add_argument( srv_group.add_argument('-p', '--port',
"-p", action='store', default=DEFAULT_PORT, type=int,
"--port",
action="store",
default=DEFAULT_PORT,
type=int,
help="Port to listen on (default: %d)" % DEFAULT_PORT, help="Port to listen on (default: %d)" % DEFAULT_PORT,
) )
glob_group = parser.add_argument_group("global arguments") glob_group = parser.add_argument_group('global arguments')
glob_group.add_argument( glob_group.add_argument('--debug',
"--debug", action='store_true', default=False,
action="store_true",
default=False,
help="Set log level to DEBUG", help="Set log level to DEBUG",
) )
glob_group.add_argument( glob_group.add_argument('--help',
"--help", action='help', help="Show this help message and exit",
action="help",
help="Show this help message and exit",
) )
return parser.parse_args() return parser.parse_args()
if __name__ == "__main__": if __name__ == '__main__':
opts = _parse_api_args() opts = _parse_api_args()
# Run the server # Run the server
yunohost.api(debug=opts.debug, host=opts.host, port=opts.port) yunohost.api(debug=opts.debug, host=opts.host, port=opts.port)

View file

@ -21,20 +21,8 @@ def get_network_local_interfaces() -> Dict[str, Dict[str, List[str]]]:
interfaces = { interfaces = {
adapter.name: { adapter.name: {
"ipv4": [ "ipv4": [ip.ip for ip in adapter.ips if ip.is_IPv4 and ip_address(ip.ip).is_private],
ip.ip "ipv6": [ip.ip[0] for ip in adapter.ips if ip.is_IPv6 and ip_address(ip.ip[0]).is_private and not ip_address(ip.ip[0]).is_link_local],
for ip in adapter.ips
if ip.is_IPv4
and ip_address(ip.ip).is_private
and not ip_address(ip.ip).is_link_local
],
"ipv6": [
ip.ip[0]
for ip in adapter.ips
if ip.is_IPv6
and ip_address(ip.ip[0]).is_private
and not ip_address(ip.ip[0]).is_link_local
],
} }
for adapter in ifaddr.get_adapters() for adapter in ifaddr.get_adapters()
if adapter.name != "lo" if adapter.name != "lo"
@ -45,6 +33,7 @@ def get_network_local_interfaces() -> Dict[str, Dict[str, List[str]]]:
# Listener class, to detect duplicates on the network # Listener class, to detect duplicates on the network
# Stores the list of servers in its list property # Stores the list of servers in its list property
class Listener: class Listener:
def __init__(self): def __init__(self):
self.list = [] self.list = []
@ -77,18 +66,14 @@ def main() -> bool:
return False return False
if "interfaces" not in config: if "interfaces" not in config:
config["interfaces"] = [ config["interfaces"] = [interface
interface for interface, local_ips in interfaces.items()
for interface, local_ips in interfaces.items() if local_ips["ipv4"]]
if local_ips["ipv4"]
]
if "ban_interfaces" in config: if "ban_interfaces" in config:
config["interfaces"] = [ config["interfaces"] = [interface
interface for interface in config["interfaces"]
for interface in config["interfaces"] if interface not in config["ban_interfaces"]]
if interface not in config["ban_interfaces"]
]
# Let's discover currently published .local domains accross the network # Let's discover currently published .local domains accross the network
zc = Zeroconf() zc = Zeroconf()
@ -118,18 +103,14 @@ def main() -> bool:
return domain_i return domain_i
config["domains"] = [ config['domains'] = [find_domain_not_already_published(domain) for domain in config['domains']]
find_domain_not_already_published(domain) for domain in config["domains"]
]
zcs: Dict[Zeroconf, List[ServiceInfo]] = {} zcs: Dict[Zeroconf, List[ServiceInfo]] = {}
for interface in config["interfaces"]: for interface in config["interfaces"]:
if interface not in interfaces: if interface not in interfaces:
print( print(f"Interface {interface} listed in config file is not present on system.")
f"Interface {interface} listed in config file is not present on system."
)
continue continue
# Only broadcast IPv4 because IPv6 is buggy ... because we ain't using python3-ifaddr >= 0.1.7 # Only broadcast IPv4 because IPv6 is buggy ... because we ain't using python3-ifaddr >= 0.1.7
@ -168,9 +149,7 @@ def main() -> bool:
print("Registering...") print("Registering...")
for zc, infos in zcs.items(): for zc, infos in zcs.items():
for info in infos: for info in infos:
zc.register_service( zc.register_service(info, allow_name_change=True, cooperating_responders=True)
info, allow_name_change=True, cooperating_responders=True
)
try: try:
print("Registered. Press Ctrl+C or stop service to stop.") print("Registered. Press Ctrl+C or stop service to stop.")

View file

@ -1,34 +1,77 @@
#!/usr/bin/env python3 #!/bin/bash
import sys set -e
import requests set -u
import json
SERVER_URL = "https://paste.yunohost.org" PASTE_URL="https://paste.yunohost.org"
TIMEOUT = 3
def create_snippet(data): _die() {
try: printf "Error: %s\n" "$*"
url = SERVER_URL + "/documents" exit 1
response = requests.post(url, data=data.encode('utf-8'), timeout=TIMEOUT) }
response.raise_for_status()
dockey = json.loads(response.text)['key']
return SERVER_URL + "/raw/" + dockey
except requests.exceptions.RequestException as e:
print("\033[31mError: {}\033[0m".format(e))
sys.exit(1)
check_dependencies() {
curl -V > /dev/null 2>&1 || _die "This script requires curl."
}
def main(): paste_data() {
output = sys.stdin.read() json=$(curl -X POST -s -d "$1" "${PASTE_URL}/documents")
[[ -z "$json" ]] && _die "Unable to post the data to the server."
if not output: key=$(echo "$json" \
print("\033[31mError: No input received from stdin.\033[0m") | python -c 'import json,sys;o=json.load(sys.stdin);print o["key"]' \
sys.exit(1) 2>/dev/null)
[[ -z "$key" ]] && _die "Unable to parse the server response."
url = create_snippet(output) echo "${PASTE_URL}/${key}"
}
print("\033[32mURL: {}\033[0m".format(url)) usage() {
printf "Usage: ${0} [OPTION]...
if __name__ == "__main__": Read from input stream and paste the data to the YunoHost
main() Haste server.
For example, to paste the output of the YunoHost diagnosis, you
can simply execute the following:
yunohost diagnosis show | ${0}
It will return the URL where you can access the pasted data.
Options:
-h, --help show this help message and exit
"
}
main() {
# parse options
while (( ${#} )); do
case "${1}" in
--help|-h)
usage
exit 0
;;
*)
echo "Unknown parameter detected: ${1}" >&2
echo >&2
usage >&2
exit 1
;;
esac
shift 1
done
# check input stream
read -t 0 || {
echo -e "Invalid usage: No input is provided.\n" >&2
usage
exit 1
}
paste_data "$(cat)"
}
check_dependencies
main "${@}"

View file

@ -56,7 +56,7 @@ EOF
echo "$LOGO_AND_FINGERPRINTS" > /etc/issue echo "$LOGO_AND_FINGERPRINTS" > /etc/issue
if ! groups | grep -q all_users && [[ ! -f /etc/yunohost/installed ]] if [[ ! -f /etc/yunohost/installed ]]
then then
chvt 2 chvt 2
@ -69,7 +69,7 @@ then
You should now proceed with YunoHost post-installation. This is where you will You should now proceed with YunoHost post-installation. This is where you will
be asked for: be asked for:
- the main domain of your server; - the main domain of your server;
- the username and password for the first admin - the administration password.
You can perform this step: You can perform this step:
- from your web browser, by accessing: https://yunohost.local/ or ${local_ip} - from your web browser, by accessing: https://yunohost.local/ or ${local_ip}

View file

@ -1,10 +0,0 @@
domain-needed
expand-hosts
localise-queries
{% set interfaces = wireless_interfaces.strip().split(' ') %}
{% for interface in interfaces %}
interface={{ interface }}
{% endfor %}
resolv-file=/etc/resolv.dnsmasq.conf
cache-size=256

View file

@ -1,6 +0,0 @@
# Fail2Ban filter for postfix authentication failures
[INCLUDES]
before = common.conf
[Definition]
_daemon = postfix/smtpd
failregex = ^%(__prefix_line)swarning: [-._\w]+\[<HOST>\]: SASL (?:LOGIN|PLAIN|(?:CRAM|DIGEST)-MD5) authentication failed(: [ A-Za-z0-9+/]*={0,2})?\s*$

View file

@ -1,75 +0,0 @@
VirtualHost "{{ domain }}"
enable = true
ssl = {
key = "/etc/yunohost/certs/{{ domain }}/key.pem";
certificate = "/etc/yunohost/certs/{{ domain }}/crt.pem";
}
authentication = "ldap2"
ldap = {
hostname = "localhost",
user = {
basedn = "ou=users,dc=yunohost,dc=org",
filter = "(&(objectClass=posixAccount)(mail=*@{{ domain }})(permission=cn=xmpp.main,ou=permission,dc=yunohost,dc=org))",
usernamefield = "mail",
namefield = "cn",
},
}
-- Discovery items
disco_items = {
{ "muc.{{ domain }}" },
{ "pubsub.{{ domain }}" },
{ "jabber.{{ domain }}" },
{ "vjud.{{ domain }}" },
{ "xmpp-upload.{{ domain }}" },
};
-- contact_info = {
-- abuse = { "mailto:abuse@{{ domain }}", "xmpp:admin@{{ domain }}" };
-- admin = { "mailto:root@{{ domain }}", "xmpp:admin@{{ domain }}" };
-- };
------ Components ------
-- You can specify components to add hosts that provide special services,
-- like multi-user conferences, and transports.
---Set up a MUC (multi-user chat) room server
Component "muc.{{ domain }}" "muc"
name = "{{ domain }} Chatrooms"
modules_enabled = {
"muc_limits";
"muc_log";
"muc_log_mam";
"muc_log_http";
"muc_vcard";
}
muc_event_rate = 0.5
muc_burst_factor = 10
room_default_config = {
logging = true,
persistent = true
};
---Set up a PubSub server
Component "pubsub.{{ domain }}" "pubsub"
name = "{{ domain }} Publish/Subscribe"
unrestricted_node_creation = true -- Anyone can create a PubSub node (from any server)
---Set up a HTTP Upload service
Component "xmpp-upload.{{ domain }}" "http_upload"
name = "{{ domain }} Sharing Service"
http_file_path = "/var/xmpp-upload/{{ domain }}/upload"
http_external_url = "https://xmpp-upload.{{ domain }}:443"
http_file_base_path = "/upload"
http_file_size_limit = 6*1024*1024
http_file_quota = 60*1024*1024
http_upload_file_size_limit = 100 * 1024 * 1024 -- bytes
http_upload_quota = 10 * 1024 * 1024 * 1024 -- bytes
---Set up a VJUD service
Component "vjud.{{ domain }}" "vjud"
vjud_disco_name = "{{ domain }} User Directory"

View file

@ -1,123 +0,0 @@
-- ** Metronome's config file example **
--
-- The format is exactly equal to Prosody's:
--
-- Lists are written { "like", "this", "one" }
-- Lists can also be of { 1, 2, 3 } numbers, etc.
-- Either commas, or semi-colons; may be used as seperators.
--
-- A table is a list of values, except each value has a name. An
-- example would be:
--
-- ssl = { key = "keyfile.key", certificate = "certificate.cert" }
--
-- Tip: You can check that the syntax of this file is correct when you have finished
-- by running: luac -p metronome.cfg.lua
-- If there are any errors, it will let you know what and where they are, otherwise it
-- will keep quiet.
-- Global settings go in this section
-- This is the list of modules Metronome will load on startup.
-- It looks for mod_modulename.lua in the plugins folder, so make sure that exists too.
modules_enabled = {
-- Generally required
"roster"; -- Allow users to have a roster. Recommended.
"saslauth"; -- Authentication for clients. Recommended if you want to log in.
"tls"; -- Add support for secure TLS on c2s/s2s connections
"disco"; -- Service discovery
-- Not essential, but recommended
"private"; -- Private XML storage (for room bookmarks, etc.)
"vcard"; -- Allow users to set vCards
"pep"; -- Allows setting of mood, tune, etc.
"pubsub"; -- Publish-subscribe XEP-0060
"posix"; -- POSIX functionality, sends server to background, enables syslog, etc.
"bidi"; -- Enables Bidirectional Server-to-Server Streams.
-- Nice to have
"version"; -- Replies to server version requests
"uptime"; -- Report how long server has been running
"time"; -- Let others know the time here on this server
"ping"; -- Replies to XMPP pings with pongs
"register"; -- Allow users to register on this server using a client and change passwords
"stream_management"; -- Allows clients and servers to use Stream Management
"stanza_optimizations"; -- Allows clients to use Client State Indication and SIFT
"message_carbons"; -- Allows clients to enable carbon copies of messages
"mam"; -- Enable server-side message archives using Message Archive Management
"push"; -- Enable Push Notifications via PubSub using XEP-0357
"lastactivity"; -- Enables clients to know the last presence status of an user
"adhoc_cm"; -- Allow to set client certificates to login through SASL External via adhoc
"admin_adhoc"; -- administration adhoc commands
"bookmarks"; -- XEP-0048 Bookmarks synchronization between PEP and Private Storage
"sec_labels"; -- Allows to use a simplified version XEP-0258 Security Labels and related ACDFs.
"privacy"; -- Add privacy lists and simple blocking command support
-- Other specific functionality
--"admin_telnet"; -- administration console, telnet to port 5582
--"admin_web"; -- administration web interface
"bosh"; -- Enable support for BOSH clients, aka "XMPP over Bidirectional Streams over Synchronous HTTP"
--"compression"; -- Allow clients to enable Stream Compression
--"spim_block"; -- Require authorization via OOB form for messages from non-contacts and block unsollicited messages
--"gate_guard"; -- Enable config-based blacklisting and hit-based auto-banning features
--"incidents_handling"; -- Enable Incidents Handling support (can be administered via adhoc commands)
--"server_presence"; -- Enables Server Buddies extension support
--"service_directory"; -- Enables Service Directories extension support
--"public_service"; -- Enables Server vCard support for public services in directories and advertises in features
--"register_api"; -- Provides secure API for both Out-Of-Band and In-Band registration for E-Mail verification
"websocket"; -- Enable support for WebSocket clients, aka "XMPP over WebSockets"
};
-- Server PID
pidfile = "/var/run/metronome/metronome.pid"
-- HTTP server
http_ports = { 5290 }
http_interfaces = { "127.0.0.1", "::1" }
--https_ports = { 5291 }
--https_interfaces = { "127.0.0.1", "::1" }
-- Enable IPv6
use_ipv6 = true
-- BOSH configuration (mod_bosh)
consider_bosh_secure = true
cross_domain_bosh = true
-- WebSocket configuration (mod_websocket)
consider_websocket_secure = true
cross_domain_websocket = true
-- Disable account creation by default, for security
allow_registration = false
-- Use LDAP storage backend for all stores
storage = "ldap"
-- stanza optimization
csi_config_queue_all_muc_messages_but_mentions = false;
-- Logging configuration
log = {
info = "/var/log/metronome/metronome.log"; -- Change 'info' to 'debug' for verbose logging
error = "/var/log/metronome/metronome.err";
-- "*syslog"; -- Uncomment this for logging to syslog
-- "*console"; -- Log to the console, useful for debugging with daemonize=false
}
------ Components ------
-- You can specify components to add hosts that provide special services,
-- like multi-user conferences, and transports.
---Set up a local BOSH service
Component "localhost" "http"
modules_enabled = { "bosh" }
----------- Virtual hosts -----------
-- You need to add a VirtualHost entry for each domain you wish Metronome to serve.
-- Settings under each VirtualHost entry apply *only* to that host.
Include "conf.d/*.cfg.lua"

View file

@ -1,90 +0,0 @@
-- vim:sts=4 sw=4
-- Metronome IM
-- Copyright (C) 2008-2010 Matthew Wild
-- Copyright (C) 2008-2010 Waqas Hussain
-- Copyright (C) 2012 Rob Hoelz
-- Copyright (C) 2015 YUNOHOST.ORG
--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
-- https://github.com/YunoHost/yunohost-config-metronome/blob/unstable/lib/modules/mod_auth_ldap2.lua
-- adapted to use common LDAP store on Metronome
local ldap = module:require 'ldap';
local new_sasl = require 'util.sasl'.new;
local jsplit = require 'util.jid'.split;
local log = module._log
if not ldap then
return;
end
function new_default_provider(host)
local provider = { name = "ldap2" };
log("debug", "initializing ldap2 authentication provider for host '%s'", host);
function provider.test_password(username, password)
return ldap.bind(username, password);
end
function provider.user_exists(username)
local params = ldap.getparams()
local filter = ldap.filter.combine_and(params.user.filter, params.user.usernamefield .. '=' .. username);
if params.user.usernamefield == 'mail' then
filter = ldap.filter.combine_and(params.user.filter, 'mail=' .. username .. '@*');
end
return ldap.singlematch {
base = params.user.basedn,
filter = filter,
};
end
function provider.get_password(username)
return nil, "Passwords unavailable for LDAP.";
end
function provider.set_password(username, password)
return nil, "Passwords unavailable for LDAP.";
end
function provider.create_user(username, password)
return nil, "Account creation/modification not available with LDAP.";
end
function provider.get_sasl_handler(session)
local testpass_authentication_profile = {
session = session,
plain_test = function(sasl, username, password, realm)
return provider.test_password(username, password), true;
end,
order = { "plain_test" },
};
return new_sasl(module.host, testpass_authentication_profile);
end
function provider.is_admin(jid)
local admin_config = ldap.getparams().admin;
if not admin_config then
return;
end
local ld = ldap:getconnection();
local username = jsplit(jid);
local filter = ldap.filter.combine_and(admin_config.filter, admin_config.namefield .. '=' .. username);
return ldap.singlematch {
base = admin_config.basedn,
filter = filter,
};
end
return provider;
end
module:add_item("auth-provider", new_default_provider(module.host));

View file

@ -1,86 +0,0 @@
-- Prosody IM
-- Copyright (C) 2008-2010 Matthew Wild
-- Copyright (C) 2008-2010 Waqas Hussain
--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
--
local st = require "util.stanza";
local t_concat = table.concat;
local secure_auth_only = module:get_option("c2s_require_encryption")
or module:get_option("require_encryption")
or not(module:get_option("allow_unencrypted_plain_auth"));
local sessionmanager = require "core.sessionmanager";
local usermanager = require "core.usermanager";
local nodeprep = require "util.encodings".stringprep.nodeprep;
local resourceprep = require "util.encodings".stringprep.resourceprep;
module:add_feature("jabber:iq:auth");
module:hook("stream-features", function(event)
local origin, features = event.origin, event.features;
if secure_auth_only and not origin.secure then
-- Sorry, not offering to insecure streams!
return;
elseif not origin.username then
features:tag("auth", {xmlns='http://jabber.org/features/iq-auth'}):up();
end
end);
module:hook("stanza/iq/jabber:iq:auth:query", function(event)
local session, stanza = event.origin, event.stanza;
if session.type ~= "c2s_unauthed" then
(session.sends2s or session.send)(st.error_reply(stanza, "cancel", "service-unavailable", "Legacy authentication is only allowed for unauthenticated client connections."));
return true;
end
if secure_auth_only and not session.secure then
session.send(st.error_reply(stanza, "modify", "not-acceptable", "Encryption (SSL or TLS) is required to connect to this server"));
return true;
end
local username = stanza.tags[1]:child_with_name("username");
local password = stanza.tags[1]:child_with_name("password");
local resource = stanza.tags[1]:child_with_name("resource");
if not (username and password and resource) then
local reply = st.reply(stanza);
session.send(reply:query("jabber:iq:auth")
:tag("username"):up()
:tag("password"):up()
:tag("resource"):up());
else
username, password, resource = t_concat(username), t_concat(password), t_concat(resource);
username = nodeprep(username);
resource = resourceprep(resource)
if not (username and resource) then
session.send(st.error_reply(stanza, "modify", "bad-request"));
return true;
end
if usermanager.test_password(username, session.host, password) then
-- Authentication successful!
local success, err = sessionmanager.make_authenticated(session, username);
if success then
local err_type, err_msg;
success, err_type, err, err_msg = sessionmanager.bind_resource(session, resource);
if not success then
session.send(st.error_reply(stanza, err_type, err, err_msg));
session.username, session.type = nil, "c2s_unauthed"; -- FIXME should this be placed in sessionmanager?
return true;
elseif resource ~= session.resource then -- server changed resource, not supported by legacy auth
session.send(st.error_reply(stanza, "cancel", "conflict", "The requested resource could not be assigned to this session."));
session:close(); -- FIXME undo resource bind and auth instead of closing the session?
return true;
end
end
session.send(st.reply(stanza));
else
session.send(st.error_reply(stanza, "auth", "not-authorized"));
end
end
return true;
end);

View file

@ -1,243 +0,0 @@
-- vim:sts=4 sw=4
-- Metronome IM
-- Copyright (C) 2008-2010 Matthew Wild
-- Copyright (C) 2008-2010 Waqas Hussain
-- Copyright (C) 2012 Rob Hoelz
-- Copyright (C) 2015 YUNOHOST.ORG
--
-- This project is MIT/X11 licensed. Please see the
-- COPYING file in the source package for more information.
----------------------------------------
-- Constants and such --
----------------------------------------
local setmetatable = setmetatable;
local get_config = require "core.configmanager".get;
local ldap = module:require 'ldap';
local vcardlib = module:require 'vcard';
local st = require 'util.stanza';
local gettime = require 'socket'.gettime;
local log = module._log
if not ldap then
return;
end
local CACHE_EXPIRY = 300;
----------------------------------------
-- Utility Functions --
----------------------------------------
local function ldap_record_to_vcard(record, format)
return vcardlib.create {
record = record,
format = format,
}
end
local get_alias_for_user;
do
local user_cache;
local last_fetch_time;
local function populate_user_cache()
local user_c = get_config(module.host, 'ldap').user;
if not user_c then return; end
local ld = ldap.getconnection();
local usernamefield = user_c.usernamefield;
local namefield = user_c.namefield;
user_cache = {};
for _, attrs in ld:search { base = user_c.basedn, scope = 'onelevel', filter = user_c.filter } do
user_cache[attrs[usernamefield]] = attrs[namefield];
end
last_fetch_time = gettime();
end
function get_alias_for_user(user)
if last_fetch_time and last_fetch_time + CACHE_EXPIRY < gettime() then
user_cache = nil;
end
if not user_cache then
populate_user_cache();
end
return user_cache[user];
end
end
----------------------------------------
-- Base LDAP store class --
----------------------------------------
local function ldap_store(config)
local self = {};
local config = config;
function self:get(username)
return nil, "Data getting is not available for this storage backend";
end
function self:set(username, data)
return nil, "Data setting is not available for this storage backend";
end
return self;
end
local adapters = {};
----------------------------------------
-- Roster Storage Implementation --
----------------------------------------
adapters.roster = function (config)
-- Validate configuration requirements
if not config.groups then return nil; end
local self = ldap_store(config)
function self:get(username)
local ld = ldap.getconnection();
local contacts = {};
local memberfield = config.groups.memberfield;
local namefield = config.groups.namefield;
local filter = memberfield .. '=' .. tostring(username);
local groups = {};
for _, config in ipairs(config.groups) do
groups[ config[namefield] ] = config.name;
end
log("debug", "Found %d group(s) for user %s", select('#', groups), username)
-- XXX this kind of relies on the way we do groups at INOC
for _, attrs in ld:search { base = config.groups.basedn, scope = 'onelevel', filter = filter } do
if groups[ attrs[namefield] ] then
local members = attrs[memberfield];
for _, user in ipairs(members) do
if user ~= username then
local jid = user .. '@' .. module.host;
local record = contacts[jid];
if not record then
record = {
subscription = 'both',
groups = {},
name = get_alias_for_user(user),
};
contacts[jid] = record;
end
record.groups[ groups[ attrs[namefield] ] ] = true;
end
end
end
end
return contacts;
end
function self:set(username, data)
log("warn", "Setting data in Roster LDAP storage is not supported yet")
return nil, "not supported";
end
return self;
end
----------------------------------------
-- vCard Storage Implementation --
----------------------------------------
adapters.vcard = function (config)
-- Validate configuration requirements
if not config.vcard_format or not config.user then return nil; end
local self = ldap_store(config)
function self:get(username)
local ld = ldap.getconnection();
local filter = config.user.usernamefield .. '=' .. tostring(username);
log("debug", "Retrieving vCard for user '%s'", username);
local match = ldap.singlematch {
base = config.user.basedn,
filter = filter,
};
if match then
match.jid = username .. '@' .. module.host
return st.preserialize(ldap_record_to_vcard(match, config.vcard_format));
else
return nil, "username not found";
end
end
function self:set(username, data)
log("warn", "Setting data in vCard LDAP storage is not supported yet")
return nil, "not supported";
end
return self;
end
----------------------------------------
-- Driver Definition --
----------------------------------------
cache = {};
local driver = { name = "ldap" };
function driver:open(store)
log("debug", "Opening ldap storage backend for host '%s' and store '%s'", module.host, store);
if not cache[module.host] then
log("debug", "Caching adapters for the host '%s'", module.host);
local ad_config = get_config(module.host, "ldap");
local ad_cache = {};
for k, v in pairs(adapters) do
ad_cache[k] = v(ad_config);
end
cache[module.host] = ad_cache;
end
local adapter = cache[module.host][store];
if not adapter then
log("info", "Unavailable adapter for store '%s'", store);
return nil, "unsupported-store";
end
return adapter;
end
function driver:stores(username, type, pattern)
return nil, "not implemented";
end
function driver:store_exists(username, type)
return nil, "not implemented";
end
function driver:purge(username)
return nil, "not implemented";
end
function driver:nodes(type)
return nil, "not implemented";
end
module:add_item("data-driver", driver);

View file

@ -1,7 +0,0 @@
error_page 502 /502.html;
location = /502.html {
root /usr/share/yunohost/html/;
}

View file

@ -1,3 +0,0 @@
location / {
return 302 https://$host/yunohost/admin;
}

View file

@ -1,7 +0,0 @@
server_host = localhost
server_port = 389
search_base = dc=yunohost,dc=org
query_filter = (&(objectClass=groupOfNamesYnh)(mail=%s))
scope = sub
result_attribute = memberUid, mail
terminal_result_attribute = memberUid

View file

@ -1,4 +0,0 @@
# This maps domain to certificates to properly handle multi-domain context
# (also we need a comment in this file such that it's never empty to prevent regenconf issues)
{% for domain in domain_list.split() %}{{ domain }} /etc/yunohost/certs/{{ domain }}/key.pem /etc/yunohost/certs/{{ domain }}/crt.pem
{% endfor %}

View file

@ -1,2 +0,0 @@
# set redis server
servers = "127.0.0.1";

461
share/actionsmap.yml → data/actionsmap/yunohost.yml Executable file → Normal file
View file

@ -33,10 +33,18 @@
# Global parameters # # Global parameters #
############################# #############################
_global: _global:
namespace: yunohost name: yunohost.admin
authentication: authentication:
api: ldap_admin api: ldap_admin
cli: null cli: null
arguments:
-v:
full: --version
help: Display YunoHost packages versions
action: callback
callback:
method: yunohost.utils.packages.ynh_packages_version
return: true
############################# #############################
# User # # User #
@ -63,33 +71,27 @@ user:
help: The unique username to create help: The unique username to create
extra: extra:
pattern: &pattern_username pattern: &pattern_username
- !!str ^[a-z0-9_\.]+$ - !!str ^[a-z0-9_]+$
- "pattern_username" - "pattern_username"
-F:
full: --fullname
help: The full name of the user. For example 'Camille Dupont'
extra:
ask: ask_fullname
required: False
pattern: &pattern_fullname
- !!str ^([^\W_]{1,30}[ ,.'-]{0,3})+$
- "pattern_fullname"
-f: -f:
full: --firstname full: --firstname
help: Deprecated. Use --fullname instead.
extra: extra:
required: False ask: ask_firstname
required: True
pattern: &pattern_firstname pattern: &pattern_firstname
- !!str ^([^\W\d_]{1,30}[ ,.'-]{0,3})+$ - !!str ^([^\W\d_]{1,30}[ ,.'-]{0,3})+$
- "pattern_firstname" - "pattern_firstname"
-l: -l:
full: --lastname full: --lastname
help: Deprecated. Use --fullname instead.
extra: extra:
required: False ask: ask_lastname
required: True
pattern: &pattern_lastname pattern: &pattern_lastname
- !!str ^([^\W\d_]{1,30}[ ,.'-]{0,3})+$ - !!str ^([^\W\d_]{1,30}[ ,.'-]{0,3})+$
- "pattern_lastname" - "pattern_lastname"
-m:
full: --mail
help: (Deprecated, see --domain) Main unique email address
-p: -p:
full: --password full: --password
help: User password help: User password
@ -116,11 +118,6 @@ user:
pattern: &pattern_mailbox_quota pattern: &pattern_mailbox_quota
- !!str ^(\d+[bkMGT])|0$ - !!str ^(\d+[bkMGT])|0$
- "pattern_mailbox_quota" - "pattern_mailbox_quota"
-s:
full: --loginShell
help: The login shell used
default: "/bin/bash"
### user_delete() ### user_delete()
delete: delete:
@ -142,19 +139,12 @@ user:
arguments: arguments:
username: username:
help: Username to update help: Username to update
-F:
full: --fullname
help: The full name of the user. For example 'Camille Dupont'
extra:
pattern: *pattern_fullname
-f: -f:
full: --firstname full: --firstname
help: Deprecated. Use --fullname instead.
extra: extra:
pattern: *pattern_firstname pattern: *pattern_firstname
-l: -l:
full: --lastname full: --lastname
help: Deprecated. Use --fullname instead.
extra: extra:
pattern: *pattern_lastname pattern: *pattern_lastname
-m: -m:
@ -200,10 +190,6 @@ user:
metavar: "{SIZE|0}" metavar: "{SIZE|0}"
extra: extra:
pattern: *pattern_mailbox_quota pattern: *pattern_mailbox_quota
-s:
full: --loginShell
help: The login shell used
default: "/bin/bash"
### user_info() ### user_info()
info: info:
@ -322,35 +308,6 @@ user:
extra: extra:
pattern: *pattern_username pattern: *pattern_username
add-mailalias:
action_help: Add mail aliases to group
api: PUT /users/groups/<groupname>/aliases/<aliases>
arguments:
groupname:
help: Name of the group to add user(s) to
extra:
pattern: *pattern_groupname
aliases:
help: Mail aliases to add
nargs: "+"
metavar: MAIL
extra:
pattern: *pattern_email
remove-mailalias:
action_help: Remove mail aliases to group
api: DELETE /users/groups/<groupname>/aliases/<aliases>
arguments:
groupname:
help: Name of the group to add user(s) to
extra:
pattern: *pattern_groupname
aliases:
help: Mail aliases to remove
nargs: "+"
metavar: MAIL
permission: permission:
subcategory_help: Manage permissions subcategory_help: Manage permissions
actions: actions:
@ -489,22 +446,6 @@ domain:
--exclude-subdomains: --exclude-subdomains:
help: Filter out domains that are obviously subdomains of other declared domains help: Filter out domains that are obviously subdomains of other declared domains
action: store_true action: store_true
--tree:
help: Display domains as a tree
action: store_true
--features:
help: List only domains with features enabled (xmpp, mail_in, mail_out)
nargs: "*"
### domain_info()
info:
action_help: Get domain aggredated data
api: GET /domains/<domain>
arguments:
domain:
help: Domain to check
extra:
pattern: *pattern_domain
### domain_add() ### domain_add()
add: add:
@ -515,16 +456,10 @@ domain:
help: Domain name to add help: Domain name to add
extra: extra:
pattern: *pattern_domain pattern: *pattern_domain
--ignore-dyndns: -d:
help: If adding a DynDNS domain, only add the domain, without subscribing to the DynDNS service full: --dyndns
help: Subscribe to the DynDNS service
action: store_true action: store_true
--dyndns-recovery-password:
metavar: PASSWORD
nargs: "?"
const: 0
help: If adding a DynDNS domain, subscribe to the DynDNS service with a password, used to later delete the domain
extra:
pattern: *pattern_password
### domain_remove() ### domain_remove()
remove: remove:
@ -543,16 +478,6 @@ domain:
full: --force full: --force
help: Do not ask confirmation to remove apps help: Do not ask confirmation to remove apps
action: store_true action: store_true
--ignore-dyndns:
help: If removing a DynDNS domain, only remove the domain, without unsubscribing from the DynDNS service
action: store_true
--dyndns-recovery-password:
metavar: PASSWORD
nargs: "?"
const: 0
help: If removing a DynDNS domain, unsubscribe from the DynDNS service with a password
extra:
pattern: *pattern_password
### domain_dns_conf() ### domain_dns_conf()
@ -570,7 +495,9 @@ domain:
action_help: Check the current main domain, or change it action_help: Check the current main domain, or change it
deprecated_alias: deprecated_alias:
- maindomain - maindomain
api: PUT /domains/<new_main_domain>/main api:
- GET /domains/main
- PUT /domains/<new_main_domain>/main
arguments: arguments:
-n: -n:
full: --new-main-domain full: --new-main-domain
@ -607,6 +534,9 @@ domain:
--self-signed: --self-signed:
help: Install self-signed certificate instead of Let's Encrypt help: Install self-signed certificate instead of Let's Encrypt
action: store_true action: store_true
--staging:
help: Use the fake/staging Let's Encrypt certification authority. The new certificate won't actually be enabled - it is only intended to test the main steps of the procedure.
action: store_true
### certificate_renew() ### certificate_renew()
cert-renew: cert-renew:
@ -617,7 +547,7 @@ domain:
help: Domains for which to renew the certificates help: Domains for which to renew the certificates
nargs: "*" nargs: "*"
--force: --force:
help: Ignore the validity threshold (15 days) help: Ignore the validity threshold (30 days)
action: store_true action: store_true
--email: --email:
help: Send an email to root with logs if some renewing fails help: Send an email to root with logs if some renewing fails
@ -625,10 +555,12 @@ domain:
--no-checks: --no-checks:
help: Does not perform any check that your domain seems correctly configured (DNS, reachability) before attempting to renew. (Not recommended) help: Does not perform any check that your domain seems correctly configured (DNS, reachability) before attempting to renew. (Not recommended)
action: store_true action: store_true
--staging:
help: Use the fake/staging Let's Encrypt certification authority. The new certificate won't actually be enabled - it is only intended to test the main steps of the procedure.
action: store_true
### domain_url_available() ### domain_url_available()
url-available: url-available:
hide_in_help: True
action_help: Check availability of a web path action_help: Check availability of a web path
api: GET /domain/<domain>/urlavailable api: GET /domain/<domain>/urlavailable
arguments: arguments:
@ -638,84 +570,17 @@ domain:
pattern: *pattern_domain pattern: *pattern_domain
path: path:
help: The path to check (e.g. /coffee) help: The path to check (e.g. /coffee)
### domain_action_run()
action-run:
hide_in_help: True
action_help: Run domain action
api: PUT /domain/<domain>/actions/<action>
arguments:
domain:
help: Domain name
action:
help: action id
-a:
full: --args
help: Serialized arguments for action (i.e. "foo=bar&lorem=ipsum")
subcategories: subcategories:
dyndns:
subcategory_help: Subscribe and Update DynDNS Hosts
actions:
### domain_dyndns_subscribe()
subscribe:
action_help: Subscribe to a DynDNS service
arguments:
domain:
help: Domain to subscribe to the DynDNS service
extra:
pattern: *pattern_domain
-p:
full: --recovery-password
nargs: "?"
const: 0
help: Password used to later recover the domain if needed
extra:
pattern: *pattern_password
### domain_dyndns_unsubscribe()
unsubscribe:
action_help: Unsubscribe from a DynDNS service
arguments:
domain:
help: Domain to unsubscribe from the DynDNS service
extra:
pattern: *pattern_domain
required: True
-p:
full: --recovery-password
nargs: "?"
const: 0
help: Recovery password used to delete the domain
extra:
pattern: *pattern_password
### domain_dyndns_set_recovery_password()
set-recovery-password:
action_help: Set recovery password
arguments:
domain:
help: Domain to set recovery password for
extra:
pattern: *pattern_domain
required: True
-p:
full: --recovery-password
help: The new recovery password
extra:
password: ask_dyndns_recovery_password
pattern: *pattern_password
config: config:
subcategory_help: Domain settings subcategory_help: Domain settings
actions: actions:
### domain_config_get() ### domain_config_get()
get: get:
action_help: Display a domain configuration action_help: Display a domain configuration
api: api: GET /domains/<domain>/config
- GET /domains/<domain>/config
- GET /domains/<domain>/config/<key>
arguments: arguments:
domain: domain:
help: Domain name help: Domain name
@ -734,7 +599,7 @@ domain:
### domain_config_set() ### domain_config_set()
set: set:
action_help: Apply a new configuration action_help: Apply a new configuration
api: PUT /domains/<domain>/config/<key> api: PUT /domains/<domain>/config
arguments: arguments:
domain: domain:
help: Domain name help: Domain name
@ -815,6 +680,9 @@ domain:
--self-signed: --self-signed:
help: Install self-signed certificate instead of Let's Encrypt help: Install self-signed certificate instead of Let's Encrypt
action: store_true action: store_true
--staging:
help: Use the fake/staging Let's Encrypt certification authority. The new certificate won't actually be enabled - it is only intended to test the main steps of the procedure.
action: store_true
### certificate_renew() ### certificate_renew()
renew: renew:
@ -833,6 +701,9 @@ domain:
--no-checks: --no-checks:
help: Does not perform any check that your domain seems correctly configured (DNS, reachability) before attempting to renew. (Not recommended) help: Does not perform any check that your domain seems correctly configured (DNS, reachability) before attempting to renew. (Not recommended)
action: store_true action: store_true
--staging:
help: Use the fake/staging Let's Encrypt certification authority. The new certificate won't actually be enabled - it is only intended to test the main steps of the procedure.
action: store_true
############################# #############################
@ -854,10 +725,6 @@ app:
full: --with-categories full: --with-categories
help: Also return a list of app categories help: Also return a list of app categories
action: store_true action: store_true
-a:
full: --with-antifeatures
help: Also return a list of antifeatures categories
action: store_true
### app_search() ### app_search()
search: search:
@ -873,10 +740,9 @@ app:
arguments: arguments:
app: app:
help: Name, local path or git URL of the app to fetch the manifest of help: Name, local path or git URL of the app to fetch the manifest of
-s:
full: --with-screenshot fetchlist:
help: Also return a base64 screenshot if any (API only) deprecated: true
action: store_true
### app_list() ### app_list()
list: list:
@ -887,10 +753,12 @@ app:
full: --full full: --full
help: Display all details, including the app manifest and various other infos help: Display all details, including the app manifest and various other infos
action: store_true action: store_true
-u: -i:
full: --upgradable full: --installed
help: List only apps that can upgrade to a newer version help: Dummy argument, does nothing anymore (still there only for backward compatibility)
action: store_true action: store_true
filter:
nargs: '?'
### app_info() ### app_info()
info: info:
@ -934,14 +802,14 @@ app:
help: Custom name for the app help: Custom name for the app
-a: -a:
full: --args full: --args
help: Serialized arguments for app script (i.e. "domain=domain.tld&path=/path&init_main_permission=visitors") help: Serialized arguments for app script (i.e. "domain=domain.tld&path=/path")
-n: -n:
full: --no-remove-on-failure full: --no-remove-on-failure
help: Debug option to avoid removing the app on a failed installation help: Debug option to avoid removing the app on a failed installation
action: store_true action: store_true
-f: -f:
full: --force full: --force
help: Do not ask confirmation if the app is not safe to use (low quality, experimental or 3rd party), or when the app displays a post-install notification help: Do not ask confirmation if the app is not safe to use (low quality, experimental or 3rd party)
action: store_true action: store_true
### app_remove() ### app_remove()
@ -978,10 +846,6 @@ app:
full: --no-safety-backup full: --no-safety-backup
help: Disable the safety backup during upgrade help: Disable the safety backup during upgrade
action: store_true action: store_true
-c:
full: --continue-on-failure
help: Continue to upgrade apps even if one or more upgrade failed
action: store_true
### app_change_url() ### app_change_url()
change-url: change-url:
@ -1021,18 +885,9 @@ app:
help: Delete the key help: Delete the key
action: store_true action: store_true
### app_shell()
shell:
action_help: Open an interactive shell with the app environment already loaded
# Here we set a GET only not to lock the command line. There is no actual API endpoint for app_shell()
api: GET /apps/<app>/shell
arguments:
app:
help: App ID
### app_register_url() ### app_register_url()
register-url: register-url:
hide_in_help: True
action_help: Book/register a web path for a given app action_help: Book/register a web path for a given app
arguments: arguments:
app: app:
@ -1045,7 +900,6 @@ app:
### app_makedefault() ### app_makedefault()
makedefault: makedefault:
hide_in_help: True
action_help: Redirect domain root to an app action_help: Redirect domain root to an app
api: PUT /apps/<app>/default api: PUT /apps/<app>/default
arguments: arguments:
@ -1054,21 +908,6 @@ app:
-d: -d:
full: --domain full: --domain
help: Specific domain to put app on (the app domain by default) help: Specific domain to put app on (the app domain by default)
-u:
full: --undo
help: Undo redirection
action: store_true
### app_dismiss_notification
dismiss-notification:
hide_in_help: True
action_help: Dismiss post_install or post_upgrade notification
api: PUT /apps/<app>/dismiss_notification/<name>
arguments:
app:
help: App ID to dismiss notification for
name:
help: Notification name, either post_install or post_upgrade
### app_ssowatconf() ### app_ssowatconf()
ssowatconf: ssowatconf:
@ -1084,6 +923,36 @@ app:
new_label: new_label:
help: New app label help: New app label
### app_addaccess() TODO: Write help
addaccess:
action_help: Grant access right to users (everyone by default)
deprecated: true
arguments:
apps:
nargs: "+"
-u:
full: --users
nargs: "*"
### app_removeaccess() TODO: Write help
removeaccess:
action_help: Revoke access right to users (everyone by default)
deprecated: true
arguments:
apps:
nargs: "+"
-u:
full: --users
nargs: "*"
### app_clearaccess()
clearaccess:
action_help: Reset access rights for the app
deprecated: true
arguments:
apps:
nargs: "+"
subcategories: subcategories:
action: action:
@ -1118,9 +987,7 @@ app:
### app_config_get() ### app_config_get()
get: get:
action_help: Display an app configuration action_help: Display an app configuration
api: api: GET /apps/<app>/config-panel
- GET /apps/<app>/config
- GET /apps/<app>/config/<key>
arguments: arguments:
app: app:
help: App name help: App name
@ -1139,7 +1006,7 @@ app:
### app_config_set() ### app_config_set()
set: set:
action_help: Apply a new configuration action_help: Apply a new configuration
api: PUT /apps/<app>/config/<key> api: PUT /apps/<app>/config
arguments: arguments:
app: app:
help: App name help: App name
@ -1201,7 +1068,7 @@ backup:
api: PUT /backups/<name>/restore api: PUT /backups/<name>/restore
arguments: arguments:
name: name:
help: Name or path of the backup archive help: Name of the local backup archive
--system: --system:
help: List of system parts to restore (or all if none is given) help: List of system parts to restore (or all if none is given)
nargs: "*" nargs: "*"
@ -1232,7 +1099,7 @@ backup:
api: GET /backups/<name> api: GET /backups/<name>
arguments: arguments:
name: name:
help: Name or path of the backup archive help: Name of the local backup archive
-d: -d:
full: --with-details full: --with-details
help: Show additional backup information help: Show additional backup information
@ -1244,7 +1111,6 @@ backup:
### backup_download() ### backup_download()
download: download:
hide_in_help: True
action_help: (API only) Request to download the file action_help: (API only) Request to download the file
api: GET /backups/<name>/download api: GET /backups/<name>/download
arguments: arguments:
@ -1273,11 +1139,6 @@ settings:
list: list:
action_help: list all entries of the settings action_help: list all entries of the settings
api: GET /settings api: GET /settings
arguments:
-f:
full: --full
help: Display all details (meant to be used by the API)
action: store_true
### settings_get() ### settings_get()
get: get:
@ -1286,29 +1147,22 @@ settings:
arguments: arguments:
key: key:
help: Settings key help: Settings key
-f: --full:
full: --full help: Show more details
help: Display all details (meant to be used by the API)
action: store_true
-e:
full: --export
help: Only export key/values, meant to be reimported using "config set --args-file"
action: store_true action: store_true
### settings_set() ### settings_set()
set: set:
action_help: set an entry value in the settings action_help: set an entry value in the settings
api: PUT /settings/<key> api: POST /settings/<key>
arguments: arguments:
key: key:
help: The question or form key help: Settings key
nargs: '?'
-v: -v:
full: --value full: --value
help: new value help: new value
-a: extra:
full: --args required: True
help: Serialized arguments for new configuration (i.e. "mail_in=0&mail_out=0")
### settings_reset_all() ### settings_reset_all()
reset-all: reset-all:
@ -1343,6 +1197,13 @@ service:
full: --log full: --log
help: Absolute path to log file to display help: Absolute path to log file to display
nargs: "+" nargs: "+"
-t:
full: --log_type
help: Type of the log (file or systemd)
nargs: "+"
choices:
- file
- systemd
--test_status: --test_status:
help: Specify a custom bash command to check the status of the service. Note that it only makes sense to specify this if the corresponding systemd service does not return the proper information already. help: Specify a custom bash command to check the status of the service. Note that it only makes sense to specify this if the corresponding systemd service does not return the proper information already.
--test_conf: --test_conf:
@ -1356,6 +1217,9 @@ service:
full: --need_lock full: --need_lock
help: Use this option to prevent deadlocks if the service does invoke yunohost commands. help: Use this option to prevent deadlocks if the service does invoke yunohost commands.
action: store_true action: store_true
-s:
full: --status
help: Deprecated, old option. Does nothing anymore. Possibly check the --test_status option.
### service_remove() ### service_remove()
remove: remove:
@ -1457,6 +1321,35 @@ service:
default: 50 default: 50
type: int type: int
### service_regen_conf()
regen-conf:
action_help: Regenerate the configuration file(s) for a service
deprecated_alias:
- regenconf
arguments:
names:
help: Services name to regenerate configuration of
nargs: "*"
metavar: NAME
-d:
full: --with-diff
help: Show differences in case of configuration changes
action: store_true
-f:
full: --force
help: >
Override all manual modifications in configuration
files
action: store_true
-n:
full: --dry-run
help: Show what would have been regenerated
action: store_true
-p:
full: --list-pending
help: List pending configuration files and exit
action: store_true
############################# #############################
# Firewall # # Firewall #
############################# #############################
@ -1583,36 +1476,43 @@ firewall:
# DynDNS # # DynDNS #
############################# #############################
dyndns: dyndns:
category_help: Subscribe and Update DynDNS Hosts ( deprecated, use 'yunohost domain dyndns' instead ) category_help: Subscribe and Update DynDNS Hosts
actions: actions:
### dyndns_subscribe() ### dyndns_subscribe()
subscribe: subscribe:
action_help: Subscribe to a DynDNS service action_help: Subscribe to a DynDNS service
deprecated: true
arguments: arguments:
--subscribe-host:
help: Dynette HTTP API to subscribe to
default: "dyndns.yunohost.org"
-d: -d:
full: --domain full: --domain
help: Full domain to subscribe with ( deprecated, use 'yunohost domain dyndns subscribe' instead ) help: Full domain to subscribe with
extra: extra:
pattern: *pattern_domain pattern: *pattern_domain
-p: -k:
full: --recovery-password full: --key
nargs: "?" help: Public DNS key
const: 0
help: Password used to later recover the domain if needed
extra:
pattern: *pattern_password
### dyndns_update() ### dyndns_update()
update: update:
action_help: Update IP on DynDNS platform action_help: Update IP on DynDNS platform
arguments: arguments:
--dyn-host:
help: Dynette DNS server to inform
default: "dyndns.yunohost.org"
-d: -d:
full: --domain full: --domain
help: Full domain to update help: Full domain to update
extra: extra:
pattern: *pattern_domain pattern: *pattern_domain
-k:
full: --key
help: Public DNS key
-i:
full: --ipv4
help: IP address to send
-f: -f:
full: --force full: --force
help: Force the update (for debugging only) help: Force the update (for debugging only)
@ -1621,6 +1521,17 @@ dyndns:
full: --dry-run full: --dry-run
help: Only display the generated zone help: Only display the generated zone
action: store_true action: store_true
-6:
full: --ipv6
help: IPv6 address to send
### dyndns_installcron()
installcron:
deprecated: true
### dyndns_removecron()
removecron:
deprecated: true
############################# #############################
@ -1630,10 +1541,10 @@ tools:
category_help: Specific tools category_help: Specific tools
actions: actions:
### tools_rootpw() ### tools_adminpw()
rootpw: adminpw:
action_help: Change root password action_help: Change password of admin and root users
api: PUT /rootpw api: PUT /adminpw
arguments: arguments:
-n: -n:
full: --new-password full: --new-password
@ -1668,20 +1579,6 @@ tools:
ask: ask_main_domain ask: ask_main_domain
pattern: *pattern_domain pattern: *pattern_domain
required: True required: True
-u:
full: --username
help: Username for the first (admin) user. For example 'camille'
extra:
ask: ask_admin_username
pattern: *pattern_username
required: True
-F:
full: --fullname
help: The full name for the first (admin) user. For example 'Camille Dupont'
extra:
ask: ask_admin_fullname
required: True
pattern: *pattern_fullname
-p: -p:
full: --password full: --password
help: YunoHost admin password help: YunoHost admin password
@ -1691,19 +1588,16 @@ tools:
required: True required: True
comment: good_practices_about_admin_password comment: good_practices_about_admin_password
--ignore-dyndns: --ignore-dyndns:
help: If adding a DynDNS domain, only add the domain, without subscribing to the DynDNS service help: Do not subscribe domain to a DynDNS service
action: store_true
--force-password:
help: Use this if you really want to set a weak password
action: store_true action: store_true
--dyndns-recovery-password:
metavar: PASSWORD
nargs: "?"
const: 0
help: If adding a DynDNS domain, subscribe to the DynDNS service with a password, used to later recover the domain if needed
extra:
pattern: *pattern_password
--force-diskspace: --force-diskspace:
help: Use this if you really want to install YunoHost on a setup with less than 10 GB on the root filesystem help: Use this if you really want to install YunoHost on a setup with less than 10 GB on the root filesystem
action: store_true action: store_true
### tools_update() ### tools_update()
update: update:
action_help: YunoHost update action_help: YunoHost update
@ -1718,6 +1612,12 @@ tools:
nargs: "?" nargs: "?"
metavar: TARGET metavar: TARGET
default: all default: all
--apps:
help: (Deprecated, see first positional arg) Fetch the application list to check which apps can be upgraded
action: store_true
--system:
help: (Deprecated, see first positional arg) Fetch available system packages upgrades (equivalent to apt update)
action: store_true
### tools_upgrade() ### tools_upgrade()
upgrade: upgrade:
@ -1730,6 +1630,12 @@ tools:
- apps - apps
- system - system
nargs: "?" nargs: "?"
--apps:
help: (Deprecated, see first positional arg) Upgrade all applications
action: store_true
--system:
help: (Deprecated, see first positional arg) Upgrade only the system packages
action: store_true
### tools_shell() ### tools_shell()
shell: shell:
@ -1739,10 +1645,6 @@ tools:
help: python command to execute help: python command to execute
full: --command full: --command
### tools_basic_space_cleanup()
basic-space-cleanup:
action_help: Basic space cleanup (apt, journalctl, logs, ...)
### tools_shutdown() ### tools_shutdown()
shutdown: shutdown:
action_help: Shutdown the server action_help: Shutdown the server
@ -1869,7 +1771,6 @@ hook:
### hook_info() ### hook_info()
info: info:
hide_in_help: True
action_help: Get information about a given hook action_help: Get information about a given hook
arguments: arguments:
action: action:
@ -1899,7 +1800,6 @@ hook:
### hook_callback() ### hook_callback()
callback: callback:
hide_in_help: True
action_help: Execute all scripts binded to an action action_help: Execute all scripts binded to an action
arguments: arguments:
action: action:
@ -1922,7 +1822,6 @@ hook:
### hook_exec() ### hook_exec()
exec: exec:
hide_in_help: True
action_help: Execute hook from a file with arguments action_help: Execute hook from a file with arguments
arguments: arguments:
path: path:
@ -1976,7 +1875,7 @@ log:
- display - display
arguments: arguments:
path: path:
help: Log file which to display the content. 'last' or 'last-X' selects the last but X log file help: Log file which to display the content
-n: -n:
full: --number full: --number
help: Number of lines to display help: Number of lines to display
@ -2079,6 +1978,6 @@ diagnosis:
api: PUT /diagnosis/unignore api: PUT /diagnosis/unignore
arguments: arguments:
--filter: --filter:
help: Remove a filter (it should be an existing filter as listed with "ignore --list") help: Remove a filter (it should be an existing filter as listed with --list)
nargs: "*" nargs: "*"
metavar: CRITERIA metavar: CRITERIA

View file

@ -8,14 +8,13 @@ adds `--help` at the end if one presses [tab] again.
author: Christophe Vuillot author: Christophe Vuillot
""" """
import os import os
import yaml import yaml
THIS_SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) THIS_SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
ACTIONSMAP_FILE = THIS_SCRIPT_DIR + "/../share/actionsmap.yml" ACTIONSMAP_FILE = THIS_SCRIPT_DIR + "/yunohost.yml"
BASH_COMPLETION_FOLDER = THIS_SCRIPT_DIR + "/bash_completion.d" os.system(f"mkdir {THIS_SCRIPT_DIR}/../bash-completion.d")
BASH_COMPLETION_FILE = BASH_COMPLETION_FOLDER + "/yunohost" BASH_COMPLETION_FILE = THIS_SCRIPT_DIR + "/../bash-completion.d/yunohost"
def get_dict_actions(OPTION_SUBTREE, category): def get_dict_actions(OPTION_SUBTREE, category):
@ -32,6 +31,7 @@ def get_dict_actions(OPTION_SUBTREE, category):
with open(ACTIONSMAP_FILE, "r") as stream: with open(ACTIONSMAP_FILE, "r") as stream:
# Getting the dictionary containning what actions are possible per category # Getting the dictionary containning what actions are possible per category
OPTION_TREE = yaml.safe_load(stream) OPTION_TREE = yaml.safe_load(stream)
@ -62,9 +62,8 @@ with open(ACTIONSMAP_FILE, "r") as stream:
OPTION_TREE[category]["subcategories"], subcategory OPTION_TREE[category]["subcategories"], subcategory
) )
os.makedirs(BASH_COMPLETION_FOLDER, exist_ok=True)
with open(BASH_COMPLETION_FILE, "w") as generated_file: with open(BASH_COMPLETION_FILE, "w") as generated_file:
# header of the file # header of the file
generated_file.write("#\n") generated_file.write("#\n")
generated_file.write("# completion for yunohost\n") generated_file.write("# completion for yunohost\n")

8
data/helpers Normal file
View file

@ -0,0 +1,8 @@
# -*- shell-script -*-
readonly XTRACE_ENABLE=$(set +o | grep xtrace) # This is a trick to later only restore set -x if it was set when calling this script
set +x
for helper in $(run-parts --list /usr/share/yunohost/helpers.d 2>/dev/null) ; do
[ -r $helper ] && . $helper || true
done
eval "$XTRACE_ENABLE"

View file

@ -12,21 +12,25 @@ ynh_wait_dpkg_free() {
local try local try
set +o xtrace # set +x set +o xtrace # set +x
# With seq 1 17, timeout will be almost 30 minutes # With seq 1 17, timeout will be almost 30 minutes
for try in $(seq 1 17); do for try in `seq 1 17`
do
# Check if /var/lib/dpkg/lock is used by another process # Check if /var/lib/dpkg/lock is used by another process
if lsof /var/lib/dpkg/lock > /dev/null; then if lsof /var/lib/dpkg/lock > /dev/null
then
echo "apt is already in use..." echo "apt is already in use..."
# Sleep an exponential time at each round # Sleep an exponential time at each round
sleep $((try * try)) sleep $(( try * try ))
else else
# Check if dpkg hasn't been interrupted and is fully available. # Check if dpkg hasn't been interrupted and is fully available.
# See this for more information: https://sources.debian.org/src/apt/1.4.9/apt-pkg/deb/debsystem.cc/#L141-L174 # See this for more information: https://sources.debian.org/src/apt/1.4.9/apt-pkg/deb/debsystem.cc/#L141-L174
local dpkg_dir="/var/lib/dpkg/updates/" local dpkg_dir="/var/lib/dpkg/updates/"
# For each file in $dpkg_dir # For each file in $dpkg_dir
while read dpkg_file <&9; do while read dpkg_file <&9
do
# Check if the name of this file contains only numbers. # Check if the name of this file contains only numbers.
if echo "$dpkg_file" | grep --perl-regexp --quiet "^[[:digit:]]+$"; then if echo "$dpkg_file" | grep --perl-regexp --quiet "^[[:digit:]]+$"
then
# If so, that a remaining of dpkg. # If so, that a remaining of dpkg.
ynh_print_err "dpkg was interrupted, you must manually run 'sudo dpkg --configure -a' to correct the problem." ynh_print_err "dpkg was interrupted, you must manually run 'sudo dpkg --configure -a' to correct the problem."
set -o xtrace # set -x set -o xtrace # set -x
@ -53,21 +57,20 @@ ynh_wait_dpkg_free() {
ynh_package_is_installed() { ynh_package_is_installed() {
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local legacy_args=p local legacy_args=p
local -A args_array=([p]=package=) local -A args_array=( [p]=package= )
local package local package
# Manage arguments with getopts # Manage arguments with getopts
ynh_handle_getopts_args "$@" ynh_handle_getopts_args "$@"
dpkg-query --show --showformat='${Status}' "$package" 2> /dev/null \ ynh_wait_dpkg_free
| grep --count "ok installed" &> /dev/null dpkg-query --show --showformat='${Status}' "$package" 2>/dev/null \
| grep --count "ok installed" &>/dev/null
} }
# Get the version of an installed package # Get the version of an installed package
# #
# example: version=$(ynh_package_version --package=yunohost) # example: version=$(ynh_package_version --package=yunohost)
# #
# [internal]
#
# usage: ynh_package_version --package=name # usage: ynh_package_version --package=name
# | arg: -p, --package= - the package name to get version # | arg: -p, --package= - the package name to get version
# | ret: the version or an empty string # | ret: the version or an empty string
@ -76,13 +79,14 @@ ynh_package_is_installed() {
ynh_package_version() { ynh_package_version() {
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local legacy_args=p local legacy_args=p
local -A args_array=([p]=package=) local -A args_array=( [p]=package= )
local package local package
# Manage arguments with getopts # Manage arguments with getopts
ynh_handle_getopts_args "$@" ynh_handle_getopts_args "$@"
if ynh_package_is_installed "$package"; then if ynh_package_is_installed "$package"
dpkg-query --show --showformat='${Version}' "$package" 2> /dev/null then
dpkg-query --show --showformat='${Version}' "$package" 2>/dev/null
else else
echo '' echo ''
fi fi
@ -102,8 +106,6 @@ ynh_apt() {
# Update package index files # Update package index files
# #
# [internal]
#
# usage: ynh_package_update # usage: ynh_package_update
# #
# Requires YunoHost version 2.2.4 or higher. # Requires YunoHost version 2.2.4 or higher.
@ -113,8 +115,6 @@ ynh_package_update() {
# Install package(s) # Install package(s)
# #
# [internal]
#
# usage: ynh_package_install name [name [...]] # usage: ynh_package_install name [name [...]]
# | arg: name - the package name to install # | arg: name - the package name to install
# #
@ -126,8 +126,6 @@ ynh_package_install() {
# Remove package(s) # Remove package(s)
# #
# [internal]
#
# usage: ynh_package_remove name [name [...]] # usage: ynh_package_remove name [name [...]]
# | arg: name - the package name to remove # | arg: name - the package name to remove
# #
@ -138,8 +136,6 @@ ynh_package_remove() {
# Remove package(s) and their uneeded dependencies # Remove package(s) and their uneeded dependencies
# #
# [internal]
#
# usage: ynh_package_autoremove name [name [...]] # usage: ynh_package_autoremove name [name [...]]
# | arg: name - the package name to remove # | arg: name - the package name to remove
# #
@ -150,8 +146,6 @@ ynh_package_autoremove() {
# Purge package(s) and their uneeded dependencies # Purge package(s) and their uneeded dependencies
# #
# [internal]
#
# usage: ynh_package_autopurge name [name [...]] # usage: ynh_package_autopurge name [name [...]]
# | arg: name - the package name to autoremove and purge # | arg: name - the package name to autoremove and purge
# #
@ -172,52 +166,45 @@ ynh_package_autopurge() {
# | arg: controlfile - path of the equivs control file # | arg: controlfile - path of the equivs control file
# #
# Requires YunoHost version 2.2.4 or higher. # Requires YunoHost version 2.2.4 or higher.
ynh_package_install_from_equivs() { ynh_package_install_from_equivs () {
local controlfile=$1 local controlfile=$1
# retrieve package information # retrieve package information
local pkgname=$(grep '^Package: ' $controlfile | cut --delimiter=' ' --fields=2) # Retrieve the name of the debian package local pkgname=$(grep '^Package: ' $controlfile | cut --delimiter=' ' --fields=2) # Retrieve the name of the debian package
local pkgversion=$(grep '^Version: ' $controlfile | cut --delimiter=' ' --fields=2) # And its version number local pkgversion=$(grep '^Version: ' $controlfile | cut --delimiter=' ' --fields=2) # And its version number
[[ -z "$pkgname" || -z "$pkgversion" ]] \ [[ -z "$pkgname" || -z "$pkgversion" ]] \
&& ynh_die --message="Invalid control file" # Check if this 2 variables aren't empty. && ynh_die --message="Invalid control file" # Check if this 2 variables aren't empty.
# Update packages cache # Update packages cache
ynh_package_update ynh_package_update
# Build and install the package # Build and install the package
local TMPDIR=$(mktemp --directory) local TMPDIR=$(mktemp --directory)
mkdir -p ${TMPDIR}/${pkgname}/DEBIAN/
# For some reason, dpkg-deb insists for folder perm to be 755 and sometimes it's 777 o_O? # Force the compatibility level at 10, levels below are deprecated
chmod -R 755 ${TMPDIR}/${pkgname} echo 10 > /usr/share/equivs/template/debian/compat
# Note that the cd executes into a sub shell # Note that the cd executes into a sub shell
# Create a fake deb package with equivs-build and the given control file # Create a fake deb package with equivs-build and the given control file
# Install the fake package without its dependencies with dpkg # Install the fake package without its dependencies with dpkg
# Install missing dependencies with ynh_package_install # Install missing dependencies with ynh_package_install
ynh_wait_dpkg_free ynh_wait_dpkg_free
cp "$controlfile" "${TMPDIR}/control"
(cd "$TMPDIR"
LC_ALL=C equivs-build ./control 1> /dev/null
LC_ALL=C dpkg --force-depends --install "./${pkgname}_${pkgversion}_all.deb" 2>&1 | tee ./dpkg_log)
cp "$controlfile" "${TMPDIR}/${pkgname}/DEBIAN/control" ynh_package_install --fix-broken || \
{ # If the installation failed
# Install the fake package without its dependencies with dpkg --force-depends # (the following is ran inside { } to not start a subshell otherwise ynh_die wouldnt exit the original process)
if ! LC_ALL=C dpkg-deb --build "${TMPDIR}/${pkgname}" "${TMPDIR}/${pkgname}.deb" > "${TMPDIR}/dpkg_log" 2>&1; then # Parse the list of problematic dependencies from dpkg's log ...
cat "${TMPDIR}/dpkg_log" >&2 # (relevant lines look like: "foo-ynh-deps depends on bar; however:")
ynh_die --message="Unable to install dependencies" local problematic_dependencies="$(cat $TMPDIR/dpkg_log | grep -oP '(?<=-ynh-deps depends on ).*(?=; however)' | tr '\n' ' ')"
fi # Fake an install of those dependencies to see the errors
# Don't crash in case of error, because is nicely covered by the following line # The sed command here is, Print only from 'Reading state info' to the end.
LC_ALL=C dpkg --force-depends --install "${TMPDIR}/${pkgname}.deb" 2>&1 | tee "${TMPDIR}/dpkg_log" || true [[ -n "$problematic_dependencies" ]] && ynh_package_install $problematic_dependencies --dry-run 2>&1 | sed --quiet '/Reading state info/,$p' | grep -v "fix-broken\|Reading state info" >&2
ynh_die --message="Unable to install dependencies"; }
ynh_package_install --fix-broken \ [[ -n "$TMPDIR" ]] && rm --recursive --force $TMPDIR # Remove the temp dir.
|| { # If the installation failed
# (the following is ran inside { } to not start a subshell otherwise ynh_die wouldnt exit the original process)
# Parse the list of problematic dependencies from dpkg's log ...
# (relevant lines look like: "foo-ynh-deps depends on bar; however:")
local problematic_dependencies="$(cat $TMPDIR/dpkg_log | grep -oP '(?<=-ynh-deps depends on ).*(?=; however)' | tr '\n' ' ')"
# Fake an install of those dependencies to see the errors
# The sed command here is, Print only from 'Reading state info' to the end.
[[ -n "$problematic_dependencies" ]] && ynh_package_install $problematic_dependencies --dry-run 2>&1 | sed --quiet '/Reading state info/,$p' | grep -v "fix-broken\|Reading state info" >&2
ynh_die --message="Unable to install dependencies"
}
[[ -n "$TMPDIR" ]] && rm --recursive --force $TMPDIR # Remove the temp dir.
# check if the package is actually installed # check if the package is actually installed
ynh_package_is_installed "$pkgname" ynh_package_is_installed "$pkgname"
@ -236,20 +223,22 @@ YNH_INSTALL_APP_DEPENDENCIES_REPLACE="true"
# | arg: "dep1|dep2|…" - You can specify alternatives. It will require to install (dep1 or dep2, etc). # | arg: "dep1|dep2|…" - You can specify alternatives. It will require to install (dep1 or dep2, etc).
# #
# Requires YunoHost version 2.6.4 or higher. # Requires YunoHost version 2.6.4 or higher.
ynh_install_app_dependencies() { ynh_install_app_dependencies () {
local dependencies=$@ local dependencies=$@
# Add a comma for each space between packages. But not add a comma if the space separate a version specification. (See below) # Add a comma for each space between packages. But not add a comma if the space separate a version specification. (See below)
dependencies="$(echo "$dependencies" | sed 's/\([^\<=\>]\)\ \([^(]\)/\1, \2/g')" dependencies="$(echo "$dependencies" | sed 's/\([^\<=\>]\)\ \([^(]\)/\1, \2/g')"
local dependencies=${dependencies//|/ | } local dependencies=${dependencies//|/ | }
local manifest_path="$YNH_APP_BASEDIR/manifest.json"
local version=$(ynh_read_manifest --manifest_key="version") local version=$(jq -r '.version' "$manifest_path")
if [ -z "${version}" ] || [ "$version" == "null" ]; then if [ -z "${version}" ] || [ "$version" == "null" ]; then
version="1.0" version="1.0"
fi fi
local dep_app=${app//_/-} # Replace all '_' by '-' local dep_app=${app//_/-} # Replace all '_' by '-'
# Handle specific versions # Handle specific versions
if [[ "$dependencies" =~ [\<=\>] ]]; then if [[ "$dependencies" =~ [\<=\>] ]]
then
# Replace version specifications by relationships syntax # Replace version specifications by relationships syntax
# https://www.debian.org/doc/debian-policy/ch-relationships.html # https://www.debian.org/doc/debian-policy/ch-relationships.html
# Sed clarification # Sed clarification
@ -264,92 +253,117 @@ ynh_install_app_dependencies() {
# Check for specific php dependencies which requires sury # Check for specific php dependencies which requires sury
# This grep will for example return "7.4" if dependencies is "foo bar php7.4-pwet php-gni" # This grep will for example return "7.4" if dependencies is "foo bar php7.4-pwet php-gni"
# The (?<=php) syntax corresponds to lookbehind ;) # The (?<=php) syntax corresponds to lookbehind ;)
local specific_php_version=$(echo $dependencies | grep -oP '(?<=php)[0-9.]+(?=-|\>|)' | sort -u) local specific_php_version=$(echo $dependencies | grep -oP '(?<=php)[0-9.]+(?=-|\>)' | sort -u)
if [[ -n "$specific_php_version" ]]; then # Ignore case where the php version found is the one available in debian vanilla
[[ "$specific_php_version" != "$YNH_DEFAULT_PHP_VERSION" ]] || specific_php_version=""
if [[ -n "$specific_php_version" ]]
then
# Cover a small edge case where a packager could have specified "php7.4-pwet php5-gni" which is confusing # Cover a small edge case where a packager could have specified "php7.4-pwet php5-gni" which is confusing
[[ $(echo $specific_php_version | wc -l) -eq 1 ]] \ [[ $(echo $specific_php_version | wc -l) -eq 1 ]] \
|| ynh_die --message="Inconsistent php versions in dependencies ... found : $specific_php_version" || ynh_die --message="Inconsistent php versions in dependencies ... found : $specific_php_version"
dependencies+=", php${specific_php_version}, php${specific_php_version}-fpm, php${specific_php_version}-common" dependencies+=", php${specific_php_version}, php${specific_php_version}-fpm, php${specific_php_version}-common"
local old_phpversion=$(ynh_app_setting_get --app=$app --key=phpversion) ynh_add_sury
# If the PHP version changed, remove the old fpm conf
if [ -n "$old_phpversion" ] && [ "$old_phpversion" != "$specific_php_version" ]; then
local old_php_fpm_config_dir=$(ynh_app_setting_get --app=$app --key=fpm_config_dir)
local old_php_finalphpconf="$old_php_fpm_config_dir/pool.d/$app.conf"
if [[ -f "$old_php_finalphpconf" ]]; then
ynh_backup_if_checksum_is_different --file="$old_php_finalphpconf"
ynh_remove_fpm_config
fi
fi
# Store phpversion into the config of this app
ynh_app_setting_set --app=$app --key=phpversion --value=$specific_php_version
# Set the default php version back as the default version for php-cli.
if test -e /usr/bin/php$YNH_DEFAULT_PHP_VERSION; then
update-alternatives --set php /usr/bin/php$YNH_DEFAULT_PHP_VERSION
fi
elif grep --quiet 'php' <<< "$dependencies"; then
ynh_app_setting_set --app=$app --key=phpversion --value=$YNH_DEFAULT_PHP_VERSION
fi fi
local psql_installed="$(ynh_package_is_installed "postgresql-$PSQL_VERSION" && echo yes || echo no)"
# The first time we run ynh_install_app_dependencies, we will replace the # The first time we run ynh_install_app_dependencies, we will replace the
# entire control file (This is in particular meant to cover the case of # entire control file (This is in particular meant to cover the case of
# upgrade script where ynh_install_app_dependencies is called with this # upgrade script where ynh_install_app_dependencies is called with this
# expected effect) Otherwise, any subsequent call will add dependencies # expected effect) Otherwise, any subsequent call will add dependencies
# to those already present in the equivs control file. # to those already present in the equivs control file.
if [[ $YNH_INSTALL_APP_DEPENDENCIES_REPLACE == "true" ]]; then if [[ $YNH_INSTALL_APP_DEPENDENCIES_REPLACE == "true" ]]
then
YNH_INSTALL_APP_DEPENDENCIES_REPLACE="false" YNH_INSTALL_APP_DEPENDENCIES_REPLACE="false"
else else
local current_dependencies="" local current_dependencies=""
if ynh_package_is_installed --package="${dep_app}-ynh-deps"; then if ynh_package_is_installed --package="${dep_app}-ynh-deps"
then
current_dependencies="$(dpkg-query --show --showformat='${Depends}' ${dep_app}-ynh-deps) " current_dependencies="$(dpkg-query --show --showformat='${Depends}' ${dep_app}-ynh-deps) "
current_dependencies=${current_dependencies// | /|} current_dependencies=${current_dependencies// | /|}
fi fi
dependencies="$current_dependencies, $dependencies" dependencies="$current_dependencies, $dependencies"
fi fi
cat > /tmp/${dep_app}-ynh-deps.control << EOF # Make a control file for equivs-build #
# Epic ugly hack to fix the goddamn dependency nightmare of sury
# Sponsored by the "Djeezusse Fokin Kraiste Why Do Adminsys Has To Be So Fucking Complicated I Should Go Grow Potatoes Instead Of This Shit" collective
# https://github.com/YunoHost/issues/issues/1407
#
# If we require to install php dependency
if grep --quiet 'php' <<< "$dependencies"
then
# And we have packages from sury installed (7.0.33-10+weirdshiftafter instead of 7.0.33-0 on debian)
if dpkg --list | grep "php7.0" | grep --quiet --invert-match "7.0.33-0+deb9"
then
# And sury ain't already in sources.lists
if ! grep --recursive --quiet "^ *deb.*sury" /etc/apt/sources.list*
then
# Re-add sury
ynh_add_sury
fi
fi
fi
cat > /tmp/${dep_app}-ynh-deps.control << EOF # Make a control file for equivs-build
Section: misc Section: misc
Priority: optional Priority: optional
Package: ${dep_app}-ynh-deps Package: ${dep_app}-ynh-deps
Version: ${version} Version: ${version}
Depends: ${dependencies//,,/,} Depends: ${dependencies}
Architecture: all Architecture: all
Maintainer: root@localhost
Description: Fake package for ${app} (YunoHost app) dependencies Description: Fake package for ${app} (YunoHost app) dependencies
This meta-package is only responsible of installing its dependencies. This meta-package is only responsible of installing its dependencies.
EOF EOF
ynh_package_install_from_equivs /tmp/${dep_app}-ynh-deps.control \ ynh_package_install_from_equivs /tmp/${dep_app}-ynh-deps.control \
|| ynh_die --message="Unable to install dependencies" # Install the fake package and its dependencies || ynh_die --message="Unable to install dependencies" # Install the fake package and its dependencies
rm /tmp/${dep_app}-ynh-deps.control rm /tmp/${dep_app}-ynh-deps.control
# Trigger postgresql regenconf if we may have just installed postgresql ynh_app_setting_set --app=$app --key=apt_dependencies --value="$dependencies"
local psql_installed2="$(ynh_package_is_installed "postgresql-$PSQL_VERSION" && echo yes || echo no)"
if [[ "$psql_installed" != "$psql_installed2" ]]; then if [[ -n "$specific_php_version" ]]
yunohost tools regen-conf postgresql then
# Set the default php version back as the default version for php-cli.
update-alternatives --set php /usr/bin/php$YNH_DEFAULT_PHP_VERSION
# Store phpversion into the config of this app
ynh_app_setting_set --app=$app --key=phpversion --value=$specific_php_version
# Integrate new php-fpm service in yunohost
yunohost service add php${specific_php_version}-fpm --log "/var/log/php${phpversion}-fpm.log"
elif grep --quiet 'php' <<< "$dependencies"; then
# Store phpversion into the config of this app
ynh_app_setting_set --app=$app --key=phpversion --value=$YNH_DEFAULT_PHP_VERSION
fi fi
}
# Add sury repository with adequate pin strategy
#
# [internal]
#
# usage: ynh_add_sury
#
ynh_add_sury() {
# Add an extra repository for those packages
ynh_install_extra_repo --repo="https://packages.sury.org/php/ $(ynh_get_debian_release) main" --key="https://packages.sury.org/php/apt.gpg" --name=extra_php_version --priority=600
} }
# Add dependencies to install with ynh_install_app_dependencies # Add dependencies to install with ynh_install_app_dependencies
# #
# [packagingv1]
#
# usage: ynh_add_app_dependencies --package=phpversion [--replace] # usage: ynh_add_app_dependencies --package=phpversion [--replace]
# | arg: -p, --package= - Packages to add as dependencies for the app. # | arg: -p, --package= - Packages to add as dependencies for the app.
# #
# Requires YunoHost version 3.8.1 or higher. # Requires YunoHost version 3.8.1 or higher.
ynh_add_app_dependencies() { ynh_add_app_dependencies () {
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local legacy_args=pr local legacy_args=pr
local -A args_array=([p]=package= [r]=replace) local -A args_array=( [p]=package= [r]=replace)
local package local package
# Manage arguments with getopts # Manage arguments with getopts
ynh_handle_getopts_args "$@" ynh_handle_getopts_args "$@"
@ -365,26 +379,27 @@ ynh_add_app_dependencies() {
# usage: ynh_remove_app_dependencies # usage: ynh_remove_app_dependencies
# #
# Requires YunoHost version 2.6.4 or higher. # Requires YunoHost version 2.6.4 or higher.
ynh_remove_app_dependencies() { ynh_remove_app_dependencies () {
local dep_app=${app//_/-} # Replace all '_' by '-' local dep_app=${app//_/-} # Replace all '_' by '-'
local current_dependencies="" local current_dependencies=""
if ynh_package_is_installed --package="${dep_app}-ynh-deps"; then if ynh_package_is_installed --package="${dep_app}-ynh-deps"
then
current_dependencies="$(dpkg-query --show --showformat='${Depends}' ${dep_app}-ynh-deps) " current_dependencies="$(dpkg-query --show --showformat='${Depends}' ${dep_app}-ynh-deps) "
current_dependencies=${current_dependencies// | /|} current_dependencies=${current_dependencies// | /|}
fi fi
# Edge case where the app dep may be on hold, ynh_package_autopurge ${dep_app}-ynh-deps # Remove the fake package and its dependencies if they not still used.
# cf https://forum.yunohost.org/t/migration-error-cause-of-ffsync/20675/4
if apt-mark showhold | grep -q -w ${dep_app}-ynh-deps; then
apt-mark unhold ${dep_app}-ynh-deps
fi
# Remove the fake package and its dependencies if they not still used. # Check if this app used a specific php version ... in which case we check
# (except if dpkg doesn't know anything about the package, # if the corresponding php-fpm is still there. Otherwise, we remove the
# which should be symptomatic of a failed install, and we don't want bash to report an error) # service from yunohost as well
if dpkg-query --show ${dep_app}-ynh-deps &> /dev/null; then
ynh_package_autopurge ${dep_app}-ynh-deps local specific_php_version=$(echo $current_dependencies | tr '-' ' ' | grep -o -E "\<php[0-9.]+\>" | sed 's/php//g' | sort | uniq)
[[ "$specific_php_version" != "$YNH_DEFAULT_PHP_VERSION" ]] || specific_php_version=""
if [[ -n "$specific_php_version" ]] && ! ynh_package_is_installed --package="php${specific_php_version}-fpm";
then
yunohost service remove php${specific_php_version}-fpm
fi fi
} }
@ -397,10 +412,10 @@ ynh_remove_app_dependencies() {
# | arg: -n, --name= - Name for the files for this repo, $app as default value. # | arg: -n, --name= - Name for the files for this repo, $app as default value.
# #
# Requires YunoHost version 3.8.1 or higher. # Requires YunoHost version 3.8.1 or higher.
ynh_install_extra_app_dependencies() { ynh_install_extra_app_dependencies () {
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local legacy_args=rpkn local legacy_args=rpkn
local -A args_array=([r]=repo= [p]=package= [k]=key= [n]=name=) local -A args_array=( [r]=repo= [p]=package= [k]=key= [n]=name= )
local repo local repo
local package local package
local key local key
@ -411,7 +426,8 @@ ynh_install_extra_app_dependencies() {
key=${key:-} key=${key:-}
# Set a key only if asked # Set a key only if asked
if [ -n "$key" ]; then if [ -n "$key" ]
then
key="--key=$key" key="--key=$key"
fi fi
# Add an extra repository for those packages # Add an extra repository for those packages
@ -420,14 +436,8 @@ ynh_install_extra_app_dependencies() {
# Install requested dependencies from this extra repository. # Install requested dependencies from this extra repository.
ynh_install_app_dependencies "$package" ynh_install_app_dependencies "$package"
# Force to upgrade to the last version...
# Without doing apt install, an already installed dep is not upgraded
local apps_auto_installed="$(apt-mark showauto $package)"
ynh_package_install "$package"
[ -z "$apps_auto_installed" ] || apt-mark auto $apps_auto_installed
# Remove this extra repository after packages are installed # Remove this extra repository after packages are installed
ynh_remove_extra_repo --name=$name ynh_remove_extra_repo --name=$app
} }
# Add an extra repository correctly, pin it and get the key. # Add an extra repository correctly, pin it and get the key.
@ -442,10 +452,10 @@ ynh_install_extra_app_dependencies() {
# | arg: -a, --append - Do not overwrite existing files. # | arg: -a, --append - Do not overwrite existing files.
# #
# Requires YunoHost version 3.8.1 or higher. # Requires YunoHost version 3.8.1 or higher.
ynh_install_extra_repo() { ynh_install_extra_repo () {
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local legacy_args=rkpna local legacy_args=rkpna
local -A args_array=([r]=repo= [k]=key= [p]=priority= [n]=name= [a]=append) local -A args_array=( [r]=repo= [k]=key= [p]=priority= [n]=name= [a]=append )
local repo local repo
local key local key
local priority local priority
@ -458,7 +468,8 @@ ynh_install_extra_repo() {
key=${key:-} key=${key:-}
priority=${priority:-} priority=${priority:-}
if [ $append -eq 1 ]; then if [ $append -eq 1 ]
then
append="--append" append="--append"
wget_append="tee --append" wget_append="tee --append"
else else
@ -466,44 +477,36 @@ ynh_install_extra_repo() {
wget_append="tee" wget_append="tee"
fi fi
if [[ "$key" == "trusted=yes" ]]; then # Split the repository into uri, suite and components.
trusted="--trusted"
else
trusted=""
fi
IFS=', ' read -r -a repo_parts <<< "$repo"
index=0
# Remove "deb " at the beginning of the repo. # Remove "deb " at the beginning of the repo.
if [[ "${repo_parts[0]}" == "deb" ]]; then repo="${repo#deb }"
index=1
fi # Get the uri
uri="${repo_parts[$index]}" local uri="$(echo "$repo" | awk '{ print $1 }')"
index=$((index + 1))
suite="${repo_parts[$index]}" # Get the suite
index=$((index + 1)) local suite="$(echo "$repo" | awk '{ print $2 }')"
# Get the components # Get the components
if (("${#repo_parts[@]}" > 0)); then local component="${repo##$uri $suite }"
component="${repo_parts[*]:$index}"
fi
# Add the repository into sources.list.d # Add the repository into sources.list.d
ynh_add_repo --uri="$uri" --suite="$suite" --component="$component" --name="$name" $append $trusted ynh_add_repo --uri="$uri" --suite="$suite" --component="$component" --name="$name" $append
# Pin the new repo with the default priority, so it won't be used for upgrades. # Pin the new repo with the default priority, so it won't be used for upgrades.
# Build $pin from the uri without http and any sub path # Build $pin from the uri without http and any sub path
local pin="${uri#*://}" local pin="${uri#*://}"
pin="${pin%%/*}" pin="${pin%%/*}"
# Set a priority only if asked # Set a priority only if asked
if [ -n "$priority" ]; then if [ -n "$priority" ]
then
priority="--priority=$priority" priority="--priority=$priority"
fi fi
ynh_pin_repo --package="*" --pin="origin \"$pin\"" $priority --name="$name" $append ynh_pin_repo --package="*" --pin="origin \"$pin\"" $priority --name="$name" $append
# Get the public key for the repo # Get the public key for the repo
if [ -n "$key" ] && [[ "$key" != "trusted=yes" ]]; then if [ -n "$key" ]
then
mkdir --parents "/etc/apt/trusted.gpg.d" mkdir --parents "/etc/apt/trusted.gpg.d"
# Timeout option is here to enforce the timeout on dns query and tcp connect (c.f. man wget) # Timeout option is here to enforce the timeout on dns query and tcp connect (c.f. man wget)
wget --timeout 900 --quiet "$key" --output-document=- | gpg --dearmor | $wget_append /etc/apt/trusted.gpg.d/$name.gpg > /dev/null wget --timeout 900 --quiet "$key" --output-document=- | gpg --dearmor | $wget_append /etc/apt/trusted.gpg.d/$name.gpg > /dev/null
@ -521,10 +524,10 @@ ynh_install_extra_repo() {
# | arg: -n, --name= - Name for the files for this repo, $app as default value. # | arg: -n, --name= - Name for the files for this repo, $app as default value.
# #
# Requires YunoHost version 3.8.1 or higher. # Requires YunoHost version 3.8.1 or higher.
ynh_remove_extra_repo() { ynh_remove_extra_repo () {
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local legacy_args=n local legacy_args=n
local -A args_array=([n]=name=) local -A args_array=( [n]=name= )
local name local name
# Manage arguments with getopts # Manage arguments with getopts
ynh_handle_getopts_args "$@" ynh_handle_getopts_args "$@"
@ -533,14 +536,8 @@ ynh_remove_extra_repo() {
ynh_secure_remove --file="/etc/apt/sources.list.d/$name.list" ynh_secure_remove --file="/etc/apt/sources.list.d/$name.list"
# Sury pinning is managed by the regenconf in the core... # Sury pinning is managed by the regenconf in the core...
[[ "$name" == "extra_php_version" ]] || ynh_secure_remove "/etc/apt/preferences.d/$name" [[ "$name" == "extra_php_version" ]] || ynh_secure_remove "/etc/apt/preferences.d/$name"
if [ -e /etc/apt/trusted.gpg.d/$name.gpg ]; then ynh_secure_remove --file="/etc/apt/trusted.gpg.d/$name.gpg" > /dev/null
ynh_secure_remove --file="/etc/apt/trusted.gpg.d/$name.gpg" ynh_secure_remove --file="/etc/apt/trusted.gpg.d/$name.asc" > /dev/null
fi
# (Do we even create a .asc file anywhere ...?)
if [ -e /etc/apt/trusted.gpg.d/$name.asc ]; then
ynh_secure_remove --file="/etc/apt/trusted.gpg.d/$name.asc"
fi
# Update the list of package to exclude the old repo # Update the list of package to exclude the old repo
ynh_package_update ynh_package_update
@ -556,43 +553,36 @@ ynh_remove_extra_repo() {
# | arg: -c, --component= - Component of the repository. # | arg: -c, --component= - Component of the repository.
# | arg: -n, --name= - Name for the files for this repo, $app as default value. # | arg: -n, --name= - Name for the files for this repo, $app as default value.
# | arg: -a, --append - Do not overwrite existing files. # | arg: -a, --append - Do not overwrite existing files.
# | arg: -t, --trusted - Add trusted=yes to the repository (not recommended)
# #
# Example for a repo like deb http://forge.yunohost.org/debian/ stretch stable # Example for a repo like deb http://forge.yunohost.org/debian/ stretch stable
# uri suite component # uri suite component
# ynh_add_repo --uri=http://forge.yunohost.org/debian/ --suite=stretch --component=stable # ynh_add_repo --uri=http://forge.yunohost.org/debian/ --suite=stretch --component=stable
# #
# Requires YunoHost version 3.8.1 or higher. # Requires YunoHost version 3.8.1 or higher.
ynh_add_repo() { ynh_add_repo () {
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local legacy_args=uscnat local legacy_args=uscna
local -A args_array=([u]=uri= [s]=suite= [c]=component= [n]=name= [a]=append [t]=trusted) local -A args_array=( [u]=uri= [s]=suite= [c]=component= [n]=name= [a]=append )
local uri local uri
local suite local suite
local component local component
local name local name
local append local append
local trusted
# Manage arguments with getopts # Manage arguments with getopts
ynh_handle_getopts_args "$@" ynh_handle_getopts_args "$@"
name="${name:-$app}" name="${name:-$app}"
append=${append:-0} append=${append:-0}
trusted=${trusted:-0}
if [ $append -eq 1 ]; then if [ $append -eq 1 ]
then
append="tee --append" append="tee --append"
else else
append="tee" append="tee"
fi fi
if [[ "$trusted" -eq 1 ]]; then
trust="[trusted=yes]"
else
trust=""
fi
mkdir --parents "/etc/apt/sources.list.d" mkdir --parents "/etc/apt/sources.list.d"
# Add the new repo in sources.list.d # Add the new repo in sources.list.d
echo "deb $trust $uri $suite $component" \ echo "deb $uri $suite $component" \
| $append "/etc/apt/sources.list.d/$name.list" | $append "/etc/apt/sources.list.d/$name.list"
} }
@ -610,10 +600,10 @@ ynh_add_repo() {
# See https://manpages.debian.org/stretch/apt/apt_preferences.5.en.html#How_APT_Interprets_Priorities for information about pinning. # See https://manpages.debian.org/stretch/apt/apt_preferences.5.en.html#How_APT_Interprets_Priorities for information about pinning.
# #
# Requires YunoHost version 3.8.1 or higher. # Requires YunoHost version 3.8.1 or higher.
ynh_pin_repo() { ynh_pin_repo () {
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local legacy_args=pirna local legacy_args=pirna
local -A args_array=([p]=package= [i]=pin= [r]=priority= [n]=name= [a]=append) local -A args_array=( [p]=package= [i]=pin= [r]=priority= [n]=name= [a]=append )
local package local package
local pin local pin
local priority local priority
@ -626,7 +616,8 @@ ynh_pin_repo() {
name="${name:-$app}" name="${name:-$app}"
append=${append:-0} append=${append:-0}
if [ $append -eq 1 ]; then if [ $append -eq 1 ]
then
append="tee --append" append="tee --append"
else else
append="tee" append="tee"
@ -640,5 +631,5 @@ ynh_pin_repo() {
Pin: $pin Pin: $pin
Pin-Priority: $priority Pin-Priority: $priority
" \ " \
| $append "/etc/apt/preferences.d/$name" | $append "/etc/apt/preferences.d/$name"
} }

View file

@ -9,6 +9,7 @@ CAN_BIND=${CAN_BIND:-1}
# | arg: -d, --dest_path= - destination file or directory inside the backup dir # | arg: -d, --dest_path= - destination file or directory inside the backup dir
# | arg: -b, --is_big - Indicate data are big (mail, video, image ...) # | arg: -b, --is_big - Indicate data are big (mail, video, image ...)
# | arg: -m, --not_mandatory - Indicate that if the file is missing, the backup can ignore it. # | arg: -m, --not_mandatory - Indicate that if the file is missing, the backup can ignore it.
# | arg: arg - Deprecated arg
# #
# This helper can be used both in a system backup hook, and in an app backup script # This helper can be used both in a system backup hook, and in an app backup script
# #
@ -66,7 +67,7 @@ ynh_backup() {
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local legacy_args=sdbm local legacy_args=sdbm
local -A args_array=([s]=src_path= [d]=dest_path= [b]=is_big [m]=not_mandatory) local -A args_array=( [s]=src_path= [d]=dest_path= [b]=is_big [m]=not_mandatory )
local src_path local src_path
local dest_path local dest_path
local is_big local is_big
@ -82,8 +83,10 @@ ynh_backup() {
# If backing up core only (used by ynh_backup_before_upgrade), # If backing up core only (used by ynh_backup_before_upgrade),
# don't backup big data items # don't backup big data items
if [ $is_big -eq 1 ] && ([ ${do_not_backup_data:-0} -eq 1 ] || [ $BACKUP_CORE_ONLY -eq 1 ]); then if [ $is_big -eq 1 ] && ( [ ${do_not_backup_data:-0} -eq 1 ] || [ $BACKUP_CORE_ONLY -eq 1 ] )
if [ $BACKUP_CORE_ONLY -eq 1 ]; then then
if [ $BACKUP_CORE_ONLY -eq 1 ]
then
ynh_print_info --message="$src_path will not be saved, because 'BACKUP_CORE_ONLY' is set." ynh_print_info --message="$src_path will not be saved, because 'BACKUP_CORE_ONLY' is set."
else else
ynh_print_info --message="$src_path will not be saved, because 'do_not_backup_data' is set." ynh_print_info --message="$src_path will not be saved, because 'do_not_backup_data' is set."
@ -95,11 +98,14 @@ ynh_backup() {
# Format correctly source and destination paths # Format correctly source and destination paths
# ============================================================================== # ==============================================================================
# Be sure the source path is not empty # Be sure the source path is not empty
if [ ! -e "$src_path" ]; then if [ ! -e "$src_path" ]
then
ynh_print_warn --message="Source path '${src_path}' does not exist" ynh_print_warn --message="Source path '${src_path}' does not exist"
if [ "$not_mandatory" == "0" ]; then if [ "$not_mandatory" == "0" ]
then
# This is a temporary fix for fail2ban config files missing after the migration to stretch. # This is a temporary fix for fail2ban config files missing after the migration to stretch.
if echo "${src_path}" | grep --quiet "/etc/fail2ban"; then if echo "${src_path}" | grep --quiet "/etc/fail2ban"
then
touch "${src_path}" touch "${src_path}"
ynh_print_info --message="The missing file will be replaced by a dummy one for the backup !!!" ynh_print_info --message="The missing file will be replaced by a dummy one for the backup !!!"
else else
@ -117,11 +123,13 @@ ynh_backup() {
# If there is no destination path, initialize it with the source path # If there is no destination path, initialize it with the source path
# relative to "/". # relative to "/".
# eg: src_path=/etc/yunohost -> dest_path=etc/yunohost # eg: src_path=/etc/yunohost -> dest_path=etc/yunohost
if [[ -z "$dest_path" ]]; then if [[ -z "$dest_path" ]]
then
dest_path="${src_path#/}" dest_path="${src_path#/}"
else else
if [[ "${dest_path:0:1}" == "/" ]]; then if [[ "${dest_path:0:1}" == "/" ]]
then
# If the destination path is an absolute path, transform it as a path # If the destination path is an absolute path, transform it as a path
# relative to the current working directory ($YNH_CWD) # relative to the current working directory ($YNH_CWD)
@ -145,7 +153,8 @@ ynh_backup() {
fi fi
# Check if dest_path already exists in tmp archive # Check if dest_path already exists in tmp archive
if [[ -e "${dest_path}" ]]; then if [[ -e "${dest_path}" ]]
then
ynh_print_err --message="Destination path '${dest_path}' already exist" ynh_print_err --message="Destination path '${dest_path}' already exist"
return 1 return 1
fi fi
@ -176,18 +185,19 @@ ynh_backup() {
# usage: ynh_restore # usage: ynh_restore
# #
# Requires YunoHost version 2.6.4 or higher. # Requires YunoHost version 2.6.4 or higher.
ynh_restore() { ynh_restore () {
# Deduce the relative path of $YNH_CWD # Deduce the relative path of $YNH_CWD
local REL_DIR="${YNH_CWD#$YNH_BACKUP_DIR/}" local REL_DIR="${YNH_CWD#$YNH_BACKUP_DIR/}"
REL_DIR="${REL_DIR%/}/" REL_DIR="${REL_DIR%/}/"
# For each destination path begining by $REL_DIR # For each destination path begining by $REL_DIR
cat ${YNH_BACKUP_CSV} | tr --delete $'\r' | grep --only-matching --no-filename --perl-regexp "^\".*\",\"$REL_DIR.*\"$" \ cat ${YNH_BACKUP_CSV} | tr --delete $'\r' | grep --only-matching --no-filename --perl-regexp "^\".*\",\"$REL_DIR.*\"$" | \
| while read line; do while read line
local ORIGIN_PATH=$(echo "$line" | grep --only-matching --no-filename --perl-regexp "^\"\K.*(?=\",\".*\"$)") do
local ARCHIVE_PATH=$(echo "$line" | grep --only-matching --no-filename --perl-regexp "^\".*\",\"$REL_DIR\K.*(?=\"$)") local ORIGIN_PATH=$(echo "$line" | grep --only-matching --no-filename --perl-regexp "^\"\K.*(?=\",\".*\"$)")
ynh_restore_file --origin_path="$ARCHIVE_PATH" --dest_path="$ORIGIN_PATH" local ARCHIVE_PATH=$(echo "$line" | grep --only-matching --no-filename --perl-regexp "^\".*\",\"$REL_DIR\K.*(?=\"$)")
done ynh_restore_file --origin_path="$ARCHIVE_PATH" --dest_path="$ORIGIN_PATH"
done
} }
# Return the path in the archive where has been stocked the origin path # Return the path in the archive where has been stocked the origin path
@ -195,7 +205,7 @@ ynh_restore() {
# [internal] # [internal]
# #
# usage: _get_archive_path ORIGIN_PATH # usage: _get_archive_path ORIGIN_PATH
_get_archive_path() { _get_archive_path () {
# For security reasons we use csv python library to read the CSV # For security reasons we use csv python library to read the CSV
python3 -c " python3 -c "
import sys import sys
@ -207,7 +217,7 @@ with open(sys.argv[1], 'r') as backup_file:
print(row['dest']) print(row['dest'])
sys.exit(0) sys.exit(0)
raise Exception('Original path for %s not found' % sys.argv[2]) raise Exception('Original path for %s not found' % sys.argv[2])
" "${YNH_BACKUP_CSV}" "$1" " "${YNH_BACKUP_CSV}" "$1"
return $? return $?
} }
@ -226,7 +236,7 @@ with open(sys.argv[1], 'r') as backup_file:
# ynh_restore_file -o "conf/nginx.conf" # ynh_restore_file -o "conf/nginx.conf"
# #
# If `DEST_PATH` already exists and is lighter than 500 Mo, a backup will be made in # If `DEST_PATH` already exists and is lighter than 500 Mo, a backup will be made in
# `/var/cache/yunohost/appconfbackup/`. Otherwise, the existing file is removed. # `/home/yunohost.conf/backup/`. Otherwise, the existing file is removed.
# #
# if `apps/$app/etc/nginx/conf.d/$domain.d/$app.conf` exists, restore it into # if `apps/$app/etc/nginx/conf.d/$domain.d/$app.conf` exists, restore it into
# `/etc/nginx/conf.d/$domain.d/$app.conf` # `/etc/nginx/conf.d/$domain.d/$app.conf`
@ -235,10 +245,10 @@ with open(sys.argv[1], 'r') as backup_file:
# #
# Requires YunoHost version 2.6.4 or higher. # Requires YunoHost version 2.6.4 or higher.
# Requires YunoHost version 3.5.0 or higher for the argument --not_mandatory # Requires YunoHost version 3.5.0 or higher for the argument --not_mandatory
ynh_restore_file() { ynh_restore_file () {
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local legacy_args=odm local legacy_args=odm
local -A args_array=([o]=origin_path= [d]=dest_path= [m]=not_mandatory) local -A args_array=( [o]=origin_path= [d]=dest_path= [m]=not_mandatory )
local origin_path local origin_path
local dest_path local dest_path
local not_mandatory local not_mandatory
@ -251,8 +261,10 @@ ynh_restore_file() {
local archive_path="$YNH_CWD${origin_path}" local archive_path="$YNH_CWD${origin_path}"
# If archive_path doesn't exist, search for a corresponding path in CSV # If archive_path doesn't exist, search for a corresponding path in CSV
if [ ! -d "$archive_path" ] && [ ! -f "$archive_path" ] && [ ! -L "$archive_path" ]; then if [ ! -d "$archive_path" ] && [ ! -f "$archive_path" ] && [ ! -L "$archive_path" ]
if [ "$not_mandatory" == "0" ]; then then
if [ "$not_mandatory" == "0" ]
then
archive_path="$YNH_BACKUP_DIR/$(_get_archive_path \"$origin_path\")" archive_path="$YNH_BACKUP_DIR/$(_get_archive_path \"$origin_path\")"
else else
return 0 return 0
@ -260,12 +272,14 @@ ynh_restore_file() {
fi fi
# Move the old directory if it already exists # Move the old directory if it already exists
if [[ -e "${dest_path}" ]]; then if [[ -e "${dest_path}" ]]
then
# Check if the file/dir size is less than 500 Mo # Check if the file/dir size is less than 500 Mo
if [[ $(du --summarize --bytes ${dest_path} | cut --delimiter="/" --fields=1) -le "500000000" ]]; then if [[ $(du --summarize --bytes ${dest_path} | cut --delimiter="/" --fields=1) -le "500000000" ]]
local backup_file="/var/cache/yunohost/appconfbackup/${dest_path}.backup.$(date '+%Y%m%d.%H%M%S')" then
local backup_file="/home/yunohost.conf/backup/${dest_path}.backup.$(date '+%Y%m%d.%H%M%S')"
mkdir --parents "$(dirname "$backup_file")" mkdir --parents "$(dirname "$backup_file")"
mv "${dest_path}" "$backup_file" # Move the current file or directory mv "${dest_path}" "$backup_file" # Move the current file or directory
else else
ynh_secure_remove --file=${dest_path} ynh_secure_remove --file=${dest_path}
fi fi
@ -275,8 +289,10 @@ ynh_restore_file() {
mkdir --parents $(dirname "$dest_path") mkdir --parents $(dirname "$dest_path")
# Do a copy if it's just a mounting point # Do a copy if it's just a mounting point
if mountpoint --quiet $YNH_BACKUP_DIR; then if mountpoint --quiet $YNH_BACKUP_DIR
if [[ -d "${archive_path}" ]]; then then
if [[ -d "${archive_path}" ]]
then
archive_path="${archive_path}/." archive_path="${archive_path}/."
mkdir --parents "$dest_path" mkdir --parents "$dest_path"
fi fi
@ -285,13 +301,18 @@ ynh_restore_file() {
else else
mv "$archive_path" "${dest_path}" mv "$archive_path" "${dest_path}"
fi fi
}
# Boring hack for nginx conf file mapped to php7.3 # Deprecated helper since it's a dangerous one!
# Note that there's no need to patch the fpm config because most php apps #
# will call "ynh_add_fpm_config" during restore, effectively recreating the file from scratch # [internal]
if [[ "${dest_path}" == "/etc/nginx/conf.d/"* ]] && grep 'php7.3.*sock' "${dest_path}"; then #
sed -i 's/php7.3/php7.4/g' "${dest_path}" ynh_bind_or_cp() {
fi local AS_ROOT=${3:-0}
local NO_ROOT=0
[[ "${AS_ROOT}" = "1" ]] || NO_ROOT=1
ynh_print_warn --message="This helper is deprecated, you should use ynh_backup instead"
ynh_backup "$1" "$2" 1
} }
# Calculate and store a file checksum into the app settings # Calculate and store a file checksum into the app settings
@ -302,10 +323,10 @@ ynh_restore_file() {
# $app should be defined when calling this helper # $app should be defined when calling this helper
# #
# Requires YunoHost version 2.6.4 or higher. # Requires YunoHost version 2.6.4 or higher.
ynh_store_file_checksum() { ynh_store_file_checksum () {
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local legacy_args=f local legacy_args=f
local -A args_array=([f]=file= [u]=update_only) local -A args_array=( [f]=file= [u]=update_only )
local file local file
local update_only local update_only
update_only="${update_only:-0}" update_only="${update_only:-0}"
@ -313,28 +334,22 @@ ynh_store_file_checksum() {
# Manage arguments with getopts # Manage arguments with getopts
ynh_handle_getopts_args "$@" ynh_handle_getopts_args "$@"
local checksum_setting_name=checksum_${file//[\/ ]/_} # Replace all '/' and ' ' by '_' local checksum_setting_name=checksum_${file//[\/ ]/_} # Replace all '/' and ' ' by '_'
# If update only, we don't save the new checksum if no old checksum exist # If update only, we don't save the new checksum if no old checksum exist
if [ $update_only -eq 1 ]; then if [ $update_only -eq 1 ] ; then
local checksum_value=$(ynh_app_setting_get --app=$app --key=$checksum_setting_name) local checksum_value=$(ynh_app_setting_get --app=$app --key=$checksum_setting_name)
if [ -z "${checksum_value}" ]; then if [ -z "${checksum_value}" ] ; then
unset backup_file_checksum unset backup_file_checksum
return 0 return 0
fi fi
fi fi
ynh_app_setting_set --app=$app --key=$checksum_setting_name --value=$(md5sum "$file" | cut --delimiter=' ' --fields=1) ynh_app_setting_set --app=$app --key=$checksum_setting_name --value=$(md5sum "$file" | cut --delimiter=' ' --fields=1)
if [ ${PACKAGE_CHECK_EXEC:-0} -eq 1 ]; then
# Using a base64 is in fact more reversible than "replace / and space by _" ... So we can in fact obtain the original file path in an easy reliable way ...
local file_path_base64=$(echo "$file" | base64 -w0)
mkdir -p /var/cache/yunohost/appconfbackup/
cat $file > /var/cache/yunohost/appconfbackup/original_${file_path_base64}
fi
# If backup_file_checksum isn't empty, ynh_backup_if_checksum_is_different has made a backup # If backup_file_checksum isn't empty, ynh_backup_if_checksum_is_different has made a backup
if [ -n "${backup_file_checksum-}" ]; then if [ -n "${backup_file_checksum-}" ]
then
# Print the diff between the previous file and the new one. # Print the diff between the previous file and the new one.
# diff return 1 if the files are different, so the || true # diff return 1 if the files are different, so the || true
diff --report-identical-files --unified --color=always $backup_file_checksum $file >&2 || true diff --report-identical-files --unified --color=always $backup_file_checksum $file >&2 || true
@ -353,33 +368,27 @@ ynh_store_file_checksum() {
# modified config files. # modified config files.
# #
# Requires YunoHost version 2.6.4 or higher. # Requires YunoHost version 2.6.4 or higher.
ynh_backup_if_checksum_is_different() { ynh_backup_if_checksum_is_different () {
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local legacy_args=f local legacy_args=f
local -A args_array=([f]=file=) local -A args_array=( [f]=file= )
local file local file
# Manage arguments with getopts # Manage arguments with getopts
ynh_handle_getopts_args "$@" ynh_handle_getopts_args "$@"
local checksum_setting_name=checksum_${file//[\/ ]/_} # Replace all '/' and ' ' by '_' local checksum_setting_name=checksum_${file//[\/ ]/_} # Replace all '/' and ' ' by '_'
local checksum_value=$(ynh_app_setting_get --app=$app --key=$checksum_setting_name) local checksum_value=$(ynh_app_setting_get --app=$app --key=$checksum_setting_name)
# backup_file_checksum isn't declare as local, so it can be reuse by ynh_store_file_checksum # backup_file_checksum isn't declare as local, so it can be reuse by ynh_store_file_checksum
backup_file_checksum="" backup_file_checksum=""
if [ -n "$checksum_value" ]; then # Proceed only if a value was stored into the app settings if [ -n "$checksum_value" ]
if [ -e $file ] && ! echo "$checksum_value $file" | md5sum --check --status; then # If the checksum is now different then # Proceed only if a value was stored into the app settings
if [ -e $file ] && ! echo "$checksum_value $file" | md5sum --check --status
backup_file_checksum="/var/cache/yunohost/appconfbackup/$file.backup.$(date '+%Y%m%d.%H%M%S')" then # If the checksum is now different
backup_file_checksum="/home/yunohost.conf/backup/$file.backup.$(date '+%Y%m%d.%H%M%S')"
mkdir --parents "$(dirname "$backup_file_checksum")" mkdir --parents "$(dirname "$backup_file_checksum")"
cp --archive "$file" "$backup_file_checksum" # Backup the current file cp --archive "$file" "$backup_file_checksum" # Backup the current file
ynh_print_warn "File $file has been manually modified since the installation or last upgrade. So it has been duplicated in $backup_file_checksum" ynh_print_warn "File $file has been manually modified since the installation or last upgrade. So it has been duplicated in $backup_file_checksum"
echo "$backup_file_checksum" # Return the name of the backup file echo "$backup_file_checksum" # Return the name of the backup file
if [ ${PACKAGE_CHECK_EXEC:-0} -eq 1 ]; then
local file_path_base64=$(echo "$file" | base64 -w0)
if test -e /var/cache/yunohost/appconfbackup/original_${file_path_base64}; then
ynh_print_warn "Diff with the original file:"
diff --report-identical-files --unified --color=always /var/cache/yunohost/appconfbackup/original_${file_path_base64} $file >&2 || true
fi
fi
fi fi
fi fi
} }
@ -392,15 +401,15 @@ ynh_backup_if_checksum_is_different() {
# $app should be defined when calling this helper # $app should be defined when calling this helper
# #
# Requires YunoHost version 3.3.1 or higher. # Requires YunoHost version 3.3.1 or higher.
ynh_delete_file_checksum() { ynh_delete_file_checksum () {
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local legacy_args=f local legacy_args=f
local -A args_array=([f]=file=) local -A args_array=( [f]=file= )
local file local file
# Manage arguments with getopts # Manage arguments with getopts
ynh_handle_getopts_args "$@" ynh_handle_getopts_args "$@"
local checksum_setting_name=checksum_${file//[\/ ]/_} # Replace all '/' and ' ' by '_' local checksum_setting_name=checksum_${file//[\/ ]/_} # Replace all '/' and ' ' by '_'
ynh_app_setting_delete --app=$app --key=$checksum_setting_name ynh_app_setting_delete --app=$app --key=$checksum_setting_name
} }
@ -408,15 +417,13 @@ ynh_delete_file_checksum() {
# #
# [internal] # [internal]
# #
ynh_backup_archive_exists() { ynh_backup_archive_exists () {
yunohost backup list --output-as json --quiet \ yunohost backup list --output-as json --quiet \
| jq -e --arg archive "$1" '.archives | index($archive)' > /dev/null | jq -e --arg archive "$1" '.archives | index($archive)' >/dev/null
} }
# Make a backup in case of failed upgrade # Make a backup in case of failed upgrade
# #
# [packagingv1]
#
# usage: ynh_backup_before_upgrade # usage: ynh_backup_before_upgrade
# #
# Usage in a package script: # Usage in a package script:
@ -429,19 +436,22 @@ ynh_backup_archive_exists() {
# ``` # ```
# #
# Requires YunoHost version 2.7.2 or higher. # Requires YunoHost version 2.7.2 or higher.
ynh_backup_before_upgrade() { ynh_backup_before_upgrade () {
if [ ! -e "/etc/yunohost/apps/$app/scripts/backup" ]; then if [ ! -e "/etc/yunohost/apps/$app/scripts/backup" ]
then
ynh_print_warn --message="This app doesn't have any backup script." ynh_print_warn --message="This app doesn't have any backup script."
return return
fi fi
backup_number=1 backup_number=1
local old_backup_number=2 local old_backup_number=2
local app_bck=${app//_/-} # Replace all '_' by '-' local app_bck=${app//_/-} # Replace all '_' by '-'
NO_BACKUP_UPGRADE=${NO_BACKUP_UPGRADE:-0} NO_BACKUP_UPGRADE=${NO_BACKUP_UPGRADE:-0}
if [ "$NO_BACKUP_UPGRADE" -eq 0 ]; then if [ "$NO_BACKUP_UPGRADE" -eq 0 ]
then
# Check if a backup already exists with the prefix 1 # Check if a backup already exists with the prefix 1
if ynh_backup_archive_exists "$app_bck-pre-upgrade1"; then if ynh_backup_archive_exists "$app_bck-pre-upgrade1"
then
# Prefix becomes 2 to preserve the previous backup # Prefix becomes 2 to preserve the previous backup
backup_number=2 backup_number=2
old_backup_number=1 old_backup_number=1
@ -449,9 +459,11 @@ ynh_backup_before_upgrade() {
# Create backup # Create backup
BACKUP_CORE_ONLY=1 yunohost backup create --apps $app --name $app_bck-pre-upgrade$backup_number --debug BACKUP_CORE_ONLY=1 yunohost backup create --apps $app --name $app_bck-pre-upgrade$backup_number --debug
if [ "$?" -eq 0 ]; then if [ "$?" -eq 0 ]
then
# If the backup succeeded, remove the previous backup # If the backup succeeded, remove the previous backup
if ynh_backup_archive_exists "$app_bck-pre-upgrade$old_backup_number"; then if ynh_backup_archive_exists "$app_bck-pre-upgrade$old_backup_number"
then
# Remove the previous backup only if it exists # Remove the previous backup only if it exists
yunohost backup delete $app_bck-pre-upgrade$old_backup_number > /dev/null yunohost backup delete $app_bck-pre-upgrade$old_backup_number > /dev/null
fi fi
@ -465,8 +477,6 @@ ynh_backup_before_upgrade() {
# Restore a previous backup if the upgrade process failed # Restore a previous backup if the upgrade process failed
# #
# [packagingv1]
#
# usage: ynh_restore_upgradebackup # usage: ynh_restore_upgradebackup
# #
# Usage in a package script: # Usage in a package script:
@ -479,24 +489,22 @@ ynh_backup_before_upgrade() {
# ``` # ```
# #
# Requires YunoHost version 2.7.2 or higher. # Requires YunoHost version 2.7.2 or higher.
ynh_restore_upgradebackup() { ynh_restore_upgradebackup () {
ynh_print_err --message="Upgrade failed." ynh_print_err --message="Upgrade failed."
local app_bck=${app//_/-} # Replace all '_' by '-' local app_bck=${app//_/-} # Replace all '_' by '-'
NO_BACKUP_UPGRADE=${NO_BACKUP_UPGRADE:-0} NO_BACKUP_UPGRADE=${NO_BACKUP_UPGRADE:-0}
if [ "$NO_BACKUP_UPGRADE" -eq 0 ]; then if [ "$NO_BACKUP_UPGRADE" -eq 0 ]
then
# Check if an existing backup can be found before removing and restoring the application. # Check if an existing backup can be found before removing and restoring the application.
if ynh_backup_archive_exists "$app_bck-pre-upgrade$backup_number"; then if ynh_backup_archive_exists "$app_bck-pre-upgrade$backup_number"
then
# Remove the application then restore it # Remove the application then restore it
yunohost app remove $app yunohost app remove $app
# Restore the backup # Restore the backup
yunohost backup restore $app_bck-pre-upgrade$backup_number --apps $app --force --debug yunohost backup restore $app_bck-pre-upgrade$backup_number --apps $app --force --debug
if [[ -d /etc/yunohost/apps/$app ]]; then ynh_die --message="The app was restored to the way it was before the failed upgrade."
ynh_die --message="The app was restored to the way it was before the failed upgrade."
else
ynh_die --message="Uhoh ... Yunohost failed to restore the app to the way it was before the failed upgrade :|"
fi
fi fi
else else
ynh_print_warn --message="\$NO_BACKUP_UPGRADE is set, that means there's no backup to restore. You have to fix this upgrade by yourself !" ynh_print_warn --message="\$NO_BACKUP_UPGRADE is set, that means there's no backup to restore. You have to fix this upgrade by yourself !"

View file

@ -1,53 +1,64 @@
#!/bin/bash #!/bin/bash
_ynh_app_config_get_one() { _ynh_app_config_get_one() {
local short_setting="$1" local short_setting="$1"
local type="$2" local type="$2"
local bind="$3" local bind="$3"
local getter="get__${short_setting}" local getter="get__${short_setting}"
# Get value from getter if exists # Get value from getter if exists
if type -t $getter 2> /dev/null | grep -q '^function$' 2> /dev/null; then if type -t $getter 2>/dev/null | grep -q '^function$' 2>/dev/null;
then
old[$short_setting]="$($getter)" old[$short_setting]="$($getter)"
formats[${short_setting}]="yaml" formats[${short_setting}]="yaml"
elif [[ "$bind" == *"("* ]] && type -t "get__${bind%%(*}" 2> /dev/null | grep -q '^function$' 2> /dev/null; then elif [[ "$bind" == *"("* ]] && type -t "get__${bind%%(*}" 2>/dev/null | grep -q '^function$' 2>/dev/null;
then
old[$short_setting]="$("get__${bind%%(*}" $short_setting $type $bind)" old[$short_setting]="$("get__${bind%%(*}" $short_setting $type $bind)"
formats[${short_setting}]="yaml" formats[${short_setting}]="yaml"
elif [[ "$bind" == "null" ]]; then elif [[ "$bind" == "null" ]]
then
old[$short_setting]="YNH_NULL" old[$short_setting]="YNH_NULL"
# Get value from app settings or from another file # Get value from app settings or from another file
elif [[ "$type" == "file" ]]; then elif [[ "$type" == "file" ]]
if [[ "$bind" == "settings" ]]; then then
if [[ "$bind" == "settings" ]]
then
ynh_die --message="File '${short_setting}' can't be stored in settings" ynh_die --message="File '${short_setting}' can't be stored in settings"
fi fi
old[$short_setting]="$(ls "$bind" 2> /dev/null || echo YNH_NULL)" old[$short_setting]="$(ls "$(echo $bind | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/)" 2> /dev/null || echo YNH_NULL)"
file_hash[$short_setting]="true" file_hash[$short_setting]="true"
# Get multiline text from settings or from a full file # Get multiline text from settings or from a full file
elif [[ "$type" == "text" ]]; then elif [[ "$type" == "text" ]]
if [[ "$bind" == "settings" ]]; then then
if [[ "$bind" == "settings" ]]
then
old[$short_setting]="$(ynh_app_setting_get $app $short_setting)" old[$short_setting]="$(ynh_app_setting_get $app $short_setting)"
elif [[ "$bind" == *":"* ]]; then elif [[ "$bind" == *":"* ]]
then
ynh_die --message="For technical reasons, multiline text '${short_setting}' can't be stored automatically in a variable file, you have to create custom getter/setter" ynh_die --message="For technical reasons, multiline text '${short_setting}' can't be stored automatically in a variable file, you have to create custom getter/setter"
else else
old[$short_setting]="$(cat "$bind" 2> /dev/null || echo YNH_NULL)" old[$short_setting]="$(cat $(echo $bind | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/) 2> /dev/null || echo YNH_NULL)"
fi fi
# Get value from a kind of key/value file # Get value from a kind of key/value file
else else
local bind_after="" local bind_after=""
if [[ "$bind" == "settings" ]]; then if [[ "$bind" == "settings" ]]
then
bind=":/etc/yunohost/apps/$app/settings.yml" bind=":/etc/yunohost/apps/$app/settings.yml"
fi fi
local bind_key_="$(echo "$bind" | cut -d: -f1)" local bind_key_="$(echo "$bind" | cut -d: -f1)"
bind_key_=${bind_key_:-$short_setting} bind_key_=${bind_key_:-$short_setting}
if [[ "$bind_key_" == *">"* ]]; then if [[ "$bind_key_" == *">"* ]];
then
bind_after="$(echo "${bind_key_}" | cut -d'>' -f1)" bind_after="$(echo "${bind_key_}" | cut -d'>' -f1)"
bind_key_="$(echo "${bind_key_}" | cut -d'>' -f2)" bind_key_="$(echo "${bind_key_}" | cut -d'>' -f2)"
fi fi
local bind_file="$(echo "$bind" | cut -d: -f2)" local bind_file="$(echo "$bind" | cut -d: -f2 | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/)"
old[$short_setting]="$(ynh_read_var_in_file --file="${bind_file}" --key="${bind_key_}" --after="${bind_after}")" old[$short_setting]="$(ynh_read_var_in_file --file="${bind_file}" --key="${bind_key_}" --after="${bind_after}")"
fi fi
@ -57,31 +68,39 @@ _ynh_app_config_apply_one() {
local setter="set__${short_setting}" local setter="set__${short_setting}"
local bind="${binds[$short_setting]}" local bind="${binds[$short_setting]}"
local type="${types[$short_setting]}" local type="${types[$short_setting]}"
if [ "${changed[$short_setting]}" == "true" ]; then if [ "${changed[$short_setting]}" == "true" ]
then
# Apply setter if exists # Apply setter if exists
if type -t $setter 2> /dev/null | grep -q '^function$' 2> /dev/null; then if type -t $setter 2>/dev/null | grep -q '^function$' 2>/dev/null;
then
$setter $setter
elif [[ "$bind" == *"("* ]] && type -t "set__${bind%%(*}" 2> /dev/null | grep -q '^function$' 2> /dev/null; then elif [[ "$bind" == *"("* ]] && type -t "set__${bind%%(*}" 2>/dev/null | grep -q '^function$' 2>/dev/null;
then
"set__${bind%%(*}" $short_setting $type $bind "set__${bind%%(*}" $short_setting $type $bind
elif [[ "$bind" == "null" ]]; then elif [[ "$bind" == "null" ]]
return then
continue
# Save in a file # Save in a file
elif [[ "$type" == "file" ]]; then elif [[ "$type" == "file" ]]
if [[ "$bind" == "settings" ]]; then then
if [[ "$bind" == "settings" ]]
then
ynh_die --message="File '${short_setting}' can't be stored in settings" ynh_die --message="File '${short_setting}' can't be stored in settings"
fi fi
local bind_file="$bind" local bind_file="$(echo "$bind" | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/)"
if [[ "${!short_setting}" == "" ]]; then if [[ "${!short_setting}" == "" ]]
then
ynh_backup_if_checksum_is_different --file="$bind_file" ynh_backup_if_checksum_is_different --file="$bind_file"
ynh_secure_remove --file="$bind_file" ynh_secure_remove --file="$bind_file"
ynh_delete_file_checksum --file="$bind_file" ynh_delete_file_checksum --file="$bind_file" --update_only
ynh_print_info --message="File '$bind_file' removed" ynh_print_info --message="File '$bind_file' removed"
else else
ynh_backup_if_checksum_is_different --file="$bind_file" ynh_backup_if_checksum_is_different --file="$bind_file"
if [[ "${!short_setting}" != "$bind_file" ]]; then if [[ "${!short_setting}" != "$bind_file" ]]
then
cp "${!short_setting}" "$bind_file" cp "${!short_setting}" "$bind_file"
fi fi
ynh_store_file_checksum --file="$bind_file" --update_only ynh_store_file_checksum --file="$bind_file" --update_only
@ -89,16 +108,19 @@ _ynh_app_config_apply_one() {
fi fi
# Save value in app settings # Save value in app settings
elif [[ "$bind" == "settings" ]]; then elif [[ "$bind" == "settings" ]]
then
ynh_app_setting_set --app=$app --key=$short_setting --value="${!short_setting}" ynh_app_setting_set --app=$app --key=$short_setting --value="${!short_setting}"
ynh_print_info --message="Configuration key '$short_setting' edited in app settings" ynh_print_info --message="Configuration key '$short_setting' edited in app settings"
# Save multiline text in a file # Save multiline text in a file
elif [[ "$type" == "text" ]]; then elif [[ "$type" == "text" ]]
if [[ "$bind" == *":"* ]]; then then
if [[ "$bind" == *":"* ]]
then
ynh_die --message="For technical reasons, multiline text '${short_setting}' can't be stored automatically in a variable file, you have to create custom getter/setter" ynh_die --message="For technical reasons, multiline text '${short_setting}' can't be stored automatically in a variable file, you have to create custom getter/setter"
fi fi
local bind_file="$bind" local bind_file="$(echo "$bind" | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/)"
ynh_backup_if_checksum_is_different --file="$bind_file" ynh_backup_if_checksum_is_different --file="$bind_file"
echo "${!short_setting}" > "$bind_file" echo "${!short_setting}" > "$bind_file"
ynh_store_file_checksum --file="$bind_file" --update_only ynh_store_file_checksum --file="$bind_file" --update_only
@ -108,12 +130,13 @@ _ynh_app_config_apply_one() {
else else
local bind_after="" local bind_after=""
local bind_key_="$(echo "$bind" | cut -d: -f1)" local bind_key_="$(echo "$bind" | cut -d: -f1)"
if [[ "$bind_key_" == *">"* ]]; then bind_key_=${bind_key_:-$short_setting}
if [[ "$bind_key_" == *">"* ]];
then
bind_after="$(echo "${bind_key_}" | cut -d'>' -f1)" bind_after="$(echo "${bind_key_}" | cut -d'>' -f1)"
bind_key_="$(echo "${bind_key_}" | cut -d'>' -f2)" bind_key_="$(echo "${bind_key_}" | cut -d'>' -f2)"
fi fi
bind_key_=${bind_key_:-$short_setting} local bind_file="$(echo "$bind" | cut -d: -f2 | sed s@__FINALPATH__@$final_path@ | sed s/__APP__/$app/)"
local bind_file="$(echo "$bind" | cut -d: -f2)"
ynh_backup_if_checksum_is_different --file="$bind_file" ynh_backup_if_checksum_is_different --file="$bind_file"
ynh_write_var_in_file --file="${bind_file}" --key="${bind_key_}" --value="${!short_setting}" --after="${bind_after}" ynh_write_var_in_file --file="${bind_file}" --key="${bind_key_}" --value="${!short_setting}" --after="${bind_after}"
@ -126,33 +149,63 @@ _ynh_app_config_apply_one() {
fi fi
fi fi
} }
_ynh_app_config_get() { _ynh_app_config_get() {
for line in $YNH_APP_CONFIG_PANEL_OPTIONS_TYPES_AND_BINDS; do # From settings
local lines
lines=$(python3 << EOL
import toml
from collections import OrderedDict
with open("../config_panel.toml", "r") as f:
file_content = f.read()
loaded_toml = toml.loads(file_content, _dict=OrderedDict)
for panel_name, panel in loaded_toml.items():
if not isinstance(panel, dict): continue
for section_name, section in panel.items():
if not isinstance(section, dict): continue
for name, param in section.items():
if not isinstance(param, dict):
continue
print(';'.join([
name,
param.get('type', 'string'),
param.get('bind', 'settings' if param.get('type', 'string') != 'file' else 'null')
]))
EOL
)
for line in $lines
do
# Split line into short_setting, type and bind # Split line into short_setting, type and bind
IFS='|' read short_setting type bind <<< "$line" IFS=';' read short_setting type bind <<< "$line"
binds[${short_setting}]="$bind" binds[${short_setting}]="$bind"
types[${short_setting}]="$type" types[${short_setting}]="$type"
file_hash[${short_setting}]="" file_hash[${short_setting}]=""
formats[${short_setting}]="" formats[${short_setting}]=""
ynh_app_config_get_one $short_setting $type $bind ynh_app_config_get_one $short_setting $type $bind
done done
} }
_ynh_app_config_apply() { _ynh_app_config_apply() {
for short_setting in "${!old[@]}"; do for short_setting in "${!old[@]}"
do
ynh_app_config_apply_one $short_setting ynh_app_config_apply_one $short_setting
done done
} }
_ynh_app_config_show() { _ynh_app_config_show() {
for short_setting in "${!old[@]}"; do for short_setting in "${!old[@]}"
if [[ "${old[$short_setting]}" != YNH_NULL ]]; then do
if [[ "${formats[$short_setting]}" == "yaml" ]]; then if [[ "${old[$short_setting]}" != YNH_NULL ]]
then
if [[ "${formats[$short_setting]}" == "yaml" ]]
then
ynh_return "${short_setting}:" ynh_return "${short_setting}:"
ynh_return "$(echo "${old[$short_setting]}" | sed 's/^/ /g')" ynh_return "$(echo "${old[$short_setting]}" | sed 's/^/ /g')"
else else
ynh_return "${short_setting}: '$(echo "${old[$short_setting]}" | sed "s/'/''/g" | sed ':a;N;$!ba;s/\n/\n\n/g')'" ynh_return "${short_setting}: "'"'"$(echo "${old[$short_setting]}" | sed 's/"/\\"/g' | sed ':a;N;$!ba;s/\n/\n\n/g')"'"'
fi fi
fi fi
done done
@ -163,39 +216,48 @@ _ynh_app_config_validate() {
ynh_script_progression --message="Checking what changed in the new configuration..." --weight=1 ynh_script_progression --message="Checking what changed in the new configuration..." --weight=1
local nothing_changed=true local nothing_changed=true
local changes_validated=true local changes_validated=true
for short_setting in "${!old[@]}"; do for short_setting in "${!old[@]}"
do
changed[$short_setting]=false changed[$short_setting]=false
if [ -z ${!short_setting+x} ]; then if [ -z ${!short_setting+x} ]
then
# Assign the var with the old value in order to allows multiple # Assign the var with the old value in order to allows multiple
# args validation # args validation
declare -g "$short_setting"="${old[$short_setting]}" declare "$short_setting"="${old[$short_setting]}"
continue continue
fi fi
if [ ! -z "${file_hash[${short_setting}]}" ]; then if [ ! -z "${file_hash[${short_setting}]}" ]
then
file_hash[old__$short_setting]="" file_hash[old__$short_setting]=""
file_hash[new__$short_setting]="" file_hash[new__$short_setting]=""
if [ -f "${old[$short_setting]}" ]; then if [ -f "${old[$short_setting]}" ]
then
file_hash[old__$short_setting]=$(sha256sum "${old[$short_setting]}" | cut -d' ' -f1) file_hash[old__$short_setting]=$(sha256sum "${old[$short_setting]}" | cut -d' ' -f1)
if [ -z "${!short_setting}" ]; then if [ -z "${!short_setting}" ]
then
changed[$short_setting]=true changed[$short_setting]=true
nothing_changed=false nothing_changed=false
fi fi
fi fi
if [ -f "${!short_setting}" ]; then if [ -f "${!short_setting}" ]
then
file_hash[new__$short_setting]=$(sha256sum "${!short_setting}" | cut -d' ' -f1) file_hash[new__$short_setting]=$(sha256sum "${!short_setting}" | cut -d' ' -f1)
if [[ "${file_hash[old__$short_setting]}" != "${file_hash[new__$short_setting]}" ]]; then if [[ "${file_hash[old__$short_setting]}" != "${file_hash[new__$short_setting]}" ]]
then
changed[$short_setting]=true changed[$short_setting]=true
nothing_changed=false nothing_changed=false
fi fi
fi fi
else else
if [[ "${!short_setting}" != "${old[$short_setting]}" ]]; then if [[ "${!short_setting}" != "${old[$short_setting]}" ]]
then
changed[$short_setting]=true changed[$short_setting]=true
nothing_changed=false nothing_changed=false
fi fi
fi fi
done done
if [[ "$nothing_changed" == "true" ]]; then if [[ "$nothing_changed" == "true" ]]
then
ynh_print_info --message="Nothing has changed" ynh_print_info --message="Nothing has changed"
exit 0 exit 0
fi fi
@ -203,15 +265,19 @@ _ynh_app_config_validate() {
# Run validation if something is changed # Run validation if something is changed
ynh_script_progression --message="Validating the new configuration..." --weight=1 ynh_script_progression --message="Validating the new configuration..." --weight=1
for short_setting in "${!old[@]}"; do for short_setting in "${!old[@]}"
do
[[ "${changed[$short_setting]}" == "false" ]] && continue [[ "${changed[$short_setting]}" == "false" ]] && continue
local result="" local result=""
if type -t validate__$short_setting | grep -q '^function$' 2> /dev/null; then if type -t validate__$short_setting | grep -q '^function$' 2>/dev/null;
then
result="$(validate__$short_setting)" result="$(validate__$short_setting)"
elif [[ "$bind" == *"("* ]] && type -t "validate__${bind%%(*}" 2> /dev/null | grep -q '^function$' 2> /dev/null; then elif [[ "$bind" == *"("* ]] && type -t "validate__${bind%%(*}" 2>/dev/null | grep -q '^function$' 2>/dev/null;
then
"validate__${bind%%(*}" $short_setting "validate__${bind%%(*}" $short_setting
fi fi
if [ -n "$result" ]; then if [ -n "$result" ]
then
# #
# Return a yaml such as: # Return a yaml such as:
# #
@ -221,7 +287,8 @@ _ynh_app_config_validate() {
# #
# We use changes_validated to know if this is # We use changes_validated to know if this is
# the first validation error # the first validation error
if [[ "$changes_validated" == true ]]; then if [[ "$changes_validated" == true ]]
then
ynh_return "validation_errors:" ynh_return "validation_errors:"
fi fi
ynh_return " ${short_setting}: \"$result\"" ynh_return " ${short_setting}: \"$result\""
@ -231,7 +298,8 @@ _ynh_app_config_validate() {
# If validation failed, exit the script right now (instead of going into apply) # If validation failed, exit the script right now (instead of going into apply)
# Yunohost core will pick up the errors returned via ynh_return previously # Yunohost core will pick up the errors returned via ynh_return previously
if [[ "$changes_validated" == "false" ]]; then if [[ "$changes_validated" == "false" ]]
then
exit 0 exit 0
fi fi
@ -260,18 +328,6 @@ ynh_app_config_apply() {
_ynh_app_config_apply _ynh_app_config_apply
} }
ynh_app_action_run() {
local runner="run__$1"
# Get value from getter if exists
if type -t "$runner" 2> /dev/null | grep -q '^function$' 2> /dev/null; then
$runner
#ynh_return "result:"
#ynh_return "$(echo "${result}" | sed 's/^/ /g')"
else
ynh_die "No handler defined in app's script for action $1. If you are the maintainer of this app, you should define '$runner'"
fi
}
ynh_app_config_run() { ynh_app_config_run() {
declare -Ag old=() declare -Ag old=()
declare -Ag changed=() declare -Ag changed=()
@ -296,8 +352,6 @@ ynh_app_config_run() {
ynh_app_config_apply ynh_app_config_apply
ynh_script_progression --message="Configuration of $app completed" --last ynh_script_progression --message="Configuration of $app completed" --last
;; ;;
*)
ynh_app_action_run $1
;;
esac esac
} }

View file

@ -8,8 +8,11 @@
# | arg: -m, --max_retry= - Maximum number of retries allowed before banning IP address - default: 3 # | arg: -m, --max_retry= - Maximum number of retries allowed before banning IP address - default: 3
# | arg: -p, --ports= - Ports blocked for a banned IP address - default: http,https # | arg: -p, --ports= - Ports blocked for a banned IP address - default: http,https
# #
# usage 2: ynh_add_fail2ban_config --use_template # -----------------------------------------------------------------------------
#
# usage 2: ynh_add_fail2ban_config --use_template [--others_var="list of others variables to replace"]
# | arg: -t, --use_template - Use this helper in template mode # | arg: -t, --use_template - Use this helper in template mode
# | arg: -v, --others_var= - List of others variables to replace separeted by a space for example : 'var_1 var_2 ...'
# #
# This will use a template in `../conf/f2b_jail.conf` and `../conf/f2b_filter.conf` # This will use a template in `../conf/f2b_jail.conf` and `../conf/f2b_filter.conf`
# See the documentation of `ynh_add_config` for a description of the template # See the documentation of `ynh_add_config` for a description of the template
@ -40,7 +43,9 @@
# ignoreregex = # ignoreregex =
# ``` # ```
# #
# ##### Note about the "failregex" option: # -----------------------------------------------------------------------------
#
# Note about the "failregex" option:
# #
# regex to match the password failure messages in the logfile. The host must be # regex to match the password failure messages in the logfile. The host must be
# matched by a group named "`host`". The tag "`<HOST>`" can be used for standard # matched by a group named "`host`". The tag "`<HOST>`" can be used for standard
@ -49,28 +54,35 @@
# You can find some more explainations about how to make a regex here : # You can find some more explainations about how to make a regex here :
# https://www.fail2ban.org/wiki/index.php/MANUAL_0_8#Filters # https://www.fail2ban.org/wiki/index.php/MANUAL_0_8#Filters
# #
# Note that the logfile need to exist before to call this helper !!
#
# To validate your regex you can test with this command: # To validate your regex you can test with this command:
# ``` # ```
# fail2ban-regex /var/log/YOUR_LOG_FILE_PATH /etc/fail2ban/filter.d/YOUR_APP.conf # fail2ban-regex /var/log/YOUR_LOG_FILE_PATH /etc/fail2ban/filter.d/YOUR_APP.conf
# ``` # ```
# #
# Requires YunoHost version 4.1.0 or higher. # Requires YunoHost version 4.1.0 or higher.
ynh_add_fail2ban_config() { ynh_add_fail2ban_config () {
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local legacy_args=lrmptv local legacy_args=lrmptv
local -A args_array=([l]=logpath= [r]=failregex= [m]=max_retry= [p]=ports= [t]=use_template) local -A args_array=( [l]=logpath= [r]=failregex= [m]=max_retry= [p]=ports= [t]=use_template [v]=others_var=)
local logpath local logpath
local failregex local failregex
local max_retry local max_retry
local ports local ports
local others_var
local use_template local use_template
# Manage arguments with getopts # Manage arguments with getopts
ynh_handle_getopts_args "$@" ynh_handle_getopts_args "$@"
max_retry=${max_retry:-3} max_retry=${max_retry:-3}
ports=${ports:-http,https} ports=${ports:-http,https}
others_var="${others_var:-}"
use_template="${use_template:-0}" use_template="${use_template:-0}"
if [ "$use_template" -ne 1 ]; then [[ -z "$others_var" ]] || ynh_print_warn --message="Packagers: using --others_var is unecessary since YunoHost 4.2"
if [ $use_template -ne 1 ]
then
# Usage 1, no template. Build a config file from scratch. # Usage 1, no template. Build a config file from scratch.
test -n "$logpath" || ynh_die --message="ynh_add_fail2ban_config expects a logfile path as first argument and received nothing." test -n "$logpath" || ynh_die --message="ynh_add_fail2ban_config expects a logfile path as first argument and received nothing."
test -n "$failregex" || ynh_die --message="ynh_add_fail2ban_config expects a failure regex as second argument and received nothing." test -n "$failregex" || ynh_die --message="ynh_add_fail2ban_config expects a failure regex as second argument and received nothing."
@ -82,41 +94,25 @@ port = __PORTS__
filter = __APP__ filter = __APP__
logpath = __LOGPATH__ logpath = __LOGPATH__
maxretry = __MAX_RETRY__ maxretry = __MAX_RETRY__
" > "$YNH_APP_BASEDIR/conf/f2b_jail.conf" " > $YNH_APP_BASEDIR/conf/f2b_jail.conf
echo " echo "
[INCLUDES] [INCLUDES]
before = common.conf before = common.conf
[Definition] [Definition]
failregex = __FAILREGEX__ failregex = __FAILREGEX__
ignoreregex = ignoreregex =
" > "$YNH_APP_BASEDIR/conf/f2b_filter.conf" " > $YNH_APP_BASEDIR/conf/f2b_filter.conf
fi fi
ynh_add_config --template="f2b_jail.conf" --destination="/etc/fail2ban/jail.d/$app.conf" ynh_add_config --template="$YNH_APP_BASEDIR/conf/f2b_jail.conf" --destination="/etc/fail2ban/jail.d/$app.conf"
ynh_add_config --template="f2b_filter.conf" --destination="/etc/fail2ban/filter.d/$app.conf" ynh_add_config --template="$YNH_APP_BASEDIR/conf/f2b_filter.conf" --destination="/etc/fail2ban/filter.d/$app.conf"
# if "$logpath" doesn't exist (as if using --use_template argument), assign
# "$logpath" using the one in the previously generated fail2ban conf file
if [ -z "${logpath:-}" ]; then
# the first sed deletes possibles spaces and the second one extract the path
logpath=$(grep "^logpath" "/etc/fail2ban/jail.d/$app.conf" | sed "s/ //g" | sed "s/logpath=//g")
fi
# Create the folder and logfile if they doesn't exist,
# as fail2ban require an existing logfile before configuration
mkdir -p "/var/log/$app"
if [ ! -f "$logpath" ]; then
touch "$logpath"
fi
# Make sure log folder's permissions are correct
chown -R "$app:$app" "/var/log/$app"
chmod -R u=rwX,g=rX,o= "/var/log/$app"
ynh_systemd_action --service_name=fail2ban --action=reload --line_match="(Started|Reloaded) Fail2Ban Service" --log_path=systemd ynh_systemd_action --service_name=fail2ban --action=reload --line_match="(Started|Reloaded) Fail2Ban Service" --log_path=systemd
local fail2ban_error="$(journalctl --no-hostname --unit=fail2ban | tail --lines=50 | grep "WARNING.*$app.*")" local fail2ban_error="$(journalctl --no-hostname --unit=fail2ban | tail --lines=50 | grep "WARNING.*$app.*")"
if [[ -n "$fail2ban_error" ]]; then if [[ -n "$fail2ban_error" ]]
then
ynh_print_err --message="Fail2ban failed to load the jail for $app" ynh_print_err --message="Fail2ban failed to load the jail for $app"
ynh_print_warn --message="${fail2ban_error#*WARNING}" ynh_print_warn --message="${fail2ban_error#*WARNING}"
fi fi
@ -127,7 +123,7 @@ ignoreregex =
# usage: ynh_remove_fail2ban_config # usage: ynh_remove_fail2ban_config
# #
# Requires YunoHost version 3.5.0 or higher. # Requires YunoHost version 3.5.0 or higher.
ynh_remove_fail2ban_config() { ynh_remove_fail2ban_config () {
ynh_secure_remove --file="/etc/fail2ban/jail.d/$app.conf" ynh_secure_remove --file="/etc/fail2ban/jail.d/$app.conf"
ynh_secure_remove --file="/etc/fail2ban/filter.d/$app.conf" ynh_secure_remove --file="/etc/fail2ban/filter.d/$app.conf"
ynh_systemd_action --service_name=fail2ban --action=reload ynh_systemd_action --service_name=fail2ban --action=reload

View file

@ -20,7 +20,7 @@
# | arg: $@ - Simply "$@" to tranfert all the positionnal arguments to the function # | arg: $@ - Simply "$@" to tranfert all the positionnal arguments to the function
# #
# This helper need an array, named "args_array" with all the arguments used by the helper # This helper need an array, named "args_array" with all the arguments used by the helper
# that want to use ynh_handle_getopts_args # that want to use ynh_handle_getopts_args
# Be carreful, this array has to be an associative array, as the following example: # Be carreful, this array has to be an associative array, as the following example:
# local -A args_array=( [a]=arg1 [b]=arg2= [c]=arg3 ) # local -A args_array=( [a]=arg1 [b]=arg2= [c]=arg3 )
# Let's explain this array: # Let's explain this array:
@ -45,10 +45,11 @@
# e.g. for `my_helper "val1" val2`, arg1 will be filled with val1, and arg2 with val2. # e.g. for `my_helper "val1" val2`, arg1 will be filled with val1, and arg2 with val2.
# #
# Requires YunoHost version 3.2.2 or higher. # Requires YunoHost version 3.2.2 or higher.
ynh_handle_getopts_args() { ynh_handle_getopts_args () {
# Manage arguments only if there's some provided # Manage arguments only if there's some provided
set +o xtrace # set +x set +o xtrace # set +x
if [ $# -ne 0 ]; then if [ $# -ne 0 ]
then
# Store arguments in an array to keep each argument separated # Store arguments in an array to keep each argument separated
local arguments=("$@") local arguments=("$@")
@ -57,12 +58,14 @@ ynh_handle_getopts_args() {
# ${!args_array[@]} is the list of all option_flags in the array (An option_flag is 'u' in [u]=user, user is a value) # ${!args_array[@]} is the list of all option_flags in the array (An option_flag is 'u' in [u]=user, user is a value)
local getopts_parameters="" local getopts_parameters=""
local option_flag="" local option_flag=""
for option_flag in "${!args_array[@]}"; do for option_flag in "${!args_array[@]}"
do
# Concatenate each option_flags of the array to build the string of arguments for getopts # Concatenate each option_flags of the array to build the string of arguments for getopts
# Will looks like 'abcd' for -a -b -c -d # Will looks like 'abcd' for -a -b -c -d
# If the value of an option_flag finish by =, it's an option with additionnal values. (e.g. --user bob or -u bob) # If the value of an option_flag finish by =, it's an option with additionnal values. (e.g. --user bob or -u bob)
# Check the last character of the value associate to the option_flag # Check the last character of the value associate to the option_flag
if [ "${args_array[$option_flag]: -1}" = "=" ]; then if [ "${args_array[$option_flag]: -1}" = "=" ]
then
# For an option with additionnal values, add a ':' after the letter for getopts. # For an option with additionnal values, add a ':' after the letter for getopts.
getopts_parameters="${getopts_parameters}${option_flag}:" getopts_parameters="${getopts_parameters}${option_flag}:"
else else
@ -71,23 +74,25 @@ ynh_handle_getopts_args() {
# Check each argument given to the function # Check each argument given to the function
local arg="" local arg=""
# ${#arguments[@]} is the size of the array # ${#arguments[@]} is the size of the array
for arg in $(seq 0 $((${#arguments[@]} - 1))); do for arg in `seq 0 $(( ${#arguments[@]} - 1 ))`
do
# Escape options' values starting with -. Otherwise the - will be considered as another option. # Escape options' values starting with -. Otherwise the - will be considered as another option.
arguments[arg]="${arguments[arg]//--${args_array[$option_flag]}-/--${args_array[$option_flag]}\\TOBEREMOVED\\-}" arguments[arg]="${arguments[arg]//--${args_array[$option_flag]}-/--${args_array[$option_flag]}\\TOBEREMOVED\\-}"
# And replace long option (value of the option_flag) by the short option, the option_flag itself # And replace long option (value of the option_flag) by the short option, the option_flag itself
# (e.g. for [u]=user, --user will be -u) # (e.g. for [u]=user, --user will be -u)
# Replace long option with = (match the beginning of the argument) # Replace long option with = (match the beginning of the argument)
arguments[arg]="$(printf '%s\n' "${arguments[arg]}" | sed "s/^--${args_array[$option_flag]}/-${option_flag} /")" arguments[arg]="$(echo "${arguments[arg]}" | sed "s/^--${args_array[$option_flag]}/-${option_flag} /")"
# And long option without = (match the whole line) # And long option without = (match the whole line)
arguments[arg]="$(printf '%s\n' "${arguments[arg]}" | sed "s/^--${args_array[$option_flag]%=}$/-${option_flag} /")" arguments[arg]="$(echo "${arguments[arg]}" | sed "s/^--${args_array[$option_flag]%=}$/-${option_flag} /")"
done done
done done
# Read and parse all the arguments # Read and parse all the arguments
# Use a function here, to use standart arguments $@ and be able to use shift. # Use a function here, to use standart arguments $@ and be able to use shift.
parse_arg() { parse_arg () {
# Read all arguments, until no arguments are left # Read all arguments, until no arguments are left
while [ $# -ne 0 ]; do while [ $# -ne 0 ]
do
# Initialize the index of getopts # Initialize the index of getopts
OPTIND=1 OPTIND=1
# Parse with getopts only if the argument begin by -, that means the argument is an option # Parse with getopts only if the argument begin by -, that means the argument is an option
@ -95,9 +100,11 @@ ynh_handle_getopts_args() {
local parameter="" local parameter=""
getopts ":$getopts_parameters" parameter || true getopts ":$getopts_parameters" parameter || true
if [ "$parameter" = "?" ]; then if [ "$parameter" = "?" ]
then
ynh_die --message="Invalid argument: -${OPTARG:-}" ynh_die --message="Invalid argument: -${OPTARG:-}"
elif [ "$parameter" = ":" ]; then elif [ "$parameter" = ":" ]
then
ynh_die --message="-$OPTARG parameter requires an argument." ynh_die --message="-$OPTARG parameter requires an argument."
else else
local shift_value=1 local shift_value=1
@ -108,7 +115,8 @@ ynh_handle_getopts_args() {
local option_var="${args_array[$parameter]%=}" local option_var="${args_array[$parameter]%=}"
# If this option doesn't take values # If this option doesn't take values
# if there's a '=' at the end of the long option name, this option takes values # if there's a '=' at the end of the long option name, this option takes values
if [ "${args_array[$parameter]: -1}" != "=" ]; then if [ "${args_array[$parameter]: -1}" != "=" ]
then
# 'eval ${option_var}' will use the content of 'option_var' # 'eval ${option_var}' will use the content of 'option_var'
eval ${option_var}=1 eval ${option_var}=1
else else
@ -118,35 +126,41 @@ ynh_handle_getopts_args() {
# If the first argument is longer than 2 characters, # If the first argument is longer than 2 characters,
# There's a value attached to the option, in the same array cell # There's a value attached to the option, in the same array cell
if [ ${#all_args[0]} -gt 2 ]; then if [ ${#all_args[0]} -gt 2 ]
then
# Remove the option and the space, so keep only the value itself. # Remove the option and the space, so keep only the value itself.
all_args[0]="${all_args[0]#-${parameter} }" all_args[0]="${all_args[0]#-${parameter} }"
# At this point, if all_args[0] start with "-", then the argument is not well formed # At this point, if all_args[0] start with "-", then the argument is not well formed
if [ "${all_args[0]:0:1}" == "-" ]; then if [ "${all_args[0]:0:1}" == "-" ]
then
ynh_die --message="Argument \"${all_args[0]}\" not valid! Did you use a single \"-\" instead of two?" ynh_die --message="Argument \"${all_args[0]}\" not valid! Did you use a single \"-\" instead of two?"
fi fi
# Reduce the value of shift, because the option has been removed manually # Reduce the value of shift, because the option has been removed manually
shift_value=$((shift_value - 1)) shift_value=$(( shift_value - 1 ))
fi fi
# Declare the content of option_var as a variable. # Declare the content of option_var as a variable.
eval ${option_var}="" eval ${option_var}=""
# Then read the array value per value # Then read the array value per value
local i local i
for i in $(seq 0 $((${#all_args[@]} - 1))); do for i in `seq 0 $(( ${#all_args[@]} - 1 ))`
do
# If this argument is an option, end here. # If this argument is an option, end here.
if [ "${all_args[$i]:0:1}" == "-" ]; then if [ "${all_args[$i]:0:1}" == "-" ]
then
# Ignore the first value of the array, which is the option itself # Ignore the first value of the array, which is the option itself
if [ "$i" -ne 0 ]; then if [ "$i" -ne 0 ]; then
break break
fi fi
else else
# Ignore empty parameters # Ignore empty parameters
if [ -n "${all_args[$i]}" ]; then if [ -n "${all_args[$i]}" ]
then
# Else, add this value to this option # Else, add this value to this option
# Each value will be separated by ';' # Each value will be separated by ';'
if [ -n "${!option_var}" ]; then if [ -n "${!option_var}" ]
then
# If there's already another value for this option, add a ; before adding the new value # If there's already another value for this option, add a ; before adding the new value
eval ${option_var}+="\;" eval ${option_var}+="\;"
fi fi
@ -163,7 +177,7 @@ ynh_handle_getopts_args() {
eval ${option_var}+='"${all_args[$i]}"' eval ${option_var}+='"${all_args[$i]}"'
fi fi
shift_value=$((shift_value + 1)) shift_value=$(( shift_value + 1 ))
fi fi
done done
fi fi
@ -176,23 +190,24 @@ ynh_handle_getopts_args() {
# LEGACY MODE # LEGACY MODE
# Check if there's getopts arguments # Check if there's getopts arguments
if [ "${arguments[0]:0:1}" != "-" ]; then if [ "${arguments[0]:0:1}" != "-" ]
then
# If not, enter in legacy mode and manage the arguments as positionnal ones.. # If not, enter in legacy mode and manage the arguments as positionnal ones..
# Dot not echo, to prevent to go through a helper output. But print only in the log. # Dot not echo, to prevent to go through a helper output. But print only in the log.
set -x set -x; echo "! Helper used in legacy mode !" > /dev/null; set +x
echo "! Helper used in legacy mode !" > /dev/null
set +x
local i local i
for i in $(seq 0 $((${#arguments[@]} - 1))); do for i in `seq 0 $(( ${#arguments[@]} -1 ))`
do
# Try to use legacy_args as a list of option_flag of the array args_array # Try to use legacy_args as a list of option_flag of the array args_array
# Otherwise, fallback to getopts_parameters to get the option_flag. But an associative arrays isn't always sorted in the correct order... # Otherwise, fallback to getopts_parameters to get the option_flag. But an associative arrays isn't always sorted in the correct order...
# Remove all ':' in getopts_parameters # Remove all ':' in getopts_parameters
getopts_parameters=${legacy_args:-${getopts_parameters//:/}} getopts_parameters=${legacy_args:-${getopts_parameters//:}}
# Get the option_flag from getopts_parameters, by using the option_flag according to the position of the argument. # Get the option_flag from getopts_parameters, by using the option_flag according to the position of the argument.
option_flag=${getopts_parameters:$i:1} option_flag=${getopts_parameters:$i:1}
if [ -z "$option_flag" ]; then if [ -z "$option_flag" ]
ynh_print_warn --message="Too many arguments ! \"${arguments[$i]}\" will be ignored." then
continue ynh_print_warn --message="Too many arguments ! \"${arguments[$i]}\" will be ignored."
continue
fi fi
# Use the long option, corresponding to the option_flag, as a variable # Use the long option, corresponding to the option_flag, as a variable
# (e.g. for [u]=user, 'user' will be used as a variable) # (e.g. for [u]=user, 'user' will be used as a variable)

View file

@ -2,8 +2,6 @@
# Get the total or free amount of RAM+swap on the system # Get the total or free amount of RAM+swap on the system
# #
# [packagingv1]
#
# usage: ynh_get_ram [--free|--total] [--ignore_swap|--only_swap] # usage: ynh_get_ram [--free|--total] [--ignore_swap|--only_swap]
# | arg: -f, --free - Count free RAM+swap # | arg: -f, --free - Count free RAM+swap
# | arg: -t, --total - Count total RAM+swap # | arg: -t, --total - Count total RAM+swap
@ -12,10 +10,10 @@
# | ret: the amount of free ram, in MB (MegaBytes) # | ret: the amount of free ram, in MB (MegaBytes)
# #
# Requires YunoHost version 3.8.1 or higher. # Requires YunoHost version 3.8.1 or higher.
ynh_get_ram() { ynh_get_ram () {
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local legacy_args=ftso local legacy_args=ftso
local -A args_array=([f]=free [t]=total [s]=ignore_swap [o]=only_swap) local -A args_array=( [f]=free [t]=total [s]=ignore_swap [o]=only_swap )
local free local free
local total local total
local ignore_swap local ignore_swap
@ -27,34 +25,41 @@ ynh_get_ram() {
free=${free:-0} free=${free:-0}
total=${total:-0} total=${total:-0}
if [ $free -eq $total ]; then if [ $free -eq $total ]
then
ynh_print_warn --message="You have to choose --free or --total when using ynh_get_ram" ynh_print_warn --message="You have to choose --free or --total when using ynh_get_ram"
ram=0 ram=0
# Use the total amount of ram # Use the total amount of ram
elif [ $free -eq 1 ]; then elif [ $free -eq 1 ]
local free_ram=$(LC_ALL=C vmstat --stats --unit M | grep "free memory" | awk '{print $1}') then
local free_swap=$(LC_ALL=C vmstat --stats --unit M | grep "free swap" | awk '{print $1}') local free_ram=$(vmstat --stats --unit M | grep "free memory" | awk '{print $1}')
local free_ram_swap=$((free_ram + free_swap)) local free_swap=$(vmstat --stats --unit M | grep "free swap" | awk '{print $1}')
local free_ram_swap=$(( free_ram + free_swap ))
# Use the total amount of free ram # Use the total amount of free ram
local ram=$free_ram_swap local ram=$free_ram_swap
if [ $ignore_swap -eq 1 ]; then if [ $ignore_swap -eq 1 ]
then
# Use only the amount of free ram # Use only the amount of free ram
ram=$free_ram ram=$free_ram
elif [ $only_swap -eq 1 ]; then elif [ $only_swap -eq 1 ]
then
# Use only the amount of free swap # Use only the amount of free swap
ram=$free_swap ram=$free_swap
fi fi
elif [ $total -eq 1 ]; then elif [ $total -eq 1 ]
local total_ram=$(LC_ALL=C vmstat --stats --unit M | grep "total memory" | awk '{print $1}') then
local total_swap=$(LC_ALL=C vmstat --stats --unit M | grep "total swap" | awk '{print $1}') local total_ram=$(vmstat --stats --unit M | grep "total memory" | awk '{print $1}')
local total_ram_swap=$((total_ram + total_swap)) local total_swap=$(vmstat --stats --unit M | grep "total swap" | awk '{print $1}')
local total_ram_swap=$(( total_ram + total_swap ))
local ram=$total_ram_swap local ram=$total_ram_swap
if [ $ignore_swap -eq 1 ]; then if [ $ignore_swap -eq 1 ]
then
# Use only the amount of free ram # Use only the amount of free ram
ram=$total_ram ram=$total_ram
elif [ $only_swap -eq 1 ]; then elif [ $only_swap -eq 1 ]
then
# Use only the amount of free swap # Use only the amount of free swap
ram=$total_swap ram=$total_swap
fi fi
@ -65,8 +70,6 @@ ynh_get_ram() {
# Return 0 or 1 depending if the system has a given amount of RAM+swap free or total # Return 0 or 1 depending if the system has a given amount of RAM+swap free or total
# #
# [packagingv1]
#
# usage: ynh_require_ram --required=RAM [--free|--total] [--ignore_swap|--only_swap] # usage: ynh_require_ram --required=RAM [--free|--total] [--ignore_swap|--only_swap]
# | arg: -r, --required= - The amount to require, in MB # | arg: -r, --required= - The amount to require, in MB
# | arg: -f, --free - Count free RAM+swap # | arg: -f, --free - Count free RAM+swap
@ -76,10 +79,10 @@ ynh_get_ram() {
# | ret: 1 if the ram is under the requirement, 0 otherwise. # | ret: 1 if the ram is under the requirement, 0 otherwise.
# #
# Requires YunoHost version 3.8.1 or higher. # Requires YunoHost version 3.8.1 or higher.
ynh_require_ram() { ynh_require_ram () {
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local legacy_args=rftso local legacy_args=rftso
local -A args_array=([r]=required= [f]=free [t]=total [s]=ignore_swap [o]=only_swap) local -A args_array=( [r]=required= [f]=free [t]=total [s]=ignore_swap [o]=only_swap )
local required local required
local free local free
local total local total
@ -97,7 +100,8 @@ ynh_require_ram() {
local ram=$(ynh_get_ram $free $total $ignore_swap $only_swap) local ram=$(ynh_get_ram $free $total $ignore_swap $only_swap)
if [ $ram -lt $required ]; then if [ $ram -lt $required ]
then
return 1 return 1
else else
return 0 return 0

View file

@ -10,7 +10,7 @@
ynh_die() { ynh_die() {
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local legacy_args=mc local legacy_args=mc
local -A args_array=([m]=message= [c]=ret_code=) local -A args_array=( [m]=message= [c]=ret_code= )
local message local message
local ret_code local ret_code
# Manage arguments with getopts # Manage arguments with getopts
@ -30,7 +30,7 @@ ynh_die() {
ynh_print_info() { ynh_print_info() {
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local legacy_args=m local legacy_args=m
local -A args_array=([m]=message=) local -A args_array=( [m]=message= )
local message local message
# Manage arguments with getopts # Manage arguments with getopts
ynh_handle_getopts_args "$@" ynh_handle_getopts_args "$@"
@ -38,12 +38,31 @@ ynh_print_info() {
echo "$message" >&$YNH_STDINFO echo "$message" >&$YNH_STDINFO
} }
# Ignore the yunohost-cli log to prevent errors with conditional commands
#
# [internal]
#
# usage: ynh_no_log COMMAND
#
# Simply duplicate the log, execute the yunohost command and replace the log without the result of this command
# It's a very badly hack...
#
# Requires YunoHost version 2.6.4 or higher.
ynh_no_log() {
local ynh_cli_log=/var/log/yunohost/yunohost-cli.log
cp --archive ${ynh_cli_log} ${ynh_cli_log}-move
eval $@
local exit_code=$?
mv ${ynh_cli_log}-move ${ynh_cli_log}
return $exit_code
}
# Main printer, just in case in the future we have to change anything about that. # Main printer, just in case in the future we have to change anything about that.
# #
# [internal] # [internal]
# #
# Requires YunoHost version 3.2.0 or higher. # Requires YunoHost version 3.2.0 or higher.
ynh_print_log() { ynh_print_log () {
echo -e "${1}" echo -e "${1}"
} }
@ -53,10 +72,10 @@ ynh_print_log() {
# | arg: -m, --message= - The text to print # | arg: -m, --message= - The text to print
# #
# Requires YunoHost version 3.2.0 or higher. # Requires YunoHost version 3.2.0 or higher.
ynh_print_warn() { ynh_print_warn () {
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local legacy_args=m local legacy_args=m
local -A args_array=([m]=message=) local -A args_array=( [m]=message= )
local message local message
# Manage arguments with getopts # Manage arguments with getopts
ynh_handle_getopts_args "$@" ynh_handle_getopts_args "$@"
@ -70,10 +89,10 @@ ynh_print_warn() {
# | arg: -m, --message= - The text to print # | arg: -m, --message= - The text to print
# #
# Requires YunoHost version 3.2.0 or higher. # Requires YunoHost version 3.2.0 or higher.
ynh_print_err() { ynh_print_err () {
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local legacy_args=m local legacy_args=m
local -A args_array=([m]=message=) local -A args_array=( [m]=message= )
local message local message
# Manage arguments with getopts # Manage arguments with getopts
ynh_handle_getopts_args "$@" ynh_handle_getopts_args "$@"
@ -83,145 +102,91 @@ ynh_print_err() {
# Execute a command and print the result as an error # Execute a command and print the result as an error
# #
# usage: ynh_exec_err your command and args # usage: ynh_exec_err "your_command [ | other_command ]"
# | arg: command - command to execute # | arg: command - command to execute
# #
# Note that you should NOT quote the command but only prefix it with ynh_exec_err # When using pipes, double quotes are required - otherwise, this helper will run the first command, and the whole output will be sent through the next pipe.
#
# If the command to execute uses double quotes, they have to be escaped or they will be interpreted and removed.
# #
# Requires YunoHost version 3.2.0 or higher. # Requires YunoHost version 3.2.0 or higher.
ynh_exec_err() { ynh_exec_err () {
# Boring legacy handling for when people calls ynh_exec_* wrapping the command in quotes, ynh_print_err "$(eval $@)"
# (because in the past eval was used) ...
# we detect this by checking that there's no 2nd arg, and $1 contains a space
if [[ "$#" -eq 1 ]] && [[ "$1" == *" "* ]]; then
ynh_print_err --message="$(eval $@)"
else
# Note that "$@" is used and not $@, c.f. https://unix.stackexchange.com/a/129077
ynh_print_err --message="$("$@")"
fi
} }
# Execute a command and print the result as a warning # Execute a command and print the result as a warning
# #
# usage: ynh_exec_warn your command and args # usage: ynh_exec_warn "your_command [ | other_command ]"
# | arg: command - command to execute # | arg: command - command to execute
# #
# Note that you should NOT quote the command but only prefix it with ynh_exec_warn # When using pipes, double quotes are required - otherwise, this helper will run the first command, and the whole output will be sent through the next pipe.
#
# If the command to execute uses double quotes, they have to be escaped or they will be interpreted and removed.
# #
# Requires YunoHost version 3.2.0 or higher. # Requires YunoHost version 3.2.0 or higher.
ynh_exec_warn() { ynh_exec_warn () {
# Boring legacy handling for when people calls ynh_exec_* wrapping the command in quotes, ynh_print_warn "$(eval $@)"
# (because in the past eval was used) ...
# we detect this by checking that there's no 2nd arg, and $1 contains a space
if [[ "$#" -eq 1 ]] && [[ "$1" == *" "* ]]; then
ynh_print_warn --message="$(eval $@)"
else
# Note that "$@" is used and not $@, c.f. https://unix.stackexchange.com/a/129077
ynh_print_warn --message="$("$@")"
fi
} }
# Execute a command and force the result to be printed on stdout # Execute a command and force the result to be printed on stdout
# #
# usage: ynh_exec_warn_less your command and args # usage: ynh_exec_warn_less "your_command [ | other_command ]"
# | arg: command - command to execute # | arg: command - command to execute
# #
# Note that you should NOT quote the command but only prefix it with ynh_exec_warn # When using pipes, double quotes are required - otherwise, this helper will run the first command, and the whole output will be sent through the next pipe.
#
# If the command to execute uses double quotes, they have to be escaped or they will be interpreted and removed.
# #
# Requires YunoHost version 3.2.0 or higher. # Requires YunoHost version 3.2.0 or higher.
ynh_exec_warn_less() { ynh_exec_warn_less () {
# Boring legacy handling for when people calls ynh_exec_* wrapping the command in quotes, eval $@ 2>&1
# (because in the past eval was used) ...
# we detect this by checking that there's no 2nd arg, and $1 contains a space
if [[ "$#" -eq 1 ]] && [[ "$1" == *" "* ]]; then
eval $@ 2>&1
else
# Note that "$@" is used and not $@, c.f. https://unix.stackexchange.com/a/129077
"$@" 2>&1
fi
} }
# Execute a command and redirect stdout in /dev/null # Execute a command and redirect stdout in /dev/null
# #
# usage: ynh_exec_quiet your command and args # usage: ynh_exec_quiet "your_command [ | other_command ]"
# | arg: command - command to execute # | arg: command - command to execute
# #
# Note that you should NOT quote the command but only prefix it with ynh_exec_warn # When using pipes, double quotes are required - otherwise, this helper will run the first command, and the whole output will be sent through the next pipe.
#
# If the command to execute uses double quotes, they have to be escaped or they will be interpreted and removed.
# #
# Requires YunoHost version 3.2.0 or higher. # Requires YunoHost version 3.2.0 or higher.
ynh_exec_quiet() { ynh_exec_quiet () {
# Boring legacy handling for when people calls ynh_exec_* wrapping the command in quotes, eval $@ > /dev/null
# (because in the past eval was used) ...
# we detect this by checking that there's no 2nd arg, and $1 contains a space
if [[ "$#" -eq 1 ]] && [[ "$1" == *" "* ]]; then
eval $@ > /dev/null
else
# Note that "$@" is used and not $@, c.f. https://unix.stackexchange.com/a/129077
"$@" > /dev/null
fi
} }
# Execute a command and redirect stdout and stderr in /dev/null # Execute a command and redirect stdout and stderr in /dev/null
# #
# usage: ynh_exec_quiet your command and args # usage: ynh_exec_fully_quiet "your_command [ | other_command ]"
# | arg: command - command to execute # | arg: command - command to execute
# #
# Note that you should NOT quote the command but only prefix it with ynh_exec_quiet # When using pipes, double quotes are required - otherwise, this helper will run the first command, and the whole output will be sent through the next pipe.
#
# If the command to execute uses double quotes, they have to be escaped or they will be interpreted and removed.
# #
# Requires YunoHost version 3.2.0 or higher. # Requires YunoHost version 3.2.0 or higher.
ynh_exec_fully_quiet() { ynh_exec_fully_quiet () {
# Boring legacy handling for when people calls ynh_exec_* wrapping the command in quotes, eval $@ > /dev/null 2>&1
# (because in the past eval was used) ...
# we detect this by checking that there's no 2nd arg, and $1 contains a space
if [[ "$#" -eq 1 ]] && [[ "$1" == *" "* ]]; then
eval $@ > /dev/null 2>&1
else
# Note that "$@" is used and not $@, c.f. https://unix.stackexchange.com/a/129077
"$@" > /dev/null 2>&1
fi
}
# Execute a command and redirect stderr in /dev/null. Print stderr on error.
#
# usage: ynh_exec_and_print_stderr_only_if_error your command and args
# | arg: command - command to execute
#
# Note that you should NOT quote the command but only prefix it with ynh_exec_and_print_stderr_only_if_error
#
# Requires YunoHost version 11.2 or higher.
ynh_exec_and_print_stderr_only_if_error() {
logfile="$(mktemp)"
rc=0
# Note that "$@" is used and not $@, c.f. https://unix.stackexchange.com/a/129077
"$@" 2> "$logfile" || rc="$?"
if ((rc != 0)); then
ynh_exec_warn cat "$logfile"
ynh_secure_remove "$logfile"
return "$rc"
fi
} }
# Remove any logs for all the following commands. # Remove any logs for all the following commands.
# #
# usage: ynh_print_OFF # usage: ynh_print_OFF
# #
# [internal]
#
# WARNING: You should be careful with this helper, and never forget to use ynh_print_ON as soon as possible to restore the logging. # WARNING: You should be careful with this helper, and never forget to use ynh_print_ON as soon as possible to restore the logging.
# #
# Requires YunoHost version 3.2.0 or higher. # Requires YunoHost version 3.2.0 or higher.
ynh_print_OFF() { ynh_print_OFF () {
exec {BASH_XTRACEFD}> /dev/null exec {BASH_XTRACEFD}>/dev/null
} }
# Restore the logging after ynh_print_OFF # Restore the logging after ynh_print_OFF
# #
# usage: ynh_print_ON # usage: ynh_print_ON
# #
# [internal]
#
# Requires YunoHost version 3.2.0 or higher. # Requires YunoHost version 3.2.0 or higher.
ynh_print_ON() { ynh_print_ON () {
exec {BASH_XTRACEFD}>&1 exec {BASH_XTRACEFD}>&1
# Print an echo only for the log, to be able to know that ynh_print_ON has been called. # Print an echo only for the log, to be able to know that ynh_print_ON has been called.
echo ynh_print_ON > /dev/null echo ynh_print_ON > /dev/null
@ -249,11 +214,11 @@ base_time=$(date +%s)
# | arg: -l, --last - Use for the last call of the helper, to fill the progression bar. # | arg: -l, --last - Use for the last call of the helper, to fill the progression bar.
# #
# Requires YunoHost version 3.5.0 or higher. # Requires YunoHost version 3.5.0 or higher.
ynh_script_progression() { ynh_script_progression () {
set +o xtrace # set +x set +o xtrace # set +x
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local legacy_args=mwtl local legacy_args=mwtl
local -A args_array=([m]=message= [w]=weight= [t]=time [l]=last) local -A args_array=( [m]=message= [w]=weight= [t]=time [l]=last )
local message local message
local weight local weight
local time local time
@ -263,22 +228,16 @@ ynh_script_progression() {
# Re-disable xtrace, ynh_handle_getopts_args set it back # Re-disable xtrace, ynh_handle_getopts_args set it back
set +o xtrace # set +x set +o xtrace # set +x
weight=${weight:-1} weight=${weight:-1}
time=${time:-0}
# Always activate time when running inside CI tests
if [ ${PACKAGE_CHECK_EXEC:-0} -eq 1 ]; then
time=${time:-1}
else
time=${time:-0}
fi
last=${last:-0} last=${last:-0}
# Get execution time since the last $base_time # Get execution time since the last $base_time
local exec_time=$(($(date +%s) - $base_time)) local exec_time=$(( $(date +%s) - $base_time ))
base_time=$(date +%s) base_time=$(date +%s)
# Compute $max_progression (if we didn't already) # Compute $max_progression (if we didn't already)
if [ "$max_progression" = -1 ]; then if [ "$max_progression" = -1 ]
then
# Get the number of occurrences of 'ynh_script_progression' in the script. Except those are commented. # Get the number of occurrences of 'ynh_script_progression' in the script. Except those are commented.
local helper_calls="$(grep --count "^[^#]*ynh_script_progression" $0)" local helper_calls="$(grep --count "^[^#]*ynh_script_progression" $0)"
# Get the number of call with a weight value # Get the number of call with a weight value
@ -290,22 +249,23 @@ ynh_script_progression() {
local weight_valuesB="$(grep --perl-regexp "^[^#]*ynh_script_progression.*-w " $0 | sed 's/.*-w[= ]\([[:digit:]]*\).*/\1/g')" local weight_valuesB="$(grep --perl-regexp "^[^#]*ynh_script_progression.*-w " $0 | sed 's/.*-w[= ]\([[:digit:]]*\).*/\1/g')"
# Each value will be on a different line. # Each value will be on a different line.
# Remove each 'end of line' and replace it by a '+' to sum the values. # Remove each 'end of line' and replace it by a '+' to sum the values.
local weight_values=$(($(echo "$weight_valuesA" "$weight_valuesB" | grep -v -E '^\s*$' | tr '\n' '+' | sed 's/+$/+0/g'))) local weight_values=$(( $(echo "$weight_valuesA" | tr '\n' '+') + $(echo "$weight_valuesB" | tr '\n' '+') 0 ))
# max_progression is a total number of calls to this helper. # max_progression is a total number of calls to this helper.
# Less the number of calls with a weight value. # Less the number of calls with a weight value.
# Plus the total of weight values # Plus the total of weight values
max_progression=$(($helper_calls - $weight_calls + $weight_values)) max_progression=$(( $helper_calls - $weight_calls + $weight_values ))
fi fi
# Increment each execution of ynh_script_progression in this script by the weight of the previous call. # Increment each execution of ynh_script_progression in this script by the weight of the previous call.
increment_progression=$(($increment_progression + $previous_weight)) increment_progression=$(( $increment_progression + $previous_weight ))
# Store the weight of the current call in $previous_weight for next call # Store the weight of the current call in $previous_weight for next call
previous_weight=$weight previous_weight=$weight
# Reduce $increment_progression to the size of the scale # Reduce $increment_progression to the size of the scale
if [ $last -eq 0 ]; then if [ $last -eq 0 ]
local effective_progression=$(($increment_progression * $progress_scale / $max_progression)) then
local effective_progression=$(( $increment_progression * $progress_scale / $max_progression ))
# If last is specified, fill immediately the progression_bar # If last is specified, fill immediately the progression_bar
else else
local effective_progression=$progress_scale local effective_progression=$progress_scale
@ -313,18 +273,20 @@ ynh_script_progression() {
# Build $progression_bar from progress_string(0,1,2) according to $effective_progression and the weight of the current task # Build $progression_bar from progress_string(0,1,2) according to $effective_progression and the weight of the current task
# expected_progression is the progression expected after the current task # expected_progression is the progression expected after the current task
local expected_progression="$((($increment_progression + $weight) * $progress_scale / $max_progression - $effective_progression))" local expected_progression="$(( ( $increment_progression + $weight ) * $progress_scale / $max_progression - $effective_progression ))"
if [ $last -eq 1 ]; then if [ $last -eq 1 ]
then
expected_progression=0 expected_progression=0
fi fi
# left_progression is the progression not yet done # left_progression is the progression not yet done
local left_progression="$(($progress_scale - $effective_progression - $expected_progression))" local left_progression="$(( $progress_scale - $effective_progression - $expected_progression ))"
# Build the progression bar with $effective_progression, work done, $expected_progression, current work and $left_progression, work to be done. # Build the progression bar with $effective_progression, work done, $expected_progression, current work and $left_progression, work to be done.
local progression_bar="${progress_string2:0:$effective_progression}${progress_string1:0:$expected_progression}${progress_string0:0:$left_progression}" local progression_bar="${progress_string2:0:$effective_progression}${progress_string1:0:$expected_progression}${progress_string0:0:$left_progression}"
local print_exec_time="" local print_exec_time=""
if [ $time -eq 1 ] && [ "$exec_time" -gt 10 ]; then if [ $time -eq 1 ]
print_exec_time=" [$(bc <<< "scale=1; $exec_time / 60") minutes]" then
print_exec_time=" [$(date +%Hh%Mm,%Ss --date="0 + $exec_time sec")]"
fi fi
ynh_print_info "[$progression_bar] > ${message}${print_exec_time}" ynh_print_info "[$progression_bar] > ${message}${print_exec_time}"
@ -337,6 +299,73 @@ ynh_script_progression() {
# usage: ynh_return somedata # usage: ynh_return somedata
# #
# Requires YunoHost version 3.6.0 or higher. # Requires YunoHost version 3.6.0 or higher.
ynh_return() { ynh_return () {
echo "$1" >> "$YNH_STDRETURN" echo "$1" >> "$YNH_STDRETURN"
} }
# Debugger for app packagers
#
# usage: ynh_debug [--message=message] [--trace=1/0]
# | arg: -m, --message= - The text to print
# | arg: -t, --trace= - Turn on or off the trace of the script. Usefull to trace nonly a small part of a script.
#
# Requires YunoHost version 3.5.0 or higher.
ynh_debug () {
# Disable set xtrace for the helper itself, to not pollute the debug log
set +o xtrace # set +x
# Declare an array to define the options of this helper.
local legacy_args=mt
local -A args_array=( [m]=message= [t]=trace= )
local message
local trace
# Manage arguments with getopts
ynh_handle_getopts_args "$@"
# Re-disable xtrace, ynh_handle_getopts_args set it back
set +o xtrace # set +x
message=${message:-}
trace=${trace:-}
if [ -n "$message" ]
then
ynh_print_log "[Debug] ${message}" >&2
fi
if [ "$trace" == "1" ]
then
ynh_debug --message="Enable debugging"
set +o xtrace # set +x
# Get the current file descriptor of xtrace
old_bash_xtracefd=$BASH_XTRACEFD
# Add the current file name and the line number of any command currently running while tracing.
PS4='$(basename ${BASH_SOURCE[0]})-L${LINENO}: '
# Force xtrace to stderr
BASH_XTRACEFD=2
# Force stdout to stderr
exec 1>&2
fi
if [ "$trace" == "0" ]
then
ynh_debug --message="Disable debugging"
set +o xtrace # set +x
# Put xtrace back to its original fild descriptor
BASH_XTRACEFD=$old_bash_xtracefd
# Restore stdout
exec 1>&1
fi
# Renable set xtrace
set -o xtrace # set -x
}
# Execute a command and print the result as debug
#
# usage: ynh_debug_exec "your_command [ | other_command ]"
# | arg: command - command to execute
#
# When using pipes, double quotes are required - otherwise, this helper will run the first command, and the whole output will be sent through the next pipe.
#
# If the command to execute uses double quotes, they have to be escaped or they will be interpreted and removed.
#
# Requires YunoHost version 3.5.0 or higher.
ynh_debug_exec () {
ynh_debug --message="$(eval $@)"
}

116
data/helpers.d/logrotate Normal file
View file

@ -0,0 +1,116 @@
#!/bin/bash
# Use logrotate to manage the logfile
#
# usage: ynh_use_logrotate [--logfile=/log/file] [--nonappend] [--specific_user=user/group]
# | arg: -l, --logfile= - absolute path of logfile
# | arg: -n, --nonappend - (optional) Replace the config file instead of appending this new config.
# | arg: -u, --specific_user= - run logrotate as the specified user and group. If not specified logrotate is runned as root.
#
# If no `--logfile` is provided, `/var/log/$app` will be used as default.
# `logfile` can point to a directory or a file.
#
# It's possible to use this helper multiple times, each config will be added to
# the same logrotate config file. Unless you use the option `--non-append`
#
# Requires YunoHost version 2.6.4 or higher.
# Requires YunoHost version 3.2.0 or higher for the argument `--specific_user`
ynh_use_logrotate () {
# Declare an array to define the options of this helper.
local legacy_args=lnuya
local -A args_array=( [l]=logfile= [n]=nonappend [u]=specific_user= [y]=non [a]=append )
# [y]=non [a]=append are only for legacy purpose, to not fail on the old option '--non-append'
local logfile
local nonappend
local specific_user
# Manage arguments with getopts
ynh_handle_getopts_args "$@"
logfile="${logfile:-}"
nonappend="${nonappend:-0}"
specific_user="${specific_user:-}"
# LEGACY CODE - PRE GETOPTS
if [ $# -gt 0 ] && [ "$1" == "--non-append" ]
then
nonappend=1
# Destroy this argument for the next command.
shift
elif [ $# -gt 1 ] && [ "$2" == "--non-append" ]
then
nonappend=1
fi
if [ $# -gt 0 ] && [ "$(echo ${1:0:1})" != "-" ]
then
# If the given logfile parameter already exists as a file, or if it ends up with ".log",
# we just want to manage a single file
if [ -f "$1" ] || [ "$(echo ${1##*.})" == "log" ]
then
local logfile=$1
# Otherwise we assume we want to manage a directory and all its .log file inside
else
local logfile=$1/*.log
fi
fi
# LEGACY CODE
local customtee="tee --append"
if [ "$nonappend" -eq 1 ]; then
customtee="tee"
fi
if [ -n "$logfile" ]
then
if [ ! -f "$1" ] && [ "$(echo ${logfile##*.})" != "log" ]; then # Keep only the extension to check if it's a logfile
local logfile="$logfile/*.log" # Else, uses the directory and all logfile into it.
fi
else
logfile="/var/log/${app}/*.log" # Without argument, use a defaut directory in /var/log
fi
local su_directive=""
if [[ -n $specific_user ]]
then
su_directive=" # Run logorotate as specific user - group
su ${specific_user%/*} ${specific_user#*/}"
fi
cat > ./${app}-logrotate << EOF # Build a config file for logrotate
$logfile {
# Rotate if the logfile exceeds 100Mo
size 100M
# Keep 12 old log maximum
rotate 12
# Compress the logs with gzip
compress
# Compress the log at the next cycle. So keep always 2 non compressed logs
delaycompress
# Copy and truncate the log to allow to continue write on it. Instead of move the log.
copytruncate
# Do not do an error if the log is missing
missingok
# Not rotate if the log is empty
notifempty
# Keep old logs in the same dir
noolddir
$su_directive
}
EOF
mkdir --parents $(dirname "$logfile") # Create the log directory, if not exist
cat ${app}-logrotate | $customtee /etc/logrotate.d/$app > /dev/null # Append this config to the existing config file, or replace the whole config file (depending on $customtee)
if ynh_user_exists --username="$app"; then
chown $app:$app "$logfile"
chmod o-rwx "$logfile"
fi
}
# Remove the app's logrotate config.
#
# usage: ynh_remove_logrotate
#
# Requires YunoHost version 2.6.4 or higher.
ynh_remove_logrotate () {
if [ -e "/etc/logrotate.d/$app" ]; then
rm "/etc/logrotate.d/$app"
fi
}

View file

@ -22,7 +22,8 @@ ynh_multimedia_build_main_dir() {
mkdir -p "$MEDIA_DIRECTORY/share/eBook" mkdir -p "$MEDIA_DIRECTORY/share/eBook"
## Création des dossiers utilisateurs ## Création des dossiers utilisateurs
for user in $(yunohost user list --output-as json | jq -r '.users | keys[]'); do for user in $(yunohost user list --output-as json | jq -r '.users | keys[]')
do
mkdir -p "$MEDIA_DIRECTORY/$user" mkdir -p "$MEDIA_DIRECTORY/$user"
mkdir -p "$MEDIA_DIRECTORY/$user/Music" mkdir -p "$MEDIA_DIRECTORY/$user/Music"
mkdir -p "$MEDIA_DIRECTORY/$user/Picture" mkdir -p "$MEDIA_DIRECTORY/$user/Picture"
@ -44,11 +45,11 @@ ynh_multimedia_build_main_dir() {
## Application des droits étendus sur le dossier multimedia. ## Application des droits étendus sur le dossier multimedia.
# Droit d'écriture pour le groupe et le groupe multimedia en acl et droit de lecture pour other: # Droit d'écriture pour le groupe et le groupe multimedia en acl et droit de lecture pour other:
setfacl -RnL -m g:$MEDIA_GROUP:rwX,g::rwX,o:r-X "$MEDIA_DIRECTORY" || true setfacl -RnL -m g:$MEDIA_GROUP:rwX,g::rwX,o:r-X "$MEDIA_DIRECTORY"
# Application de la même règle que précédemment, mais par défaut pour les nouveaux fichiers. # Application de la même règle que précédemment, mais par défaut pour les nouveaux fichiers.
setfacl -RnL -m d:g:$MEDIA_GROUP:rwX,g::rwX,o:r-X "$MEDIA_DIRECTORY" || true setfacl -RnL -m d:g:$MEDIA_GROUP:rwX,g::rwX,o:r-X "$MEDIA_DIRECTORY"
# Réglage du masque par défaut. Qui garantie (en principe...) un droit maximal à rwx. Donc pas de restriction de droits par l'acl. # Réglage du masque par défaut. Qui garantie (en principe...) un droit maximal à rwx. Donc pas de restriction de droits par l'acl.
setfacl -RL -m m::rwx "$MEDIA_DIRECTORY" || true setfacl -RL -m m::rwx "$MEDIA_DIRECTORY"
} }
# Add a directory in yunohost.multimedia # Add a directory in yunohost.multimedia
@ -65,22 +66,22 @@ ynh_multimedia_addfolder() {
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local legacy_args=sd local legacy_args=sd
local -A args_array=([s]=source_dir= [d]=dest_dir=) local -A args_array=( [s]=source_dir= [d]=dest_dir= )
local source_dir local source_dir
local dest_dir local dest_dir
# Manage arguments with getopts # Manage arguments with getopts
ynh_handle_getopts_args "$@" ynh_handle_getopts_args "$@"
# Ajout d'un lien symbolique vers le dossier à partager # Ajout d'un lien symbolique vers le dossier à partager
ln -sfn "$source_dir" "$MEDIA_DIRECTORY/$dest_dir" ln -sfn "$source_dir" "$MEDIA_DIRECTORY/$dest_dir"
## Application des droits étendus sur le dossier ajouté ## Application des droits étendus sur le dossier ajouté
# Droit d'écriture pour le groupe et le groupe multimedia en acl et droit de lecture pour other: # Droit d'écriture pour le groupe et le groupe multimedia en acl et droit de lecture pour other:
setfacl -RnL -m g:$MEDIA_GROUP:rwX,g::rwX,o:r-X "$source_dir" setfacl -RnL -m g:$MEDIA_GROUP:rwX,g::rwX,o:r-X "$source_dir"
# Application de la même règle que précédemment, mais par défaut pour les nouveaux fichiers. # Application de la même règle que précédemment, mais par défaut pour les nouveaux fichiers.
setfacl -RnL -m d:g:$MEDIA_GROUP:rwX,g::rwX,o:r-X "$source_dir" setfacl -RnL -m d:g:$MEDIA_GROUP:rwX,g::rwX,o:r-X "$source_dir"
# Réglage du masque par défaut. Qui garantie (en principe...) un droit maximal à rwx. Donc pas de restriction de droits par l'acl. # Réglage du masque par défaut. Qui garantie (en principe...) un droit maximal à rwx. Donc pas de restriction de droits par l'acl.
setfacl -RL -m m::rwx "$source_dir" setfacl -RL -m m::rwx "$source_dir"
} }
# Allow an user to have an write authorisation in multimedia directories # Allow an user to have an write authorisation in multimedia directories
@ -90,14 +91,14 @@ ynh_multimedia_addfolder() {
# | arg: -u, --user_name= - The name of the user which gain this access. # | arg: -u, --user_name= - The name of the user which gain this access.
# #
# Requires YunoHost version 4.2 or higher. # Requires YunoHost version 4.2 or higher.
ynh_multimedia_addaccess() { ynh_multimedia_addaccess () {
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local legacy_args=u local legacy_args=u
declare -Ar args_array=([u]=user_name=) declare -Ar args_array=( [u]=user_name=)
local user_name local user_name
# Manage arguments with getopts # Manage arguments with getopts
ynh_handle_getopts_args "$@" ynh_handle_getopts_args "$@"
groupadd -f multimedia groupadd -f multimedia
usermod -a -G multimedia $user_name usermod -a -G multimedia $user_name
} }

View file

@ -15,7 +15,7 @@
ynh_mysql_connect_as() { ynh_mysql_connect_as() {
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local legacy_args=upd local legacy_args=upd
local -A args_array=([u]=user= [p]=password= [d]=database=) local -A args_array=( [u]=user= [p]=password= [d]=database= )
local user local user
local password local password
local database local database
@ -36,14 +36,15 @@ ynh_mysql_connect_as() {
ynh_mysql_execute_as_root() { ynh_mysql_execute_as_root() {
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local legacy_args=sd local legacy_args=sd
local -A args_array=([s]=sql= [d]=database=) local -A args_array=( [s]=sql= [d]=database= )
local sql local sql
local database local database
# Manage arguments with getopts # Manage arguments with getopts
ynh_handle_getopts_args "$@" ynh_handle_getopts_args "$@"
database="${database:-}" database="${database:-}"
if [ -n "$database" ]; then if [ -n "$database" ]
then
database="--database=$database" database="--database=$database"
fi fi
@ -60,14 +61,15 @@ ynh_mysql_execute_as_root() {
ynh_mysql_execute_file_as_root() { ynh_mysql_execute_file_as_root() {
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local legacy_args=fd local legacy_args=fd
local -A args_array=([f]=file= [d]=database=) local -A args_array=( [f]=file= [d]=database= )
local file local file
local database local database
# Manage arguments with getopts # Manage arguments with getopts
ynh_handle_getopts_args "$@" ynh_handle_getopts_args "$@"
database="${database:-}" database="${database:-}"
if [ -n "$database" ]; then if [ -n "$database" ]
then
database="--database=$database" database="--database=$database"
fi fi
@ -90,7 +92,8 @@ ynh_mysql_create_db() {
local sql="CREATE DATABASE ${db};" local sql="CREATE DATABASE ${db};"
# grant all privilegies to user # grant all privilegies to user
if [[ $# -gt 1 ]]; then if [[ $# -gt 1 ]]
then
sql+=" GRANT ALL PRIVILEGES ON ${db}.* TO '${2}'@'localhost'" sql+=" GRANT ALL PRIVILEGES ON ${db}.* TO '${2}'@'localhost'"
if [[ -n ${3:-} ]]; then if [[ -n ${3:-} ]]; then
sql+=" IDENTIFIED BY '${3}'" sql+=" IDENTIFIED BY '${3}'"
@ -128,12 +131,12 @@ ynh_mysql_drop_db() {
ynh_mysql_dump_db() { ynh_mysql_dump_db() {
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local legacy_args=d local legacy_args=d
local -A args_array=([d]=database=) local -A args_array=( [d]=database= )
local database local database
# Manage arguments with getopts # Manage arguments with getopts
ynh_handle_getopts_args "$@" ynh_handle_getopts_args "$@"
mysqldump --single-transaction --skip-dump-date --routines "$database" mysqldump --single-transaction --skip-dump-date "$database"
} }
# Create a user # Create a user
@ -152,41 +155,28 @@ ynh_mysql_create_user() {
# Check if a mysql user exists # Check if a mysql user exists
# #
# [internal]
#
# usage: ynh_mysql_user_exists --user=user # usage: ynh_mysql_user_exists --user=user
# | arg: -u, --user= - the user for which to check existence # | arg: -u, --user= - the user for which to check existence
# | ret: 0 if the user exists, 1 otherwise. # | ret: 0 if the user exists, 1 otherwise.
# #
# Requires YunoHost version 2.2.4 or higher. # Requires YunoHost version 2.2.4 or higher.
ynh_mysql_user_exists() { ynh_mysql_user_exists()
{
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local legacy_args=u local legacy_args=u
local -A args_array=([u]=user=) local -A args_array=( [u]=user= )
local user local user
# Manage arguments with getopts # Manage arguments with getopts
ynh_handle_getopts_args "$@" ynh_handle_getopts_args "$@"
if [[ -z $(ynh_mysql_execute_as_root --sql="SELECT User from mysql.user WHERE User = '$user';") ]]; then if [[ -z $(ynh_mysql_execute_as_root --sql="SELECT User from mysql.user WHERE User = '$user';") ]]
then
return 1 return 1
else else
return 0 return 0
fi fi
} }
# Check if a mysql database exists
#
# [internal]
#
# usage: ynh_mysql_database_exists database
# | arg: database - the database for which to check existence
# | exit: Return 1 if the database doesn't exist, 0 otherwise
#
ynh_mysql_database_exists() {
local database=$1
mysqlshow | grep -qE "^|\s+$database\s+|"
}
# Drop a user # Drop a user
# #
# [internal] # [internal]
@ -201,8 +191,6 @@ ynh_mysql_drop_user() {
# Create a database, an user and its password. Then store the password in the app's config # Create a database, an user and its password. Then store the password in the app's config
# #
# [packagingv1]
#
# usage: ynh_mysql_setup_db --db_user=user --db_name=name [--db_pwd=pwd] # usage: ynh_mysql_setup_db --db_user=user --db_name=name [--db_pwd=pwd]
# | arg: -u, --db_user= - Owner of the database # | arg: -u, --db_user= - Owner of the database
# | arg: -n, --db_name= - Name of the database # | arg: -n, --db_name= - Name of the database
@ -212,10 +200,10 @@ ynh_mysql_drop_user() {
# It will also be stored as "`mysqlpwd`" into the app settings. # It will also be stored as "`mysqlpwd`" into the app settings.
# #
# Requires YunoHost version 2.6.4 or higher. # Requires YunoHost version 2.6.4 or higher.
ynh_mysql_setup_db() { ynh_mysql_setup_db () {
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local legacy_args=unp local legacy_args=unp
local -A args_array=([u]=db_user= [n]=db_name= [p]=db_pwd=) local -A args_array=( [u]=db_user= [n]=db_name= [p]=db_pwd= )
local db_user local db_user
local db_name local db_name
db_pwd="" db_pwd=""
@ -233,23 +221,21 @@ ynh_mysql_setup_db() {
# Remove a database if it exists, and the associated user # Remove a database if it exists, and the associated user
# #
# [packagingv1]
#
# usage: ynh_mysql_remove_db --db_user=user --db_name=name # usage: ynh_mysql_remove_db --db_user=user --db_name=name
# | arg: -u, --db_user= - Owner of the database # | arg: -u, --db_user= - Owner of the database
# | arg: -n, --db_name= - Name of the database # | arg: -n, --db_name= - Name of the database
# #
# Requires YunoHost version 2.6.4 or higher. # Requires YunoHost version 2.6.4 or higher.
ynh_mysql_remove_db() { ynh_mysql_remove_db () {
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local legacy_args=un local legacy_args=un
local -Ar args_array=([u]=db_user= [n]=db_name=) local -Ar args_array=( [u]=db_user= [n]=db_name= )
local db_user local db_user
local db_name local db_name
# Manage arguments with getopts # Manage arguments with getopts
ynh_handle_getopts_args "$@" ynh_handle_getopts_args "$@"
if ynh_mysql_database_exists "$db_name"; then if mysqlshow | grep -q "^| $db_name "; then
ynh_mysql_drop_db $db_name ynh_mysql_drop_db $db_name
else else
ynh_print_warn --message="Database $db_name not found" ynh_print_warn --message="Database $db_name not found"

View file

@ -2,8 +2,6 @@
# Find a free port and return it # Find a free port and return it
# #
# [packagingv1]
#
# usage: ynh_find_port --port=begin_port # usage: ynh_find_port --port=begin_port
# | arg: -p, --port= - port to start to search # | arg: -p, --port= - port to start to search
# | ret: the port number # | ret: the port number
@ -11,25 +9,24 @@
# example: port=$(ynh_find_port --port=8080) # example: port=$(ynh_find_port --port=8080)
# #
# Requires YunoHost version 2.6.4 or higher. # Requires YunoHost version 2.6.4 or higher.
ynh_find_port() { ynh_find_port () {
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local legacy_args=p local legacy_args=p
local -A args_array=([p]=port=) local -A args_array=( [p]=port= )
local port local port
# Manage arguments with getopts # Manage arguments with getopts
ynh_handle_getopts_args "$@" ynh_handle_getopts_args "$@"
test -n "$port" || ynh_die --message="The argument of ynh_find_port must be a valid port." test -n "$port" || ynh_die --message="The argument of ynh_find_port must be a valid port."
while ! ynh_port_available --port=$port; do while ! ynh_port_available --port=$port
port=$((port + 1)) do
port=$((port+1))
done done
echo $port echo $port
} }
# Test if a port is available # Test if a port is available
# #
# [packagingv1]
#
# usage: ynh_find_port --port=XYZ # usage: ynh_find_port --port=XYZ
# | arg: -p, --port= - port to check # | arg: -p, --port= - port to check
# | ret: 0 if the port is available, 1 if it is already used by another process. # | ret: 0 if the port is available, 1 if it is already used by another process.
@ -37,25 +34,28 @@ ynh_find_port() {
# example: ynh_port_available --port=1234 || ynh_die --message="Port 1234 is needs to be available for this app" # example: ynh_port_available --port=1234 || ynh_die --message="Port 1234 is needs to be available for this app"
# #
# Requires YunoHost version 3.8.0 or higher. # Requires YunoHost version 3.8.0 or higher.
ynh_port_available() { ynh_port_available () {
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local legacy_args=p local legacy_args=p
local -A args_array=([p]=port=) local -A args_array=( [p]=port= )
local port local port
# Manage arguments with getopts # Manage arguments with getopts
ynh_handle_getopts_args "$@" ynh_handle_getopts_args "$@"
# Check if the port is free # Check if the port is free
if ss --numeric --listening --tcp --udp | awk '{print$5}' | grep --quiet --extended-regexp ":$port$"; then if ss --numeric --listening --tcp --udp | awk '{print$5}' | grep --quiet --extended-regexp ":$port$"
then
return 1 return 1
# This is to cover (most) case where an app is using a port yet ain't currently using it for some reason (typically service ain't up) # This is to cover (most) case where an app is using a port yet ain't currently using it for some reason (typically service ain't up)
elif grep -q "port: '$port'" /etc/yunohost/apps/*/settings.yml; then elif grep -q "port: '$port'" /etc/yunohost/apps/*/settings.yml
then
return 1 return 1
else else
return 0 return 0
fi fi
} }
# Validate an IP address # Validate an IP address
# #
# [internal] # [internal]
@ -66,12 +66,13 @@ ynh_port_available() {
# example: ynh_validate_ip 4 111.222.333.444 # example: ynh_validate_ip 4 111.222.333.444
# #
# Requires YunoHost version 2.2.4 or higher. # Requires YunoHost version 2.2.4 or higher.
ynh_validate_ip() { ynh_validate_ip()
{
# http://stackoverflow.com/questions/319279/how-to-validate-ip-address-in-python#319298 # http://stackoverflow.com/questions/319279/how-to-validate-ip-address-in-python#319298
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local legacy_args=fi local legacy_args=fi
local -A args_array=([f]=family= [i]=ip_address=) local -A args_array=( [f]=family= [i]=ip_address= )
local family local family
local ip_address local ip_address
# Manage arguments with getopts # Manage arguments with getopts
@ -100,10 +101,11 @@ EOF
# example: ynh_validate_ip4 111.222.333.444 # example: ynh_validate_ip4 111.222.333.444
# #
# Requires YunoHost version 2.2.4 or higher. # Requires YunoHost version 2.2.4 or higher.
ynh_validate_ip4() { ynh_validate_ip4()
{
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local legacy_args=i local legacy_args=i
local -A args_array=([i]=ip_address=) local -A args_array=( [i]=ip_address= )
local ip_address local ip_address
# Manage arguments with getopts # Manage arguments with getopts
ynh_handle_getopts_args "$@" ynh_handle_getopts_args "$@"
@ -111,6 +113,7 @@ ynh_validate_ip4() {
ynh_validate_ip --family=4 --ip_address=$ip_address ynh_validate_ip --family=4 --ip_address=$ip_address
} }
# Validate an IPv6 address # Validate an IPv6 address
# #
# usage: ynh_validate_ip6 --ip_address=ip_address # usage: ynh_validate_ip6 --ip_address=ip_address
@ -120,10 +123,11 @@ ynh_validate_ip4() {
# example: ynh_validate_ip6 2000:dead:beef::1 # example: ynh_validate_ip6 2000:dead:beef::1
# #
# Requires YunoHost version 2.2.4 or higher. # Requires YunoHost version 2.2.4 or higher.
ynh_validate_ip6() { ynh_validate_ip6()
{
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local legacy_args=i local legacy_args=i
local -A args_array=([i]=ip_address=) local -A args_array=( [i]=ip_address= )
local ip_address local ip_address
# Manage arguments with getopts # Manage arguments with getopts
ynh_handle_getopts_args "$@" ynh_handle_getopts_args "$@"

View file

@ -16,19 +16,19 @@
# location # location
# #
# Requires YunoHost version 4.1.0 or higher. # Requires YunoHost version 4.1.0 or higher.
ynh_add_nginx_config() { ynh_add_nginx_config () {
local finalnginxconf="/etc/nginx/conf.d/$domain.d/$app.conf" local finalnginxconf="/etc/nginx/conf.d/$domain.d/$app.conf"
ynh_add_config --template="nginx.conf" --destination="$finalnginxconf" if [ "${path_url:-}" != "/" ]
then
if [ "${path_url:-}" != "/" ]; then ynh_replace_string --match_string="^#sub_path_only" --replace_string="" --target_file="$YNH_APP_BASEDIR/conf/nginx.conf"
ynh_replace_string --match_string="^#sub_path_only" --replace_string="" --target_file="$finalnginxconf"
else else
ynh_replace_string --match_string="^#root_path_only" --replace_string="" --target_file="$finalnginxconf" ynh_replace_string --match_string="^#root_path_only" --replace_string="" --target_file="$YNH_APP_BASEDIR/conf/nginx.conf"
fi fi
ynh_store_file_checksum --file="$finalnginxconf" ynh_add_config --template="$YNH_APP_BASEDIR/conf/nginx.conf" --destination="$finalnginxconf"
ynh_systemd_action --service_name=nginx --action=reload ynh_systemd_action --service_name=nginx --action=reload
} }
@ -38,27 +38,7 @@ ynh_add_nginx_config() {
# usage: ynh_remove_nginx_config # usage: ynh_remove_nginx_config
# #
# Requires YunoHost version 2.7.2 or higher. # Requires YunoHost version 2.7.2 or higher.
ynh_remove_nginx_config() { ynh_remove_nginx_config () {
ynh_secure_remove --file="/etc/nginx/conf.d/$domain.d/$app.conf" ynh_secure_remove --file="/etc/nginx/conf.d/$domain.d/$app.conf"
ynh_systemd_action --service_name=nginx --action=reload ynh_systemd_action --service_name=nginx --action=reload
} }
# Regen the nginx config in a change url context
#
# usage: ynh_change_url_nginx_config
#
# Requires YunoHost version 11.1.9 or higher.
ynh_change_url_nginx_config() {
# Make a backup of the original NGINX config file if manually modified
# (nb: this is possibly different from the same instruction called by
# ynh_add_config inside ynh_add_nginx_config because the path may have
# changed if we're changing the domain too...)
local old_nginx_conf_path=/etc/nginx/conf.d/$old_domain.d/$app.conf
ynh_backup_if_checksum_is_different --file="$old_nginx_conf_path"
ynh_delete_file_checksum --file="$old_nginx_conf_path"
ynh_secure_remove --file="$old_nginx_conf_path"
# Regen the nginx conf
ynh_add_nginx_config
}

View file

@ -1,10 +1,30 @@
#!/bin/bash #!/bin/bash
n_version=7.5.0
n_install_dir="/opt/node_n" n_install_dir="/opt/node_n"
node_version_path="$n_install_dir/n/versions/node" node_version_path="$n_install_dir/n/versions/node"
# N_PREFIX is the directory of n, it needs to be loaded as a environment variable. # N_PREFIX is the directory of n, it needs to be loaded as a environment variable.
export N_PREFIX="$n_install_dir" export N_PREFIX="$n_install_dir"
# Install Node version management
#
# [internal]
#
# usage: ynh_install_n
#
# Requires YunoHost version 2.7.12 or higher.
ynh_install_n () {
ynh_print_info --message="Installation of N - Node.js version management"
# Build an app.src for n
echo "SOURCE_URL=https://github.com/tj/n/archive/v${n_version}.tar.gz
SOURCE_SUM=d4da7ea91f680de0c9b5876e097e2a793e8234fcd0f7ca87a0599b925be087a3" > "$YNH_APP_BASEDIR/conf/n.src"
# Download and extract n
ynh_setup_source --dest_dir="$n_install_dir/git" --source_id=n
# Install n
(cd "$n_install_dir/git"
PREFIX=$N_PREFIX make install 2>&1)
}
# Load the version of node for an app, and set variables. # Load the version of node for an app, and set variables.
# #
# usage: ynh_use_nodejs # usage: ynh_use_nodejs
@ -49,7 +69,7 @@ export N_PREFIX="$n_install_dir"
# - $nodejs_version: Just the version number of node for this app. Stored as 'nodejs_version' in settings.yml. # - $nodejs_version: Just the version number of node for this app. Stored as 'nodejs_version' in settings.yml.
# #
# Requires YunoHost version 2.7.12 or higher. # Requires YunoHost version 2.7.12 or higher.
ynh_use_nodejs() { ynh_use_nodejs () {
nodejs_version=$(ynh_app_setting_get --app=$app --key=nodejs_version) nodejs_version=$(ynh_app_setting_get --app=$app --key=nodejs_version)
# Get the absolute path of this version of node # Get the absolute path of this version of node
@ -74,8 +94,6 @@ ynh_use_nodejs() {
ynh_node_load_PATH="PATH=$node_PATH" ynh_node_load_PATH="PATH=$node_PATH"
# Same var but in lower case to be compatible with ynh_replace_vars... # Same var but in lower case to be compatible with ynh_replace_vars...
ynh_node_load_path="PATH=$node_PATH" ynh_node_load_path="PATH=$node_PATH"
# Prevent yet another Node and Corepack madness, with Corepack wanting the user to confirm download of Yarn
export COREPACK_ENABLE_DOWNLOAD_PROMPT=0
} }
# Install a specific version of nodejs # Install a specific version of nodejs
@ -83,7 +101,7 @@ ynh_use_nodejs() {
# ynh_install_nodejs will install the version of node provided as argument by using n. # ynh_install_nodejs will install the version of node provided as argument by using n.
# #
# usage: ynh_install_nodejs --nodejs_version=nodejs_version # usage: ynh_install_nodejs --nodejs_version=nodejs_version
# | arg: -n, --nodejs_version= - Version of node to install. When possible, your should prefer to use major version number (e.g. 8 instead of 8.10.0). # | arg: -n, --nodejs_version= - Version of node to install. When possible, your should prefer to use major version number (e.g. 8 instead of 8.10.0). The crontab will then handle the update of minor versions when needed.
# #
# `n` (Node version management) uses the `PATH` variable to store the path of the version of node it is going to use. # `n` (Node version management) uses the `PATH` variable to store the path of the version of node it is going to use.
# That's how it changes the version # That's how it changes the version
@ -91,12 +109,12 @@ ynh_use_nodejs() {
# Refer to `ynh_use_nodejs` for more information about available commands and variables # Refer to `ynh_use_nodejs` for more information about available commands and variables
# #
# Requires YunoHost version 2.7.12 or higher. # Requires YunoHost version 2.7.12 or higher.
ynh_install_nodejs() { ynh_install_nodejs () {
# Use n, https://github.com/tj/n to manage the nodejs versions # Use n, https://github.com/tj/n to manage the nodejs versions
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local legacy_args=n local legacy_args=n
local -A args_array=([n]=nodejs_version=) local -A args_array=( [n]=nodejs_version= )
local nodejs_version local nodejs_version
# Manage arguments with getopts # Manage arguments with getopts
ynh_handle_getopts_args "$@" ynh_handle_getopts_args "$@"
@ -113,10 +131,16 @@ ynh_install_nodejs() {
test -x /usr/bin/node && mv /usr/bin/node /usr/bin/node_n test -x /usr/bin/node && mv /usr/bin/node /usr/bin/node_n
test -x /usr/bin/npm && mv /usr/bin/npm /usr/bin/npm_n test -x /usr/bin/npm && mv /usr/bin/npm /usr/bin/npm_n
# Install (or update if YunoHost vendor/ folder updated since last install) n # If n is not previously setup, install it
mkdir -p $n_install_dir/bin/ if ! $n_install_dir/bin/n --version > /dev/null 2>&1
cp "$YNH_HELPERS_DIR/vendor/n/n" $n_install_dir/bin/n then
# Tweak for n to understand it's installed in $N_PREFIX ynh_install_n
elif dpkg --compare-versions "$($n_install_dir/bin/n --version)" lt $n_version
then
ynh_install_n
fi
# Modify the default N_PREFIX in n script
ynh_replace_string --match_string="^N_PREFIX=\${N_PREFIX-.*}$" --replace_string="N_PREFIX=\${N_PREFIX-$N_PREFIX}" --target_file="$n_install_dir/bin/n" ynh_replace_string --match_string="^N_PREFIX=\${N_PREFIX-.*}$" --replace_string="N_PREFIX=\${N_PREFIX-$N_PREFIX}" --target_file="$n_install_dir/bin/n"
# Restore /usr/local/bin in PATH # Restore /usr/local/bin in PATH
@ -128,7 +152,8 @@ ynh_install_nodejs() {
# Install the requested version of nodejs # Install the requested version of nodejs
uname=$(uname --machine) uname=$(uname --machine)
if [[ $uname =~ aarch64 || $uname =~ arm64 ]]; then if [[ $uname =~ aarch64 || $uname =~ arm64 ]]
then
n $nodejs_version --arch=arm64 n $nodejs_version --arch=arm64
else else
n $nodejs_version n $nodejs_version
@ -139,16 +164,20 @@ ynh_install_nodejs() {
real_nodejs_version=$(basename $real_nodejs_version) real_nodejs_version=$(basename $real_nodejs_version)
# Create a symbolic link for this major version if the file doesn't already exist # Create a symbolic link for this major version if the file doesn't already exist
if [ ! -e "$node_version_path/$nodejs_version" ]; then if [ ! -e "$node_version_path/$nodejs_version" ]
then
ln --symbolic --force --no-target-directory $node_version_path/$real_nodejs_version $node_version_path/$nodejs_version ln --symbolic --force --no-target-directory $node_version_path/$real_nodejs_version $node_version_path/$nodejs_version
fi fi
# Store the ID of this app and the version of node requested for it # Store the ID of this app and the version of node requested for it
echo "$app:$nodejs_version" | tee --append "$n_install_dir/ynh_app_version" echo "$YNH_APP_INSTANCE_NAME:$nodejs_version" | tee --append "$n_install_dir/ynh_app_version"
# Store nodejs_version into the config of this app # Store nodejs_version into the config of this app
ynh_app_setting_set --app=$app --key=nodejs_version --value=$nodejs_version ynh_app_setting_set --app=$app --key=nodejs_version --value=$nodejs_version
# Build the update script and set the cronjob
ynh_cron_upgrade_node
ynh_use_nodejs ynh_use_nodejs
} }
@ -161,21 +190,80 @@ ynh_install_nodejs() {
# - If no other app uses node, n will be also removed. # - If no other app uses node, n will be also removed.
# #
# Requires YunoHost version 2.7.12 or higher. # Requires YunoHost version 2.7.12 or higher.
ynh_remove_nodejs() { ynh_remove_nodejs () {
nodejs_version=$(ynh_app_setting_get --app=$app --key=nodejs_version) nodejs_version=$(ynh_app_setting_get --app=$app --key=nodejs_version)
# Remove the line for this app # Remove the line for this app
sed --in-place "/$app:$nodejs_version/d" "$n_install_dir/ynh_app_version" sed --in-place "/$YNH_APP_INSTANCE_NAME:$nodejs_version/d" "$n_install_dir/ynh_app_version"
# If no other app uses this version of nodejs, remove it. # If no other app uses this version of nodejs, remove it.
if ! grep --quiet "$nodejs_version" "$n_install_dir/ynh_app_version"; then if ! grep --quiet "$nodejs_version" "$n_install_dir/ynh_app_version"
then
$n_install_dir/bin/n rm $nodejs_version $n_install_dir/bin/n rm $nodejs_version
fi fi
# If no other app uses n, remove n # If no other app uses n, remove n
if [ ! -s "$n_install_dir/ynh_app_version" ]; then if [ ! -s "$n_install_dir/ynh_app_version" ]
then
ynh_secure_remove --file="$n_install_dir" ynh_secure_remove --file="$n_install_dir"
ynh_secure_remove --file="/usr/local/n" ynh_secure_remove --file="/usr/local/n"
sed --in-place "/N_PREFIX/d" /root/.bashrc sed --in-place "/N_PREFIX/d" /root/.bashrc
rm --force /etc/cron.daily/node_update
fi fi
} }
# Set a cron design to update your node versions
#
# [internal]
#
# This cron will check and update all minor node versions used by your apps.
#
# usage: ynh_cron_upgrade_node
#
# Requires YunoHost version 2.7.12 or higher.
ynh_cron_upgrade_node () {
# Build the update script
cat > "$n_install_dir/node_update.sh" << EOF
#!/bin/bash
version_path="$node_version_path"
n_install_dir="$n_install_dir"
# Log the date
date
# List all real installed version of node
all_real_version="\$(find \$version_path/* -maxdepth 0 -type d | sed "s@\$version_path/@@g")"
# Keep only the major version number of each line
all_real_version=\$(echo "\$all_real_version" | sed 's/\..*\$//')
# Remove double entries
all_real_version=\$(echo "\$all_real_version" | sort --unique)
# Read each major version
while read version
do
echo "Update of the version \$version"
sudo \$n_install_dir/bin/n \$version
# Find the last "real" version for this major version of node.
real_nodejs_version=\$(find \$version_path/\$version* -maxdepth 0 | sort --version-sort | tail --lines=1)
real_nodejs_version=\$(basename \$real_nodejs_version)
# Update the symbolic link for this version
sudo ln --symbolic --force --no-target-directory \$version_path/\$real_nodejs_version \$version_path/\$version
done <<< "\$(echo "\$all_real_version")"
EOF
chmod +x "$n_install_dir/node_update.sh"
# Build the cronjob
cat > "/etc/cron.daily/node_update" << EOF
#!/bin/bash
$n_install_dir/node_update.sh >> $n_install_dir/node_update.log
EOF
chmod +x "/etc/cron.daily/node_update"
}

View file

@ -66,7 +66,7 @@
ynh_permission_create() { ynh_permission_create() {
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local legacy_args=puAhaltP local legacy_args=puAhaltP
local -A args_array=([p]=permission= [u]=url= [A]=additional_urls= [h]=auth_header= [a]=allowed= [l]=label= [t]=show_tile= [P]=protected=) local -A args_array=( [p]=permission= [u]=url= [A]=additional_urls= [h]=auth_header= [a]=allowed= [l]=label= [t]=show_tile= [P]=protected= )
local permission local permission
local url local url
local additional_urls local additional_urls
@ -84,11 +84,13 @@ ynh_permission_create() {
show_tile=${show_tile:-} show_tile=${show_tile:-}
protected=${protected:-} protected=${protected:-}
if [[ -n $url ]]; then if [[ -n $url ]]
then
url=",url='$url'" url=",url='$url'"
fi fi
if [[ -n $additional_urls ]]; then if [[ -n $additional_urls ]]
then
# Convert a list from getopts to python list # Convert a list from getopts to python list
# Note that getopts separate the args with ';' # Note that getopts separate the args with ';'
# By example: # By example:
@ -98,15 +100,18 @@ ynh_permission_create() {
additional_urls=",additional_urls=['${additional_urls//;/\',\'}']" additional_urls=",additional_urls=['${additional_urls//;/\',\'}']"
fi fi
if [[ -n $auth_header ]]; then if [[ -n $auth_header ]]
if [ $auth_header == "true" ]; then then
if [ $auth_header == "true" ]
then
auth_header=",auth_header=True" auth_header=",auth_header=True"
else else
auth_header=",auth_header=False" auth_header=",auth_header=False"
fi fi
fi fi
if [[ -n $allowed ]]; then if [[ -n $allowed ]]
then
# Convert a list from getopts to python list # Convert a list from getopts to python list
# Note that getopts separate the args with ';' # Note that getopts separate the args with ';'
# By example: # By example:
@ -122,16 +127,20 @@ ynh_permission_create() {
label=",label='$permission'" label=",label='$permission'"
fi fi
if [[ -n ${show_tile:-} ]]; then if [[ -n ${show_tile:-} ]]
if [ $show_tile == "true" ]; then then
if [ $show_tile == "true" ]
then
show_tile=",show_tile=True" show_tile=",show_tile=True"
else else
show_tile=",show_tile=False" show_tile=",show_tile=False"
fi fi
fi fi
if [[ -n ${protected:-} ]]; then if [[ -n ${protected:-} ]]
if [ $protected == "true" ]; then then
if [ $protected == "true" ]
then
protected=",protected=True" protected=",protected=True"
else else
protected=",protected=False" protected=",protected=False"
@ -152,7 +161,7 @@ ynh_permission_create() {
ynh_permission_delete() { ynh_permission_delete() {
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local legacy_args=p local legacy_args=p
local -A args_array=([p]=permission=) local -A args_array=( [p]=permission= )
local permission local permission
ynh_handle_getopts_args "$@" ynh_handle_getopts_args "$@"
@ -169,12 +178,12 @@ ynh_permission_delete() {
ynh_permission_exists() { ynh_permission_exists() {
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local legacy_args=p local legacy_args=p
local -A args_array=([p]=permission=) local -A args_array=( [p]=permission= )
local permission local permission
ynh_handle_getopts_args "$@" ynh_handle_getopts_args "$@"
yunohost user permission list "$app" --output-as json --quiet \ yunohost user permission list "$app" --output-as json --quiet \
| jq -e --arg perm "$app.$permission" '.permissions[$perm]' > /dev/null | jq -e --arg perm "$app.$permission" '.permissions[$perm]' >/dev/null
} }
# Redefine the url associated to a permission # Redefine the url associated to a permission
@ -192,7 +201,7 @@ ynh_permission_exists() {
ynh_permission_url() { ynh_permission_url() {
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local legacy_args=puarhc local legacy_args=puarhc
local -A args_array=([p]=permission= [u]=url= [a]=add_url= [r]=remove_url= [h]=auth_header= [c]=clear_urls) local -A args_array=( [p]=permission= [u]=url= [a]=add_url= [r]=remove_url= [h]=auth_header= [c]=clear_urls )
local permission local permission
local url local url
local add_url local add_url
@ -206,11 +215,13 @@ ynh_permission_url() {
auth_header=${auth_header:-} auth_header=${auth_header:-}
clear_urls=${clear_urls:-} clear_urls=${clear_urls:-}
if [[ -n $url ]]; then if [[ -n $url ]]
then
url=",url='$url'" url=",url='$url'"
fi fi
if [[ -n $add_url ]]; then if [[ -n $add_url ]]
then
# Convert a list from getopts to python list # Convert a list from getopts to python list
# Note that getopts separate the args with ';' # Note that getopts separate the args with ';'
# For example: # For example:
@ -220,7 +231,8 @@ ynh_permission_url() {
add_url=",add_url=['${add_url//;/\',\'}']" add_url=",add_url=['${add_url//;/\',\'}']"
fi fi
if [[ -n $remove_url ]]; then if [[ -n $remove_url ]]
then
# Convert a list from getopts to python list # Convert a list from getopts to python list
# Note that getopts separate the args with ';' # Note that getopts separate the args with ';'
# For example: # For example:
@ -230,21 +242,25 @@ ynh_permission_url() {
remove_url=",remove_url=['${remove_url//;/\',\'}']" remove_url=",remove_url=['${remove_url//;/\',\'}']"
fi fi
if [[ -n $auth_header ]]; then if [[ -n $auth_header ]]
if [ $auth_header == "true" ]; then then
if [ $auth_header == "true" ]
then
auth_header=",auth_header=True" auth_header=",auth_header=True"
else else
auth_header=",auth_header=False" auth_header=",auth_header=False"
fi fi
fi fi
if [[ -n $clear_urls ]] && [ $clear_urls -eq 1 ]; then if [[ -n $clear_urls ]] && [ $clear_urls -eq 1 ]
then
clear_urls=",clear_urls=True" clear_urls=",clear_urls=True"
fi fi
yunohost tools shell -c "from yunohost.permission import permission_url; permission_url('$app.$permission' $url $add_url $remove_url $auth_header $clear_urls)" yunohost tools shell -c "from yunohost.permission import permission_url; permission_url('$app.$permission' $url $add_url $remove_url $auth_header $clear_urls)"
} }
# Update a permission for the app # Update a permission for the app
# #
# usage: ynh_permission_update --permission "permission" [--add="group" ["group" ...]] [--remove="group" ["group" ...]] # usage: ynh_permission_update --permission "permission" [--add="group" ["group" ...]] [--remove="group" ["group" ...]]
@ -260,7 +276,7 @@ ynh_permission_url() {
ynh_permission_update() { ynh_permission_update() {
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local legacy_args=parltP local legacy_args=parltP
local -A args_array=([p]=permission= [a]=add= [r]=remove= [l]=label= [t]=show_tile= [P]=protected=) local -A args_array=( [p]=permission= [a]=add= [r]=remove= [l]=label= [t]=show_tile= [P]=protected= )
local permission local permission
local add local add
local remove local remove
@ -274,7 +290,8 @@ ynh_permission_update() {
show_tile=${show_tile:-} show_tile=${show_tile:-}
protected=${protected:-} protected=${protected:-}
if [[ -n $add ]]; then if [[ -n $add ]]
then
# Convert a list from getopts to python list # Convert a list from getopts to python list
# Note that getopts separate the args with ';' # Note that getopts separate the args with ';'
# For example: # For example:
@ -283,7 +300,8 @@ ynh_permission_update() {
# add=['alice', 'bob'] # add=['alice', 'bob']
add=",add=['${add//';'/"','"}']" add=",add=['${add//';'/"','"}']"
fi fi
if [[ -n $remove ]]; then if [[ -n $remove ]]
then
# Convert a list from getopts to python list # Convert a list from getopts to python list
# Note that getopts separate the args with ';' # Note that getopts separate the args with ';'
# For example: # For example:
@ -293,12 +311,15 @@ ynh_permission_update() {
remove=",remove=['${remove//';'/"','"}']" remove=",remove=['${remove//';'/"','"}']"
fi fi
if [[ -n $label ]]; then if [[ -n $label ]]
then
label=",label='$label'" label=",label='$label'"
fi fi
if [[ -n $show_tile ]]; then if [[ -n $show_tile ]]
if [ $show_tile == "true" ]; then then
if [ $show_tile == "true" ]
then
show_tile=",show_tile=True" show_tile=",show_tile=True"
else else
show_tile=",show_tile=False" show_tile=",show_tile=False"
@ -306,7 +327,8 @@ ynh_permission_update() {
fi fi
if [[ -n $protected ]]; then if [[ -n $protected ]]; then
if [ $protected == "true" ]; then if [ $protected == "true" ]
then
protected=",protected=True" protected=",protected=True"
else else
protected=",protected=False" protected=",protected=False"
@ -329,20 +351,23 @@ ynh_permission_update() {
ynh_permission_has_user() { ynh_permission_has_user() {
local legacy_args=pu local legacy_args=pu
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local -A args_array=([p]=permission= [u]=user=) local -A args_array=( [p]=permission= [u]=user= )
local permission local permission
local user local user
# Manage arguments with getopts # Manage arguments with getopts
ynh_handle_getopts_args "$@" ynh_handle_getopts_args "$@"
if ! ynh_permission_exists --permission=$permission; then if ! ynh_permission_exists --permission=$permission
then
return 1 return 1
fi fi
# Check both allowed and corresponding_users sections in the json # Check both allowed and corresponding_users sections in the json
for section in "allowed" "corresponding_users"; do for section in "allowed" "corresponding_users"
do
if yunohost user permission info "$app.$permission" --output-as json --quiet \ if yunohost user permission info "$app.$permission" --output-as json --quiet \
| jq -e --arg user $user --arg section $section '.[$section] | index($user)' > /dev/null; then | jq -e --arg user $user --arg section $section '.[$section] | index($user)' >/dev/null
then
return 0 return 0
fi fi
done done
@ -356,8 +381,9 @@ ynh_permission_has_user() {
# | exit: Return 1 if the permission doesn't exist, 0 otherwise # | exit: Return 1 if the permission doesn't exist, 0 otherwise
# #
# Requires YunoHost version 4.1.2 or higher. # Requires YunoHost version 4.1.2 or higher.
ynh_legacy_permissions_exists() { ynh_legacy_permissions_exists () {
for permission in "skipped" "unprotected" "protected"; do for permission in "skipped" "unprotected" "protected"
do
if ynh_permission_exists --permission="legacy_${permission}_uris"; then if ynh_permission_exists --permission="legacy_${permission}_uris"; then
return 0 return 0
fi fi
@ -376,8 +402,9 @@ ynh_legacy_permissions_exists() {
# # You can recreate the required permissions here with ynh_permission_create # # You can recreate the required permissions here with ynh_permission_create
# fi # fi
# Requires YunoHost version 4.1.2 or higher. # Requires YunoHost version 4.1.2 or higher.
ynh_legacy_permissions_delete_all() { ynh_legacy_permissions_delete_all () {
for permission in "skipped" "unprotected" "protected"; do for permission in "skipped" "unprotected" "protected"
do
if ynh_permission_exists --permission="legacy_${permission}_uris"; then if ynh_permission_exists --permission="legacy_${permission}_uris"; then
ynh_permission_delete --permission="legacy_${permission}_uris" ynh_permission_delete --permission="legacy_${permission}_uris"
fi fi

View file

@ -1,50 +1,39 @@
#!/bin/bash #!/bin/bash
readonly YNH_DEFAULT_PHP_VERSION=7.4 readonly YNH_DEFAULT_PHP_VERSION=7.3
# Declare the actual PHP version to use. # Declare the actual PHP version to use.
# A packager willing to use another version of PHP can override the variable into its _common.sh. # A packager willing to use another version of PHP can override the variable into its _common.sh.
YNH_PHP_VERSION=${YNH_PHP_VERSION:-$YNH_DEFAULT_PHP_VERSION} YNH_PHP_VERSION=${YNH_PHP_VERSION:-$YNH_DEFAULT_PHP_VERSION}
# Create a dedicated PHP-FPM config # Create a dedicated PHP-FPM config
# #
# usage: ynh_add_fpm_config # usage 1: ynh_add_fpm_config [--phpversion=7.X] [--use_template] [--package=packages] [--dedicated_service]
# | arg: -v, --phpversion= - Version of PHP to use.
# | arg: -t, --use_template - Use this helper in template mode.
# | arg: -p, --package= - Additionnal PHP packages to install
# | arg: -d, --dedicated_service - Use a dedicated PHP-FPM service instead of the common one.
# #
# Case 1 (recommended) : your provided a snippet conf/extra_php-fpm.conf # -----------------------------------------------------------------------------
# #
# The actual PHP configuration will be automatically generated, # usage 2: ynh_add_fpm_config [--phpversion=7.X] --usage=usage --footprint=footprint [--package=packages] [--dedicated_service]
# and your extra_php-fpm.conf will be appended (typically contains PHP upload limits) # | arg: -v, --phpversion= - Version of PHP to use.
# # | arg: -f, --footprint= - Memory footprint of the service (low/medium/high).
# The resulting configuration will be deployed to the appropriate place, /etc/php/$phpversion/fpm/pool.d/$app.conf
#
# Performance-related options in the PHP conf, such as :
# pm.max_children, pm.start_servers, pm.min_spare_servers pm.max_spare_servers
# are computed from two parameters called "usage" and "footprint" which can be set to low/medium/high. (cf details below)
#
# If you wish to tweak those, please initialize the settings `fpm_usage` and `fpm_footprint`
# *prior* to calling this helper. Otherwise, "low" will be used as a default for both values.
#
# Otherwise, if you want the user to have control over these, we encourage to create a config panel
# (which should ultimately be standardized by the core ...)
#
# Case 2 (deprecate) : you provided an entire conf/php-fpm.conf
#
# The configuration will be hydrated, replacing __FOOBAR__ placeholders with $foobar values, etc.
#
# The resulting configuration will be deployed to the appropriate place, /etc/php/$phpversion/fpm/pool.d/$app.conf
#
# ----------------------
#
# fpm_footprint: Memory footprint of the service (low/medium/high).
# low - Less than 20 MB of RAM by pool. # low - Less than 20 MB of RAM by pool.
# medium - Between 20 MB and 40 MB of RAM by pool. # medium - Between 20 MB and 40 MB of RAM by pool.
# high - More than 40 MB of RAM by pool. # high - More than 40 MB of RAM by pool.
# N - Or you can specify a quantitative footprint as MB by pool (use watch -n0.5 ps -o user,cmd,%cpu,rss -u APP) # Or specify exactly the footprint, the load of the service as MB by pool instead of having a standard value.
# To have this value, use the following command and stress the service.
# watch -n0.5 ps -o user,cmd,%cpu,rss -u APP
# #
# fpm_usage: Expected usage of the service (low/medium/high). # | arg: -u, --usage= - Expected usage of the service (low/medium/high).
# low - Personal usage, behind the SSO. # low - Personal usage, behind the SSO.
# medium - Low usage, few people or/and publicly accessible. # medium - Low usage, few people or/and publicly accessible.
# high - High usage, frequently visited website. # high - High usage, frequently visited website.
# #
# | arg: -p, --package= - Additionnal PHP packages to install for a specific version of PHP
# | arg: -d, --dedicated_service - Use a dedicated PHP-FPM service instead of the common one.
#
#
# The footprint of the service will be used to defined the maximum footprint we can allow, which is half the maximum RAM. # The footprint of the service will be used to defined the maximum footprint we can allow, which is half the maximum RAM.
# So it will be used to defined 'pm.max_children' # So it will be used to defined 'pm.max_children'
# A lower value for the footprint will allow more children for 'pm.max_children'. And so for # A lower value for the footprint will allow more children for 'pm.max_children'. And so for
@ -67,13 +56,12 @@ YNH_PHP_VERSION=${YNH_PHP_VERSION:-$YNH_DEFAULT_PHP_VERSION}
# children ready to answer. # children ready to answer.
# #
# Requires YunoHost version 4.1.0 or higher. # Requires YunoHost version 4.1.0 or higher.
ynh_add_fpm_config() { ynh_add_fpm_config () {
local _globalphpversion=${phpversion-:}
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local legacy_args=vufpdg local legacy_args=vtufpd
local -A args_array=([v]=phpversion= [u]=usage= [f]=footprint= [p]=package= [d]=dedicated_service [g]=group=) local -A args_array=( [v]=phpversion= [t]=use_template [u]=usage= [f]=footprint= [p]=package= [d]=dedicated_service )
local group
local phpversion local phpversion
local use_template
local usage local usage
local footprint local footprint
local package local package
@ -81,63 +69,53 @@ ynh_add_fpm_config() {
# Manage arguments with getopts # Manage arguments with getopts
ynh_handle_getopts_args "$@" ynh_handle_getopts_args "$@"
package=${package:-} package=${package:-}
group=${group:-}
# The default behaviour is to use the template. # The default behaviour is to use the template.
local autogenconf=false use_template="${use_template:-1}"
usage="${usage:-}" usage="${usage:-}"
footprint="${footprint:-}" footprint="${footprint:-}"
if [ -n "$usage" ] || [ -n "$footprint" ] || [[ -e $YNH_APP_BASEDIR/conf/extra_php-fpm.conf ]]; then if [ -n "$usage" ] || [ -n "$footprint" ]; then
autogenconf=true use_template=0
# If no usage provided, default to the value existing in setting ... or to low
local fpm_usage_in_setting=$(ynh_app_setting_get --app=$app --key=fpm_usage)
if [ -z "$usage" ]; then
usage=${fpm_usage_in_setting:-low}
ynh_app_setting_set --app=$app --key=fpm_usage --value=$usage
fi
# If no footprint provided, default to the value existing in setting ... or to low
local fpm_footprint_in_setting=$(ynh_app_setting_get --app=$app --key=fpm_footprint)
if [ -z "$footprint" ]; then
footprint=${fpm_footprint_in_setting:-low}
ynh_app_setting_set --app=$app --key=fpm_footprint --value=$footprint
fi
fi fi
# Do not use a dedicated service by default # Do not use a dedicated service by default
dedicated_service=${dedicated_service:-0} dedicated_service=${dedicated_service:-0}
# Set the default PHP-FPM version by default # Set the default PHP-FPM version by default
if dpkg --compare-versions ${YNH_APP_PACKAGING_FORMAT:-0} lt 2; then phpversion="${phpversion:-$YNH_PHP_VERSION}"
phpversion="${phpversion:-$YNH_PHP_VERSION}"
else
phpversion="${phpversion:-$_globalphpversion}"
fi
local old_phpversion=$(ynh_app_setting_get --app=$app --key=phpversion) local old_phpversion=$(ynh_app_setting_get --app=$app --key=phpversion)
# If the PHP version changed, remove the old fpm conf # If the PHP version changed, remove the old fpm conf
# (NB: This stuff is also handled by the apt helper, which is usually triggered before this helper) if [ -n "$old_phpversion" ] && [ "$old_phpversion" != "$phpversion" ]
if [ -n "$old_phpversion" ] && [ "$old_phpversion" != "$phpversion" ]; then then
local old_php_fpm_config_dir=$(ynh_app_setting_get --app=$app --key=fpm_config_dir) local old_php_fpm_config_dir=$(ynh_app_setting_get --app=$app --key=fpm_config_dir)
local old_php_finalphpconf="$old_php_fpm_config_dir/pool.d/$app.conf" local old_php_finalphpconf="$old_php_fpm_config_dir/pool.d/$app.conf"
if [[ -f "$old_php_finalphpconf" ]]; then ynh_backup_if_checksum_is_different --file="$old_php_finalphpconf"
ynh_backup_if_checksum_is_different --file="$old_php_finalphpconf"
ynh_remove_fpm_config ynh_remove_fpm_config
fi
fi fi
# Legacy args (packager should just list their php dependency as regular apt dependencies... # If the requested PHP version is not the default version for YunoHost
if [ -n "$package" ]; then if [ "$phpversion" != "$YNH_DEFAULT_PHP_VERSION" ]
then
# If the argument --package is used, add the packages to ynh_install_php to install them from sury
if [ -n "$package" ]
then
local additionnal_packages="--package=$package"
else
local additionnal_packages=""
fi
# Install this specific version of PHP.
ynh_install_php --phpversion="$phpversion" "$additionnal_packages"
elif [ -n "$package" ]
then
# Install the additionnal packages from the default repository # Install the additionnal packages from the default repository
ynh_print_warn --message "Argument --package of ynh_add_fpm_config is deprecated and to be removed in the future"
ynh_install_app_dependencies "$package" ynh_install_app_dependencies "$package"
fi fi
if [ $dedicated_service -eq 1 ]; then if [ $dedicated_service -eq 1 ]
ynh_print_warn --message "Argument --dedicated_service of ynh_add_fpm_config is deprecated and to be removed in the future" then
local fpm_service="${app}-phpfpm" local fpm_service="${app}-phpfpm"
local fpm_config_dir="/etc/php/$phpversion/dedicated-fpm" local fpm_config_dir="/etc/php/$phpversion/dedicated-fpm"
else else
@ -154,10 +132,12 @@ ynh_add_fpm_config() {
ynh_app_setting_set --app=$app --key=phpversion --value=$phpversion ynh_app_setting_set --app=$app --key=phpversion --value=$phpversion
# Migrate from mutual PHP service to dedicated one. # Migrate from mutual PHP service to dedicated one.
if [ $dedicated_service -eq 1 ]; then if [ $dedicated_service -eq 1 ]
then
local old_fpm_config_dir="/etc/php/$phpversion/fpm" local old_fpm_config_dir="/etc/php/$phpversion/fpm"
# If a config file exist in the common pool, move it. # If a config file exist in the common pool, move it.
if [ -e "$old_fpm_config_dir/pool.d/$app.conf" ]; then if [ -e "$old_fpm_config_dir/pool.d/$app.conf" ]
then
ynh_print_info --message="Migrate to a dedicated php-fpm service for $app." ynh_print_info --message="Migrate to a dedicated php-fpm service for $app."
# Create a backup of the old file before migration # Create a backup of the old file before migration
ynh_backup_if_checksum_is_different --file="$old_fpm_config_dir/pool.d/$app.conf" ynh_backup_if_checksum_is_different --file="$old_fpm_config_dir/pool.d/$app.conf"
@ -168,7 +148,8 @@ ynh_add_fpm_config() {
fi fi
fi fi
if [ $autogenconf == "false" ]; then if [ $use_template -eq 1 ]
then
# Usage 1, use the template in conf/php-fpm.conf # Usage 1, use the template in conf/php-fpm.conf
local phpfpm_path="$YNH_APP_BASEDIR/conf/php-fpm.conf" local phpfpm_path="$YNH_APP_BASEDIR/conf/php-fpm.conf"
# Make sure now that the template indeed exists # Make sure now that the template indeed exists
@ -176,18 +157,21 @@ ynh_add_fpm_config() {
else else
# Usage 2, generate a PHP-FPM config file with ynh_get_scalable_phpfpm # Usage 2, generate a PHP-FPM config file with ynh_get_scalable_phpfpm
# Store settings
ynh_app_setting_set --app=$app --key=fpm_footprint --value=$footprint
ynh_app_setting_set --app=$app --key=fpm_usage --value=$usage
# Define the values to use for the configuration of PHP. # Define the values to use for the configuration of PHP.
ynh_get_scalable_phpfpm --usage=$usage --footprint=$footprint ynh_get_scalable_phpfpm --usage=$usage --footprint=$footprint
local phpfpm_group=$([[ -n "$group" ]] && echo "$group" || echo "$app")
local phpfpm_path="$YNH_APP_BASEDIR/conf/php-fpm.conf" local phpfpm_path="$YNH_APP_BASEDIR/conf/php-fpm.conf"
echo " echo "
[__APP__] [__APP__]
user = __APP__ user = __APP__
group = __PHPFPM_GROUP__ group = __APP__
chdir = __INSTALL_DIR__ chdir = __FINALPATH__
listen = /var/run/php/php__PHPVERSION__-fpm-__APP__.sock listen = /var/run/php/php__PHPVERSION__-fpm-__APP__.sock
listen.owner = www-data listen.owner = www-data
@ -197,19 +181,21 @@ pm = __PHP_PM__
pm.max_children = __PHP_MAX_CHILDREN__ pm.max_children = __PHP_MAX_CHILDREN__
pm.max_requests = 500 pm.max_requests = 500
request_terminate_timeout = 1d request_terminate_timeout = 1d
" > "$phpfpm_path" " > $phpfpm_path
if [ "$php_pm" = "dynamic" ]; then if [ "$php_pm" = "dynamic" ]
then
echo " echo "
pm.start_servers = __PHP_START_SERVERS__ pm.start_servers = __PHP_START_SERVERS__
pm.min_spare_servers = __PHP_MIN_SPARE_SERVERS__ pm.min_spare_servers = __PHP_MIN_SPARE_SERVERS__
pm.max_spare_servers = __PHP_MAX_SPARE_SERVERS__ pm.max_spare_servers = __PHP_MAX_SPARE_SERVERS__
" >> "$phpfpm_path" " >> $phpfpm_path
elif [ "$php_pm" = "ondemand" ]; then elif [ "$php_pm" = "ondemand" ]
then
echo " echo "
pm.process_idle_timeout = 10s pm.process_idle_timeout = 10s
" >> "$phpfpm_path" " >> $phpfpm_path
fi fi
# Concatene the extra config. # Concatene the extra config.
@ -221,23 +207,25 @@ pm.process_idle_timeout = 10s
local finalphpconf="$fpm_config_dir/pool.d/$app.conf" local finalphpconf="$fpm_config_dir/pool.d/$app.conf"
ynh_add_config --template="$phpfpm_path" --destination="$finalphpconf" ynh_add_config --template="$phpfpm_path" --destination="$finalphpconf"
if [ -e "$YNH_APP_BASEDIR/conf/php-fpm.ini" ]; then if [ -e "$YNH_APP_BASEDIR/conf/php-fpm.ini" ]
then
ynh_print_warn --message="Packagers ! Please do not use a separate php ini file, merge your directives in the pool file instead." ynh_print_warn --message="Packagers ! Please do not use a separate php ini file, merge your directives in the pool file instead."
ynh_add_config --template="php-fpm.ini" --destination="$fpm_config_dir/conf.d/20-$app.ini" ynh_add_config --template="$YNH_APP_BASEDIR/conf/php-fpm.ini" --destination="$fpm_config_dir/conf.d/20-$app.ini"
fi fi
if [ $dedicated_service -eq 1 ]; then if [ $dedicated_service -eq 1 ]
then
# Create a dedicated php-fpm.conf for the service # Create a dedicated php-fpm.conf for the service
local globalphpconf=$fpm_config_dir/php-fpm-$app.conf local globalphpconf=$fpm_config_dir/php-fpm-$app.conf
echo "[global] echo "[global]
pid = /run/php/php__PHPVERSION__-fpm-__APP__.pid pid = /run/php/php__PHPVERSION__-fpm-__APP__.pid
error_log = /var/log/php/fpm-php.__APP__.log error_log = /var/log/php/fpm-php.__APP__.log
syslog.ident = php-fpm-__APP__ syslog.ident = php-fpm-__APP__
include = __FINALPHPCONF__ include = __FINALPHPCONF__
" > $YNH_APP_BASEDIR/conf/php-fpm-$app.conf " > $YNH_APP_BASEDIR/conf/php-fpm-$app.conf
ynh_add_config --template="php-fpm-$app.conf" --destination="$globalphpconf" ynh_add_config --template="$YNH_APP_BASEDIR/conf/php-fpm-$app.conf" --destination="$globalphpconf"
# Create a config for a dedicated PHP-FPM service for the app # Create a config for a dedicated PHP-FPM service for the app
echo "[Unit] echo "[Unit]
@ -264,7 +252,8 @@ WantedBy=multi-user.target
ynh_systemd_action --service_name=$fpm_service --action=restart ynh_systemd_action --service_name=$fpm_service --action=restart
else else
# Validate that the new php conf doesn't break php-fpm entirely # Validate that the new php conf doesn't break php-fpm entirely
if ! php-fpm${phpversion} --test 2> /dev/null; then if ! php-fpm${phpversion} --test 2>/dev/null
then
php-fpm${phpversion} --test || true php-fpm${phpversion} --test || true
ynh_secure_remove --file="$finalphpconf" ynh_secure_remove --file="$finalphpconf"
ynh_die --message="The new configuration broke php-fpm?" ynh_die --message="The new configuration broke php-fpm?"
@ -278,7 +267,7 @@ WantedBy=multi-user.target
# usage: ynh_remove_fpm_config # usage: ynh_remove_fpm_config
# #
# Requires YunoHost version 2.7.2 or higher. # Requires YunoHost version 2.7.2 or higher.
ynh_remove_fpm_config() { ynh_remove_fpm_config () {
local fpm_config_dir=$(ynh_app_setting_get --app=$app --key=fpm_config_dir) local fpm_config_dir=$(ynh_app_setting_get --app=$app --key=fpm_config_dir)
local fpm_service=$(ynh_app_setting_get --app=$app --key=fpm_service) local fpm_service=$(ynh_app_setting_get --app=$app --key=fpm_service)
local dedicated_service=$(ynh_app_setting_get --app=$app --key=fpm_dedicated_service) local dedicated_service=$(ynh_app_setting_get --app=$app --key=fpm_dedicated_service)
@ -290,17 +279,20 @@ ynh_remove_fpm_config() {
phpversion="${phpversion:-$YNH_DEFAULT_PHP_VERSION}" phpversion="${phpversion:-$YNH_DEFAULT_PHP_VERSION}"
# Assume default PHP files if not set # Assume default PHP files if not set
if [ -z "$fpm_config_dir" ]; then if [ -z "$fpm_config_dir" ]
then
fpm_config_dir="/etc/php/$YNH_DEFAULT_PHP_VERSION/fpm" fpm_config_dir="/etc/php/$YNH_DEFAULT_PHP_VERSION/fpm"
fpm_service="php$YNH_DEFAULT_PHP_VERSION-fpm" fpm_service="php$YNH_DEFAULT_PHP_VERSION-fpm"
fi fi
ynh_secure_remove --file="$fpm_config_dir/pool.d/$app.conf" ynh_secure_remove --file="$fpm_config_dir/pool.d/$app.conf"
if [ -e $fpm_config_dir/conf.d/20-$app.ini ]; then if [ -e $fpm_config_dir/conf.d/20-$app.ini ]
then
ynh_secure_remove --file="$fpm_config_dir/conf.d/20-$app.ini" ynh_secure_remove --file="$fpm_config_dir/conf.d/20-$app.ini"
fi fi
if [ $dedicated_service -eq 1 ]; then if [ $dedicated_service -eq 1 ]
then
# Remove the dedicated service PHP-FPM service for the app # Remove the dedicated service PHP-FPM service for the app
ynh_remove_systemd_config --service=$fpm_service ynh_remove_systemd_config --service=$fpm_service
# Remove the global PHP-FPM conf # Remove the global PHP-FPM conf
@ -312,11 +304,10 @@ ynh_remove_fpm_config() {
fi fi
# If the PHP version used is not the default version for YunoHost # If the PHP version used is not the default version for YunoHost
# The second part with YNH_APP_PURGE is an ugly hack to guess that we're inside the remove script if [ "$phpversion" != "$YNH_DEFAULT_PHP_VERSION" ]
# (we don't actually care about its value, we just check its not empty hence it exists) then
if [ "$phpversion" != "$YNH_DEFAULT_PHP_VERSION" ] && [ -n "${YNH_APP_PURGE:-}" ] && dpkg --compare-versions ${YNH_APP_PACKAGING_FORMAT:-0} lt 2; then # Remove this specific version of PHP
# Remove app dependencies ... but ideally should happen via an explicit call from packager ynh_remove_php
ynh_remove_app_dependencies
fi fi
} }
@ -324,40 +315,38 @@ ynh_remove_fpm_config() {
# #
# [internal] # [internal]
# #
# Legacy, to be remove on bullseye
#
# usage: ynh_install_php --phpversion=phpversion [--package=packages] # usage: ynh_install_php --phpversion=phpversion [--package=packages]
# | arg: -v, --phpversion= - Version of PHP to install. # | arg: -v, --phpversion= - Version of PHP to install.
# | arg: -p, --package= - Additionnal PHP packages to install # | arg: -p, --package= - Additionnal PHP packages to install
# #
# Requires YunoHost version 3.8.1 or higher. # Requires YunoHost version 3.8.1 or higher.
ynh_install_php() { ynh_install_php () {
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local legacy_args=vp local legacy_args=vp
local -A args_array=([v]=phpversion= [p]=package=) local -A args_array=( [v]=phpversion= [p]=package= )
local phpversion local phpversion
local package local package
# Manage arguments with getopts # Manage arguments with getopts
ynh_handle_getopts_args "$@" ynh_handle_getopts_args "$@"
package=${package:-} package=${package:-}
if [ "$phpversion" == "$YNH_DEFAULT_PHP_VERSION" ]; then if [ "$phpversion" == "$YNH_DEFAULT_PHP_VERSION" ]
then
ynh_die --message="Do not use ynh_install_php to install php$YNH_DEFAULT_PHP_VERSION" ynh_die --message="Do not use ynh_install_php to install php$YNH_DEFAULT_PHP_VERSION"
fi fi
ynh_install_app_dependencies "$package" ynh_install_app_dependencies "$package"
ynh_app_setting_set --app=$app --key=phpversion --value=$specific_php_version
} }
# Remove the specific version of PHP used by the app. # Remove the specific version of PHP used by the app.
# #
# [internal] # [internal]
# #
# Legacy, to be remove on bullseye # usage: ynh_install_php
#
# usage: ynh_remove_php
# #
# Requires YunoHost version 3.8.1 or higher. # Requires YunoHost version 3.8.1 or higher.
ynh_remove_php() { ynh_remove_php () {
ynh_remove_app_dependencies ynh_remove_app_dependencies
} }
@ -380,10 +369,10 @@ ynh_remove_php() {
# high - High usage, frequently visited website. # high - High usage, frequently visited website.
# #
# | arg: -p, --print - Print the result (intended for debug purpose only when packaging the app) # | arg: -p, --print - Print the result (intended for debug purpose only when packaging the app)
ynh_get_scalable_phpfpm() { ynh_get_scalable_phpfpm () {
local legacy_args=ufp local legacy_args=ufp
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local -A args_array=([u]=usage= [f]=footprint= [p]=print) local -A args_array=( [u]=usage= [f]=footprint= [p]=print )
local usage local usage
local footprint local footprint
local print local print
@ -394,30 +383,38 @@ ynh_get_scalable_phpfpm() {
usage=${usage,,} usage=${usage,,}
print=${print:-0} print=${print:-0}
if [ "$footprint" = "low" ]; then if [ "$footprint" = "low" ]
then
footprint=20 footprint=20
elif [ "$footprint" = "medium" ]; then elif [ "$footprint" = "medium" ]
then
footprint=35 footprint=35
elif [ "$footprint" = "high" ]; then elif [ "$footprint" = "high" ]
then
footprint=50 footprint=50
fi fi
# Define the factor to determine min_spare_servers # Define the factor to determine min_spare_servers
# to avoid having too few children ready to start for heavy apps # to avoid having too few children ready to start for heavy apps
if [ $footprint -le 20 ]; then if [ $footprint -le 20 ]
then
min_spare_servers_factor=8 min_spare_servers_factor=8
elif [ $footprint -le 35 ]; then elif [ $footprint -le 35 ]
then
min_spare_servers_factor=5 min_spare_servers_factor=5
else else
min_spare_servers_factor=3 min_spare_servers_factor=3
fi fi
# Define the way the process manager handle child processes. # Define the way the process manager handle child processes.
if [ "$usage" = "low" ]; then if [ "$usage" = "low" ]
then
php_pm=ondemand php_pm=ondemand
elif [ "$usage" = "medium" ]; then elif [ "$usage" = "medium" ]
then
php_pm=dynamic php_pm=dynamic
elif [ "$usage" = "high" ]; then elif [ "$usage" = "high" ]
then
php_pm=static php_pm=static
else else
ynh_die --message="Does not recognize '$usage' as an usage value." ynh_die --message="Does not recognize '$usage' as an usage value."
@ -428,7 +425,8 @@ ynh_get_scalable_phpfpm() {
at_least_one() { at_least_one() {
# Do not allow value below 1 # Do not allow value below 1
if [ $1 -le 0 ]; then if [ $1 -le 0 ]
then
echo 1 echo 1
else else
echo $1 echo $1
@ -438,18 +436,20 @@ ynh_get_scalable_phpfpm() {
# Define pm.max_children # Define pm.max_children
# The value of pm.max_children is the total amount of ram divide by 2 and divide again by the footprint of a pool for this app. # The value of pm.max_children is the total amount of ram divide by 2 and divide again by the footprint of a pool for this app.
# So if PHP-FPM start the maximum of children, it won't exceed half of the ram. # So if PHP-FPM start the maximum of children, it won't exceed half of the ram.
php_max_children=$(($max_ram / 2 / $footprint)) php_max_children=$(( $max_ram / 2 / $footprint ))
# If process manager is set as static, use half less children. # If process manager is set as static, use half less children.
# Used as static, there's always as many children as the value of pm.max_children # Used as static, there's always as many children as the value of pm.max_children
if [ "$php_pm" = "static" ]; then if [ "$php_pm" = "static" ]
php_max_children=$(($php_max_children / 2)) then
php_max_children=$(( $php_max_children / 2 ))
fi fi
php_max_children=$(at_least_one $php_max_children) php_max_children=$(at_least_one $php_max_children)
# To not overload the proc, limit the number of children to 4 times the number of cores. # To not overload the proc, limit the number of children to 4 times the number of cores.
local core_number=$(nproc) local core_number=$(nproc)
local max_proc=$(($core_number * 4)) local max_proc=$(( $core_number * 4 ))
if [ $php_max_children -gt $max_proc ]; then if [ $php_max_children -gt $max_proc ]
then
php_max_children=$max_proc php_max_children=$max_proc
fi fi
@ -459,15 +459,16 @@ ynh_get_scalable_phpfpm() {
php_max_children=$php_forced_max_children php_max_children=$php_forced_max_children
fi fi
if [ "$php_pm" = "dynamic" ]; then if [ "$php_pm" = "dynamic" ]
then
# Define pm.start_servers, pm.min_spare_servers and pm.max_spare_servers for a dynamic process manager # Define pm.start_servers, pm.min_spare_servers and pm.max_spare_servers for a dynamic process manager
php_min_spare_servers=$(($php_max_children / $min_spare_servers_factor)) php_min_spare_servers=$(( $php_max_children / $min_spare_servers_factor ))
php_min_spare_servers=$(at_least_one $php_min_spare_servers) php_min_spare_servers=$(at_least_one $php_min_spare_servers)
php_max_spare_servers=$(($php_max_children / 2)) php_max_spare_servers=$(( $php_max_children / 2 ))
php_max_spare_servers=$(at_least_one $php_max_spare_servers) php_max_spare_servers=$(at_least_one $php_max_spare_servers)
php_start_servers=$(($php_min_spare_servers + ($php_max_spare_servers - $php_min_spare_servers) / 2)) php_start_servers=$(( $php_min_spare_servers + ( $php_max_spare_servers - $php_min_spare_servers ) /2 ))
php_start_servers=$(at_least_one $php_start_servers) php_start_servers=$(at_least_one $php_start_servers)
else else
php_min_spare_servers=0 php_min_spare_servers=0
@ -475,25 +476,94 @@ ynh_get_scalable_phpfpm() {
php_start_servers=0 php_start_servers=0
fi fi
if [ $print -eq 1 ]; then if [ $print -eq 1 ]
ynh_print_warn --message="Footprint=${footprint}Mb by pool." then
ynh_print_warn --message="Process manager=$php_pm" ynh_debug --message="Footprint=${footprint}Mb by pool."
ynh_print_warn --message="Max RAM=${max_ram}Mb" ynh_debug --message="Process manager=$php_pm"
if [ "$php_pm" != "static" ]; then ynh_debug --message="Max RAM=${max_ram}Mb"
ynh_print_warn --message="\nMax estimated footprint=$(($php_max_children * $footprint))" if [ "$php_pm" != "static" ]
ynh_print_warn --message="Min estimated footprint=$(($php_min_spare_servers * $footprint))" then
ynh_debug --message="\nMax estimated footprint=$(( $php_max_children * $footprint ))"
ynh_debug --message="Min estimated footprint=$(( $php_min_spare_servers * $footprint ))"
fi fi
if [ "$php_pm" = "dynamic" ]; then if [ "$php_pm" = "dynamic" ]
ynh_print_warn --message="Estimated average footprint=$(($php_max_spare_servers * $footprint))" then
elif [ "$php_pm" = "static" ]; then ynh_debug --message="Estimated average footprint=$(( $php_max_spare_servers * $footprint ))"
ynh_print_warn --message="Estimated footprint=$(($php_max_children * $footprint))" elif [ "$php_pm" = "static" ]
then
ynh_debug --message="Estimated footprint=$(( $php_max_children * $footprint ))"
fi fi
ynh_print_warn --message="\nRaw php-fpm values:" ynh_debug --message="\nRaw php-fpm values:"
ynh_print_warn --message="pm.max_children = $php_max_children" ynh_debug --message="pm.max_children = $php_max_children"
if [ "$php_pm" = "dynamic" ]; then if [ "$php_pm" = "dynamic" ]
ynh_print_warn --message="pm.start_servers = $php_start_servers" then
ynh_print_warn --message="pm.min_spare_servers = $php_min_spare_servers" ynh_debug --message="pm.start_servers = $php_start_servers"
ynh_print_warn --message="pm.max_spare_servers = $php_max_spare_servers" ynh_debug --message="pm.min_spare_servers = $php_min_spare_servers"
ynh_debug --message="pm.max_spare_servers = $php_max_spare_servers"
fi fi
fi fi
} }
readonly YNH_DEFAULT_COMPOSER_VERSION=1.10.17
# Declare the actual composer version to use.
# A packager willing to use another version of composer can override the variable into its _common.sh.
YNH_COMPOSER_VERSION=${YNH_COMPOSER_VERSION:-$YNH_DEFAULT_COMPOSER_VERSION}
# Execute a command with Composer
#
# usage: ynh_composer_exec [--phpversion=phpversion] [--workdir=$final_path] --commands="commands"
# | arg: -v, --phpversion - PHP version to use with composer
# | arg: -w, --workdir - The directory from where the command will be executed. Default $final_path.
# | arg: -c, --commands - Commands to execute.
#
# Requires YunoHost version 4.2 or higher.
ynh_composer_exec () {
# Declare an array to define the options of this helper.
local legacy_args=vwc
declare -Ar args_array=( [v]=phpversion= [w]=workdir= [c]=commands= )
local phpversion
local workdir
local commands
# Manage arguments with getopts
ynh_handle_getopts_args "$@"
workdir="${workdir:-$final_path}"
phpversion="${phpversion:-$YNH_PHP_VERSION}"
COMPOSER_HOME="$workdir/.composer" COMPOSER_MEMORY_LIMIT=-1 \
php${phpversion} "$workdir/composer.phar" $commands \
-d "$workdir" --quiet --no-interaction
}
# Install and initialize Composer in the given directory
#
# usage: ynh_install_composer [--phpversion=phpversion] [--workdir=$final_path] [--install_args="--optimize-autoloader"] [--composerversion=composerversion]
# | arg: -v, --phpversion - PHP version to use with composer
# | arg: -w, --workdir - The directory from where the command will be executed. Default $final_path.
# | arg: -a, --install_args - Additional arguments provided to the composer install. Argument --no-dev already include
# | arg: -c, --composerversion - Composer version to install
#
# Requires YunoHost version 4.2 or higher.
ynh_install_composer () {
# Declare an array to define the options of this helper.
local legacy_args=vwac
declare -Ar args_array=( [v]=phpversion= [w]=workdir= [a]=install_args= [c]=composerversion=)
local phpversion
local workdir
local install_args
local composerversion
# Manage arguments with getopts
ynh_handle_getopts_args "$@"
workdir="${workdir:-$final_path}"
phpversion="${phpversion:-$YNH_PHP_VERSION}"
install_args="${install_args:-}"
composerversion="${composerversion:-$YNH_COMPOSER_VERSION}"
curl -sS https://getcomposer.org/installer \
| COMPOSER_HOME="$workdir/.composer" \
php${phpversion} -- --quiet --install-dir="$workdir" --version=$composerversion \
|| ynh_die --message="Unable to install Composer."
# install dependencies
ynh_composer_exec --phpversion="${phpversion}" --workdir="$workdir" --commands="install --no-dev $install_args" \
|| ynh_die --message="Unable to install core dependencies with Composer."
}

View file

@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
PSQL_ROOT_PWD_FILE=/etc/yunohost/psql PSQL_ROOT_PWD_FILE=/etc/yunohost/psql
PSQL_VERSION=13 PSQL_VERSION=11
# Open a connection as a user # Open a connection as a user
# #
@ -46,12 +46,13 @@ ynh_psql_execute_as_root() {
ynh_handle_getopts_args "$@" ynh_handle_getopts_args "$@"
database="${database:-}" database="${database:-}"
if [ -n "$database" ]; then if [ -n "$database" ]
then
database="--database=$database" database="--database=$database"
fi fi
ynh_psql_connect_as --user="postgres" --password="$(cat $PSQL_ROOT_PWD_FILE)" \ ynh_psql_connect_as --user="postgres" --password="$(cat $PSQL_ROOT_PWD_FILE)" \
$database <<< "$sql" $database <<<"$sql"
} }
# Execute a command from a file as root user # Execute a command from a file as root user
@ -71,12 +72,13 @@ ynh_psql_execute_file_as_root() {
ynh_handle_getopts_args "$@" ynh_handle_getopts_args "$@"
database="${database:-}" database="${database:-}"
if [ -n "$database" ]; then if [ -n "$database" ]
then
database="--database=$database" database="--database=$database"
fi fi
ynh_psql_connect_as --user="postgres" --password="$(cat $PSQL_ROOT_PWD_FILE)" \ ynh_psql_connect_as --user="postgres" --password="$(cat $PSQL_ROOT_PWD_FILE)" \
$database < "$file" $database <"$file"
} }
# Create a database and grant optionnaly privilegies to a user # Create a database and grant optionnaly privilegies to a user
@ -160,8 +162,6 @@ ynh_psql_create_user() {
# Check if a psql user exists # Check if a psql user exists
# #
# [packagingv1]
#
# usage: ynh_psql_user_exists --user=user # usage: ynh_psql_user_exists --user=user
# | arg: -u, --user= - the user for which to check existence # | arg: -u, --user= - the user for which to check existence
# | exit: Return 1 if the user doesn't exist, 0 otherwise # | exit: Return 1 if the user doesn't exist, 0 otherwise
@ -175,7 +175,8 @@ ynh_psql_user_exists() {
# Manage arguments with getopts # Manage arguments with getopts
ynh_handle_getopts_args "$@" ynh_handle_getopts_args "$@"
if ! sudo --login --user=postgres PGUSER="postgres" PGPASSWORD="$(cat $PSQL_ROOT_PWD_FILE)" psql -tAc "SELECT rolname FROM pg_roles WHERE rolname='$user';" | grep --quiet "$user"; then if ! sudo --login --user=postgres PGUSER="postgres" PGPASSWORD="$(cat $PSQL_ROOT_PWD_FILE)" psql -tAc "SELECT rolname FROM pg_roles WHERE rolname='$user';" | grep --quiet "$user"
then
return 1 return 1
else else
return 0 return 0
@ -197,12 +198,8 @@ ynh_psql_database_exists() {
# Manage arguments with getopts # Manage arguments with getopts
ynh_handle_getopts_args "$@" ynh_handle_getopts_args "$@"
# if psql is not there, we cannot check the db if ! sudo --login --user=postgres PGUSER="postgres" PGPASSWORD="$(cat $PSQL_ROOT_PWD_FILE)" psql -tAc "SELECT datname FROM pg_database WHERE datname='$database';" | grep --quiet "$database"
# though it could exists. then
if ! command -v psql; then
ynh_print_err -m "PostgreSQL is not installed, impossible to check for db existence."
return 1
elif ! sudo --login --user=postgres PGUSER="postgres" PGPASSWORD="$(cat $PSQL_ROOT_PWD_FILE)" psql -tAc "SELECT datname FROM pg_database WHERE datname='$database';" | grep --quiet "$database"; then
return 1 return 1
else else
return 0 return 0
@ -223,8 +220,6 @@ ynh_psql_drop_user() {
# Create a database, an user and its password. Then store the password in the app's config # Create a database, an user and its password. Then store the password in the app's config
# #
# [packagingv1]
#
# usage: ynh_psql_setup_db --db_user=user --db_name=name [--db_pwd=pwd] # usage: ynh_psql_setup_db --db_user=user --db_name=name [--db_pwd=pwd]
# | arg: -u, --db_user= - Owner of the database # | arg: -u, --db_user= - Owner of the database
# | arg: -n, --db_name= - Name of the database # | arg: -n, --db_name= - Name of the database
@ -260,8 +255,6 @@ ynh_psql_setup_db() {
# Remove a database if it exists, and the associated user # Remove a database if it exists, and the associated user
# #
# [packagingv1]
#
# usage: ynh_psql_remove_db --db_user=user --db_name=name # usage: ynh_psql_remove_db --db_user=user --db_name=name
# | arg: -u, --db_user= - Owner of the database # | arg: -u, --db_user= - Owner of the database
# | arg: -n, --db_name= - Name of the database # | arg: -n, --db_name= - Name of the database
@ -276,14 +269,16 @@ ynh_psql_remove_db() {
# Manage arguments with getopts # Manage arguments with getopts
ynh_handle_getopts_args "$@" ynh_handle_getopts_args "$@"
if ynh_psql_database_exists --database=$db_name; then # Check if the database exists if ynh_psql_database_exists --database=$db_name
ynh_psql_drop_db $db_name # Remove the database then # Check if the database exists
ynh_psql_drop_db $db_name # Remove the database
else else
ynh_print_warn --message="Database $db_name not found" ynh_print_warn --message="Database $db_name not found"
fi fi
# Remove psql user if it exists # Remove psql user if it exists
if ynh_psql_user_exists --user=$db_user; then if ynh_psql_user_exists --user=$db_user
then
ynh_psql_drop_user $db_user ynh_psql_drop_user $db_user
else else
ynh_print_warn --message="User $db_user not found" ynh_print_warn --message="User $db_user not found"
@ -292,8 +287,6 @@ ynh_psql_remove_db() {
# Create a master password and set up global settings # Create a master password and set up global settings
# #
# [internal]
#
# usage: ynh_psql_test_if_first_run # usage: ynh_psql_test_if_first_run
# #
# It also make sure that postgresql is installed and running # It also make sure that postgresql is installed and running
@ -305,5 +298,35 @@ ynh_psql_test_if_first_run() {
# Make sure postgresql is indeed installed # Make sure postgresql is indeed installed
dpkg --list | grep -q "ii postgresql-$PSQL_VERSION" || ynh_die --message="postgresql-$PSQL_VERSION is not installed !?" dpkg --list | grep -q "ii postgresql-$PSQL_VERSION" || ynh_die --message="postgresql-$PSQL_VERSION is not installed !?"
yunohost tools regen-conf postgresql # Check for some weird issue where postgresql could be installed but etc folder would not exist ...
[ -e "/etc/postgresql/$PSQL_VERSION" ] || ynh_die --message="It looks like postgresql was not properly configured ? /etc/postgresql/$PSQL_VERSION is missing ... Could be due to a locale issue, c.f.https://serverfault.com/questions/426989/postgresql-etc-postgresql-doesnt-exist"
# Make sure postgresql is started and enabled
# (N.B. : to check the active state, we check the cluster state because
# postgresql could be flagged as active even though the cluster is in
# failed state because of how the service is configured..)
systemctl is-active postgresql@$PSQL_VERSION-main -q || ynh_systemd_action --service_name=postgresql --action=restart
systemctl is-enabled postgresql -q || systemctl enable postgresql --quiet
# If this is the very first time, we define the root password
# and configure a few things
if [ ! -f "$PSQL_ROOT_PWD_FILE" ]
then
local pg_hba=/etc/postgresql/$PSQL_VERSION/main/pg_hba.conf
local psql_root_password="$(ynh_string_random)"
echo "$psql_root_password" >$PSQL_ROOT_PWD_FILE
sudo --login --user=postgres psql -c"ALTER user postgres WITH PASSWORD '$psql_root_password'" postgres
# force all user to connect to local databases using hashed passwords
# https://www.postgresql.org/docs/current/static/auth-pg-hba-conf.html#EXAMPLE-PG-HBA.CONF
# Note: we can't use peer since YunoHost create users with nologin
# See: https://github.com/YunoHost/yunohost/blob/unstable/data/helpers.d/user
ynh_replace_string --match_string="local\(\s*\)all\(\s*\)all\(\s*\)peer" --replace_string="local\1all\2all\3md5" --target_file="$pg_hba"
# Integrate postgresql service in yunohost
yunohost service add postgresql --log "/var/log/postgresql/"
ynh_systemd_action --service_name=postgresql --action=reload
fi
} }

View file

@ -8,15 +8,13 @@
# #
# Requires YunoHost version 2.2.4 or higher. # Requires YunoHost version 2.2.4 or higher.
ynh_app_setting_get() { ynh_app_setting_get() {
local _globalapp=${app-:}
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local legacy_args=ak local legacy_args=ak
local -A args_array=([a]=app= [k]=key=) local -A args_array=( [a]=app= [k]=key= )
local app local app
local key local key
# Manage arguments with getopts # Manage arguments with getopts
ynh_handle_getopts_args "$@" ynh_handle_getopts_args "$@"
app="${app:-$_globalapp}"
if [[ $key =~ (unprotected|protected|skipped)_ ]]; then if [[ $key =~ (unprotected|protected|skipped)_ ]]; then
yunohost app setting $app $key yunohost app setting $app $key
@ -34,16 +32,14 @@ ynh_app_setting_get() {
# #
# Requires YunoHost version 2.2.4 or higher. # Requires YunoHost version 2.2.4 or higher.
ynh_app_setting_set() { ynh_app_setting_set() {
local _globalapp=${app-:}
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local legacy_args=akv local legacy_args=akv
local -A args_array=([a]=app= [k]=key= [v]=value=) local -A args_array=( [a]=app= [k]=key= [v]=value= )
local app local app
local key local key
local value local value
# Manage arguments with getopts # Manage arguments with getopts
ynh_handle_getopts_args "$@" ynh_handle_getopts_args "$@"
app="${app:-$_globalapp}"
if [[ $key =~ (unprotected|protected|skipped)_ ]]; then if [[ $key =~ (unprotected|protected|skipped)_ ]]; then
yunohost app setting $app $key -v $value yunohost app setting $app $key -v $value
@ -52,42 +48,6 @@ ynh_app_setting_set() {
fi fi
} }
# Set an application setting but only if the "$key" variable ain't set yet
#
# Note that it doesn't just define the setting but ALSO define the $foobar variable
#
# Hence it's meant as a replacement for this legacy overly complex syntax:
#
# if [ -z "${foo:-}" ]
# then
# foo="bar"
# ynh_app_setting_set --key="foo" --value="$foo"
# fi
#
# usage: ynh_app_setting_set_default --app=app --key=key --value=value
# | arg: -a, --app= - the application id
# | arg: -k, --key= - the setting name to set
# | arg: -v, --value= - the default setting value to set
#
# Requires YunoHost version 11.1.16 or higher.
ynh_app_setting_set_default() {
local _globalapp=${app-:}
# Declare an array to define the options of this helper.
local legacy_args=akv
local -A args_array=([a]=app= [k]=key= [v]=value=)
local app
local key
local value
# Manage arguments with getopts
ynh_handle_getopts_args "$@"
app="${app:-$_globalapp}"
if [ -z "${!key:-}" ]; then
eval $key=\$value
ynh_app_setting "set" "$app" "$key" "$value"
fi
}
# Delete an application setting # Delete an application setting
# #
# usage: ynh_app_setting_delete --app=app --key=key # usage: ynh_app_setting_delete --app=app --key=key
@ -96,15 +56,13 @@ ynh_app_setting_set_default() {
# #
# Requires YunoHost version 2.2.4 or higher. # Requires YunoHost version 2.2.4 or higher.
ynh_app_setting_delete() { ynh_app_setting_delete() {
local _globalapp=${app-:}
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local legacy_args=ak local legacy_args=ak
local -A args_array=([a]=app= [k]=key=) local -A args_array=( [a]=app= [k]=key= )
local app local app
local key local key
# Manage arguments with getopts # Manage arguments with getopts
ynh_handle_getopts_args "$@" ynh_handle_getopts_args "$@"
app="${app:-$_globalapp}"
if [[ "$key" =~ (unprotected|skipped|protected)_ ]]; then if [[ "$key" =~ (unprotected|skipped|protected)_ ]]; then
yunohost app setting $app $key -d yunohost app setting $app $key -d
@ -118,9 +76,10 @@ ynh_app_setting_delete() {
# #
# [internal] # [internal]
# #
ynh_app_setting() { ynh_app_setting()
{
set +o xtrace # set +x set +o xtrace # set +x
ACTION="$1" APP="$2" KEY="$3" VALUE="${4:-}" python3 - << EOF ACTION="$1" APP="$2" KEY="$3" VALUE="${4:-}" python3 - <<EOF
import os, yaml, sys import os, yaml, sys
app, action = os.environ['APP'], os.environ['ACTION'].lower() app, action = os.environ['APP'], os.environ['ACTION'].lower()
key, value = os.environ['KEY'], os.environ.get('VALUE', None) key, value = os.environ['KEY'], os.environ.get('VALUE', None)
@ -149,8 +108,6 @@ EOF
# Check availability of a web path # Check availability of a web path
# #
# [packagingv1]
#
# usage: ynh_webpath_available --domain=domain --path_url=path # usage: ynh_webpath_available --domain=domain --path_url=path
# | arg: -d, --domain= - the domain/host of the url # | arg: -d, --domain= - the domain/host of the url
# | arg: -p, --path_url= - the web path to check the availability of # | arg: -p, --path_url= - the web path to check the availability of
@ -158,10 +115,10 @@ EOF
# example: ynh_webpath_available --domain=some.domain.tld --path_url=/coffee # example: ynh_webpath_available --domain=some.domain.tld --path_url=/coffee
# #
# Requires YunoHost version 2.6.4 or higher. # Requires YunoHost version 2.6.4 or higher.
ynh_webpath_available() { ynh_webpath_available () {
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local legacy_args=dp local legacy_args=dp
local -A args_array=([d]=domain= [p]=path_url=) local -A args_array=( [d]=domain= [p]=path_url= )
local domain local domain
local path_url local path_url
# Manage arguments with getopts # Manage arguments with getopts
@ -172,8 +129,6 @@ ynh_webpath_available() {
# Register/book a web path for an app # Register/book a web path for an app
# #
# [packagingv1]
#
# usage: ynh_webpath_register --app=app --domain=domain --path_url=path # usage: ynh_webpath_register --app=app --domain=domain --path_url=path
# | arg: -a, --app= - the app for which the domain should be registered # | arg: -a, --app= - the app for which the domain should be registered
# | arg: -d, --domain= - the domain/host of the web path # | arg: -d, --domain= - the domain/host of the web path
@ -182,10 +137,10 @@ ynh_webpath_available() {
# example: ynh_webpath_register --app=wordpress --domain=some.domain.tld --path_url=/coffee # example: ynh_webpath_register --app=wordpress --domain=some.domain.tld --path_url=/coffee
# #
# Requires YunoHost version 2.6.4 or higher. # Requires YunoHost version 2.6.4 or higher.
ynh_webpath_register() { ynh_webpath_register () {
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local legacy_args=adp local legacy_args=adp
local -A args_array=([a]=app= [d]=domain= [p]=path_url=) local -A args_array=( [a]=app= [d]=domain= [p]=path_url= )
local app local app
local domain local domain
local path_url local path_url

View file

@ -4,7 +4,6 @@
# #
# usage: ynh_string_random [--length=string_length] # usage: ynh_string_random [--length=string_length]
# | arg: -l, --length= - the string length to generate (default: 24) # | arg: -l, --length= - the string length to generate (default: 24)
# | arg: -f, --filter= - the kind of characters accepted in the output (default: 'A-Za-z0-9')
# | ret: the generated string # | ret: the generated string
# #
# example: pwd=$(ynh_string_random --length=8) # example: pwd=$(ynh_string_random --length=8)
@ -12,17 +11,15 @@
# Requires YunoHost version 2.2.4 or higher. # Requires YunoHost version 2.2.4 or higher.
ynh_string_random() { ynh_string_random() {
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local legacy_args=lf local legacy_args=l
local -A args_array=([l]=length= [f]=filter=) local -A args_array=( [l]=length= )
local length local length
local filter
# Manage arguments with getopts # Manage arguments with getopts
ynh_handle_getopts_args "$@" ynh_handle_getopts_args "$@"
length=${length:-24} length=${length:-24}
filter=${filter:-'A-Za-z0-9'}
dd if=/dev/urandom bs=1 count=1000 2> /dev/null \ dd if=/dev/urandom bs=1 count=1000 2> /dev/null \
| tr --complement --delete "$filter" \ | tr --complement --delete 'A-Za-z0-9' \
| sed --quiet 's/\(.\{'"$length"'\}\).*/\1/p' | sed --quiet 's/\(.\{'"$length"'\}\).*/\1/p'
} }
@ -37,10 +34,10 @@ ynh_string_random() {
# sub-expressions can be used (see sed manual page for more information) # sub-expressions can be used (see sed manual page for more information)
# #
# Requires YunoHost version 2.6.4 or higher. # Requires YunoHost version 2.6.4 or higher.
ynh_replace_string() { ynh_replace_string () {
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local legacy_args=mrf local legacy_args=mrf
local -A args_array=([m]=match_string= [r]=replace_string= [f]=target_file=) local -A args_array=( [m]=match_string= [r]=replace_string= [f]=target_file= )
local match_string local match_string
local replace_string local replace_string
local target_file local target_file
@ -48,7 +45,7 @@ ynh_replace_string() {
ynh_handle_getopts_args "$@" ynh_handle_getopts_args "$@"
set +o xtrace # set +x set +o xtrace # set +x
local delimit=$'\001' local delimit=@
# Escape the delimiter if it's in the string. # Escape the delimiter if it's in the string.
match_string=${match_string//${delimit}/"\\${delimit}"} match_string=${match_string//${delimit}/"\\${delimit}"}
replace_string=${replace_string//${delimit}/"\\${delimit}"} replace_string=${replace_string//${delimit}/"\\${delimit}"}
@ -68,10 +65,10 @@ ynh_replace_string() {
# characters, you can't use some regular expressions and sub-expressions. # characters, you can't use some regular expressions and sub-expressions.
# #
# Requires YunoHost version 2.7.7 or higher. # Requires YunoHost version 2.7.7 or higher.
ynh_replace_special_string() { ynh_replace_special_string () {
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local legacy_args=mrf local legacy_args=mrf
local -A args_array=([m]=match_string= [r]=replace_string= [f]=target_file=) local -A args_array=( [m]=match_string= [r]=replace_string= [f]=target_file= )
local match_string local match_string
local replace_string local replace_string
local target_file local target_file
@ -91,8 +88,6 @@ ynh_replace_special_string() {
# Sanitize a string intended to be the name of a database # Sanitize a string intended to be the name of a database
# #
# [packagingv1]
#
# usage: ynh_sanitize_dbid --db_name=name # usage: ynh_sanitize_dbid --db_name=name
# | arg: -n, --db_name= - name to correct/sanitize # | arg: -n, --db_name= - name to correct/sanitize
# | ret: the corrected name # | ret: the corrected name
@ -102,10 +97,10 @@ ynh_replace_special_string() {
# Underscorify the string (replace - and . by _) # Underscorify the string (replace - and . by _)
# #
# Requires YunoHost version 2.2.4 or higher. # Requires YunoHost version 2.2.4 or higher.
ynh_sanitize_dbid() { ynh_sanitize_dbid () {
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local legacy_args=n local legacy_args=n
local -A args_array=([n]=db_name=) local -A args_array=( [n]=db_name= )
local db_name local db_name
# Manage arguments with getopts # Manage arguments with getopts
ynh_handle_getopts_args "$@" ynh_handle_getopts_args "$@"
@ -132,20 +127,20 @@ ynh_sanitize_dbid() {
# | arg: -p, --path_url= - URL path to normalize before using it # | arg: -p, --path_url= - URL path to normalize before using it
# #
# Requires YunoHost version 2.6.4 or higher. # Requires YunoHost version 2.6.4 or higher.
ynh_normalize_url_path() { ynh_normalize_url_path () {
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local legacy_args=p local legacy_args=p
local -A args_array=([p]=path_url=) local -A args_array=( [p]=path_url= )
local path_url local path_url
# Manage arguments with getopts # Manage arguments with getopts
ynh_handle_getopts_args "$@" ynh_handle_getopts_args "$@"
test -n "$path_url" || ynh_die --message="ynh_normalize_url_path expect a URL path as first argument and received nothing." test -n "$path_url" || ynh_die --message="ynh_normalize_url_path expect a URL path as first argument and received nothing."
if [ "${path_url:0:1}" != "/" ]; then # If the first character is not a / if [ "${path_url:0:1}" != "/" ]; then # If the first character is not a /
path_url="/$path_url" # Add / at begin of path variable path_url="/$path_url" # Add / at begin of path variable
fi fi
if [ "${path_url:${#path_url}-1}" == "/" ] && [ ${#path_url} -gt 1 ]; then # If the last character is a / and that not the only character. if [ "${path_url:${#path_url}-1}" == "/" ] && [ ${#path_url} -gt 1 ]; then # If the last character is a / and that not the only character.
path_url="${path_url:0:${#path_url}-1}" # Delete the last character path_url="${path_url:0:${#path_url}-1}" # Delete the last character
fi fi
echo $path_url echo $path_url
} }

View file

@ -12,18 +12,22 @@
# format and how placeholders are replaced with actual variables. # format and how placeholders are replaced with actual variables.
# #
# Requires YunoHost version 4.1.0 or higher. # Requires YunoHost version 4.1.0 or higher.
ynh_add_systemd_config() { ynh_add_systemd_config () {
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local legacy_args=stv local legacy_args=stv
local -A args_array=([s]=service= [t]=template=) local -A args_array=( [s]=service= [t]=template= [v]=others_var=)
local service local service
local template local template
local others_var
# Manage arguments with getopts # Manage arguments with getopts
ynh_handle_getopts_args "$@" ynh_handle_getopts_args "$@"
service="${service:-$app}" service="${service:-$app}"
template="${template:-systemd.service}" template="${template:-systemd.service}"
others_var="${others_var:-}"
ynh_add_config --template="$template" --destination="/etc/systemd/system/$service.service" [[ -z "$others_var" ]] || ynh_print_warn --message="Packagers: using --others_var is unecessary since YunoHost 4.2"
ynh_add_config --template="$YNH_APP_BASEDIR/conf/$template" --destination="/etc/systemd/system/$service.service"
systemctl enable $service --quiet systemctl enable $service --quiet
systemctl daemon-reload systemctl daemon-reload
@ -35,17 +39,18 @@ ynh_add_systemd_config() {
# | arg: -s, --service= - Service name (optionnal, $app by default) # | arg: -s, --service= - Service name (optionnal, $app by default)
# #
# Requires YunoHost version 2.7.2 or higher. # Requires YunoHost version 2.7.2 or higher.
ynh_remove_systemd_config() { ynh_remove_systemd_config () {
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local legacy_args=s local legacy_args=s
local -A args_array=([s]=service=) local -A args_array=( [s]=service= )
local service local service
# Manage arguments with getopts # Manage arguments with getopts
ynh_handle_getopts_args "$@" ynh_handle_getopts_args "$@"
local service="${service:-$app}" local service="${service:-$app}"
local finalsystemdconf="/etc/systemd/system/$service.service" local finalsystemdconf="/etc/systemd/system/$service.service"
if [ -e "$finalsystemdconf" ]; then if [ -e "$finalsystemdconf" ]
then
ynh_systemd_action --service_name=$service --action=stop ynh_systemd_action --service_name=$service --action=stop
systemctl disable $service --quiet systemctl disable $service --quiet
ynh_secure_remove --file="$finalsystemdconf" ynh_secure_remove --file="$finalsystemdconf"
@ -61,13 +66,13 @@ ynh_remove_systemd_config() {
# | arg: -l, --line_match= - Line to match - The line to find in the log to attest the service have finished to boot. If not defined it don't wait until the service is completely started. # | arg: -l, --line_match= - Line to match - The line to find in the log to attest the service have finished to boot. If not defined it don't wait until the service is completely started.
# | arg: -p, --log_path= - Log file - Path to the log file. Default : `/var/log/$app/$app.log` # | arg: -p, --log_path= - Log file - Path to the log file. Default : `/var/log/$app/$app.log`
# | arg: -t, --timeout= - Timeout - The maximum time to wait before ending the watching. Default : 300 seconds. # | arg: -t, --timeout= - Timeout - The maximum time to wait before ending the watching. Default : 300 seconds.
# | arg: -e, --length= - Length of the error log displayed for debugging : Default : 20 # | arg: -e, --length= - Length of the error log : Default : 20
# #
# Requires YunoHost version 3.5.0 or higher. # Requires YunoHost version 3.5.0 or higher.
ynh_systemd_action() { ynh_systemd_action() {
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local legacy_args=nalpte local legacy_args=nalpte
local -A args_array=([n]=service_name= [a]=action= [l]=line_match= [p]=log_path= [t]=timeout= [e]=length=) local -A args_array=( [n]=service_name= [a]=action= [l]=line_match= [p]=log_path= [t]=timeout= [e]=length= )
local service_name local service_name
local action local action
local line_match local line_match
@ -84,15 +89,18 @@ ynh_systemd_action() {
timeout=${timeout:-300} timeout=${timeout:-300}
# Manage case of service already stopped # Manage case of service already stopped
if [ "$action" == "stop" ] && ! systemctl is-active --quiet $service_name; then if [ "$action" == "stop" ] && ! systemctl is-active --quiet $service_name
then
return 0 return 0
fi fi
# Start to read the log # Start to read the log
if [[ -n "$line_match" ]]; then if [[ -n "$line_match" ]]
then
local templog="$(mktemp)" local templog="$(mktemp)"
# Following the starting of the app in its log # Following the starting of the app in its log
if [ "$log_path" == "systemd" ]; then if [ "$log_path" == "systemd" ]
then
# Read the systemd journal # Read the systemd journal
journalctl --unit=$service_name --follow --since=-0 --quiet > "$templog" & journalctl --unit=$service_name --follow --since=-0 --quiet > "$templog" &
# Get the PID of the journalctl command # Get the PID of the journalctl command
@ -110,14 +118,14 @@ ynh_systemd_action() {
action="reload-or-restart" action="reload-or-restart"
fi fi
local time_start="$(date --utc --rfc-3339=seconds | cut -d+ -f1) UTC"
# If the service fails to perform the action # If the service fails to perform the action
if ! systemctl $action $service_name; then if ! systemctl $action $service_name
then
# Show syslog for this service # Show syslog for this service
ynh_exec_err journalctl --quiet --no-hostname --no-pager --lines=$length --unit=$service_name ynh_exec_err journalctl --quiet --no-hostname --no-pager --lines=$length --unit=$service_name
# If a log is specified for this service, show also the content of this log # If a log is specified for this service, show also the content of this log
if [ -e "$log_path" ]; then if [ -e "$log_path" ]
then
ynh_exec_err tail --lines=$length "$log_path" ynh_exec_err tail --lines=$length "$log_path"
fi fi
ynh_clean_check_starting ynh_clean_check_starting
@ -125,45 +133,34 @@ ynh_systemd_action() {
fi fi
# Start the timeout and try to find line_match # Start the timeout and try to find line_match
if [[ -n "${line_match:-}" ]]; then if [[ -n "${line_match:-}" ]]
then
set +x set +x
local i=0 local i=0
local starttime=$(date +%s) for i in $(seq 1 $timeout)
for i in $(seq 1 $timeout); do do
# Read the log until the sentence is found, that means the app finished to start. Or run until the timeout # Read the log until the sentence is found, that means the app finished to start. Or run until the timeout
if [ "$log_path" == "systemd" ]; then if grep --extended-regexp --quiet "$line_match" "$templog"
# For systemd services, we in fact dont rely on the templog, which for some reason is not reliable, but instead re-read journalctl every iteration, starting at the timestamp where we triggered the action then
if journalctl --unit=$service_name --since="$time_start" --quiet --no-pager --no-hostname | grep --extended-regexp --quiet "$line_match"; then ynh_print_info --message="The service $service_name has correctly executed the action ${action}."
ynh_print_info --message="The service $service_name has correctly executed the action ${action}." break
break
fi
else
if grep --extended-regexp --quiet "$line_match" "$templog"; then
ynh_print_info --message="The service $service_name has correctly executed the action ${action}."
break
fi
fi fi
if [ $i -eq 30 ]; then if [ $i -eq 30 ]; then
echo "(this may take some time)" >&2 echo "(this may take some time)" >&2
fi fi
# Also check the timeout using actual timestamp, because sometimes for some reason,
# journalctl may take a huge time to run, and we end up waiting literally an entire hour
# instead of 5 min ...
if [[ "$(($(date +%s) - $starttime))" -gt "$timeout" ]]; then
i=$timeout
break
fi
sleep 1 sleep 1
done done
set -x set -x
if [ $i -ge 3 ]; then if [ $i -ge 3 ]; then
echo "" >&2 echo "" >&2
fi fi
if [ $i -eq $timeout ]; then if [ $i -eq $timeout ]
then
ynh_print_warn --message="The service $service_name didn't fully executed the action ${action} before the timeout." ynh_print_warn --message="The service $service_name didn't fully executed the action ${action} before the timeout."
ynh_print_warn --message="Please find here an extract of the end of the log of the service $service_name:" ynh_print_warn --message="Please find here an extract of the end of the log of the service $service_name:"
ynh_exec_warn journalctl --quiet --no-hostname --no-pager --lines=$length --unit=$service_name ynh_exec_warn journalctl --quiet --no-hostname --no-pager --lines=$length --unit=$service_name
if [ -e "$log_path" ]; then if [ -e "$log_path" ]
then
ynh_print_warn --message="\-\-\-" ynh_print_warn --message="\-\-\-"
ynh_exec_warn tail --lines=$length "$log_path" ynh_exec_warn tail --lines=$length "$log_path"
fi fi
@ -177,12 +174,14 @@ ynh_systemd_action() {
# [internal] # [internal]
# #
# Requires YunoHost version 3.5.0 or higher. # Requires YunoHost version 3.5.0 or higher.
ynh_clean_check_starting() { ynh_clean_check_starting () {
if [ -n "${pid_tail:-}" ]; then if [ -n "${pid_tail:-}" ]
then
# Stop the execution of tail. # Stop the execution of tail.
kill -SIGTERM $pid_tail 2>&1 kill -SIGTERM $pid_tail 2>&1
fi fi
if [ -n "${templog:-}" ]; then if [ -n "${templog:-}" ]
then
ynh_secure_remove --file="$templog" 2>&1 ynh_secure_remove --file="$templog" 2>&1
fi fi
} }

View file

@ -1,8 +1,60 @@
#!/bin/bash #!/bin/bash
# Check if a user exists on the system # Check if a YunoHost user exists
# #
# [packagingv1] # usage: ynh_user_exists --username=username
# | arg: -u, --username= - the username to check
# | ret: 0 if the user exists, 1 otherwise.
#
# example: ynh_user_exists 'toto' || echo "User does not exist"
#
# Requires YunoHost version 2.2.4 or higher.
ynh_user_exists() {
# Declare an array to define the options of this helper.
local legacy_args=u
local -A args_array=( [u]=username= )
local username
# Manage arguments with getopts
ynh_handle_getopts_args "$@"
yunohost user list --output-as json --quiet | jq -e ".users.${username}" >/dev/null
}
# Retrieve a YunoHost user information
#
# usage: ynh_user_get_info --username=username --key=key
# | arg: -u, --username= - the username to retrieve info from
# | arg: -k, --key= - the key to retrieve
# | ret: the value associate to that key
#
# example: mail=$(ynh_user_get_info 'toto' 'mail')
#
# Requires YunoHost version 2.2.4 or higher.
ynh_user_get_info() {
# Declare an array to define the options of this helper.
local legacy_args=uk
local -A args_array=( [u]=username= [k]=key= )
local username
local key
# Manage arguments with getopts
ynh_handle_getopts_args "$@"
yunohost user info "$username" --output-as json --quiet | jq -r ".$key"
}
# Get the list of YunoHost users
#
# usage: ynh_user_list
# | ret: one username per line as strings
#
# example: for u in $(ynh_user_list); do ... ; done
#
# Requires YunoHost version 2.4.0 or higher.
ynh_user_list() {
yunohost user list --output-as json --quiet | jq -r ".users | keys[]"
}
# Check if a user exists on the system
# #
# usage: ynh_system_user_exists --username=username # usage: ynh_system_user_exists --username=username
# | arg: -u, --username= - the username to check # | arg: -u, --username= - the username to check
@ -12,18 +64,16 @@
ynh_system_user_exists() { ynh_system_user_exists() {
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local legacy_args=u local legacy_args=u
local -A args_array=([u]=username=) local -A args_array=( [u]=username= )
local username local username
# Manage arguments with getopts # Manage arguments with getopts
ynh_handle_getopts_args "$@" ynh_handle_getopts_args "$@"
getent passwd "$username" &> /dev/null getent passwd "$username" &>/dev/null
} }
# Check if a group exists on the system # Check if a group exists on the system
# #
# [packagingv1]
#
# usage: ynh_system_group_exists --group=group # usage: ynh_system_group_exists --group=group
# | arg: -g, --group= - the group to check # | arg: -g, --group= - the group to check
# | ret: 0 if the group exists, 1 otherwise. # | ret: 0 if the group exists, 1 otherwise.
@ -32,12 +82,12 @@ ynh_system_user_exists() {
ynh_system_group_exists() { ynh_system_group_exists() {
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local legacy_args=g local legacy_args=g
local -A args_array=([g]=group=) local -A args_array=( [g]=group= )
local group local group
# Manage arguments with getopts # Manage arguments with getopts
ynh_handle_getopts_args "$@" ynh_handle_getopts_args "$@"
getent group "$group" &> /dev/null getent group "$group" &>/dev/null
} }
# Create a system user # Create a system user
@ -58,10 +108,10 @@ ynh_system_group_exists() {
# ``` # ```
# #
# Requires YunoHost version 2.6.4 or higher. # Requires YunoHost version 2.6.4 or higher.
ynh_system_user_create() { ynh_system_user_create () {
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local legacy_args=uhs local legacy_args=uhs
local -A args_array=([u]=username= [h]=home_dir= [s]=use_shell [g]=groups=) local -A args_array=( [u]=username= [h]=home_dir= [s]=use_shell [g]=groups= )
local username local username
local home_dir local home_dir
local use_shell local use_shell
@ -73,15 +123,17 @@ ynh_system_user_create() {
home_dir="${home_dir:-}" home_dir="${home_dir:-}"
groups="${groups:-}" groups="${groups:-}"
if ! ynh_system_user_exists "$username"; then # Check if the user exists on the system if ! ynh_system_user_exists "$username" # Check if the user exists on the system
# If the user doesn't exist then # If the user doesn't exist
if [ -n "$home_dir" ]; then # If a home dir is mentioned if [ -n "$home_dir" ]
then # If a home dir is mentioned
local user_home_dir="--home-dir $home_dir" local user_home_dir="--home-dir $home_dir"
else else
local user_home_dir="--no-create-home" local user_home_dir="--no-create-home"
fi fi
if [ $use_shell -eq 1 ]; then # If we want a shell for the user if [ $use_shell -eq 1 ]
local shell="" # Use default shell then # If we want a shell for the user
local shell="" # Use default shell
else else
local shell="--shell /usr/sbin/nologin" local shell="--shell /usr/sbin/nologin"
fi fi
@ -89,7 +141,8 @@ ynh_system_user_create() {
fi fi
local group local group
for group in $groups; do for group in $groups
do
usermod -a -G "$group" "$username" usermod -a -G "$group" "$username"
done done
} }
@ -100,23 +153,25 @@ ynh_system_user_create() {
# | arg: -u, --username= - Name of the system user that will be create # | arg: -u, --username= - Name of the system user that will be create
# #
# Requires YunoHost version 2.6.4 or higher. # Requires YunoHost version 2.6.4 or higher.
ynh_system_user_delete() { ynh_system_user_delete () {
# Declare an array to define the options of this helper. # Declare an array to define the options of this helper.
local legacy_args=u local legacy_args=u
local -A args_array=([u]=username=) local -A args_array=( [u]=username= )
local username local username
# Manage arguments with getopts # Manage arguments with getopts
ynh_handle_getopts_args "$@" ynh_handle_getopts_args "$@"
# Check if the user exists on the system # Check if the user exists on the system
if ynh_system_user_exists "$username"; then if ynh_system_user_exists "$username"
then
deluser $username deluser $username
else else
ynh_print_warn --message="The user $username was not found" ynh_print_warn --message="The user $username was not found"
fi fi
# Check if the group exists on the system # Check if the group exists on the system
if ynh_system_group_exists "$username"; then if ynh_system_group_exists "$username"
then
delgroup $username delgroup $username
fi fi
} }

985
data/helpers.d/utils Normal file
View file

@ -0,0 +1,985 @@
#!/bin/bash
YNH_APP_BASEDIR=${YNH_APP_BASEDIR:-$(realpath ..)}
# Handle script crashes / failures
#
# [internal]
#
# usage:
# ynh_exit_properly is used only by the helper ynh_abort_if_errors.
# You should not use it directly.
# Instead, add to your script:
# ynh_clean_setup () {
# instructions...
# }
#
# This function provide a way to clean some residual of installation that not managed by remove script.
#
# It prints a warning to inform that the script was failed, and execute the ynh_clean_setup function if used in the app script
#
# Requires YunoHost version 2.6.4 or higher.
ynh_exit_properly () {
local exit_code=$?
rm -rf "/var/cache/yunohost/download/"
if [ "$exit_code" -eq 0 ]; then
exit 0 # Exit without error if the script ended correctly
fi
trap '' EXIT # Ignore new exit signals
# Do not exit anymore if a command fail or if a variable is empty
set +o errexit # set +e
set +o nounset # set +u
# Small tempo to avoid the next message being mixed up with other DEBUG messages
sleep 0.5
if type -t ynh_clean_setup > /dev/null; then # Check if the function exist in the app script.
ynh_clean_setup # Call the function to do specific cleaning for the app.
fi
# Exit with error status
# We don't call ynh_die basically to avoid unecessary 10-ish
# debug lines about parsing args and stuff just to exit 1..
exit 1
}
# Exits if an error occurs during the execution of the script.
#
# usage: ynh_abort_if_errors
#
# This configure the rest of the script execution such that, if an error occurs
# or if an empty variable is used, the execution of the script stops immediately
# and a call to `ynh_clean_setup` is triggered if it has been defined by your script.
#
# Requires YunoHost version 2.6.4 or higher.
ynh_abort_if_errors () {
set -o errexit # set -e; Exit if a command fail
set -o nounset # set -u; And if a variable is used unset
trap ynh_exit_properly EXIT # Capturing exit signals on shell script
}
# Download, check integrity, uncompress and patch the source from app.src
#
# usage: ynh_setup_source --dest_dir=dest_dir [--source_id=source_id] [--keep="file1 file2"]
# | arg: -d, --dest_dir= - Directory where to setup sources
# | arg: -s, --source_id= - Name of the source, defaults to `app`
# | arg: -k, --keep= - Space-separated list of files/folders that will be backup/restored in $dest_dir, such as a config file you don't want to overwrite. For example 'conf.json secrets.json logs/'
#
# This helper will read `conf/${source_id}.src`, download and install the sources.
#
# The src file need to contains:
# ```
# SOURCE_URL=Address to download the app archive
# SOURCE_SUM=Control sum
# # (Optional) Program to check the integrity (sha256sum, md5sum...). Default: sha256
# SOURCE_SUM_PRG=sha256
# # (Optional) Archive format. Default: tar.gz
# SOURCE_FORMAT=tar.gz
# # (Optional) Put false if sources are directly in the archive root. Default: true
# # Instead of true, SOURCE_IN_SUBDIR could be the number of sub directories to remove.
# SOURCE_IN_SUBDIR=false
# # (Optionnal) Name of the local archive (offline setup support). Default: ${src_id}.${src_format}
# SOURCE_FILENAME=example.tar.gz
# # (Optional) If it set as false don't extract the source. Default: true
# # (Useful to get a debian package or a python wheel.)
# SOURCE_EXTRACT=(true|false)
# ```
#
# The helper will:
# - Check if there is a local source archive in `/opt/yunohost-apps-src/$APP_ID/$SOURCE_FILENAME`
# - Download `$SOURCE_URL` if there is no local archive
# - Check the integrity with `$SOURCE_SUM_PRG -c --status`
# - Uncompress the archive to `$dest_dir`.
# - If `$SOURCE_IN_SUBDIR` is true, the first level directory of the archive will be removed.
# - If `$SOURCE_IN_SUBDIR` is a numeric value, the N first level directories will be removed.
# - Patches named `sources/patches/${src_id}-*.patch` will be applied to `$dest_dir`
# - Extra files in `sources/extra_files/$src_id` will be copied to dest_dir
#
# Requires YunoHost version 2.6.4 or higher.
ynh_setup_source () {
# Declare an array to define the options of this helper.
local legacy_args=dsk
local -A args_array=( [d]=dest_dir= [s]=source_id= [k]=keep= )
local dest_dir
local source_id
local keep
# Manage arguments with getopts
ynh_handle_getopts_args "$@"
source_id="${source_id:-app}"
keep="${keep:-}"
local src_file_path="$YNH_APP_BASEDIR/conf/${source_id}.src"
# Load value from configuration file (see above for a small doc about this file
# format)
local src_url=$(grep 'SOURCE_URL=' "$src_file_path" | cut --delimiter='=' --fields=2-)
local src_sum=$(grep 'SOURCE_SUM=' "$src_file_path" | cut --delimiter='=' --fields=2-)
local src_sumprg=$(grep 'SOURCE_SUM_PRG=' "$src_file_path" | cut --delimiter='=' --fields=2-)
local src_format=$(grep 'SOURCE_FORMAT=' "$src_file_path" | cut --delimiter='=' --fields=2-)
local src_extract=$(grep 'SOURCE_EXTRACT=' "$src_file_path" | cut --delimiter='=' --fields=2-)
local src_in_subdir=$(grep 'SOURCE_IN_SUBDIR=' "$src_file_path" | cut --delimiter='=' --fields=2-)
local src_filename=$(grep 'SOURCE_FILENAME=' "$src_file_path" | cut --delimiter='=' --fields=2-)
# Default value
src_sumprg=${src_sumprg:-sha256sum}
src_in_subdir=${src_in_subdir:-true}
src_format=${src_format:-tar.gz}
src_format=$(echo "$src_format" | tr '[:upper:]' '[:lower:]')
src_extract=${src_extract:-true}
if [ "$src_filename" = "" ]; then
src_filename="${source_id}.${src_format}"
fi
# (Unused?) mecanism where one can have the file in a special local cache to not have to download it...
local local_src="/opt/yunohost-apps-src/${YNH_APP_ID}/${src_filename}"
mkdir -p /var/cache/yunohost/download/${YNH_APP_ID}/
src_filename="/var/cache/yunohost/download/${YNH_APP_ID}/${src_filename}"
if test -e "$local_src"
then
cp $local_src $src_filename
else
[ -n "$src_url" ] || ynh_die "Couldn't parse SOURCE_URL from $src_file_path ?"
# NB. we have to declare the var as local first,
# otherwise 'local foo=$(false) || echo 'pwet'" does'nt work
# because local always return 0 ...
local out
# Timeout option is here to enforce the timeout on dns query and tcp connect (c.f. man wget)
out=$(wget --tries 3 --no-dns-cache --timeout 900 --no-verbose --output-document=$src_filename $src_url 2>&1) \
|| ynh_die --message="$out"
fi
# Check the control sum
echo "${src_sum} ${src_filename}" | ${src_sumprg} --check --status \
|| ynh_die --message="Corrupt source"
# Keep files to be backup/restored at the end of the helper
# Assuming $dest_dir already exists
rm -rf /var/cache/yunohost/files_to_keep_during_setup_source/
if [ -n "$keep" ] && [ -e "$dest_dir" ]
then
local keep_dir=/var/cache/yunohost/files_to_keep_during_setup_source/${YNH_APP_ID}
mkdir -p $keep_dir
local stuff_to_keep
for stuff_to_keep in $keep
do
if [ -e "$dest_dir/$stuff_to_keep" ]
then
mkdir --parents "$(dirname "$keep_dir/$stuff_to_keep")"
cp --archive "$dest_dir/$stuff_to_keep" "$keep_dir/$stuff_to_keep"
fi
done
fi
# Extract source into the app dir
mkdir --parents "$dest_dir"
if [ -n "${final_path:-}" ] && [ "$dest_dir" == "$final_path" ]
then
_ynh_apply_default_permissions $dest_dir
fi
if ! "$src_extract"
then
mv $src_filename $dest_dir
elif [ "$src_format" = "zip" ]
then
# Zip format
# Using of a temp directory, because unzip doesn't manage --strip-components
if $src_in_subdir
then
local tmp_dir=$(mktemp --directory)
unzip -quo $src_filename -d "$tmp_dir"
cp --archive $tmp_dir/*/. "$dest_dir"
ynh_secure_remove --file="$tmp_dir"
else
unzip -quo $src_filename -d "$dest_dir"
fi
ynh_secure_remove --file="$src_filename"
else
local strip=""
if [ "$src_in_subdir" != "false" ]
then
if [ "$src_in_subdir" == "true" ]
then
local sub_dirs=1
else
local sub_dirs="$src_in_subdir"
fi
strip="--strip-components $sub_dirs"
fi
if [[ "$src_format" =~ ^tar.gz|tar.bz2|tar.xz$ ]]
then
tar --extract --file=$src_filename --directory="$dest_dir" $strip
else
ynh_die --message="Archive format unrecognized."
fi
ynh_secure_remove --file="$src_filename"
fi
# Apply patches
if [ -d "$YNH_APP_BASEDIR/sources/patches/" ]
then
local patches_folder=$(realpath $YNH_APP_BASEDIR/sources/patches/)
if (( $(find $patches_folder -type f -name "${source_id}-*.patch" 2> /dev/null | wc --lines) > "0" ))
then
(cd "$dest_dir"
for p in $patches_folder/${source_id}-*.patch
do
echo $p
patch --strip=1 < $p
done) || ynh_die --message="Unable to apply patches"
fi
fi
# Add supplementary files
if test -e "$YNH_APP_BASEDIR/sources/extra_files/${source_id}"; then
cp --archive $YNH_APP_BASEDIR/sources/extra_files/$source_id/. "$dest_dir"
fi
# Keep files to be backup/restored at the end of the helper
# Assuming $dest_dir already exists
if [ -n "$keep" ]
then
local keep_dir=/var/cache/yunohost/files_to_keep_during_setup_source/${YNH_APP_ID}
local stuff_to_keep
for stuff_to_keep in $keep
do
if [ -e "$keep_dir/$stuff_to_keep" ]
then
mkdir --parents "$(dirname "$dest_dir/$stuff_to_keep")"
cp --archive "$keep_dir/$stuff_to_keep" "$dest_dir/$stuff_to_keep"
fi
done
fi
rm -rf /var/cache/yunohost/files_to_keep_during_setup_source/
}
# Curl abstraction to help with POST requests to local pages (such as installation forms)
#
# usage: ynh_local_curl "page_uri" "key1=value1" "key2=value2" ...
# | arg: page_uri - Path (relative to `$path_url`) of the page where POST data will be sent
# | arg: key1=value1 - (Optionnal) POST key and corresponding value
# | arg: key2=value2 - (Optionnal) Another POST key and corresponding value
# | arg: ... - (Optionnal) More POST keys and values
#
# example: ynh_local_curl "/install.php?installButton" "foo=$var1" "bar=$var2"
#
# For multiple calls, cookies are persisted between each call for the same app
#
# `$domain` and `$path_url` should be defined externally (and correspond to the domain.tld and the /path (of the app?))
#
# Requires YunoHost version 2.6.4 or higher.
ynh_local_curl () {
# Define url of page to curl
local local_page=$(ynh_normalize_url_path $1)
local full_path=$path_url$local_page
if [ "${path_url}" == "/" ]; then
full_path=$local_page
fi
local full_page_url=https://localhost$full_path
# Concatenate all other arguments with '&' to prepare POST data
local POST_data=""
local arg=""
for arg in "${@:2}"
do
POST_data="${POST_data}${arg}&"
done
if [ -n "$POST_data" ]
then
# Add --data arg and remove the last character, which is an unecessary '&'
POST_data="--data ${POST_data::-1}"
fi
# Wait untils nginx has fully reloaded (avoid curl fail with http2)
sleep 2
local cookiefile=/tmp/ynh-$app-cookie.txt
touch $cookiefile
chown root $cookiefile
chmod 700 $cookiefile
# Curl the URL
curl --silent --show-error --insecure --location --header "Host: $domain" --resolve $domain:443:127.0.0.1 $POST_data "$full_page_url" --cookie-jar $cookiefile --cookie $cookiefile
}
# Create a dedicated config file from a template
#
# usage: ynh_add_config --template="template" --destination="destination"
# | arg: -t, --template= - Template config file to use
# | arg: -d, --destination= - Destination of the config file
#
# examples:
# ynh_add_config --template=".env" --destination="$final_path/.env"
# ynh_add_config --template="../conf/.env" --destination="$final_path/.env"
# ynh_add_config --template="/etc/nginx/sites-available/default" --destination="etc/nginx/sites-available/mydomain.conf"
#
# The template can be by default the name of a file in the conf directory
# of a YunoHost Package, a relative path or an absolute path.
#
# The helper will use the template `template` to generate a config file
# `destination` by replacing the following keywords with global variables
# that should be defined before calling this helper :
# ```
# __PATH__ by $path_url
# __NAME__ by $app
# __NAMETOCHANGE__ by $app
# __USER__ by $app
# __FINALPATH__ by $final_path
# __PHPVERSION__ by $YNH_PHP_VERSION
# __YNH_NODE_LOAD_PATH__ by $ynh_node_load_PATH
# ```
# And any dynamic variables that should be defined before calling this helper like:
# ```
# __DOMAIN__ by $domain
# __APP__ by $app
# __VAR_1__ by $var_1
# __VAR_2__ by $var_2
# ```
#
# The helper will verify the checksum and backup the destination file
# if it's different before applying the new template.
#
# And it will calculate and store the destination file checksum
# into the app settings when configuration is done.
#
# Requires YunoHost version 4.1.0 or higher.
ynh_add_config () {
# Declare an array to define the options of this helper.
local legacy_args=tdv
local -A args_array=( [t]=template= [d]=destination= )
local template
local destination
# Manage arguments with getopts
ynh_handle_getopts_args "$@"
local template_path
if [ -f "$YNH_APP_BASEDIR/conf/$template" ]; then
template_path="$YNH_APP_BASEDIR/conf/$template"
elif [ -f "$template" ]; then
template_path=$template
else
ynh_die --message="The provided template $template doesn't exist"
fi
ynh_backup_if_checksum_is_different --file="$destination"
# Make sure to set the permissions before we copy the file
# This is to cover a case where an attacker could have
# created a file beforehand to have control over it
# (cp won't overwrite ownership / modes by default...)
touch $destination
chown root:root $destination
chmod 640 $destination
cp -f "$template_path" "$destination"
_ynh_apply_default_permissions $destination
ynh_replace_vars --file="$destination"
ynh_store_file_checksum --file="$destination"
}
# Replace variables in a file
#
# [internal]
#
# usage: ynh_replace_vars --file="file"
# | arg: -f, --file= - File where to replace variables
#
# The helper will replace the following keywords with global variables
# that should be defined before calling this helper :
# __PATH__ by $path_url
# __NAME__ by $app
# __NAMETOCHANGE__ by $app
# __USER__ by $app
# __FINALPATH__ by $final_path
# __PHPVERSION__ by $YNH_PHP_VERSION
# __YNH_NODE_LOAD_PATH__ by $ynh_node_load_PATH
#
# And any dynamic variables that should be defined before calling this helper like:
# __DOMAIN__ by $domain
# __APP__ by $app
# __VAR_1__ by $var_1
# __VAR_2__ by $var_2
#
# Requires YunoHost version 4.1.0 or higher.
ynh_replace_vars () {
# Declare an array to define the options of this helper.
local legacy_args=f
local -A args_array=( [f]=file= )
local file
# Manage arguments with getopts
ynh_handle_getopts_args "$@"
# Replace specific YunoHost variables
if test -n "${path_url:-}"
then
# path_url_slash_less is path_url, or a blank value if path_url is only '/'
local path_url_slash_less=${path_url%/}
ynh_replace_string --match_string="__PATH__/" --replace_string="$path_url_slash_less/" --target_file="$file"
ynh_replace_string --match_string="__PATH__" --replace_string="$path_url" --target_file="$file"
fi
if test -n "${app:-}"; then
ynh_replace_string --match_string="__NAME__" --replace_string="$app" --target_file="$file"
ynh_replace_string --match_string="__NAMETOCHANGE__" --replace_string="$app" --target_file="$file"
ynh_replace_string --match_string="__USER__" --replace_string="$app" --target_file="$file"
fi
if test -n "${final_path:-}"; then
ynh_replace_string --match_string="__FINALPATH__" --replace_string="$final_path" --target_file="$file"
fi
if test -n "${YNH_PHP_VERSION:-}"; then
ynh_replace_string --match_string="__PHPVERSION__" --replace_string="$YNH_PHP_VERSION" --target_file="$file"
fi
if test -n "${ynh_node_load_PATH:-}"; then
ynh_replace_string --match_string="__YNH_NODE_LOAD_PATH__" --replace_string="$ynh_node_load_PATH" --target_file="$file"
fi
# Replace others variables
# List other unique (__ __) variables in $file
local uniques_vars=( $(grep -oP '__[A-Z0-9]+?[A-Z0-9_]*?[A-Z0-9]*?__' $file | sort --unique | sed "s@__\([^.]*\)__@\L\1@g" ))
# Do the replacement
local delimit=@
for one_var in "${uniques_vars[@]}"
do
# Validate that one_var is indeed defined
# -v checks if the variable is defined, for example:
# -v FOO tests if $FOO is defined
# -v $FOO tests if ${!FOO} is defined
# More info: https://stackoverflow.com/questions/3601515/how-to-check-if-a-variable-is-set-in-bash/17538964#comment96392525_17538964
[[ -v "${one_var:-}" ]] || ynh_die --message="Variable \$$one_var wasn't initialized when trying to replace __${one_var^^}__ in $file"
# Escape delimiter in match/replace string
match_string="__${one_var^^}__"
match_string=${match_string//${delimit}/"\\${delimit}"}
replace_string="${!one_var}"
replace_string=${replace_string//\\/\\\\}
replace_string=${replace_string//${delimit}/"\\${delimit}"}
# Actually replace (sed is used instead of ynh_replace_string to avoid triggering an epic amount of debug logs)
sed --in-place "s${delimit}${match_string}${delimit}${replace_string}${delimit}g" "$file"
done
}
# Get a value from heterogeneous file (yaml, json, php, python...)
#
# usage: ynh_read_var_in_file --file=PATH --key=KEY
# | arg: -f, --file= - the path to the file
# | arg: -k, --key= - the key to get
#
# This helpers match several var affectation use case in several languages
# We don't use jq or equivalent to keep comments and blank space in files
# This helpers work line by line, it is not able to work correctly
# if you have several identical keys in your files
#
# Example of line this helpers can managed correctly
# .yml
# title: YunoHost documentation
# email: 'yunohost@yunohost.org'
# .json
# "theme": "colib'ris",
# "port": 8102
# "some_boolean": false,
# "user": null
# .ini
# some_boolean = On
# action = "Clear"
# port = 20
# .php
# $user=
# user => 20
# .py
# USER = 8102
# user = 'https://donate.local'
# CUSTOM['user'] = 'YunoHost'
#
# Requires YunoHost version 4.3 or higher.
ynh_read_var_in_file() {
# Declare an array to define the options of this helper.
local legacy_args=fka
local -A args_array=( [f]=file= [k]=key= [a]=after=)
local file
local key
local after
# Manage arguments with getopts
ynh_handle_getopts_args "$@"
after="${after:-}"
[[ -f $file ]] || ynh_die --message="File $file does not exists"
set +o xtrace # set +x
# Get the line number after which we search for the variable
local line_number=1
if [[ -n "$after" ]];
then
line_number=$(grep -n $after $file | cut -d: -f1)
if [[ -z "$line_number" ]];
then
set -o xtrace # set -x
return 1
fi
fi
local filename="$(basename -- "$file")"
local ext="${filename##*.}"
local endline=',;'
local assign="=>|:|="
local comments="#"
local string="\"'"
if [[ "$ext" =~ ^ini|env|toml|yml|yaml$ ]]; then
endline='#'
fi
if [[ "$ext" =~ ^ini|env$ ]]; then
comments="[;#]"
fi
if [[ "php" == "$ext" ]] || [[ "$ext" == "js" ]]; then
comments="//"
fi
local list='\[\s*['$string']?\w+['$string']?\]'
local var_part='^\s*((const|var|let)\s+)?\$?(\w+('$list')*(->|\.|\[))*\s*'
var_part+="[$string]?${key}[$string]?"
var_part+='\s*\]?\s*'
var_part+="($assign)"
var_part+='\s*'
# Extract the part after assignation sign
local expression_with_comment="$(tail +$line_number ${file} | grep -i -o -P $var_part'\K.*$' || echo YNH_NULL | head -n1)"
if [[ "$expression_with_comment" == "YNH_NULL" ]]; then
set -o xtrace # set -x
echo YNH_NULL
return 0
fi
# Remove comments if needed
local expression="$(echo "$expression_with_comment" | sed "s@$comments[^$string]*\$@@g" | sed "s@\s*[$endline]*\s*]*\$@@")"
local first_char="${expression:0:1}"
if [[ "$first_char" == '"' ]] ; then
echo "$expression" | grep -m1 -o -P '"\K([^"](\\")?)*[^\\](?=")' | head -n1 | sed 's/\\"/"/g'
elif [[ "$first_char" == "'" ]] ; then
echo "$expression" | grep -m1 -o -P "'\K([^'](\\\\')?)*[^\\\\](?=')" | head -n1 | sed "s/\\\\'/'/g"
else
echo "$expression"
fi
set -o xtrace # set -x
}
# Set a value into heterogeneous file (yaml, json, php, python...)
#
# usage: ynh_write_var_in_file --file=PATH --key=KEY --value=VALUE
# | arg: -f, --file= - the path to the file
# | arg: -k, --key= - the key to set
# | arg: -v, --value= - the value to set
#
# Requires YunoHost version 4.3 or higher.
ynh_write_var_in_file() {
# Declare an array to define the options of this helper.
local legacy_args=fkva
local -A args_array=( [f]=file= [k]=key= [v]=value= [a]=after=)
local file
local key
local value
local after
# Manage arguments with getopts
ynh_handle_getopts_args "$@"
after="${after:-}"
[[ -f $file ]] || ynh_die --message="File $file does not exists"
set +o xtrace # set +x
# Get the line number after which we search for the variable
local line_number=1
if [[ -n "$after" ]];
then
line_number=$(grep -n $after $file | cut -d: -f1)
if [[ -z "$line_number" ]];
then
set -o xtrace # set -x
return 1
fi
fi
local range="${line_number},\$ "
local filename="$(basename -- "$file")"
local ext="${filename##*.}"
local endline=',;'
local assign="=>|:|="
local comments="#"
local string="\"'"
if [[ "$ext" =~ ^ini|env|toml|yml|yaml$ ]]; then
endline='#'
fi
if [[ "$ext" =~ ^ini|env$ ]]; then
comments="[;#]"
fi
if [[ "php" == "$ext" ]] || [[ "$ext" == "js" ]]; then
comments="//"
fi
local list='\[\s*['$string']?\w+['$string']?\]'
local var_part='^\s*((const|var|let)\s+)?\$?(\w+('$list')*(->|\.|\[))*\s*'
var_part+="[$string]?${key}[$string]?"
var_part+='\s*\]?\s*'
var_part+="($assign)"
var_part+='\s*'
# Extract the part after assignation sign
local expression_with_comment="$(tail +$line_number ${file} | grep -i -o -P $var_part'\K.*$' || echo YNH_NULL | head -n1)"
if [[ "$expression_with_comment" == "YNH_NULL" ]]; then
set -o xtrace # set -x
return 1
fi
# Remove comments if needed
local expression="$(echo "$expression_with_comment" | sed "s@$comments[^$string]*\$@@g" | sed "s@\s*[$endline]*\s*]*\$@@")"
endline=${expression_with_comment#"$expression"}
endline="$(echo "$endline" | sed 's/\\/\\\\/g')"
value="$(echo "$value" | sed 's/\\/\\\\/g')"
local first_char="${expression:0:1}"
delimiter=$'\001'
if [[ "$first_char" == '"' ]] ; then
# \ and sed is quite complex you need 2 \\ to get one in a sed
# So we need \\\\ to go through 2 sed
value="$(echo "$value" | sed 's/"/\\\\"/g')"
sed -ri "${range}s$delimiter"'(^'"${var_part}"'")([^"]|\\")*("[\s;,]*)(\s*'$comments'.*)?$'$delimiter'\1'"${value}"'"'"${endline}${delimiter}i" ${file}
elif [[ "$first_char" == "'" ]] ; then
# \ and sed is quite complex you need 2 \\ to get one in a sed
# However double quotes implies to double \\ to
# So we need \\\\\\\\ to go through 2 sed and 1 double quotes str
value="$(echo "$value" | sed "s/'/\\\\\\\\'/g")"
sed -ri "${range}s$delimiter(^${var_part}')([^']|\\')*('"'[\s,;]*)(\s*'$comments'.*)?$'$delimiter'\1'"${value}'${endline}${delimiter}i" ${file}
else
if [[ "$value" == *"'"* ]] || [[ "$value" == *'"'* ]] || [[ "$ext" =~ ^php|py|json|js$ ]] ; then
value='\"'"$(echo "$value" | sed 's/"/\\\\"/g')"'\"'
fi
if [[ "$ext" =~ ^yaml|yml$ ]] ; then
value=" $value"
fi
sed -ri "${range}s$delimiter(^${var_part}).*\$$delimiter\1${value}${endline}${delimiter}i" ${file}
fi
set -o xtrace # set -x
}
# Render templates with Jinja2
#
# [internal]
#
# Attention : Variables should be exported before calling this helper to be
# accessible inside templates.
#
# usage: ynh_render_template some_template output_path
# | arg: some_template - Template file to be rendered
# | arg: output_path - The path where the output will be redirected to
ynh_render_template() {
local template_path=$1
local output_path=$2
mkdir -p "$(dirname $output_path)"
# Taken from https://stackoverflow.com/a/35009576
python3 -c 'import os, sys, jinja2; sys.stdout.write(
jinja2.Template(sys.stdin.read()
).render(os.environ));' < $template_path > $output_path
}
# Fetch the Debian release codename
#
# usage: ynh_get_debian_release
# | ret: The Debian release codename (i.e. jessie, stretch, ...)
#
# Requires YunoHost version 2.7.12 or higher.
ynh_get_debian_release () {
echo $(lsb_release --codename --short)
}
# Create a directory under /tmp
#
# [internal]
#
# Deprecated helper
#
# usage: ynh_mkdir_tmp
# | ret: the created directory path
ynh_mkdir_tmp() {
ynh_print_warn --message="The helper ynh_mkdir_tmp is deprecated."
ynh_print_warn --message="You should use 'mktemp -d' instead and manage permissions \
properly with chmod/chown."
local TMP_DIR=$(mktemp --directory)
# Give rights to other users could be a security risk.
# But for retrocompatibility we need it. (This helpers is deprecated)
chmod 755 $TMP_DIR
echo $TMP_DIR
}
# Remove a file or a directory securely
#
# usage: ynh_secure_remove --file=path_to_remove
# | arg: -f, --file= - File or directory to remove
#
# Requires YunoHost version 2.6.4 or higher.
ynh_secure_remove () {
# Declare an array to define the options of this helper.
local legacy_args=f
local -A args_array=( [f]=file= )
local file
# Manage arguments with getopts
ynh_handle_getopts_args "$@"
set +o xtrace # set +x
local forbidden_path=" \
/var/www \
/home/yunohost.app"
if [ $# -ge 2 ]
then
ynh_print_warn --message="/!\ Packager ! You provided more than one argument to ynh_secure_remove but it will be ignored... Use this helper with one argument at time."
fi
if [[ -z "$file" ]]
then
ynh_print_warn --message="ynh_secure_remove called with empty argument, ignoring."
elif [[ "$forbidden_path" =~ "$file" \
# Match all paths or subpaths in $forbidden_path
|| "$file" =~ ^/[[:alnum:]]+$ \
# Match all first level paths from / (Like /var, /root, etc...)
|| "${file:${#file}-1}" = "/" ]]
# Match if the path finishes by /. Because it seems there is an empty variable
then
ynh_print_warn --message="Not deleting '$file' because it is not an acceptable path to delete."
elif [ -e "$file" ]
then
rm --recursive "$file"
else
ynh_print_info --message="'$file' wasn't deleted because it doesn't exist."
fi
set -o xtrace # set -x
}
# Extract a key from a plain command output
#
# [internal]
#
# (Deprecated, use --output-as json and jq instead)
ynh_get_plain_key() {
local prefix="#"
local founded=0
# We call this key_ so that it's not caught as
# an info to be redacted by the core
local key_=$1
shift
while read line
do
if [[ "$founded" == "1" ]]
then
[[ "$line" =~ ^${prefix}[^#] ]] && return
echo $line
elif [[ "$line" =~ ^${prefix}${key_}$ ]]
then
if [[ -n "${1:-}" ]]
then
prefix+="#"
key_=$1
shift
else
founded=1
fi
fi
done
}
# Read the value of a key in a ynh manifest file
#
# usage: ynh_read_manifest --manifest="manifest.json" --key="key"
# | arg: -m, --manifest= - Path of the manifest to read
# | arg: -k, --key= - Name of the key to find
# | ret: the value associate to that key
#
# Requires YunoHost version 3.5.0 or higher.
ynh_read_manifest () {
# Declare an array to define the options of this helper.
local legacy_args=mk
local -A args_array=( [m]=manifest= [k]=manifest_key= )
local manifest
local manifest_key
# Manage arguments with getopts
ynh_handle_getopts_args "$@"
if [ ! -e "$manifest" ]; then
# If the manifest isn't found, try the common place for backup and restore script.
manifest="$YNH_APP_BASEDIR/manifest.json"
fi
jq ".$manifest_key" "$manifest" --raw-output
}
# Read the upstream version from the manifest or `$YNH_APP_MANIFEST_VERSION`
#
# usage: ynh_app_upstream_version [--manifest="manifest.json"]
# | arg: -m, --manifest= - Path of the manifest to read
# | ret: the version number of the upstream app
#
# If the `manifest` is not specified, the envvar `$YNH_APP_MANIFEST_VERSION` will be used.
#
# The version number in the manifest is defined by `<upstreamversion>~ynh<packageversion>`.
#
# For example, if the manifest contains `4.3-2~ynh3` the function will return `4.3-2`
#
# Requires YunoHost version 3.5.0 or higher.
ynh_app_upstream_version () {
# Declare an array to define the options of this helper.
local legacy_args=m
local -A args_array=( [m]=manifest= )
local manifest
# Manage arguments with getopts
ynh_handle_getopts_args "$@"
manifest="${manifest:-}"
if [[ "$manifest" != "" ]] && [[ -e "$manifest" ]];
then
version_key_=$(ynh_read_manifest --manifest="$manifest" --manifest_key="version")
else
version_key_=$YNH_APP_MANIFEST_VERSION
fi
echo "${version_key_/~ynh*/}"
}
# Read package version from the manifest
#
# usage: ynh_app_package_version [--manifest="manifest.json"]
# | arg: -m, --manifest= - Path of the manifest to read
# | ret: the version number of the package
#
# The version number in the manifest is defined by `<upstreamversion>~ynh<packageversion>`.
#
# For example, if the manifest contains `4.3-2~ynh3` the function will return `3`
#
# Requires YunoHost version 3.5.0 or higher.
ynh_app_package_version () {
# Declare an array to define the options of this helper.
local legacy_args=m
local -A args_array=( [m]=manifest= )
local manifest
# Manage arguments with getopts
ynh_handle_getopts_args "$@"
version_key_=$YNH_APP_MANIFEST_VERSION
echo "${version_key_/*~ynh/}"
}
# Checks the app version to upgrade with the existing app version and returns:
#
# usage: ynh_check_app_version_changed
# | ret: `UPGRADE_APP` if the upstream version changed, `UPGRADE_PACKAGE` otherwise.
#
# This helper should be used to avoid an upgrade of an app, or the upstream part
# of it, when it's not needed
#
# You can force an upgrade, even if the package is up to date, with the `--force` (or `-F`) argument :
# ```
# sudo yunohost app upgrade <appname> --force
# ```
# Requires YunoHost version 3.5.0 or higher.
ynh_check_app_version_changed () {
local return_value=${YNH_APP_UPGRADE_TYPE}
if [ "$return_value" == "UPGRADE_FULL" ] || [ "$return_value" == "UPGRADE_FORCED" ] || [ "$return_value" == "DOWNGRADE_FORCED" ]
then
return_value="UPGRADE_APP"
fi
echo $return_value
}
# Compare the current package version against another version given as an argument.
#
# usage: ynh_compare_current_package_version --comparison (lt|le|eq|ne|ge|gt) --version <X~ynhY>
# | arg: --comparison - Comparison type. Could be : `lt` (lower than), `le` (lower or equal), `eq` (equal), `ne` (not equal), `ge` (greater or equal), `gt` (greater than)
# | arg: --version - The version to compare. Need to be a version in the yunohost package version type (like `2.3.1~ynh4`)
# | ret: 0 if the evaluation is true, 1 if false.
#
# example: ynh_compare_current_package_version --comparison lt --version 2.3.2~ynh1
#
# This helper is usually used when we need to do some actions only for some old package versions.
#
# Generally you might probably use it as follow in the upgrade script :
# ```
# if ynh_compare_current_package_version --comparison lt --version 2.3.2~ynh1
# then
# # Do something that is needed for the package version older than 2.3.2~ynh1
# fi
# ```
#
# Requires YunoHost version 3.8.0 or higher.
ynh_compare_current_package_version() {
local legacy_args=cv
declare -Ar args_array=( [c]=comparison= [v]=version= )
local version
local comparison
# Manage arguments with getopts
ynh_handle_getopts_args "$@"
local current_version=$YNH_APP_CURRENT_VERSION
# Check the syntax of the versions
if [[ ! $version =~ '~ynh' ]] || [[ ! $current_version =~ '~ynh' ]]
then
ynh_die --message="Invalid argument for version."
fi
# Check validity of the comparator
if [[ ! $comparison =~ (lt|le|eq|ne|ge|gt) ]]; then
ynh_die --message="Invalid comparator must be : lt, le, eq, ne, ge, gt"
fi
# Return the return value of dpkg --compare-versions
dpkg --compare-versions $current_version $comparison $version
}
# Check if we should enforce sane default permissions (= disable rwx for 'others')
# on file/folders handled with ynh_setup_source and ynh_add_config
#
# [internal]
#
# Having a file others-readable or a folder others-executable(=enterable)
# is a security risk comparable to "chmod 777"
#
# Configuration files may contain secrets. Or even just being able to enter a
# folder may allow an attacker to do nasty stuff (maybe a file or subfolder has
# some write permission enabled for 'other' and the attacker may edit the
# content or create files as leverage for priviledge escalation ...)
#
# The sane default should be to set ownership to $app:$app.
# In specific case, you may want to set the ownership to $app:www-data
# for example if nginx needs access to static files.
#
_ynh_apply_default_permissions() {
local target=$1
local ynh_requirement=$(jq -r '.requirements.yunohost' $YNH_APP_BASEDIR/manifest.json | tr -d '>= ')
if [ -z "$ynh_requirement" ] || [ "$ynh_requirement" == "null" ] || dpkg --compare-versions $ynh_requirement ge 4.2
then
chmod o-rwx $target
chmod g-w $target
chown -R root:root $target
if ynh_system_user_exists $app
then
chown $app:$app $target
fi
fi
}

View file

@ -9,7 +9,7 @@ source /usr/share/yunohost/helpers
# Backup destination # Backup destination
backup_dir="${1}/data/multimedia" backup_dir="${1}/data/multimedia"
if [ ! -e "/home/yunohost.multimedia" ] || [ -e "/home/yunohost.multimedia/.nobackup" ]; then if [ -e "/home/yunohost.multimedia/.nobackup" ]; then
exit 0 exit 0
fi fi

View file

@ -12,7 +12,7 @@ backup_dir="${1}/conf/ynh"
# Backup the configuration # Backup the configuration
ynh_backup "/etc/yunohost/firewall.yml" "${backup_dir}/firewall.yml" ynh_backup "/etc/yunohost/firewall.yml" "${backup_dir}/firewall.yml"
ynh_backup "/etc/yunohost/current_host" "${backup_dir}/current_host" ynh_backup "/etc/yunohost/current_host" "${backup_dir}/current_host"
[ ! -d "/etc/yunohost/domains" ] || ynh_backup "/etc/yunohost/domains" "${backup_dir}/domains" ynh_backup "/etc/yunohost/domains" "${backup_dir}/domains"
[ ! -e "/etc/yunohost/settings.yml" ] || ynh_backup "/etc/yunohost/settings.yml" "${backup_dir}/settings.yml" [ ! -e "/etc/yunohost/settings.json" ] || ynh_backup "/etc/yunohost/settings.json" "${backup_dir}/settings.json"
[ ! -d "/etc/yunohost/dyndns" ] || ynh_backup "/etc/yunohost/dyndns" "${backup_dir}/dyndns" [ ! -d "/etc/yunohost/dyndns" ] || ynh_backup "/etc/yunohost/dyndns" "${backup_dir}/dyndns"
[ ! -d "/etc/dkim" ] || ynh_backup "/etc/dkim" "${backup_dir}/dkim" [ ! -d "/etc/dkim" ] || ynh_backup "/etc/dkim" "${backup_dir}/dkim"

View file

@ -10,8 +10,9 @@ yunohost tools shell -c "from yunohost.regenconf import manually_modified_files;
ynh_backup --src_path="./manually_modified_files_list" ynh_backup --src_path="./manually_modified_files_list"
for file in $(cat ./manually_modified_files_list); do for file in $(cat ./manually_modified_files_list)
do
[[ -e $file ]] && ynh_backup --src_path="$file" [[ -e $file ]] && ynh_backup --src_path="$file"
done done
ynh_backup --src_path="/etc/ssowat/conf.json.persistent" ynh_backup --src_path="/etc/ssowat/conf.json.persistent"

240
data/hooks/conf_regen/01-yunohost Executable file
View file

@ -0,0 +1,240 @@
#!/bin/bash
set -e
do_init_regen() {
if [[ $EUID -ne 0 ]]; then
echo "You must be root to run this script" 1>&2
exit 1
fi
cd /usr/share/yunohost/templates/yunohost
[[ -d /etc/yunohost ]] || mkdir -p /etc/yunohost
# set default current_host
[[ -f /etc/yunohost/current_host ]] \
|| echo "yunohost.org" > /etc/yunohost/current_host
# copy default services and firewall
[[ -f /etc/yunohost/firewall.yml ]] \
|| cp firewall.yml /etc/yunohost/firewall.yml
# allow users to access /media directory
[[ -d /etc/skel/media ]] \
|| (mkdir -p /media && ln -s /media /etc/skel/media)
# Cert folders
mkdir -p /etc/yunohost/certs
chown -R root:ssl-cert /etc/yunohost/certs
chmod 750 /etc/yunohost/certs
# App folders
mkdir -p /etc/yunohost/apps
chmod 700 /etc/yunohost/apps
mkdir -p /home/yunohost.app
chmod 755 /home/yunohost.app
# Domain settings
mkdir -p /etc/yunohost/domains
chmod 700 /etc/yunohost/domains
# Backup folders
mkdir -p /home/yunohost.backup/archives
chmod 750 /home/yunohost.backup/archives
chown root:root /home/yunohost.backup/archives # This is later changed to admin:root once admin user exists
# Empty ssowat json persistent conf
echo "{}" > '/etc/ssowat/conf.json.persistent'
chmod 644 /etc/ssowat/conf.json.persistent
chown root:root /etc/ssowat/conf.json.persistent
# Empty service conf
touch /etc/yunohost/services.yml
mkdir -p /var/cache/yunohost/repo
chown root:root /var/cache/yunohost
chmod 700 /var/cache/yunohost
cp yunoprompt.service /etc/systemd/system/yunoprompt.service
cp dpkg-origins /etc/dpkg/origins/yunohost
# Change dpkg vendor
# see https://wiki.debian.org/Derivatives/Guidelines#Vendor
readlink -f /etc/dpkg/origins/default | grep -q debian \
&& rm -f /etc/dpkg/origins/default \
&& ln -s /etc/dpkg/origins/yunohost /etc/dpkg/origins/default
}
do_pre_regen() {
pending_dir=$1
cd /usr/share/yunohost/templates/yunohost
# Legacy code that can be removed once on bullseye
touch /etc/yunohost/services.yml
yunohost tools shell -c "from yunohost.service import _get_services, _save_services; _save_services(_get_services())"
mkdir -p $pending_dir/etc/systemd/system
mkdir -p $pending_dir/etc/cron.d/
mkdir -p $pending_dir/etc/cron.daily/
# add cron job for diagnosis to be ran at 7h and 19h + a random delay between
# 0 and 20min, meant to avoid every instances running their diagnosis at
# exactly the same time, which may overload the diagnosis server.
cat > $pending_dir/etc/cron.d/yunohost-diagnosis << EOF
SHELL=/bin/bash
0 7,19 * * * root : YunoHost Automatic Diagnosis; sleep \$((RANDOM\\%1200)); yunohost diagnosis run --email > /dev/null 2>/dev/null || echo "Running the automatic diagnosis failed miserably"
EOF
# Cron job that upgrade the app list everyday
cat > $pending_dir/etc/cron.daily/yunohost-fetch-apps-catalog << EOF
#!/bin/bash
(sleep \$((RANDOM%3600)); yunohost tools update --apps > /dev/null) &
EOF
# Cron job that renew lets encrypt certificates if there's any that needs renewal
cat > $pending_dir/etc/cron.daily/yunohost-certificate-renew << EOF
#!/bin/bash
yunohost domain cert renew --email
EOF
# If we subscribed to a dyndns domain, add the corresponding cron
# - delay between 0 and 60 secs to spread the check over a 1 min window
# - do not run the command if some process already has the lock, to avoid queuing hundreds of commands...
if ls -l /etc/yunohost/dyndns/K*.private 2>/dev/null
then
cat > $pending_dir/etc/cron.d/yunohost-dyndns << EOF
SHELL=/bin/bash
*/10 * * * * root : YunoHost DynDNS update; sleep \$((RANDOM\\%60)); test -e /var/run/moulinette_yunohost.lock || yunohost dyndns update >> /dev/null
EOF
fi
# legacy stuff to avoid yunohost reporting etckeeper as manually modified
# (this make sure that the hash is null / file is flagged as to-delete)
mkdir -p $pending_dir/etc/etckeeper
touch $pending_dir/etc/etckeeper/etckeeper.conf
# Skip ntp if inside a container (inspired from the conf of systemd-timesyncd)
mkdir -p ${pending_dir}/etc/systemd/system/ntp.service.d/
echo "
[Unit]
ConditionCapability=CAP_SYS_TIME
ConditionVirtualization=!container
" > ${pending_dir}/etc/systemd/system/ntp.service.d/ynh-override.conf
# Make nftable conflict with yunohost-firewall
mkdir -p ${pending_dir}/etc/systemd/system/nftables.service.d/
cat > ${pending_dir}/etc/systemd/system/nftables.service.d/ynh-override.conf << EOF
[Unit]
# yunohost-firewall and nftables conflict with each other
Conflicts=yunohost-firewall.service
ConditionFileIsExecutable=!/etc/init.d/yunohost-firewall
ConditionPathExists=!/etc/systemd/system/multi-user.target.wants/yunohost-firewall.service
EOF
# Don't suspend computer on LidSwitch
mkdir -p ${pending_dir}/etc/systemd/logind.conf.d/
cat > ${pending_dir}/etc/systemd/logind.conf.d/ynh-override.conf << EOF
[Login]
HandleLidSwitch=ignore
HandleLidSwitchDocked=ignore
HandleLidSwitchExternalPower=ignore
EOF
cp yunoprompt.service ${pending_dir}/etc/systemd/system/yunoprompt.service
if [[ "$(yunohost settings get 'security.experimental.enabled')" == "True" ]]
then
cp proc-hidepid.service ${pending_dir}/etc/systemd/system/proc-hidepid.service
else
touch ${pending_dir}/etc/systemd/system/proc-hidepid.service
fi
mkdir -p ${pending_dir}/etc/dpkg/origins/
cp dpkg-origins ${pending_dir}/etc/dpkg/origins/yunohost
}
do_post_regen() {
regen_conf_files=$1
######################
# Enfore permissions #
######################
chmod 750 /home/admin
chmod 750 /home/yunohost.conf
chmod 750 /home/yunohost.backup
chmod 750 /home/yunohost.backup/archives
chown root:root /home/yunohost.conf
chown admin:root /home/yunohost.backup
chown admin:root /home/yunohost.backup/archives
# Certs
# We do this with find because there could be a lot of them...
chown -R root:ssl-cert /etc/yunohost/certs
chmod 750 /etc/yunohost/certs
find /etc/yunohost/certs/ -type f -exec chmod 640 {} \;
find /etc/yunohost/certs/ -type d -exec chmod 750 {} \;
find /etc/cron.*/yunohost-* -type f -exec chmod 755 {} \;
find /etc/cron.d/yunohost-* -type f -exec chmod 644 {} \;
find /etc/cron.*/yunohost-* -type f -exec chown root:root {} \;
chown root:root /var/cache/yunohost
chmod 700 /var/cache/yunohost
chown root:root /var/cache/moulinette
chmod 700 /var/cache/moulinette
setfacl -m g:all_users:--- /var/www
setfacl -m g:all_users:--- /var/log/nginx
setfacl -m g:all_users:--- /etc/yunohost
setfacl -m g:all_users:--- /etc/ssowat
for USER in $(yunohost user list --quiet --output-as json | jq -r '.users | .[] | .username')
do
[ ! -e "/home/$USER" ] || setfacl -m g:all_users:--- /home/$USER
done
# Domain settings
mkdir -p /etc/yunohost/domains
# Misc configuration / state files
chown root:root $(ls /etc/yunohost/{*.yml,*.yaml,*.json,mysql,psql} 2>/dev/null)
chmod 600 $(ls /etc/yunohost/{*.yml,*.yaml,*.json,mysql,psql} 2>/dev/null)
# Apps folder, custom hooks folder
[[ ! -e /etc/yunohost/hooks.d ]] || (chown root /etc/yunohost/hooks.d && chmod 700 /etc/yunohost/hooks.d)
[[ ! -e /etc/yunohost/apps ]] || (chown root /etc/yunohost/apps && chmod 700 /etc/yunohost/apps)
[[ ! -e /etc/yunohost/domains ]] || (chown root /etc/yunohost/domains && chmod 700 /etc/yunohost/domains)
# Create ssh.app and sftp.app groups if they don't exist yet
grep -q '^ssh.app:' /etc/group || groupadd ssh.app
grep -q '^sftp.app:' /etc/group || groupadd sftp.app
# Propagates changes in systemd service config overrides
[[ ! "$regen_conf_files" =~ "ntp.service.d/ynh-override.conf" ]] || { systemctl daemon-reload; systemctl restart ntp; }
[[ ! "$regen_conf_files" =~ "nftables.service.d/ynh-override.conf" ]] || systemctl daemon-reload
[[ ! "$regen_conf_files" =~ "login.conf.d/ynh-override.conf" ]] || systemctl daemon-reload
if [[ "$regen_conf_files" =~ "yunoprompt.service" ]]
then
systemctl daemon-reload
action=$([[ -e /etc/systemd/system/yunoprompt.service ]] && echo 'enable' || echo 'disable')
systemctl $action yunoprompt --quiet --now
fi
if [[ "$regen_conf_files" =~ "proc-hidepid.service" ]]
then
systemctl daemon-reload
action=$([[ -e /etc/systemd/system/proc-hidepid.service ]] && echo 'enable' || echo 'disable')
systemctl $action proc-hidepid --quiet --now
fi
# Change dpkg vendor
# see https://wiki.debian.org/Derivatives/Guidelines#Vendor
readlink -f /etc/dpkg/origins/default | grep -q debian \
&& rm -f /etc/dpkg/origins/default \
&& ln -s /etc/dpkg/origins/yunohost /etc/dpkg/origins/default
}
do_$1_regen ${@:2}

122
data/hooks/conf_regen/02-ssl Executable file
View file

@ -0,0 +1,122 @@
#!/bin/bash
set -e
ssl_dir="/usr/share/yunohost/yunohost-config/ssl/yunoCA"
ynh_ca="/etc/yunohost/certs/yunohost.org/ca.pem"
ynh_crt="/etc/yunohost/certs/yunohost.org/crt.pem"
ynh_key="/etc/yunohost/certs/yunohost.org/key.pem"
openssl_conf="/usr/share/yunohost/templates/ssl/openssl.cnf"
regen_local_ca() {
domain="$1"
echo -e "\n# Creating local certification authority with domain=$domain\n"
# create certs and SSL directories
mkdir -p "/etc/yunohost/certs/yunohost.org"
mkdir -p "${ssl_dir}/"{ca,certs,crl,newcerts}
pushd ${ssl_dir}
# (Update the serial so that it's specific to this very instance)
# N.B. : the weird RANDFILE thing comes from:
# https://stackoverflow.com/questions/94445/using-openssl-what-does-unable-to-write-random-state-mean
RANDFILE=.rnd openssl rand -hex 19 > serial
rm -f index.txt
touch index.txt
cp /usr/share/yunohost/templates/ssl/openssl.cnf openssl.ca.cnf
sed -i "s/yunohost.org/${domain}/g" openssl.ca.cnf
openssl req -x509 \
-new \
-config openssl.ca.cnf \
-days 3650 \
-out ca/cacert.pem \
-keyout ca/cakey.pem \
-nodes \
-batch \
-subj /CN=${domain}/O=${domain%.*} 2>&1
chmod 640 ca/cacert.pem
chmod 640 ca/cakey.pem
cp ca/cacert.pem $ynh_ca
ln -sf "$ynh_ca" /etc/ssl/certs/ca-yunohost_crt.pem
update-ca-certificates
popd
}
do_init_regen() {
LOGFILE=/tmp/yunohost-ssl-init
echo "" > $LOGFILE
chown root:root $LOGFILE
chmod 640 $LOGFILE
# Make sure this conf exists
mkdir -p ${ssl_dir}
cp /usr/share/yunohost/templates/ssl/openssl.cnf ${ssl_dir}/openssl.ca.cnf
# create default certificates
if [[ ! -f "$ynh_ca" ]]; then
regen_local_ca yunohost.org >>$LOGFILE
fi
if [[ ! -f "$ynh_crt" ]]; then
echo -e "\n# Creating initial key and certificate \n" >>$LOGFILE
openssl req -new \
-config "$openssl_conf" \
-days 730 \
-out "${ssl_dir}/certs/yunohost_csr.pem" \
-keyout "${ssl_dir}/certs/yunohost_key.pem" \
-nodes -batch &>>$LOGFILE
openssl ca \
-config "$openssl_conf" \
-days 730 \
-in "${ssl_dir}/certs/yunohost_csr.pem" \
-out "${ssl_dir}/certs/yunohost_crt.pem" \
-batch &>>$LOGFILE
chmod 640 "${ssl_dir}/certs/yunohost_key.pem"
chmod 640 "${ssl_dir}/certs/yunohost_crt.pem"
cp "${ssl_dir}/certs/yunohost_key.pem" "$ynh_key"
cp "${ssl_dir}/certs/yunohost_crt.pem" "$ynh_crt"
ln -sf "$ynh_crt" /etc/ssl/certs/yunohost_crt.pem
ln -sf "$ynh_key" /etc/ssl/private/yunohost_key.pem
fi
chown -R root:ssl-cert /etc/yunohost/certs/yunohost.org/
chmod o-rwx /etc/yunohost/certs/yunohost.org/
install -D -m 644 $openssl_conf "${ssl_dir}/openssl.cnf"
}
do_pre_regen() {
pending_dir=$1
cd /usr/share/yunohost/templates/ssl
install -D -m 644 openssl.cnf "${pending_dir}/${ssl_dir}/openssl.cnf"
}
do_post_regen() {
regen_conf_files=$1
current_local_ca_domain=$(openssl x509 -in $ynh_ca -text | tr ',' '\n' | grep Issuer | awk '{print $4}')
main_domain=$(cat /etc/yunohost/current_host)
if [[ "$current_local_ca_domain" != "$main_domain" ]]
then
regen_local_ca $main_domain
# Idk how useful this is, but this was in the previous python code (domain.main_domain())
ln -sf /etc/yunohost/certs/$domain/crt.pem /etc/ssl/certs/yunohost_crt.pem
ln -sf /etc/yunohost/certs/$domain/key.pem /etc/ssl/private/yunohost_key.pem
fi
}
do_$1_regen ${@:2}

51
data/hooks/conf_regen/03-ssh Executable file
View file

@ -0,0 +1,51 @@
#!/bin/bash
set -e
. /usr/share/yunohost/helpers
do_pre_regen() {
pending_dir=$1
# If the (legacy) 'from_script' flag is here,
# we won't touch anything in the ssh config.
[[ ! -f /etc/yunohost/from_script ]] || return 0
cd /usr/share/yunohost/templates/ssh
# do not listen to IPv6 if unavailable
[[ -f /proc/net/if_inet6 ]] && ipv6_enabled=true || ipv6_enabled=false
ssh_keys=$(ls /etc/ssh/ssh_host_{ed25519,rsa,ecdsa}_key 2>/dev/null || true)
# Support legacy setting (this setting might be disabled by a user during a migration)
if [[ "$(yunohost settings get 'service.ssh.allow_deprecated_dsa_hostkey')" == "True" ]]; then
ssh_keys="$ssh_keys $(ls /etc/ssh/ssh_host_dsa_key 2>/dev/null || true)"
fi
# Support different strategy for security configurations
export compatibility="$(yunohost settings get 'security.ssh.compatibility')"
export port="$(yunohost settings get 'security.ssh.port')"
export ssh_keys
export ipv6_enabled
ynh_render_template "sshd_config" "${pending_dir}/etc/ssh/sshd_config"
}
do_post_regen() {
regen_conf_files=$1
# If the (legacy) 'from_script' flag is here,
# we won't touch anything in the ssh config.
[[ ! -f /etc/yunohost/from_script ]] || return 0
# If no file changed, there's nothing to do
[[ -n "$regen_conf_files" ]] || return 0
# Enforce permissions for /etc/ssh/sshd_config
chown root:root "/etc/ssh/sshd_config"
chmod 644 "/etc/ssh/sshd_config"
systemctl restart ssh
}
do_$1_regen ${@:2}

202
data/hooks/conf_regen/06-slapd Executable file
View file

@ -0,0 +1,202 @@
#!/bin/bash
set -e
tmp_backup_dir_file="/root/slapd-backup-dir.txt"
config="/usr/share/yunohost/templates/slapd/config.ldif"
db_init="/usr/share/yunohost/templates/slapd/db_init.ldif"
do_init_regen() {
if [[ $EUID -ne 0 ]]; then
echo "You must be root to run this script" 1>&2
exit 1
fi
do_pre_regen ""
# Drop current existing slapd data
rm -rf /var/backups/*.ldapdb
rm -rf /var/backups/slapd-*
debconf-set-selections << EOF
slapd slapd/password1 password yunohost
slapd slapd/password2 password yunohost
slapd slapd/domain string yunohost.org
slapd shared/organization string yunohost.org
slapd slapd/allow_ldap_v2 boolean false
slapd slapd/invalid_config boolean true
slapd slapd/backend select MDB
slapd slapd/move_old_database boolean true
slapd slapd/no_configuration boolean false
slapd slapd/purge_database boolean false
EOF
DEBIAN_FRONTEND=noninteractive dpkg-reconfigure slapd -u
# Enforce permissions
chown -R openldap:openldap /etc/ldap/schema/
usermod -aG ssl-cert openldap
# (Re-)init data according to default ldap entries
echo ' Initializing LDAP with YunoHost DB structure'
rm -rf /etc/ldap/slapd.d
mkdir -p /etc/ldap/slapd.d
slapadd -F /etc/ldap/slapd.d -b cn=config -l "$config" 2>&1 \
| grep -v "none elapsed\|Closing DB" || true
chown -R openldap: /etc/ldap/slapd.d
rm -rf /var/lib/ldap
mkdir -p /var/lib/ldap
slapadd -F /etc/ldap/slapd.d -b dc=yunohost,dc=org -l "$db_init" 2>&1 \
| grep -v "none elapsed\|Closing DB" || true
chown -R openldap: /var/lib/ldap
nscd -i group || true
nscd -i passwd || true
systemctl restart slapd
# We don't use mkhomedir_helper because 'admin' may not be recognized
# when this script is ran in a chroot (e.g. ISO install)
# We also refer to admin as uid 1007 for the same reason
if [ ! -d /home/admin ]
then
cp -r /etc/skel /home/admin
chown -R 1007:1007 /home/admin
fi
}
_regenerate_slapd_conf() {
# Validate the new slapd config
# To do so, we have to use the .ldif to generate the config directory
# so we use a temporary directory slapd_new.d
rm -Rf /etc/ldap/slapd_new.d
mkdir /etc/ldap/slapd_new.d
slapadd -b cn=config -l "$config" -F /etc/ldap/slapd_new.d/ 2>&1 \
| grep -v "none elapsed\|Closing DB" || true
# Actual validation (-Q is for quiet, -u is for dry-run)
slaptest -Q -u -F /etc/ldap/slapd_new.d
# "Commit" / apply the new config (meaning we delete the old one and replace
# it with the new one)
rm -Rf /etc/ldap/slapd.d
mv /etc/ldap/slapd_new.d /etc/ldap/slapd.d
chown -R openldap:openldap /etc/ldap/slapd.d/
}
do_pre_regen() {
pending_dir=$1
# remove temporary backup file
rm -f "$tmp_backup_dir_file"
# Define if we need to migrate from hdb to mdb
curr_backend=$(grep '^database' /etc/ldap/slapd.conf 2>/dev/null | awk '{print $2}')
if [ -e /etc/ldap/slapd.conf ] && [ -n "$curr_backend" ] && \
[ $curr_backend != 'mdb' ]; then
backup_dir="/var/backups/dc=yunohost,dc=org-${curr_backend}-$(date +%s)"
mkdir -p "$backup_dir"
slapcat -b dc=yunohost,dc=org -l "${backup_dir}/dc=yunohost-dc=org.ldif"
echo "$backup_dir" > "$tmp_backup_dir_file"
fi
# create needed directories
ldap_dir="${pending_dir}/etc/ldap"
schema_dir="${ldap_dir}/schema"
mkdir -p "$ldap_dir" "$schema_dir"
# remove legacy configuration file
[ ! -f /etc/ldap/slapd-yuno.conf ] || touch "${ldap_dir}/slapd-yuno.conf"
[ ! -f /etc/ldap/slapd.conf ] || touch "${ldap_dir}/slapd.conf"
[ ! -f /etc/ldap/schema/yunohost.schema ] || touch "${schema_dir}/yunohost.schema"
cd /usr/share/yunohost/templates/slapd
# copy configuration files
cp -a ldap.conf "$ldap_dir"
cp -a sudo.ldif mailserver.ldif permission.ldif "$schema_dir"
mkdir -p ${pending_dir}/etc/systemd/system/slapd.service.d/
cp systemd-override.conf ${pending_dir}/etc/systemd/system/slapd.service.d/ynh-override.conf
install -D -m 644 slapd.default "${pending_dir}/etc/default/slapd"
}
do_post_regen() {
regen_conf_files=$1
# fix some permissions
echo "Enforce permissions on ldap/slapd directories and certs ..."
# penldap user should be in the ssl-cert group to let it access the certificate for TLS
usermod -aG ssl-cert openldap
chown -R openldap:openldap /etc/ldap/schema/
chown -R openldap:openldap /etc/ldap/slapd.d/
# If we changed the systemd ynh-override conf
if echo "$regen_conf_files" | sed 's/,/\n/g' | grep -q "^/etc/systemd/system/slapd.service.d/ynh-override.conf$"
then
systemctl daemon-reload
systemctl restart slapd
sleep 3
fi
# For some reason, old setups don't have the admins group defined...
if ! slapcat | grep -q 'cn=admins,ou=groups,dc=yunohost,dc=org'
then
slapadd -F /etc/ldap/slapd.d -b dc=yunohost,dc=org <<< \
"dn: cn=admins,ou=groups,dc=yunohost,dc=org
cn: admins
gidNumber: 4001
memberUid: admin
objectClass: posixGroup
objectClass: top"
chown -R openldap: /var/lib/ldap
systemctl restart slapd
nscd -i group
fi
[ -z "$regen_conf_files" ] && exit 0
# regenerate LDAP config directory from slapd.conf
echo "Regenerate LDAP config directory from config.ldif"
_regenerate_slapd_conf
# If there's a backup, re-import its data
backup_dir=$(cat "$tmp_backup_dir_file" 2>/dev/null || true)
if [[ -n "$backup_dir" && -f "${backup_dir}/dc=yunohost-dc=org.ldif" ]]; then
# regenerate LDAP config directory and import database as root
echo "Import the database using slapadd"
slapadd -F /etc/ldap/slapd.d -b dc=yunohost,dc=org -l "${backup_dir}/dc=yunohost-dc=org.ldif"
chown -R openldap:openldap /var/lib/ldap 2>&1
fi
echo "Running slapdindex"
su openldap -s "/bin/bash" -c "/usr/sbin/slapindex"
echo "Reloading slapd"
systemctl force-reload slapd
# on slow hardware/vm this regen conf would exit before the admin user that
# is stored in ldap is available because ldap seems to slow to restart
# so we'll wait either until we are able to log as admin or until a timeout
# is reached
# we need to do this because the next hooks executed after this one during
# postinstall requires to run as admin thus breaking postinstall on slow
# hardware which mean yunohost can't be correctly installed on those hardware
# and this sucks
# wait a maximum time of 5 minutes
# yes, force-reload behave like a restart
number_of_wait=0
while ! su admin -c '' && ((number_of_wait < 60))
do
sleep 5
((number_of_wait += 1))
done
}
do_$1_regen ${@:2}

25
data/hooks/conf_regen/09-nslcd Executable file
View file

@ -0,0 +1,25 @@
#!/bin/bash
set -e
do_init_regen() {
do_pre_regen ""
systemctl restart nslcd
}
do_pre_regen() {
pending_dir=$1
cd /usr/share/yunohost/templates/nslcd
install -D -m 644 nslcd.conf "${pending_dir}/etc/nslcd.conf"
}
do_post_regen() {
regen_conf_files=$1
[[ -z "$regen_conf_files" ]] \
|| systemctl restart nslcd
}
do_$1_regen ${@:2}

57
data/hooks/conf_regen/10-apt Executable file
View file

@ -0,0 +1,57 @@
#!/bin/bash
set -e
do_pre_regen() {
pending_dir=$1
mkdir --parents "${pending_dir}/etc/apt/preferences.d"
packages_to_refuse_from_sury="php php-fpm php-mysql php-xml php-zip php-mbstring php-ldap php-gd php-curl php-bz2 php-json php-sqlite3 php-intl openssl libssl1.1 libssl-dev"
for package in $packages_to_refuse_from_sury
do
echo "
Package: $package
Pin: origin \"packages.sury.org\"
Pin-Priority: -1" >> "${pending_dir}/etc/apt/preferences.d/extra_php_version"
done
echo "
# PLEASE READ THIS WARNING AND DON'T EDIT THIS FILE
# You are probably reading this file because you tried to install apache2 or
# bind9. These 2 packages conflict with YunoHost.
# Installing apache2 will break nginx and break the entire YunoHost ecosystem
# on your server, therefore don't remove those lines!
# You have been warned.
Package: apache2
Pin: release *
Pin-Priority: -1
Package: apache2-bin
Pin: release *
Pin-Priority: -1
# Also bind9 will conflict with dnsmasq.
# Same story as for apache2.
# Don't install it, don't remove those lines.
Package: bind9
Pin: release *
Pin-Priority: -1
" >> "${pending_dir}/etc/apt/preferences.d/ban_packages"
}
do_post_regen() {
regen_conf_files=$1
# Make sure php7.3 is the default version when using php in cli
update-alternatives --set php /usr/bin/php7.3
}
do_$1_regen ${@:2}

View file

@ -0,0 +1,73 @@
#!/bin/bash
set -e
do_pre_regen() {
pending_dir=$1
cd /usr/share/yunohost/templates/metronome
# create directories for pending conf
metronome_dir="${pending_dir}/etc/metronome"
metronome_conf_dir="${metronome_dir}/conf.d"
mkdir -p "$metronome_conf_dir"
# retrieve variables
main_domain=$(cat /etc/yunohost/current_host)
# install main conf file
cat metronome.cfg.lua \
| sed "s/{{ main_domain }}/${main_domain}/g" \
> "${metronome_dir}/metronome.cfg.lua"
# add domain conf files
for domain in $YNH_DOMAINS; do
cat domain.tpl.cfg.lua \
| sed "s/{{ domain }}/${domain}/g" \
> "${metronome_conf_dir}/${domain}.cfg.lua"
done
# remove old domain conf files
conf_files=$(ls -1 /etc/metronome/conf.d \
| awk '/^[^\.]+\.[^\.]+.*\.cfg\.lua$/ { print $1 }')
for file in $conf_files; do
domain=${file%.cfg.lua}
[[ $YNH_DOMAINS =~ $domain ]] \
|| touch "${metronome_conf_dir}/${file}"
done
}
do_post_regen() {
regen_conf_files=$1
# retrieve variables
main_domain=$(cat /etc/yunohost/current_host)
# FIXME : small optimization to do to avoid calling a yunohost command ...
# maybe another env variable like YNH_MAIN_DOMAINS idk
domain_list=$(yunohost domain list --exclude-subdomains --output-as plain --quiet)
# create metronome directories for domains
for domain in $domain_list; do
mkdir -p "/var/lib/metronome/${domain//./%2e}/pep"
# http_upload directory must be writable by metronome and readable by nginx
mkdir -p "/var/xmpp-upload/${domain}/upload"
# sgid bit allows that file created in that dir will be owned by www-data
# despite the fact that metronome ain't in the www-data group
chmod g+s "/var/xmpp-upload/${domain}/upload"
done
# fix some permissions
[ ! -e '/var/xmpp-upload' ] || chown -R metronome:www-data "/var/xmpp-upload/"
[ ! -e '/var/xmpp-upload' ] || chmod 750 "/var/xmpp-upload/"
# metronome should be in ssl-cert group to let it access SSL certificates
usermod -aG ssl-cert metronome
chown -R metronome: /var/lib/metronome/
chown -R metronome: /etc/metronome/conf.d/
[[ -z "$regen_conf_files" ]] \
|| systemctl restart metronome
}
do_$1_regen ${@:2}

152
data/hooks/conf_regen/15-nginx Executable file
View file

@ -0,0 +1,152 @@
#!/bin/bash
set -e
. /usr/share/yunohost/helpers
do_init_regen() {
if [[ $EUID -ne 0 ]]; then
echo "You must be root to run this script" 1>&2
exit 1
fi
cd /usr/share/yunohost/templates/nginx
nginx_dir="/etc/nginx"
nginx_conf_dir="${nginx_dir}/conf.d"
mkdir -p "$nginx_conf_dir"
# install plain conf files
cp plain/* "$nginx_conf_dir"
# probably run with init: just disable default site, restart NGINX and exit
rm -f "${nginx_dir}/sites-enabled/default"
export compatibility="intermediate"
ynh_render_template "security.conf.inc" "${nginx_conf_dir}/security.conf.inc"
ynh_render_template "yunohost_admin.conf" "${nginx_conf_dir}/yunohost_admin.conf"
ynh_render_template "yunohost_admin.conf.inc" "${nginx_conf_dir}/yunohost_admin.conf.inc"
ynh_render_template "yunohost_api.conf.inc" "${nginx_conf_dir}/yunohost_api.conf.inc"
mkdir -p $nginx_conf_dir/default.d/
cp "redirect_to_admin.conf" $nginx_conf_dir/default.d/
# Restart nginx if conf looks good, otherwise display error and exit unhappy
nginx -t 2>/dev/null || { nginx -t; exit 1; }
systemctl restart nginx || { journalctl --no-pager --lines=10 -u nginx >&2; exit 1; }
exit 0
}
do_pre_regen() {
pending_dir=$1
cd /usr/share/yunohost/templates/nginx
nginx_dir="${pending_dir}/etc/nginx"
nginx_conf_dir="${nginx_dir}/conf.d"
mkdir -p "$nginx_conf_dir"
# install / update plain conf files
cp plain/* "$nginx_conf_dir"
# remove the panel overlay if this is specified in settings
panel_overlay=$(yunohost settings get 'ssowat.panel_overlay.enabled')
if [ "$panel_overlay" == "false" ] || [ "$panel_overlay" == "False" ]
then
echo "#" > "${nginx_conf_dir}/yunohost_panel.conf.inc"
fi
# retrieve variables
main_domain=$(cat /etc/yunohost/current_host)
# Support different strategy for security configurations
export redirect_to_https="$(yunohost settings get 'security.nginx.redirect_to_https')"
export compatibility="$(yunohost settings get 'security.nginx.compatibility')"
export experimental="$(yunohost settings get 'security.experimental.enabled')"
ynh_render_template "security.conf.inc" "${nginx_conf_dir}/security.conf.inc"
cert_status=$(yunohost domain cert status --json)
# add domain conf files
for domain in $YNH_DOMAINS; do
domain_conf_dir="${nginx_conf_dir}/${domain}.d"
mkdir -p "$domain_conf_dir"
mail_autoconfig_dir="${pending_dir}/var/www/.well-known/${domain}/autoconfig/mail/"
mkdir -p "$mail_autoconfig_dir"
# NGINX server configuration
export domain
export domain_cert_ca=$(echo $cert_status \
| jq ".certificates.\"$domain\".CA_type" \
| tr -d '"')
ynh_render_template "server.tpl.conf" "${nginx_conf_dir}/${domain}.conf"
ynh_render_template "autoconfig.tpl.xml" "${mail_autoconfig_dir}/config-v1.1.xml"
touch "${domain_conf_dir}/yunohost_local.conf" # Clean legacy conf files
done
export webadmin_allowlist_enabled=$(yunohost settings get security.webadmin.allowlist.enabled)
if [ "$webadmin_allowlist_enabled" == "True" ]
then
export webadmin_allowlist=$(yunohost settings get security.webadmin.allowlist)
fi
ynh_render_template "yunohost_admin.conf.inc" "${nginx_conf_dir}/yunohost_admin.conf.inc"
ynh_render_template "yunohost_api.conf.inc" "${nginx_conf_dir}/yunohost_api.conf.inc"
ynh_render_template "yunohost_admin.conf" "${nginx_conf_dir}/yunohost_admin.conf"
mkdir -p $nginx_conf_dir/default.d/
cp "redirect_to_admin.conf" $nginx_conf_dir/default.d/
# remove old domain conf files
conf_files=$(ls -1 /etc/nginx/conf.d \
| awk '/^[^\.]+\.[^\.]+.*\.conf$/ { print $1 }')
for file in $conf_files; do
domain=${file%.conf}
[[ $YNH_DOMAINS =~ $domain ]] \
|| touch "${nginx_conf_dir}/${file}"
done
# remove old mail-autoconfig files
autoconfig_files=$(ls -1 /var/www/.well-known/*/autoconfig/mail/config-v1.1.xml 2>/dev/null || true)
for file in $autoconfig_files; do
domain=$(basename $(readlink -f $(dirname $file)/../..))
[[ $YNH_DOMAINS =~ $domain ]] \
|| (mkdir -p "$(dirname ${pending_dir}/${file})" && touch "${pending_dir}/${file}")
done
# disable default site
mkdir -p "${nginx_dir}/sites-enabled"
touch "${nginx_dir}/sites-enabled/default"
}
do_post_regen() {
regen_conf_files=$1
[ -z "$regen_conf_files" ] && exit 0
# create NGINX conf directories for domains
for domain in $YNH_DOMAINS; do
mkdir -p "/etc/nginx/conf.d/${domain}.d"
done
# Get rid of legacy lets encrypt snippets
for domain in $YNH_DOMAINS; do
# If the legacy letsencrypt / acme-challenge domain-specific snippet is still there
if [ -e /etc/nginx/conf.d/${domain}.d/000-acmechallenge.conf ]
then
# And if we're effectively including the new domain-independant snippet now
if grep -q "include /etc/nginx/conf.d/acme-challenge.conf.inc;" /etc/nginx/conf.d/${domain}.conf
then
# Delete the old domain-specific snippet
rm /etc/nginx/conf.d/${domain}.d/000-acmechallenge.conf
fi
fi
done
# Reload nginx if conf looks good, otherwise display error and exit unhappy
nginx -t 2>/dev/null || { nginx -t; exit 1; }
pgrep nginx && systemctl reload nginx || { journalctl --no-pager --lines=10 -u nginx >&2; exit 1; }
}
do_$1_regen ${@:2}

View file

@ -0,0 +1,83 @@
#!/bin/bash
set -e
. /usr/share/yunohost/helpers
do_pre_regen() {
pending_dir=$1
cd /usr/share/yunohost/templates/postfix
postfix_dir="${pending_dir}/etc/postfix"
mkdir -p "$postfix_dir"
default_dir="${pending_dir}/etc/default/"
mkdir -p "$default_dir"
# install plain conf files
cp plain/* "$postfix_dir"
# prepare main.cf conf file
main_domain=$(cat /etc/yunohost/current_host)
# Support different strategy for security configurations
export compatibility="$(yunohost settings get 'security.postfix.compatibility')"
# Add possibility to specify a relay
# Could be useful with some isp with no 25 port open or more complex setup
export relay_port=""
export relay_user=""
export relay_host="$(yunohost settings get 'smtp.relay.host')"
if [ -n "${relay_host}" ]
then
relay_port="$(yunohost settings get 'smtp.relay.port')"
relay_user="$(yunohost settings get 'smtp.relay.user')"
relay_password="$(yunohost settings get 'smtp.relay.password')"
# Avoid to display "Relay account paswword" to other users
touch ${postfix_dir}/sasl_passwd
chmod 750 ${postfix_dir}/sasl_passwd
# Avoid "postmap: warning: removing zero-length database file"
chown postfix ${pending_dir}/etc/postfix
chown postfix ${pending_dir}/etc/postfix/sasl_passwd
cat <<< "[${relay_host}]:${relay_port} ${relay_user}:${relay_password}" > ${postfix_dir}/sasl_passwd
postmap ${postfix_dir}/sasl_passwd
fi
export main_domain
export domain_list="$YNH_DOMAINS"
ynh_render_template "main.cf" "${postfix_dir}/main.cf"
cat postsrsd \
| sed "s/{{ main_domain }}/${main_domain}/g" \
| sed "s/{{ domain_list }}/${YNH_DOMAINS}/g" \
> "${default_dir}/postsrsd"
# adapt it for IPv4-only hosts
ipv6="$(yunohost settings get 'smtp.allow_ipv6')"
if [ "$ipv6" == "False" ] || [ ! -f /proc/net/if_inet6 ]; then
sed -i \
's/ \[::ffff:127.0.0.0\]\/104 \[::1\]\/128//g' \
"${postfix_dir}/main.cf"
sed -i \
's/inet_interfaces = all/&\ninet_protocols = ipv4/' \
"${postfix_dir}/main.cf"
fi
}
do_post_regen() {
regen_conf_files=$1
if [ -e /etc/postfix/sasl_passwd ]
then
chmod 750 /etc/postfix/sasl_passwd*
chown postfix:root /etc/postfix/sasl_passwd*
fi
[[ -z "$regen_conf_files" ]] \
|| { systemctl restart postfix && systemctl restart postsrsd; }
}
do_$1_regen ${@:2}

View file

@ -0,0 +1,66 @@
#!/bin/bash
set -e
. /usr/share/yunohost/helpers
do_pre_regen() {
pending_dir=$1
cd /usr/share/yunohost/templates/dovecot
dovecot_dir="${pending_dir}/etc/dovecot"
mkdir -p "${dovecot_dir}/global_script"
# copy simple conf files
cp dovecot-ldap.conf "${dovecot_dir}/dovecot-ldap.conf"
cp dovecot.sieve "${dovecot_dir}/global_script/dovecot.sieve"
export pop3_enabled="$(yunohost settings get 'pop3.enabled')"
export main_domain=$(cat /etc/yunohost/current_host)
ynh_render_template "dovecot.conf" "${dovecot_dir}/dovecot.conf"
# adapt it for IPv4-only hosts
if [ ! -f /proc/net/if_inet6 ]; then
sed -i \
's/^\(listen =\).*/\1 */' \
"${dovecot_dir}/dovecot.conf"
fi
mkdir -p "${dovecot_dir}/yunohost.d"
cp pre-ext.conf "${dovecot_dir}/yunohost.d"
cp post-ext.conf "${dovecot_dir}/yunohost.d"
}
do_post_regen() {
regen_conf_files=$1
mkdir -p "/etc/dovecot/yunohost.d/pre-ext.d"
mkdir -p "/etc/dovecot/yunohost.d/post-ext.d"
# create vmail user
id vmail > /dev/null 2>&1 \
|| adduser --system --ingroup mail --uid 500 vmail --home /var/vmail --no-create-home
# Delete legacy home for vmail that existed in the past but was empty, poluting /home/
[ ! -e /home/vmail ] || rmdir --ignore-fail-on-non-empty /home/vmail
# fix permissions
chown -R vmail:mail /etc/dovecot/global_script
chmod 770 /etc/dovecot/global_script
chown root:mail /var/mail
chmod 1775 /var/mail
[ -z "$regen_conf_files" ] && exit 0
# compile sieve script
[[ "$regen_conf_files" =~ dovecot\.sieve ]] && {
sievec /etc/dovecot/global_script/dovecot.sieve
chown -R vmail:mail /etc/dovecot/global_script
}
systemctl restart dovecot
}
do_$1_regen ${@:2}

62
data/hooks/conf_regen/31-rspamd Executable file
View file

@ -0,0 +1,62 @@
#!/bin/bash
set -e
do_pre_regen() {
pending_dir=$1
cd /usr/share/yunohost/templates/rspamd
install -D -m 644 metrics.local.conf \
"${pending_dir}/etc/rspamd/local.d/metrics.conf"
install -D -m 644 dkim_signing.conf \
"${pending_dir}/etc/rspamd/local.d/dkim_signing.conf"
install -D -m 644 rspamd.sieve \
"${pending_dir}/etc/dovecot/global_script/rspamd.sieve"
}
do_post_regen() {
##
## DKIM key generation
##
# create DKIM directory with proper permission
mkdir -p /etc/dkim
chown _rspamd /etc/dkim
# create DKIM key for domains
for domain in $YNH_DOMAINS; do
domain_key="/etc/dkim/${domain}.mail.key"
[ ! -f "$domain_key" ] && {
# We use a 1024 bit size because nsupdate doesn't seem to be able to
# handle 2048...
opendkim-genkey --domain="$domain" \
--selector=mail --directory=/etc/dkim -b 1024
mv /etc/dkim/mail.private "$domain_key"
mv /etc/dkim/mail.txt "/etc/dkim/${domain}.mail.txt"
}
done
# fix DKIM keys permissions
chown _rspamd /etc/dkim/*.mail.key
chmod 400 /etc/dkim/*.mail.key
[ ! -e /var/log/rspamd ] || chown -R _rspamd:_rspamd /var/log/rspamd
regen_conf_files=$1
[ -z "$regen_conf_files" ] && exit 0
# compile sieve script
[[ "$regen_conf_files" =~ rspamd\.sieve ]] && {
sievec /etc/dovecot/global_script/rspamd.sieve
chown -R vmail:mail /etc/dovecot/global_script
systemctl restart dovecot
}
# Restart rspamd due to the upgrade
# https://rspamd.com/announce/2016/08/01/rspamd-1.3.1.html
systemctl -q restart rspamd.service
}
do_$1_regen ${@:2}

72
data/hooks/conf_regen/34-mysql Executable file
View file

@ -0,0 +1,72 @@
#!/bin/bash
set -e
. /usr/share/yunohost/helpers
do_pre_regen() {
pending_dir=$1
cd /usr/share/yunohost/templates/mysql
install -D -m 644 my.cnf "${pending_dir}/etc/mysql/my.cnf"
}
do_post_regen() {
regen_conf_files=$1
if [[ ! -d /var/lib/mysql/mysql ]]
then
# dpkg-reconfigure will initialize mysql (if it ain't already)
# It enabled auth_socket for root, so no need to define any root password...
# c.f. : cat /var/lib/dpkg/info/mariadb-server-10.3.postinst | grep install_db -C3
MYSQL_PKG="$(dpkg --list | sed -ne 's/^ii \(mariadb-server-[[:digit:].]\+\) .*$/\1/p')"
dpkg-reconfigure -freadline -u "$MYSQL_PKG" 2>&1
systemctl -q is-active mariadb.service \
|| systemctl start mariadb
sleep 5
echo "" | mysql && echo "Can't connect to mysql using unix_socket auth ... something went wrong during initial configuration of mysql !?" >&2
fi
# Legacy code to get rid of /etc/yunohost/mysql ...
# Nowadays, we can simply run mysql while being run as root of unix_socket/auth_socket is enabled...
if [ -f /etc/yunohost/mysql ]; then
# This is a trick to check if we're able to use mysql without password
# Expect instances installed in stretch to already have unix_socket
#configured, but not old instances from the jessie/wheezy era
if ! echo "" | mysql 2>/dev/null
then
password="$(cat /etc/yunohost/mysql)"
# Enable plugin unix_socket for root on localhost
mysql -u root -p"$password" <<< "GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' IDENTIFIED WITH unix_socket WITH GRANT OPTION;"
fi
# If now we're able to login without password, drop the mysql password
if echo "" | mysql 2>/dev/null
then
rm /etc/yunohost/mysql
else
echo "Can't connect to mysql using unix_socket auth ... something went wrong while trying to get rid of mysql password !?" >&2
fi
fi
# mysql is supposed to be an alias to mariadb... but in some weird case is not
# c.f. https://forum.yunohost.org/t/mysql-ne-fonctionne-pas/11661
# Playing with enable/disable allows to recreate the proper symlinks.
if [ ! -e /etc/systemd/system/mysql.service ]
then
systemctl stop mysql -q
systemctl disable mysql -q
systemctl disable mariadb -q
systemctl enable mariadb -q
systemctl is-active mariadb -q || systemctl start mariadb
fi
[[ -z "$regen_conf_files" ]] \
|| systemctl restart mysql
}
do_$1_regen ${@:2}

13
data/hooks/conf_regen/35-redis Executable file
View file

@ -0,0 +1,13 @@
#!/bin/bash
do_pre_regen() {
:
}
do_post_regen() {
# Enforce these damn permissions because for some reason in some weird cases
# they are spontaneously replaced by root:root -_-
chown -R redis:adm /var/log/redis
}
do_$1_regen ${@:2}

57
data/hooks/conf_regen/37-mdns Executable file
View file

@ -0,0 +1,57 @@
#!/bin/bash
set -e
_generate_config() {
echo "domains:"
echo " - yunohost.local"
for domain in $YNH_DOMAINS
do
# Only keep .local domains (don't keep
[[ "$domain" =~ [^.]+\.[^.]+\.local$ ]] && echo "Subdomain $domain cannot be handled by Bonjour/Zeroconf/mDNS" >&2
[[ "$domain" =~ ^[^.]+\.local$ ]] || continue
echo " - $domain"
done
}
do_init_regen() {
do_pre_regen
do_post_regen /etc/systemd/system/yunomdns.service
systemctl enable yunomdns
}
do_pre_regen() {
pending_dir="$1"
cd /usr/share/yunohost/templates/mdns
mkdir -p ${pending_dir}/etc/systemd/system/
cp yunomdns.service ${pending_dir}/etc/systemd/system/
getent passwd mdns &>/dev/null || useradd --no-create-home --shell /usr/sbin/nologin --system --user-group mdns
mkdir -p ${pending_dir}/etc/yunohost
_generate_config > ${pending_dir}/etc/yunohost/mdns.yml
}
do_post_regen() {
regen_conf_files="$1"
chown mdns:mdns /etc/yunohost/mdns.yml
# If we changed the systemd ynh-override conf
if echo "$regen_conf_files" | sed 's/,/\n/g' | grep -q "^/etc/systemd/system/yunomdns.service$"
then
systemctl daemon-reload
fi
# Legacy stuff to enable the new yunomdns service on legacy systems
if [[ -e /etc/avahi/avahi-daemon.conf ]] && grep -q 'yunohost' /etc/avahi/avahi-daemon.conf
then
systemctl enable yunomdns
fi
[[ -z "$regen_conf_files" ]] \
|| systemctl restart yunomdns
}
do_$1_regen ${@:2}

View file

@ -0,0 +1,83 @@
#!/bin/bash
set -e
. /usr/share/yunohost/helpers
do_pre_regen() {
pending_dir=$1
cd /usr/share/yunohost/templates/dnsmasq
# create directory for pending conf
dnsmasq_dir="${pending_dir}/etc/dnsmasq.d"
mkdir -p "$dnsmasq_dir"
etcdefault_dir="${pending_dir}/etc/default"
mkdir -p "$etcdefault_dir"
# add general conf files
cp plain/etcdefault ${pending_dir}/etc/default/dnsmasq
cp plain/dnsmasq.conf ${pending_dir}/etc/dnsmasq.conf
# add resolver file
cat plain/resolv.dnsmasq.conf | grep "^nameserver" | shuf > ${pending_dir}/etc/resolv.dnsmasq.conf
# retrieve variables
ipv4=$(curl -s -4 https://ip.yunohost.org 2>/dev/null || true)
ynh_validate_ip4 "$ipv4" || ipv4='127.0.0.1'
ipv6=$(curl -s -6 https://ip6.yunohost.org 2>/dev/null || true)
ynh_validate_ip6 "$ipv6" || ipv6=''
export ipv4
export ipv6
# add domain conf files
for domain in $YNH_DOMAINS; do
export domain
ynh_render_template "domain.tpl" "${dnsmasq_dir}/${domain}"
done
# remove old domain conf files
conf_files=$(ls -1 /etc/dnsmasq.d \
| awk '/^[^\.]+\.[^\.]+.*$/ { print $1 }')
for domain in $conf_files; do
[[ $YNH_DOMAINS =~ $domain ]] \
|| touch "${dnsmasq_dir}/${domain}"
done
}
do_post_regen() {
regen_conf_files=$1
# Fuck it, those domain/search entries from dhclient are usually annoying
# lying shit from the ISP trying to MiTM
if grep -q -E "^ *(domain|search)" /run/resolvconf/resolv.conf
then
if grep -q -E "^ *(domain|search)" /run/resolvconf/interface/*.dhclient 2>/dev/null
then
sed -E "s/^(domain|search)/#\1/g" -i /run/resolvconf/interface/*.dhclient
fi
grep -q '^supersede domain-name "";' /etc/dhcp/dhclient.conf 2>/dev/null || echo 'supersede domain-name "";' >> /etc/dhcp/dhclient.conf
grep -q '^supersede domain-search "";' /etc/dhcp/dhclient.conf 2>/dev/null || echo 'supersede domain-search "";' >> /etc/dhcp/dhclient.conf
grep -q '^supersede name "";' /etc/dhcp/dhclient.conf 2>/dev/null || echo 'supersede name "";' >> /etc/dhcp/dhclient.conf
systemctl restart resolvconf
fi
# Some stupid things like rabbitmq-server used by onlyoffice won't work if
# the *short* hostname doesn't exists in /etc/hosts -_-
short_hostname=$(hostname -s)
grep -q "127.0.0.1.*$short_hostname" /etc/hosts || echo -e "\n127.0.0.1\t$short_hostname" >>/etc/hosts
[[ -n "$regen_conf_files" ]] || return
# Remove / disable services likely to conflict with dnsmasq
for SERVICE in systemd-resolved bind9
do
systemctl is-enabled $SERVICE &>/dev/null && systemctl disable $SERVICE 2>/dev/null
systemctl is-active $SERVICE &>/dev/null && systemctl stop $SERVICE
done
systemctl restart dnsmasq
}
do_$1_regen ${@:2}

View file

@ -0,0 +1,25 @@
#!/bin/bash
set -e
do_init_regen() {
do_pre_regen ""
systemctl restart unscd
}
do_pre_regen() {
pending_dir=$1
cd /usr/share/yunohost/templates/nsswitch
install -D -m 644 nsswitch.conf "${pending_dir}/etc/nsswitch.conf"
}
do_post_regen() {
regen_conf_files=$1
[[ -z "$regen_conf_files" ]] \
|| systemctl restart unscd
}
do_$1_regen ${@:2}

View file

@ -0,0 +1,30 @@
#!/bin/bash
set -e
. /usr/share/yunohost/helpers
do_pre_regen() {
pending_dir=$1
cd /usr/share/yunohost/templates/fail2ban
fail2ban_dir="${pending_dir}/etc/fail2ban"
mkdir -p "${fail2ban_dir}/filter.d"
mkdir -p "${fail2ban_dir}/jail.d"
cp yunohost.conf "${fail2ban_dir}/filter.d/yunohost.conf"
cp jail.conf "${fail2ban_dir}/jail.conf"
export ssh_port="$(yunohost settings get 'security.ssh.port')"
ynh_render_template "yunohost-jails.conf" "${fail2ban_dir}/jail.d/yunohost-jails.conf"
}
do_post_regen() {
regen_conf_files=$1
[[ -z "$regen_conf_files" ]] \
|| systemctl reload fail2ban
}
do_$1_regen ${@:2}

View file

@ -1,51 +1,32 @@
# #!/usr/bin/env python
# Copyright (c) 2024 YunoHost Contributors
#
# This file is part of YunoHost (see https://yunohost.org)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import os import os
import json import json
import subprocess import subprocess
from typing import List
from moulinette.utils import log
from moulinette.utils.process import check_output from moulinette.utils.process import check_output
from moulinette.utils.filesystem import read_file, read_json, write_to_json from moulinette.utils.filesystem import read_file, read_json, write_to_json
from yunohost.diagnosis import Diagnoser from yunohost.diagnosis import Diagnoser
from yunohost.utils.system import ( from yunohost.utils.packages import ynh_packages_version
ynh_packages_version,
system_virt,
system_arch,
)
logger = log.getActionLogger("yunohost.diagnosis")
class MyDiagnoser(Diagnoser): class BaseSystemDiagnoser(Diagnoser):
id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1]
cache_duration = 600 cache_duration = 600
dependencies: List[str] = [] dependencies = []
def run(self): def run(self):
virt = system_virt()
# Detect virt technology (if not bare metal) and arch
# Gotta have this "|| true" because it systemd-detect-virt return 'none'
# with an error code on bare metal ~.~
virt = check_output("systemd-detect-virt || true", shell=True)
if virt.lower() == "none": if virt.lower() == "none":
virt = "bare-metal" virt = "bare-metal"
# Detect arch # Detect arch
arch = system_arch() arch = check_output("dpkg --print-architecture")
hardware = dict( hardware = dict(
meta={"test": "hardware"}, meta={"test": "hardware"},
status="INFO", status="INFO",
@ -61,10 +42,10 @@ class MyDiagnoser(Diagnoser):
elif os.path.exists("/sys/devices/virtual/dmi/id/sys_vendor"): elif os.path.exists("/sys/devices/virtual/dmi/id/sys_vendor"):
model = read_file("/sys/devices/virtual/dmi/id/sys_vendor").strip() model = read_file("/sys/devices/virtual/dmi/id/sys_vendor").strip()
if os.path.exists("/sys/devices/virtual/dmi/id/product_name"): if os.path.exists("/sys/devices/virtual/dmi/id/product_name"):
product_name = read_file( model = "%s %s" % (
"/sys/devices/virtual/dmi/id/product_name" model,
).strip() read_file("/sys/devices/virtual/dmi/id/product_name").strip(),
model = f"{model} {product_name}" )
hardware["data"]["model"] = model hardware["data"]["model"] = model
hardware["details"] = ["diagnosis_basesystem_hardware_model"] hardware["details"] = ["diagnosis_basesystem_hardware_model"]
@ -118,11 +99,9 @@ class MyDiagnoser(Diagnoser):
"repo": ynh_packages["yunohost"]["repo"], "repo": ynh_packages["yunohost"]["repo"],
}, },
status="INFO" if consistent_versions else "ERROR", status="INFO" if consistent_versions else "ERROR",
summary=( summary="diagnosis_basesystem_ynh_main_version"
"diagnosis_basesystem_ynh_main_version" if consistent_versions
if consistent_versions else "diagnosis_basesystem_ynh_inconsistent_versions",
else "diagnosis_basesystem_ynh_inconsistent_versions"
),
details=ynh_version_details, details=ynh_version_details,
) )
@ -137,7 +116,7 @@ class MyDiagnoser(Diagnoser):
bad_sury_packages = list(self.bad_sury_packages()) bad_sury_packages = list(self.bad_sury_packages())
if bad_sury_packages: if bad_sury_packages:
cmd_to_fix = "apt install --allow-downgrades " + " ".join( cmd_to_fix = "apt install --allow-downgrades " + " ".join(
[f"{package}={version}" for package, version in bad_sury_packages] ["%s=%s" % (package, version) for package, version in bad_sury_packages]
) )
yield dict( yield dict(
meta={"test": "packages_from_sury"}, meta={"test": "packages_from_sury"},
@ -154,55 +133,15 @@ class MyDiagnoser(Diagnoser):
summary="diagnosis_backports_in_sources_list", summary="diagnosis_backports_in_sources_list",
) )
# Using yunohost testing channel if self.number_of_recent_auth_failure() > 500:
if (
os.system(
"grep -q '^\\s*deb\\s*.*yunohost.org.*\\stesting' /etc/apt/sources.list /etc/apt/sources.list.d/*"
)
== 0
):
yield dict(
meta={"test": "apt_yunohost_channel"},
status="WARNING",
summary="diagnosis_using_yunohost_testing",
details=["diagnosis_using_yunohost_testing_details"],
)
# Apt being mapped to 'stable' (instead of 'buster/bullseye/bookworm/trixie/...')
# will cause the machine to spontaenously upgrade everything as soon as next debian is released ...
# Note that we grep this from the policy for libc6, because it's hard to know exactly which apt repo
# is configured (it may not be simply debian.org)
if (
os.system(
"apt policy libc6 2>/dev/null | grep '^\\s*500' | awk '{print $3}' | tr '/' ' ' | awk '{print $1}' | grep -q 'stable'"
)
== 0
):
yield dict(
meta={"test": "apt_debian_codename"},
status="WARNING",
summary="diagnosis_using_stable_codename",
details=["diagnosis_using_stable_codename_details"],
)
if self.number_of_recent_auth_failure() > 750:
yield dict( yield dict(
meta={"test": "high_number_auth_failure"}, meta={"test": "high_number_auth_failure"},
status="WARNING", status="WARNING",
summary="diagnosis_high_number_auth_failures", summary="diagnosis_high_number_auth_failures",
) )
rfkill_wifi = self.rfkill_wifi()
if len(rfkill_wifi) > 0:
yield dict(
meta={"test": "rfkill_wifi"},
status="ERROR",
summary="diagnosis_rfkill_wifi",
details=["diagnosis_rfkill_wifi_details"],
data={"rfkill_wifi_error": rfkill_wifi},
)
def bad_sury_packages(self): def bad_sury_packages(self):
packages_to_check = ["openssl", "libssl1.1", "libssl-dev"] packages_to_check = ["openssl", "libssl1.1", "libssl-dev"]
for package in packages_to_check: for package in packages_to_check:
cmd = "dpkg --list | grep '^ii' | grep gbp | grep -q -w %s" % package cmd = "dpkg --list | grep '^ii' | grep gbp | grep -q -w %s" % package
@ -218,10 +157,12 @@ class MyDiagnoser(Diagnoser):
yield (package, version_to_downgrade_to) yield (package, version_to_downgrade_to)
def backports_in_sources_list(self): def backports_in_sources_list(self):
cmd = "grep -q -nr '^ *deb .*-backports' /etc/apt/sources.list*" cmd = "grep -q -nr '^ *deb .*-backports' /etc/apt/sources.list*"
return os.system(cmd) == 0 return os.system(cmd) == 0
def number_of_recent_auth_failure(self): def number_of_recent_auth_failure(self):
# Those syslog facilities correspond to auth and authpriv # Those syslog facilities correspond to auth and authpriv
# c.f. https://unix.stackexchange.com/a/401398 # c.f. https://unix.stackexchange.com/a/401398
# and https://wiki.archlinux.org/title/Systemd/Journal#Facility # and https://wiki.archlinux.org/title/Systemd/Journal#Facility
@ -231,7 +172,7 @@ class MyDiagnoser(Diagnoser):
try: try:
return int(n_failures) return int(n_failures)
except Exception: except Exception:
logger.warning( self.logger_warning(
"Failed to parse number of recent auth failures, expected an int, got '%s'" "Failed to parse number of recent auth failures, expected an int, got '%s'"
% n_failures % n_failures
) )
@ -255,20 +196,20 @@ class MyDiagnoser(Diagnoser):
if not os.path.exists(dpkg_log) or os.path.getmtime( if not os.path.exists(dpkg_log) or os.path.getmtime(
cache_file cache_file
) > os.path.getmtime(dpkg_log): ) > os.path.getmtime(dpkg_log):
logger.debug( self.logger_debug(
"Using cached results for meltdown checker, from %s" % cache_file "Using cached results for meltdown checker, from %s" % cache_file
) )
return read_json(cache_file)[0]["VULNERABLE"] return read_json(cache_file)[0]["VULNERABLE"]
# script taken from https://github.com/speed47/spectre-meltdown-checker # script taken from https://github.com/speed47/spectre-meltdown-checker
# script commit id is store directly in the script # script commit id is store directly in the script
SCRIPT_PATH = "/usr/lib/python3/dist-packages/yunohost/vendor/spectre-meltdown-checker/spectre-meltdown-checker.sh" SCRIPT_PATH = "/usr/lib/moulinette/yunohost/vendor/spectre-meltdown-checker/spectre-meltdown-checker.sh"
# '--variant 3' corresponds to Meltdown # '--variant 3' corresponds to Meltdown
# example output from the script: # example output from the script:
# [{"NAME":"MELTDOWN","CVE":"CVE-2017-5754","VULNERABLE":false,"INFOS":"PTI mitigates the vulnerability"}] # [{"NAME":"MELTDOWN","CVE":"CVE-2017-5754","VULNERABLE":false,"INFOS":"PTI mitigates the vulnerability"}]
try: try:
logger.debug("Running meltdown vulnerability checker") self.logger_debug("Running meltdown vulnerability checker")
call = subprocess.Popen( call = subprocess.Popen(
"bash %s --batch json --variant 3" % SCRIPT_PATH, "bash %s --batch json --variant 3" % SCRIPT_PATH,
shell=True, shell=True,
@ -290,7 +231,7 @@ class MyDiagnoser(Diagnoser):
# stuff which should be the last line # stuff which should be the last line
output = output.strip() output = output.strip()
if "\n" in output: if "\n" in output:
logger.debug("Original meltdown checker output : %s" % output) self.logger_debug("Original meltdown checker output : %s" % output)
output = output.split("\n")[-1] output = output.split("\n")[-1]
CVEs = json.loads(output) CVEs = json.loads(output)
@ -300,21 +241,18 @@ class MyDiagnoser(Diagnoser):
import traceback import traceback
traceback.print_exc() traceback.print_exc()
logger.warning( self.logger_warning(
"Something wrong happened when trying to diagnose Meltdown vunerability, exception: %s" "Something wrong happened when trying to diagnose Meltdown vunerability, exception: %s"
% e % e
) )
raise Exception("Command output for failed meltdown check: '%s'" % output) raise Exception("Command output for failed meltdown check: '%s'" % output)
logger.debug( self.logger_debug(
"Writing results from meltdown checker to cache file, %s" % cache_file "Writing results from meltdown checker to cache file, %s" % cache_file
) )
write_to_json(cache_file, CVEs) write_to_json(cache_file, CVEs)
return CVEs[0]["VULNERABLE"] return CVEs[0]["VULNERABLE"]
def rfkill_wifi(self):
if os.path.isfile("/etc/profile.d/wifi-check.sh"): def main(args, env, loggers):
cmd = "bash /etc/profile.d/wifi-check.sh" return BaseSystemDiagnoser(args, env, loggers).diagnose()
return check_output(cmd)
else:
return ""

View file

@ -1,44 +1,25 @@
# #!/usr/bin/env python
# Copyright (c) 2024 YunoHost Contributors
#
# This file is part of YunoHost (see https://yunohost.org)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import re import re
import os import os
import random import random
from typing import List
from moulinette.utils import log
from moulinette.utils.network import download_text from moulinette.utils.network import download_text
from moulinette.utils.process import check_output from moulinette.utils.process import check_output
from moulinette.utils.filesystem import read_file from moulinette.utils.filesystem import read_file
from yunohost.diagnosis import Diagnoser from yunohost.diagnosis import Diagnoser
from yunohost.utils.network import get_network_interfaces from yunohost.utils.network import get_network_interfaces
from yunohost.settings import settings_get
logger = log.getActionLogger("yunohost.diagnosis")
class MyDiagnoser(Diagnoser): class IPDiagnoser(Diagnoser):
id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1]
cache_duration = 600 cache_duration = 600
dependencies: List[str] = [] dependencies = []
def run(self): def run(self):
# ############################################################ # # ############################################################ #
# PING : Check that we can ping outside at least in ipv4 or v6 # # PING : Check that we can ping outside at least in ipv4 or v6 #
# ############################################################ # # ############################################################ #
@ -73,11 +54,9 @@ class MyDiagnoser(Diagnoser):
yield dict( yield dict(
meta={"test": "dnsresolv"}, meta={"test": "dnsresolv"},
status="ERROR", status="ERROR",
summary=( summary="diagnosis_ip_broken_dnsresolution"
"diagnosis_ip_broken_dnsresolution" if good_resolvconf
if good_resolvconf else "diagnosis_ip_broken_resolvconf",
else "diagnosis_ip_broken_resolvconf"
),
) )
return return
# Otherwise, if the resolv conf is bad but we were able to resolve domain name, # Otherwise, if the resolv conf is bad but we were able to resolve domain name,
@ -119,15 +98,10 @@ class MyDiagnoser(Diagnoser):
else: else:
return local_ip return local_ip
def is_ipvx_important(x):
return settings_get("misc.network.dns_exposure") in ["both", "ipv" + str(x)]
yield dict( yield dict(
meta={"test": "ipv4"}, meta={"test": "ipv4"},
data={"global": ipv4, "local": get_local_ip("ipv4")}, data={"global": ipv4, "local": get_local_ip("ipv4")},
status=( status="SUCCESS" if ipv4 else "ERROR",
"SUCCESS" if ipv4 else "ERROR" if is_ipvx_important(4) else "WARNING"
),
summary="diagnosis_ip_connected_ipv4" if ipv4 else "diagnosis_ip_no_ipv4", summary="diagnosis_ip_connected_ipv4" if ipv4 else "diagnosis_ip_no_ipv4",
details=["diagnosis_ip_global", "diagnosis_ip_local"] if ipv4 else None, details=["diagnosis_ip_global", "diagnosis_ip_local"] if ipv4 else None,
) )
@ -135,32 +109,17 @@ class MyDiagnoser(Diagnoser):
yield dict( yield dict(
meta={"test": "ipv6"}, meta={"test": "ipv6"},
data={"global": ipv6, "local": get_local_ip("ipv6")}, data={"global": ipv6, "local": get_local_ip("ipv6")},
status=( status="SUCCESS" if ipv6 else "WARNING",
"SUCCESS"
if ipv6
else (
"ERROR"
if settings_get("misc.network.dns_exposure") == "ipv6"
else "WARNING"
)
),
summary="diagnosis_ip_connected_ipv6" if ipv6 else "diagnosis_ip_no_ipv6", summary="diagnosis_ip_connected_ipv6" if ipv6 else "diagnosis_ip_no_ipv6",
details=( details=["diagnosis_ip_global", "diagnosis_ip_local"]
["diagnosis_ip_global", "diagnosis_ip_local"] if ipv6
if ipv6 else ["diagnosis_ip_no_ipv6_tip"],
else [
(
"diagnosis_ip_no_ipv6_tip_important"
if is_ipvx_important(6)
else "diagnosis_ip_no_ipv6_tip"
)
]
),
) )
# TODO / FIXME : add some attempt to detect ISP (using whois ?) ? # TODO / FIXME : add some attempt to detect ISP (using whois ?) ?
def can_ping_outside(self, protocol=4): def can_ping_outside(self, protocol=4):
assert protocol in [ assert protocol in [
4, 4,
6, 6,
@ -185,14 +144,16 @@ class MyDiagnoser(Diagnoser):
) )
if not any(is_default_route(r) for r in routes): if not any(is_default_route(r) for r in routes):
logger.debug( self.logger_debug(
"No default route for IPv%s, so assuming there's no IP address for that version" "No default route for IPv%s, so assuming there's no IP address for that version"
% protocol % protocol
) )
return None return None
# We use the resolver file as a list of well-known, trustable (ie not google ;)) IPs that we can ping # We use the resolver file as a list of well-known, trustable (ie not google ;)) IPs that we can ping
resolver_file = "/usr/share/yunohost/conf/dnsmasq/plain/resolv.dnsmasq.conf" resolver_file = (
"/usr/share/yunohost/templates/dnsmasq/plain/resolv.dnsmasq.conf"
)
resolvers = [ resolvers = [
r.split(" ")[1] r.split(" ")[1]
for r in read_file(resolver_file).split("\n") for r in read_file(resolver_file).split("\n")
@ -206,7 +167,10 @@ class MyDiagnoser(Diagnoser):
assert ( assert (
resolvers != [] resolvers != []
), f"Uhoh, need at least one IPv{protocol} DNS resolver in {resolver_file} ..." ), "Uhoh, need at least one IPv%s DNS resolver in %s ..." % (
protocol,
resolver_file,
)
# So let's try to ping the first 4~5 resolvers (shuffled) # So let's try to ping the first 4~5 resolvers (shuffled)
# If we succesfully ping any of them, we conclude that we are indeed connected # If we succesfully ping any of them, we conclude that we are indeed connected
@ -239,6 +203,7 @@ class MyDiagnoser(Diagnoser):
return len(content) == 1 and content[0].split() == ["nameserver", "127.0.0.1"] return len(content) == 1 and content[0].split() == ["nameserver", "127.0.0.1"]
def get_public_ip(self, protocol=4): def get_public_ip(self, protocol=4):
# FIXME - TODO : here we assume that DNS resolution for ip.yunohost.org is working # FIXME - TODO : here we assume that DNS resolution for ip.yunohost.org is working
# but if we want to be able to diagnose DNS resolution issues independently from # but if we want to be able to diagnose DNS resolution issues independently from
# internet connectivity, we gotta rely on fixed IPs first.... # internet connectivity, we gotta rely on fixed IPs first....
@ -255,7 +220,11 @@ class MyDiagnoser(Diagnoser):
try: try:
return download_text(url, timeout=30).strip() return download_text(url, timeout=30).strip()
except Exception as e: except Exception as e:
protocol = str(protocol) self.logger_debug(
e = str(e) "Could not get public IPv%s : %s" % (str(protocol), str(e))
logger.debug(f"Could not get public IPv{protocol} : {e}") )
return None return None
def main(args, env, loggers):
return IPDiagnoser(args, env, loggers).diagnose()

View file

@ -1,28 +1,11 @@
# #!/usr/bin/env python
# Copyright (c) 2024 YunoHost Contributors
#
# This file is part of YunoHost (see https://yunohost.org)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import os import os
import re import re
from typing import List
from datetime import datetime, timedelta
from publicsuffix2 import PublicSuffixList
from moulinette.utils import log from datetime import datetime, timedelta
from publicsuffix import PublicSuffixList
from moulinette.utils.process import check_output from moulinette.utils.process import check_output
from yunohost.utils.dns import ( from yunohost.utils.dns import (
@ -33,26 +16,22 @@ from yunohost.utils.dns import (
) )
from yunohost.diagnosis import Diagnoser from yunohost.diagnosis import Diagnoser
from yunohost.domain import domain_list, _get_maindomain from yunohost.domain import domain_list, _get_maindomain
from yunohost.dns import ( from yunohost.dns import _build_dns_conf, _get_dns_zone_for_domain
_build_dns_conf,
_get_dns_zone_for_domain,
_get_relative_name_for_dns_zone,
)
logger = log.getActionLogger("yunohost.diagnosis")
class MyDiagnoser(Diagnoser): class DNSRecordsDiagnoser(Diagnoser):
id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1]
cache_duration = 600 cache_duration = 600
dependencies: List[str] = ["ip"] dependencies = ["ip"]
def run(self): def run(self):
main_domain = _get_maindomain() main_domain = _get_maindomain()
major_domains = domain_list(exclude_subdomains=True)["domains"] major_domains = domain_list(exclude_subdomains=True)["domains"]
for domain in major_domains: for domain in major_domains:
logger.debug("Diagnosing DNS conf for %s" % domain) self.logger_debug("Diagnosing DNS conf for %s" % domain)
for report in self.check_domain( for report in self.check_domain(
domain, domain,
@ -75,17 +54,18 @@ class MyDiagnoser(Diagnoser):
yield report yield report
def check_domain(self, domain, is_main_domain): def check_domain(self, domain, is_main_domain):
if is_special_use_tld(domain): if is_special_use_tld(domain):
categories = []
yield dict( yield dict(
meta={"domain": domain}, meta={"domain": domain},
data={}, data={},
status="INFO", status="INFO",
summary="diagnosis_dns_specialusedomain", summary="diagnosis_dns_specialusedomain",
) )
return
base_dns_zone = _get_dns_zone_for_domain(domain) base_dns_zone = _get_dns_zone_for_domain(domain)
basename = _get_relative_name_for_dns_zone(domain, base_dns_zone) basename = domain.replace(base_dns_zone, "").rstrip(".") or "@"
expected_configuration = _build_dns_conf( expected_configuration = _build_dns_conf(
domain, include_empty_AAAA_if_no_ipv6=True domain, include_empty_AAAA_if_no_ipv6=True
@ -94,11 +74,13 @@ class MyDiagnoser(Diagnoser):
categories = ["basic", "mail", "xmpp", "extra"] categories = ["basic", "mail", "xmpp", "extra"]
for category in categories: for category in categories:
records = expected_configuration[category] records = expected_configuration[category]
discrepancies = [] discrepancies = []
results = {} results = {}
for r in records: for r in records:
id_ = r["type"] + ":" + r["name"] id_ = r["type"] + ":" + r["name"]
fqdn = r["name"] + "." + base_dns_zone if r["name"] != "@" else domain fqdn = r["name"] + "." + base_dns_zone if r["name"] != "@" else domain
@ -115,8 +97,6 @@ class MyDiagnoser(Diagnoser):
r["current"] = self.get_current_record(fqdn, r["type"]) r["current"] = self.get_current_record(fqdn, r["type"])
if r["value"] == "@": if r["value"] == "@":
r["value"] = domain + "." r["value"] = domain + "."
elif r["type"] == "CNAME":
r["value"] = r["value"] # + f".{base_dns_zone}."
if self.current_record_match_expected(r): if self.current_record_match_expected(r):
results[id_] = "OK" results[id_] = "OK"
@ -155,7 +135,7 @@ class MyDiagnoser(Diagnoser):
# If status is okay and there's actually no expected records # If status is okay and there's actually no expected records
# (e.g. XMPP disabled) # (e.g. XMPP disabled)
# then let's not yield any diagnosis line # then let's not yield any diagnosis line
if not records and status == "SUCCESS": if not records and "status" == "SUCCESS":
continue continue
output = dict( output = dict(
@ -177,15 +157,12 @@ class MyDiagnoser(Diagnoser):
yield output yield output
def get_current_record(self, fqdn, type_): def get_current_record(self, fqdn, type_):
success, answers = dig(fqdn, type_, resolvers="force_external") success, answers = dig(fqdn, type_, resolvers="force_external")
if success != "ok": if success != "ok":
return None return None
else: else:
if type_ == "TXT" and isinstance(answers, list):
for part in answers:
if part.startswith('"v=spf1'):
return part
return answers[0] if len(answers) == 1 else answers return answers[0] if len(answers) == 1 else answers
def current_record_match_expected(self, r): def current_record_match_expected(self, r):
@ -215,24 +192,12 @@ class MyDiagnoser(Diagnoser):
for part in current for part in current
if not part.startswith("ip4:") and not part.startswith("ip6:") if not part.startswith("ip4:") and not part.startswith("ip6:")
} }
if "v=DMARC1" in r["value"]:
for param in current:
if "=" not in param:
return False
key, value = param.split("=", 1)
if key == "p":
return value in ["none", "quarantine", "reject"]
return expected == current return expected == current
elif r["type"] == "MX": elif r["type"] == "MX":
# For MX, we want to ignore the priority # For MX, we want to ignore the priority
expected = r["value"].split()[-1] expected = r["value"].split()[-1]
current = r["current"].split()[-1] current = r["current"].split()[-1]
return expected == current return expected == current
elif r["type"] == "CAA":
# For CAA, check only the last item, ignore the 0 / 128 nightmare
expected = r["value"].split()[-1]
current = r["current"].split()[-1]
return expected == current
else: else:
return r["current"] == r["value"] return r["current"] == r["value"]
@ -257,7 +222,7 @@ class MyDiagnoser(Diagnoser):
) )
) )
else: else:
logger.debug("Dyndns domain: %s" % (domain)) self.logger_debug("Dyndns domain: %s" % (domain))
continue continue
expire_in = expire_date - datetime.now() expire_in = expire_date - datetime.now()
@ -293,9 +258,9 @@ class MyDiagnoser(Diagnoser):
yield dict( yield dict(
meta=meta, meta=meta,
data={}, data={},
status=( status=alert_type.upper()
alert_type.upper() if alert_type != "not_found" else "WARNING" if alert_type != "not_found"
), else "WARNING",
summary="diagnosis_domain_expiration_" + alert_type, summary="diagnosis_domain_expiration_" + alert_type,
details=details[alert_type], details=details[alert_type],
) )
@ -332,3 +297,7 @@ class MyDiagnoser(Diagnoser):
return datetime.strptime(match.group(1), "%d-%b-%Y") return datetime.strptime(match.group(1), "%d-%b-%Y")
return "expiration_not_found" return "expiration_not_found"
def main(args, env, loggers):
return DNSRecordsDiagnoser(args, env, loggers).diagnose()

View file

@ -1,35 +1,19 @@
# #!/usr/bin/env python
# Copyright (c) 2024 YunoHost Contributors
#
# This file is part of YunoHost (see https://yunohost.org)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import os import os
from typing import List
from yunohost.diagnosis import Diagnoser from yunohost.diagnosis import Diagnoser
from yunohost.service import _get_services from yunohost.service import _get_services
from yunohost.settings import settings_get
class MyDiagnoser(Diagnoser): class PortsDiagnoser(Diagnoser):
id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1]
cache_duration = 600 cache_duration = 600
dependencies: List[str] = ["ip"] dependencies = ["ip"]
def run(self): def run(self):
# TODO: report a warning if port 53 or 5353 is exposed to the outside world... # TODO: report a warning if port 53 or 5353 is exposed to the outside world...
# This dict is something like : # This dict is something like :
@ -45,10 +29,7 @@ class MyDiagnoser(Diagnoser):
ipversions = [] ipversions = []
ipv4 = Diagnoser.get_cached_report("ip", item={"test": "ipv4"}) or {} ipv4 = Diagnoser.get_cached_report("ip", item={"test": "ipv4"}) or {}
if ( if ipv4.get("status") == "SUCCESS":
ipv4.get("status") == "SUCCESS"
or settings_get("misc.network.dns_exposure") != "ipv6"
):
ipversions.append(4) ipversions.append(4)
# To be discussed: we could also make this check dependent on the # To be discussed: we could also make this check dependent on the
@ -122,10 +103,7 @@ class MyDiagnoser(Diagnoser):
for record in dnsrecords.get("items", []) for record in dnsrecords.get("items", [])
) )
if ( if failed == 4 or ipv6_is_important():
failed == 4
and settings_get("misc.network.dns_exposure") in ["both", "ipv4"]
) or (failed == 6 and ipv6_is_important()):
yield dict( yield dict(
meta={"port": port}, meta={"port": port},
data={ data={
@ -168,3 +146,7 @@ class MyDiagnoser(Diagnoser):
"diagnosis_ports_forwarding_tip", "diagnosis_ports_forwarding_tip",
], ],
) )
def main(args, env, loggers):
return PortsDiagnoser(args, env, loggers).diagnose()

View file

@ -1,45 +1,30 @@
# #!/usr/bin/env python
# Copyright (c) 2024 YunoHost Contributors
#
# This file is part of YunoHost (see https://yunohost.org)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import os import os
import random import random
import requests import requests
from typing import List
from moulinette.utils.filesystem import read_file, mkdir, rm from moulinette.utils.filesystem import read_file
from yunohost.diagnosis import Diagnoser from yunohost.diagnosis import Diagnoser
from yunohost.domain import domain_list from yunohost.domain import domain_list
from yunohost.utils.dns import is_special_use_tld from yunohost.utils.dns import is_special_use_tld
from yunohost.settings import settings_get
DIAGNOSIS_SERVER = "diagnosis.yunohost.org" DIAGNOSIS_SERVER = "diagnosis.yunohost.org"
class MyDiagnoser(Diagnoser): class WebDiagnoser(Diagnoser):
id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1]
cache_duration = 600 cache_duration = 600
dependencies: List[str] = ["ip"] dependencies = ["ip"]
def run(self): def run(self):
all_domains = domain_list()["domains"] all_domains = domain_list()["domains"]
domains_to_check = [] domains_to_check = []
for domain in all_domains: for domain in all_domains:
# If the diagnosis location ain't defined, can't do diagnosis, # If the diagnosis location ain't defined, can't do diagnosis,
# probably because nginx conf manually modified... # probably because nginx conf manually modified...
nginx_conf = "/etc/nginx/conf.d/%s.conf" % domain nginx_conf = "/etc/nginx/conf.d/%s.conf" % domain
@ -60,9 +45,9 @@ class MyDiagnoser(Diagnoser):
domains_to_check.append(domain) domains_to_check.append(domain)
self.nonce = "".join(random.choice("0123456789abcedf") for i in range(16)) self.nonce = "".join(random.choice("0123456789abcedf") for i in range(16))
rm("/var/www/.well-known/ynh-diagnosis/", recursive=True, force=True) os.system("rm -rf /tmp/.well-known/ynh-diagnosis/")
mkdir("/var/www/.well-known/ynh-diagnosis/", parents=True, mode=0o0775) os.system("mkdir -p /tmp/.well-known/ynh-diagnosis/")
os.system("touch /var/www/.well-known/ynh-diagnosis/%s" % self.nonce) os.system("touch /tmp/.well-known/ynh-diagnosis/%s" % self.nonce)
if not domains_to_check: if not domains_to_check:
return return
@ -74,9 +59,7 @@ class MyDiagnoser(Diagnoser):
ipversions = [] ipversions = []
ipv4 = Diagnoser.get_cached_report("ip", item={"test": "ipv4"}) or {} ipv4 = Diagnoser.get_cached_report("ip", item={"test": "ipv4"}) or {}
if ipv4.get("status") == "SUCCESS" and settings_get( if ipv4.get("status") == "SUCCESS":
"misc.network.dns_exposure"
) in ["both", "ipv4"]:
ipversions.append(4) ipversions.append(4)
# To be discussed: we could also make this check dependent on the # To be discussed: we could also make this check dependent on the
@ -96,10 +79,7 @@ class MyDiagnoser(Diagnoser):
# "curl --head the.global.ip" will simply timeout... # "curl --head the.global.ip" will simply timeout...
if self.do_hairpinning_test: if self.do_hairpinning_test:
global_ipv4 = ipv4.get("data", {}).get("global", None) global_ipv4 = ipv4.get("data", {}).get("global", None)
if global_ipv4 and settings_get("misc.network.dns_exposure") in [ if global_ipv4:
"both",
"ipv4",
]:
try: try:
requests.head("http://" + global_ipv4, timeout=5) requests.head("http://" + global_ipv4, timeout=5)
except requests.exceptions.Timeout: except requests.exceptions.Timeout:
@ -116,6 +96,7 @@ class MyDiagnoser(Diagnoser):
pass pass
def test_http(self, domains, ipversions): def test_http(self, domains, ipversions):
results = {} results = {}
for ipversion in ipversions: for ipversion in ipversions:
try: try:
@ -140,6 +121,7 @@ class MyDiagnoser(Diagnoser):
return return
for domain in domains: for domain in domains:
# i18n: diagnosis_http_bad_status_code # i18n: diagnosis_http_bad_status_code
# i18n: diagnosis_http_connection_error # i18n: diagnosis_http_connection_error
# i18n: diagnosis_http_timeout # i18n: diagnosis_http_timeout
@ -148,10 +130,7 @@ class MyDiagnoser(Diagnoser):
if all( if all(
results[ipversion][domain]["status"] == "ok" for ipversion in ipversions results[ipversion][domain]["status"] == "ok" for ipversion in ipversions
): ):
if 4 in ipversions and settings_get("misc.network.dns_exposure") in [ if 4 in ipversions:
"both",
"ipv4",
]:
self.do_hairpinning_test = True self.do_hairpinning_test = True
yield dict( yield dict(
meta={"domain": domain}, meta={"domain": domain},
@ -189,9 +168,7 @@ class MyDiagnoser(Diagnoser):
) )
AAAA_status = dnsrecords.get("data", {}).get("AAAA:@") AAAA_status = dnsrecords.get("data", {}).get("AAAA:@")
return AAAA_status in ["OK", "WRONG"] or settings_get( return AAAA_status in ["OK", "WRONG"]
"misc.network.dns_exposure"
) in ["both", "ipv6"]
if failed == 4 or ipv6_is_important_for_this_domain(): if failed == 4 or ipv6_is_important_for_this_domain():
yield dict( yield dict(
@ -221,3 +198,7 @@ class MyDiagnoser(Diagnoser):
summary="diagnosis_http_partially_unreachable", summary="diagnosis_http_partially_unreachable",
details=[detail.replace("error_http_check", "diagnosis_http")], details=[detail.replace("error_http_check", "diagnosis_http")],
) )
def main(args, env, loggers):
return WebDiagnoser(args, env, loggers).diagnose()

View file

@ -1,29 +1,11 @@
# #!/usr/bin/env python
# Copyright (c) 2024 YunoHost Contributors
#
# This file is part of YunoHost (see https://yunohost.org)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import os import os
import dns.resolver import dns.resolver
import re import re
from typing import List
from subprocess import CalledProcessError from subprocess import CalledProcessError
from moulinette.utils import log
from moulinette.utils.process import check_output from moulinette.utils.process import check_output
from moulinette.utils.filesystem import read_yaml from moulinette.utils.filesystem import read_yaml
@ -32,18 +14,18 @@ from yunohost.domain import _get_maindomain, domain_list
from yunohost.settings import settings_get from yunohost.settings import settings_get
from yunohost.utils.dns import dig from yunohost.utils.dns import dig
DEFAULT_DNS_BLACKLIST = "/usr/share/yunohost/dnsbl_list.yml" DEFAULT_DNS_BLACKLIST = "/usr/share/yunohost/other/dnsbl_list.yml"
logger = log.getActionLogger("yunohost.diagnosis")
class MyDiagnoser(Diagnoser): class MailDiagnoser(Diagnoser):
id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1]
cache_duration = 600 cache_duration = 600
dependencies: List[str] = ["ip"] dependencies = ["ip"]
def run(self): def run(self):
self.ehlo_domain = _get_maindomain().lower()
self.ehlo_domain = _get_maindomain()
self.mail_domains = domain_list()["domains"] self.mail_domains = domain_list()["domains"]
self.ipversions, self.ips = self.get_ips_checked() self.ipversions, self.ips = self.get_ips_checked()
@ -60,7 +42,7 @@ class MyDiagnoser(Diagnoser):
"check_queue", # i18n: diagnosis_mail_queue_ok "check_queue", # i18n: diagnosis_mail_queue_ok
] ]
for check in checks: for check in checks:
logger.debug("Running " + check) self.logger_debug("Running " + check)
reports = list(getattr(self, check)()) reports = list(getattr(self, check)())
for report in reports: for report in reports:
yield report yield report
@ -132,7 +114,7 @@ class MyDiagnoser(Diagnoser):
summary=summary, summary=summary,
details=[summary + "_details"], details=[summary + "_details"],
) )
elif r["helo"].lower() != self.ehlo_domain: elif r["helo"] != self.ehlo_domain:
yield dict( yield dict(
meta={"test": "mail_ehlo", "ipversion": ipversion}, meta={"test": "mail_ehlo", "ipversion": ipversion},
data={"wrong_ehlo": r["helo"], "right_ehlo": self.ehlo_domain}, data={"wrong_ehlo": r["helo"], "right_ehlo": self.ehlo_domain},
@ -185,7 +167,7 @@ class MyDiagnoser(Diagnoser):
rdns_domain = "" rdns_domain = ""
if len(value) > 0: if len(value) > 0:
rdns_domain = value[0][:-1] if value[0].endswith(".") else value[0] rdns_domain = value[0][:-1] if value[0].endswith(".") else value[0]
if rdns_domain.lower() != self.ehlo_domain: if rdns_domain != self.ehlo_domain:
details = [ details = [
"diagnosis_mail_fcrdns_different_from_ehlo_domain_details" "diagnosis_mail_fcrdns_different_from_ehlo_domain_details"
] + details ] + details
@ -194,7 +176,7 @@ class MyDiagnoser(Diagnoser):
data={ data={
"ip": ip, "ip": ip,
"ehlo_domain": self.ehlo_domain, "ehlo_domain": self.ehlo_domain,
"rdns_domain": rdns_domain.lower(), "rdns_domain": rdns_domain,
}, },
status="ERROR", status="ERROR",
summary="diagnosis_mail_fcrdns_different_from_ehlo_domain", summary="diagnosis_mail_fcrdns_different_from_ehlo_domain",
@ -227,11 +209,8 @@ class MyDiagnoser(Diagnoser):
query = subdomain + "." + blacklist["dns_server"] query = subdomain + "." + blacklist["dns_server"]
# Do the DNS Query # Do the DNS Query
status, answers = dig(query, "A") status, _ = dig(query, "A")
if status != "ok" or ( if status != "ok":
answers
and set(answers) <= set(blacklist["non_blacklisted_return_code"])
):
continue continue
# Try to get the reason # Try to get the reason
@ -277,7 +256,7 @@ class MyDiagnoser(Diagnoser):
data={"error": str(e)}, data={"error": str(e)},
status="ERROR", status="ERROR",
summary="diagnosis_mail_queue_unavailable", summary="diagnosis_mail_queue_unavailable",
details=["diagnosis_mail_queue_unavailable_details"], details="diagnosis_mail_queue_unavailable_details",
) )
else: else:
if pending_emails > 100: if pending_emails > 100:
@ -299,17 +278,13 @@ class MyDiagnoser(Diagnoser):
outgoing_ipversions = [] outgoing_ipversions = []
outgoing_ips = [] outgoing_ips = []
ipv4 = Diagnoser.get_cached_report("ip", {"test": "ipv4"}) or {} ipv4 = Diagnoser.get_cached_report("ip", {"test": "ipv4"}) or {}
if ipv4.get("status") == "SUCCESS" and settings_get( if ipv4.get("status") == "SUCCESS":
"misc.network.dns_exposure"
) in ["both", "ipv4"]:
outgoing_ipversions.append(4) outgoing_ipversions.append(4)
global_ipv4 = ipv4.get("data", {}).get("global", {}) global_ipv4 = ipv4.get("data", {}).get("global", {})
if global_ipv4: if global_ipv4:
outgoing_ips.append(global_ipv4) outgoing_ips.append(global_ipv4)
if settings_get("email.smtp.smtp_allow_ipv6") or settings_get( if settings_get("smtp.allow_ipv6"):
"misc.network.dns_exposure"
) in ["both", "ipv6"]:
ipv6 = Diagnoser.get_cached_report("ip", {"test": "ipv6"}) or {} ipv6 = Diagnoser.get_cached_report("ip", {"test": "ipv6"}) or {}
if ipv6.get("status") == "SUCCESS": if ipv6.get("status") == "SUCCESS":
outgoing_ipversions.append(6) outgoing_ipversions.append(6)
@ -317,3 +292,7 @@ class MyDiagnoser(Diagnoser):
if global_ipv6: if global_ipv6:
outgoing_ips.append(global_ipv6) outgoing_ips.append(global_ipv6)
return (outgoing_ipversions, outgoing_ips) return (outgoing_ipversions, outgoing_ips)
def main(args, env, loggers):
return MailDiagnoser(args, env, loggers).diagnose()

View file

@ -1,37 +1,23 @@
# #!/usr/bin/env python
# Copyright (c) 2024 YunoHost Contributors
#
# This file is part of YunoHost (see https://yunohost.org)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import os import os
from typing import List
from yunohost.diagnosis import Diagnoser from yunohost.diagnosis import Diagnoser
from yunohost.service import service_status from yunohost.service import service_status
class MyDiagnoser(Diagnoser): class ServicesDiagnoser(Diagnoser):
id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1]
cache_duration = 300 cache_duration = 300
dependencies: List[str] = [] dependencies = []
def run(self): def run(self):
all_result = service_status() all_result = service_status()
for service, result in sorted(all_result.items()): for service, result in sorted(all_result.items()):
item = dict( item = dict(
meta={"service": service}, meta={"service": service},
data={ data={
@ -55,3 +41,7 @@ class MyDiagnoser(Diagnoser):
item["summary"] = "diagnosis_services_running" item["summary"] = "diagnosis_services_running"
yield item yield item
def main(args, env, loggers):
return ServicesDiagnoser(args, env, loggers).diagnose()

View file

@ -1,39 +1,23 @@
# #!/usr/bin/env python
# Copyright (c) 2024 YunoHost Contributors
#
# This file is part of YunoHost (see https://yunohost.org)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import os import os
import psutil import psutil
import datetime import datetime
import re import re
from typing import List
from moulinette.utils.process import check_output from moulinette.utils.process import check_output
from yunohost.diagnosis import Diagnoser from yunohost.diagnosis import Diagnoser
class MyDiagnoser(Diagnoser): class SystemResourcesDiagnoser(Diagnoser):
id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1]
cache_duration = 300 cache_duration = 300
dependencies: List[str] = [] dependencies = []
def run(self): def run(self):
MB = 1024**2
MB = 1024 ** 2
GB = MB * 1024 GB = MB * 1024
# #
@ -148,7 +132,7 @@ class MyDiagnoser(Diagnoser):
d for d in disk_partitions if d.mountpoint in ["/", "/var"] d for d in disk_partitions if d.mountpoint in ["/", "/var"]
] ]
main_space = sum( main_space = sum(
psutil.disk_usage(d.mountpoint).total for d in main_disk_partitions [psutil.disk_usage(d.mountpoint).total for d in main_disk_partitions]
) )
if main_space < 10 * GB: if main_space < 10 * GB:
yield dict( yield dict(
@ -172,7 +156,7 @@ class MyDiagnoser(Diagnoser):
kills_count = self.recent_kills_by_oom_reaper() kills_count = self.recent_kills_by_oom_reaper()
if kills_count: if kills_count:
kills_summary = "\n".join( kills_summary = "\n".join(
[f"{proc} (x{count})" for proc, count in kills_count] ["%s (x%s)" % (proc, count) for proc, count in kills_count]
) )
yield dict( yield dict(
@ -187,6 +171,7 @@ class MyDiagnoser(Diagnoser):
return [] return []
def analyzed_kern_log(): def analyzed_kern_log():
cmd = 'tail -n 10000 /var/log/kern.log | grep "oom_reaper: reaped process" || true' cmd = 'tail -n 10000 /var/log/kern.log | grep "oom_reaper: reaped process" || true'
out = check_output(cmd) out = check_output(cmd)
lines = out.split("\n") if out else [] lines = out.split("\n") if out else []
@ -217,11 +202,9 @@ def human_size(bytes_):
# Adapted from https://stackoverflow.com/a/1094933 # Adapted from https://stackoverflow.com/a/1094933
for unit in ["", "ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"]: for unit in ["", "ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi"]:
if abs(bytes_) < 1024.0: if abs(bytes_) < 1024.0:
bytes_ = round_(bytes_) return "%s %sB" % (round_(bytes_), unit)
return f"{bytes_} {unit}B"
bytes_ /= 1024.0 bytes_ /= 1024.0
bytes_ = round_(bytes_) return "%s %sB" % (round_(bytes_), "Yi")
return f"{bytes_} YiB"
def round_(n): def round_(n):
@ -231,3 +214,7 @@ def round_(n):
if n > 10: if n > 10:
n = int(round(n)) n = int(round(n))
return n return n
def main(args, env, loggers):
return SystemResourcesDiagnoser(args, env, loggers).diagnose()

View file

@ -1,24 +1,7 @@
# #!/usr/bin/env python
# Copyright (c) 2024 YunoHost Contributors
#
# This file is part of YunoHost (see https://yunohost.org)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import os import os
import re import re
from typing import List
from yunohost.settings import settings_get from yunohost.settings import settings_get
from yunohost.diagnosis import Diagnoser from yunohost.diagnosis import Diagnoser
@ -26,12 +9,14 @@ from yunohost.regenconf import _get_regenconf_infos, _calculate_hash
from moulinette.utils.filesystem import read_file from moulinette.utils.filesystem import read_file
class MyDiagnoser(Diagnoser): class RegenconfDiagnoser(Diagnoser):
id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1]
cache_duration = 300 cache_duration = 300
dependencies: List[str] = [] dependencies = []
def run(self): def run(self):
regenconf_modified_files = list(self.manually_modified_files()) regenconf_modified_files = list(self.manually_modified_files())
if not regenconf_modified_files: if not regenconf_modified_files:
@ -67,7 +52,7 @@ class MyDiagnoser(Diagnoser):
) )
# Check consistency between actual ssh port in sshd_config vs. setting # Check consistency between actual ssh port in sshd_config vs. setting
ssh_port_setting = settings_get("security.ssh.ssh_port") ssh_port_setting = settings_get("security.ssh.port")
ssh_port_line = re.findall( ssh_port_line = re.findall(
r"\bPort *([0-9]{2,5})\b", read_file("/etc/ssh/sshd_config") r"\bPort *([0-9]{2,5})\b", read_file("/etc/ssh/sshd_config")
) )
@ -80,7 +65,12 @@ class MyDiagnoser(Diagnoser):
) )
def manually_modified_files(self): def manually_modified_files(self):
for category, infos in _get_regenconf_infos().items(): for category, infos in _get_regenconf_infos().items():
for path, hash_ in infos["conffiles"].items(): for path, hash_ in infos["conffiles"].items():
if hash_ != _calculate_hash(path): if hash_ != _calculate_hash(path):
yield {"path": path, "category": category} yield {"path": path, "category": category}
def main(args, env, loggers):
return RegenconfDiagnoser(args, env, loggers).diagnose()

View file

@ -1,35 +1,20 @@
# #!/usr/bin/env python
# Copyright (c) 2024 YunoHost Contributors
#
# This file is part of YunoHost (see https://yunohost.org)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import os import os
from typing import List
from yunohost.app import app_list from yunohost.app import app_list
from yunohost.diagnosis import Diagnoser from yunohost.diagnosis import Diagnoser
class MyDiagnoser(Diagnoser): class AppDiagnoser(Diagnoser):
id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1] id_ = os.path.splitext(os.path.basename(__file__))[0].split("-")[1]
cache_duration = 300 cache_duration = 300
dependencies: List[str] = [] dependencies = []
def run(self): def run(self):
apps = app_list(full=True)["apps"] apps = app_list(full=True)["apps"]
for app in apps: for app in apps:
app["issues"] = list(self.issues(app)) app["issues"] = list(self.issues(app))
@ -42,6 +27,7 @@ class MyDiagnoser(Diagnoser):
) )
else: else:
for app in apps: for app in apps:
if not app["issues"]: if not app["issues"]:
continue continue
@ -59,15 +45,16 @@ class MyDiagnoser(Diagnoser):
) )
def issues(self, app): def issues(self, app):
# Check quality level in catalog # Check quality level in catalog
if not app.get("from_catalog") or app["from_catalog"].get("state") != "working": if not app.get("from_catalog") or app["from_catalog"].get("state") != "working":
yield ("warning", "diagnosis_apps_not_in_app_catalog") yield ("error", "diagnosis_apps_not_in_app_catalog")
elif ( elif (
not isinstance(app["from_catalog"].get("level"), int) not isinstance(app["from_catalog"].get("level"), int)
or app["from_catalog"]["level"] == 0 or app["from_catalog"]["level"] == 0
): ):
yield ("warning", "diagnosis_apps_broken") yield ("error", "diagnosis_apps_broken")
elif app["from_catalog"]["level"] <= 4: elif app["from_catalog"]["level"] <= 4:
yield ("warning", "diagnosis_apps_bad_quality") yield ("warning", "diagnosis_apps_bad_quality")
@ -76,9 +63,7 @@ class MyDiagnoser(Diagnoser):
yunohost_version_req = ( yunohost_version_req = (
app["manifest"].get("requirements", {}).get("yunohost", "").strip(">= ") app["manifest"].get("requirements", {}).get("yunohost", "").strip(">= ")
) )
if yunohost_version_req.startswith("2.") or yunohost_version_req.startswith( if yunohost_version_req.startswith("2."):
"3."
):
yield ("error", "diagnosis_apps_outdated_ynh_requirement") yield ("error", "diagnosis_apps_outdated_ynh_requirement")
deprecated_helpers = [ deprecated_helpers = [
@ -105,3 +90,7 @@ class MyDiagnoser(Diagnoser):
== 0 == 0
): ):
yield ("error", "diagnosis_apps_deprecated_practices") yield ("error", "diagnosis_apps_deprecated_practices")
def main(args, env, loggers):
return AppDiagnoser(args, env, loggers).diagnose()

View file

@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
user=$1 user=$1
readonly MEDIA_GROUP=multimedia readonly MEDIA_GROUP=multimedia
readonly MEDIA_DIRECTORY=/home/yunohost.multimedia readonly MEDIA_DIRECTORY=/home/yunohost.multimedia

View file

@ -14,11 +14,11 @@ die() {
# Restore saved configuration and database # Restore saved configuration and database
[[ $state -ge 1 ]] \ [[ $state -ge 1 ]] \
&& (rm -rf /etc/ldap/slapd.d \ && (rm -rf /etc/ldap/slapd.d &&
&& mv "${TMPDIR}/slapd.d" /etc/ldap/slapd.d) mv "${TMPDIR}/slapd.d" /etc/ldap/slapd.d)
[[ $state -ge 2 ]] \ [[ $state -ge 2 ]] \
&& (rm -rf /var/lib/ldap \ && (rm -rf /var/lib/ldap &&
&& mv "${TMPDIR}/ldap" /var/lib/ldap) mv "${TMPDIR}/ldap" /var/lib/ldap)
chown -R openldap: /etc/ldap/slapd.d /var/lib/ldap chown -R openldap: /etc/ldap/slapd.d /var/lib/ldap
systemctl start slapd systemctl start slapd
@ -38,7 +38,7 @@ cp -a "${backup_dir}/ldap.conf" /etc/ldap/ldap.conf
|| cp -a "${backup_dir}/slapd.conf" /etc/ldap/slapd.conf || cp -a "${backup_dir}/slapd.conf" /etc/ldap/slapd.conf
slapadd -F /etc/ldap/slapd.d -b cn=config \ slapadd -F /etc/ldap/slapd.d -b cn=config \
-l "${backup_dir}/cn=config.master.ldif" \ -l "${backup_dir}/cn=config.master.ldif" \
|| die 1 "Unable to restore LDAP configuration" || die 1 "Unable to restore LDAP configuration"
chown -R openldap: /etc/ldap/slapd.d chown -R openldap: /etc/ldap/slapd.d
# Restore the database # Restore the database
@ -46,7 +46,7 @@ mv /var/lib/ldap "$TMPDIR"
mkdir -p /var/lib/ldap mkdir -p /var/lib/ldap
slapadd -F /etc/ldap/slapd.d -b dc=yunohost,dc=org \ slapadd -F /etc/ldap/slapd.d -b dc=yunohost,dc=org \
-l "${backup_dir}/dc=yunohost-dc=org.ldif" \ -l "${backup_dir}/dc=yunohost-dc=org.ldif" \
|| die 2 "Unable to restore LDAP database" || die 2 "Unable to restore LDAP database"
chown -R openldap: /var/lib/ldap chown -R openldap: /var/lib/ldap
systemctl start slapd systemctl start slapd

View file

@ -0,0 +1,9 @@
#!/bin/bash
# Exit hook on subcommand error or unset variable
set -eu
# Source YNH helpers
source /usr/share/yunohost/helpers
ynh_restore_file --origin_path="/home/yunohost.multimedia" --not_mandatory

View file

@ -2,7 +2,7 @@ backup_dir="$1/conf/ynh"
cp -a "${backup_dir}/current_host" /etc/yunohost/current_host cp -a "${backup_dir}/current_host" /etc/yunohost/current_host
cp -a "${backup_dir}/firewall.yml" /etc/yunohost/firewall.yml cp -a "${backup_dir}/firewall.yml" /etc/yunohost/firewall.yml
[ ! -d "${backup_dir}/domains" ] || cp -a "${backup_dir}/domains" /etc/yunohost/domains cp -a "${backup_dir}/domains" /etc/yunohost/domains
[ ! -e "${backup_dir}/settings.yml" ] || cp -a "${backup_dir}/settings.yml" "/etc/yunohost/settings.yml" [ ! -e "${backup_dir}/settings.json" ] || cp -a "${backup_dir}/settings.json" "/etc/yunohost/settings.json"
[ ! -d "${backup_dir}/dyndns" ] || cp -raT "${backup_dir}/dyndns" "/etc/yunohost/dyndns" [ ! -d "${backup_dir}/dyndns" ] || cp -raT "${backup_dir}/dyndns" "/etc/yunohost/dyndns"
[ ! -d "${backup_dir}/dkim" ] || cp -raT "${backup_dir}/dkim" "/etc/dkim" [ ! -d "${backup_dir}/dkim" ] || cp -raT "${backup_dir}/dkim" "/etc/dkim"

Some files were not shown because too many files have changed in this diff Show more