Compare commits

..

3 commits

Author SHA1 Message Date
3a8d9239b2 Merge pull request 'feat: add support for sshfs option (-o) in config' (#51) from feature/50-add-support-for-sshfs-options into develop
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #51
2024-10-05 12:44:14 +00:00
5f775ac45f feat: add support for sshfs option (-o) in config
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-10-05 10:54:40 +02:00
37710103ec Update README.md
All checks were successful
continuous-integration/drone/push Build is passing
2024-05-14 07:33:09 +00:00
28 changed files with 78 additions and 207 deletions

View file

@ -8,20 +8,15 @@
# List of patterns to ignore during preloading # List of patterns to ignore during preloading
ignore_list: ignore_list:
- ^bin/
- ^\.code_preloader.yml
- ^doc/
- ^\.drone.yml
- ^\.git/ - ^\.git/
- ^\.gitattributes
- ^\.gitignore
- ^lib.* - ^lib.*
- ^LICENSES/ - ^doc/
- ^bin/
- ^_prompts/ - ^_prompts/
- ^\.reuse/ - ^\.reuse/
- ^scripts/ - ^LICENSES/
- ^\.tool-versions
- ^\.vagrant/ - ^\.vagrant/
- ^scripts/
# Path to the output file (if null, output to STDOUT) # Path to the output file (if null, output to STDOUT)
output_path: null output_path: null

View file

@ -1 +1 @@
crystal 1.13.1 crystal 1.10.1

View file

@ -24,11 +24,8 @@ test:
install: install:
install \ install \
-m 755 \ -m 755 \
bin/mfm \ bin/code-preloader \
$(PREFIX)/bin $(PREFIX)/bin
clean:
rm -f bin/mfm
.PHONY: spec test build all prepare install .PHONY: spec test build all prepare install

View file

@ -14,8 +14,6 @@
> version of our project, please visit our primary repository at: > version of our project, please visit our primary repository at:
> <https://code.apps.glenux.net/glenux/mfm>. > <https://code.apps.glenux.net/glenux/mfm>.
<!-- hello -->
# Minimalist Fuse Manager (MFM) # Minimalist Fuse Manager (MFM)
MFM is a Crystal-lang CLI designed to streamline the management of various FUSE filesystems, such as sshfs, gocryptfs, httpdirfs, and more. Through its user-friendly interface, users can effortlessly mount and unmount filesystems, get real-time filesystem status, and handle errors proficiently. MFM is a Crystal-lang CLI designed to streamline the management of various FUSE filesystems, such as sshfs, gocryptfs, httpdirfs, and more. Through its user-friendly interface, users can effortlessly mount and unmount filesystems, get real-time filesystem status, and handle errors proficiently.
@ -46,23 +44,18 @@ To build from source, you'll also need:
For Debian/Ubuntu you can use the following command: For Debian/Ubuntu you can use the following command:
```shell-session ```shell-session
$ sudo apt-get update && sudo apt-get install libpcre3-dev libevent-2.1-dev make $ sudo apt-get update && sudo apt-get install libpcre3-dev libevent-2.1-dev
``` ```
## Installation ## Installation
### 1. From Source ### 1. From Source
To get started with MFM, ensure that you have the prerequisites installed on your system (see above). 1. Clone or download the source code.
2. Navigate to the source directory.
Then follow these steps to install: 3. Run `shards install` to fetch dependencies.
4. Compile using `shards build`.
git clone https://code.apps.glenux.net/glenux/mfm 5. The compiled binary will be in the `bin` directory.
cd mfm
make prepare
make build
sudo make install # either to install system-wide
make install PREFIX=$HOME/.local # or to install as a user
### 2. Binary Download ### 2. Binary Download

View file

@ -4,10 +4,6 @@ shards:
git: https://github.com/crystal-ameba/ameba.git git: https://github.com/crystal-ameba/ameba.git
version: 1.6.1 version: 1.6.1
baked_file_system:
git: https://github.com/schovi/baked_file_system.git
version: 0.10.0
crinja: crinja:
git: https://github.com/straight-shoota/crinja.git git: https://github.com/straight-shoota/crinja.git
version: 0.8.1 version: 0.8.1

View file

@ -26,9 +26,6 @@ dependencies:
github: hugopl/version_from_shard github: hugopl/version_from_shard
tablo: tablo:
github: hutou/tablo github: hutou/tablo
baked_file_system:
github: schovi/baked_file_system
version: 0.10.0
development_dependencies: development_dependencies:
ameba: ameba:

View file

@ -16,7 +16,7 @@ module GX
class Cli class Cli
Log = ::Log.for("cli") Log = ::Log.for("cli")
@config : GX::Config @config : GX::Config
def initialize def initialize
# Main execution starts here # Main execution starts here

View file

@ -1,36 +1,11 @@
require "./abstract_command" require "./abstract_command"
require "../file_storage"
module GX::Commands module GX::Commands
class ConfigInit < AbstractCommand class ConfigInit < AbstractCommand
def initialize(@config : GX::Config) def initialize(config : GX::Config) # FIXME
end end
def execute def execute
config_dir = File.join(@config.home_dir, ".config", "mfm")
config_file_path = File.join(config_dir, "config.yml")
# Guard condition to exit if the configuration file already exists
if File.exists?(config_file_path)
puts "Configuration file already exists at #{config_file_path}. No action taken."
return
end
puts "Creating initial configuration file at #{config_file_path}"
# Ensure the configuration directory exists
FileUtils.mkdir_p(config_dir)
# Read the default configuration content from the baked file storage
default_config_content = FileStorage.get("sample.mfm.yaml")
# Write the default configuration to the target path
File.write(config_file_path, default_config_content)
puts "Configuration file created successfully."
rescue ex
STDERR.puts "Error creating the configuration file: #{ex.message}"
exit(1)
end end
def self.handles_mode def self.handles_mode

View file

@ -6,12 +6,10 @@ module GX::Commands
end end
def execute def execute
puts "FIXME: detect option (either zsh or bash)"
puts "FIXME: output the right file from embedded data"
end end
def self.handles_mode def self.handles_mode
GX::Types::Mode::GlobalCompletion GX::Types::Mode::GlobalConfig
end end
end end
end end

View file

@ -2,7 +2,7 @@ require "./abstract_command"
module GX::Commands module GX::Commands
class GlobalConfig < AbstractCommand class GlobalConfig < AbstractCommand
def initialize(config : GX::Config) def initialize(config : GX::Config) # FIXME
end end
def execute def execute

View file

@ -2,7 +2,7 @@ require "./abstract_command"
module GX::Commands module GX::Commands
class GlobalHelp < AbstractCommand class GlobalHelp < AbstractCommand
def initialize(@config : GX::Config) def initialize(@config : GX::Config) # FIXME
end end
def execute def execute

View file

@ -5,7 +5,7 @@ module GX::Commands
class MappingMount < AbstractCommand class MappingMount < AbstractCommand
@file_system_manager : FileSystemManager @file_system_manager : FileSystemManager
def initialize(@config : GX::Config) def initialize(@config : GX::Config) # FIXME
@config.load_from_env @config.load_from_env
@config.load_from_file @config.load_from_file
@file_system_manager = FileSystemManager.new(@config) @file_system_manager = FileSystemManager.new(@config)

View file

@ -5,7 +5,7 @@ module GX::Commands
class MappingUmount < AbstractCommand class MappingUmount < AbstractCommand
@file_system_manager : FileSystemManager @file_system_manager : FileSystemManager
def initialize(@config : GX::Config) def initialize(@config : GX::Config) # FIXME
@config.load_from_env @config.load_from_env
@config.load_from_file @config.load_from_file
@file_system_manager = FileSystemManager.new(@config) @file_system_manager = FileSystemManager.new(@config)

View file

@ -1,8 +0,0 @@
require "baked_file_system"
class FileStorage
extend BakedFileSystem
bake_folder "../static"
end

View file

@ -89,9 +89,7 @@ module GX
end end
def choose_filesystem def choose_filesystem
names_display = {} of String => NamedTuple( names_display = {} of String => NamedTuple(filesystem: Models::AbstractFilesystemConfig, ansi_name: String)
filesystem: Models::AbstractFilesystemConfig,
ansi_name: String)
config_root = @config.root config_root = @config.root
return if config_root.nil? return if config_root.nil?
@ -116,16 +114,16 @@ module GX
end end
# # FIXME: feat: allow to sort by name or by filesystem # # FIXME: feat: allow to sort by name or by filesystem
sorted_values = names_display.values.sort_by!(&.[:filesystem].name) sorted_values = names_display.values.sort_by { |item| item[:filesystem].name }
result_filesystem_name = Utils::Fzf.run(sorted_values.map(&.[:ansi_name])).strip result_filesystem_name = Utils::Fzf.run(sorted_values.map(&.[:ansi_name])).strip
selected_filesystem = names_display[result_filesystem_name][:filesystem] selected_filesystem = names_display[result_filesystem_name][:filesystem]
puts ">> #{selected_filesystem.name}".colorize(:yellow) puts ">> #{selected_filesystem.name}".colorize(:yellow)
if !selected_filesystem if !selected_filesystem
STDERR.puts "Mapping not found: #{selected_filesystem}.".colorize(:red) STDERR.puts "Vault not found: #{selected_filesystem}.".colorize(:red)
return return
end end
selected_filesystem return selected_filesystem
end end
private def generate_display_name(filesystem : Models::AbstractFilesystemConfig) : String private def generate_display_name(filesystem : Models::AbstractFilesystemConfig) : String
@ -138,7 +136,7 @@ module GX
if ENV["DISPLAY"]? || ENV["WAYLAND_DISPLAY"]? if ENV["DISPLAY"]? || ENV["WAYLAND_DISPLAY"]?
return true return true
end end
false return false
end end
end end
end end

View file

@ -31,6 +31,6 @@ Log.setup do |config|
end end
end end
app = GX::Cli.new cli = GX::Cli.new
app.parse_command_line(ARGV) cli.parse_command_line(ARGV)
app.run cli.run

View file

@ -31,7 +31,7 @@ module GX::Models::Concerns
end end
end end
def _mount_wrapper(&) : Nil def _mount_wrapper(&block) : Nil
mount_point_safe = mount_point mount_point_safe = mount_point
return if mount_point_safe.nil? return if mount_point_safe.nil?
@ -46,7 +46,7 @@ module GX::Models::Concerns
if result_status.success? if result_status.success?
puts "Models #{name} is now available on #{mount_point_safe}".colorize(:green) puts "Models #{name} is now available on #{mount_point_safe}".colorize(:green)
else else
puts "Error mounting the mapping".colorize(:red) puts "Error mounting the vault".colorize(:red)
return return
end end
end end

View file

@ -32,7 +32,7 @@ module GX::Models
output: STDOUT, output: STDOUT,
error: STDERR error: STDERR
) )
process.wait return process.wait
end end
end end
end end

