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
- /backup/
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
- Pour tout ce qui est Let’s Encrypt, l’incontournable tuto de CaptainSqrt2.
- Déjà précisé plus haut, mais pour la rédaction des règles udev.
- Pour contourner le timeout de udev.
- Manpage de duplicity.
- Envoi du log par mail.