]> vilimpoc.org git repositories - dotfiles/blob - setup-linux.sh
dotfiles: Linux dev-box provisioning script
[dotfiles] / setup-linux.sh
1 #!/bin/bash
2 #
3 # setup-linux.sh -- install everything needed to build rsync on Ubuntu.
4 #
5 # Tested on Ubuntu 22.04.5 LTS and 26.04 LTS.  Covers both build systems:
6 # the autoconf one (./configure && make) and the CMake one (CMakeLists.txt),
7 # plus the optional feature libraries and the manpage generator.
8 #
9 # Usage:
10 #     ./setup-linux.sh              # install required + optional
11 #     ./setup-linux.sh --minimal    # required only (no ACLs/xattrs/zstd/...)
12 #     ./setup-linux.sh --dry-run    # show what would be installed
13 #
14 # Safe to re-run: apt skips anything already present.
15
16 set -uo pipefail
17
18 MINIMAL=0
19 DRY_RUN=0
20
21 for arg in "$@"; do
22     case "$arg" in
23     --minimal)  MINIMAL=1 ;;
24     --dry-run)  DRY_RUN=1 ;;
25     -h|--help)
26         sed -n '2,17p' "$0" | sed 's/^# \{0,1\}//'
27         exit 0 ;;
28     *)
29         echo "setup-linux.sh: unknown option '$arg' (try --help)" >&2
30         exit 2 ;;
31     esac
32 done
33
34 # ---------------------------------------------------------------- helpers
35
36 red()   { printf '\033[31m%s\033[0m\n' "$*"; }
37 green() { printf '\033[32m%s\033[0m\n' "$*"; }
38 bold()  { printf '\033[1m%s\033[0m\n' "$*"; }
39
40 die() { red "ERROR: $*"; exit 1; }
41
42 # ------------------------------------------------------- sanity checks
43
44 command -v apt-get >/dev/null 2>&1 \
45     || die "no apt-get found -- this script is for Debian/Ubuntu systems."
46
47 DISTRO="unknown"
48 RELEASE="unknown"
49 if [ -r /etc/os-release ]; then
50     # shellcheck disable=SC1091  # runtime file, not available to the linter
51     . /etc/os-release
52     DISTRO="${ID:-unknown}"
53     RELEASE="${VERSION_ID:-unknown}"
54 fi
55
56 bold "rsync build dependencies"
57 echo "  distro ......... ${PRETTY_NAME:-$DISTRO $RELEASE}"
58 echo "  architecture ... $(uname -m)"
59
60 case "$DISTRO" in
61 ubuntu|debian|linuxmint|pop) ;;
62 *)  echo
63     red "Warning: this script targets Ubuntu/Debian; '$DISTRO' is untested."
64     echo "Continuing anyway -- package names may differ."
65     ;;
66 esac
67
68 # apt needs root.  Re-exec under sudo rather than sprinkling it around, so
69 # that a single password prompt covers the whole run.
70 if [ "$(id -u)" -ne 0 ] && [ "$DRY_RUN" -eq 0 ]; then
71     command -v sudo >/dev/null 2>&1 \
72         || die "not running as root and sudo is not installed."
73     echo
74     echo "Re-running under sudo..."
75     # Plain sudo, not "sudo -E": some sudoers configs refuse to preserve the
76     # environment, and the exports below happen in the root instance anyway.
77     exec sudo "$0" "$@"
78 fi
79
80 export DEBIAN_FRONTEND=noninteractive
81 export NEEDRESTART_MODE=a          # don't prompt about restarting services
82
83 # ------------------------------------------------------------- packages
84
85 # Needed to build rsync at all.
86 REQUIRED=(
87     build-essential     # gcc, g++, make, libc headers
88     gawk                # rsync needs a modern awk for its code generators
89     autoconf            # ./configure is generated from configure.ac
90     automake
91     python3             # manpage + header generators, and the test suite
92     cmake               # the CMake build
93     ninja-build         # ...and its default generator
94     git                 # version stamping (git-version.h)
95     pkg-config
96 )
97
98 # Optional: each one switches on an rsync feature.  Missing ones only mean
99 # a smaller feature set, never a failed build.
100 OPTIONAL=(
101     acl libacl1-dev         # --acls  (helper tools also used by the tests)
102     attr libattr1-dev       # --xattrs (likewise)
103     libxxhash-dev           # xxhash checksums (becomes the default)
104     libzstd-dev             # zstd compression (becomes the default)
105     liblz4-dev              # lz4 compression
106     libssl-dev              # OpenSSL MD4/MD5
107     zlib1g-dev              # for cmake -DRSYNC_EXTERNAL_ZLIB=ON
108     libpopt-dev             # for ./configure --with-included-popt=no
109 )
110
111 # Development tooling: not needed to build rsync, but useful when working on
112 # its shell scripts -- including this one, which shellcheck keeps honest.
113 DEVTOOLS=(
114     shellcheck
115 )
116 OPTIONAL+=("${DEVTOOLS[@]}")
117
118 # The manpages need one of two python3 markdown libraries; upstream prefers
119 # cmarkgfm.  Pick whichever this release actually offers.
120 MARKDOWN_PKG=""
121 for p in python3-cmarkgfm python3-commonmark; do
122     cand=$(apt-cache policy "$p" 2>/dev/null | awk '/Candidate:/{print $2}')
123     if [ -n "$cand" ] && [ "$cand" != "(none)" ]; then
124         MARKDOWN_PKG="$p"
125         break
126     fi
127 done
128 if [ -n "$MARKDOWN_PKG" ]; then
129     OPTIONAL+=("$MARKDOWN_PKG")
130 fi
131
132 if [ "$MINIMAL" -eq 1 ]; then
133     OPTIONAL=()
134 fi
135
136 if [ "$DRY_RUN" -eq 1 ]; then
137     echo
138     bold "Would install (required):"
139     printf '  %s\n' "${REQUIRED[@]}"
140     if [ ${#OPTIONAL[@]} -gt 0 ]; then
141         bold "Would install (optional):"
142         printf '  %s\n' "${OPTIONAL[@]}"
143     fi
144     exit 0
145 fi
146
147 # ------------------------------------------------------------- install
148
149 echo
150 bold "Updating package lists..."
151 if ! apt-get update -qq; then
152     red "apt-get update failed -- continuing with the cached lists."
153 fi
154
155 echo
156 bold "Installing required packages..."
157 if ! apt-get install -y "${REQUIRED[@]}"; then
158     # Fall back to one-at-a-time so the failing package is obvious.
159     red "Bulk install failed; retrying individually to find the culprit..."
160     FAILED=()
161     for p in "${REQUIRED[@]}"; do
162         apt-get install -y "$p" || FAILED+=("$p")
163     done
164     [ ${#FAILED[@]} -eq 0 ] || die "could not install: ${FAILED[*]}"
165 fi
166
167 OPT_FAILED=()
168 if [ ${#OPTIONAL[@]} -gt 0 ]; then
169     echo
170     bold "Installing optional packages..."
171     if ! apt-get install -y "${OPTIONAL[@]}"; then
172         red "Bulk install failed; retrying individually..."
173         for p in "${OPTIONAL[@]}"; do
174             apt-get install -y "$p" || OPT_FAILED+=("$p")
175         done
176     fi
177 fi
178
179 # --------------------------------------------------------------- verify
180
181 echo
182 bold "Verifying the toolchain..."
183
184 MISSING=()
185 check_cmd() {
186     if command -v "$1" >/dev/null 2>&1; then
187         printf '  %-16s %s\n' "$1" "$(command -v "$1")"
188     else
189         printf '  %-16s %s\n' "$1" "MISSING"
190         MISSING+=("$1")
191     fi
192 }
193
194 for c in gcc g++ make gawk autoconf automake cmake ninja python3 git; do
195     check_cmd "$c"
196 done
197
198 # Not required to build, so report it without failing the run.
199 if command -v shellcheck >/dev/null 2>&1; then
200     printf '  %-16s %s\n' "shellcheck" "$(command -v shellcheck)"
201 else
202     printf '  %-16s %s\n' "shellcheck" "not installed (optional)"
203 fi
204
205 echo
206 bold "Verifying optional libraries..."
207 check_header() {
208     # $1 = human name, $2 = header path
209     if [ -e "/usr/include/$2" ] || \
210        find /usr/include -maxdepth 3 -name "$(basename "$2")" -print -quit \
211             2>/dev/null | grep -q .; then
212         printf '  %-16s yes\n' "$1"
213     else
214         printf '  %-16s no\n' "$1"
215     fi
216 }
217 check_header acl        sys/acl.h
218 check_header xattr      sys/xattr.h
219 check_header xxhash     xxhash.h
220 check_header zstd       zstd.h
221 check_header lz4        lz4.h
222 check_header openssl    openssl/md5.h
223 check_header zlib       zlib.h
224
225 echo
226 bold "Verifying the manpage generator..."
227 MD_OK=0
228 for m in cmarkgfm commonmark; do
229     if python3 -c "import $m" >/dev/null 2>&1; then
230         echo "  python3 module '$m' importable"
231         MD_OK=1
232         break
233     fi
234 done
235 if [ "$MD_OK" -eq 0 ]; then
236     if [ "$MINIMAL" -eq 1 ]; then
237         echo "  skipped (--minimal); build with ./configure --disable-md2man"
238     else
239         red "  neither cmarkgfm nor commonmark is importable."
240         echo "  Install one with:  python3 -mpip install --user commonmark"
241         echo "  ...or build with:  ./configure --disable-md2man"
242     fi
243 fi
244
245 # ---------------------------------------------------------------- done
246
247 echo
248 if [ ${#MISSING[@]} -ne 0 ]; then
249     red "Missing required tools: ${MISSING[*]}"
250     exit 1
251 fi
252
253 if [ ${#OPT_FAILED[@]} -ne 0 ]; then
254     red "Optional packages that failed to install: ${OPT_FAILED[*]}"
255     echo "The build will still work, with those features disabled."
256 fi
257
258 green "All required build dependencies are installed."
259 cat <<'EOF'
260
261 Build rsync with either build system:
262
263   autoconf:   ./configure && make
264   CMake:      cmake -B build -G Ninja && cmake --build build
265
266 Run the test suite (needs the autoconf build's helper programs):
267
268   make check
269 EOF