View file

@ -32,7 +32,7 @@ module GX::Models
output: STDOUT, output: STDOUT,
error: STDERR error: STDERR
) )
process.wait return process.wait
end end
end end
end end

View file

@ -13,6 +13,7 @@ module GX::Models
getter remote_user : String = "" getter remote_user : String = ""
getter remote_host : String = "" getter remote_host : String = ""
getter remote_port : String = "22" getter remote_port : String = "22"
getter options : Array(String) = [] of String
include Concerns::Base include Concerns::Base
@ -28,18 +29,25 @@ module GX::Models
mount_point_safe = @mount_point mount_point_safe = @mount_point
raise InvalidMountpointError.new("Invalid mount point") if mount_point_safe.nil? raise InvalidMountpointError.new("Invalid mount point") if mount_point_safe.nil?
options = [] of String
# merge sshfs options
@options.each do |option|
options.push("-o", option)
end
options.push("-p", remote_port)
options.push(
"#{@remote_user}@#{@remote_host}:#{@remote_path}",
mount_point_safe
)
process = Process.new( process = Process.new(
"sshfs", "sshfs",
[ options,
"-p", remote_port,
"#{@remote_user}@#{@remote_host}:#{@remote_path}",
mount_point_safe,
],
input: STDIN, input: STDIN,
output: STDOUT, output: STDOUT,
error: STDERR error: STDERR
) )
process.wait return process.wait
end end
end end
end end

