661f4ece48
Merge in DNS/adguard-home from 3868-imp-uninstall to master
Closes #3868.
Updates #3457.
Squashed commit of the following:
commit 6f50713407980c27e5b14bef4dc8839e134ec5c8
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date: Mon Dec 27 19:06:13 2021 +0300
all: imp openwrt
commit 59f058f8ec7f5ac8cb795bf837c396601652a6ff
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date: Mon Dec 27 17:26:32 2021 +0300
all: imp code && docs
commit bab95366b0ffa40d96de5bb8116ec14606e310ed
Merge: 92ebc210 52f36f20
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date: Mon Dec 27 17:06:25 2021 +0300
Merge branch 'master' into 3868-imp-uninstall
commit 92ebc210f04d5e02c3eef726017a0d5687f4bc4c
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date: Mon Dec 27 13:18:58 2021 +0300
home: imp freebsd script & log changes
commit 583ffc256e9f87cf19da2eca8bbefc9e00ea86cc
Author: Eugene Burkov <E.Burkov@AdGuard.COM>
Date: Thu Dec 16 14:08:46 2021 +0300
all: imp service uninstall
577 lines
11 KiB
Bash
577 lines
11 KiB
Bash
#!/bin/sh
|
|
|
|
# AdGuard Home Installation Script
|
|
|
|
# Exit the script if a pipeline fails (-e), prevent accidental filename
|
|
# expansion (-f), and consider undefined variables as errors (-u).
|
|
set -e -f -u
|
|
|
|
# Function log is an echo wrapper that writes to stderr if the caller
|
|
# requested verbosity level greater than 0. Otherwise, it does nothing.
|
|
log() {
|
|
if [ "$verbose" -gt '0' ]
|
|
then
|
|
echo "$1" 1>&2
|
|
fi
|
|
}
|
|
|
|
# Function error_exit is an echo wrapper that writes to stderr and stops the
|
|
# script execution with code 1.
|
|
error_exit() {
|
|
echo "$1" 1>&2
|
|
|
|
exit 1
|
|
}
|
|
|
|
# Function usage prints the note about how to use the script.
|
|
#
|
|
# TODO(e.burkov): Document each option.
|
|
usage() {
|
|
echo 'install.sh: usage: [-c channel] [-C cpu_type] [-h] [-O os] [-o output_dir]'\
|
|
'[-r|-R] [-u|-U] [-v|-V]' 1>&2
|
|
|
|
exit 2
|
|
}
|
|
|
|
# Function is_command checks if the command exists on the machine.
|
|
is_command() {
|
|
command -v "$1" >/dev/null 2>&1
|
|
}
|
|
|
|
# Function is_little_endian checks if the CPU is little-endian.
|
|
is_little_endian() {
|
|
[ "$( head -c 6 /bin/sh | tail -c 1 )" = "$( printf '\001' )" ]
|
|
}
|
|
|
|
# Function check_required checks if the required software is available on the
|
|
# machine. The required software:
|
|
#
|
|
# curl
|
|
# unzip (macOS) / tar (other unixes)
|
|
#
|
|
check_required() {
|
|
required_darwin="unzip"
|
|
required_unix="tar"
|
|
readonly required_darwin required_unix
|
|
|
|
# Split with space.
|
|
required="curl"
|
|
case "$os"
|
|
in
|
|
('freebsd'|'linux'|'openbsd')
|
|
required="$required $required_unix"
|
|
;;
|
|
('darwin')
|
|
required="$required $required_darwin"
|
|
;;
|
|
(*)
|
|
# Generally shouldn't happen, since the OS has already been
|
|
# validated.
|
|
error_exit "unsupported operating system: '$os'"
|
|
;;
|
|
esac
|
|
|
|
# Don't use quotes to get word splitting.
|
|
for cmd in ${required}
|
|
do
|
|
log "checking $cmd"
|
|
if ! is_command "$cmd"
|
|
then
|
|
log "the full list of required software: [$required]"
|
|
|
|
error_exit "$cmd is required to install AdGuard Home via this script"
|
|
fi
|
|
done
|
|
}
|
|
|
|
# Function check_out_dir requires the output directory to be set and exist.
|
|
check_out_dir() {
|
|
if [ "$out_dir" = '' ]
|
|
then
|
|
error_exit 'output directory should be presented'
|
|
fi
|
|
|
|
if ! [ -d "$out_dir" ]
|
|
then
|
|
log "$out_dir directory will be created"
|
|
fi
|
|
}
|
|
|
|
# Function parse_opts parses the options list and validates it's combinations.
|
|
parse_opts() {
|
|
while getopts "C:c:hO:o:rRuUvV" opt "$@"
|
|
do
|
|
case "$opt"
|
|
in
|
|
(C)
|
|
cpu="$OPTARG"
|
|
;;
|
|
(c)
|
|
channel="$OPTARG"
|
|
;;
|
|
(h)
|
|
usage
|
|
;;
|
|
(O)
|
|
os="$OPTARG"
|
|
;;
|
|
(o)
|
|
out_dir="$OPTARG"
|
|
;;
|
|
(R)
|
|
reinstall='0'
|
|
;;
|
|
(U)
|
|
uninstall='0'
|
|
;;
|
|
(r)
|
|
reinstall='1'
|
|
;;
|
|
(u)
|
|
uninstall='1'
|
|
;;
|
|
(V)
|
|
verbose='0'
|
|
;;
|
|
(v)
|
|
verbose='1'
|
|
;;
|
|
(*)
|
|
log "bad option $OPTARG"
|
|
|
|
usage
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if [ "$uninstall" -eq '1' ] && [ "$reinstall" -eq '1' ]
|
|
then
|
|
error_exit 'the -r and -u options are mutually exclusive'
|
|
fi
|
|
}
|
|
|
|
# Function set_channel sets the channel if needed and validates the value.
|
|
set_channel() {
|
|
# Validate.
|
|
case "$channel"
|
|
in
|
|
('development'|'edge'|'beta'|'release')
|
|
# All is well, go on.
|
|
;;
|
|
(*)
|
|
error_exit \
|
|
"invalid channel '$channel'
|
|
supported values are 'development', 'edge', 'beta', and 'release'"
|
|
;;
|
|
esac
|
|
|
|
# Log.
|
|
log "channel: $channel"
|
|
}
|
|
|
|
# Function set_os sets the os if needed and validates the value.
|
|
set_os() {
|
|
# Set if needed.
|
|
if [ "$os" = '' ]
|
|
then
|
|
os="$( uname -s )"
|
|
case "$os"
|
|
in
|
|
('Darwin')
|
|
os='darwin'
|
|
;;
|
|
('FreeBSD')
|
|
os='freebsd'
|
|
;;
|
|
('Linux')
|
|
os='linux'
|
|
;;
|
|
('OpenBSD')
|
|
os='openbsd'
|
|
;;
|
|
esac
|
|
fi
|
|
|
|
# Validate.
|
|
case "$os"
|
|
in
|
|
('darwin'|'freebsd'|'linux'|'openbsd')
|
|
# All right, go on.
|
|
;;
|
|
(*)
|
|
error_exit "unsupported operating system: '$os'"
|
|
;;
|
|
esac
|
|
|
|
# Log.
|
|
log "operating system: $os"
|
|
}
|
|
|
|
# Function set_cpu sets the cpu if needed and validates the value.
|
|
set_cpu() {
|
|
# Set if needed.
|
|
if [ "$cpu" = '' ]
|
|
then
|
|
cpu="$( uname -m )"
|
|
case "$cpu"
|
|
in
|
|
('x86_64'|'x86-64'|'x64'|'amd64')
|
|
cpu='amd64'
|
|
;;
|
|
('i386'|'i486'|'i686'|'i786'|'x86')
|
|
cpu='386'
|
|
;;
|
|
('armv5l')
|
|
cpu='armv5'
|
|
;;
|
|
('armv6l')
|
|
cpu='armv6'
|
|
;;
|
|
('armv7l' | 'armv8l')
|
|
cpu='armv7'
|
|
;;
|
|
('aarch64'|'arm64')
|
|
cpu='arm64'
|
|
;;
|
|
('mips'|'mips64')
|
|
if is_little_endian
|
|
then
|
|
cpu="${cpu}le"
|
|
fi
|
|
cpu="${cpu}_softfloat"
|
|
;;
|
|
esac
|
|
fi
|
|
|
|
# Validate.
|
|
case "$cpu"
|
|
in
|
|
('amd64'|'386'|'armv5'|'armv6'|'armv7'|'arm64')
|
|
# All right, go on.
|
|
;;
|
|
('mips64le_softfloat'|'mips64_softfloat'|'mipsle_softfloat'|'mips_softfloat')
|
|
# That's right too.
|
|
;;
|
|
(*)
|
|
error_exit "unsupported cpu type: $cpu"
|
|
;;
|
|
esac
|
|
|
|
# Log.
|
|
log "cpu type: $cpu"
|
|
}
|
|
|
|
# Function fix_darwin performs some configuration changes for macOS if needed.
|
|
#
|
|
# TODO(a.garipov): Remove after the final v0.107.0 release.
|
|
#
|
|
# See https://github.com/AdguardTeam/AdGuardHome/issues/2443.
|
|
fix_darwin() {
|
|
if [ "$os" != 'darwin' ]
|
|
then
|
|
return 0
|
|
fi
|
|
|
|
if [ "$cpu" = 'arm64' ]
|
|
then
|
|
case "$channel"
|
|
in
|
|
('beta'|'development'|'edge')
|
|
# Everything is fine, we have Apple Silicon support on
|
|
# these channels.
|
|
;;
|
|
('release')
|
|
cpu='amd64'
|
|
log "use $cpu build on Mac M1 until the native ARM support is added."
|
|
;;
|
|
(*)
|
|
# Generally shouldn't happen, since the release channel
|
|
# has already been validated.
|
|
error_exit "invalid channel '$channel'"
|
|
;;
|
|
esac
|
|
fi
|
|
|
|
# Set the package extension.
|
|
pkg_ext='zip'
|
|
|
|
# It is important to install AdGuard Home into the /Applications
|
|
# directory on macOS. Otherwise, it may not grant enough privileges to
|
|
# the AdGuard Home.
|
|
out_dir='/Applications'
|
|
}
|
|
|
|
# Function fix_freebsd performs some fixes to make it work on FreeBSD.
|
|
fix_freebsd() {
|
|
if ! [ "$os" = 'freebsd' ]
|
|
then
|
|
return 0
|
|
fi
|
|
|
|
rcd='/usr/local/etc/rc.d'
|
|
readonly rcd
|
|
|
|
if ! [ -d "$rcd" ]
|
|
then
|
|
mkdir "$rcd"
|
|
fi
|
|
}
|
|
|
|
# Function set_sudo_cmd sets the appropriate command to run a command under
|
|
# superuser privileges.
|
|
set_sudo_cmd() {
|
|
case "$os"
|
|
in
|
|
('openbsd')
|
|
sudo_cmd='doas'
|
|
;;
|
|
('darwin'|'freebsd'|'linux')
|
|
# Go on and use the default, sudo.
|
|
;;
|
|
(*)
|
|
error_exit "unsupported operating system: '$os'"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# Function configure sets the script's configuration.
|
|
configure() {
|
|
set_channel
|
|
set_os
|
|
set_cpu
|
|
fix_darwin
|
|
set_sudo_cmd
|
|
check_out_dir
|
|
|
|
pkg_name="AdGuardHome_${os}_${cpu}.${pkg_ext}"
|
|
url="https://static.adguard.com/adguardhome/${channel}/${pkg_name}"
|
|
agh_dir="${out_dir}/AdGuardHome"
|
|
readonly pkg_name url agh_dir
|
|
|
|
log "AdGuard Home will be installed into $agh_dir"
|
|
}
|
|
|
|
# Function is_root checks for root privileges to be granted.
|
|
is_root() {
|
|
if [ "$( id -u )" -eq '0' ]
|
|
then
|
|
log 'script is executed with root privileges'
|
|
|
|
return 0
|
|
fi
|
|
|
|
if is_command "$sudo_cmd"
|
|
then
|
|
log 'note that AdGuard Home requires root privileges to install using this script'
|
|
|
|
return 1
|
|
fi
|
|
|
|
error_exit \
|
|
'root privileges are required to install AdGuard Home using this script
|
|
please, restart it with root privileges'
|
|
}
|
|
|
|
# Function rerun_with_root downloads the script, runs it with root privileges,
|
|
# and exits the current script. It passes the necessary configuration of the
|
|
# current script to the child script.
|
|
#
|
|
# TODO(e.burkov): Try to avoid restarting.
|
|
rerun_with_root() {
|
|
script_url=\
|
|
'https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/scripts/install.sh'
|
|
readonly script_url
|
|
|
|
r='-R'
|
|
if [ "$reinstall" -eq '1' ]
|
|
then
|
|
r='-r'
|
|
fi
|
|
|
|
u='-U'
|
|
if [ "$uninstall" -eq '1' ]
|
|
then
|
|
u='-u'
|
|
fi
|
|
|
|
v='-V'
|
|
if [ "$verbose" -eq '1' ]
|
|
then
|
|
v='-v'
|
|
fi
|
|
|
|
readonly r u v
|
|
|
|
log 'restarting with root privileges'
|
|
|
|
# Group curl together with an echo, so that if curl fails before
|
|
# producing any output, the echo prints an exit command for the
|
|
# following shell to execute to prevent it from getting an empty input
|
|
# and exiting with a zero code in that case.
|
|
{ curl -L -S -s "$script_url" || echo 'exit 1'; }\
|
|
| $sudo_cmd sh -s -- -c "$channel" -C "$cpu" -O "$os" -o "$out_dir" "$r" "$u" "$v"
|
|
|
|
# Exit the script. Since if the code of the previous pipeline is
|
|
# non-zero, the execution won't reach this point thanks to set -e, exit
|
|
# with zero.
|
|
exit 0
|
|
}
|
|
|
|
# Function download downloads the file from the URL and saves it to the
|
|
# specified filepath.
|
|
download() {
|
|
log "downloading package from $url -> $pkg_name"
|
|
|
|
if ! curl -s "$url" --output "$pkg_name"
|
|
then
|
|
error_exit "cannot download the package from $url into $pkg_name"
|
|
fi
|
|
|
|
log "successfully downloaded $pkg_name"
|
|
}
|
|
|
|
# Function unpack unpacks the passed archive depending on it's extension.
|
|
unpack() {
|
|
log "unpacking package from $pkg_name into $out_dir"
|
|
if ! mkdir -p "$out_dir"
|
|
then
|
|
error_exit "cannot create directory $out_dir"
|
|
fi
|
|
|
|
case "$pkg_ext"
|
|
in
|
|
('zip')
|
|
unzip "$pkg_name" -d "$out_dir"
|
|
;;
|
|
('tar.gz')
|
|
tar -C "$out_dir" -f "$pkg_name" -x -z
|
|
;;
|
|
(*)
|
|
error_exit "unexpected package extension: '$pkg_ext'"
|
|
;;
|
|
esac
|
|
|
|
log "successfully unpacked, contents: $( echo; ls -l -A "$agh_dir" )"
|
|
|
|
rm "$pkg_name"
|
|
}
|
|
|
|
# Function handle_existing detects the existing AGH installation and takes care
|
|
# of removing it if needed.
|
|
handle_existing() {
|
|
if ! [ -d "$agh_dir" ]
|
|
then
|
|
log 'no need to uninstall'
|
|
|
|
if [ "$uninstall" -eq '1' ]
|
|
then
|
|
exit 0
|
|
fi
|
|
|
|
return 0
|
|
fi
|
|
|
|
if [ "$( ls -1 -A $agh_dir )" != '' ]
|
|
then
|
|
log 'the existing AdGuard Home installation is detected'
|
|
|
|
if [ "$reinstall" -ne '1' ] && [ "$uninstall" -ne '1' ]
|
|
then
|
|
error_exit \
|
|
"to reinstall/uninstall the AdGuard Home using this script specify one of the '-r' or '-u' flags"
|
|
fi
|
|
|
|
# TODO(e.burkov): Remove the stop once v0.107.1 released.
|
|
if ( cd "$agh_dir" && ! ./AdGuardHome -s stop || ! ./AdGuardHome -s uninstall )
|
|
then
|
|
# It doesn't terminate the script since it is possible
|
|
# that AGH just not installed as service but appearing
|
|
# in the directory.
|
|
log "cannot uninstall AdGuard Home from $agh_dir"
|
|
fi
|
|
|
|
rm -r "$agh_dir"
|
|
|
|
log 'AdGuard Home was successfully uninstalled'
|
|
fi
|
|
|
|
if [ "$uninstall" -eq '1' ]
|
|
then
|
|
exit 0
|
|
fi
|
|
}
|
|
|
|
# Function install_service tries to install AGH as service.
|
|
install_service() {
|
|
# Installing as root is required at least on FreeBSD.
|
|
#
|
|
# TODO(e.burkov): Think about AGH's output suppressing with no verbose
|
|
# flag.
|
|
if ( cd "$agh_dir" && $sudo_cmd ./AdGuardHome -s install )
|
|
then
|
|
return 0
|
|
fi
|
|
|
|
log "installation failed, removing $agh_dir"
|
|
|
|
rm -r "$agh_dir"
|
|
|
|
# Some devices detected to have armv7 CPU face the compatibility
|
|
# issues with actual armv7 builds. We should try to install the
|
|
# armv5 binary instead.
|
|
#
|
|
# See https://github.com/AdguardTeam/AdGuardHome/issues/2542.
|
|
if [ "$cpu" = 'armv7' ]
|
|
then
|
|
cpu='armv5'
|
|
reinstall='1'
|
|
|
|
log "trying to use $cpu cpu"
|
|
|
|
rerun_with_root
|
|
fi
|
|
|
|
error_exit 'cannot install AdGuardHome as a service'
|
|
}
|
|
|
|
|
|
|
|
# Entrypoint
|
|
|
|
# Set default values of configuration variables.
|
|
channel='release'
|
|
reinstall='0'
|
|
uninstall='0'
|
|
verbose='0'
|
|
cpu=''
|
|
os=''
|
|
out_dir='/opt'
|
|
pkg_ext='tar.gz'
|
|
sudo_cmd='sudo'
|
|
|
|
parse_opts "$@"
|
|
|
|
echo 'starting AdGuard Home installation script'
|
|
|
|
configure
|
|
check_required
|
|
|
|
if ! is_root
|
|
then
|
|
rerun_with_root
|
|
fi
|
|
# Needs rights.
|
|
fix_freebsd
|
|
|
|
handle_existing
|
|
|
|
download
|
|
unpack
|
|
|
|
install_service
|
|
|
|
echo "\
|
|
AdGuard Home is now installed and running
|
|
you can control the service status with the following commands:
|
|
$sudo_cmd ${agh_dir}/AdGuardHome -s start|stop|restart|status|install|uninstall"
|