summaryrefslogtreecommitdiff
path: root/debian/cloud-init.postinst
blob: 268f3ec8a7d03f315f487476a23d9b03946c0b64 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
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
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
#!/bin/sh

set -e

. /usr/share/debconf/confmodule

set -f # disable pathname expansion
db_capb escape # to support carriage return / multi-line values

debug() {
   [ "${_CI_UPGRADE_DEBUG:-0}" = "0" ] && return 0
   echo "$@" 1>&2 || :
}

update_cfg() {
   # takes filename, header, new object (in yaml), optionally 'remover'
   # and merges new into existing object in filename, and then updates file
   # remover a string that means "delete existing entry"
   python3 -c '
import sys, yaml

def update(src, cand):
    if not (isinstance(src, dict) and isinstance(cand, dict)):
        return cand
    for k, v in cand.items():
        # if the candidate has _ as value, delete source
        if v == REMOVER:
            if k in src:
                del src[k]
            continue
        if k not in src:
            src[k] = v
        else:
            src[k] = update(src[k], v)
    return src

(fname, header, newyaml) = sys.argv[1:4]
REMOVER = object
if len(sys.argv) == 5:
   REMOVER = sys.argv[4]
newcfg = yaml.load(newyaml)

with open(fname, "r") as fp:
    cfg = yaml.load(fp)
if not cfg: cfg = {}

cfg = update(cfg, newcfg)

with open(fname, "w") as fp:
    fp.write(header + "\n")
    fp.write(yaml.dump(cfg))' "$@"
}

