Commit | Line | Data |
---|---|---|
f5fdc54d | 1 | #!/bin/sh |
6f4e8693 RW |
2 | # GNU Guix --- Functional package management for GNU |
3 | # Copyright © 2017 sharlatan <sharlatanus@gmail.com> | |
4 | # Copyright © 2018 Ricardo Wurmus <rekado@elephly.net> | |
ea6b1bae | 5 | # Copyright © 2018 Efraim Flashner <efraim@flashner.co.il> |
6f4e8693 RW |
6 | # |
7 | # This file is part of GNU Guix. | |
8 | # | |
9 | # GNU Guix is free software; you can redistribute it and/or modify it | |
10 | # under the terms of the GNU General Public License as published by | |
11 | # the Free Software Foundation; either version 3 of the License, or (at | |
12 | # your option) any later version. | |
13 | # | |
14 | # GNU Guix is distributed in the hope that it will be useful, but | |
15 | # WITHOUT ANY WARRANTY; without even the implied warranty of | |
16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
17 | # GNU General Public License for more details. | |
18 | # | |
19 | # You should have received a copy of the GNU General Public License | |
20 | # along with GNU Guix. If not, see <http://www.gnu.org/licenses/>. | |
21 | ||
f5fdc54d LC |
22 | # We require Bash but for portability we'd rather not use /bin/bash or |
23 | # /usr/bin/env in the shebang, hence this hack. | |
24 | if [ "x$BASH_VERSION" = "x" ] | |
25 | then | |
26 | exec bash "$0" "$@" | |
27 | fi | |
28 | ||
6f4e8693 RW |
29 | set -e |
30 | ||
31 | [ "$UID" -eq 0 ] || { echo "This script must be run as root."; exit 1; } | |
32 | ||
33 | REQUIRE=( | |
34 | "dirname" | |
35 | "readlink" | |
36 | "wget" | |
37 | "gpg" | |
38 | "grep" | |
39 | "which" | |
40 | "sed" | |
41 | "sort" | |
42 | "getent" | |
43 | "mktemp" | |
44 | "rm" | |
45 | "chmod" | |
46 | "uname" | |
47 | "groupadd" | |
48 | "tail" | |
49 | "tr" | |
50 | ) | |
51 | ||
52 | PAS=$'[ \033[32;1mPASS\033[0m ] ' | |
53 | ERR=$'[ \033[31;1mFAIL\033[0m ] ' | |
54 | INF="[ INFO ] " | |
55 | ||
56 | DEBUG=0 | |
3a3e9f2b | 57 | GNU_URL="https://ftp.gnu.org/gnu/guix/" |
6f4e8693 RW |
58 | OPENPGP_SIGNING_KEY_ID="3CE464558A84FDC69DB40CFB090B11993D9AEBB5" |
59 | ||
3cd4447f CM |
60 | # This script needs to know where root's home directory is. However, we |
61 | # cannot simply use the HOME environment variable, since there is no guarantee | |
62 | # that it points to root's home directory. | |
63 | ROOT_HOME="$(echo ~root)" | |
64 | ||
6f4e8693 RW |
65 | # ------------------------------------------------------------------------------ |
66 | #+UTILITIES | |
67 | ||
68 | _err() | |
69 | { # All errors go to stderr. | |
70 | printf "[%s]: %s\n" "$(date +%s.%3N)" "$1" | |
71 | } | |
72 | ||
73 | _msg() | |
74 | { # Default message to stdout. | |
75 | printf "[%s]: %s\n" "$(date +%s.%3N)" "$1" | |
76 | } | |
77 | ||
78 | _debug() | |
79 | { | |
80 | if [ "${DEBUG}" = '1' ]; then | |
81 | printf "[%s]: %s\n" "$(date +%s.%3N)" "$1" | |
82 | fi | |
83 | } | |
84 | ||
85 | ||
86 | chk_require() | |
87 | { # Check that every required command is available. | |
88 | declare -a cmds | |
89 | declare -a warn | |
90 | ||
91 | cmds=(${1}) | |
92 | ||
93 | _debug "--- [ $FUNCNAME ] ---" | |
94 | ||
95 | for c in ${cmds[@]}; do | |
593fe736 | 96 | command -v "$c" &>/dev/null || warn+=("$c") |
6f4e8693 RW |
97 | done |
98 | ||
99 | [ "${#warn}" -ne 0 ] && | |
100 | { _err "${ERR}Missing commands: ${warn[*]}."; | |
101 | return 1; } | |
102 | ||
103 | _msg "${PAS}verification of required commands completed" | |
104 | ||
105 | gpg --list-keys ${OPENPGP_SIGNING_KEY_ID} >/dev/null 2>&1 || ( | |
106 | _err "${ERR}Missing OpenPGP public key. Fetch it with this command:" | |
1fc90e89 | 107 | echo " gpg --keyserver pool.sks-keyservers.net --recv-keys ${OPENPGP_SIGNING_KEY_ID}" |
6f4e8693 RW |
108 | exit 1 |
109 | ) | |
110 | } | |
111 | ||
112 | chk_term() | |
113 | { # Check for ANSI terminal for color printing. | |
114 | local ansi_term | |
115 | ||
116 | if [ -t 2 ]; then | |
117 | if [ "${TERM+set}" = 'set' ]; then | |
118 | case "$TERM" in | |
119 | xterm*|rxvt*|urxvt*|linux*|vt*|eterm*|screen*) | |
120 | ansi_term=true | |
121 | ;; | |
122 | *) | |
123 | ansi_term=false | |
124 | ERR="[ FAIL ] " | |
125 | PAS="[ PASS ] " | |
126 | ;; | |
127 | esac | |
128 | fi | |
129 | fi | |
130 | } | |
131 | ||
132 | chk_init_sys() | |
133 | { # Return init system type name. | |
134 | if [[ $(/sbin/init --version 2>/dev/null) =~ upstart ]]; then | |
135 | _msg "${INF}init system is: upstart" | |
136 | INIT_SYS="upstart" | |
137 | return 0 | |
138 | elif [[ $(systemctl) =~ -\.mount ]]; then | |
139 | _msg "${INF}init system is: systemd" | |
140 | INIT_SYS="systemd" | |
141 | return 0 | |
142 | elif [[ -f /etc/init.d/cron && ! -h /etc/init.d/cron ]]; then | |
143 | _msg "${INF}init system is: sysv-init" | |
144 | INIT_SYS="sysv-init" | |
145 | return 0 | |
146 | else | |
147 | INIT_SYS="NA" | |
148 | _err "${ERR}Init system could not be detected." | |
149 | fi | |
150 | } | |
151 | ||
152 | chk_sys_arch() | |
153 | { # Check for operating system and architecture type. | |
154 | local os | |
155 | local arch | |
156 | ||
157 | os="$(uname -s)" | |
158 | arch="$(uname -m)" | |
159 | ||
160 | case "$arch" in | |
161 | i386 | i486 | i686 | i786 | x86) | |
162 | local arch=i686 | |
163 | ;; | |
164 | x86_64 | x86-64 | x64 | amd64) | |
165 | local arch=x86_64 | |
166 | ;; | |
ea6b1bae EF |
167 | aarch64) |
168 | local arch=aarch64 | |
169 | ;; | |
2510bd87 LC |
170 | armv7l) |
171 | local arch=armhf | |
172 | ;; | |
6f4e8693 RW |
173 | *) |
174 | _err "${ERR}Unsupported CPU type: ${arch}" | |
175 | exit 1 | |
176 | esac | |
177 | ||
178 | case "$os" in | |
179 | Linux | linux) | |
180 | local os=linux | |
181 | ;; | |
182 | *) | |
183 | _err "${ERR}Your operation system (${os}) is not supported." | |
184 | exit 1 | |
185 | esac | |
186 | ||
187 | ARCH_OS="${arch}-${os}" | |
188 | } | |
189 | ||
190 | # ------------------------------------------------------------------------------ | |
191 | #+MAIN | |
192 | ||
193 | guix_get_bin_list() | |
194 | { # Scan GNU archive and save list of binaries | |
195 | local gnu_url="$1" | |
196 | local -a bin_ver_ls | |
197 | local latest_ver | |
198 | local default_ver | |
199 | ||
200 | _debug "--- [ $FUNCNAME ] ---" | |
201 | ||
202 | # Filter only version and architecture | |
203 | bin_ver_ls=("$(wget -qO- "$gnu_url" \ | |
204 | | sed -n -e 's/.*guix-binary-\([0-9.]*\)\..*.tar.xz.*/\1/p' \ | |
205 | | sort -Vu)") | |
206 | ||
207 | latest_ver="$(echo "$bin_ver_ls" \ | |
208 | | grep -oP "([0-9]{1,2}\.){2}[0-9]{1,2}" \ | |
209 | | tail -n1)" | |
210 | ||
211 | default_ver="guix-binary-${latest_ver}.${ARCH_OS}" | |
212 | ||
213 | if [[ "${#bin_ver_ls}" -ne "0" ]]; then | |
214 | _msg "${PAS}Release for your system: ${default_ver}" | |
215 | else | |
216 | _err "${ERR}Could not obtain list of Guix releases." | |
217 | exit 1 | |
218 | fi | |
219 | ||
220 | # Use default to download according to the list and local ARCH_OS. | |
221 | BIN_VER="$default_ver" | |
222 | } | |
223 | ||
224 | guix_get_bin() | |
225 | { # Download and verify binary package. | |
226 | local url="$1" | |
227 | local bin_ver="$2" | |
228 | local dl_path="$3" | |
229 | ||
230 | _debug "--- [ $FUNCNAME ] ---" | |
231 | ||
232 | _msg "${INF}Downloading Guix release archive" | |
233 | ||
234 | wget --help | grep -q '\--show-progress' && \ | |
235 | _PROGRESS_OPT="-q --show-progress" || _PROGRESS_OPT="" | |
236 | wget $_PROGRESS_OPT -P "$dl_path" "${url}/${bin_ver}.tar.xz" "${url}/${bin_ver}.tar.xz.sig" | |
237 | ||
238 | if [[ "$?" -eq 0 ]]; then | |
239 | _msg "${PAS}download completed." | |
240 | else | |
241 | _err "${ERR}could not download ${url}/${bin_ver}.tar.xz." | |
242 | exit 1 | |
243 | fi | |
244 | ||
245 | pushd $dl_path >/dev/null | |
246 | gpg --verify "${bin_ver}.tar.xz.sig" >/dev/null 2>&1 | |
247 | if [[ "$?" -eq 0 ]]; then | |
248 | _msg "${PAS}Signature is valid." | |
249 | popd >/dev/null | |
250 | else | |
251 | _err "${ERR}could not verify the signature." | |
252 | exit 1 | |
253 | fi | |
254 | } | |
255 | ||
256 | sys_create_store() | |
257 | { # Unpack and install /gnu/store and /var/guix | |
258 | local pkg="$1" | |
259 | local tmp_path="$2" | |
260 | ||
261 | _debug "--- [ $FUNCNAME ] ---" | |
262 | ||
263 | cd "$tmp_path" | |
264 | tar --warning=no-timestamp \ | |
265 | --extract \ | |
266 | --file "$pkg" && | |
267 | _msg "${PAS}unpacked archive" | |
268 | ||
269 | if [[ -e "/var/guix" || -e "/gnu" ]]; then | |
270 | _err "${ERR}A previous Guix installation was found. Refusing to overwrite." | |
271 | exit 1 | |
272 | else | |
273 | _msg "${INF}Installing /var/guix and /gnu..." | |
274 | mv "${tmp_path}/var/guix" /var/ | |
275 | mv "${tmp_path}/gnu" / | |
276 | fi | |
277 | ||
278 | _msg "${INF}Linking the root user's profile" | |
e9926f80 LC |
279 | mkdir -p "${ROOT_HOME}/.config/guix" |
280 | ln -sf /var/guix/profiles/per-user/root/current-guix \ | |
281 | "${ROOT_HOME}/.config/guix/current" | |
6f4e8693 | 282 | |
e9926f80 | 283 | GUIX_PROFILE="${ROOT_HOME}/.config/guix/current" |
6f4e8693 | 284 | source "${GUIX_PROFILE}/etc/profile" |
e9926f80 | 285 | _msg "${PAS}activated root profile at ${ROOT_HOME}/.config/guix/current" |
6f4e8693 RW |
286 | } |
287 | ||
288 | sys_create_build_user() | |
289 | { # Create the group and user accounts for build users. | |
290 | ||
291 | _debug "--- [ $FUNCNAME ] ---" | |
292 | ||
293 | if [ $(getent group guixbuild) ]; then | |
294 | _msg "${INF}group guixbuild exists" | |
295 | else | |
296 | groupadd --system guixbuild | |
297 | _msg "${PAS}group <guixbuild> created" | |
298 | fi | |
299 | ||
300 | for i in $(seq -w 1 10); do | |
301 | if id "guixbuilder${i}" &>/dev/null; then | |
302 | _msg "${INF}user is already in the system, reset" | |
303 | usermod -g guixbuild -G guixbuild \ | |
304 | -d /var/empty -s "$(which nologin)" \ | |
305 | -c "Guix build user $i" \ | |
306 | "guixbuilder${i}"; | |
307 | else | |
308 | useradd -g guixbuild -G guixbuild \ | |
309 | -d /var/empty -s "$(which nologin)" \ | |
310 | -c "Guix build user $i" --system \ | |
311 | "guixbuilder${i}"; | |
312 | _msg "${PAS}user added <guixbuilder${i}>" | |
313 | fi | |
314 | done | |
315 | } | |
316 | ||
317 | sys_enable_guix_daemon() | |
318 | { # Run the daemon, and set it to automatically start on boot. | |
319 | ||
320 | local info_path | |
321 | local local_bin | |
322 | local var_guix | |
323 | ||
324 | _debug "--- [ $FUNCNAME ] ---" | |
325 | ||
326 | info_path="/usr/local/share/info" | |
327 | local_bin="/usr/local/bin" | |
e9926f80 | 328 | var_guix="/var/guix/profiles/per-user/root/current-guix" |
6f4e8693 RW |
329 | |
330 | case "$INIT_SYS" in | |
331 | upstart) | |
332 | { initctl reload-configuration; | |
e9926f80 | 333 | cp "${ROOT_HOME}/.config/guix/current/lib/upstart/system/guix-daemon.conf" \ |
6f4e8693 RW |
334 | /etc/init/ && |
335 | start guix-daemon; } && | |
336 | _msg "${PAS}enabled Guix daemon via upstart" | |
337 | ;; | |
338 | systemd) | |
e9926f80 | 339 | { cp "${ROOT_HOME}/.config/guix/current/lib/systemd/system/guix-daemon.service" \ |
6f4e8693 RW |
340 | /etc/systemd/system/; |
341 | chmod 664 /etc/systemd/system/guix-daemon.service; | |
342 | systemctl daemon-reload && | |
343 | systemctl start guix-daemon && | |
344 | systemctl enable guix-daemon; } && | |
345 | _msg "${PAS}enabled Guix daemon via systemd" | |
346 | ;; | |
347 | NA|*) | |
348 | _msg "${ERR}unsupported init system; run the daemon manually:" | |
e9926f80 | 349 | echo " ${ROOT_HOME}/.config/guix/current/bin/guix-daemon --build-users-group=guixbuild" |
6f4e8693 RW |
350 | ;; |
351 | esac | |
352 | ||
353 | _msg "${INF}making the guix command available to other users" | |
354 | ||
355 | [ -e "$local_bin" ] || mkdir -p "$local_bin" | |
356 | ln -sf "${var_guix}/bin/guix" "$local_bin" | |
357 | ||
358 | [ -e "$info_path" ] || mkdir -p "$info_path" | |
359 | for i in ${var_guix}/share/info/*; do | |
360 | ln -sf "$i" "$info_path" | |
361 | done | |
362 | } | |
363 | ||
364 | sys_authorize_build_farms() | |
365 | { # authorize the public keys of the two build farms | |
366 | while true; do | |
367 | read -p "Permit downloading pre-built package binaries from the project's build farms? (yes/no) " yn | |
368 | case $yn in | |
e9926f80 | 369 | [Yy]*) guix archive --authorize < "${ROOT_HOME}/.config/guix/current/share/guix/hydra.gnu.org.pub" && |
6f4e8693 | 370 | _msg "${PAS}Authorized public key for hydra.gnu.org"; |
4a0b87f0 LC |
371 | guix archive --authorize < "${ROOT_HOME}/.config/guix/current/share/guix/ci.guix.info.pub" && |
372 | _msg "${PAS}Authorized public key for ci.guix.info"; | |
6f4e8693 RW |
373 | break;; |
374 | [Nn]*) _msg "${INF}Skipped authorizing build farm public keys" | |
375 | break;; | |
376 | *) _msg "Please answer yes or no."; | |
377 | esac | |
378 | done | |
379 | } | |
380 | ||
381 | welcome() | |
382 | { | |
383 | cat<<"EOF" | |
384 | ░░░ ░░░ | |
385 | ░░▒▒░░░░░░░░░ ░░░░░░░░░▒▒░░ | |
386 | ░░▒▒▒▒▒░░░░░░░ ░░░░░░░▒▒▒▒▒░ | |
387 | ░▒▒▒░░▒▒▒▒▒ ░░░░░░░▒▒░ | |
388 | ░▒▒▒▒░ ░░░░░░ | |
389 | ▒▒▒▒▒ ░░░░░░ | |
390 | ▒▒▒▒▒ ░░░░░ | |
391 | ░▒▒▒▒▒ ░░░░░ | |
392 | ▒▒▒▒▒ ░░░░░ | |
393 | ▒▒▒▒▒ ░░░░░ | |
394 | ░▒▒▒▒▒░░░░░ | |
395 | ▒▒▒▒▒▒░░░ | |
396 | ▒▒▒▒▒▒░ | |
397 | _____ _ _ _ _ _____ _ | |
398 | / ____| \ | | | | | / ____| (_) | |
399 | | | __| \| | | | | | | __ _ _ ___ __ | |
400 | | | |_ | . ' | | | | | | |_ | | | | \ \/ / | |
401 | | |__| | |\ | |__| | | |__| | |_| | |> < | |
402 | \_____|_| \_|\____/ \_____|\__,_|_/_/\_\ | |
403 | ||
404 | This script installs GNU Guix on your system | |
405 | ||
406 | https://www.gnu.org/software/guix/ | |
407 | EOF | |
408 | echo -n "Press return to continue..." | |
409 | read -r ANSWER | |
410 | } | |
411 | ||
412 | main() | |
413 | { | |
414 | local tmp_path | |
415 | welcome | |
416 | ||
417 | _msg "Starting installation ($(date))" | |
418 | ||
419 | chk_term | |
420 | chk_require "${REQUIRE[*]}" | |
421 | chk_init_sys | |
422 | chk_sys_arch | |
423 | ||
424 | _msg "${INF}system is ${ARCH_OS}" | |
425 | ||
426 | tmp_path="$(mktemp -t -d guix.XXX)" | |
427 | ||
428 | guix_get_bin_list "${GNU_URL}" | |
429 | guix_get_bin "${GNU_URL}" "${BIN_VER}" "$tmp_path" | |
430 | ||
431 | sys_create_store "${BIN_VER}.tar.xz" "${tmp_path}" | |
432 | sys_create_build_user | |
433 | sys_enable_guix_daemon | |
434 | sys_authorize_build_farms | |
435 | ||
436 | _msg "${INF}cleaning up ${tmp_path}" | |
437 | rm -r "${tmp_path}" | |
438 | ||
439 | _msg "${PAS}Guix has successfully been installed!" | |
440 | _msg "${INF}Run 'info guix' to read the manual." | |
441 | } | |
442 | ||
443 | main "$@" |