rootfs-wip/src/main.cr

252 lines
5.5 KiB
Crystal
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

require "colorize"
require "option_parser"
require "file_utils"
require "./quadruplet.cr"
require "./backends.cr"
# 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
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
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])
},
:mount => ->(args : Array(String)){
RootFS.load(args[0]).mount(args[1]?)
},
:unmount => ->(args : Array(String)){
RootFS.load(args[0]).unmount(args[1]?)
}
}
command = nil
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
parser.on "-c", "--color", "Forces the use of colors." do
Colorize.enabled = true
end
parser.on "-h", "--help", "Displays this help message." do
puts parser
exit 0
end
parser.unknown_args do |args|
if command.nil?
raise "no command provided" if args.size < 1
end
arguments = args
end
end
if command.nil?
puts options_parser
exit 1
end
if proc = commands[command]?
proc.call(arguments)
else
# FIXME: This is an internal error. It shouldnt trigger.
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)
quadruplet = Quadruplet.new template
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
execute "btrfs subvolume create '#{@directory}'"
# FIXME: We should homogeneize quadruplet components.
backend.new(@directory, self).create! quadruplet.architecture, quadruplet.version, quadruplet.variant
File.write template, "#{@directory}/rootfs"
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)
if directory
execute "mount --bind '#{directory}' '#{@directory}/#{directory}'"
return
end
mount "/proc"
mount "/sys"
end
def unmount(directory = nil)
if directory
execute "mountpoint -q '#{@directory}/#{directory}' || exit 0 && umount '#{@directory}/#{directory}'"
return
end
unmount "/proc"
unmount "/sys"
end
def show!
puts "#{@directory}"
end
end
RootFS::Context.new.run