handle_preseed_maas() {
   local cfg_file="/etc/cloud/cloud.cfg.d/90_dpkg_maas.cfg"
   local md_url="" creds_all="" c_key="" t_key="" t_sec="" c_sec="";

   db_get "cloud-init/maas-metadata-url" && md_url="$RET" || :
   db_get "cloud-init/maas-metadata-credentials" && creds_all="$RET" || :

   # nothing to do
   [ -n "$md_url" -o -n "$creds_all" ] || return 0

   # change a url query string format into : delimited
   if [ -n "$creds_all" -a "${creds_all#*&}" != "${creds_all}" ]; then
      # the command here ends up looking like:
      # python3 -c '...' 'oauth_consumer_key=v1&oauth_token_key=v2...' \
      #   oauth_consumer_key oauth_token_key oauth_token_secret
      creds_all=$(python3 -c 'from six.moves.urllib.parse import parse_qs;
import sys;
keys = parse_qs(sys.argv[1])
for k in sys.argv[2:]:
   sys.stdout.write("%s:" % keys.get(k,[""])[0])' "$creds_all" \
   oauth_consumer_key oauth_token_key oauth_token_secret
)
   fi

   # now, if non-empty creds_all is: consumer_key:token_key:token_secret
   if [ -n "$creds_all" ]; then
      OIFS="$IFS"; IFS=:; set -- $creds_all; IFS="$OIFS"
      c_key=$1; t_key=$2; t_sec=$3
   fi

   if [ "$md_url" = "_" -a "${c_key}:${t_key}:${t_sec}" = "_:_:_" ]; then
      # if all these values were '_', the delete value, just delete the file.
      rm -f "$cfg_file"
   else
      local header="# written by cloud-init debian package per preseed entries
# cloud-init/{maas-metadata-url,/maas-metadata-credentials}"

      local pair="" k="" v="" pload="" orig_umask=""
      for pair in "metadata_url:$md_url" "consumer_key:${c_key}" \
         "token_key:${t_key}" "token_secret:$t_sec"; do
         k=${pair%%:*}
         v=${pair#${k}:}
         [ -n "$v" ] && pload="${pload} $k: \"$v\","
      done

      # '_' would indicate "delete", otherwise, existing entries are left
      orig_umask=$(umask)
      umask 066
      : >> "$cfg_file" && chmod 600 "$cfg_file"
      update_cfg "$cfg_file" "$header" "datasource: { MAAS: { ${pload%,} } }" _
      umask ${orig_umask}
   fi

   # now clear the database of the values, as they've been consumed
   db_unregister "cloud-init/maas-metadata-url" || :
   db_unregister "cloud-init/maas-metadata-credentials" || :
}

handle_preseed_local_cloud_config() {
   local ccfg="" debconf_name="cloud-init/local-cloud-config"
   local cfg_file="/etc/cloud/cloud.cfg.d/90_dpkg_local_cloud_config.cfg"
   local header="# written by cloud-init debian package per preseed entry
# $debconf_name"

   db_get "${debconf_name}" && ccfg="$RET" || :

   if [ "$ccfg" = "_" ]; then
      rm -f "$cfg_file"
   elif [ -n "$ccfg" ]; then
      { echo "$header"; echo "$ccfg"; } > "$cfg_file"
   fi
   db_unregister "${debconf_name}" || :
}

fix_1336855() {
  ### Begin fix for LP: 1336855
  # fix issue where cloud-init misidentifies the location of grub and
  # where grub misidentifies the location of the device

  # if cloud-init's grub module did not run, then it did not break anything.
  [ -f /var/lib/cloud/instance/sem/config_grub_dpkg ] || return 0

  # This bug only happened on /dev/xvda devices
  [ -b /dev/xvda ] || return 0

  # we can't fix the system without /proc/cmdline
  [ -r /proc/cmdline ] || return 0

  # Don't do anything unless we have grub
  command -v grub-install > /dev/null || return 0

  # First, identify the kernel device for the parent.
  for parm in $(cat /proc/cmdline); do
       dev=$(echo $parm | awk -F\= '{print$NF}')
       case $parm in
           root=UUID*)  [ -d /dev/disk/by-uuid ] &&
                            root_dev=$(readlink -f /dev/disk/by-uuid/$dev);;
           root=LABEL*) [ -d /dev/disk/by-label ] &&
                            root_dev=$(readlink -f /dev/disk/by-label/$dev);;
           root=/dev*)  [ -d /dev ] &&
                            root_dev=$(readlink -f $dev);;
       esac
       [ -n "$root_dev" ] && break
  done

  # Don't continue if we don't have a root directive
  [ -z "$root_dev" ] && return 0

  # Only deal with simple, cloud-based devices
  case $root_dev in
       /dev/vda*|/dev/xvda*|/dev/sda*) ;;
       *) return 0;;
  esac

  # Make sure that we are not chrooted.
  [ "$(stat -c %d:%i /)" != "$(stat -c %d:%i /proc/1/root/.)" ] && return 0

  # Check if we are in a container, i.e. LXC
  if systemd-detect-virt --quiet --container || lxc-is-container 2>/dev/null; then
      return 0
  fi

  # Find out where grub thinks the root device is. Only continue if
  # grub postinst would install/reinstall grub
  db_get grub-pc/install_devices && grub_cfg_dev=${RET} || return 0
  db_get grub-pc/install_devices_empty && grub_dev_empty=${RET} || return 0

  # Find out the parent device for the root device.
  #     example output: sda/sda1
  block_path=$(udevadm info -q path -n $root_dev | awk '-Fblock/' '{print$NF}') || return 0

  # Extract the parent device name. This works where the device is a block device
  #     example output: /dev/sda
  parent_dev=$(echo $block_path | awk '-F/' '$1 { if ( $1 ) {print"/dev/"$1}}')
  [ -b "${parent_dev}" ] || return 0

  # Do nothing if the device that the grub postinst would install is already used
  [ "$grub_cfg_dev" = "$parent_dev" -o "$grub_cfg_dev" = "$root_dev" ] && return 0

  # If we get here, do the installation
  echo "Reconfiguring grub install device due to mismatch (LP: #1336855)"
  echo "   Grub should use $parent_dev but is configured for $grub_cfg_dev"
  db_set grub-pc/install_devices "$parent_dev"
  grub-install $parent_dev &&
     echo "Reinstalled grub" ||
     echo "WARNING! Unable to fix grub device mismatch. You may be broken."

}

