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