1
0
Fork 0
forked from glenux/mfm

Compare commits

...

22 commits

Author SHA1 Message Date
26510531e7 doc: add stupid comment for demo 2024-02-12 11:53:49 +01:00
0e2ddde081 doc: improve build instructions 2024-01-24 14:15:42 +01:00
6ec7ae0ec7 fix: implement config init 2024-01-24 14:13:29 +01:00
92aaf5f0b5 fix: remove useless comment 2024-01-24 11:08:16 +01:00
59ab4ce272 fix: change binary name 2024-01-24 01:25:44 +01:00
1f5a2f33ec fix: follow the crystal way for to_s 2024-01-24 00:28:46 +01:00
cb99019be5 fix: add missing cli options for mapping command 2024-01-24 00:20:28 +01:00
91f2e7a554 fix: update code_preloader ignore list 2024-01-24 00:07:29 +01:00
bb5941a86a fix: remove useless FIXME comments 2024-01-24 00:06:52 +01:00
1a5c2cd223 feat: prepare (empty) files for completion and sample config 2024-01-24 00:06:28 +01:00
be8980b74c fix: run ameba --fix on parser_lines.cr 2024-01-23 23:48:29 +01:00
8f145189c0 fix: run ameba --fix on root_parser.cr 2024-01-23 23:48:29 +01:00
dbb0a42e91 fix: run ameba --fix on mapping_parser.cr 2024-01-23 23:48:29 +01:00
ed2cf5227f fix: run ameba --fix on completion_parser.cr 2024-01-23 23:48:29 +01:00
b59f1011ac fix: run ameba --fix on sshfs_config.cr 2024-01-23 23:48:29 +01:00
f5d28671a2 fix: run ameba --fix on httpdirfs_config.cr 2024-01-23 23:48:29 +01:00
531cba0dc7 fix: run ameba --fix on gocryptfs_config.cr 2024-01-23 23:48:28 +01:00
16bb660fc2 fix: run ameba --fix on base.cr 2024-01-23 23:48:28 +01:00
275f66d19d fix: run ameba --fix on file_system_manager.cr 2024-01-23 23:48:28 +01:00
8f1862eb43 fix: run ameba --fix on global_completion.cr 2024-01-23 23:48:28 +01:00
29ab85a61f fix: run ameba --fix on config_init.cr 2024-01-23 23:48:28 +01:00
aec45eebd4 fix: replace vault by mapping
As the software object has evolved along the way, and the handling of
vaults has become generic FUSE filesystems mapping management, we need
to correct all the places that still mention vaults.
2024-01-15 09:08:36 +01:00
25 changed files with 194 additions and 60 deletions

View file

@ -8,15 +8,20 @@
# List of patterns to ignore during preloading # List of patterns to ignore during preloading
ignore_list: ignore_list:
- ^\.git/
- ^lib.*
- ^doc/
- ^bin/ - ^bin/
- ^\.code_preloader.yml
- ^doc/
- ^\.drone.yml
- ^\.git/
- ^\.gitattributes
- ^\.gitignore
- ^lib.*
- ^LICENSES/
- ^_prompts/ - ^_prompts/
- ^\.reuse/ - ^\.reuse/
- ^LICENSES/
- ^\.vagrant/
- ^scripts/ - ^scripts/
- ^\.tool-versions
- ^\.vagrant/
# 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

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

View file

@ -14,6 +14,8 @@
> 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.
@ -44,18 +46,23 @@ 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 $ sudo apt-get update && sudo apt-get install libpcre3-dev libevent-2.1-dev make
``` ```
## Installation ## Installation
### 1. From Source ### 1. From Source
1. Clone or download the source code. To get started with MFM, ensure that you have the prerequisites installed on your system (see above).
2. Navigate to the source directory.
3. Run `shards install` to fetch dependencies. Then follow these steps to install:
4. Compile using `shards build`.
5. The compiled binary will be in the `bin` directory. git clone https://code.apps.glenux.net/glenux/mfm
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,6 +4,10 @@ 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,6 +26,9 @@ 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

@ -1,11 +1,36 @@
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) # FIXME def initialize(@config : GX::Config)
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,10 +6,12 @@ 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::GlobalConfig GX::Types::Mode::GlobalCompletion
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) # FIXME def initialize(config : GX::Config)
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) # FIXME def initialize(@config : GX::Config)
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) # FIXME def initialize(@config : GX::Config)
@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) # FIXME def initialize(@config : GX::Config)
@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)

