build_archiso.sh 7.73 KB
Newer Older
1
2
#!/usr/bin/env bash
#
David Runge's avatar
David Runge committed
3
4
# This script is run within a virtual environment to build the available archiso profiles and their available build
# modes and create checksum files for the resulting images.
5
# The script needs to be run as root and assumes $PWD to be the root of the repository.
David Runge's avatar
David Runge committed
6
7
8
#
# Dependencies:
# * all archiso dependencies
David Runge's avatar
David Runge committed
9
10
11
# * coreutils
# * gnupg
# * openssl
David Runge's avatar
David Runge committed
12
13
14
15
16
17
18
# * zsync
#
# $1: profile
# $2: buildmode

set -euo pipefail
shopt -s extglob
19
20
21

readonly orig_pwd="${PWD}"
readonly output="${orig_pwd}/output"
David Runge's avatar
David Runge committed
22
readonly tmpdir_base="${orig_pwd}/tmp"
David Runge's avatar
David Runge committed
23
24
25
26
readonly profile="${1}"
readonly buildmode="${2}"
readonly install_dir="arch"

27
tmpdir=""
David Runge's avatar
David Runge committed
28
tmpdir="$(mktemp --dry-run --directory --tmpdir="${tmpdir_base}")"
29
gnupg_homedir=""
David Runge's avatar
David Runge committed
30
31
32
codesigning_dir=""
codesigning_cert=""
codesigning_key=""
33
pgp_key_id=""
34

David Runge's avatar
David Runge committed
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
print_section_start() {
  # gitlab collapsible sections start: https://docs.gitlab.com/ee/ci/jobs/#custom-collapsible-sections
  local _section _title
  _section="${1}"
  _title="${2}"

  printf "\e[0Ksection_start:%(%s)T:%s\r\e[0K%s\n" '-1' "${_section}" "${_title}"
}

print_section_end() {
  # gitlab collapsible sections end: https://docs.gitlab.com/ee/ci/jobs/#custom-collapsible-sections
  local _section
  _section="${1}"

  printf "\e[0Ksection_end:%(%s)T:%s\r\e[0K\n" '-1' "${_section}"
}

52
53
cleanup() {
  # clean up temporary directories
David Runge's avatar
David Runge committed
54
  print_section_start "cleanup" "Cleaning up temporary directory"
David Runge's avatar
David Runge committed
55

David Runge's avatar
David Runge committed
56
57
  if [ -n "${tmpdir_base:-}" ]; then
    rm -fr "${tmpdir_base}"
58
  fi
David Runge's avatar
David Runge committed
59

David Runge's avatar
David Runge committed
60
  print_section_end "cleanup"
61
62
63
}

create_checksums() {
David Runge's avatar
David Runge committed
64
65
  # create checksums for files
  # $@: files
David Runge's avatar
David Runge committed
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
  local _file_path _file_name _current_pwd
  _current_pwd="${PWD}"

  print_section_start "checksums" "Creating checksums"

  for _file_path in "$@"; do
    cd "$(dirname "${_file_path}")"
    _file_name="$(basename "${_file_path}")"
    b2sum "${_file_name}" > "${_file_name}.b2"
    md5sum "${_file_name}" > "${_file_name}.md5"
    sha1sum "${_file_name}" > "${_file_name}.sha1"
    sha256sum "${_file_name}" > "${_file_name}.sha256"
    sha512sum "${_file_name}" > "${_file_name}.sha512"
    ls -lah "${_file_name}."{b2,md5,sha{1,256,512}}
    cat "${_file_name}."{b2,md5,sha{1,256,512}}
David Runge's avatar
David Runge committed
81
  done
David Runge's avatar
David Runge committed
82
  cd "${_current_pwd}"
David Runge's avatar
David Runge committed
83

David Runge's avatar
David Runge committed
84
  print_section_end "checksums"
85
86
}

87
create_zsync_delta() {
David Runge's avatar
David Runge committed
88
89
90
91
  # create zsync control files for files
  # $@: files
  local _file

David Runge's avatar
David Runge committed
92
93
  print_section_start "zsync_delta" "Creating zsync delta"

David Runge's avatar
David Runge committed
94
95
96
  for _file in "$@"; do
    if [[ "${buildmode}" == "bootstrap" ]]; then
      # zsyncmake fails on 'too long between blocks' with default block size on bootstrap image
David Runge's avatar
David Runge committed
97
      zsyncmake -v -b 512 -C -u "${_file##*/}" -o "${_file}".zsync "${_file}"
David Runge's avatar
David Runge committed
98
    else
David Runge's avatar
David Runge committed
99
      zsyncmake -v -C -u "${_file##*/}" -o "${_file}".zsync "${_file}"
David Runge's avatar
David Runge committed
100
101
102
    fi
  done

David Runge's avatar
David Runge committed
103
  print_section_end "zsync_delta"
104
105
}

