rootfs-wip/src/main.cr

252 lines
5.4 KiB
Crystal
Raw Permalink Normal View History

2020-11-25 22:01:45 +01:00
require "colorize"
require "option_parser"
require "file_utils"
require "./quadruplet.cr"
require "./backends.cr"
2020-11-25 22:01:45 +01:00
# 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
2020-11-28 21:21:01 +01:00
module Baguette::Log
2020-11-25 22:01:45 +01:00
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)
2020-11-28 21:21:01 +01:00
STDERR << (":: ".colorize :red) << (text.colorize :red) << "\n"
2020-11-25 22:01:45 +01:00
end
end
2020-11-28 21:21:01 +01:00
module Baguette::Base
getter configuration : Configuration
include Baguette::Log
end
class OptionParser
def to_s(io : IO) : Nil
if banner = @banner
io << banner << '\n'
end
@flags.select(&.starts_with? " -").join io, '\n'
io << "\ncommands:\n"
@flags.select{ |x| !x.starts_with? " -" }.join io, '\n'
end
end
2020-11-25 22:01:45 +01:00
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)){
2020-11-28 21:21:01 +01:00
RootFS.new args[1], args[0]
},
:save => ->(args : Array(String)){
2020-11-28 21:21:01 +01:00
RootFS.load(args[0]).save(args[1])
},
:restore => ->(args : Array(String)){
2020-11-28 21:21:01 +01:00
RootFS.load(args[0]).restore(args[1])
},
:mount => ->(args : Array(String)){
2020-11-29 00:23:28 +01:00
RootFS.load(args[0]).mount(args[1]?)
},
:unmount => ->(args : Array(String)){
2020-11-29 00:23:28 +01:00
RootFS.load(args[0]).unmount(args[1]?)
2020-11-25 22:01:45 +01:00
}
}
command = nil
2020-11-25 22:01:45 +01:00
arguments = uninitialized Array(String)
options_parser = OptionParser.parse do |parser|
parser.banner = "usage: #{@configuration.application_name} <command> [options]"
parser.on "new", "Creates a new rootfs." do
parser.banner = "usage: #{@configuration.application_name} new <template> <rootfs-directory>"
command = :new
end
parser.on "save", "Creates a snapshot of a rootfs." do
parser.banner = "usage: #{@configuration.application_name} save <rootfs-directory> <snapshot-directory>"
command = :save
end
parser.on "restore", "Restores a rootfs from a snapshot." do
parser.banner = "usage: #{@configuration.application_name} restore <rootfs-directory> <snapshot-directory>"
command = :restore
end
parser.on "mount", "Bind-mount a directory in a rootfs." do
parser.banner = "usage: #{@configuration.application_name} mount <rootfs-directory> [mount-point]"
command = :mount
end
parser.on "unmount", "Unmount a directory from a rootfs." do
parser.banner = "usage: #{@configuration.application_name} unmount <rootfs-directory> [mount-point]"
command = :unmount
end
2020-11-25 22:01:45 +01:00
parser.on "-c", "--color", "Forces the use of colors." do
Colorize.enabled = true
end
2020-11-28 21:21:01 +01:00
parser.on "-h", "--help", "Displays this help message." do
puts parser
2020-11-28 21:21:01 +01:00
exit 0
end
2020-11-25 22:01:45 +01:00
parser.unknown_args do |args|
if command.nil?
raise "no command provided" if args.size < 1
end
2020-11-25 22:01:45 +01:00
arguments = args
end
end
if command.nil?
puts options_parser
exit 1
end
2020-11-25 22:01:45 +01:00
if proc = commands[command]?
proc.call(arguments)
else
# FIXME: This is an internal error. It shouldnt trigger.
2020-11-25 22:01:45 +01:00
raise "unrecognized command: #{command}"
end
rescue e
if message = e.message
error message
else
error e.to_s
end
end
end
class RootFS::RootFS
2020-11-28 21:21:01 +01:00
include Baguette::Log
2020-11-25 22:01:45 +01:00
getter directory : String
def initialize(@directory)
end
def self.new(directory, template : String)
instance = RootFS.allocate
instance.initialize directory
instance.create! template
instance
end
2020-11-28 21:21:01 +01:00
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
2020-11-25 22:01:45 +01:00
protected def create!(template : String)
quadruplet = Quadruplet.new template
2020-11-29 20:48:47 +01:00
2020-11-28 21:21:01 +01:00
is_btrfs = true # FIXME: do the actual detection
unless is_btrfs
raise "can only create rootfs in a btrfs filesystem"
end
unless backend = Backend.all.find &.name.==(quadruplet.name)
error "unrecognized quadruplet name: #{quadruplet.name}"
exit 2
end
2020-12-12 05:25:39 +01:00
execute "btrfs subvolume create '#{@directory}'"
2020-12-12 05:25:39 +01:00
# FIXME: We should homogeneize quadruplet components.
backend.new(@directory, self).create! quadruplet.architecture, quadruplet.version, quadruplet.variant
2020-12-12 05:25:39 +01:00
File.write template, "#{@directory}/rootfs"
2020-11-25 22:01:45 +01:00
end
def self.load(directory)
2020-11-28 21:21:01 +01:00
new directory
2020-11-25 22:01:45 +01:00
end
def delete
show!
puts "UNIMPLEMENTED: removing rootfs directory here"
end
2020-11-28 21:21:01 +01:00
def save(snapshot_directory)
execute "btrfs subvolume snapshot '#{@directory}' '#{snapshot_directory}'"
2020-11-25 22:01:45 +01:00
end
2020-11-28 21:21:01 +01:00
def restore(snapshot_directory)
unmount
execute "btrfs subvolume delete '#{@directory}'"
execute "btrfs subvolume snapshot '#{snapshot_directory}' '#{@directory}'"
2020-11-25 22:01:45 +01:00
end
2020-11-28 21:21:01 +01:00
def mount(directory = nil)
2020-11-29 00:23:28 +01:00
if directory
execute "mount --bind '#{directory}' '#{@directory}/#{directory}'"
return
end
mount "/proc"
mount "/sys"
2020-11-25 22:01:45 +01:00
end
2020-11-28 21:21:01 +01:00
def unmount(directory = nil)
2020-11-29 00:23:28 +01:00
if directory
execute "umount '#{@directory}/#{directory}'"
return
end
unmount "/proc"
unmount "/sys"
2020-11-25 22:01:45 +01:00
end
def show!
2020-11-28 21:21:01 +01:00
puts "#{@directory}"
2020-11-25 22:01:45 +01:00
end
end
RootFS::Context.new.run