Compare commits

...

24 commits

Author SHA1 Message Date
9cce357dd0 Merge branch 'feature/1-add-support-for-fs-crud' of code.apps.glenux.net:glenux/mfm into feature/1-add-support-for-fs-crud
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2023-11-26 16:05:17 +01:00
fb168d5308 feat: add base structure for commands 2023-11-26 16:05:01 +01:00
4730c77992 feat: add scaffold for operations (command design pattern) 2023-11-24 20:15:44 +01:00
9f3f3b24c1 fix: auto-open should not run on umounted filesystems
All checks were successful
continuous-integration/drone/push Build is passing
2023-11-24 19:25:50 +01:00
041550cc0f fix: handle mount errors (with the right message) 2023-11-24 19:25:21 +01:00
fd9829c283 Merge branch 'develop' of code.apps.glenux.net:glenux/mfm into develop
All checks were successful
continuous-integration/drone/push Build is passing
2023-11-24 17:04:32 +01:00
70b51527df doc: update example config to demonstrate templating 2023-11-24 17:02:17 +01:00
58e4ab05bf Update README.md
All checks were successful
continuous-integration/drone/push Build is passing
2023-11-24 09:52:05 +00:00
d4c52cd044 Merge branch 'develop' of code.apps.glenux.net:glenux/mfm into develop
All checks were successful
continuous-integration/drone/push Build is passing
2023-11-24 10:50:29 +01:00
32fea233d1 fix: rename global.mount_point to avoid misunderstanding
All checks were successful
continuous-integration/drone/push Build is passing
2023-11-24 10:49:55 +01:00
84230a6828 feat: sort by fs.name instead of fs.type 2023-11-24 10:49:32 +01:00
211419ea02 chore: add watch+rebuild target 2023-11-24 10:48:55 +01:00
5107e80aa7 Update README.md
All checks were successful
continuous-integration/drone/push Build is passing
2023-11-24 09:29:12 +00:00
7f789daefa Merge pull request 'Add option to auto-open directory after mount' (#33) from feature/30-add-option-to-auto-open-directory into develop
All checks were successful
continuous-integration/drone/push Build is passing
Reviewed-on: #33
2023-11-24 09:26:23 +00:00
cb14a04fbe feat: add support for auto-open option (-o, --open)
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2023-11-24 10:25:30 +01:00
63c0bbbb1c Merge pull request 'feature/6-add-configurable-global-mountpoint' (#32) from feature/6-add-configurable-global-mountpoint into develop
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
Reviewed-on: #32
2023-11-24 08:26:27 +00:00
8fc9f2cfda Merge branch 'develop' into feature/6-add-configurable-global-mountpoint
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-11-24 08:09:46 +00:00
23d4def217 feat: implement local & global mount_point definition
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-11-24 00:20:16 +01:00
ee3f57ec20 refactor: define abstract defs & move most functions to concerns/base
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing
2023-11-21 23:11:21 +01:00
8efe8ea5d9 Merge branch 'develop' of code.apps.glenux.net:glenux/mfm into develop
All checks were successful
continuous-integration/drone/push Build is passing
2023-11-21 00:33:47 +01:00
587bff04ca chore: pin crystal version with tool-versions 2023-11-21 00:33:37 +01:00
994f9e1885 refactor: use a better class hierarchy for filesystems
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2023-11-21 00:30:59 +01:00
8f2c2442a3 feat: add global.mountpoint and version parsing from YAML 2023-11-21 00:29:48 +01:00
d91e9a8fcd Add examples for templating & disable non-implemented parts.
All checks were successful
continuous-integration/drone/push Build is passing
2023-11-20 15:45:40 +00:00
32 changed files with 611 additions and 249 deletions

1
.tool-versions Normal file
View file

@ -0,0 +1 @@
crystal 1.10.1

View file

@ -6,4 +6,8 @@
all: build
build:
shards build
shards build --error-trace
@echo SUCCESS
watch:
watchexec --restart --delay-run 3 -c -e cr make build

View file

@ -62,9 +62,12 @@ version](https://code.apps.glenux.net/glenux/mfm/releases) of MFM.
```
Usage: mfm [options]
Global options:
-c, --config FILE Specify configuration file
-h, --help Display this help
Global options
-c, --config FILE Set configuration file
-v, --verbose Set more verbosity
-o, --open Automatically open directory after mount
--version Show version
-h, --help Show this help
Commands (not implemented yet):
create Add a new filesystem
@ -84,19 +87,20 @@ detail the filesystem names, types, and respective configurations.
### YAML File Format
```yaml
---
version: "1"
global:
mountpoint: "/home/user/mnt/{{name}}"
mountpoint: "{{env.HOME}}/mnt"
filesystems:
- type: "gocryptfs"
name: "Work - SSH Keys"
encrypted_path: "/home/user/.ssh/keyring.work"
encrypted_path: "/home/user/.ssh/keyring.work.vault"
- type: "sshfs"
name: "Personal - Media Server"
remote_user: "user"
remote_user: "{{env.USER}}"
remote_host: "mediaserver.local"
remote_path: "/mnt/largedisk/music"
remote_port: 22

View file

@ -1,9 +1,8 @@
---
version: 1
global:
mountpoint: "~/mnt"
mountpoint: "{{env.HOME}}/mnt"
filesystems:
- type: gocryptfs
@ -16,7 +15,7 @@ filesystems:
- type: sshfs
name: "Personal - Remote Media Server"
remote_user: user
remote_user: "{{env.USER}}"
remote_host: mediaserver.local
remote_port: 22
remote_path: "/remote/path/to/media"

View file

@ -40,6 +40,11 @@ module GX
@config.verbose = true
end
parser.on("-o", "--open", "Automatically open directory after mount") do |flag|
Log.info { "Auto-open enabled" }
@config.auto_open = true
end
parser.on("--version", "Show version") do |flag|
@config.mode = Config::Mode::ShowVersion
end
@ -94,13 +99,69 @@ module GX
STDOUT.puts "#{PROGRAM_NAME} #{VERSION}"
when Config::Mode::Mount
@config.load_from_file
mount
filesystem = choose_filesystem
raise Models::InvalidFilesystemError.new("Invalid filesystem") if filesystem.nil?
mount_or_umount(filesystem)
auto_open(filesystem) if filesystem.mounted? && @config.auto_open
end
end
def mount()
names_display = {} of String => NamedTuple(filesystem: Filesystem, ansi_name: String)
@config.filesystems.each do |filesystem|
def auto_open(filesystem)
# FIXME: support xdg-open
# FIXME: support mailcap
# FIXME: support user-defined command
# FIXME: detect graphical environment
mount_point_safe = filesystem.mount_point
raise Models::InvalidMountpointError.new("Invalid filesystem") if mount_point_safe.nil?
if graphical_environment?
process = Process.new(
"xdg-open", ## FIXME: make configurable
[mount_point_safe],
input: STDIN,
output: STDOUT,
error: STDERR
)
unless process.wait.success?
puts "Error opening filesystem".colorize(:red)
return
end
else
process = Process.new(
"vifm", ## FIXME: make configurable
[mount_point_safe],
input: STDIN,
output: STDOUT,
error: STDERR
)
unless process.wait.success?
puts "Error opening filesystem".colorize(:red)
return
end
end
end
def graphical_environment?
if ENV["DISPLAY"]? || ENV["WAYLAND_DISPLAY"]?
return true
end
return false
end
alias FsTuple = NamedTuple(
filesystem: Models::Filesystems::AbstractConfig,
ansi_name: String
)
def choose_filesystem()
names_display = {} of String => FsTuple
config_root = @config.root
return if config_root.nil?
config_root.filesystems.each do |filesystem|
fs_str = filesystem.type.ljust(12,' ')
suffix = ""
@ -119,16 +180,25 @@ module GX
}
end
result_filesystem_name = Fzf.run(names_display.values.map(&.[:ansi_name]).sort).strip
## FIXME: feat: allow to sort by name or by filesystem
sorted_values = names_display.values.sort_by { |item| item[:filesystem].name }
result_filesystem_name = 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
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)
if !selected_filesystem.mounted?
selected_filesystem.mount()
else
selected_filesystem.umount()
end
end
end
end

