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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +3 -0
- data/LICENSE.txt +21 -0
- data/README.md +169 -0
- data/exe/cryptreboot +9 -0
- data/lib/basic_loader.rb +60 -0
- data/lib/crypt_reboot/boot_config.rb +24 -0
- data/lib/crypt_reboot/cli/exiter.rb +25 -0
- data/lib/crypt_reboot/cli/happy_exiter.rb +14 -0
- data/lib/crypt_reboot/cli/params/definition.rb +125 -0
- data/lib/crypt_reboot/cli/params/flattener.rb +24 -0
- data/lib/crypt_reboot/cli/params/help_generator.rb +22 -0
- data/lib/crypt_reboot/cli/params/parser.rb +29 -0
- data/lib/crypt_reboot/cli/params_parsing_executor.rb +73 -0
- data/lib/crypt_reboot/cli/sad_exiter.rb +14 -0
- data/lib/crypt_reboot/cli.rb +8 -0
- data/lib/crypt_reboot/concatenator.rb +24 -0
- data/lib/crypt_reboot/config.rb +20 -0
- data/lib/crypt_reboot/crypt_tab/deserializer.rb +28 -0
- data/lib/crypt_reboot/crypt_tab/entry.rb +39 -0
- data/lib/crypt_reboot/crypt_tab/entry_deserializer.rb +51 -0
- data/lib/crypt_reboot/crypt_tab/entry_serializer.rb +21 -0
- data/lib/crypt_reboot/crypt_tab/keyfile_locator.rb +21 -0
- data/lib/crypt_reboot/crypt_tab/luks_to_plain_converter.rb +39 -0
- data/lib/crypt_reboot/crypt_tab/serializer.rb +29 -0
- data/lib/crypt_reboot/files_generator.rb +44 -0
- data/lib/crypt_reboot/files_writer.rb +17 -0
- data/lib/crypt_reboot/gziper.rb +22 -0
- data/lib/crypt_reboot/initramfs/archiver.rb +35 -0
- data/lib/crypt_reboot/initramfs/decompressor/intolerant_decompressor.rb +65 -0
- data/lib/crypt_reboot/initramfs/decompressor/tolerant_decompressor.rb +28 -0
- data/lib/crypt_reboot/initramfs/decompressor.rb +12 -0
- data/lib/crypt_reboot/initramfs/extractor.rb +36 -0
- data/lib/crypt_reboot/initramfs/patcher.rb +47 -0
- data/lib/crypt_reboot/initramfs_patch_squeezer.rb +28 -0
- data/lib/crypt_reboot/instantiable_config.rb +52 -0
- data/lib/crypt_reboot/kexec/loader.rb +30 -0
- data/lib/crypt_reboot/kexec_patching_loader.rb +28 -0
- data/lib/crypt_reboot/lazy_config.rb +22 -0
- data/lib/crypt_reboot/luks/checker.rb +27 -0
- data/lib/crypt_reboot/luks/data.rb +37 -0
- data/lib/crypt_reboot/luks/data_fetcher.rb +30 -0
- data/lib/crypt_reboot/luks/dumper/luks_v1_parser.rb +64 -0
- data/lib/crypt_reboot/luks/dumper/luks_v2_parser.rb +62 -0
- data/lib/crypt_reboot/luks/dumper.rb +30 -0
- data/lib/crypt_reboot/luks/key_fetcher.rb +50 -0
- data/lib/crypt_reboot/luks/version_detector.rb +32 -0
- data/lib/crypt_reboot/passphrase_asker.rb +15 -0
- data/lib/crypt_reboot/patched_initramfs_generator.rb +21 -0
- data/lib/crypt_reboot/rebooter.rb +20 -0
- data/lib/crypt_reboot/runner/binary.rb +12 -0
- data/lib/crypt_reboot/runner/boolean.rb +18 -0
- data/lib/crypt_reboot/runner/generic.rb +46 -0
- data/lib/crypt_reboot/runner/lines.rb +13 -0
- data/lib/crypt_reboot/runner/no_result.rb +13 -0
- data/lib/crypt_reboot/runner/text.rb +12 -0
- data/lib/crypt_reboot/runner.rb +8 -0
- data/lib/crypt_reboot/safe_temp/directory.rb +28 -0
- data/lib/crypt_reboot/safe_temp/file_name.rb +23 -0
- data/lib/crypt_reboot/safe_temp/mounter.rb +33 -0
- data/lib/crypt_reboot/single_assign_restricted_map.rb +26 -0
- data/lib/crypt_reboot/version.rb +5 -0
- data/lib/crypt_reboot.rb +17 -0
- metadata +138 -0
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CryptReboot
|
4
|
+
# Patch initramfs and load it along with kernel using kexec,
|
5
|
+
# so it is ready to be executed.
|
6
|
+
class KexecPatchingLoader
|
7
|
+
def call(boot_config = BootConfig.new(
|
8
|
+
kernel: Config.kernel,
|
9
|
+
initramfs: Config.initramfs,
|
10
|
+
cmdline: Config.cmdline
|
11
|
+
))
|
12
|
+
generator.call(boot_config.initramfs) do |patched_initramfs|
|
13
|
+
patched_boot_config = boot_config.with_initramfs(patched_initramfs)
|
14
|
+
loader.call(patched_boot_config)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
attr_reader :generator, :loader
|
21
|
+
|
22
|
+
def initialize(generator: PatchedInitramfsGenerator.new,
|
23
|
+
loader: Kexec::Loader.new)
|
24
|
+
@generator = generator
|
25
|
+
@loader = loader
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CryptReboot
|
4
|
+
# Return getter lambdas instead of configuration settings directly
|
5
|
+
class LazyConfig
|
6
|
+
class << self
|
7
|
+
def method_missing(method_name, *args, **kwargs, &block)
|
8
|
+
return super unless instance.respond_to?(method_name)
|
9
|
+
|
10
|
+
-> { instance.send(method_name, *args, **kwargs, &block) }
|
11
|
+
end
|
12
|
+
|
13
|
+
def respond_to_missing?(name, *_, **_)
|
14
|
+
instance.respond_to?(name)
|
15
|
+
end
|
16
|
+
|
17
|
+
def instance
|
18
|
+
Config.instance
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CryptReboot
|
4
|
+
module Luks
|
5
|
+
# Return true in case given device is LUKS (of given version is provided), false otherwise
|
6
|
+
class Checker
|
7
|
+
def call(headevice, version = :any)
|
8
|
+
args = version == :any ? [] : ['--type', version]
|
9
|
+
runner.call(binary, 'isLuks', 'none', '--header', headevice, *args)
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def binary
|
15
|
+
lazy_binary.call
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_reader :lazy_binary, :runner
|
19
|
+
|
20
|
+
def initialize(lazy_binary: LazyConfig.cryptsetup_path,
|
21
|
+
runner: Runner::Boolean.new)
|
22
|
+
@lazy_binary = lazy_binary
|
23
|
+
@runner = runner
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CryptReboot
|
4
|
+
module Luks
|
5
|
+
# Value-object with encryption parameters
|
6
|
+
class Data
|
7
|
+
attr_reader :cipher, :offset, :sector_size, :key
|
8
|
+
|
9
|
+
def ==(other)
|
10
|
+
cipher == other.cipher && offset == other.offset &&
|
11
|
+
sector_size == other.sector_size && key == other.key
|
12
|
+
end
|
13
|
+
|
14
|
+
def key_bits
|
15
|
+
key.bytesize * 8
|
16
|
+
end
|
17
|
+
|
18
|
+
def with_key(new_key)
|
19
|
+
self.class.new(
|
20
|
+
cipher: cipher,
|
21
|
+
offset: offset,
|
22
|
+
sector_size: sector_size,
|
23
|
+
key: new_key
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def initialize(cipher:, offset:, sector_size:, key: '')
|
30
|
+
@cipher = cipher
|
31
|
+
@offset = offset
|
32
|
+
@sector_size = sector_size
|
33
|
+
@key = key
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CryptReboot
|
4
|
+
module Luks
|
5
|
+
# Fetch LUKS data including key (user will be asked for passphrase)
|
6
|
+
class DataFetcher
|
7
|
+
def call(headevice)
|
8
|
+
version = detector.call(headevice)
|
9
|
+
data = dumper.call(headevice, version)
|
10
|
+
pass = asker.call("Enter passphrase to unlock #{headevice}: ")
|
11
|
+
key = key_fetcher.call(headevice, pass)
|
12
|
+
data.with_key(key)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
attr_reader :detector, :dumper, :asker, :key_fetcher
|
18
|
+
|
19
|
+
def initialize(detector: VersionDetector.new,
|
20
|
+
dumper: Dumper.new,
|
21
|
+
asker: PassphraseAsker.new,
|
22
|
+
key_fetcher: KeyFetcher.new)
|
23
|
+
@detector = detector
|
24
|
+
@dumper = dumper
|
25
|
+
@asker = asker
|
26
|
+
@key_fetcher = key_fetcher
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CryptReboot
|
4
|
+
module Luks
|
5
|
+
class Dumper
|
6
|
+
# Parse LUKS1
|
7
|
+
class LuksV1Parser
|
8
|
+
ParsingError = Class.new StandardError
|
9
|
+
|
10
|
+
def call(lines)
|
11
|
+
data = parse_lines(lines)
|
12
|
+
instantiate(data.to_h)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
SECTOR_SIZE = 512 # LUKS1 support only this sector size
|
18
|
+
private_constant :SECTOR_SIZE
|
19
|
+
|
20
|
+
def instantiate(raw)
|
21
|
+
data_class.new(
|
22
|
+
cipher: [raw.fetch(:cipher_name), raw.fetch(:cipher_mode)].join('-'),
|
23
|
+
offset: raw.fetch(:offset),
|
24
|
+
sector_size: SECTOR_SIZE
|
25
|
+
)
|
26
|
+
rescue KeyError => e
|
27
|
+
raise ParsingError, 'Parsing failed because of missing data', cause: e
|
28
|
+
end
|
29
|
+
|
30
|
+
def parse_lines(lines)
|
31
|
+
map_generator.call.tap do |result|
|
32
|
+
lines.each do |line|
|
33
|
+
update_result!(result, line: line)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def update_result!(result, line:)
|
39
|
+
case line
|
40
|
+
when /^Cipher name:\s+([\w-]+)$/
|
41
|
+
result[:cipher_name] = Regexp.last_match(1)
|
42
|
+
when /^Cipher mode:\s+([\w-]+)$/
|
43
|
+
result[:cipher_mode] = Regexp.last_match(1)
|
44
|
+
when /^Payload offset:\s+(\d+)$/
|
45
|
+
# LUKS1 provides offset in sectors
|
46
|
+
result[:offset] = Regexp.last_match(1).to_i * SECTOR_SIZE
|
47
|
+
end
|
48
|
+
rescue duplicate_exception => e
|
49
|
+
raise ParsingError, "Parsing failed on: `#{line}`", cause: e
|
50
|
+
end
|
51
|
+
|
52
|
+
attr_reader :data_class, :map_generator, :duplicate_exception
|
53
|
+
|
54
|
+
def initialize(data_class: Data,
|
55
|
+
map_generator: -> { SingleAssignRestrictedMap.new },
|
56
|
+
duplicate_exception: SingleAssignRestrictedMap::AlreadyAssigned)
|
57
|
+
@data_class = data_class
|
58
|
+
@map_generator = map_generator
|
59
|
+
@duplicate_exception = duplicate_exception
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CryptReboot
|
4
|
+
module Luks
|
5
|
+
class Dumper
|
6
|
+
# Parse LUKS2
|
7
|
+
class LuksV2Parser
|
8
|
+
ParsingError = Class.new StandardError
|
9
|
+
|
10
|
+
def call(lines)
|
11
|
+
data = parse_lines(lines)
|
12
|
+
instantiate(data.to_h)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def instantiate(args)
|
18
|
+
data_class.new(**args)
|
19
|
+
rescue ArgumentError => e
|
20
|
+
raise ParsingError, 'Parsing failed because of missing data', cause: e
|
21
|
+
end
|
22
|
+
|
23
|
+
def parse_lines(lines)
|
24
|
+
map_generator.call.tap do |result|
|
25
|
+
section_found = false
|
26
|
+
lines.each do |line|
|
27
|
+
if section_found
|
28
|
+
break if line =~ /^\w/
|
29
|
+
|
30
|
+
update_result!(result, line: line)
|
31
|
+
end
|
32
|
+
line =~ /^Data segments:/ && section_found = true
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def update_result!(result, line:)
|
38
|
+
case line
|
39
|
+
when /offset:\s+(\d+) \[bytes\]/
|
40
|
+
result[:offset] = Regexp.last_match(1).to_i
|
41
|
+
when /cipher:\s+([\w-]+)$/
|
42
|
+
result[:cipher] = Regexp.last_match(1)
|
43
|
+
when /sector:\s+(\d+) \[bytes\]/
|
44
|
+
result[:sector_size] = Regexp.last_match(1).to_i
|
45
|
+
end
|
46
|
+
rescue duplicate_exception => e
|
47
|
+
raise ParsingError, "Parsing failed on: `#{line}`", cause: e
|
48
|
+
end
|
49
|
+
|
50
|
+
attr_reader :data_class, :map_generator, :duplicate_exception
|
51
|
+
|
52
|
+
def initialize(data_class: Data,
|
53
|
+
map_generator: -> { SingleAssignRestrictedMap.new },
|
54
|
+
duplicate_exception: SingleAssignRestrictedMap::AlreadyAssigned)
|
55
|
+
@data_class = data_class
|
56
|
+
@map_generator = map_generator
|
57
|
+
@duplicate_exception = duplicate_exception
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CryptReboot
|
4
|
+
module Luks
|
5
|
+
# Depending on LUKS version, delegates parsing to different parser
|
6
|
+
class Dumper
|
7
|
+
def call(headevice, version)
|
8
|
+
dump = runner.call(binary, 'luksDump', 'none', '--header', headevice)
|
9
|
+
parser = parsers.fetch(version)
|
10
|
+
parser.call(dump)
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def binary
|
16
|
+
lazy_binary.call
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_reader :lazy_binary, :runner, :parsers
|
20
|
+
|
21
|
+
def initialize(lazy_binary: LazyConfig.cryptsetup_path,
|
22
|
+
runner: Runner::Lines.new,
|
23
|
+
parsers: { 'LUKS2' => LuksV2Parser.new, 'LUKS1' => LuksV1Parser.new })
|
24
|
+
@lazy_binary = lazy_binary
|
25
|
+
@runner = runner
|
26
|
+
@parsers = parsers
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CryptReboot
|
4
|
+
module Luks
|
5
|
+
# Fetch LUKS key
|
6
|
+
class KeyFetcher
|
7
|
+
InvalidPassphrase = Class.new StandardError
|
8
|
+
|
9
|
+
def call(headevice, passphrase)
|
10
|
+
temp_provider.call do |key_file|
|
11
|
+
luks_dump(headevice, key_file, passphrase)
|
12
|
+
file_reader.call(key_file)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def luks_dump(headevice, master_key_file, passphrase)
|
19
|
+
runner.call(
|
20
|
+
binary, 'luksDump', 'none', '--header', headevice,
|
21
|
+
'--dump-master-key', '--master-key-file', master_key_file,
|
22
|
+
'--key-file', '-',
|
23
|
+
input: passphrase
|
24
|
+
)
|
25
|
+
rescue run_exception
|
26
|
+
# For simplicity sake let's assume it's invalid passphrase.
|
27
|
+
# Other errors such as invalid device/header were handled by previous validation.
|
28
|
+
raise InvalidPassphrase
|
29
|
+
end
|
30
|
+
|
31
|
+
def binary
|
32
|
+
lazy_binary.call
|
33
|
+
end
|
34
|
+
|
35
|
+
attr_reader :lazy_binary, :runner, :run_exception, :file_reader, :temp_provider
|
36
|
+
|
37
|
+
def initialize(lazy_binary: LazyConfig.cryptsetup_path,
|
38
|
+
runner: Runner::Lines.new,
|
39
|
+
run_exception: Runner::ExitError,
|
40
|
+
file_reader: File.method(:read),
|
41
|
+
temp_provider: SafeTemp::FileName.new)
|
42
|
+
@lazy_binary = lazy_binary
|
43
|
+
@runner = runner
|
44
|
+
@run_exception = run_exception
|
45
|
+
@file_reader = file_reader
|
46
|
+
@temp_provider = temp_provider
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CryptReboot
|
4
|
+
module Luks
|
5
|
+
# Return LUKS version or raise the exception if given file doesn't represent a valid LUKS device
|
6
|
+
class VersionDetector
|
7
|
+
Error = Class.new StandardError
|
8
|
+
NotLuks = Class.new Error
|
9
|
+
UnsupportedVersion = Class.new Error
|
10
|
+
|
11
|
+
def call(headevice)
|
12
|
+
version = supported_versions.find do |tested_version|
|
13
|
+
checker.call(headevice, tested_version)
|
14
|
+
end
|
15
|
+
return version if version
|
16
|
+
raise UnsupportedVersion if checker.call(headevice)
|
17
|
+
|
18
|
+
raise NotLuks
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
attr_reader :checker, :supported_versions
|
24
|
+
|
25
|
+
def initialize(checker: Checker.new,
|
26
|
+
supported_versions: %w[LUKS2 LUKS1])
|
27
|
+
@checker = checker
|
28
|
+
@supported_versions = supported_versions
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CryptReboot
|
4
|
+
# Yield path to patched version of provided initramfs
|
5
|
+
class PatchedInitramfsGenerator
|
6
|
+
def call(initramfs_path, &block)
|
7
|
+
patch = squeezer.call(initramfs_path)
|
8
|
+
patcher.call(initramfs_path, patch, &block)
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
attr_reader :squeezer, :patcher
|
14
|
+
|
15
|
+
def initialize(squeezer: InitramfsPatchSqueezer.new,
|
16
|
+
patcher: Initramfs::Patcher.new)
|
17
|
+
@squeezer = squeezer
|
18
|
+
@patcher = patcher
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CryptReboot
|
4
|
+
# Perform the reboot or exit (doesn't return)
|
5
|
+
class Rebooter
|
6
|
+
def call(act = !Config.prepare_only)
|
7
|
+
act ? runner.call : exiter.call
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
attr_reader :runner, :exiter
|
13
|
+
|
14
|
+
def initialize(runner: -> { Process.exec Config.reboot_path },
|
15
|
+
exiter: -> { exit 0 })
|
16
|
+
@runner = runner
|
17
|
+
@exiter = exiter
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CryptReboot
|
4
|
+
module Runner
|
5
|
+
# Return true or false, depending if command succeeded or failed
|
6
|
+
class Boolean < Generic
|
7
|
+
def call(...)
|
8
|
+
super(...).success?
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def initialize(**opts)
|
14
|
+
super(**opts.merge(run_method: :run!))
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'tty-command'
|
4
|
+
|
5
|
+
module CryptReboot
|
6
|
+
module Runner
|
7
|
+
# Run an external process. Abstract class, use descendants.
|
8
|
+
class Generic
|
9
|
+
private
|
10
|
+
|
11
|
+
def call(*args, input: nil, output_file: nil, binary: false)
|
12
|
+
options = build_options(input, output_file, binary)
|
13
|
+
cmd.send(run_method, *args, **options)
|
14
|
+
rescue exceptions[:exit] => e
|
15
|
+
raise ExitError, cause: e
|
16
|
+
rescue exceptions[:not_found] => e
|
17
|
+
raise CommandNotFound, cause: e
|
18
|
+
end
|
19
|
+
|
20
|
+
def build_options(input, output_file, binary)
|
21
|
+
{}.tap do |options|
|
22
|
+
options[:input] = input if input
|
23
|
+
options[:out] = output_file if output_file
|
24
|
+
options[:binmode] = true if binary
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def cmd
|
29
|
+
lazy_cmd.call
|
30
|
+
end
|
31
|
+
|
32
|
+
attr_reader :lazy_cmd, :run_method, :exceptions
|
33
|
+
|
34
|
+
def initialize(lazy_cmd: -> { TTY::Command.new(uuid: false, printer: Config.debug ? :pretty : :null) },
|
35
|
+
run_method: :run,
|
36
|
+
exceptions: {
|
37
|
+
exit: TTY::Command::ExitError,
|
38
|
+
not_found: Errno::ENOENT
|
39
|
+
})
|
40
|
+
@lazy_cmd = lazy_cmd
|
41
|
+
@run_method = run_method
|
42
|
+
@exceptions = exceptions
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'tmpdir'
|
4
|
+
|
5
|
+
module CryptReboot
|
6
|
+
module SafeTemp
|
7
|
+
# Create temporary directory, mounts tmpfs and yields tmp dir location.
|
8
|
+
# Make sure to cleanup afterwards.
|
9
|
+
class Directory
|
10
|
+
def call
|
11
|
+
tmp_maker.call do |dir|
|
12
|
+
mounter.call(dir) do
|
13
|
+
yield dir
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
attr_reader :mounter, :tmp_maker
|
21
|
+
|
22
|
+
def initialize(mounter: Mounter.new, tmp_maker: Dir.method(:mktmpdir))
|
23
|
+
@mounter = mounter
|
24
|
+
@tmp_maker = tmp_maker
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CryptReboot
|
4
|
+
module SafeTemp
|
5
|
+
# Yield non-existing temporary file name located in a safe dir.
|
6
|
+
# Afterwards the directory containing this file is deleted.
|
7
|
+
class FileName
|
8
|
+
def call(name = 'file')
|
9
|
+
dir_provider.call do |dir|
|
10
|
+
yield File.join(dir, name)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
attr_reader :dir_provider
|
17
|
+
|
18
|
+
def initialize(dir_provider: Directory.new)
|
19
|
+
@dir_provider = dir_provider
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CryptReboot
|
4
|
+
module SafeTemp
|
5
|
+
# Mount tmpfs at the given mount point, yield and unmount
|
6
|
+
class Mounter
|
7
|
+
def call(dir, &block)
|
8
|
+
mounter.call(dir)
|
9
|
+
run(dir, &block)
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def run(dir, &block)
|
15
|
+
block.call
|
16
|
+
ensure
|
17
|
+
umounter.call(dir)
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_reader :runner, :mounter, :umounter
|
21
|
+
|
22
|
+
def initialize(runner: Runner::NoResult.new,
|
23
|
+
mounter: lambda { |dir|
|
24
|
+
runner.call(Config.mount_path, '-t', 'tmpfs', '-o', 'mode=700', 'none', dir)
|
25
|
+
},
|
26
|
+
umounter: ->(dir) { runner.call(Config.umount_path, dir) })
|
27
|
+
@runner = runner
|
28
|
+
@mounter = mounter
|
29
|
+
@umounter = umounter
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|