8
src/file_storage.cr Normal file
View file

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

View file

@ -89,7 +89,9 @@ module GX
end end
def choose_filesystem def choose_filesystem
names_display = {} of String => NamedTuple(filesystem: Models::AbstractFilesystemConfig, ansi_name: String) names_display = {} of String => NamedTuple(
filesystem: Models::AbstractFilesystemConfig,
ansi_name: String)
config_root = @config.root config_root = @config.root
return if config_root.nil? return if config_root.nil?
@ -114,16 +116,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 { |item| item[:filesystem].name } sorted_values = names_display.values.sort_by!(&.[: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 "Vault not found: #{selected_filesystem}.".colorize(:red) STDERR.puts "Mapping not found: #{selected_filesystem}.".colorize(:red)
return return
end end
return selected_filesystem selected_filesystem
end end
private def generate_display_name(filesystem : Models::AbstractFilesystemConfig) : String private def generate_display_name(filesystem : Models::AbstractFilesystemConfig) : String
@ -136,7 +138,7 @@ module GX
if ENV["DISPLAY"]? || ENV["WAYLAND_DISPLAY"]? if ENV["DISPLAY"]? || ENV["WAYLAND_DISPLAY"]?
return true return true
end end
return false false
end end
end end
end end

View file

@ -31,7 +31,7 @@ module GX::Models::Concerns
end end
end end
def _mount_wrapper(&block) : Nil def _mount_wrapper(&) : 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 vault".colorize(:red) puts "Error mounting the mapping".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
) )
return process.wait 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
) )
return process.wait process.wait
end end
end end
end end

View file

@ -39,7 +39,7 @@ module GX::Models
output: STDOUT, output: STDOUT,
error: STDERR error: STDERR
) )
return process.wait 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 |flag| parser.on("--bash", "Generate bash completion") do |_|
Log.info { "Set bash completion" } Log.info { "Set bash completion" }
end end
parser.on("--zsh", "Generate zsh completion") do |flag| parser.on("--zsh", "Generate zsh completion") do |_|
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 vault encrypted path") do |path| parser.on("-p", "--path", "Set mapping 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,8 +5,10 @@ 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"
add_args = {name: "", path: ""} create_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,
@ -18,50 +20,94 @@ 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("-n", "--name", "Set vault name") do |name| parser.on("-t", "--type TYPE", "Set filesystem type") do |type|
add_args = add_args.merge({name: name}) create_args = create_args.merge({type: type})
end end
parser.on("-p", "--path", "Set vault encrypted path") do |path| parser.on("-n", "--name", "Set mapping name") do |name|
add_args = add_args.merge({path: path}) create_args = create_args.merge({name: name})
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 |flag| parser.on("edit", "Edit configuration") do |_|
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 |flag| parser.on("mount", "Mount mapping") do |_|
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 |flag| parser.on("umount", "Umount mapping") do |_|
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 vault name") do |name| parser.on("-n", "--name", "Set mapping 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 |flag| parser.on("-v", "--verbose", "Set more verbosity") do |_|
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 |flag| parser.on("-o", "--open", "Automatically open directory after mount") do |_|
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 |flag| parser.on("--version", "Show version") do |_|
config.mode = Types::Mode::GlobalVersion config.mode = Types::Mode::GlobalVersion
end end
parser.on("-h", "--help", "Show this help") do |flag| parser.on("-h", "--help", "Show this help") 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 { |opts| opts.parser_snapshot = parser.dup } config.help_options.try(&.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 { |opts| opts.parser_snapshot = parser.dup } config.help_options.try(&.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 { |opts| opts.parser_snapshot = parser.dup } config.help_options.try(&.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 +(elem : String) def +(other : String)
b = BreadCrumbs.new(@ancestors + [elem]) BreadCrumbs.new(@ancestors + [other])
end end
def to_s def to_s(io : IO)
@ancestors.join(" ") io << @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.to_s}#{has_commands ? " [commands]" : ""} [options]", "Usage: #{breadcrumbs}#{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.to_s} COMMAND --help' for more information on a command." "\nRun '#{breadcrumbs} COMMAND --help' for more information on a command."
end end
end end

0
static/completion.zsh Normal file
View file

32
static/sample.mfm.yaml Normal file
View file

@ -0,0 +1,32 @@
---
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"
#