Skip to content

Instantly share code, notes, and snippets.

@mentha
Last active September 16, 2023 11:19
Show Gist options
  • Save mentha/6aa925c754cc4b8e00d8286b3f3eeabe to your computer and use it in GitHub Desktop.
Save mentha/6aa925c754cc4b8e00d8286b3f3eeabe to your computer and use it in GitHub Desktop.
reboot by kexec
#!/usr/bin/env python3
from argparse import ArgumentParser
from contextlib import suppress
import json
import os
import re
import shlex
import shutil
import subprocess as sp
import sys
def kexec_main(a):
unshare_env = f'reboot_unshared_{os.getpid()}'
if unshare_env not in os.environ:
os.environ[unshare_env] = '1'
c = ['unshare', '-m', sys.executable] + sys.argv
os.execvp(c[0], c)
entry = None
for e in json.loads(sp.run(['bootctl', '--json=short', 'list'], check=True, stdout=sp.PIPE).stdout):
if a.boot_entry is not None:
if e['id'] == a.boot_entry:
entry = e
break
else:
if e['isDefault']:
entry = e
break
if entry is None or 'version' not in entry:
raise RuntimeError('no suitable boot entry found')
version = entry['version']
kernel = os.path.join(entry['root'], entry['linux'].lstrip('/'))
option = entry.get('options', '') + ' rd.shell=0 rd.break=0 rd.timeout=300 rd.emergency=reboot'
print(f'Selected kernel {kernel} version {version}')
dracutcmd = ['dracut', '--no-compress']
if 'rd.luks.uuid' in option and os.path.exists('/etc/crypttab'):
luksuuid = []
for o in option.split():
if o.startswith('rd.luks.uuid='):
u = o.split('=', 1)[1]
if u.startswith('keysource:'):
u = u.split(':', 1)[1]
if u.startswith('luks-'):
u = u[5:]
luksuuid.append(u)
keyfiles = set()
with open('/etc/crypttab') as f:
for l in f:
l = l.split()
if len(l) >= 3 and l[1].startswith('UUID=') and l[2].startswith('/'):
uuid = l[1][5:]
for u in luksuuid:
if uuid.startswith(u):
keyfiles.add(l[2])
if keyfiles:
dracutcmd += ['--install', '/etc/crypttab']
for f in keyfiles:
dracutcmd += ['--install', f]
dracutcmd += ['/tmp/initrd.img', version]
sp.run(['mount', '-t', 'tmpfs', 'none', '/tmp'], stdin=sp.DEVNULL, check=True)
sp.run(dracutcmd, stdin=sp.DEVNULL, check=True)
sp.run(['kexec', '-l', '-a', '--append=' + option, '--initrd=/tmp/initrd.img', kernel], stdin=sp.DEVNULL, check=True)
if not a.load:
sp.run(['systemctl', '--no-block', 'kexec'], stdin=sp.DEVNULL, check=True)
def main():
a = ArgumentParser(description='reboot by kexec')
a.add_argument('--load', '-l', action='store_true', help='load kernel but do not reboot')
a.add_argument('boot_entry', nargs='?', help='select boot entry')
kexec_main(a.parse_args())
if __name__ == '__main__':
main()
[Unit]
DefaultDependencies=no
Wants=kexec.target
Before=kexec.target
[Service]
Type=oneshot
ExecStart=@libexec@/kexec-reboot.py --load
[Unit]
DefaultDependencies=no
Wants=kexec-reboot.service
AllowIsolate=yes
[Install]
Alias=reboot.target
Alias=ctrl-alt-del.target
pkgname=kexec-reboot
pkgver=0.20230916.0
pkgrel=1
pkgdesc='reboot by kexec'
arch=(any)
license=(Unlicense)
depends=(
systemd
python
util-linux
kexec-tools
)
makedepends=(
sed
)
source=(
kexec-reboot.py
kexec-reboot.service.in
kexec-reboot.target
)
cksums=(
SKIP
SKIP
SKIP
)
build() {
sed 's!@libexec@!/usr/lib!' < "$srcdir/kexec-reboot.service.in" > "$srcdir/kexec-reboot.service"
}
package() {
install -Dm644 -t "$pkgdir/usr/lib/systemd/system" "$srcdir/kexec-reboot.service"
install -Dm644 -t "$pkgdir/usr/lib/systemd/system" "$srcdir/kexec-reboot.target"
install -Dm755 -t "$pkgdir/usr/lib" "$srcdir/kexec-reboot.py"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment