Guide configuration Yunohost

Bonjour à tous!

Après pas mal de tatonnements, et beaucoup de demandes d’aide, j’ai fini par obtenir le petit YunoHost de mes rêves.
Je me suis donc dit que ça pouvait être sympa, notamment pour les moins expérimentés, de présenter dans un topic un petit récapitulatif.
J’ai fait ça sous forme d’une archive comprenant des scripts que j’ai transférés de mon serveur de test à un serveur de production fraîchement installé (un raspberry pi 2, qui boote sur un dd).
Le principe est un sous-domaine par app, je trouve ça plus propre (le domaine principal étant consacré à custom_webapp).
Pour les backup, elles se lancent quotidiennement pour /boot, et au branchement d’un dd pour le reste. Un mail m’est envoyé pour me notifier de la fin du backup.

J’extrais l’archive dans /root/, ce qui donne:

  • /root/
  • /installServeur/
    • /backup/
      • 90-myrules.rules
      • GuideBackup.sh
      • backupBoot
      • backupGeneral.sh
      • backupRelay.sh
    • /letsencrypt/
      • certificateRenewer
      • conf.ini
      • conf.json.persistent
      • letsencrypt.conf
    • guide.sh

La pièce principale

Le contenu du guide.sh :

[details=Résumé]> #!/bin/sh
##########################################################################
### Liste de commandes à effectuer lors de la configuration du serveur ###
##########################################################################

# La connexion en ssh: admin:yunohost
# Lancer ce script en tant que root
##########################################################################
# Configurer le fuseau horaire

dpkg-reconfigure tzdata
##########################################################################
# Rechercher et installer les mises à jour, installer paquets perso

apt-get update && apt-get upgrade
apt-get install emacs24-nox duplicity at mailutils
##########################################################################
# Lancer la postinstall

yunohost tools postinstall
##########################################################################
# Créer les sous domaines

yunohost domain add torrent.domaine.tld transmission
yunohost domain add search.domaine.tld searx
yunohost domain add cloud.domaine.tld nextcloud
yunohost domain add sondage.domaine.tld opensondage
yunohost domain add upload.domaine.tld jirafeau
yunohost domain add pma.domaine.tld phpmyadmin
yunohost domain add blog.domaine.tld wordpress
yunohost domain add jappix.domaine.tld jappix
yunohost domain add pads.domaine.tld #etherpads
yunohost domain add webmail.domaine.tld rainloop
yunohost domain add wiki.domaine.tld dokuwiki
##########################################################################
# Créer l'utilisateur

yunohost user create nomUser -f prenom -m mailUser@domaine.tld -l nom -p motdepasse
##########################################################################
# Installer Let's Encrypt

cd
git clone GitHub - certbot/certbot: Certbot is EFF's tool to obtain certs from Let's Encrypt and (optionally) auto-enable HTTPS on your server. It can also act as a client for any other CA that uses the ACME protocol.
cd letsencrypt/
./letsencrypt-auto --help

cd /etc/nginx/conf.d/
echo domaine.tld.d/ torrent.domaine.tld.d/ search.domaine.tld.d/ cloud.domaine.tld.d/ sondage.domaine.tld.d/ upload.domaine.tld.d/ pma.domaine.tld.d/ blog.domaine.tld.d/ jappix.domaine.tld.d/ pads.domaine.tld.d/ webmail.domaine.tld.d/ wiki.domaine.tld.d/ | xargs -n 1 cp /root/installServeur/letsencrypt/letsencrypt.conf

cp /etc/ssowat/conf.json.persistent /etc/ssowat/conf.json.persistent.back
cp /root/installServeur/letsencrypt/conf.json.persistent /etc/ssowat/conf.json.persistent

nginx -t
service nginx restart

mkdir /etc/letsencrypt
cp /root/installServeur/letsencrypt/conf.ini /etc/letsencrypt/

export DOMAINS="-d domaine.tld -d torrent.domaine.tld -d search.domaine.tld -d cloud.domaine.tld -d sondage.domaine.tld -d upload.domaine.tld -d pma.domaine.tld -d blog.domaine.tld -d jappix.domaine.tld -d pads.domaine.tld -d webmail.domaine.tld -d wiki.domaine.tld "

cd ~/letsencrypt/
mkdir -p /tmp/letsencrypt-auto
./letsencrypt-auto certonly --config /etc/letsencrypt/conf.ini $DOMAINS

