crypt_reboot 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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