crypt_reboot 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +3 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +169 -0
  5. data/exe/cryptreboot +9 -0
  6. data/lib/basic_loader.rb +60 -0
  7. data/lib/crypt_reboot/boot_config.rb +24 -0
  8. data/lib/crypt_reboot/cli/exiter.rb +25 -0
  9. data/lib/crypt_reboot/cli/happy_exiter.rb +14 -0
  10. data/lib/crypt_reboot/cli/params/definition.rb +125 -0
  11. data/lib/crypt_reboot/cli/params/flattener.rb +24 -0
  12. data/lib/crypt_reboot/cli/params/help_generator.rb +22 -0
  13. data/lib/crypt_reboot/cli/params/parser.rb +29 -0
  14. data/lib/crypt_reboot/cli/params_parsing_executor.rb +73 -0
  15. data/lib/crypt_reboot/cli/sad_exiter.rb +14 -0
  16. data/lib/crypt_reboot/cli.rb +8 -0
  17. data/lib/crypt_reboot/concatenator.rb +24 -0
  18. data/lib/crypt_reboot/config.rb +20 -0
  19. data/lib/crypt_reboot/crypt_tab/deserializer.rb +28 -0
  20. data/lib/crypt_reboot/crypt_tab/entry.rb +39 -0
  21. data/lib/crypt_reboot/crypt_tab/entry_deserializer.rb +51 -0
  22. data/lib/crypt_reboot/crypt_tab/entry_serializer.rb +21 -0
  23. data/lib/crypt_reboot/crypt_tab/keyfile_locator.rb +21 -0
  24. data/lib/crypt_reboot/crypt_tab/luks_to_plain_converter.rb +39 -0
  25. data/lib/crypt_reboot/crypt_tab/serializer.rb +29 -0
  26. data/lib/crypt_reboot/files_generator.rb +44 -0
  27. data/lib/crypt_reboot/files_writer.rb +17 -0
  28. data/lib/crypt_reboot/gziper.rb +22 -0
  29. data/lib/crypt_reboot/initramfs/archiver.rb +35 -0
  30. data/lib/crypt_reboot/initramfs/decompressor/intolerant_decompressor.rb +65 -0
  31. data/lib/crypt_reboot/initramfs/decompressor/tolerant_decompressor.rb +28 -0
  32. data/lib/crypt_reboot/initramfs/decompressor.rb +12 -0
  33. data/lib/crypt_reboot/initramfs/extractor.rb +36 -0
  34. data/lib/crypt_reboot/initramfs/patcher.rb +47 -0
  35. data/lib/crypt_reboot/initramfs_patch_squeezer.rb +28 -0
  36. data/lib/crypt_reboot/instantiable_config.rb +52 -0
  37. data/lib/crypt_reboot/kexec/loader.rb +30 -0
  38. data/lib/crypt_reboot/kexec_patching_loader.rb +28 -0
  39. data/lib/crypt_reboot/lazy_config.rb +22 -0
  40. data/lib/crypt_reboot/luks/checker.rb +27 -0
  41. data/lib/crypt_reboot/luks/data.rb +37 -0
  42. data/lib/crypt_reboot/luks/data_fetcher.rb +30 -0
  43. data/lib/crypt_reboot/luks/dumper/luks_v1_parser.rb +64 -0
  44. data/lib/crypt_reboot/luks/dumper/luks_v2_parser.rb +62 -0
  45. data/lib/crypt_reboot/luks/dumper.rb +30 -0
  46. data/lib/crypt_reboot/luks/key_fetcher.rb +50 -0
  47. data/lib/crypt_reboot/luks/version_detector.rb +32 -0
  48. data/lib/crypt_reboot/passphrase_asker.rb +15 -0
  49. data/lib/crypt_reboot/patched_initramfs_generator.rb +21 -0
  50. data/lib/crypt_reboot/rebooter.rb +20 -0
  51. data/lib/crypt_reboot/runner/binary.rb +12 -0
  52. data/lib/crypt_reboot/runner/boolean.rb +18 -0
  53. data/lib/crypt_reboot/runner/generic.rb +46 -0
  54. data/lib/crypt_reboot/runner/lines.rb +13 -0
  55. data/lib/crypt_reboot/runner/no_result.rb +13 -0
  56. data/lib/crypt_reboot/runner/text.rb +12 -0
  57. data/lib/crypt_reboot/runner.rb +8 -0
  58. data/lib/crypt_reboot/safe_temp/directory.rb +28 -0
  59. data/lib/crypt_reboot/safe_temp/file_name.rb +23 -0
  60. data/lib/crypt_reboot/safe_temp/mounter.rb +33 -0
  61. data/lib/crypt_reboot/single_assign_restricted_map.rb +26 -0
  62. data/lib/crypt_reboot/version.rb +5 -0
  63. data/lib/crypt_reboot.rb +17 -0
  64. metadata +138 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 449c9b12be881f213dffc9090686e7d73bb3f2523806c5edf41dc2e4d7d17c4b