mkdir /root/backupCerts/
mv /etc/yunohost/certs/* /root/backupCerts/
cd /etc/yunohost/certs/
mkdir domaine.tld torrent.domaine.tld search.domaine.tld cloud.domaine.tld sondage.domaine.tld upload.domaine.tld pma.domaine.tld blog.domaine.tld jappix.domaine.tld pads.domaine.tld webmail.domaine.tld wiki.domaine.tld

ln -s /etc/letsencrypt/live/domaine.tld/fullchain.pem domaine.tld/crt.pem
ln -s /etc/letsencrypt/live/domaine.tld/privkey.pem domaine.tld/key.pem
ln -s /etc/letsencrypt/live/domaine.tld/fullchain.pem torrent.domaine.tld/crt.pem
ln -s /etc/letsencrypt/live/domaine.tld/privkey.pem torrent.domaine.tld/key.pem
ln -s /etc/letsencrypt/live/domaine.tld/fullchain.pem search.domaine.tld/crt.pem
ln -s /etc/letsencrypt/live/domaine.tld/privkey.pem search.domaine.tld/key.pem
ln -s /etc/letsencrypt/live/domaine.tld/fullchain.pem cloud.domaine.tld/crt.pem
ln -s /etc/letsencrypt/live/domaine.tld/privkey.pem cloud.domaine.tld/key.pem
ln -s /etc/letsencrypt/live/domaine.tld/fullchain.pem sondage.domaine.tld/crt.pem
ln -s /etc/letsencrypt/live/domaine.tld/privkey.pem sondage.domaine.tld/key.pem
ln -s /etc/letsencrypt/live/domaine.tld/fullchain.pem upload.domaine.tld/crt.pem
ln -s /etc/letsencrypt/live/domaine.tld/privkey.pem upload.domaine.tld/key.pem
ln -s /etc/letsencrypt/live/domaine.tld/fullchain.pem pma.domaine.tld/crt.pem
ln -s /etc/letsencrypt/live/domaine.tld/privkey.pem pma.domaine.tld/key.pem
ln -s /etc/letsencrypt/live/domaine.tld/fullchain.pem blog.domaine.tld/crt.pem
ln -s /etc/letsencrypt/live/domaine.tld/privkey.pem blog.domaine.tld/key.pem
ln -s /etc/letsencrypt/live/domaine.tld/fullchain.pem jappix.domaine.tld/crt.pem
ln -s /etc/letsencrypt/live/domaine.tld/privkey.pem jappix.domaine.tld/key.pem
ln -s /etc/letsencrypt/live/domaine.tld/fullchain.pem pads.domaine.tld/crt.pem
ln -s /etc/letsencrypt/live/domaine.tld/privkey.pem pads.domaine.tld/key.pem
ln -s /etc/letsencrypt/live/domaine.tld/fullchain.pem webmail.domaine.tld/crt.pem
ln -s /etc/letsencrypt/live/domaine.tld/privkey.pem webmail.domaine.tld/key.pem
ln -s /etc/letsencrypt/live/domaine.tld/fullchain.pem wiki.domaine.tld/crt.pem
ln -s /etc/letsencrypt/live/domaine.tld/privkey.pem wiki.domaine.tld/key.pem

nginx -t
service nginx restart

chown root:metronome /etc/letsencrypt/archive/
chown root:metronome /etc/letsencrypt/live/
chmod g+rx /etc/letsencrypt/archive/
chmod g+rx /etc/letsencrypt/live/

service metronome restart

cp /root/installServeur/letsencrypt/certificateRenewer /etc/cron.weekly/
chmod +x /etc/cron.weekly/certificateRenewer

##########################################################################
# Installation des app

# Yunohost multimedia
wget -nv https://github.com/maniackcrudelis/yunohost.multimedia/archive/master.zip
unzip master.zip
./yunohost.multimedia-master/script/ynh_media_build.sh

#Transmission
yunohost app install transmission -a “domain=torrent.domaine.tld”
./yunohost.multimedia-master/script/ynh_media_addfolder.sh --source=“/home/yunohost.transmission/completed” --dest=“Téléchargements”

#Mini dlna
yunohost app install GitHub - YunoHost-Apps/minidlna_ynh: MiniDLNA (ReadyMedia) package for YunoHost

#Searx
yunohost app install searx -a “domain=search.domaine.tld&path=/”

#NextCloud
yunohost app install GitHub - YunoHost-Apps/nextcloud_ynh: Nextcloud package for YunoHost -a “domain=cloud.domaine.tld&path=/”

#OpenSondage
yunohost app install opensondage -a “domain=sondage.domaine.tld&path=/”

#Jirafeau
yunohost app install jirafeau -a “domain=upload.domaine.tld&path=/”

#PhpMyAdmin
yunohost app install phpmyadmin -a “domain=pma.domaine.tld&path=/”

#Wordpress
yunohost app install wordpress -a “domain=blog.domaine.tld&path=/”

#My Webapp

yunohost app install my_webapp -a “domain=domaine.tld&path=/”

#Jappix
yunohost app install jappix -a “domain=jappix.domaine.tld&path=/”

#Etherpad pour l'instant ne s'installe pas
#yunohost app install https://github.com/YunoHost-Apps/etherpad_mypads_ynh -a "domain=pads.domaine.tld&path=/"

#Rainloop
yunohost app install GitHub - YunoHostPlugins-Testing/rainloop_ynh: Rainloop for Yunohost -a “domain=webmail.domaine.tld&path=/”

##########################################################################[/details]

Maintenant, tout ce qui est lié à Let’s Encrypt

  • certificateRenewer:

[details=Résumé]>
#!/bin/bash

###############################################################################
# Inspired from                                                               #
# https://community.letsencrypt.org/t/                                        #
# how-to-completely-automating-certificate-renewals-on-debian/5615            #
###############################################################################
###################
#  Configuration  #
###################
# This line MUST be present in all scripts executed by cron!
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
# Certs that will expire in less than this value will be renewed
REMAINING_DAYS_TO_RENEW=30
# Command to execute if certs have been renewed
SERVICES_TO_RESTART="nginx postfix metronome"
# Parameters for email alert
EMAIL_ALERT_FROM="cron-certrenewer@domaine.tld (Cron certificate renewer)"
EMAIL_ALERT_TO="email@contact"
EMAIL_ALERT_SUBJ="WARNING: SSL certificate renewal for CERT_NAME failed!"
# Letsencrypt stuff
# The executable
LEBIN="/root/letsencrypt/letsencrypt-auto"
# The config file
LECFG="/etc/letsencrypt/conf.ini"
# The directory where current .pem certificates are stored
LELIVE="/etc/letsencrypt/live"
# Renewal directory, which contains renewal configs for all certificates.
LERENEWAL="/etc/letsencrypt/renewal"
################
#  Misc tools  #
################
# -----------------------------------
# Given a certificate file, return the number of days before it expires
# -----------------------------------
function daysBeforeCertificateExpire()
{
    local CERT_FILE=$1
    local DATE_EXPIRE=$(openssl x509 -in $CERT_FILE -text -noout \
                       | grep "Not After"                        \
                       | cut -c 25-)
    local D1=$(date -d "$DATE_EXPIRE" +%s)
    local D2=$(date -d "now"        +%s)
    local DAYS_EXP=$(( ( D1 - D2) / 86400 ))
    echo $DAYS_EXP
}
# -----------------------------------
# Send an alert email stating that the renewing of a cert failed, and paste the
# logs into the mail body
# -----------------------------------
function sendAlert()
{
    local CERT_NAME=$1
    local LOG_FILE=$2
    local SUBJ=$(echo $EMAIL_ALERT_SUBJ | sed "s/CERT_NAME/${CERT_NAME}/g")
    echo -e " Here is the log of what happened\n"             \
            "Consider also checking /var/log/letsencrypt/\n"  \
            "--------------------------------------------\n"  \
    | cat - ${LOG_FILE}                                       \
    | mail -s "${SUBJ}"                                       \
           -r "${EMAIL_ALERT_FROM}"                           \
               ${EMAIL_ALERT_TO}
}
# -----------------------------------
# -----------------------------------
function restartServices()
{
    eval "/bin/sync"
    local SERVICE
    for SERVICE in ${SERVICES_TO_RESTART}
    do
        eval "service ${SERVICE} restart"
    done
}
###############################
#  Actual lets encrypt stuff  #
###############################
# -----------------------------------
# Given a certificate name, echo True or False if it will soon expire
# (see REMAINING_DAYS_TO_RENEW)
# -----------------------------------
function certificateNeedsToBeRenewed()
{
    local CERT_NAME=$1
    local CERT_FILE="${LELIVE}/${CERT_NAME}/cert.pem"
    local DAYS_BEFORE_EXPIRE=`daysBeforeCertificateExpire $CERT_FILE`
    if [[ ${DAYS_BEFORE_EXPIRE} -lt ${REMAINING_DAYS_TO_RENEW} ]]
    then
        echo "True"
    else
        echo "False"
    fi
}
# -----------------------------------
# Given a certificate name, attempt to renew it
# Stuff is logged in a file
# -----------------------------------
function renewCertificate()
{
    local CERT_NAME=$1
    local LOG_FILE=$2
    local CERT_FILE="${LELIVE}/${CERT_NAME}/cert.pem"
    local CERT_CONF="${LERENEWAL}/${CERT_NAME}.conf"
    # Parse "domains = xxxx", we might need to remove the last character
    # if it's a comma
    local DOMAINS=$(grep -o --perl-regex "(?<=domains \= ).*" "${CERT_CONF}")
    local LAST_CHAR=$(echo ${DOMAINS} | awk '{print substr($0,length,1)}')
    if [ "${LAST_CHAR}" = "," ]
    then
        local DOMAINS=$(echo ${DOMAINS} |awk '{print substr($0, 1, length-1)}')
    fi
    # Recreate the webroot folder (expected to be in /tmp/)
    WEBROOT_PATH=$(cat $CERT_CONF    \
                 | grep webroot_path \
                 | tr ',' ' '        \
                 | awk '{print $3}')
    mkdir -p ${WEBROOT_PATH}
    rm ${LOG_FILE}
    touch ${LOG_FILE}
    ${LEBIN} certonly          \
        --renew-by-default     \
        --config "${LECFG}"    \
        --domains "${DOMAINS}" \
        > ${LOG_FILE} 2>&1
}
# -----------------------------------
# Attempt to renew all certificates in LELIVE directory
# -----------------------------------
function renewAllCertificates()
{
    local AT_LEAST_ONE_CERT_RENEWED="False"
    # Loop on certificates in live directory
    local CERT
    for CERT in $(ls -1 "${LELIVE}")
    do
        echo "Checking $CERT certificate ..."
        # Check if current certificate needs to be renewed
        if [[ `certificateNeedsToBeRenewed ${CERT}` == "True" ]]
        then
            echo " > Needs to be renewed. Attempting to ..."
            # If yes, attempt to renew it
            local LOG_FILE="/tmp/cron-cert-renewer.log"
            renewCertificate ${CERT} ${LOG_FILE}
            # Check it worked
            if [[ `certificateNeedsToBeRenewed $CERT` == "False" ]]
            then
                echo " > Cert was succesfully renewed."
                local AT_LEAST_ONE_CERT_RENEWED="True"
            else
                echo " > An error occured, an email was sent."
                sendAlert ${CERT} ${LOG_FILE}
            fi
        else
            echo " > No need to renew it."
        fi
    done
    if [[ ${AT_LEAST_ONE_CERT_RENEWED} == "True" ]]
    then
        return 1
    else
        return 0
    fi
}
###################
#  Main function  #
###################
function main()
{
    renewAllCertificates
    if [[ $? -eq 1 ]]
    then
        restartServices
    fi
}
main[/details]
  • conf.ini:

[details=Résumé]> #################################

#  Let's encrypt configuration  #
#################################
# Taille de la clef
rsa-key-size = 4096
# Email de notification / contact si nécessaire dans le futur
email = email@contact
# Utiliser l'interface texte
text = True
# Accepter les Conditions d'Utilisation du service
agree-tos = True
# Utiliser la méthode d'authentification webroot
# avec le contenu dans /tmp/letsencrypt-auto
authenticator = webroot
webroot-path = /tmp/letsencrypt-auto[/details]
  • conf.json.persistent:

[details=Résumé]> “redirect_urls”:{},

	"unprotected_urls" : [
	"domaine.tld/.well-known/acme-challenge" ,
	"torrent.domaine.tld/.well-known/acme-challenge" ,
	"search.domaine.tld/.well-known/acme-challenge" ,
	"cloud.domaine.tld/.well-known/acme-challenge" ,
	"sondage.domaine.tld/.well-known/acme-challenge" ,
	"upload.domaine.tld/.well-known/acme-challenge" ,
	"pma.domaine.tld/.well-known/acme-challenge" ,
	"blog.domaine.tld/.well-known/acme-challenge" ,
	"jappix.domaine.tld/.well-known/acme-challenge" ,
	"pads.domaine.tld/.well-known/acme-challenge" ,
	"wiki.domaine.tld/.well-known/acme-challenge", 
	"webmail.domaine.tld/.well-known/acme-challenge" ] }[/details]
  • letsencrypt.conf:

[details=Résumé]> location ‘/.well-known/acme-challenge’ {

    default_type "text/plain";
    root        /tmp/letsencrypt-auto;
}[/details]

Last but not least, la partie backup

Utilisant la carte SD de mon raspberry uniquement pour la partition boot, j’ai décidé d’en faire une sauvegarde quotidienne dans mon répertoire contenant les scripts et les logs (sur mon disque dur / donc). Ce répertoire est quant à lui sauvegardé, comme presque tout le reste du système, lors du branchement de mon disque dur de sauvegarde.

  • 90-myrules.rules, la règle udev, qu’il faut personnaliser avant le lancement du script:
Résumé

> ACTION==“add”, KERNEL==“sd?1”, SUBSYSTEM==“block”, ATTR{size}==“1953521664”, ATTR{start}==“2048”, RUN+=“/var/backup/backupRelay.sh”

  • le script GuideBackup.sh, pour initier le tout:

[details=Résumé]> #!/bin/sh

###########################################################################
##################### Guide pour préparer les backup ######################
###########################################################################
cd /root/installServeur/backup/
###########################################################################
# Création des dossiers de backup

mkdir /var/backup
mkdir /var/backup/backupBoot

###########################################################################
# Création des dossiers de log pour duplicity

mkdir /var/backup/log/
touch /var/backup/log/lastBackup.log
touch /var/backup/log/oldBackup.log
touch /var/backup/log/historicBackup.log

###########################################################################
# Copie de la règle udev ATTENTION, CETTE REGLE A DU ETRE ADAPTEE AUPARAVANT
# à l'aide par exemple de:
# udevadm info -a -p $(udevadm info -q path -n /dev/sdb1)

cp 90-myrules.rules /etc/udev/rules.d/

###########################################################################
# Placer les scripts

chmod 700 backup*

cp backupBoot /etc/cron.daily/
cp backupRelay.sh /var/backup/
cp backupGeneral.sh /var/backup/[/details]

  • backupBoot :

[details=Résumé]> #!/bin/sh

export PASSPHRASE=motdepasse
duplicity /boot file:///var/backup/backupBoot/
unset PASSPHRASE[/details]
  • backupRelay.sh, qui sert juste à contourner le timeout de udev:

[details=Résumé]> #!/bin/sh

at now +1 minutes -f /var/backup/backupGeneral.sh[/details]
  • backupGeneral.sh, le gros morceau:

[details=Résumé]> #!/bin/sh

sleep 5
# Dump des bases de données
##MySQL
mysqldump -pMotDePasse --all-databases --single-transaction > /var/backup/dumpMySQL.sql # Le mdp se trouve dans /etc/yunohost/mysql
##PostgreSQL
#pg_dumpall -W motdepasse > /var/backup/dumpPostgreSQL.sql
# Montage de la partition
mount /dev/sdb1 /mnt/
# Gestion des logs
cat /var/backup/log/oldBackup.log >> /var/backup/log/historicBackup.log
cp /var/backup/log/lastBackup.log /var/backup/log/oldBackup.log
# Précision du mot de passe
PASSPHRASE=motDePasse
export PASSPHRASE
# Lancement du backup
duplicity / --exclude /boot --exclude /dev --exclude /media --exclude /mnt --exclude /proc --exclude /run --exclude /sys --exclude /tmp --exclude /var/lib/mysql/ file:///mnt/ > /var/backup/log/lastBackup.log # Ne pas oublier d'exclure /var/lib/pgsql si PostgreSQL est installé
# Démontage de la partition
umount /dev/sdb1
# Envoi du log par mail
mail -s "Log duplicity" user@domaine.tld < /var/backup/log/lastBackup.log[/details]

À noter que je n’ai pas testé le dump de PostgreSQL, j’ai juste pris l’exemple de la doc pour l’instant.

#Sources

2 Likes

Merci pour tout ça :slight_smile:
Je serai en effet de curieux de voir les scripts de backup.

Pinaise par contre ton Lets encrypt il doit cravacher avec tous ces sous-domaines ! Dommage qu’il n’y a pas de certificat wildcard (il me semble). D’ailleurs dans 2.5 LE sera intégré de base.

Pour Rainloop, je t’invite à utiliser plutot la version sur YunoHost-Apps qui est bien plus avancée : https://github.com/YunoHost-Apps/rainloop_ynh
Ou si tu en attends encore plus, tu peux utiliser mes améliorations qui ne vont pas tarder à être rajoutées: https://github.com/YunoHost-Apps/rainloop_ynh/pull/11

Pour les backup, j’avoue avoir galéré hier, mais @Maniack_Crudelis m’a bien aidé.

C’est vrai qu’il met du temps à pondre tous ces certificats ! M’en fous, ils disent 100 par semaine :smile: Mais c’est vrai qu’un wildcard serait tellement plus pratique…

Pour Rainloop je vais regarder tout ça… Tu as amélioré le support PGP, c’est ça?

Entre autre, je détaillé tout sur le lien de la PR, y’a beaucoup de trucs

Ah ouais, effectivement, c’est intéressant!

Je désinstalle ma version avant d’installer la tienne je suppose, plutôt que faire un upgrade?

C’est toujours mieux oui, mais a priori l’upgrade devrait fonctionner.