106
create_metrics() {
107
  local _metrics="${output}/metrics.txt"
108
  # create metrics
David Runge's avatar
David Runge committed
109
  print_section_start "metrics" "Creating metrics"
David Runge's avatar
David Runge committed
110

111
  {
David Runge's avatar
David Runge committed
112
113
114
115
116
    # create metrics based on buildmode
    case "${buildmode}" in
      iso)
        printf 'image_size_mebibytes{image="%s"} %s\n' \
          "${profile}" \
117
          "$(du -m -- "${output}/"*.iso | cut -f1)"
David Runge's avatar
David Runge committed
118
119
        printf 'package_count{image="%s"} %s\n' \
          "${profile}" \
120
121
          "$(sort -u -- "${tmpdir}/iso/"*/pkglist.*.txt | wc -l)"
        if [[ -e "${tmpdir}/efiboot.img" ]]; then
David Runge's avatar
David Runge committed
122
123
          printf 'eltorito_efi_image_size_mebibytes{image="%s"} %s\n' \
            "${profile}" \
124
            "$(du -m -- "${tmpdir}/efiboot.img" | cut -f1)"
David Runge's avatar
David Runge committed
125
126
127
128
        fi
        # shellcheck disable=SC2046
        # shellcheck disable=SC2183
        printf 'initramfs_size_mebibytes{image="%s",initramfs="%s"} %s\n' \
129
          $(du -m -- "${tmpdir}/iso/"*/boot/**/initramfs*.img | \
David Runge's avatar
David Runge committed
130
131
132
133
134
135
136
137
138
139
140
            awk -v profile="${profile}" \
            'function basename(file) {
              sub(".*/", "", file)
              return file
            }
            { print profile, basename($2), $1 }'
          )
        ;;
      netboot)
        printf 'netboot_size_mebibytes{image="%s"} %s\n' \
          "${profile}" \
141
          "$(du -m -- "${output}/${install_dir}/" | tail -n1 | cut -f1)"
David Runge's avatar
David Runge committed
142
143
        printf 'netboot_package_count{image="%s"} %s\n' \
          "${profile}" \
144
          "$(sort -u -- "${tmpdir}/iso/"*/pkglist.*.txt | wc -l)"
David Runge's avatar
David Runge committed
145
146
147
148
        ;;
      bootstrap)
        printf 'bootstrap_size_mebibytes{image="%s"} %s\n' \
          "${profile}" \
149
          "$(du -m -- "${output}/"*.tar*(.gz|.xz|.zst) | cut -f1)"
David Runge's avatar
David Runge committed
150
151
        printf 'bootstrap_package_count{image="%s"} %s\n' \
          "${profile}" \
152
          "$(sort -u -- "${tmpdir}/"*/bootstrap/root.*/pkglist.*.txt | wc -l)"
David Runge's avatar
David Runge committed
153
154
        ;;
    esac
David Runge's avatar
David Runge committed
155
156
157
  } > "${_metrics}"
  ls -lah "${_metrics}"
  cat "${_metrics}"
David Runge's avatar
David Runge committed
158

David Runge's avatar
David Runge committed
159
  print_section_end "metrics"
160
161
}

David Runge's avatar
David Runge committed
162
create_ephemeral_pgp_key() {
163
  # create an ephemeral PGP key for signing the rootfs image
David Runge's avatar
David Runge committed
164
  print_section_start "ephemeral_pgp_key" "Creating ephemeral PGP key"
David Runge's avatar
David Runge committed
165

166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
  gnupg_homedir="$tmpdir/.gnupg"
  mkdir -p "${gnupg_homedir}"
  chmod 700 "${gnupg_homedir}"

  cat << __EOF__ > "${gnupg_homedir}"/gpg.conf
quiet
batch
no-tty
no-permission-warning
export-options no-export-attributes,export-clean
list-options no-show-keyring
armor
no-emit-version
__EOF__

  gpg --homedir "${gnupg_homedir}" --gen-key <<EOF
%echo Generating ephemeral Arch Linux release engineering key pair...
Key-Type: default
Key-Length: 3072
Key-Usage: sign
Name-Real: Arch Linux Release Engineering
Name-Comment: Ephemeral Signing Key
Name-Email: arch-releng@lists.archlinux.org
Expire-Date: 0
%no-protection
%commit
%echo Done
EOF

  pgp_key_id="$(
    gpg --homedir "${gnupg_homedir}" \
        --list-secret-keys \
        --with-colons \
        | awk -F':' '{if($1 ~ /sec/){ print $5 }}'
  )"
