require "colorize" require "option_parser" require "file_utils" # Not currently used. Waiting for API updates. #require "baguette-crystal-base" # Prototyping for the next generation of baguette-crystal-base. class Baguette::Configuration property application_name : String def initialize(@application_name) end end module Baguette::Log def info(text) STDOUT << (":: ".colorize :blue) << (text.colorize :white) << "\n" end def warn(text) STDERR << (":: ".colorize :yellow) << (text.colorize :yellow) << "\n" end def error(text) STDERR << (":: ".colorize :red) << (text.colorize :red) << "\n" end end module Baguette::Base getter configuration : Configuration include Baguette::Log end Colorize.on_tty_only! # CAUTION # backends: storage, ??? # rootfs templates class RootFS::Context include Baguette::Base def initialize @configuration = Baguette::Configuration.new "rootfs" end def run options_parser = uninitialized OptionParser commands = { "new" => ->(args : Array(String)){ RootFS.new args[1], args[0] }, "save" => ->(args : Array(String)){ RootFS.load(args[0]).save(args[1]) }, "restore" => ->(args : Array(String)){ RootFS.load(args[0]).restore(args[1]) }, # FIXME: This needs access to the list of commands. "help" => ->(args : Array(String)){ STDOUT << options_parser << "\n" } } command = uninitialized String arguments = uninitialized Array(String) options_parser = OptionParser.parse do |parser| parser.banner = "usage: #{@configuration.application_name} [options]" parser.on "-c", "--color", "Forces the use of colors." do Colorize.enabled = true end parser.on "-h", "--help", "Displays this help message." do STDOUT << parser << "\n" exit 0 end # FIXME: This leads to unusual behaviors when using the CLI. # Replace by dedicated executables. parser.unknown_args do |args| # args => Array(String) raise "no command provided" if args.size < 1 command = args.shift arguments = args end end if proc = commands[command]? proc.call(arguments) else raise "unrecognized command: #{command}" end rescue e if message = e.message error message else error e.to_s end end end class RootFS::RootFS include Baguette::Log getter directory : String def initialize(@directory) end def self.new(directory, template : String) instance = RootFS.allocate instance.initialize directory instance.create! template instance end protected def execute(command : String) info "Executing: #{command}" r = Process.new( "sh", ["-c", command], input: Process::Redirect::Inherit, output: Process::Redirect::Inherit, error: Process::Redirect::Inherit ).wait raise "Command returned #{r.exit_status}." unless r.success? end protected def create!(template : String) # FIXME: Alternate backends. # FIXME: template string parsing. is_btrfs = true # FIXME: do the actual detection unless is_btrfs raise "can only create rootfs in a btrfs filesystem" end execute "btrfs subvolume create '#{@directory}'" execute "debootstrap --arch amd64 '#{template}' '#{@directory}'" end def self.load(directory) new directory end def delete show! puts "UNIMPLEMENTED: removing rootfs directory here" end def save(snapshot_directory) execute "btrfs subvolume snapshot '#{@directory}' '#{snapshot_directory}'" end def restore(snapshot_directory) unmount execute "btrfs subvolume delete '#{@directory}'" execute "btrfs subvolume snapshot '#{snapshot_directory}' '#{@directory}'" end def mount(directory = nil) show! puts "UNIMPLEMENTED: binding file systems here" end def unmount(directory = nil) show! puts "UNIMPLEMENTED: unbinding file systems here" end def show! puts "#{@directory}" end end RootFS::Context.new.run