diff --git a/boot/init/lib/mounting.rb b/boot/init/lib/mounting.rb index 864a97a8..6b4e1db2 100644 --- a/boot/init/lib/mounting.rb +++ b/boot/init/lib/mounting.rb @@ -75,6 +75,10 @@ module Mounting [mount_point, task] end.to_h auto_depend_mount_points(mount_points) + + (Configuration["luksDevices"] or []).each do |mapper, info| + Tasks::Luks.new(info["device"], mapper) + end end end diff --git a/boot/init/lib/progress.rb b/boot/init/lib/progress.rb index e2a35025..96060c8b 100644 --- a/boot/init/lib/progress.rb +++ b/boot/init/lib/progress.rb @@ -1,44 +1,120 @@ # Progress-reporting plumbing module Progress - SOCKET = "/run/mobile-nixos-init.socket" + SOCKET_PREFIX = "/run/mobile-nixos-init" + # Starts the queue sockets. + # This is waiting for /run/ to be available. + # It needs to be possible to let some consumers (e.g. splash) alive from + # stage-1 and waiting for fresh messages from stage-2. + # A stage-2 process could ask that splash to "hand-off" to a stage-2 splash. def self.start() @progress = 0 $logger.debug("Starting progress IPC through ZeroMQ") - $logger.debug(" -> #{SOCKET}") - @pub = ZMQ::Pub.new("ipc://#{SOCKET}") + + $logger.debug(" -> messages: #{SOCKET_PREFIX}") + @messages_socket = ZMQ::Pub.new("ipc://#{SOCKET_PREFIX}-messages") + + $logger.debug(" -> replies: #{SOCKET_PREFIX}") + @replies_socket = ZMQ::Sub.new("ipc://#{SOCKET_PREFIX}-replies", "") end - # Prefer not sending messages directly, rather use the helpers. - def self.publish(msg) - msg = msg.to_json - if @pub + # Given values (in a Hash), it will update the state with them, and send the + # updated state to the messages queue. + # +nil+ values are compacted out of the state. + def self.update(values) + @state ||= {} + @state.merge!(values).compact! + send_state() + end + + # Get a specific value from the state. + # This should be done as little as possible. + def self.get(attr) + @state ||= {} + @state[attr] + end + + # See +#get+ + def self.[](name) + get(name) + end + + # Send the current state over the messages socket. + def self.send_state() + msg = @state.to_json + if @messages_socket $logger.debug("[send] #{msg}") - @pub.send(msg) + @messages_socket.send(msg) else - $logger.debug("[send] Couldn't send #{msg}") + $logger.debug("[send] Socket not open yet.") + $logger.debug("[send] Couldn't send: #{msg}") end end - # Sets the progress to a specific amount - def self.set(amount) - @progress = amount - - publish({ - progress: @progress, - }) - end - # Executes the given block, showing the message beforehand, and removing the # message once done. - def self.with_message(msg) - publish({ - progress: @progress, - label: msg, - }) - yield - publish({ - progress: @progress, + def self.exec_with_message(label) + previous = get(:label) + update({label: label}) + ret = yield + update({label: previous}) + ret + end + + def self.ask(placeholder, label: nil) + identifier = "0x#{Random.rand(0xFFFFF).to_s(16)}" + + previous_label = get(:label) + Progress.update({label: label}) if label + + update(command: { + name: "ask", + identifier: identifier, + placeholder: placeholder, }) + + value = loop do + # Keep progress state updated for processes attaching late. + send_state() + value = + each_replies do |reply| + # A reply for the current question? + if reply and reply["type"] == "reply" and reply["identifier"] == identifier + break reply["value"] + else + nil + end + end + break value if value + + # Leave some breathing room to the CPU! + sleep(0.1) + end + + update({label: previous_label}) + + value + end + + # Read one reply + # If none are available, returns nil + def self.read_reply() + begin + msg = @replies_socket.recv(LibZMQ::DONTWAIT).to_str + $logger.debug("[recv] #{msg}") + JSON.parse(msg) + rescue Errno::EWOULDBLOCK + # No message? + nil + end + end + + # Reads replies until there are none + def self.each_replies() + loop do + msg = read_reply + break unless msg + yield msg + end end end diff --git a/boot/init/lib/system.rb b/boot/init/lib/system.rb index 4e827cc3..144b8ca3 100644 --- a/boot/init/lib/system.rb +++ b/boot/init/lib/system.rb @@ -51,6 +51,30 @@ module System Kernel.spawn(*args) end + # Runs a long-running task in the background while we keep the progress + # reporting active. + def self.run_long_running(*args) + pretty_command = prettify_command(*args) + pid = System.spawn(*args) + ret = nil + + loop do + # Update progress + Progress.send_state() + # Look at the status + break if ret = Process.wait(pid, Process::WNOHANG) + # Don't loop too tightly + sleep(0.1) + end + + status = $?.exitstatus + if status == 127 + raise CommandNotFound.new("Command not found... `#{pretty_command}` (#{status})") + elsif !$?.success? + raise CommandError.new("Command failed... `#{pretty_command}` (#{status})") + end + end + # Discovers the location of given program name. def self.which(program_name) ENV["PATH"].split(":").each do |path| diff --git a/boot/init/lib/task.rb b/boot/init/lib/task.rb index a9ab7fc1..ba02b46b 100644 --- a/boot/init/lib/task.rb +++ b/boot/init/lib/task.rb @@ -43,7 +43,7 @@ module Tasks # Update the current progress count = @tasks.length.to_f - Progress.set((100 * (1 - (todo.length / count))).ceil) + Progress.update({progress: (100 * (1 - (todo.length / count))).ceil}) todo.each do |task| if task._try_run_task then diff --git a/boot/init/tasks/auto_resize.rb b/boot/init/tasks/auto_resize.rb index 6564d7ea..faabad3d 100644 --- a/boot/init/tasks/auto_resize.rb +++ b/boot/init/tasks/auto_resize.rb @@ -11,7 +11,7 @@ class Tasks::AutoResize < Task def run() log("Resizing #{@device}...") if @type.match(/^ext[234]$/) - Progress.with_message("Verifying #{@device}...") do + Progress.exec_with_message("Verifying #{@device}...") do # TODO: Understand the actual underlying issue with e2fsck. # It seems `e2fsck` succeeds, according to the output, but has a >0 exit # status. Running it again in those situations is a no-op, which is weird @@ -19,14 +19,14 @@ class Tasks::AutoResize < Task # This is why we unconditionally run it once, then twice. # The second will hopefully abort the boot if it fails too. begin - System.run("e2fsck", "-fp", @device) + System.run_long_running("e2fsck", "-fp", @device) rescue System::CommandError $logger.info("Re-running e2fsc...") - System.run("e2fsck", "-fp", @device) + System.run_long_running("e2fsck", "-fp", @device) end end - Progress.with_message("Resizing #{@device}...") do - System.run("resize2fs", "-f", @device) + Progress.exec_with_message("Resizing #{@device}...") do + System.run_long_running("resize2fs", "-f", @device) end else $logger.warn("Cannot resize #{@type}... filesystem left untouched.") diff --git a/boot/init/tasks/luks.rb b/boot/init/tasks/luks.rb new file mode 100644 index 00000000..8f9d78cd --- /dev/null +++ b/boot/init/tasks/luks.rb @@ -0,0 +1,64 @@ +# Opens LUKS devices +class Tasks::Luks < Task + attr_reader :source + attr_reader :mapper + + TRIES = 10 + + class ExistingLuksTask < StandardError + end + + class CouldNotUnlock < StandardError + end + + def self.register(mapper, instance) + @registry ||= {} + unless @registry[mapper].nil? then + raise ExistingLuksTask.new("LUKS task for '#{mapper}' already exists.") + end + @registry[mapper] = instance + end + + def self.registry() + @registry + end + + def initialize(source, mapper) + @source = source + @mapper = mapper + + add_dependency(:Task, Tasks::UDev.instance) + add_dependency(:Files, source) + add_dependency(:Mount, "/run") + add_dependency(:Target, :Environment) + self.class.register(@mapper, self) + end + + def run() + FileUtils.mkdir_p("/run/cryptsetup") + + TRIES.times do + passphrase = Progress.ask("Passphrase for #{mapper}") + + begin + Progress.exec_with_message("Checking...") do + # TODO: implement with process redirection rather than shelling out + System.run("echo #{passphrase.shellescape} | exec cryptsetup luksOpen #{source.shellescape} #{mapper.shellescape}") + end + Progress.update({label: nil}) + + # If we're there, we're done! + return + rescue System::CommandError + Progress.update({label: "Wrong passphrase given..."}) + end + end + + # We failed multiple times. + raise CouldNotUnlock.new("Could not unlock #{source}; tried #{TRIES} times.") + end + + def name() + "#{super}(#{source}, #{mapper})" + end +end diff --git a/boot/init/tasks/splash.rb b/boot/init/tasks/splash.rb index 55bb03fc..a17051b3 100644 --- a/boot/init/tasks/splash.rb +++ b/boot/init/tasks/splash.rb @@ -24,14 +24,17 @@ class Tasks::Splash < SingletonTask # Implementation details-y; ask for the splash applet to be exited. def quit(reason) + # Ensures the progress is shown + Progress.update({progress: 100, label: reason}) + + # Command it to quit + Progress.update({command: {name: "quit"}}) + # Ensures that if for any reason the splash didn't start in time for the # socket to listen to this message, that we'll be quitting it. loop do - # Ensures the progress is shown - Progress.publish({progress: 100, label: reason}) - # Command it to quit - Progress.publish("quit") - + # Repeatedly send the current state (which has the quit command). + Progress.send_state() # If it has quit, break out! break if Process.wait(@pid, Process::WNOHANG) diff --git a/boot/splash/lib/keyboard.rb b/boot/splash/lib/keyboard.rb new file mode 100644 index 00000000..ea81ed20 --- /dev/null +++ b/boot/splash/lib/keyboard.rb @@ -0,0 +1,61 @@ +# Wraps a raw +lv_keyboard+ in minimal helpers +# This is intended to be used as a singleton instance, where you can +# "re-parent" the keyboard as needed. +class Keyboard < LVGUI::Widget + include Singleton + + private + + def initialize() + @shown = false + # Attach the keyboard to the current active screen, by default. + super(LVGL::LVKeyboard.new(LVGL::LVDisplay.get_scr_act())) + set_cursor_manage(true) + + get_style(LVGL::KB_STYLE::BG).dup.tap do |style| + set_style(LVGL::KB_STYLE::BG, style) + padding = 4 + style.body_padding_top = padding + style.body_padding_left = padding + style.body_padding_right = padding + style.body_padding_bottom = padding + style.body_padding_inner = padding + end + set_y(get_parent.get_height()) + end + + public + + def set_height(value) + super(value) + _set_position() + end + + def show() + _animate_y(get_parent.get_height() - get_height()) + end + + def hide() + _animate_y(get_parent.get_height) + end + + def _set_position() + if @shown + _animate_y(get_parent.get_height() - get_height()) + else + _animate_y(get_parent.get_height()) + end + end + + def _animate_y(ending) + LVGL::LVAnim.new().tap do |anim| + anim.set_exec_cb(self, :lv_obj_set_y) + anim.set_time(300, 0) + anim.set_values(get_y(), ending) + anim.set_path_cb(LVGL::LVAnim::Path::EASE_OUT) + + # Launch the animation + anim.create() + end + end +end diff --git a/boot/splash/lib/progress_bar.rb b/boot/splash/lib/progress_bar.rb index 334cc2eb..34fcbca7 100644 --- a/boot/splash/lib/progress_bar.rb +++ b/boot/splash/lib/progress_bar.rb @@ -60,6 +60,7 @@ class ProgressBar < LVGUI::Widget end def progress=(val) + val = 100 if val > 100 @changed = true @progress_amount = val refresh_progress() diff --git a/boot/splash/lib/text_area.rb b/boot/splash/lib/text_area.rb new file mode 100644 index 00000000..7eab7e83 --- /dev/null +++ b/boot/splash/lib/text_area.rb @@ -0,0 +1,90 @@ +# Wraps a raw +lv_ta+ in minimal helpers +class TextArea < LVGUI::Widget + attr_reader :hidden + + def initialize(parent) + super(LVGL::LVTextArea.new(parent)) + + @hidden = false + set_text("") + set_placeholder_text("") + set_pwd_mode(true) + set_one_line(true) + set_opa_scale_enable(true) + get_style(LVGL::TA_STYLE::BG).dup.tap do |style| + set_style(LVGL::TA_STYLE::BG, style) + style.body_main_color = 0xFF000000 + style.body_grad_color = 0xFF000000 + style.body_radius = 5 + style.body_border_color = 0xFFFFFFFF + style.body_border_width = 3 + style.body_border_opa = 255 + style.text_color = 0xFFFFFFFF + end + get_style(LVGL::TA_STYLE::PLACEHOLDER).dup.tap do |style| + set_style(LVGL::TA_STYLE::PLACEHOLDER, style) + style.text_color = 0xFFAAAAAA + end + set_cursor_type(get_cursor_type() | LVGL::CURSOR::HIDDEN) + + self.event_handler = -> (event) do + return if hidden() + case event + when LVGL::EVENT::CLICKED + Keyboard.instance.set_ta(self) + Keyboard.instance.show() + when LVGL::EVENT::INSERT + # Not exactly right, but right enough. + char = LVGL::FFI.lv_event_get_data().to_str(1) + # Assume there is only one input. + # Also assume Enter sends; that it is a single line. + if char == "\n" + Keyboard.instance.set_ta(nil) + Keyboard.instance.hide() + # Create a new string + # get_text() gives us a Fiddle::Pointer (leaky abstraction!!!) + value = "#{get_text()}" + @on_submit.call(value) if @on_submit + hide() + end + #else + # puts "Unhandled event for #{self}: #{LVGL::EVENT.from_value(event)}" + end + end + end + + def show() + @hidden = false + LVGL::LVAnim.new().tap do |anim| + anim.set_exec_cb(self, :lv_obj_set_opa_scale) + anim.set_time(FADE_LENGTH, 0) + anim.set_values(0, 255) + anim.set_path_cb(LVGL::LVAnim::Path::EASE_OUT) + + # Launch the animation + anim.create() + end + end + + def hide(skip_animation: false) + @hidden = true + if skip_animation + set_opa_scale(0) + return + end + + LVGL::LVAnim.new().tap do |anim| + anim.set_exec_cb(self, :lv_obj_set_opa_scale) + anim.set_time(FADE_LENGTH, 0) + anim.set_values(255, 0) + anim.set_path_cb(LVGL::LVAnim::Path::EASE_IN) + + # Launch the animation + anim.create() + end + end + + def on_submit=(cb) + @on_submit = cb + end +end diff --git a/boot/splash/lib/ui.rb b/boot/splash/lib/ui.rb index e77334e6..f9d88eaf 100644 --- a/boot/splash/lib/ui.rb +++ b/boot/splash/lib/ui.rb @@ -13,22 +13,28 @@ end class UI attr_reader :screen attr_reader :progress_bar + attr_reader :ask_identifier # As this is not using BaseWindow, LVGUI::init isn't handled for us. LVGUI.init() def initialize() add_screen + add_page # Biggest of horizontal or vertical; a percent. @unit = ([@screen.get_width, @screen.get_height].max * 0.01).ceil add_logo add_progress_bar add_label + + add_textarea + add_keyboard + add_cover # last end def add_label() - @label = LVGL::LVLabel.new(@screen) + @label = LVGL::LVLabel.new(@page) @label.get_style(LVGL::LABEL_STYLE::MAIN).dup.tap do |style| @label.set_style(LVGL::LABEL_STYLE::MAIN, style) style.text_color = 0xFFFFFFFF @@ -36,7 +42,7 @@ class UI @label.set_long_mode(LVGL::LABEL_LONG::BREAK) @label.set_align(LVGL::LABEL_ALIGN::CENTER) - @label.set_width(@screen.get_width * 0.9) + @label.set_width(@page.get_width * 0.9) @label.set_pos(*center(@label, 0, 5*@unit)) @label.set_text("") end @@ -48,15 +54,15 @@ class UI file = "./logo.svg" if File.exist?("./logo.svg") return unless file - if @screen.get_height > @screen.get_width + if @page.get_height > @page.get_width # 80% of the width - LVGL::Hacks::LVNanoSVG.resize_next_width((@screen.get_width * 0.8).to_i) + LVGL::Hacks::LVNanoSVG.resize_next_width((@page.get_width * 0.8).to_i) else # 15% of the height - LVGL::Hacks::LVNanoSVG.resize_next_height((@screen.get_height * 0.15).to_i) + LVGL::Hacks::LVNanoSVG.resize_next_height((@page.get_height * 0.15).to_i) end - @logo = LVGL::LVImage.new(@screen) + @logo = LVGL::LVImage.new(@page) @logo.set_src(file) # Position the logo @@ -64,9 +70,9 @@ class UI end def add_progress_bar() - @progress_bar = ProgressBar.new(@screen) + @progress_bar = ProgressBar.new(@page) @progress_bar.set_height(3 * @unit) - @progress_bar.set_width(@screen.get_width * 0.7) + @progress_bar.set_width(@page.get_width * 0.7) @progress_bar.set_pos(*center(@progress_bar)) end @@ -81,14 +87,27 @@ class UI end end + def add_page() + @page = LVGL::LVContainer.new(@screen) + @page.set_width(@screen.get_width) + @page.set_height(@screen.get_height) + @page.get_style(LVGL::CONT_STYLE::MAIN).dup.tap do |style| + @page.set_style(LVGL::CONT_STYLE::MAIN, style) + style.body_main_color = 0xFF000000 + style.body_grad_color = 0xFF000000 + style.body_border_width = 0 + end + end + # Used to handle fade-in/fade-out # This is because opacity handles multiple overlaid objects wrong. def add_cover() @cover = LVGL::LVObject.new(@screen) # Make it so we can use the opacity to fade in/out - @cover.set_opa_scale_enable(1) + @cover.set_opa_scale_enable(true) @cover.set_width(@screen.get_width()) @cover.set_height(@screen.get_height()) + @cover.set_click(false) @cover.get_style().dup.tap do |style| @cover.set_style(style) @@ -101,6 +120,22 @@ class UI end end + def add_textarea() + @ta = TextArea.new(@page) + @ta.set_width(@page.get_width * 0.9) + @ta.set_pos(*center(@ta, 0, @unit * 14)) + # Always present, but initially hidden + @ta.hide(skip_animation: true) + end + + def add_keyboard() + @keyboard = Keyboard.instance() + # The keyboard is not added to the page; the page holds the elements that + # may move to ensure they're not covered by the keyboard. + @keyboard.set_parent(@screen) + @keyboard.set_height(@screen.get_width * 0.55) + end + def set_progress(amount) progress_bar.progress = amount end @@ -109,6 +144,39 @@ class UI @label.set_text(text) end + # +cb+ is a proc that wille be +#call+'d with the text once submitted. + def ask_user(placeholder: "", identifier: , cb:) + return if identifier == @ask_identifier + + @ask_identifier = identifier + @ta.set_placeholder_text(placeholder) + @ta.show() + @keyboard.set_ta(@ta) + @keyboard.show() + + bottom_space = @screen.get_height() - (@ta.get_y() + @ta.get_height()) + delta = bottom_space - @keyboard.get_height() - 3*@unit + offset_page(delta) if delta < 0 + + @ta.on_submit = ->(value) do + @ta.set_text("") + offset_page(0) + cb.call(value) + end + end + + def offset_page(delta) + LVGL::LVAnim.new().tap do |anim| + anim.set_exec_cb(@page, :lv_obj_set_y) + anim.set_time(300, 0) + anim.set_values(@page.get_y(), delta) + anim.set_path_cb(LVGL::LVAnim::Path::EASE_OUT) + + # Launch the animation + anim.create() + end + end + # Fade-in animation # Note that this looks like inverted logic because it is! # We're actually fading-out the cover! diff --git a/boot/splash/main.rb b/boot/splash/main.rb index 93fa36d1..5923a255 100644 --- a/boot/splash/main.rb +++ b/boot/splash/main.rb @@ -9,14 +9,17 @@ FADE_LENGTH = 400 PROGRESS_UPDATE_LENGTH = 500 VERBOSE = !!Args.get(:verbose, false) -SOCKET = File.expand_path(Args.get(:socket, "/run/mobile-nixos-init.socket")) +SOCKET = File.expand_path(Args.get(:socket, "/run/mobile-nixos-init")) # Create the UI ui = UI.new # Socket for status updates -puts "[splash] Listening on: ipc://#{SOCKET}" -$sub = ZMQ::Sub.new("ipc://#{SOCKET}", "") +puts "[splash] Listening on: ipc://#{SOCKET}-messages" +$messages = ZMQ::Sub.new("ipc://#{SOCKET}-messages", "") + +puts "[splash] Replying on: ipc://#{SOCKET}-replies" +$replies = ZMQ::Pub.new("ipc://#{SOCKET}-replies") # Initial fade-in ui.fade_in() @@ -28,7 +31,7 @@ LVGUI.main_loop do # Empty all messages from the queue before continuing. loop do begin - msg = JSON.parse($sub.recv(LibZMQ::DONTWAIT).to_str) + msg = JSON.parse($messages.recv(LibZMQ::DONTWAIT).to_str) rescue Errno::EWOULDBLOCK # No messages left? break out! break @@ -39,25 +42,43 @@ LVGUI.main_loop do p msg end - # We might have a special command, if we got a String rather than a Hash. - if msg.is_a? String then - if msg == "quit" + # We might have a special command; handle it. + if msg["command"] then + command = msg["command"] + + case command["name"] + when "quit" ui.quit! + when "ask" + ui.ask_user(placeholder: command["placeholder"], identifier: command["identifier"], cb: ->(value) do + msg = { + type: "reply", + identifier: command["identifier"], + value: value, + }.to_json + + if VERBOSE + print "[splash:send] " + p msg + end + + $replies.send(msg) + end) else - $stderr.puts "[splash] Unexpected command #{msg}..." + $stderr.puts "[splash] Unexpected command #{command.to_json}..." end + end + + # Update the UI... + + # First updating the current progress + ui.set_progress(msg["progress"]) + + # And updating the label as needed. + if msg["label"] + ui.set_label(msg["label"]) else - # Update the UI... - - # First updating the current progress - ui.set_progress(msg["progress"]) - - # And updating the label as needed. - if msg["label"] - ui.set_label(msg["label"]) - else - ui.set_label("") - end + ui.set_label("") end end end diff --git a/examples/testing/README.md b/examples/testing/README.md new file mode 100644 index 00000000..4d601e35 --- /dev/null +++ b/examples/testing/README.md @@ -0,0 +1,13 @@ +Testing examples +================ + +These examples are made specifically to test features. + +They are not to be used as a normal system, as they likely have some fatal +flaws, either usability-wise (e.g. not usable at all) or security-wise (e.g. +default passwords). + +More details are available inside each testing systems directories. + +Though, please refer to those systems as reference implementation for +validating changes around their features under test! diff --git a/examples/testing/qemu-cryptsetup/README.md b/examples/testing/qemu-cryptsetup/README.md new file mode 100644 index 00000000..85b86f5d --- /dev/null +++ b/examples/testing/qemu-cryptsetup/README.md @@ -0,0 +1,45 @@ +`qemu-cryptsetup` +================= + +What does this test? +-------------------- + +Using the `hello` system, pre-configured to use the `qemu` system type. + +This tests: + + - Encryption passphrase at boot + +**This test is manual.** + +The passphrase in use is: + +``` +1234 +``` + + +Why is this scary? +------------------ + + - Secrets in the store! + - Well-known insecure passphrase + + +How is success defined? +----------------------- + +The `hello` system applet is booted-to after the user supplies the encryption +passphrase during boot. + + +How is success defined? +----------------------- + +Assuming you are `cd`'d into the root of a Mobile NixOS checkout: + +``` +nix-build ./examples/testing/qemu-cryptsetup && ./result +``` + +As always, be mindful of your `NIX_PATH`. diff --git a/examples/testing/qemu-cryptsetup/configuration.nix b/examples/testing/qemu-cryptsetup/configuration.nix new file mode 100644 index 00000000..561c0f2c --- /dev/null +++ b/examples/testing/qemu-cryptsetup/configuration.nix @@ -0,0 +1,68 @@ +{ config, lib, pkgs, ... }: + +let + # This is not a secure or safe way to create an encrypted drive in a build. + # This is SOLELY for testing purposes. + passphrase = "1234"; + uuid = "12345678-1234-1234-1234-123456789abc"; # heh + + # We are re-using the raw filesystem from the hello system. + rootfsExt4 = ( + import ../../hello { device = config.mobile.device.name; } + ).build.rootfs; + + # This is not a facility from the disk images builder because **it is really + # insecure to use**. + # So, for now, we have an implementation details-y way of producing an + # encrypted rootfs. + encryptedRootfs = pkgs.vmTools.runInLinuxVM ( + pkgs.runCommand "encrypted-rootfs" { + buildInputs = [ pkgs.cryptsetup ]; + } '' + (PS4=" $ "; set -x + mkdir -p /run/cryptsetup + mkdir -p $out + cd $out + + slack=32 # MiB + + # Some slack space we'll append to the raw fs + # Used by `--reduce-device-size` read cryptsetup(8). + dd if=/dev/zero of=tmp.img bs=1024 count=$((slack*1024)) + + # Catting both to ensure it's writable, and to add some slack space at + # the end + cat ${rootfsExt4}/${rootfsExt4.label}.img tmp.img > encrypted.img + rm tmp.img + + echo ${builtins.toJSON passphrase} | cryptsetup \ + reencrypt \ + --encrypt ./encrypted.img \ + --reduce-device-size $((slack*1024*1024)) + + #echo YES | + cryptsetup luksUUID --uuid=${builtins.toJSON uuid} ./encrypted.img + ) + '' + ); +in + +{ + boot.initrd.luks.devices = { + LUKS-MOBILE-ROOTFS = { + device = "/dev/disk/by-uuid/${uuid}"; + }; + }; + + fileSystems = { + "/" = { + device = "/dev/mapper/LUKS-MOBILE-ROOTFS"; + fsType = "ext4"; + }; + }; + + # Instead of the (mkDefault) rootfs, provide our raw encrypted rootfs. + mobile.generatedFilesystems.rootfs = { + raw = encryptedRootfs; + }; +} diff --git a/examples/testing/qemu-cryptsetup/default.nix b/examples/testing/qemu-cryptsetup/default.nix new file mode 100644 index 00000000..436e432a --- /dev/null +++ b/examples/testing/qemu-cryptsetup/default.nix @@ -0,0 +1,11 @@ +let + device = "qemu-x86_64"; + system-build = import ../../../. { + inherit device; + configuration = [ { imports = [ + ../../hello/configuration.nix + ./configuration.nix + ]; } ]; + }; +in + system-build.build.default diff --git a/modules/generated-filesystems.nix b/modules/generated-filesystems.nix index 3e94cf95..4e9b3763 100644 --- a/modules/generated-filesystems.nix +++ b/modules/generated-filesystems.nix @@ -62,6 +62,14 @@ let limitations in CI situations. ''; }; + raw = lib.mkOption { + internal = true; + type = types.nullOr types.package; + default = null; + description = '' + Use an output directly rather than creating it from the options. + ''; + }; }; config = { }; @@ -79,7 +87,8 @@ in }; config = { - system.build.generatedFilesystems = lib.attrsets.mapAttrs (name: {type, id, label, ...} @ attrs: + system.build.generatedFilesystems = lib.attrsets.mapAttrs (name: {raw, type, id, label, ...} @ attrs: + if raw != null then raw else filesystemFunctions."${type}" (attrs // { name = label; partitionID = id; diff --git a/modules/luks.nix b/modules/luks.nix new file mode 100644 index 00000000..d29167ce --- /dev/null +++ b/modules/luks.nix @@ -0,0 +1,27 @@ +{ config, lib, pkgs, ... }: + +let + inherit (config.boot.initrd) luks; +in + +lib.mkIf (luks.devices != {} || luks.forceLuksSupportInInitrd) { + mobile.boot.stage-1 = { + bootConfig = { + luksDevices = luks.devices; + }; + kernel = { + modules = [ + "dm_mod" "dm_crypt" "cryptd" "input_leds" + ] ++ luks.cryptoModules + ; + }; + + extraUtils = [ + { package = pkgs.cryptsetup; } + # dmsetup is required for device mapper stuff to work in stage-1. + { package = lib.getBin pkgs.lvm2; binaries = [ + "lvm" "dmsetup" + ];} + ]; + }; +} diff --git a/modules/module-list.nix b/modules/module-list.nix index 72af46ba..344034a6 100644 --- a/modules/module-list.nix +++ b/modules/module-list.nix @@ -30,6 +30,7 @@ ./initrd-vendor.nix ./initrd.nix ./internal.nix + ./luks.nix ./mobile-device.nix ./nixpkgs.nix ./quirks diff --git a/overlay/lib/extra-utils.nix b/overlay/lib/extra-utils.nix index ed308aca..6da51032 100644 --- a/overlay/lib/extra-utils.nix +++ b/overlay/lib/extra-utils.nix @@ -16,11 +16,17 @@ let install_package = set: let pkg = if set ? type && set.type == "derivation" then set else set.package; + binaries = if set ? binaries then set.binaries else [ "*" ]; in - '' - for BIN in ${pkg}/{s,}bin/*; do - copy_bin_and_libs $BIN + (concat (map (path: '' + for BIN in ${pkg}/{s,}bin/${path}; do + if [ -e "$BIN" ]; then + copy_bin_and_libs "$BIN" + fi done + '') binaries )) + + + '' ${if set ? extraCommand then set.extraCommand else ""} ''; install_packages = concat(map (install_package) packages); diff --git a/overlay/mruby-builder/mrbgems/mruby-lvgui/default.nix b/overlay/mruby-builder/mrbgems/mruby-lvgui/default.nix index 97276b1b..a549a841 100644 --- a/overlay/mruby-builder/mrbgems/mruby-lvgui/default.nix +++ b/overlay/mruby-builder/mrbgems/mruby-lvgui/default.nix @@ -18,8 +18,8 @@ mrbgems.mkGem { src = fetchFromGitHub { repo = "mruby-lvgui"; owner = "mobile-nixos"; - rev = "ab7cf5b1b2e318a4bf5fc973507eb842dce80214"; - sha256 = "09h9f3xlbvxdwpmzpf7whq0gphiv68842shy03ld712fw25393jx"; + rev = "f1bb1dd9b2c5aa3d3df4fcc41ca706f426d182a8"; + sha256 = "0ybjkzg743d21rn3q0vi0fa9zwp3ym9zw2q5ym24wc7gxdspjcjs"; }; gemBuildInputs = [ diff --git a/overlay/mruby-builder/mrbgems/mruby-lvgui/lvgui.nix b/overlay/mruby-builder/mrbgems/mruby-lvgui/lvgui.nix index 24664f5f..14c1b31f 100644 --- a/overlay/mruby-builder/mrbgems/mruby-lvgui/lvgui.nix +++ b/overlay/mruby-builder/mrbgems/mruby-lvgui/lvgui.nix @@ -24,13 +24,13 @@ let in stdenv.mkDerivation { pname = "lvgui"; - version = "2020-07-25"; + version = "2020-11-01"; src = fetchFromGitHub { repo = "lvgui"; owner = "mobile-nixos"; - rev = "0dc257d07271fad023a5e6e9ac42222d2397c5cf"; - sha256 = "1zb7naamqfzcsfi5809c93f1ygxp4w3aiw6172bpmnk1vxdchwsh"; + rev = "4f8af498a81bd669d42ce3b370fc66fe4ec681b5"; + sha256 = "00rik18c3c3l4glzh2azg90cwvp56s4wnski86rsn00bxslia5ma"; }; # Document `LVGL_ENV_SIMULATOR` in the built headers.