David Runge's avatar
David Runge committed
201

202
203
  pgp_sender="Arch Linux Release Engineering (Ephemeral Signing Key) <arch-releng@lists.archlinux.org>"

David Runge's avatar
David Runge committed
204
  print_section_end "ephemeral_pgp_key"
David Runge's avatar
David Runge committed
205
206
207
208
}

create_ephemeral_codesigning_key() {
  # create ephemeral certificates used for codesigning
David Runge's avatar
David Runge committed
209
  print_section_start "ephemeral_codesigning_key" "Creating ephemeral codesigning key"
David Runge's avatar
David Runge committed
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230

  codesigning_dir="${tmpdir}/.codesigning/"
  local codesigning_conf="${codesigning_dir}/openssl.cnf"
  local codesigning_subj="/C=DE/ST=Berlin/L=Berlin/O=Arch Linux/OU=Release Engineering/CN=archlinux.org"
  codesigning_cert="${codesigning_dir}/codesign.crt"
  codesigning_key="${codesigning_dir}/codesign.key"
  mkdir -p "${codesigning_dir}"
  cp -- /etc/ssl/openssl.cnf "${codesigning_conf}"
  printf "\n[codesigning]\nkeyUsage=digitalSignature\nextendedKeyUsage=codeSigning\n" >> "${codesigning_conf}"
  openssl req \
      -newkey rsa:4096 \
      -keyout "${codesigning_key}" \
      -nodes \
      -sha256 \
      -x509 \
      -days 365 \
      -out "${codesigning_cert}" \
      -config "${codesigning_conf}" \
      -subj "${codesigning_subj}" \
      -extensions codesigning

David Runge's avatar
David Runge committed
231
  print_section_end "ephemeral_codesigning_key"
232
233
}

234
235
run_mkarchiso() {
  # run mkarchiso
David Runge's avatar
David Runge committed
236
237
238
  create_ephemeral_pgp_key
  create_ephemeral_codesigning_key

David Runge's avatar
David Runge committed
239
  print_section_start "mkarchiso" "Running mkarchiso"
240
  mkdir -p "${output}/" "${tmpdir}/"
241
  GNUPGHOME="${gnupg_homedir}" ./archiso/mkarchiso \
David Runge's avatar
David Runge committed
242
243
      -D "${install_dir}" \
      -c "${codesigning_cert} ${codesigning_key}" \
244
      -g "${pgp_key_id}" \
245
      -G "${pgp_sender}" \
246
247
      -o "${output}/" \
      -w "${tmpdir}/" \
David Runge's avatar
David Runge committed
248
249
250
      -m "${buildmode}" \
      -v "configs/${profile}"

David Runge's avatar
David Runge committed
251
  print_section_end "mkarchiso"
David Runge's avatar
David Runge committed
252
253

  if [[ "${buildmode}" =~ "iso" ]]; then
254
255
    create_zsync_delta "${output}/"*.iso
    create_checksums "${output}/"*.iso
David Runge's avatar
David Runge committed
256
257
  fi
  if [[ "${buildmode}" == "bootstrap" ]]; then
258
259
    create_zsync_delta "${output}/"*.tar*(.gz|.xz|.zst)
    create_checksums "${output}/"*.tar*(.gz|.xz|.zst)
David Runge's avatar
David Runge committed
260
261
  fi
  create_metrics
David Runge's avatar
David Runge committed
262
263
264
265
266
267
268

  print_section_start "ownership" "Setting ownership on output"

  if [[ -n "${SUDO_UID:-}" ]] && [[ -n "${SUDO_GID:-}" ]]; then
    chown -Rv "${SUDO_UID}:${SUDO_GID}" -- "${output}"
  fi
  print_section_end "ownership"
269
270
271
272
}

trap cleanup EXIT

David Runge's avatar
David Runge committed
273
run_mkarchiso