View file

@ -5,12 +5,15 @@
require "crinja"
require "./filesystems"
require "./models"
module GX
class Config
Log = ::Log.for("config")
class MissingFileError < Exception
end
enum Mode
ConfigAdd
ConfigDelete
@ -23,28 +26,30 @@ module GX
record AddArgs, name : String, path : String
record DelArgs, name : String
getter filesystems : Array(Filesystem)
getter home_dir : String
getter root : Models::RootConfig?
property verbose : Bool
property mode : Mode
property path : String?
property args : AddArgs.class | DelArgs.class | NoArgs.class
property auto_open : Bool
def initialize()
if !ENV["HOME"]?
raise "Home directory not found"
end
raise Models::InvalidEnvironmentError.new("Home directory not found") if !ENV["HOME"]?
@home_dir = ENV["HOME"]
@verbose = false
@auto_open = false
@mode = Mode::Mount
@filesystems = [] of Filesystem
@filesystems = [] of Models::Filesystems::AbstractConfig
@path = 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"),
@ -56,47 +61,44 @@ module GX
possible_files.each do |file_path|
if File.exists?(file_path)
Log.info { "Configuration file found: #{file_path}" }
Log.info { "Configuration file found: #{file_path}" } if @verbose
return file_path if File.exists?(file_path)
else
Log.debug { "Configuration file not found: #{file_path}" }
Log.debug { "Configuration file not found: #{file_path}" } if @verbose
end
end
Log.error { "No configuration file found in any of the standard locations" }
raise "Configuration file not found"
raise MissingFileError.new("Configuration file not found")
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
@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
private def load_filesystems(config_path : String)
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
root = Models::RootConfig.from_yaml(file_patched)
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.from_yaml(filesystem_data.to_yaml)
# @filesystems << Filesystem.new(name, encrypted_path, "#{name}.Open")
mount_point_base_safe = root.global.mount_point_base
raise Models::InvalidMountpointError.new("Invalid global mount point") if mount_point_base_safe.nil?
root.filesystems.each do |selected_filesystem|
if !selected_filesystem.mount_point?
selected_filesystem.mount_point =
File.join(mount_point_base_safe, selected_filesystem.mounted_name)
end
end
@root = root
end
end
end

