Скрипт для Debian 12
#!/usr/bin/env bash
# shellcheck disable=SC2059
# Copyright (C) 2015-2023 YunoHost
#
# 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/>.
# RUN INSTALL SCRIPT WITH -a FOR NON-INTERACTIVE MODE.
set -u
# Globals
YUNOHOST_LOG="/var/log/yunohost-installation_$(date +%Y%m%d_%H%M%S).log"
readonly YUNOHOST_LOG
export DEBIAN_FRONTEND=noninteractive
###############################################################################
# Main functions #
###############################################################################
function check_connection() {
TIMEOUT=$1
while [ "$TIMEOUT" -gt 0 ]; do
ping -c 1 -W 2 yunohost.org > /dev/null 2>&1 && return 0
sleep 1
TIMEOUT=$((TIMEOUT - 1))
done
return 1
}
function usage() {
cat << EOF
Usage :
$(basename "$0") [-a] [-d <DISTRIB>] [-h]
Options :
-a Enable automatic mode. No questions are asked.
This does not perform the post-install step.
-d Choose the distribution to install ('stable', 'testing', 'unstable').
Defaults to 'stable'
-f Ignore checks before starting the installation. Use only if you know
what you are doing.
-h Prints this help and exit
EOF
}
function parse_options()
{
AUTOMODE=false
DISTRIB=stable
BUILD_IMAGE=false
FORCE=false
while getopts ":aid:fh" option; do
case $option in
a)
AUTOMODE=true
export DEBIAN_FRONTEND=noninteractive
;;
d)
DISTRIB=$OPTARG
;;
f)
FORCE=true
;;
i)
# This hidden option will allow to build generic image for Rpi/Olimex
BUILD_IMAGE=true
;;
h)
usage
exit 0
;;
:)
usage
exit 1
;;
\?)
usage
exit 1
;;
esac
done
}
function main()
{
parse_options "$@"
check_assertions || exit 1
confirm_installation || exit 1
upgrade_system || die "Failed to upgrade the system"
boring_workarounds || die "Failed to run the boring workarounds"
setup_package_source || die "Setting up deb package sources failed"
install_yunohost_packages || die "Installation of Yunohost packages failed"
# For some reason sometimes dbus is not properly started/enabled ...
if [[ "$BUILD_IMAGE" == "false" ]] ; then
systemctl is-active dbus >/dev/null || systemctl enable dbus --now
fi
if [[ "$BUILD_IMAGE" == "true" ]] ; then
clean_image || die "Unable to clean image"
fi
if is_raspbian ; then
# FIXME : add a proper conclusion + timer warning?
# Reboot should be done before postinstall to be able to run iptables rules
reboot
fi
conclusion
exit 0
}
###############################################################################
# Helpers #
###############################################################################
normal=$(printf '\033[0m')
bold=$(printf '\033[1m')
# faint=$(printf '\033[2m')
# underline=$(printf '\033[4m')
# negative=$(printf '\033[7m')
red=$(printf '\033[31m')
green=$(printf '\033[32m')
orange=$(printf '\033[33m')
blue=$(printf '\033[34m')
# yellow=$(printf '\033[93m')
# white=$(printf '\033[39m')
resetline=$(printf '\r\033[K')
readonly normal bold red green orange blue resetline
# shellcheck disable=SC2317
function success()
{
local msg=${1}
echo "[${bold}${green} OK ${normal}] ${msg}" | tee -a "$YUNOHOST_LOG"
}
function info()
{
local msg=${1}
echo "[${bold}${blue}INFO${normal}] ${msg}" | tee -a "$YUNOHOST_LOG"
}
# shellcheck disable=SC2317
function warn()
{
local msg=${1}
echo "[${bold}${orange}WARN${normal}] ${msg}" | tee -a "$YUNOHOST_LOG" >&2
}
function error()
{
local msg=${1}
echo "[${bold}${red}FAIL${normal}] ${msg}" | tee -a "$YUNOHOST_LOG" >&2
}
function die() {
error "$1"
info "Installation logs are available in $YUNOHOST_LOG"
exit 1
}
trap trapint 2
# shellcheck disable=SC2317
function trapint {
echo ""
die "Aborted"
exit 0
}
function show_apt_progress {
local percent="$1"
local title="$2"
local message="$3"
local done=$((${percent%.*}*40/100))
local todo=$((39 - done))
local done_sub_bar todo_sub_bar
done_sub_bar="$(printf "%${done}s")"
todo_sub_bar="$(printf "%${todo}s")"
echo -ne "$resetline $bold$blue$title$normal [${done_sub_bar// /=}>${todo_sub_bar}] ${percent:0:4}% ${message:0:40}"
}
function _apt_with_progress() {
local wat percent message title
apt-get "$@" -o 'APT::Status-Fd=3' 3>&1 >> "$YUNOHOST_LOG" 2>&1 \
| while read -r line; do
wat=$(echo "$line" | cut -d: -f1)
percent=$(echo "$line" | cut -d: -f3)
message=$(echo "$line" | cut -d: -f2)
if [[ $wat == "dlstatus" ]]; then
title="Downloading"
else
title="Installing"
fi
show_apt_progress "$percent" "$title" "$message";
done
}
function _apt() {
set -o pipefail
cat << EOF >> "$YUNOHOST_LOG"
===================
Running: apt-get $*
===================
EOF
if [[ "$AUTOMODE" == "true" ]]; then
# Why we need pipefail : https://stackoverflow.com/a/6872163
apt-get "$@" 2>&1 | tee -a "$YUNOHOST_LOG"
ret="$?"
else
if _apt_with_progress "$@"; then
ret=0
printf "$resetline $bold${green}Done$normal"
else
ret=1
printf "$resetline $bold${red}'apt-get $*' failed.$normal Please check $YUNOHOST_LOG for debugging\n\n";
fi
fi
set +o pipefail
return "$ret"
}
function apt_update() {
_apt update --allow-releaseinfo-change
}
function apt_install() {
_apt install --assume-yes -o Dpkg::Options::="--force-confold" "$@"
}
###############################################################################
# Installation steps #
###############################################################################
function check_assertions()
{
# Assert we're on Debian
# Note : we do not rely on lsb_release to avoid installing a dependency
# only to check this...
if [[ ! -f "/etc/debian_version" ]]; then
error "This script can only be ran on Debian 12 (Bookworm)."
return 1
fi
# Assert we're on Bookworm
# Note : we do not rely on lsb_release to avoid installing a dependency
# only to check this...
# TODO: remove the line with "bookworm/sid"
debian_version=$(cat /etc/debian_version)
if ! [[ "$debian_version" =~ ^12.* ]] && ! [[ "$debian_version" =~ "bookworm/sid" ]]; then
error "YunoHost is only available for the version 12 (Bookworm) of Debian, you are using '$(cat /etc/debian_version)'."
return 1
fi
# Forbid people from installing on Ubuntu or Linux mint ...
if [[ -f "/etc/lsb-release" ]];
then
if grep -q -i "Ubuntu\|Mint" /etc/lsb-release
then
error "Please don't try to install YunoHost on an Ubuntu or Linux Mint system ... You need a 'raw' Debian 12 (Bookworm)."
return 1
fi
fi
# Assert we're root
if [[ "$(id -u)" != "0" ]]; then
error "This script must be run as root. On most setups, the command 'sudo -i' can be run first to become root."
return 1
fi
# Assert Internet is reachable
if ! check_connection 30; then
die "You need internet to use this script! yunohost.org did not respond to ping after more than 30s."
fi
# Assert curl is setup
if ! command -v curl >/dev/null 2>&1 && ! apt_install curl; then
error "Yunohost installer requires curl to be installed, but it failed to install it."
return 1
fi
# Check PATH var
if [[ "$PATH" != *"/sbin"* ]]; then
error "Your environment PATH variable must contains /sbin directory. Maybe try running 'PATH=/sbin:\$PATH' to fix this."
return 1
fi
# Assert systemd is installed
if ! command -v systemctl > /dev/null; then
error "YunoHost requires systemd to be installed."
return 1
fi
# Check that kernel is >= 3.12, otherwise systemd won't work properly. Cf. https://github.com/systemd/systemd/issues/5236#issuecomment-277779394
if dpkg --compare-versions "$(uname -r)" "lt" "3.12"; then
error "YunoHost requires a kernel >= 3.12. Please consult your hardware documentation or VPS provider to learn how to upgrade your kernel."
return 1
fi
# Check we aren't running in docker or other weird containers that we can't probably install on
if systemd-detect-virt | grep -q -w "docker\|container-other" && [[ "$FORCE" != "true" ]]; then
error "It seems like you are trying to install YunoHost in docker or a weird container technology which probably is not supported by this install script (or YunoHost as a whole). If you know what you are doing, you can run this script with -f."
return 1
fi
# Check possible conflict with apache, bind9.
if dpkg --get-selections | grep -v deinstall | grep -q 'bind9\s' && [[ "$FORCE" != "true" ]]; then
error "Bind9 is installed on your system. Yunohost conflicts with Bind9 because it requires dnsmasq. To be able to run this script, you should first run 'apt remove bind9 --purge --autoremove'."
return 1
fi
if dpkg --get-selections | grep -v deinstall | grep -q 'apache2\s' && [[ "$FORCE" != "true" ]]; then
error "Apache is installed on your system. Yunohost conflicts with apache2 because it requires nginx. To be able to run this script, you should first run 'apt remove apache2 --purge --autoremove'."
return 1
fi
}
function confirm_installation() {
[[ "$AUTOMODE" == "true" ]] && return 0
cat << EOF | tee -a "$YUNOHOST_LOG"
$bold
╭───────────────────────╮
│ YunoHost Installation │
╰───────────────────────╯
$normal
• Installing YunoHost requires to install various important services,
and possibly rework the configuration of some services that may already
be installed (such as: nginx, postfix, dovecot, fail2ban, slapd)
EOF
read -r -p " Are you sure you want to proceed (y/n) ? " choice < /dev/tty
choice="$(echo "$choice" | tr '[:upper:]' '[:lower:]')"
[[ "$choice" == "yes" ]] || [[ "$choice" == "y" ]] || { error "Aborting"; return 1; }
if [[ "$DISTRIB" == "unstable" ]]
then
cat << EOF | tee -a "$YUNOHOST_LOG"
• You are installing the unstable/alpha version of YunoHost 12/Bookworm.
You should be warned that THIS IS ALPHA-STAGE DEVELOPMENT.
WE ABSOLUTELY DISCOURAGE ANY USE OF THIS VERSION
IN A PRODUCTION CONTEXT, THIS IS ONLY MEANT FOR *TESTING*.
THINGS **WILL** BREAK.
EOF
read -r -p " Type 'Yes, I understand' if you understand: " choice < /dev/tty
[[ "$choice" == "Yes, I understand" ]] || { error "Aborting"; return 1; }
fi
# SSH config warning
if [[ -f /etc/ssh/sshd_config ]]
then
# If root login is currently enabled
local root_login_warning=""
if ! grep -E "^[[:blank:]]*PermitRootLogin[[:blank:]]+no" /etc/ssh/sshd_config ; then
root_login_warning=" • SSH login using root will be disabled (except from local network).\n"
root_login_warning+=" Instead, you should login using the first YunoHost user."
fi
# If current conf uses a custom ssh port
local ssh_port_warning=""
if grep -Ev "^[[:blank:]]*Port[[:blank:]]+22[[:blank:]]*(#.*)?$" /etc/ssh/sshd_config | grep -E "^[[:blank:]]*Port[[:blank:]]+[[:digit:]]+$" ; then
ssh_port_warning=" • You will have to connect using port 22 instead of your custom SSH port,\n"
ssh_port_warning+=" though you can reconfigure this from YunoHost after the postinstall."
fi
if [[ -n "$root_login_warning" ]] || [[ -n "$ssh_port_warning" ]]
then
cat << EOF | tee -a "$YUNOHOST_LOG"
• Additionally, it is encouraged to let YunoHost manage the SSH configuration.
However, you should be aware that:
$(test -n "$root_login_warning" && echo -e "$root_login_warning")
$(test -n "$ssh_port_warning" && echo -e "$ssh_port_warning")
(Note that this will only be effective *after* you run YunoHost's postinstall)
EOF
read -r -p " Should YunoHost override the SSH configuration (y/n) ? " choice < /dev/tty
choice="$(echo "$choice" | tr '[:upper:]' '[:lower:]')"
if [[ "$choice" != "yes" ]] && [[ "$choice" != "y" ]]
then
# Keep a copy to be restored during the postinstall
# so that the ssh confs behaves as manually modified.
cp /etc/ssh/sshd_config /etc/ssh/sshd_config.before_yunohost
fi
fi
fi
cat << EOF | tee -a "$YUNOHOST_LOG"
🚀 ${bold}Let's go !$normal
📜 Detailed logs will be available in $YUNOHOST_LOG
EOF
return 0
}
function upgrade_system() {
echo "" | tee -a "$YUNOHOST_LOG"
echo "$bold 1/5 • Running system upgrades$normal" | tee -a "$YUNOHOST_LOG"
echo "" | tee -a "$YUNOHOST_LOG"
apt_update || return 1
# We need libtext-iconv-perl even before the dist-upgrade,
# otherwise the dist-upgrade might fails on some setups because
# perl is yolomacnuggets :|
# Stuff like "Can't locate object method "new" via package "Text::Iconv""
apt_install libtext-iconv-perl || return 1
# Manually upgrade grub stuff in non-interactive mode,
# otherwise a weird technical question is asked to the user
# regarding how to upgrade grub's configuration...
apt_install --only-upgrade grub-common grub2-common || true
_apt dist-upgrade -y -o Dpkg::Options::="--force-confold" || return 1
if is_raspbian ; then
apt_install rpi-update || return 1
if [[ "$BUILD_IMAGE" == "false" ]] ; then
(rpi-update 2>&1 | tee -a "$YUNOHOST_LOG") || return 1
fi
fi
}
function boring_workarounds() {
echo "" | tee -a "$YUNOHOST_LOG"
echo "" | tee -a "$YUNOHOST_LOG"
echo "$bold 2/5 • Install dependencies needed before the main install$normal" | tee -a "$YUNOHOST_LOG"
echo "" | tee -a "$YUNOHOST_LOG"
# ###################################################################### #
# Dependencies that must be installed prior to the rest, for reasons ... #
# (for example https://github.com/YunoHost/issues/issues/1382) #
# ###################################################################### #
apt_install --no-install-recommends lsb-release dialog curl gnupg apt-transport-https adduser debconf debhelper dh-autoreconf locales
echo "" | tee -a "$YUNOHOST_LOG"
echo "" | tee -a "$YUNOHOST_LOG"
echo "$bold 3/5 • Apply various tweaks to prepare installation$normal" | tee -a "$YUNOHOST_LOG"
echo "" | tee -a "$YUNOHOST_LOG"
# #################################### #
# Attempt to fix the usual locale mess #
# #################################### #
# This function tries to fix the whole locale and perl mess about missing locale files
# Generate at least en_US.UTF-8
grep -q "^ *en_US.UTF-8" /etc/locale.gen || echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen
# FIXME: here some day we should try to identify the user's lang from LANG or LC_ALL and generate the appropriate locale ...
# (and set this lang as the default in /etc/env 3 lines below)
locale-gen >/dev/null
# If no /etc/environment exists, default to en_US.UTF-8
grep -q LC_ALL /etc/environment || echo 'LC_ALL="en_US.UTF-8"' >> /etc/environment
source /etc/environment
export LC_ALL
# ######################## #
# Workarounds for fail2ban #
# ######################## #
# We need to create auth.log in case it does not exists, because in some situation,
# this file does not exists, fail2ban will miserably fail to start because
# the default fail2ban jail include the sshd jail ... >.>
touch /var/log/auth.log
# ######################## #
# Workarounds for avahi #
# ######################## #
# When attempting several installation of Yunohost on the same host
# with a light VM system like LXC
# we hit a bug with avahi-daemon postinstallation
# This is described in detail in https://github.com/lxc/lxc/issues/25
#
# It makes the configure step of avahi-daemon fail, because the service does
# start correctly. Then all other packages depending on avahi-daemon refuse to
# configure themselves.
#
# The workaround we use is to generate a random uid for the avahi user, and
# create the user with this id beforehand, so that the avahi-daemon postinst
# script does not do it on its own. Our randomized uid has far less chances to
# be already in use in another system than the automated one (which tries to use
# consecutive uids).
# Return without error if avahi already exists
if ! id avahi > /dev/null 2>&1;
then
# Get a random unused uid between 500 and 999 (system-user)
local avahi_id=$((500 + RANDOM % 500))
while cut -d ':' -f 3 /etc/passwd | grep -q $avahi_id ;
do
avahi_id=$((500 + RANDOM % 500))
done
#info "Workaround for avahi : creating avahi user with uid $avahi_id"
# Use the same adduser parameter as in the avahi-daemon postinst script
# Just specify --uid explicitely
adduser --disabled-password --quiet --system \
--home /var/run/avahi-daemon --no-create-home \
--gecos "Avahi mDNS daemon" --group avahi \
--uid $avahi_id
fi
# ########## #
# Resolvconf #
# ########## #
# On some machines (e.g. OVH VPS), the /etc/resolv.conf is immutable
# We need to make it mutable for the resolvconf dependency to be installed
chattr -i /etc/resolv.conf 2>/dev/null || true
# Done
printf "$resetline $bold${green}Done$normal"
}
function setup_package_source() {
echo "" | tee -a "$YUNOHOST_LOG"
echo "" | tee -a "$YUNOHOST_LOG"
echo "$bold 4/5 • Adding YunoHost repository to apt$normal" | tee -a "$YUNOHOST_LOG"
echo "" | tee -a "$YUNOHOST_LOG"
local CUSTOMAPT=/etc/apt/sources.list.d/yunohost.list
# Debian repository
local CUSTOMDEB="deb [signed-by=/usr/share/keyrings/yunohost-bookworm.gpg] http://forge.yunohost.org/debian/ bookworm stable"
if [[ "$DISTRIB" == "stable" ]] ; then
echo "$CUSTOMDEB" > $CUSTOMAPT
elif [[ "$DISTRIB" == "testing" ]] ; then
echo "$CUSTOMDEB testing" > $CUSTOMAPT
elif [[ "$DISTRIB" == "unstable" ]] ; then
echo "$CUSTOMDEB testing unstable" > $CUSTOMAPT
fi
# Add YunoHost repository key to the keyring
curl --fail --silent https://forge.yunohost.org/yunohost_bookworm.asc | gpg --dearmor > /usr/share/keyrings/yunohost-bookworm.gpg
apt_update
}
function install_yunohost_packages() {
echo "" | tee -a "$YUNOHOST_LOG"
echo "" | tee -a "$YUNOHOST_LOG"
echo "$bold 5/5 • Installing YunoHost$normal" | tee -a "$YUNOHOST_LOG"
echo "" | tee -a "$YUNOHOST_LOG"
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
postfix postfix/main_mailer_type select Internet Site
postfix postfix/mailname string /etc/mailname
nslcd nslcd/ldap-bindpw password
nslcd nslcd/ldap-starttls boolean false
nslcd nslcd/ldap-reqcert select
nslcd nslcd/ldap-uris string ldap://localhost/
nslcd nslcd/ldap-binddn string
nslcd nslcd/ldap-base string dc=yunohost,dc=org
libnss-ldapd libnss-ldapd/nsswitch multiselect group, passwd, shadow
postsrsd postsrsd/domain string yunohost.org
EOF
# Allow sudo removal even if no root password has been set (on some DO
# droplet or Vagrant virtual machines), as YunoHost use sudo-ldap
export SUDO_FORCE_REMOVE=yes
# Install YunoHost
# FIXME : do we still want to install recommends ?
apt_install \
-o APT::install-recommends=true \
yunohost yunohost-admin postfix \
|| return 1
}
function conclusion() {
# Get first local IP and global IP
local local_ip
local_ip=$(hostname --all-ip-address | tr ' ' '\n' | grep -v ":" | head -n1)
local global_ip
global_ip=$(curl https://ip.yunohost.org 2>/dev/null)
local no_ip=""
# Will ignore local ip if it's already the global IP (e.g. for some VPS)
[[ "$local_ip" != "$global_ip" ]] || local_ip=""
# Formatting
local width=79
[[ -z "$local_ip" ]] || {
local_ip=$(echo -e "\n │ - https://$local_ip/ (local IP, if self-hosting at home)")
local nb_spaces=$(( width - ${#local_ip} ))
local_ip+="$(printf "%${nb_spaces}s")│"
}
[[ -z "$global_ip" ]] || {
global_ip=$(echo -e "\n │ - https://$global_ip/ (global IP, if you're on a VPS)")
local nb_spaces=$(( width - ${#global_ip} ))
global_ip+="$(printf "%${nb_spaces}s")│"
}
[[ -n "$local_ip" ]] || [[ -n "$global_ip" ]] || {
no_ip=$(echo -e "\n │ - (no local nor global IP detected ?)")
local nb_spaces=$(( width - ${#no_ip} ))
no_ip+="$(printf "%${nb_spaces}s")│"
}
cat << EOF | tee -a "$YUNOHOST_LOG"
🎉 ${bold}YunoHost installation completed!$normal
╭───────────────────────────────────────────────────────────────────────────╮
│ You should now proceed with Yunohost post-installation. │
│ This is where you will be asked for: │
│ • the main domain of your server ; │
│ • the administration password ; │
│ • the name and password of the first user, which will also be admin. │
│ │
│ You can perform this step, either: │
│ • from the command line, by running 'yunohost tools postinstall' as root │
│ • or from your web browser, by accessing : │${local_ip}${global_ip}${no_ip}
│ │
│ If this is your first time with YunoHost, it is strongly recommended to │
│ take time to read the administator documentation and in particular the │
│ sections 'Finalizing your setup' and 'Getting to know YunoHost'. │
│ │
│ It is available at the following URL : ➡️ https://yunohost.org/admindoc │
╰───────────────────────────────────────────────────────────────────────────╯
EOF
}
###############################################################################
# Raspbian specific stuff #
###############################################################################
function is_raspbian() {
# On Raspbian image lsb_release is available
if [[ "$(lsb_release -i -s 2> /dev/null)" != "Raspbian" ]] ;
then
return 1
fi
return 0
}
###############################################################################
# Image building specific stuff #
###############################################################################
function clean_image() {
{
# Delete SSH keys
rm -f /etc/ssh/ssh_host_*
yes | ssh-keygen -f /etc/ssh/ssh_host_rsa_key -N '' -t rsa
yes | ssh-keygen -f /etc/ssh/ssh_host_dsa_key -N '' -t dsa
yes | ssh-keygen -f /etc/ssh/ssh_host_ecdsa_key -N '' -t ecdsa -b 521
# Deleting logs ...
find /var/log -type f -exec rm {} \;
# Purging apt ...
apt-get clean
} >> "$YUNOHOST_LOG" 2>&1
}
###############################################################################
main "$@"