Compare commits

..

28 commits

Author SHA1 Message Date
d48b585bda fix: remove code for GlobalCompletion action
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-08-04 22:50:03 +02:00
cc90d09fae fix(logs): switch to STDERR to avoid breaking completion script
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2024-08-04 22:42:27 +02:00
c912f480b7 feat: add runtime modes for completion 2024-08-04 22:39:58 +02:00
093c9d2fa1 feat: add parser support for completion
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is failing
2024-08-04 22:19:36 +02:00
b1567a1aa0 feat: add base classes for handling completion 2024-08-04 22:14:10 +02:00
a51ce2e98f feat: add basic zsh completion script 2024-08-04 22:12:59 +02:00
26510531e7 doc: add stupid comment for demo
All checks were successful
continuous-integration/drone/push Build is passing
2024-02-12 11:53:49 +01:00
0e2ddde081 doc: improve build instructions
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-24 14:15:42 +01:00
6ec7ae0ec7 fix: implement config init
All checks were successful
continuous-integration/drone/push Build is passing
2024-01-24 14:13:29 +01:00
92aaf5f0b5 fix: remove useless comment
All checks were successful
continuous-integration/drone/push Build is passing
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
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-01-24 00:28:46 +01:00
cb99019be5 fix: add missing cli options for mapping command
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2024-01-24 00:20:28 +01:00
91f2e7a554 fix: update code_preloader ignore list
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is passing
2024-01-24 00:07:29 +01:00
bb5941a86a fix: remove useless FIXME comments
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is passing
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
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
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
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
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
31 changed files with 270 additions and 95 deletions

View file

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

View file

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

View file