View file

@ -1,48 +0,0 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2023 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2023 Glenn Y. Rolland <glenux@glenux.net>
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"

View file

@ -1,47 +0,0 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2023 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2023 Glenn Y. Rolland <glenux@glenux.net>
require "shellwords"
require "./filesystem"
module GX
class GoCryptFS < Filesystem
getter name : String = ""
getter encrypted_path : String = ""
@[YAML::Field(key: "mount_dir", ignore: true)]
getter mount_dir : String = ""
include GenericFilesystem
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 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
end

View file

@ -1,48 +0,0 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2023 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2023 Glenn Y. Rolland <glenux@glenux.net>
require "shellwords"
require "./filesystem"
module GX
class HttpDirFS < Filesystem
getter name : String = ""
getter url : String = ""
@[YAML::Field(key: "mount_dir", ignore: true)]
getter mount_dir : String = ""
include GenericFilesystem
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}")
end
def mount
super do
input = STDIN
output = STDOUT
error = STDERR
process = Process.new(
"httpdirfs",
["#{url}", mount_dir],
input: input,
output: output,
error: error
)
unless process.wait.success?
puts "Error mounting the filesystem".colorize(:red)
return
end
end
end
end
end

View file

@ -1,54 +0,0 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2023 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2023 Glenn Y. Rolland <glenux@glenux.net>
require "shellwords"
require "./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"
@[YAML::Field(key: "mount_dir", ignore: true)]
getter mount_dir : String = ""
include GenericFilesystem
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 mount
super do
input = STDIN
output = STDOUT
error = STDERR
process = Process.new(
"sshfs",
[
"-p", remote_port,
"#{remote_user}@#{remote_host}:#{remote_path}",
mount_dir
],
input: input,
output: output,
error: error
)
unless process.wait.success?
puts "Error mounting the filesystem".colorize(:red)
return
end
end
end
end
end

View file

@ -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

12
src/models.cr Normal file
View file

@ -0,0 +1,12 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2023 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2023 Glenn Y. Rolland <glenux@glenux.net>
require "./models/root_config"
require "./models/global_config"
require "./models/filesystems/gocryptfs_config"
require "./models/filesystems/sshfs_config"
require "./models/filesystems/httpdirfs_config"
require "./models/filesystems/abstract_config"
require "./models/exceptions"