View file

@ -12,11 +12,11 @@ module GX::Parsers
) )
parser.separator("\nCompletion commands:") parser.separator("\nCompletion commands:")
parser.on("--bash", "Generate bash completion") do |_| parser.on("--bash", "Generate bash completion") do |flag|
Log.info { "Set bash completion" } Log.info { "Set bash completion" }
end end
parser.on("--zsh", "Generate zsh completion") do |_| parser.on("--zsh", "Generate zsh completion") do |flag|
Log.info { "Set zsh completion" } Log.info { "Set zsh completion" }
end end

View file

@ -23,7 +23,7 @@ module GX::Parsers
parser.banner = Utils.usage_line(breadcrumbs + "init", "Create initial mfm configuration") parser.banner = Utils.usage_line(breadcrumbs + "init", "Create initial mfm configuration")
parser.separator("\nInit options") parser.separator("\nInit options")
parser.on("-p", "--path", "Set mapping encrypted path") do |path| parser.on("-p", "--path", "Set vault encrypted path") do |path|
config.config_init_options.try do |opts| config.config_init_options.try do |opts|
opts.path = path opts.path = path
end end

View file

@ -5,10 +5,8 @@ module GX::Parsers
class MappingParser < AbstractParser class MappingParser < AbstractParser
def build(parser, ancestors, config) def build(parser, ancestors, config)
breadcrumbs = ancestors + "mapping" breadcrumbs = ancestors + "mapping"
create_args = {name: "", path: ""} add_args = {name: "", path: ""}
delete_args = {name: ""} delete_args = {name: ""}
mount_args = {name: ""}
umount_args = {name: ""}
parser.banner = Utils.usage_line( parser.banner = Utils.usage_line(
breadcrumbs, breadcrumbs,
@ -20,94 +18,50 @@ module GX::Parsers
parser.on("list", "List mappings") do parser.on("list", "List mappings") do
config.mode = Types::Mode::MappingList config.mode = Types::Mode::MappingList
parser.separator(Utils.help_line(breadcrumbs + "list")) parser.separator(Utils.help_line(breadcrumbs + "list"))
# abort("FIXME: Not implemented")
end end
parser.on("create", "Create mapping") do parser.on("create", "Create mapping") do
config.mode = Types::Mode::MappingCreate config.mode = Types::Mode::MappingCreate
# pp parser
pp parser
parser.banner = Utils.usage_line(breadcrumbs + "create", "Create mapping", true) parser.banner = Utils.usage_line(breadcrumbs + "create", "Create mapping", true)
parser.separator("\nCreate options") parser.separator("\nCreate options")
parser.on("-t", "--type TYPE", "Set filesystem type") do |type| parser.on("-n", "--name", "Set vault name") do |name|
create_args = create_args.merge({type: type}) add_args = add_args.merge({name: name})
end end
parser.on("-n", "--name", "Set mapping name") do |name| parser.on("-p", "--path", "Set vault encrypted path") do |path|
create_args = create_args.merge({name: name}) add_args = add_args.merge({path: path})
end end
# Filesystem specific
parser.on("--encrypted-path PATH", "Set encrypted path (for gocryptfs)") do |path|
encrypted_path = path
end
parser.on("--remote-user USER", "Set SSH user (for sshfs)") do |user|
create_args = create_args.merge({remote_user: user})
end
parser.on("--remote-host HOST", "Set SSH host (for sshfs)") do |host|
create_args = create_args.merge({remote_host: host})
end
parser.on("--source-path PATH", "Set remote path (for sshfs)") do |path|
create_args = create_args.merge({remote_path: path})
end
parser.on("--remote-port PORT", "Set SSH port (for sshfs)") do |port|
create_args = create_args.merge({remote_port: port})
end
parser.on("--url URL", "Set URL (for httpdirfs)") do |url|
create_args = create_args.merge({url: url})
end
parser.separator(Utils.help_line(breadcrumbs + "create")) parser.separator(Utils.help_line(breadcrumbs + "create"))
end end
parser.on("edit", "Edit configuration") do |_| parser.on("edit", "Edit configuration") do |flag|
config.mode = Types::Mode::MappingEdit config.mode = Types::Mode::MappingEdit
parser.on("--remote-user USER", "Set SSH user") do |user|
create_args = create_args.merge({remote_user: user})
end
parser.on("--remote-host HOST", "Set SSH host") do |host|
create_args = create_args.merge({remote_host: host})
end
parser.on("--source-path PATH", "Set remote path") do |path|
create_args = create_args.merge({remote_path: path})
end
parser.separator(Utils.help_line(breadcrumbs + "edit")) parser.separator(Utils.help_line(breadcrumbs + "edit"))
# abort("FIXME: Not implemented") # abort("FIXME: Not implemented")
end end
parser.on("mount", "Mount mapping") do |_| parser.on("mount", "Mount mapping") do |flag|
config.mode = Types::Mode::MappingMount config.mode = Types::Mode::MappingMount
parser.banner = Utils.usage_line(breadcrumbs + "mount", "mount mapping", true)
parser.separator("\nMount options")
parser.on("-n", "--name", "Set mapping name") do |name|
mount_args = mount_args.merge({name: name})
end
parser.separator(Utils.help_line(breadcrumbs + "mount")) parser.separator(Utils.help_line(breadcrumbs + "mount"))
# abort("FIXME: Not implemented")
end end
parser.on("umount", "Umount mapping") do |_| parser.on("umount", "Umount mapping") do |flag|
config.mode = Types::Mode::MappingUmount config.mode = Types::Mode::MappingUmount
parser.banner = Utils.usage_line(breadcrumbs + "umount", "umount mapping", true)
parser.separator("\nUmount options")
parser.on("-n", "--name", "Set mapping name") do |name|
umount_args = umount_args.merge({name: name})
end
parser.separator(Utils.help_line(breadcrumbs + "umount")) parser.separator(Utils.help_line(breadcrumbs + "umount"))
# abort("FIXME: Not implemented")
end end
parser.on("delete", "Delete mapping") do parser.on("delete", "Delete mapping") do
config.mode = Types::Mode::MappingDelete config.mode = Types::Mode::MappingDelete
parser.banner = Utils.usage_line(breadcrumbs + "delete", "delete mapping", true) parser.banner = Utils.usage_line(breadcrumbs + "delete", "Delete mapping", true)
parser.separator("\ndelete options") parser.separator("\nDelete options")
parser.on("-n", "--name", "Set mapping name") do |name| parser.on("-n", "--name", "Set vault name") do |name|
delete_args = delete_args.merge({name: name}) delete_args = delete_args.merge({name: name})
end end
parser.separator(Utils.help_line(breadcrumbs + "delete")) parser.separator(Utils.help_line(breadcrumbs + "delete"))

View file

@ -21,24 +21,24 @@ module GX::Parsers
config.path = path config.path = path
end end
parser.on("-v", "--verbose", "Set more verbosity") do |_| parser.on("-v", "--verbose", "Set more verbosity") do |flag|
Log.info { "Verbosity enabled" } Log.info { "Verbosity enabled" }
config.verbose = true config.verbose = true
end end
parser.on("-o", "--open", "Automatically open directory after mount") do |_| parser.on("-o", "--open", "Automatically open directory after mount") do |flag|
Log.info { "Auto-open enabled" } Log.info { "Auto-open enabled" }
config.auto_open = true config.auto_open = true
end end
parser.on("--version", "Show version") do |_| parser.on("--version", "Show version") do |flag|
config.mode = Types::Mode::GlobalVersion config.mode = Types::Mode::GlobalVersion
end end
parser.on("-h", "--help", "Show this help") do |_| parser.on("-h", "--help", "Show this help") do |flag|
config.mode = Types::Mode::GlobalHelp config.mode = Types::Mode::GlobalHelp
config.help_options = Parsers::Options::HelpOptions.new config.help_options = Parsers::Options::HelpOptions.new
config.help_options.try(&.parser_snapshot=(parser.dup)) config.help_options.try { |opts| opts.parser_snapshot = parser.dup }
end end
parser.separator("\nGlobal commands:") parser.separator("\nGlobal commands:")
@ -46,7 +46,7 @@ module GX::Parsers
parser.on("config", "Manage configuration file") do parser.on("config", "Manage configuration file") do
config.mode = Types::Mode::GlobalHelp config.mode = Types::Mode::GlobalHelp
config.help_options = Parsers::Options::HelpOptions.new config.help_options = Parsers::Options::HelpOptions.new
config.help_options.try(&.parser_snapshot=(parser.dup)) config.help_options.try { |opts| opts.parser_snapshot = parser.dup }
# config.command = Commands::Config.new(config) # config.command = Commands::Config.new(config)
Parsers::ConfigParser.new.build(parser, breadcrumbs, config) Parsers::ConfigParser.new.build(parser, breadcrumbs, config)
@ -59,7 +59,7 @@ module GX::Parsers
parser.on("mapping", "Manage mappings") do parser.on("mapping", "Manage mappings") do
config.mode = Types::Mode::GlobalHelp config.mode = Types::Mode::GlobalHelp
config.help_options = Parsers::Options::HelpOptions.new config.help_options = Parsers::Options::HelpOptions.new
config.help_options.try(&.parser_snapshot=(parser.dup)) config.help_options.try { |opts| opts.parser_snapshot = parser.dup }
Parsers::MappingParser.new.build(parser, breadcrumbs, config) Parsers::MappingParser.new.build(parser, breadcrumbs, config)
end end

View file

@ -4,12 +4,12 @@ module GX::Utils
@ancestors = base @ancestors = base
end end
def +(other : String) def +(elem : String)
BreadCrumbs.new(@ancestors + [other]) b = BreadCrumbs.new(@ancestors + [elem])
end end
def to_s(io : IO) def to_s
io << @ancestors.join(" ") @ancestors.join(" ")
end end
def to_a def to_a

View file

@ -3,7 +3,7 @@ require "./breadcrumbs"
module GX::Utils module GX::Utils
def self.usage_line(breadcrumbs : BreadCrumbs, description : String, has_commands : Bool = false) def self.usage_line(breadcrumbs : BreadCrumbs, description : String, has_commands : Bool = false)
[ [
"Usage: #{breadcrumbs}#{has_commands ? " [commands]" : ""} [options]", "Usage: #{breadcrumbs.to_s}#{has_commands ? " [commands]" : ""} [options]",
"", "",
description, description,
"", "",
@ -12,6 +12,6 @@ module GX::Utils
end end
def self.help_line(breadcrumbs : BreadCrumbs) def self.help_line(breadcrumbs : BreadCrumbs)
"\nRun '#{breadcrumbs} COMMAND --help' for more information on a command." "\nRun '#{breadcrumbs.to_s} COMMAND --help' for more information on a command."
end end
end end

View file

View file

@ -1,32 +0,0 @@
---
version: 1
global:
mount_point_base: "{{env.HOME}}/mnt"
filesystems:
##
## Sample configuration for encrypted vault (gocryptfs)
##
# - type: gocryptfs
# name: "Credential Vault"
# encrypted_path: "{{env.HOME}}/Documents/Credential.Vault"
#
##
## Sample configuration remote SSH directory (sshfs)
##
# - type: sshfs
# name: "Remote SSH server"
# remote_host: ssh.example.com
# remote_user: "{{env.USER}}"
# remote_path: "/home/{{env.USER}}"
# remote_port: 443
#
##
## Sample configuration for remote HTTP directory (httpdirfs)
##
- type: httpdirfs
name: "Debian Repository"
url: "http://ftp.debian.org/debian/"
# mount_point: "{{env.HOME}}/another.dir"
#