From 8f2c2442a3ee24e2fcb2bcb4571e5ac536792006 Mon Sep 17 00:00:00 2001 From: Glenn Date: Tue, 21 Nov 2023 00:29:48 +0100 Subject: [PATCH 1/4] feat: add global.mountpoint and version parsing from YAML --- src/config.cr | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/config.cr b/src/config.cr index 913c066..311485f 100644 --- a/src/config.cr +++ b/src/config.cr @@ -25,6 +25,7 @@ module GX getter filesystems : Array(Filesystem) getter home_dir : String + getter global_mount_point : String? property verbose : Bool property mode : Mode property path : String? @@ -40,6 +41,7 @@ module GX @mode = Mode::Mount @filesystems = [] of Filesystem @path = nil + @global_mount_point = nil @args = NoArgs end @@ -82,14 +84,29 @@ module GX load_filesystems(path) end + # FIXME: render template on a value basis (instead of global) private def load_filesystems(config_path : String) + schema_version = nil file_data = File.read(config_path) - # FIXME: render template on a value basis (instead of global) file_patched = Crinja.render(file_data, {"env" => ENV.to_h}) yaml_data = YAML.parse(file_patched) - vaults_data = yaml_data["filesystems"].as_a + # Extract schema version + if yaml_data["version"]? + schema_version = yaml_data["version"].as_s? + end + + # Extract global settings + if yaml_data["global"]?.try &.as_h? + global_data = yaml_data["global"] + if global_data["mountpoint"]? + @global_mount_point = global_data["mountpoint"].as_s? + end + end + + # Extract filesystem data + vaults_data = yaml_data["filesystems"].as_a vaults_data.each do |filesystem_data| type = filesystem_data["type"].as_s name = filesystem_data["name"].as_s From 994f9e1885a3188f808ff1416a87b74034a8a12b Mon Sep 17 00:00:00 2001 From: Glenn Date: Tue, 21 Nov 2023 00:30:59 +0100 Subject: [PATCH 2/4] refactor: use a better class hierarchy for filesystems --- src/cli.cr | 2 +- src/config.cr | 8 ++-- src/filesystems.cr | 2 +- src/filesystems/abstract_filesystem.cr | 50 +++++++++++++++++++++ src/filesystems/filesystem.cr | 48 --------------------- src/filesystems/gocryptfs.cr | 60 +++++++++++++------------- src/filesystems/httpdirfs.cr | 8 ++-- src/filesystems/sshfs.cr | 60 +++++++++++++------------- 8 files changed, 123 insertions(+), 115 deletions(-) create mode 100644 src/filesystems/abstract_filesystem.cr delete mode 100644 src/filesystems/filesystem.cr diff --git a/src/cli.cr b/src/cli.cr index f9a6578..f7ebb1d 100644 --- a/src/cli.cr +++ b/src/cli.cr @@ -97,7 +97,7 @@ module GX end def mount() - names_display = {} of String => NamedTuple(filesystem: Filesystem, ansi_name: String) + names_display = {} of String => NamedTuple(filesystem: Filesystem::AbstractFilesystem, ansi_name: String) @config.filesystems.each do |filesystem| fs_str = filesystem.type.ljust(12,' ') diff --git a/src/config.cr b/src/config.cr index 311485f..b35f6b7 100644 --- a/src/config.cr +++ b/src/config.cr @@ -23,7 +23,7 @@ module GX record AddArgs, name : String, path : String record DelArgs, name : String - getter filesystems : Array(Filesystem) + getter filesystems : Array(Filesystem::AbstractFilesystem) getter home_dir : String getter global_mount_point : String? property verbose : Bool @@ -39,7 +39,7 @@ module GX @verbose = false @mode = Mode::Mount - @filesystems = [] of Filesystem + @filesystems = [] of Filesystem::AbstractFilesystem @path = nil @global_mount_point = nil @@ -75,7 +75,7 @@ module GX path = detect_config_file() end @path = path - @filesystems = [] of Filesystem + @filesystems = [] of Filesystem::AbstractFilesystem if !File.exists? path Log.error { "File #{path} does not exist!".colorize(:red) } @@ -111,7 +111,7 @@ module GX type = filesystem_data["type"].as_s name = filesystem_data["name"].as_s # encrypted_path = filesystem_data["encrypted_path"].as_s - @filesystems << Filesystem.from_yaml(filesystem_data.to_yaml) + @filesystems << Filesystem::AbstractFilesystem.from_yaml(filesystem_data.to_yaml) # @filesystems << Filesystem.new(name, encrypted_path, "#{name}.Open") end end diff --git a/src/filesystems.cr b/src/filesystems.cr index 2026122..a8a1425 100644 --- a/src/filesystems.cr +++ b/src/filesystems.cr @@ -6,4 +6,4 @@ require "./filesystems/gocryptfs" require "./filesystems/sshfs" require "./filesystems/httpdirfs" -require "./filesystems/filesystem" +require "./filesystems/abstract_filesystem" diff --git a/src/filesystems/abstract_filesystem.cr b/src/filesystems/abstract_filesystem.cr new file mode 100644 index 0000000..443535d --- /dev/null +++ b/src/filesystems/abstract_filesystem.cr @@ -0,0 +1,50 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# SPDX-FileCopyrightText: 2023 Glenn Y. Rolland +# Copyright © 2023 Glenn Y. Rolland + +require "yaml" + +module GX + module Filesystem + abstract class AbstractFilesystem + include YAML::Serializable + + use_yaml_discriminator "type", { + gocryptfs: GoCryptFS, + sshfs: SshFS, + httpdirfs: HttpDirFS + } + + property type : String + end + + module FilesystemBase + def unmount + system("fusermount -u #{mount_dir.shellescape}") + fusermount_status = $? + + if fusermount_status.success? + puts "Filesystem #{name} is now closed.".colorize(:green) + else + puts "Error: Unable to unmount filesystem #{name} (exit code: #{fusermount_status.exit_code}).".colorize(:red) + end + end + + def mount(&block) + Dir.mkdir_p(mount_dir) unless Dir.exists?(mount_dir) + if mounted? + puts "Already mounted. Skipping.".colorize(:yellow) + return + end + + yield + + puts "Filesystem #{name} is now available on #{mount_dir}".colorize(:green) + end + end + end +end + +require "./gocryptfs" +require "./sshfs" diff --git a/src/filesystems/filesystem.cr b/src/filesystems/filesystem.cr deleted file mode 100644 index 8881bad..0000000 --- a/src/filesystems/filesystem.cr +++ /dev/null @@ -1,48 +0,0 @@ -# SPDX-License-Identifier: GPL-3.0-or-later -# -# SPDX-FileCopyrightText: 2023 Glenn Y. Rolland -# Copyright © 2023 Glenn Y. Rolland - -require "yaml" - -module GX - abstract class Filesystem - include YAML::Serializable - - use_yaml_discriminator "type", { - gocryptfs: GoCryptFS, - sshfs: SshFS, - httpdirfs: HttpDirFS - } - - property type : String - end - - module GenericFilesystem - def unmount - system("fusermount -u #{mount_dir.shellescape}") - fusermount_status = $? - - if fusermount_status.success? - puts "Filesystem #{name} is now closed.".colorize(:green) - else - puts "Error: Unable to unmount filesystem #{name} (exit code: #{fusermount_status.exit_code}).".colorize(:red) - end - end - - def mount(&block) - Dir.mkdir_p(mount_dir) unless Dir.exists?(mount_dir) - if mounted? - puts "Already mounted. Skipping.".colorize(:yellow) - return - end - - yield - - puts "Filesystem #{name} is now available on #{mount_dir}".colorize(:green) - end - end -end - -require "./gocryptfs" -require "./sshfs" diff --git a/src/filesystems/gocryptfs.cr b/src/filesystems/gocryptfs.cr index ebe7f83..b405482 100644 --- a/src/filesystems/gocryptfs.cr +++ b/src/filesystems/gocryptfs.cr @@ -4,42 +4,44 @@ # Copyright © 2023 Glenn Y. Rolland require "shellwords" -require "./filesystem" +require "./abstract_filesystem" module GX - class GoCryptFS < Filesystem - getter name : String = "" - getter encrypted_path : String = "" + module Filesystem + class GoCryptFS < AbstractFilesystem + getter name : String = "" + getter encrypted_path : String = "" - @[YAML::Field(key: "mount_dir", ignore: true)] - getter mount_dir : String = "" + @[YAML::Field(key: "mount_dir", ignore: true)] + getter mount_dir : String = "" - include GenericFilesystem + include FilesystemBase - def after_initialize() - home_dir = ENV["HOME"] || raise "Home directory not found" - @mount_dir = File.join(home_dir, "mnt/#{@name}.Open") - end + def after_initialize() + home_dir = ENV["HOME"] || raise "Home directory not found" + @mount_dir = File.join(home_dir, "mnt/#{@name}.Open") + end - def mounted? : Bool - `mount`.includes?("#{encrypted_path} on #{mount_dir}") - end + def mounted? : Bool + `mount`.includes?("#{encrypted_path} on #{mount_dir}") + end - def mount - super do - input = STDIN - output = STDOUT - error = STDERR - process = Process.new( - "gocryptfs", - ["-idle", "15m", encrypted_path, mount_dir], - input: input, - output: output, - error: error - ) - unless process.wait.success? - puts "Error mounting the vault".colorize(:red) - return + def mount + super do + input = STDIN + output = STDOUT + error = STDERR + process = Process.new( + "gocryptfs", + ["-idle", "15m", encrypted_path, mount_dir], + input: input, + output: output, + error: error + ) + unless process.wait.success? + puts "Error mounting the vault".colorize(:red) + return + end end end end diff --git a/src/filesystems/httpdirfs.cr b/src/filesystems/httpdirfs.cr index e1e4279..481e4ba 100644 --- a/src/filesystems/httpdirfs.cr +++ b/src/filesystems/httpdirfs.cr @@ -4,17 +4,18 @@ # Copyright © 2023 Glenn Y. Rolland require "shellwords" -require "./filesystem" +require "./abstract_filesystem" module GX - class HttpDirFS < Filesystem + module Filesystem + class HttpDirFS < AbstractFilesystem getter name : String = "" getter url : String = "" @[YAML::Field(key: "mount_dir", ignore: true)] getter mount_dir : String = "" - include GenericFilesystem + include FilesystemBase def after_initialize() home_dir = ENV["HOME"] || raise "Home directory not found" @@ -44,5 +45,6 @@ module GX end end end + end end diff --git a/src/filesystems/sshfs.cr b/src/filesystems/sshfs.cr index 44ddab9..16ca006 100644 --- a/src/filesystems/sshfs.cr +++ b/src/filesystems/sshfs.cr @@ -4,38 +4,39 @@ # Copyright © 2023 Glenn Y. Rolland require "shellwords" -require "./filesystem" +require "./abstract_filesystem" module GX - class SshFS < Filesystem - getter name : String = "" - getter remote_path : String = "" - getter remote_user : String = "" - getter remote_host : String = "" - getter remote_port : String = "22" + module Filesystem + class SshFS < AbstractFilesystem + getter name : String = "" + getter remote_path : String = "" + getter remote_user : String = "" + getter remote_host : String = "" + getter remote_port : String = "22" - @[YAML::Field(key: "mount_dir", ignore: true)] - getter mount_dir : String = "" + @[YAML::Field(key: "mount_dir", ignore: true)] + getter mount_dir : String = "" - include GenericFilesystem + include FilesystemBase - def after_initialize() - home_dir = ENV["HOME"] || raise "Home directory not found" - @mount_dir = File.join(home_dir, "mnt/#{@name}") - end + def after_initialize() + home_dir = ENV["HOME"] || raise "Home directory not found" + @mount_dir = File.join(home_dir, "mnt/#{@name}") + end - def mounted? : Bool - `mount`.includes?("#{remote_user}@#{remote_host}:#{remote_path} on #{mount_dir}") - end + def mounted? : Bool + `mount`.includes?("#{remote_user}@#{remote_host}:#{remote_path} on #{mount_dir}") + end - def mount - super do - input = STDIN - output = STDOUT - error = STDERR - process = Process.new( - "sshfs", - [ + def mount + super do + input = STDIN + output = STDOUT + error = STDERR + process = Process.new( + "sshfs", + [ "-p", remote_port, "#{remote_user}@#{remote_host}:#{remote_path}", mount_dir @@ -43,10 +44,11 @@ module GX input: input, output: output, error: error - ) - unless process.wait.success? - puts "Error mounting the filesystem".colorize(:red) - return + ) + unless process.wait.success? + puts "Error mounting the filesystem".colorize(:red) + return + end end end end From ee3f57ec2011c5ad8b3d8c7a2b49a5e4c042e7a1 Mon Sep 17 00:00:00 2001 From: Glenn Date: Tue, 21 Nov 2023 23:11:21 +0100 Subject: [PATCH 3/4] refactor: define abstract defs & move most functions to concerns/base --- src/filesystems/abstract_filesystem.cr | 29 ++---------------- src/filesystems/concerns/base.cr | 41 ++++++++++++++++++++++++++ src/filesystems/gocryptfs.cr | 23 +++++---------- src/filesystems/httpdirfs.cr | 23 +++++---------- src/filesystems/sshfs.cr | 25 ++++++---------- 5 files changed, 69 insertions(+), 72 deletions(-) create mode 100644 src/filesystems/concerns/base.cr diff --git a/src/filesystems/abstract_filesystem.cr b/src/filesystems/abstract_filesystem.cr index 443535d..29af3f8 100644 --- a/src/filesystems/abstract_filesystem.cr +++ b/src/filesystems/abstract_filesystem.cr @@ -17,34 +17,11 @@ module GX } property type : String - end - module FilesystemBase - def unmount - system("fusermount -u #{mount_dir.shellescape}") - fusermount_status = $? - - if fusermount_status.success? - puts "Filesystem #{name} is now closed.".colorize(:green) - else - puts "Error: Unable to unmount filesystem #{name} (exit code: #{fusermount_status.exit_code}).".colorize(:red) - end - end - - def mount(&block) - Dir.mkdir_p(mount_dir) unless Dir.exists?(mount_dir) - if mounted? - puts "Already mounted. Skipping.".colorize(:yellow) - return - end - - yield - - puts "Filesystem #{name} is now available on #{mount_dir}".colorize(:green) - end + abstract def mount() + abstract def unmount() + abstract def mounted_prefix() end end end -require "./gocryptfs" -require "./sshfs" diff --git a/src/filesystems/concerns/base.cr b/src/filesystems/concerns/base.cr new file mode 100644 index 0000000..ac0cd17 --- /dev/null +++ b/src/filesystems/concerns/base.cr @@ -0,0 +1,41 @@ + +module GX::Filesystem::Concerns + module Base + def after_initialize() + home_dir = ENV["HOME"] || raise "Home directory not found" + + # Use default mountpoint if none defined + if @mount_dir.empty? + @mount_dir = File.join(home_dir, "mnt/#{@name}") + end + end + + def mounted? : Bool + `mount`.includes?(" on #{mount_dir} type ") + end + + def unmount : Nil + system("fusermount -u #{mount_dir.shellescape}") + fusermount_status = $? + + if fusermount_status.success? + puts "Filesystem #{name} is now closed.".colorize(:green) + else + puts "Error: Unable to unmount filesystem #{name} (exit code: #{fusermount_status.exit_code}).".colorize(:red) + end + end + + def _mount_wrapper(&block) : Nil + Dir.mkdir_p(mount_dir) unless Dir.exists?(mount_dir) + if mounted? + puts "Already mounted. Skipping.".colorize(:yellow) + return + end + + yield + + puts "Filesystem #{name} is now available on #{mount_dir}".colorize(:green) + end + end + +end diff --git a/src/filesystems/gocryptfs.cr b/src/filesystems/gocryptfs.cr index b405482..3e5e4a1 100644 --- a/src/filesystems/gocryptfs.cr +++ b/src/filesystems/gocryptfs.cr @@ -5,6 +5,7 @@ require "shellwords" require "./abstract_filesystem" +require "./concerns/base" module GX module Filesystem @@ -15,28 +16,20 @@ module GX @[YAML::Field(key: "mount_dir", ignore: true)] getter mount_dir : String = "" - include FilesystemBase + include Concerns::Base - def after_initialize() - home_dir = ENV["HOME"] || raise "Home directory not found" - @mount_dir = File.join(home_dir, "mnt/#{@name}.Open") - end - - def mounted? : Bool - `mount`.includes?("#{encrypted_path} on #{mount_dir}") + def mounted_prefix() + "#{encrypted_path}" end def mount - super do - input = STDIN - output = STDOUT - error = STDERR + _mount_wrapper do process = Process.new( "gocryptfs", ["-idle", "15m", encrypted_path, mount_dir], - input: input, - output: output, - error: error + input: STDIN, + output: STDOUT, + error: STDERR ) unless process.wait.success? puts "Error mounting the vault".colorize(:red) diff --git a/src/filesystems/httpdirfs.cr b/src/filesystems/httpdirfs.cr index 481e4ba..16d7550 100644 --- a/src/filesystems/httpdirfs.cr +++ b/src/filesystems/httpdirfs.cr @@ -5,6 +5,7 @@ require "shellwords" require "./abstract_filesystem" +require "./concerns/base" module GX module Filesystem @@ -15,28 +16,20 @@ module GX @[YAML::Field(key: "mount_dir", ignore: true)] getter mount_dir : String = "" - include FilesystemBase + include Concerns::Base - def after_initialize() - home_dir = ENV["HOME"] || raise "Home directory not found" - @mount_dir = File.join(home_dir, "mnt/#{@name}") - end - - def mounted? : Bool - `mount`.includes?("httpdirfs on #{mount_dir}") + def mounted_prefix() + "httpdirfs" end def mount - super do - input = STDIN - output = STDOUT - error = STDERR + _mount_wrapper do process = Process.new( "httpdirfs", ["#{url}", mount_dir], - input: input, - output: output, - error: error + input: STDIN, + output: STDOUT, + error: STDERR ) unless process.wait.success? puts "Error mounting the filesystem".colorize(:red) diff --git a/src/filesystems/sshfs.cr b/src/filesystems/sshfs.cr index 16ca006..7f445ae 100644 --- a/src/filesystems/sshfs.cr +++ b/src/filesystems/sshfs.cr @@ -5,6 +5,7 @@ require "shellwords" require "./abstract_filesystem" +require "./concerns/base" module GX module Filesystem @@ -18,22 +19,14 @@ module GX @[YAML::Field(key: "mount_dir", ignore: true)] getter mount_dir : String = "" - include FilesystemBase + include Concerns::Base - def after_initialize() - home_dir = ENV["HOME"] || raise "Home directory not found" - @mount_dir = File.join(home_dir, "mnt/#{@name}") + def mounted_prefix() + "#{remote_user}@#{remote_host}:#{remote_path}" end - def mounted? : Bool - `mount`.includes?("#{remote_user}@#{remote_host}:#{remote_path} on #{mount_dir}") - end - - def mount - super do - input = STDIN - output = STDOUT - error = STDERR + def mount() + _mount_wrapper do process = Process.new( "sshfs", [ @@ -41,9 +34,9 @@ module GX "#{remote_user}@#{remote_host}:#{remote_path}", mount_dir ], - input: input, - output: output, - error: error + input: STDIN, + output: STDOUT, + error: STDERR ) unless process.wait.success? puts "Error mounting the filesystem".colorize(:red) From 23d4def2170cfd7a4db0c0e4b07552075643c60f Mon Sep 17 00:00:00 2001 From: Glenn Date: Fri, 24 Nov 2023 00:20:16 +0100 Subject: [PATCH 4/4] feat: implement local & global mount_point definition --- src/cli.cr | 29 +++++++++--- src/config.cr | 56 ++++++++---------------- src/filesystems.cr | 9 ---- src/filesystems/abstract_filesystem.cr | 27 ------------ src/filesystems/concerns/base.cr | 41 ----------------- src/filesystems/gocryptfs.cr | 42 ------------------ src/filesystems/httpdirfs.cr | 43 ------------------ src/filesystems/sshfs.cr | 49 --------------------- src/main.cr | 2 - src/models.cr | 11 +++++ src/models/abstract_filesystem_config.cr | 32 ++++++++++++++ src/models/concerns/base.cr | 51 +++++++++++++++++++++ src/models/global_config.cr | 28 ++++++++++++ src/models/gocryptfs_config.cr | 41 +++++++++++++++++ src/models/httpdirfs_config.cr | 41 +++++++++++++++++ src/models/root_config.cr | 41 +++++++++++++++++ src/models/sshfs_config.cr | 48 ++++++++++++++++++++ 17 files changed, 334 insertions(+), 257 deletions(-) delete mode 100644 src/filesystems.cr delete mode 100644 src/filesystems/abstract_filesystem.cr delete mode 100644 src/filesystems/concerns/base.cr delete mode 100644 src/filesystems/gocryptfs.cr delete mode 100644 src/filesystems/httpdirfs.cr delete mode 100644 src/filesystems/sshfs.cr create mode 100644 src/models.cr create mode 100644 src/models/abstract_filesystem_config.cr create mode 100644 src/models/concerns/base.cr create mode 100644 src/models/global_config.cr create mode 100644 src/models/gocryptfs_config.cr create mode 100644 src/models/httpdirfs_config.cr create mode 100644 src/models/root_config.cr create mode 100644 src/models/sshfs_config.cr diff --git a/src/cli.cr b/src/cli.cr index f7ebb1d..844fa91 100644 --- a/src/cli.cr +++ b/src/cli.cr @@ -92,13 +92,18 @@ module GX STDOUT.puts "#{PROGRAM_NAME} #{VERSION}" when Config::Mode::Mount @config.load_from_file - mount + filesystem = choose_filesystem + mount_or_umount(filesystem) if !filesystem.nil? end end - def mount() - names_display = {} of String => NamedTuple(filesystem: Filesystem::AbstractFilesystem, ansi_name: String) - @config.filesystems.each do |filesystem| + def choose_filesystem() + names_display = {} of String => NamedTuple(filesystem: Models::AbstractFilesystemConfig, ansi_name: String) + + config_root = @config.root + return if config_root.nil? + + config_root.filesystems.each do |filesystem| fs_str = filesystem.type.ljust(12,' ') suffix = "" @@ -121,12 +126,22 @@ module GX selected_filesystem = names_display[result_filesystem_name][:filesystem] puts ">> #{selected_filesystem.name}".colorize(:yellow) - if selected_filesystem - selected_filesystem.mounted? ? selected_filesystem.unmount : selected_filesystem.mount - else + if !selected_filesystem STDERR.puts "Vault not found: #{selected_filesystem}.".colorize(:red) + return end + return selected_filesystem + end + def mount_or_umount(selected_filesystem) + config_root_safe = @config.root + return if config_root_safe.nil? + + if !selected_filesystem.mounted? + selected_filesystem.mount() + else + selected_filesystem.umount() + end end end end diff --git a/src/config.cr b/src/config.cr index b35f6b7..c936b77 100644 --- a/src/config.cr +++ b/src/config.cr @@ -5,7 +5,7 @@ require "crinja" -require "./filesystems" +require "./models" module GX class Config @@ -23,9 +23,10 @@ module GX record AddArgs, name : String, path : String record DelArgs, name : String - getter filesystems : Array(Filesystem::AbstractFilesystem) + # getter filesystems : Array(Models::AbstractFilesystemConfig) getter home_dir : String - getter global_mount_point : String? + getter root : Models::RootConfig? + property verbose : Bool property mode : Mode property path : String? @@ -39,14 +40,13 @@ module GX @verbose = false @mode = Mode::Mount - @filesystems = [] of Filesystem::AbstractFilesystem + @filesystems = [] of Models::AbstractFilesystemConfig @path = nil - @global_mount_point = nil @args = NoArgs end - def detect_config_file() + private def detect_config_file() possible_files = [ File.join(@home_dir, ".config", "mfm", "config.yaml"), File.join(@home_dir, ".config", "mfm", "config.yml"), @@ -70,50 +70,32 @@ module GX end def load_from_file - path = @path - if path.nil? - path = detect_config_file() + config_path = @path + if config_path.nil? + config_path = detect_config_file() end - @path = path - @filesystems = [] of Filesystem::AbstractFilesystem + @path = config_path - if !File.exists? path + if !File.exists? config_path Log.error { "File #{path} does not exist!".colorize(:red) } exit(1) end - load_filesystems(path) - end - # FIXME: render template on a value basis (instead of global) - private def load_filesystems(config_path : String) - schema_version = nil file_data = File.read(config_path) file_patched = Crinja.render(file_data, {"env" => ENV.to_h}) - yaml_data = YAML.parse(file_patched) + root = Models::RootConfig.from_yaml(file_patched) - # Extract schema version - if yaml_data["version"]? - schema_version = yaml_data["version"].as_s? - end + global_mount_point = root.global.mount_point + raise "Invalid global mount point" if global_mount_point.nil? - # Extract global settings - if yaml_data["global"]?.try &.as_h? - global_data = yaml_data["global"] - if global_data["mountpoint"]? - @global_mount_point = global_data["mountpoint"].as_s? + root.filesystems.each do |selected_filesystem| + if !selected_filesystem.mount_point? + selected_filesystem.mount_point = + File.join(global_mount_point, selected_filesystem.mounted_name) end end - - # Extract filesystem data - vaults_data = yaml_data["filesystems"].as_a - vaults_data.each do |filesystem_data| - type = filesystem_data["type"].as_s - name = filesystem_data["name"].as_s - # encrypted_path = filesystem_data["encrypted_path"].as_s - @filesystems << Filesystem::AbstractFilesystem.from_yaml(filesystem_data.to_yaml) - # @filesystems << Filesystem.new(name, encrypted_path, "#{name}.Open") - end + @root = root end end end diff --git a/src/filesystems.cr b/src/filesystems.cr deleted file mode 100644 index a8a1425..0000000 --- a/src/filesystems.cr +++ /dev/null @@ -1,9 +0,0 @@ -# SPDX-License-Identifier: GPL-3.0-or-later -# -# SPDX-FileCopyrightText: 2023 Glenn Y. Rolland -# Copyright © 2023 Glenn Y. Rolland - -require "./filesystems/gocryptfs" -require "./filesystems/sshfs" -require "./filesystems/httpdirfs" -require "./filesystems/abstract_filesystem" diff --git a/src/filesystems/abstract_filesystem.cr b/src/filesystems/abstract_filesystem.cr deleted file mode 100644 index 29af3f8..0000000 --- a/src/filesystems/abstract_filesystem.cr +++ /dev/null @@ -1,27 +0,0 @@ -# SPDX-License-Identifier: GPL-3.0-or-later -# -# SPDX-FileCopyrightText: 2023 Glenn Y. Rolland -# Copyright © 2023 Glenn Y. Rolland - -require "yaml" - -module GX - module Filesystem - abstract class AbstractFilesystem - include YAML::Serializable - - use_yaml_discriminator "type", { - gocryptfs: GoCryptFS, - sshfs: SshFS, - httpdirfs: HttpDirFS - } - - property type : String - - abstract def mount() - abstract def unmount() - abstract def mounted_prefix() - end - end -end - diff --git a/src/filesystems/concerns/base.cr b/src/filesystems/concerns/base.cr deleted file mode 100644 index ac0cd17..0000000 --- a/src/filesystems/concerns/base.cr +++ /dev/null @@ -1,41 +0,0 @@ - -module GX::Filesystem::Concerns - module Base - def after_initialize() - home_dir = ENV["HOME"] || raise "Home directory not found" - - # Use default mountpoint if none defined - if @mount_dir.empty? - @mount_dir = File.join(home_dir, "mnt/#{@name}") - end - end - - def mounted? : Bool - `mount`.includes?(" on #{mount_dir} type ") - end - - def unmount : Nil - system("fusermount -u #{mount_dir.shellescape}") - fusermount_status = $? - - if fusermount_status.success? - puts "Filesystem #{name} is now closed.".colorize(:green) - else - puts "Error: Unable to unmount filesystem #{name} (exit code: #{fusermount_status.exit_code}).".colorize(:red) - end - end - - def _mount_wrapper(&block) : Nil - Dir.mkdir_p(mount_dir) unless Dir.exists?(mount_dir) - if mounted? - puts "Already mounted. Skipping.".colorize(:yellow) - return - end - - yield - - puts "Filesystem #{name} is now available on #{mount_dir}".colorize(:green) - end - end - -end diff --git a/src/filesystems/gocryptfs.cr b/src/filesystems/gocryptfs.cr deleted file mode 100644 index 3e5e4a1..0000000 --- a/src/filesystems/gocryptfs.cr +++ /dev/null @@ -1,42 +0,0 @@ -# SPDX-License-Identifier: GPL-3.0-or-later -# -# SPDX-FileCopyrightText: 2023 Glenn Y. Rolland -# Copyright © 2023 Glenn Y. Rolland - -require "shellwords" -require "./abstract_filesystem" -require "./concerns/base" - -module GX - module Filesystem - class GoCryptFS < AbstractFilesystem - getter name : String = "" - getter encrypted_path : String = "" - - @[YAML::Field(key: "mount_dir", ignore: true)] - getter mount_dir : String = "" - - include Concerns::Base - - def mounted_prefix() - "#{encrypted_path}" - end - - def mount - _mount_wrapper do - process = Process.new( - "gocryptfs", - ["-idle", "15m", encrypted_path, mount_dir], - input: STDIN, - output: STDOUT, - error: STDERR - ) - unless process.wait.success? - puts "Error mounting the vault".colorize(:red) - return - end - end - end - end - end -end diff --git a/src/filesystems/httpdirfs.cr b/src/filesystems/httpdirfs.cr deleted file mode 100644 index 16d7550..0000000 --- a/src/filesystems/httpdirfs.cr +++ /dev/null @@ -1,43 +0,0 @@ -# SPDX-License-Identifier: GPL-3.0-or-later -# -# SPDX-FileCopyrightText: 2023 Glenn Y. Rolland -# Copyright © 2023 Glenn Y. Rolland - -require "shellwords" -require "./abstract_filesystem" -require "./concerns/base" - -module GX - module Filesystem - class HttpDirFS < AbstractFilesystem - getter name : String = "" - getter url : String = "" - - @[YAML::Field(key: "mount_dir", ignore: true)] - getter mount_dir : String = "" - - include Concerns::Base - - def mounted_prefix() - "httpdirfs" - end - - def mount - _mount_wrapper do - process = Process.new( - "httpdirfs", - ["#{url}", mount_dir], - input: STDIN, - output: STDOUT, - error: STDERR - ) - unless process.wait.success? - puts "Error mounting the filesystem".colorize(:red) - return - end - end - end - end - end -end - diff --git a/src/filesystems/sshfs.cr b/src/filesystems/sshfs.cr deleted file mode 100644 index 7f445ae..0000000 --- a/src/filesystems/sshfs.cr +++ /dev/null @@ -1,49 +0,0 @@ -# SPDX-License-Identifier: GPL-3.0-or-later -# -# SPDX-FileCopyrightText: 2023 Glenn Y. Rolland -# Copyright © 2023 Glenn Y. Rolland - -require "shellwords" -require "./abstract_filesystem" -require "./concerns/base" - -module GX - module Filesystem - class SshFS < AbstractFilesystem - getter name : String = "" - getter remote_path : String = "" - getter remote_user : String = "" - getter remote_host : String = "" - getter remote_port : String = "22" - - @[YAML::Field(key: "mount_dir", ignore: true)] - getter mount_dir : String = "" - - include Concerns::Base - - def mounted_prefix() - "#{remote_user}@#{remote_host}:#{remote_path}" - end - - def mount() - _mount_wrapper do - process = Process.new( - "sshfs", - [ - "-p", remote_port, - "#{remote_user}@#{remote_host}:#{remote_path}", - mount_dir - ], - input: STDIN, - output: STDOUT, - error: STDERR - ) - unless process.wait.success? - puts "Error mounting the filesystem".colorize(:red) - return - end - end - end - end - end -end diff --git a/src/main.cr b/src/main.cr index f32183c..2fc1327 100644 --- a/src/main.cr +++ b/src/main.cr @@ -8,7 +8,6 @@ require "colorize" require "json" require "log" -require "./filesystems/gocryptfs" require "./config" require "./cli" @@ -32,7 +31,6 @@ Log.setup do |config| end end - app = GX::Cli.new app.parse_command_line(ARGV) app.run diff --git a/src/models.cr b/src/models.cr new file mode 100644 index 0000000..a5d97a4 --- /dev/null +++ b/src/models.cr @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# SPDX-FileCopyrightText: 2023 Glenn Y. Rolland +# Copyright © 2023 Glenn Y. Rolland + +require "./models/root_config" +require "./models/global_config" +require "./models/gocryptfs_config" +require "./models/sshfs_config" +require "./models/httpdirfs_config" +require "./models/abstract_filesystem_config" diff --git a/src/models/abstract_filesystem_config.cr b/src/models/abstract_filesystem_config.cr new file mode 100644 index 0000000..d1006d8 --- /dev/null +++ b/src/models/abstract_filesystem_config.cr @@ -0,0 +1,32 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# SPDX-FileCopyrightText: 2023 Glenn Y. Rolland +# Copyright © 2023 Glenn Y. Rolland + +require "yaml" + +module GX::Models + abstract class AbstractFilesystemConfig + include YAML::Serializable + # include YAML::Serializable::Strict + + use_yaml_discriminator "type", { + gocryptfs: GoCryptFSConfig, + sshfs: SshFSConfig, + httpdirfs: HttpDirFSConfig + } + + getter type : String + getter name : String + property mount_point : String? + + abstract def _mount_wrapper(&block) + abstract def _mount_action() + abstract def _mounted_prefix() + abstract def mounted_name() + abstract def mounted?() + abstract def mount() + abstract def umount() + abstract def mount_point?() + end +end diff --git a/src/models/concerns/base.cr b/src/models/concerns/base.cr new file mode 100644 index 0000000..9f0d656 --- /dev/null +++ b/src/models/concerns/base.cr @@ -0,0 +1,51 @@ + +module GX::Models::Concerns + module Base + def mounted?() : Bool + mount_point_safe = @mount_point + raise "Invalid mountpoint value" if mount_point_safe.nil? + + `mount`.includes?(" on #{mount_point_safe} type ") + end + + def umount() : Nil + mount_point_safe = @mount_point + raise "Invalid mountpoint value" if mount_point_safe.nil? + + system("fusermount -u #{mount_point_safe.shellescape}") + fusermount_status = $? + + if fusermount_status.success? + puts "Models #{name} is now closed.".colorize(:green) + else + puts "Error: Unable to unmount filesystem #{name} (exit code: #{fusermount_status.exit_code}).".colorize(:red) + end + end + + def mount_point?() + !mount_point.nil? + end + + def mount() + _mount_wrapper() do + _mount_action + end + end + + def _mount_wrapper(&block) : Nil + mount_point_safe = mount_point + return if mount_point_safe.nil? + + Dir.mkdir_p(mount_point_safe) unless Dir.exists?(mount_point_safe) + if mounted? + puts "Already mounted. Skipping.".colorize(:yellow) + return + end + + yield + + puts "Models #{name} is now available on #{mount_point_safe}".colorize(:green) + end + end + +end diff --git a/src/models/global_config.cr b/src/models/global_config.cr new file mode 100644 index 0000000..25759b5 --- /dev/null +++ b/src/models/global_config.cr @@ -0,0 +1,28 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# SPDX-FileCopyrightText: 2023 Glenn Y. Rolland +# Copyright © 2023 Glenn Y. Rolland + +require "yaml" +require "./abstract_filesystem_config" + +module GX::Models + class GlobalConfig + include YAML::Serializable + include YAML::Serializable::Strict + + @[YAML::Field(key: "mount_point")] + getter mount_point : String? + + def after_initialize() + home_dir = ENV["HOME"] || raise "Home directory not found" + + # Set default mountpoint from global if none defined + if @mount_point.nil? || @mount_point.try &.empty? + @mount_point = File.join(home_dir, "mnt") + end + end + end +end + + diff --git a/src/models/gocryptfs_config.cr b/src/models/gocryptfs_config.cr new file mode 100644 index 0000000..2dd3cb8 --- /dev/null +++ b/src/models/gocryptfs_config.cr @@ -0,0 +1,41 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# SPDX-FileCopyrightText: 2023 Glenn Y. Rolland +# Copyright © 2023 Glenn Y. Rolland + +require "shellwords" +require "./abstract_filesystem_config" +require "./concerns/base" + +module GX::Models + class GoCryptFSConfig < AbstractFilesystemConfig + getter encrypted_path : String = "" + + include Concerns::Base + + def _mounted_prefix() + "#{encrypted_path}" + end + + def mounted_name() + "#{@name}.Open" + end + + def _mount_action() + mount_point_safe = @mount_point + raise "Invalid mount point" if mount_point_safe.nil? + + process = Process.new( + "gocryptfs", + ["-idle", "15m", @encrypted_path, mount_point_safe], + input: STDIN, + output: STDOUT, + error: STDERR + ) + unless process.wait.success? + puts "Error mounting the vault".colorize(:red) + return + end + end + end +end diff --git a/src/models/httpdirfs_config.cr b/src/models/httpdirfs_config.cr new file mode 100644 index 0000000..29ab5dd --- /dev/null +++ b/src/models/httpdirfs_config.cr @@ -0,0 +1,41 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# SPDX-FileCopyrightText: 2023 Glenn Y. Rolland +# Copyright © 2023 Glenn Y. Rolland + +require "shellwords" +require "./abstract_filesystem_config" +require "./concerns/base" + +module GX::Models + class HttpDirFSConfig < AbstractFilesystemConfig + getter url : String = "" + + include Concerns::Base + + def _mounted_prefix() + "httpdirfs" + end + + def mounted_name() + @name + end + + def _mount_action() + mount_point_safe = @mount_point + raise "Invalid mount point" if mount_point_safe.nil? + + process = Process.new( + "httpdirfs", + ["#{@url}", mount_point_safe], + input: STDIN, + output: STDOUT, + error: STDERR + ) + unless process.wait.success? + puts "Error mounting the filesystem".colorize(:red) + return + end + end + end +end diff --git a/src/models/root_config.cr b/src/models/root_config.cr new file mode 100644 index 0000000..c6de32b --- /dev/null +++ b/src/models/root_config.cr @@ -0,0 +1,41 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# SPDX-FileCopyrightText: 2023 Glenn Y. Rolland +# Copyright © 2023 Glenn Y. Rolland + +require "yaml" +require "./abstract_filesystem_config" +require "./global_config" + +module GX::Models + # class CrinjaConverter + # def self.from_yaml(ctx : YAML::ParseContext , node : YAML::Nodes::Node) + # l_node = node + # if l_node.is_a?(YAML::Nodes::Scalar) + # value_patched = Crinja.render(l_node.value, {"env" => ENV.to_h}) + # return value_patched + # end + + # return "" + # end + # + # def self.to_yaml(value, builder : YAML::Nodes::Builder) + # end + # end + + class RootConfig + include YAML::Serializable + include YAML::Serializable::Strict + + # @[YAML::Field(key: "version", converter: GX::Models::CrinjaConverter)] + @[YAML::Field(key: "version")] + getter version : String + + @[YAML::Field(key: "global")] + getter global : GlobalConfig + + @[YAML::Field(key: "filesystems")] + getter filesystems : Array(AbstractFilesystemConfig) + end +end + diff --git a/src/models/sshfs_config.cr b/src/models/sshfs_config.cr new file mode 100644 index 0000000..6b7e35c --- /dev/null +++ b/src/models/sshfs_config.cr @@ -0,0 +1,48 @@ +# SPDX-License-Identifier: GPL-3.0-or-later +# +# SPDX-FileCopyrightText: 2023 Glenn Y. Rolland +# Copyright © 2023 Glenn Y. Rolland + +require "shellwords" +require "./abstract_filesystem_config" +require "./concerns/base" + +module GX::Models + class SshFSConfig < AbstractFilesystemConfig + getter remote_path : String = "" + getter remote_user : String = "" + getter remote_host : String = "" + getter remote_port : String = "22" + + include Concerns::Base + + def _mounted_prefix() + "#{@remote_user}@#{@remote_host}:#{@remote_path}" + end + + def mounted_name() + @name + end + + def _mount_action() + mount_point_safe = @mount_point + raise "Invalid mount point" if mount_point_safe.nil? + + process = Process.new( + "sshfs", + [ + "-p", remote_port, + "#{@remote_user}@#{@remote_host}:#{@remote_path}", + mount_point_safe + ], + input: STDIN, + output: STDOUT, + error: STDERR + ) + unless process.wait.success? + puts "Error mounting the filesystem".colorize(:red) + return + end + end + end +end