15
src/models/exceptions.cr Normal file
View file

@ -0,0 +1,15 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2023 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2023 Glenn Y. Rolland <glenux@glenux.net>
require "yaml"
module GX::Models
class InvalidFilesystemError < Exception
end
class InvalidMountpointError < Exception
end
end

View file

@ -0,0 +1,32 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2023 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2023 Glenn Y. Rolland <glenux@glenux.net>
require "yaml"
module GX::Models::Filesystems
abstract class AbstractConfig
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

View file

@ -0,0 +1,14 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2023 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2023 Glenn Y. Rolland <glenux@glenux.net>
module GX::Models::Concerns
module Mount
def mount()
_mount_wrapper() do
_mount_action
end
end
end
end

View file

@ -3,7 +3,12 @@
# SPDX-FileCopyrightText: 2023 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2023 Glenn Y. Rolland <glenux@glenux.net>
require "./filesystems/gocryptfs"
require "./filesystems/sshfs"
require "./filesystems/httpdirfs"
require "./filesystems/filesystem"
module GX::Models::Concerns
module MountPoint
def mount_point?()
!mount_point.nil?
end
end
end

View file

@ -0,0 +1,29 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2023 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2023 Glenn Y. Rolland <glenux@glenux.net>
module GX::Models::Concerns
module MountWrapper
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
result_status = yield
if result_status.success?
puts "Models #{name} is now available on #{mount_point_safe}".colorize(:green)
else
puts "Error mounting the vault".colorize(:red)
return
end
end
end
end

View file

@ -0,0 +1,17 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2023 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2023 Glenn Y. Rolland <glenux@glenux.net>
module GX::Models::Concerns
module Mounted
def mounted?() : Bool
mount_point_safe = @mount_point
raise InvalidMountpointError.new("Invalid mountpoint value") if mount_point_safe.nil?
`mount`.includes?(" on #{mount_point_safe} type ")
end
end
end

View file