@ -14,6 +14,8 @@
> version of our project, please visit our primary repository at:
> <https://code.apps.glenux.net/glenux/mfm>.
<!-- hello -->
# 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.
@ -44,18 +46,23 @@ To build from source, you'll also need:
For Debian/Ubuntu you can use the following command:
```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
### 1. From Source
1. Clone or download the source code.
2. Navigate to the source directory.
3. Run `shards install` to fetch dependencies.
4. Compile using `shards build`.
5. The compiled binary will be in the `bin` directory.
To get started with MFM, ensure that you have the prerequisites installed on your system (see above).
Then follow these steps to install:
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
@ -180,5 +187,5 @@ By contributing, you agree to our code of conduct and license terms.
## License
GNU GPL-3
GNU GPL-3

View file

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

View file

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

View file

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

View file

@ -0,0 +1,17 @@
require "./abstract_command"
module GX::Commands
class CompletionAutodetect < AbstractCommand
def initialize(@config : GX::Config)
end
def execute
STDERR.puts "FIXME: Completion auto-detection isn't implemented yet. Please select one of the following: --bash or --zsh"
exit(0)
end
def self.handles_mode
GX::Types::Mode::CompletionAutodetect
end
end
end

View file

@ -0,0 +1,17 @@
require "./abstract_command"
module GX::Commands
class CompletionBash < AbstractCommand
def initialize(@config : GX::Config)
end
def execute
completion_bash = FileStorage.get("completion.bash")
STDOUT.puts completion_bash.gets_to_end
end
def self.handles_mode
GX::Types::Mode::CompletionBash
end
end
end

View file

@ -0,0 +1,17 @@
require "./abstract_command"
module GX::Commands
class CompletionZsh < AbstractCommand
def initialize(@config : GX::Config)
end
def execute
completion_bash = FileStorage.get("completion.zsh")
STDOUT.puts completion_bash.gets_to_end
end
def self.handles_mode
GX::Types::Mode::CompletionZsh
end
end
end

View file

@ -1,11 +1,36 @@
require "./abstract_command"
require "../file_storage"
module GX::Commands
class ConfigInit < AbstractCommand
def initialize(config : GX::Config) # FIXME
def initialize(@config : GX::Config)
end
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
def self.handles_mode

View file

@ -1,15 +0,0 @@
require "./abstract_command"
module GX::Commands
class GlobalCompletion < AbstractCommand
def initialize(@config : GX::Config)
end
def execute
end
def self.handles_mode
GX::Types::Mode::GlobalConfig
end
end
end

View file

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

View file

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

View file

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

View file

@ -5,7 +5,7 @@ module GX::Commands
class MappingUmount < AbstractCommand
@file_system_manager : FileSystemManager
def initialize(@config : GX::Config) # FIXME
def initialize(@config : GX::Config)
@config.load_from_env
@config.load_from_file
@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
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
return if config_root.nil?
@ -114,16 +116,16 @@ module GX
end
# # 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
selected_filesystem = names_display[result_filesystem_name][:filesystem]
puts ">> #{selected_filesystem.name}".colorize(:yellow)
if !selected_filesystem
STDERR.puts "Vault not found: #{selected_filesystem}.".colorize(:red)
STDERR.puts "Mapping not found: #{selected_filesystem}.".colorize(:red)
return
end
return selected_filesystem
selected_filesystem
end
private def generate_display_name(filesystem : Models::AbstractFilesystemConfig) : String
@ -136,7 +138,7 @@ module GX
if ENV["DISPLAY"]? || ENV["WAYLAND_DISPLAY"]?
return true
end
return false
false
end
end
end

View file

@ -22,7 +22,7 @@ struct BaseFormat < Log::StaticFormatter
end
Log.setup do |config|
backend = Log::IOBackend.new(formatter: BaseFormat)
backend = Log::IOBackend.new(io: STDERR, formatter: BaseFormat)
config.bind "*", Log::Severity::Info, backend
if ENV["LOG_LEVEL"]?
@ -31,6 +31,6 @@ Log.setup do |config|
end
end
cli = GX::Cli.new
cli.parse_command_line(ARGV)
cli.run
app = GX::Cli.new
app.parse_command_line(ARGV)
app.run

View file

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

View file

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

View file

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

View file

@ -13,7 +13,6 @@ module GX::Models
getter remote_user : String = ""
getter remote_host : String = ""
getter remote_port : String = "22"
getter options : Array(String) = [] of String
include Concerns::Base
@ -29,25 +28,18 @@ module GX::Models
mount_point_safe = @mount_point
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(
"sshfs",
options,
[
"-p", remote_port,
"#{@remote_user}@#{@remote_host}:#{@remote_path}",
mount_point_safe,
],
input: STDIN,
output: STDOUT,
error: STDERR
)
return process.wait
process.wait
end
end
end

View file

@ -4,6 +4,7 @@ module GX::Parsers
class CompletionParser < AbstractParser
def build(parser, ancestors, config)
breadcrumbs = ancestors + "completion"
# config.mode = Types::Mode::CompletionAutodetect
parser.banner = Utils.usage_line(
breadcrumbs,
@ -12,11 +13,13 @@ module GX::Parsers
)
parser.separator("\nCompletion commands:")
parser.on("--bash", "Generate bash completion") do |flag|
parser.on("--bash", "Generate bash completion") do |_|
config.mode = Types::Mode::CompletionBash
Log.info { "Set bash completion" }
end
parser.on("--zsh", "Generate zsh completion") do |flag|
parser.on("--zsh", "Generate zsh completion") do |_|
config.mode = Types::Mode::CompletionZsh
Log.info { "Set zsh completion" }
end

View file

@ -23,7 +23,7 @@ module GX::Parsers
parser.banner = Utils.usage_line(breadcrumbs + "init", "Create initial mfm configuration")
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|
opts.path = path
end

View file

@ -5,8 +5,10 @@ module GX::Parsers
class MappingParser < AbstractParser
def build(parser, ancestors, config)
breadcrumbs = ancestors + "mapping"
add_args = {name: "", path: ""}
create_args = {name: "", path: ""}
delete_args = {name: ""}
mount_args = {name: ""}
umount_args = {name: ""}
parser.banner = Utils.usage_line(
breadcrumbs,
@ -18,50 +20,94 @@ module GX::Parsers
parser.on("list", "List mappings") do
config.mode = Types::Mode::MappingList
parser.separator(Utils.help_line(breadcrumbs + "list"))
# abort("FIXME: Not implemented")
end
parser.on("create", "Create mapping") do
config.mode = Types::Mode::MappingCreate
pp parser
# pp parser
parser.banner = Utils.usage_line(breadcrumbs + "create", "Create mapping", true)
parser.separator("\nCreate options")
parser.on("-n", "--name", "Set vault name") do |name|
add_args = add_args.merge({name: name})
parser.on("-t", "--type TYPE", "Set filesystem type") do |type|
create_args = create_args.merge({type: type})
end
parser.on("-p", "--path", "Set vault encrypted path") do |path|
add_args = add_args.merge({path: path})
parser.on("-n", "--name", "Set mapping name") do |name|
create_args = create_args.merge({name: name})
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"))
end
parser.on("edit", "Edit configuration") do |flag|
parser.on("edit", "Edit configuration") do |_|
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"))
# abort("FIXME: Not implemented")
end
parser.on("mount", "Mount mapping") do |flag|
parser.on("mount", "Mount mapping") do |_|
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"))
# abort("FIXME: Not implemented")
end
parser.on("umount", "Umount mapping") do |flag|
parser.on("umount", "Umount mapping") do |_|
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"))
# abort("FIXME: Not implemented")
end
parser.on("delete", "Delete mapping") do
config.mode = Types::Mode::MappingDelete
parser.banner = Utils.usage_line(breadcrumbs + "delete", "Delete mapping", true)
parser.separator("\nDelete options")
parser.banner = Utils.usage_line(breadcrumbs + "delete", "delete mapping", true)
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})
end
parser.separator(Utils.help_line(breadcrumbs + "delete"))

View file

@ -21,24 +21,24 @@ module GX::Parsers
config.path = path
end
parser.on("-v", "--verbose", "Set more verbosity") do |flag|
parser.on("-v", "--verbose", "Set more verbosity") do |_|
Log.info { "Verbosity enabled" }
config.verbose = true
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" }
config.auto_open = true
end
parser.on("--version", "Show version") do |flag|
parser.on("--version", "Show version") do |_|
config.mode = Types::Mode::GlobalVersion
end
parser.on("-h", "--help", "Show this help") do |flag|
parser.on("-h", "--help", "Show this help") do |_|
config.mode = Types::Mode::GlobalHelp
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
parser.separator("\nGlobal commands:")
@ -46,7 +46,7 @@ module GX::Parsers
parser.on("config", "Manage configuration file") do
config.mode = Types::Mode::GlobalHelp
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)
Parsers::ConfigParser.new.build(parser, breadcrumbs, config)
@ -59,7 +59,7 @@ module GX::Parsers
parser.on("mapping", "Manage mappings") do
config.mode = Types::Mode::GlobalHelp
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)
end
@ -69,7 +69,10 @@ module GX::Parsers
# end
parser.on("completion", "Manage completion") do
config.mode = Types::Mode::GlobalCompletion
config.mode = Types::Mode::GlobalHelp
config.help_options = Parsers::Options::HelpOptions.new
config.help_options.try(&.parser_snapshot=(parser.dup))
Parsers::CompletionParser.new.build(parser, breadcrumbs, config)
end

View file

@ -4,11 +4,15 @@ module GX::Types
GlobalVersion
GlobalHelp
GlobalCompletion
# GlobalCompletion
GlobalTui
GlobalConfig
GlobalMapping
CompletionBash
CompletionZsh
CompletionAutodetect
ConfigInit
MappingCreate

View file

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

View file

@ -3,7 +3,7 @@ require "./breadcrumbs"
module GX::Utils
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,
"",
@ -12,6 +12,6 @@ module GX::Utils
end
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

5
static/completion.zsh Normal file
View file

@ -0,0 +1,5 @@
#!/bin/zsh
# mfm Zsh completion script

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"
#