4
+ data.tar.gz: ba391871d8d5f438ba2474a8a21d8738be020636ac8c4bfcd0fd8100d7e07aec
5
+ SHA512:
6
+ metadata.gz: 466befee2a5027cae8be7cb036c87e8c779e64fb4725775111b112e53ff5bf2691bc543ac86e7c229e8e75642c0c3eb0168fed1bca14a2b0598b94bf3c5245c0
7
+ data.tar.gz: 68bef507bea7444722f3281b94d8ea5af0bda0d031871f82fc9ff0398614e7b83564f2b541a42d745d2eefecde9f0a409817dcd29332b01797cd13b434bd7db0
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ ## [0.1.0] - 2023-07-09
2
+
3
+ - Initial release
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2023 Paweł Pokrywka
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,169 @@
1
+ # Cryptreboot
2
+
3
+ Convenient reboot for Linux systems with encrypted root partition.
4
+
5
+ > Just type `cryptreboot` instead of `reboot`.
6
+
7
+ It asks for a passphrase and reboots the system afterward, automatically
8
+ unlocking the drive on startup using in-memory initramfs patching and kexec.
9
+ Without explicit consent, no secrets are stored on disk, even temporarily.
10
+
11
+ Useful when unlocking the drive at startup is difficult, such as on headless
12
+ and remote systems.
13
+
14
+ By default, it uses the current kernel command line, `/boot/vmlinuz` as kernel
15
+ and `/boot/initrd.img` as initramfs.
16
+
17
+ Will work properly when using standard passphrase-based disk unlocking.
18
+ Fancy methods such as using an external USB with a passphrase file will fail.
19
+
20
+ ## Compatible Linux distributions
21
+
22
+ Currently, cryptreboot depends on `initramfs-tools` package which is available in
23
+ Debian-based distributions. Therefore one should expect, this tool to work on
24
+ Debian, Ubuntu, Linux Mint, Pop!_OS, etc.
25
+
26
+ On the other hand, do not expect it to work on other distributions now.
27
+ But support for them may come in upcoming versions.
28
+
29
+ Following distributions were tested by the author:
30
+ - Ubuntu 22.04 LTS
31
+ - Ubuntu 20.04 LTS needs tiny adjustments to system settings,
32
+ specifically [changing compression](#lz4-initramfs-compression) and
33
+ [fixing systemd kexec support](#staged-kernel-not-being-executed-by-systemd)
34
+ - ~~Ubuntu 18.04 LTS~~ is not supported (initramfs uses *pre-crypttab* format)
35
+ - Pop!_OS 22.04 LTS
36
+
37
+ If you have successfully run cryptreboot on another distribution,
38
+ please contact me and I will update the list.
39
+
40
+ ## Requirements
41
+
42
+ You need to ensure those are installed:
43
+ - `ruby` >= 2.7
44
+ - `kexec-tools`
45
+ - `initramfs-tools` (other initramfs generators, such as `dracut` are
46
+ not supported yet)
47
+
48
+ If you use recent, mainstream Linux distribution, other requirements are
49
+ probably already met:
50
+ - `kexec` support in the kernel
51
+ - `tmpfs` filesystem support in kernel
52
+ - `cryptsetup` (if you use disk encryption, it should be installed)
53
+ - `systemd` or another way to guarantee staged kernel is executed on reboot
54
+ - `strace` (not required if `--skip-lz4-check` flag is specified)
55
+
56
+ ## Installation
57
+
58
+ Make sure the required software is installed, then install the gem by executing:
59
+
60
+ $ gem install crypt_reboot
61
+
62
+ ## Usage
63
+
64
+ Cryptreboot performs operations normally only available to the root user,
65
+ so it is suggested to use sudo or a similar utility.
66
+
67
+ To perform a reboot type:
68
+
69
+ $ sudo cryptreboot
70
+
71
+ To see the usage, run:
72
+
73
+ $ cryptreboot --help
74
+
75
+ ## Resolutions for common issues
76
+
77
+ ### LZ4 initramfs compression
78
+
79
+ If you get:
80
+
81
+ > LZ4 compression is not allowed, change the compression algorithm in
82
+ initramfs.conf and regenerate the initramfs image
83
+
84
+ it means initramfs was compressed using the LZ4 algorithm, which seems to
85
+ have issues with concatenating initramfs images.
86
+
87
+ In case you are 100% sure LZ4 won't cause problems, you can use
88
+ `--skip-lz4-check` command line flag. This will make the error message
89
+ go away, but you risk automatic disk unlocking at startup to fail randomly.
90
+
91
+ Instead, the recommended approach is to change the compression algorithm
92
+ in `/etc/initramfs-tools/initramfs.conf` file. Look for `COMPRESS` and
93
+ set it to some other value such as `gzip` (the safe choice), or `zstd`
94
+ (the best compression, but your kernel and `initramfs-tools` need to support it).
95
+
96
+ Here is a one-liner to change compression to `gzip`:
97
+
98
+ $ sudo sed -iE 's/^\s*COMPRESS=.*$/COMPRESS=gzip/' /etc/initramfs-tools/initramfs.conf
99
+
100
+ Then you need to regenerate all of your initramfs images:
101
+
102
+ $ sudo update-initramfs -k all -u
103
+
104
+ That's it.
105
+
106
+ Resources related to the issue:
107
+ - [Appending files to initramfs image - reliable? (StackExchange)](https://unix.stackexchange.com/a/737219)
108
+ - [What is the correct frame format for Linux (Lz4 issue)](https://github.com/lz4/lz4/issues/956)
109
+ - [Initramfs unpacking failed (Ubuntu bug report)](https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1835660)
110
+
111
+ ### Staged kernel not being executed by systemd
112
+
113
+ If rebooting with cryptreboot doesn't seem to differ from a standard
114
+ reboot, it may suggest staged kernel is not being executed by the
115
+ `systemd` at the end of the shutdown procedure.
116
+
117
+ The solution I found is to execute `kexec -e` instead of
118
+ `systemctl --force kexec` when the system is ready for a reboot.
119
+ To do that `systemd-kexec.service` has to be modified.
120
+ To make the change minimal, let's use `systemd drop-in` for that:
121
+
122
+ $ sudo mkdir -p /etc/systemd/system/systemd-kexec.service.d/
123
+ $ echo -e "[Service]\nExecStart=\nExecStart=kexec -e" | sudo tee /etc/systemd/system/systemd-kexec.service.d/override.conf
124
+
125
+ That should work.
126
+
127
+ To cancel the change, remove the file:
128
+
129
+ $ sudo rm /etc/systemd/system/systemd-kexec.service.d/override.conf
130
+
131
+ ## Development
132
+
133
+ After checking out the repo, run `bundle install` to install
134
+ dependencies. Then, run `rake spec` to run the tests. You can also
135
+ run `bin/console` for an interactive prompt that will allow you
136
+ to experiment.
137
+
138
+ To build the gem, run `rake build`. To release a new version, update
139
+ the version number in `version.rb`, and then run `rake release`, which
140
+ will create a git tag for the version, push git commits and the created
141
+ tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
142
+
143
+ ## Contributing
144
+
145
+ Bug reports and pull requests are welcome on GitHub at
146
+ https://github.com/phantom-node/cryptreboot.
147
+ This project is intended to be a safe, welcoming space for collaboration,
148
+ and contributors are expected to adhere to the
149
+ [code of conduct](https://github.com/phantom-node/cryptreboot/blob/master/CODE_OF_CONDUCT.md).
150
+
151
+ ## Author
152
+
153
+ My name is Paweł Pokrywka and I'm the author of cryptreboot.
154
+
155
+ If you want to contact me or get to know me better, check out
156
+ [my blog](https://blog.pawelpokrywka.com).
157
+
158
+ Thank you for your interest in this project :)
159
+
160
+ ## License
161
+
162
+ The software is available as open source under the terms of the
163
+ [MIT License](https://opensource.org/licenses/MIT).
164
+
165
+ ## Code of Conduct
166
+
167
+ Everyone interacting in the Cryptreboot project's codebases, issue
168
+ trackers, chat rooms, and mailing lists is expected to follow the
169
+ [code of conduct](https://github.com/phantom-node/cryptreboot/blob/master/CODE_OF_CONDUCT.md).
data/exe/cryptreboot ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/setup'
5
+ require 'crypt_reboot'
6
+
7
+ executor = CryptReboot::Cli::ParamsParsingExecutor.new
8
+ result = executor.call(ARGV)
9
+ exit result.call
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ # File generated automatically, do not edit
4
+
5
+ require 'crypt_reboot/version'
6
+ require 'crypt_reboot/boot_config'
7
+ require 'crypt_reboot/cli'
8
+ require 'crypt_reboot/concatenator'
9
+ require 'crypt_reboot/instantiable_config'
10
+ require 'crypt_reboot/config'
11
+ require 'crypt_reboot/files_generator'
12
+ require 'crypt_reboot/files_writer'
13
+ require 'crypt_reboot/gziper'
14
+ require 'crypt_reboot/initramfs_patch_squeezer'
15
+ require 'crypt_reboot/kexec_patching_loader'
16
+ require 'crypt_reboot/lazy_config'
17
+ require 'crypt_reboot/passphrase_asker'
18
+ require 'crypt_reboot/patched_initramfs_generator'
19
+ require 'crypt_reboot/rebooter'
20
+ require 'crypt_reboot/runner'
21
+ require 'crypt_reboot/single_assign_restricted_map'
22
+ require 'crypt_reboot/cli/exiter'
23
+ require 'crypt_reboot/cli/happy_exiter'
24
+ require 'crypt_reboot/cli/params_parsing_executor'
25
+ require 'crypt_reboot/cli/sad_exiter'
26
+ require 'crypt_reboot/crypt_tab/deserializer'
27
+ require 'crypt_reboot/crypt_tab/entry'
28
+ require 'crypt_reboot/crypt_tab/entry_deserializer'
29
+ require 'crypt_reboot/crypt_tab/entry_serializer'
30
+ require 'crypt_reboot/crypt_tab/keyfile_locator'
31
+ require 'crypt_reboot/crypt_tab/luks_to_plain_converter'
32
+ require 'crypt_reboot/crypt_tab/serializer'
33
+ require 'crypt_reboot/initramfs/archiver'
34
+ require 'crypt_reboot/initramfs/decompressor'
35
+ require 'crypt_reboot/initramfs/extractor'
36
+ require 'crypt_reboot/initramfs/patcher'
37
+ require 'crypt_reboot/kexec/loader'
38
+ require 'crypt_reboot/luks/checker'
39
+ require 'crypt_reboot/luks/data'
40
+ require 'crypt_reboot/luks/data_fetcher'
41
+ require 'crypt_reboot/luks/dumper'
42
+ require 'crypt_reboot/luks/key_fetcher'
43
+ require 'crypt_reboot/luks/version_detector'
44
+ require 'crypt_reboot/runner/generic'
45
+ require 'crypt_reboot/runner/binary'
46
+ require 'crypt_reboot/runner/boolean'
47
+ require 'crypt_reboot/runner/lines'
48
+ require 'crypt_reboot/runner/no_result'
49
+ require 'crypt_reboot/runner/text'
50
+ require 'crypt_reboot/safe_temp/directory'
51
+ require 'crypt_reboot/safe_temp/file_name'
52
+ require 'crypt_reboot/safe_temp/mounter'
53
+ require 'crypt_reboot/cli/params/definition'
54
+ require 'crypt_reboot/cli/params/flattener'
55
+ require 'crypt_reboot/cli/params/help_generator'
56
+ require 'crypt_reboot/cli/params/parser'
57
+ require 'crypt_reboot/initramfs/decompressor/intolerant_decompressor'
58
+ require 'crypt_reboot/initramfs/decompressor/tolerant_decompressor'
59
+ require 'crypt_reboot/luks/dumper/luks_v1_parser'
60
+ require 'crypt_reboot/luks/dumper/luks_v2_parser'
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CryptReboot
4
+ # Data required for booting
5
+ class BootConfig
6
+ attr_reader :kernel, :initramfs, :cmdline
7
+
8
+ def ==(other)
9
+ kernel == other.kernel && initramfs == other.initramfs && cmdline == other.cmdline
10
+ end
11
+
12
+ def with_initramfs(new_initramfs)
13
+ self.class.new(kernel: kernel, initramfs: new_initramfs, cmdline: cmdline)
14
+ end
15
+
16
+ private
17
+
18
+ def initialize(kernel:, initramfs: nil, cmdline: nil)
19
+ @kernel = kernel
20
+ @initramfs = initramfs
21
+ @cmdline = cmdline
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CryptReboot
4
+ module Cli
5
+ # Print a message and return exit status
6
+ class Exiter
7
+ attr_reader :text
8
+
9
+ def call
10
+ stream.puts text.strip if text
11
+ status
12
+ end
13
+
14
+ private
15
+
16
+ attr_reader :status, :stream
17
+
18
+ def initialize(text, status:, stream:)
19
+ @text = text
20
+ @status = status
21
+ @stream = stream
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CryptReboot
4
+ module Cli
5
+ # Exit with a success message
6
+ class HappyExiter < Exiter
7
+ private
8
+
9
+ def initialize(text)
10
+ super(text, status: 0, stream: $stdout)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tty-option'
4
+
5
+ module CryptReboot
6
+ module Cli
7
+ module Params
8
+ # Definition of options, flags, help and other CLI related things
9
+ class Definition
10
+ include TTY::Option
11
+
12
+ # rubocop:disable Metrics/BlockLength
13
+ usage do
14
+ header 'Reboot for Linux systems with encrypted root partition.'
15
+
16
+ program PROGRAM_NAME
17
+ no_command
18
+
19
+ desc 'It asks for a password and reboots the system afterward, automatically unlocking ' \
20
+ 'the drive on startup using in-memory initramfs patching and kexec. ' \
21
+ 'Without explicit consent, no secrets are stored on disk, even temporarily.',
22
+ '',
23
+ 'Useful when unlocking the drive at startup is difficult, such as on headless and remote systems.',
24
+ '',
25
+ "By default, it uses the current kernel command line, \"#{Config.kernel}\" as " \
26
+ "kernel and \"#{Config.initramfs}\" as initramfs.",
27
+ '',
28
+ 'It performs operations normally only available to the root user, so it is suggested to use ' \
29
+ 'sudo or a similar utility.'
30
+
31
+ example 'Normal usage:',
32
+ "$ sudo #{PROGRAM_NAME}"
33
+ example 'Reboot into custom kernel:',
34
+ "$ sudo #{PROGRAM_NAME} --kernel /boot/vmlinuz.old --initramfs /boot/initrd.old"
35
+ example 'Specify custom kernel options:',
36
+ "$ sudo #{PROGRAM_NAME} --cmdline \"root=UUID=d0...a2 ro nomodeset acpi=off\""
37
+ example 'Prepare to reboot and perform it manually later:',
38
+ "$ sudo #{PROGRAM_NAME} --prepare-only",
39
+ '$ sleep 3600 # do anything else in-between',
40
+ '$ sudo reboot --no-wall --no-wtmp'
41
+
42
+ footer 'To report a bug, get support or contribute, please visit the project page:',
43
+ 'https://phantomno.de/cryptreboot',
44
+ '',
45
+ "Thank you for using #{PROGRAM_NAME}. Happy rebooting!"
46
+ end
47
+ # rubocop:enable Metrics/BlockLength
48
+
49
+ option :kernel do
50
+ long '--kernel path'
51
+ desc 'Path to the kernel you want to reboot into'
52
+ default Config.kernel
53
+ end
54
+
55
+ option :initramfs do
56
+ long '--initramfs path'
57
+ desc 'Path to the initramfs to be used by loaded kernel'
58
+ default Config.initramfs
59
+ end
60
+
61
+ option :cmdline do
62
+ long '--cmdline string'
63
+ desc 'Command line for loaded kernel; current command line is used if not provided'
64
+ end
65
+
66
+ option :paths do
67
+ arity :any
68
+ long '--tool name:path'
69
+ desc 'Path to given external tool specified by "name". By default, tools are searched in the PATH. ' \
70
+ 'If you want to specify paths for more than 1 tool, use this option multiple times. Tool names: ' \
71
+ 'cat, cpio, cryptsetup, grep, kexec, mount, reboot, strace, umount, unmkinitramfs'
72
+ convert :map
73
+ end
74
+
75
+ # Flags
76
+
77
+ flag :prepare_only do
78
+ short '-p'
79
+ long '--prepare-only'
80
+ desc 'Load kernel and initramfs, but do not reboot'
81
+ end
82
+
83
+ flag :skip_lz4_check do
84
+ long '--skip-lz4-check'
85
+ desc 'Do not check if initramfs is compressed with LZ4 algorithm. ' \
86
+ 'If you use different compression and specify this flag, it will make ' \
87
+ 'initramfs extraction much faster. But if your initramfs uses ' \
88
+ 'LZ4 you risk you will need to manually unlock your disk on startup. ' \
89
+ 'See the README file to learn how to change the compression ' \
90
+ 'algorithm to a more robust one.'
91
+ end
92
+
93
+ flag :debug do
94
+ short '-d'
95
+ long '--debug'
96
+ desc 'Print debug messages'
97
+ end
98
+
99
+ option :patch_save_path do
100
+ long '--save-patch path'
101
+ desc 'Save initramfs patch to file for debug purposes. ' \
102
+ 'WARNING: it contains encryption keys, you are responsible for ' \
103
+ 'their safe disposal, which may be difficult after the file comes ' \
104
+ 'into contact with the disk. Deleting the file alone may not be enough.'
105
+ end
106
+
107
+ flag :version do
108
+ short '-v'
109
+ long '--version'
110
+ desc 'Print version and exit'
111
+ end
112
+
113
+ flag :help do
114
+ short '-h'
115
+ long '--help'
116
+ desc 'Print this usage and exit'
117
+ end
118
+ end
119
+
120
+ # This class contains code from external source,
121
+ # do not expose it anywhere outside of the module
122
+ private_constant :Definition
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CryptReboot
4
+ module Cli
5
+ module Params
6
+ # Replace given key in params with new keys obtained from its contents with suffixes added
7
+ class Flattener
8
+ def call(params)
9
+ paths = params.fetch(key, {}).transform_keys { |k| :"#{k}#{suffix}" }
10
+ params.reject { |k, _| k == key }.merge(paths)
11
+ end
12
+
13
+ private
14
+
15
+ attr_reader :key, :suffix
16
+
17
+ def initialize(key:, suffix:)
18
+ @key = key.to_sym
19
+ @suffix = suffix
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CryptReboot
4
+ module Cli
5
+ module Params
6
+ # Returns usage
7
+ class HelpGenerator
8
+ def call
9
+ definition.help(order: ->(params) { params })
10
+ end
11
+
12
+ private
13
+
14
+ attr_reader :definition
15
+
16
+ def initialize(definition: Definition.new)
17
+ @definition = definition
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CryptReboot
4
+ module Cli
5
+ module Params
6
+ # Parse given ARGV and return params hash or raise exception with error summary
7
+ class Parser
8
+ ParseError = Class.new StandardError
9
+
10
+ def call(raw_params)
11
+ params = definition.parse(raw_params).params
12
+ raise ParseError, params.errors.summary unless params.valid?
13
+
14
+ flattener.call params.to_h
15
+ end
16
+
17
+ private
18
+
19
+ attr_reader :definition, :flattener
20
+
21
+ def initialize(definition: Definition.new,
22
+ flattener: Flattener.new(key: 'paths', suffix: '_path'))
23
+ @definition = definition
24
+ @flattener = flattener
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CryptReboot
4
+ module Cli
5
+ # Interprets parameters, executes everything and returns callable object
6
+ class ParamsParsingExecutor
7
+ def call(raw_params)
8
+ params = parser.call(raw_params)
9
+ handle_action_params!(params) or configure_and_exec(params)
10
+ rescue StandardError => e
11
+ raise if debug?
12
+
13
+ sad_exiter_class.new(error_message(e))
14
+ end
15
+
16
+ private
17
+
18
+ def debug?
19
+ debug_checker.call
20
+ end
21
+
22
+ def configure_and_exec(params)
23
+ config_updater.call(**params)
24
+ loader.call
25
+ rebooter
26
+ end
27
+
28
+ def handle_action_params!(params)
29
+ return happy_exiter_class.new(help_generator.call) if params[:help]
30
+ return happy_exiter_class.new(version_string) if params[:version]
31
+
32
+ params.reject! { |param_name, _| %i[help version].include? param_name }
33
+
34
+ false
35
+ end
36
+
37
+ def exception_name(exception)
38
+ name = exception.class.name.split('::').last
39
+ name.gsub(/([a-z\d])([A-Z])/, '\1 \2').capitalize
40
+ end
41
+
42
+ def error_message(exception)
43
+ "#{exception_name(exception)}: #{exception.message}"
44
+ end
45
+
46
+ attr_reader :parser, :config_updater, :loader, :help_generator,
47
+ :version_string, :debug_checker, :rebooter,
48
+ :happy_exiter_class, :sad_exiter_class
49
+
50
+ # rubocop:disable Metrics/ParameterLists
51
+ def initialize(parser: Params::Parser.new,
52
+ config_updater: Config.method(:update!),
53
+ loader: KexecPatchingLoader.new,
54
+ help_generator: Params::HelpGenerator.new,
55
+ version_string: "#{PROGRAM_NAME} #{VERSION}",
56
+ debug_checker: LazyConfig.debug,
57
+ rebooter: Rebooter.new,
58
+ happy_exiter_class: HappyExiter,
59
+ sad_exiter_class: SadExiter)
60
+ @parser = parser
61
+ @config_updater = config_updater
62
+ @loader = loader
63
+ @help_generator = help_generator
64
+ @version_string = version_string
65
+ @debug_checker = debug_checker
66
+ @rebooter = rebooter
67
+ @happy_exiter_class = happy_exiter_class
68
+ @sad_exiter_class = sad_exiter_class
69
+ end
70
+ # rubocop:enable Metrics/ParameterLists
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CryptReboot
4
+ module Cli
5
+ # Exit with error message
6
+ class SadExiter < Exiter
7
+ private
8
+
9
+ def initialize(text)
10
+ super(text, status: 1, stream: $stderr)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CryptReboot
4
+ # Command Line Interface
5
+ module Cli
6
+ PROGRAM_NAME = 'cryptreboot'
7
+ end
8
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CryptReboot
4
+ # Concatenate files into one
5
+ class Concatenator
6
+ def call(*files, to:)
7
+ runner.call(tool, *files, output_file: to)
8
+ end
9
+
10
+ private
11
+
12
+ def tool
13
+ lazy_tool.call
14
+ end
15
+
16
+ attr_reader :lazy_tool, :runner
17
+
18
+ def initialize(lazy_tool: LazyConfig.cat_path,
19
+ runner: Runner::NoResult.new)
20
+ @lazy_tool = lazy_tool
21
+ @runner = runner
22
+ end
23
+ end
24
+ end