cleanup_lp1552999() {
    local oldver="$1" last_bad_ver="0.7.7~bzr1178"
    dpkg --compare-versions "$oldver" le "$last_bad_ver" || return 0
    local edir="/etc/systemd/system/multi-user.target.wants"
    rm -f "$edir/cloud-config.service" "$edir/cloud-final.service" \
        "$edir/cloud-init-local.service" "$edir/cloud-init.service"
}

remove_deprecated_cloud_config_modules() {
    local oldver="$1" last_bad_ver="22.1-14-g2e17a0d6-0ubuntu1~22.04.5"
    if dpkg --compare-versions "$oldver" le "$last_bad_ver"; then
      if grep -q emit_upstart /etc/cloud/cloud.cfg; then
         # Redact emit_upstart if present in locally modified cloud.cfg:
         # 1. "- emit_upstart" on a single line as defined in upstream
         # 2. upstart_dir config which is no longer used
         sed -i -e '/- emit_upstart/d' -e '/upstart_dir/d' /etc/cloud/cloud.cfg || true
         if grep -q emit_upstart /etc/cloud/cloud.cfg; then
             echo "DEPRECATION: unable to remove emit_upstart from /etc/cloud/cloud.cfg."
             echo "This module is deprecated, you may remove it to eliminate warnings in cloud-init logs."
         fi
      fi
    fi
}

disable_network_config_on_upgrade() {
    local oldver="$1" last_without_net="0.7.7~bzr1182-0ubuntu1"
    if [ ! -f /var/lib/cloud/instance/obj.pkl ]; then
        # this is a fresh system not one that has been booted.
        return 0
    fi
    if dpkg --compare-versions "$oldver" le "$last_without_net"; then
        echo "dpkg upgrade from $oldver" > /var/lib/cloud/data/upgraded-network
    fi
}

fix_azure_upgrade_1611074() {
    # adjust /etc/fstab on azure so boot after resize does not mount
    # /mnt as ntfs and stop re-formatting.
    local fixed_ver="0.7.8-49-1" dspath="/var/lib/cloud/instance/datasource"
    local oldver="$1" tmpf="" r="" wmsg="" me="cloud-init postinst"

    # if not on azure, or not booted with instance/ skip out.
    if [ ! -e "$dspath" ]; then
        debug "no $dspath"
        return 0
    fi
    if ! grep -qi azure "$dspath"; then
        debug "not on azure per $dspath"
        return 0
    fi

    # if there is no /etc/fstab, then nothing to fix.
    if [ ! -e /etc/fstab ]; then
        debug "no /etc/fstab"
        return 0
    fi

    if dpkg --compare-versions "$oldver" ge "$fixed_ver"; then
        debug "previous version was fixed"
        return 0
    fi

    wmsg="WARN: $me failed."
    wmsg="$wmsg Subsequent resize may not update ephemeral correctly."
    tmpf=$(mktemp "${TMPDIR:-/tmp}/cloud-init-upgrade.XXXXXX") || {
        echo "$wmsg (mktemp failed with $?)" 1>&2
        return 0;
    }

    awk '{
        if ($4 !~ /x-systemd.requires/ && $4 ~ /comment=cloudconfig/) {
            sub(/comment=cloudconfig/, "x-systemd.requires=cloud-init.service,comment=cloudconfig")
        }
        printf("%s\n", $0)}' /etc/fstab > "$tmpf" || {
            echo "$wmsg (awk reading of /etc/fstab failed with $?)" 1>&2
            rm -f "$tmpf"
            return 0;
        }
    if cmp /etc/fstab "$tmpf" >/dev/null 2>&1; then
        debug "no changes needed."
    else
        cat "$tmpf" > /etc/fstab || {
            r=$?
            echo "$wmsg (cp $tmpf /etc/fstab failed with $r)"
            echo ==== expected to write the following to /etc/fstab =====
            cat "$tmpf"
            echo ========================================================
            return $r
        } 1>&2
        echo "$me fixed /etc/fstab for x-systemd.requires" 1>&2
    fi
    rm "$tmpf" || :
}

cleanup_ureadahead() {
    local oldver="$1" last_bad_ver="0.7.9-243-ge74d775-0ubuntu2~"
    dpkg --compare-versions "$oldver" le "$last_bad_ver" || return 0
    dpkg-divert --package cloud-init --remove --rename --divert \
        /etc/init/ureadahead.conf.disabled /etc/init/ureadahead.conf
}

fix_lp1889555() {
    local oldver="$1" last_bad_ver="20.3-2-g371b392c-0ubuntu1"
    dpkg --compare-versions "$oldver" le-nl "$last_bad_ver" || return 0

    # if cloud-init's grub module did not run, then it did not break anything.
    [ -f /var/lib/cloud/instance/sem/config_grub_dpkg ] || return 0

    # Don't do anything unless we have grub
    command -v grub-install > /dev/null || return 0

    # Make sure that we are not chrooted.
    [ "$(stat -c %d:%i /)" != "$(stat -c %d:%i /proc/1/root/.)" ] && return 0

    # Check if we are in a container, i.e. LXC
    if systemd-detect-virt --quiet --container || lxc-is-container 2>/dev/null; then
        return 0
    fi

    # This bug only applies to NVMe devices
    [ -e /dev/nvme0 ] || return 0

    db_get grub-pc/install_devices && grub_cfg_dev=${RET} || return 0

    # If the current setting is not the (potentially-incorrect) default we
    # expect, this implies user intervention so leave things alone
    [ "$grub_cfg_dev" = "/dev/sda" ] || return 0

    correct_idev="$(python3 -c "import logging; from cloudinit.config.cc_grub_dpkg import fetch_idevs; print(fetch_idevs(logging.getLogger()))")" || return 0

    # If correct_idev is the empty string, we failed to determine the correct
    # install device; do nothing
    [ -z "$correct_idev" ] && return 0

    # If the correct_idev is already configured, do nothing
    [ "$grub_cfg_dev" = "$correct_idev" ] && return 0

    echo "Reconfiguring grub install device due to mismatch (LP: #1889555)"
    echo "   grub should use $correct_idev but is configured for $grub_cfg_dev"
    db_set grub-pc/install_devices "$correct_idev"
    db_set grub-pc/install_devices_empty "false"
}

change_cloud_init_output_log_permissions() {
    # As a consequence of LP: #1918303
    local oldver="$1" last_bad_ver="21.1-0ubuntu1"
    dpkg --compare-versions "$oldver" le-nl "$last_bad_ver" || return 0

    output_file="/var/log/cloud-init-output.log"
    if [ -f "$output_file" ]; then
        if getent group adm > /dev/null; then
            chown root:adm $output_file
        else
            chown root $output_file
        fi
        chmod 640 $output_file
    fi
}


if [ "$1" = "configure" ]; then
   if db_get cloud-init/datasources; then
      values="$RET"
      if [ "${values#*MaaS}" != "${values}" ]; then
         # if db had old MAAS spelling, fix it.
         values=$(echo "$values" | sed 's,MaaS,MAAS,g')
         db_set cloud-init/datasources "$values"
      fi
      cat > /etc/cloud/cloud.cfg.d/90_dpkg.cfg <<EOF
# to update this file, run dpkg-reconfigure cloud-init
datasource_list: [ $values ]
EOF
   fi

   # if there are maas settings pre-seeded apply them
   handle_preseed_maas

   # if there is generic cloud-config preseed, apply them
   handle_preseed_local_cloud_config

   # fix issue where cloud-init misidentifies the location of grub
   fix_1336855

   # make upgrades disable network changes by cloud-init
   disable_network_config_on_upgrade "$2"

   remove_deprecated_cloud_config_modules "$2"

   fix_azure_upgrade_1611074 "$2"

   cleanup_ureadahead "$2"
   fix_lp1889555 "$2"
   change_cloud_init_output_log_permissions "$2"
fi

#DEBHELPER#

if [ "$1" = "configure" ]; then
   oldver="$2"
   cleanup_lp1552999 "$oldver"
fi