@ -0,0 +1,23 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2023 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2023 Glenn Y. Rolland <glenux@glenux.net>
module GX::Models::Concerns
module Umount
def umount() : Nil
mount_point_safe = @mount_point
raise InvalidMountpointError.new("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
end
end

View file

@ -0,0 +1,46 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2023 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2023 Glenn Y. Rolland <glenux@glenux.net>
require "shellwords"
require "./abstract_config"
require "./concerns/mount_wrapper"
require "./concerns/mounted"
require "./concerns/mount"
require "./concerns/umount"
require "./concerns/mount_point"
module GX::Models::Filesystems
class GoCryptFSConfig < AbstractConfig
getter encrypted_path : String = ""
include Concerns::Mount
include Concerns::Mounted
include Concerns::MountPoint
include Concerns::MountWrapper
include Concerns::Umount
def _mounted_prefix()
"#{encrypted_path}"
end
def mounted_name()
"#{@name}.Open"
end
def _mount_action()
mount_point_safe = @mount_point
raise InvalidMountpointError.new("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
)
return process.wait
end
end
end

View file

@ -0,0 +1,47 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2023 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2023 Glenn Y. Rolland <glenux@glenux.net>
require "shellwords"
require "./abstract_config"
require "./concerns/mount_wrapper"
require "./concerns/mounted"
require "./concerns/mount"
require "./concerns/umount"
require "./concerns/mount_point"
module GX::Models::Filesystems
class HttpDirFSConfig < AbstractConfig
getter url : String = ""
include Concerns::Mount
include Concerns::Mounted
include Concerns::MountPoint
include Concerns::MountWrapper
include Concerns::Umount
def _mounted_prefix()
"httpdirfs"
end
def mounted_name()
@name
end
def _mount_action()
mount_point_safe = @mount_point
raise InvalidMountpointError.new("Invalid mount point") if mount_point_safe.nil?
process = Process.new(
"httpdirfs",
["#{@url}", mount_point_safe],
input: STDIN,
output: STDOUT,
error: STDERR
)
return process.wait
end
end
end

View file

@ -0,0 +1,53 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2023 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2023 Glenn Y. Rolland <glenux@glenux.net>
require "shellwords"
require "./abstract_config"
require "./concerns/mount_wrapper"
require "./concerns/mounted"
require "./concerns/mount"
require "./concerns/umount"
require "./concerns/mount_point"
module GX::Models::Filesystems
class SshFSConfig < AbstractConfig
getter remote_path : String = ""
getter remote_user : String = ""
getter remote_host : String = ""
getter remote_port : String = "22"
include Concerns::Mount
include Concerns::Mounted
include Concerns::MountPoint
include Concerns::MountWrapper
include Concerns::Umount
def _mounted_prefix()
"#{@remote_user}@#{@remote_host}:#{@remote_path}"
end
def mounted_name()
@name
end
def _mount_action()
mount_point_safe = @mount_point
raise InvalidMountpointError.new("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
)
return process.wait
end
end
end

View file

@ -0,0 +1,32 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2023 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2023 Glenn Y. Rolland <glenux@glenux.net>
require "yaml"
require "./filesystems/abstract_config"
module GX::Models
class InvalidEnvironmentError < Exception
end
class GlobalConfig
include YAML::Serializable
include YAML::Serializable::Strict
@[YAML::Field(key: "mount_point_base")]
getter mount_point_base : String?
def after_initialize()
raise InvalidEnvironmentError.new("Home directory not found") if !ENV["HOME"]?
home_dir = ENV["HOME"]
# Set default mountpoint from global if none defined
if @mount_point_base.nil? || @mount_point_base.try &.empty?
@mount_point_base = File.join(home_dir, "mnt")
end
end
end
end

41
src/models/root_config.cr Normal file
View file

@ -0,0 +1,41 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2023 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2023 Glenn Y. Rolland <glenux@glenux.net>
require "yaml"
require "./filesystems/abstract_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 "<null>"
# 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(Filesystems::AbstractConfig)
end
end

View file

@ -0,0 +1,14 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2023 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2023 Glenn Y. Rolland <glenux@glenux.net>
require "../abstract_command"
module GX::Operations::Filesystems
class CreateCommand < AbstractCommand
def execute()
raise NotImplementedError.new("CreateCommand is not Implemented")
end
end
end

View file

@ -0,0 +1,14 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2023 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2023 Glenn Y. Rolland <glenux@glenux.net>
require "../abstract_command"
module GX::Operations::Filesystems
class DeleteCommand < AbstractCommand
def execute()
raise NotImplementedError.new("DeleteCommand is not Implemented")
end
end
end

View file

@ -0,0 +1,14 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2023 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2023 Glenn Y. Rolland <glenux@glenux.net>
require "../abstract_command"
module GX::Operations::Filesystems
class EditCommand < AbstractCommand
def execute()
raise NotImplementedError.new("EditCommand is not Implemented")
end
end
end

View file

@ -0,0 +1,15 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2023 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2023 Glenn Y. Rolland <glenux@glenux.net>
require "../abstract_command"
module GX::Operations::Filesystems
class ListCommand < AbstractCommand
def execute()
raise NotImplementedError.new("ListCommand is not Implemented")
end
end
end

View file

@ -0,0 +1,14 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2023 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2023 Glenn Y. Rolland <glenux@glenux.net>
require "../abstract_command"
module GX::Operations::Filesystems
class MountCommand < AbstractCommand
def execute()
raise NotImplementedError.new("MountCommand is not Implemented")
end
end
end

View file

@ -0,0 +1,15 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2023 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2023 Glenn Y. Rolland <glenux@glenux.net>
require "../abstract_command"
module GX::Operations::Filesystems
class SelectCommand < AbstractCommand
def execute()
raise NotImplementedError.new("SelectCommand is not Implemented")
end
end
end

View file

@ -0,0 +1,14 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2023 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2023 Glenn Y. Rolland <glenux@glenux.net>
require "../abstract_command"
module GX::Operations::Filesystems
class UmountCommand < AbstractCommand
def execute()
raise NotImplementedError.new("UmountCommand is not Implemented")
end
end
end

View file

@ -0,0 +1,15 @@
# SPDX-License-Identifier: GPL-3.0-or-later
#
# SPDX-FileCopyrightText: 2023 Glenn Y. Rolland <glenux@glenux.net>
# Copyright © 2023 Glenn Y. Rolland <glenux@glenux.net>
require "../abstract_command"
module GX::Operations::Global
class EditCommand < AbstractCommand
def execute()
raise NotImplementedError.new("EditCommand is not Implemented")
end
end
end