Compare commits
No commits in common. "develop" and "v0.1.9" have entirely different histories.
65 changed files with 389 additions and 1735 deletions
|
@ -1,32 +0,0 @@
|
||||||
---
|
|
||||||
# Example configuration for Code-Preloader
|
|
||||||
|
|
||||||
# List of repository paths to preload
|
|
||||||
# source_list:
|
|
||||||
# - "path/to/repo1"
|
|
||||||
# - "path/to/repo2"
|
|
||||||
|
|
||||||
# List of patterns to ignore during preloading
|
|
||||||
ignore_list:
|
|
||||||
- ^\.git/
|
|
||||||
- ^lib.*
|
|
||||||
- ^doc/
|
|
||||||
- ^bin/
|
|
||||||
- ^_prompts/
|
|
||||||
- ^\.reuse/
|
|
||||||
- ^LICENSES/
|
|
||||||
- ^\.vagrant/
|
|
||||||
- ^scripts/
|
|
||||||
|
|
||||||
# Path to the output file (if null, output to STDOUT)
|
|
||||||
output_path: null
|
|
||||||
|
|
||||||
prompt:
|
|
||||||
# Optional: Path to a file containing the prompt header
|
|
||||||
header_path: null
|
|
||||||
|
|
||||||
# Optional: Path to a file containing the prompt footer
|
|
||||||
footer_path: null
|
|
||||||
|
|
||||||
# Optional: Path to a file container a jinja template to structure the prompt
|
|
||||||
template_path: null
|
|
20
.drone.yml
20
.drone.yml
|
@ -5,7 +5,7 @@ name: default
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: build:binary
|
- name: build:binary
|
||||||
image: crystallang/crystal:1.11.0-alpine
|
image: crystallang/crystal:1.7.3
|
||||||
environment:
|
environment:
|
||||||
PACKAGE_BASENAME: mfm_linux_amd64
|
PACKAGE_BASENAME: mfm_linux_amd64
|
||||||
volumes:
|
volumes:
|
||||||
|
@ -13,16 +13,11 @@ steps:
|
||||||
path: /_cache
|
path: /_cache
|
||||||
commands:
|
commands:
|
||||||
- pwd
|
- pwd
|
||||||
# - |
|
- apt-get update &&
|
||||||
# apt-get update && \
|
apt-get install -y cmake g++ libevent-dev libpcre3-dev libyaml-dev
|
||||||
# apt-get install -y \
|
|
||||||
# cmake g++ \
|
|
||||||
# libevent-dev libpcre3-dev \
|
|
||||||
# libyaml-dev liblzma-dev
|
|
||||||
- shards install
|
- shards install
|
||||||
- shards build --production --static
|
- shards build --production --static
|
||||||
- strip bin/mfm
|
- strip bin/mfm
|
||||||
- ./bin/mfm --version
|
|
||||||
- mkdir -p /_cache/bin
|
- mkdir -p /_cache/bin
|
||||||
- cp -r bin/mfm /_cache/bin/$PACKAGE_BASENAME
|
- cp -r bin/mfm /_cache/bin/$PACKAGE_BASENAME
|
||||||
|
|
||||||
|
@ -79,17 +74,8 @@ steps:
|
||||||
|
|
||||||
# FIXME: handle multi-arch
|
# FIXME: handle multi-arch
|
||||||
# FIXME: publish only on tags
|
# FIXME: publish only on tags
|
||||||
services:
|
|
||||||
- name: docker
|
|
||||||
image: docker:dind
|
|
||||||
privileged: true
|
|
||||||
volumes:
|
|
||||||
- name: dockersock
|
|
||||||
path: /var/run
|
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
- name: cache
|
- name: cache
|
||||||
temp: {}
|
temp: {}
|
||||||
- name: dockersock
|
|
||||||
temp: {}
|
|
||||||
#
|
#
|
||||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -3,7 +3,6 @@
|
||||||
# SPDX-FileCopyrightText: 2023 Glenn Y. Rolland <glenux@glenux.net>
|
# SPDX-FileCopyrightText: 2023 Glenn Y. Rolland <glenux@glenux.net>
|
||||||
# Copyright © 2023 Glenn Y. Rolland <glenux@glenux.net>
|
# Copyright © 2023 Glenn Y. Rolland <glenux@glenux.net>
|
||||||
|
|
||||||
/_*
|
|
||||||
.vagrant
|
.vagrant
|
||||||
bin
|
bin
|
||||||
lib
|
lib
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
crystal 1.10.1
|
|
24
Makefile
24
Makefile
|
@ -3,29 +3,7 @@
|
||||||
# SPDX-FileCopyrightText: 2023 Glenn Y. Rolland <glenux@glenux.net>
|
# SPDX-FileCopyrightText: 2023 Glenn Y. Rolland <glenux@glenux.net>
|
||||||
# Copyright © 2023 Glenn Y. Rolland <glenux@glenux.net>
|
# Copyright © 2023 Glenn Y. Rolland <glenux@glenux.net>
|
||||||
|
|
||||||
PREFIX=/usr
|
|
||||||
|
|
||||||
all: build
|
all: build
|
||||||
|
|
||||||
prepare:
|
|
||||||
shards install
|
|
||||||
|
|
||||||
build:
|
build:
|
||||||
shards build --error-trace -Dpreview_mt
|
shards build
|
||||||
@echo SUCCESS
|
|
||||||
|
|
||||||
watch:
|
|
||||||
watchexec --restart --delay-run 3 -c -e cr make build
|
|
||||||
|
|
||||||
spec: test
|
|
||||||
test:
|
|
||||||
crystal spec --error-trace
|
|
||||||
|
|
||||||
install:
|
|
||||||
install \
|
|
||||||
-m 755 \
|
|
||||||
bin/code-preloader \
|
|
||||||
$(PREFIX)/bin
|
|
||||||
|
|
||||||
.PHONY: spec test build all prepare install
|
|
||||||
|
|
||||||
|
|
95
README.md
95
README.md
|
@ -6,13 +6,6 @@
|
||||||
-->
|
-->
|
||||||
|
|
||||||
[![Build Status](https://cicd.apps.glenux.net/api/badges/glenux/mfm/status.svg)](https://cicd.apps.glenux.net/glenux/mfm)
|
[![Build Status](https://cicd.apps.glenux.net/api/badges/glenux/mfm/status.svg)](https://cicd.apps.glenux.net/glenux/mfm)
|
||||||
![License LGPL3.0-or-later](https://img.shields.io/badge/license-LGPL3.0--or--later-blue.svg)
|
|
||||||
[![Donate on patreon](https://img.shields.io/badge/patreon-donate-orange.svg)](https://patreon.com/glenux)
|
|
||||||
|
|
||||||
> :information_source: This project is available on our self-hosted server and
|
|
||||||
> on CodeBerg and GitHub as mirrors. For the latest updates and comprehensive
|
|
||||||
> version of our project, please visit our primary repository at:
|
|
||||||
> <https://code.apps.glenux.net/glenux/mfm>.
|
|
||||||
|
|
||||||
# Minimalist Fuse Manager (MFM)
|
# Minimalist Fuse Manager (MFM)
|
||||||
|
|
||||||
|
@ -26,27 +19,11 @@ Before using MFM, make sure the following tools are installed on your system:
|
||||||
- **sshfs**: <https://github.com/libfuse/sshfs>
|
- **sshfs**: <https://github.com/libfuse/sshfs>
|
||||||
- **httpdirfs**: <https://github.com/fangfufu/httpdirfs>
|
- **httpdirfs**: <https://github.com/fangfufu/httpdirfs>
|
||||||
- **fzf**: <https://github.com/junegunn/fzf>
|
- **fzf**: <https://github.com/junegunn/fzf>
|
||||||
- libpcre3
|
|
||||||
- libevent-2.1
|
|
||||||
|
|
||||||
For Debian/Ubuntu you can use the following command:
|
|
||||||
|
|
||||||
```shell-session
|
|
||||||
$ sudo apt-get update && sudo apt-get install libpcre3 libevent-2.1-7 fzf gocryptfs httpdirfs sshfs
|
|
||||||
```
|
|
||||||
|
|
||||||
## Building from source
|
|
||||||
|
|
||||||
To build from source, you'll also need:
|
To build from source, you'll also need:
|
||||||
|
|
||||||
- **crystal-lang**: <https://crystal-lang.org/>
|
- **crystal-lang**: <https://crystal-lang.org/>
|
||||||
|
|
||||||
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
|
|
||||||
```
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### 1. From Source
|
### 1. From Source
|
||||||
|
@ -59,63 +36,23 @@ $ sudo apt-get update && sudo apt-get install libpcre3-dev libevent-2.1-dev
|
||||||
|
|
||||||
### 2. Binary Download
|
### 2. Binary Download
|
||||||
|
|
||||||
Alternatively, download [a pre-compiled binary
|
Alternatively, download a pre-compiled binary version of MFM.
|
||||||
version](https://code.apps.glenux.net/glenux/mfm/releases) of MFM.
|
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
### Command Line Options
|
### Command Line Options
|
||||||
|
|
||||||
Global
|
|
||||||
|
|
||||||
```
|
```
|
||||||
Usage: mfm [options]
|
Usage: mfm [options]
|
||||||
|
|
||||||
Global options
|
Global options:
|
||||||
-c, --config FILE Set configuration file
|
-c, --config FILE Specify configuration file
|
||||||
-v, --verbose Set more verbosity
|
-h, --help Display this help
|
||||||
-o, --open Automatically open directory after mount
|
|
||||||
--version Show version
|
|
||||||
-h, --help Show this help
|
|
||||||
|
|
||||||
Commands (not implemented yet):
|
Commands:
|
||||||
config Manage configuration file
|
create Add a new filesystem
|
||||||
mapping Manage filesystems
|
delete Remove an existing filesystem
|
||||||
```
|
edit Modify the configuration
|
||||||
|
|
||||||
Config management
|
|
||||||
|
|
||||||
```
|
|
||||||
Usage: mfm filesystem [options]
|
|
||||||
|
|
||||||
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):
|
|
||||||
init Create init file
|
|
||||||
```
|
|
||||||
|
|
||||||
Filesystem management
|
|
||||||
|
|
||||||
```
|
|
||||||
Usage: mfm mapping [options]
|
|
||||||
|
|
||||||
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):
|
|
||||||
list List fuse mappings
|
|
||||||
create Create new fuse mapping
|
|
||||||
edit Edit fuse mapping
|
|
||||||
delete Create new fuse mapping
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Demo
|
### Demo
|
||||||
|
@ -124,26 +61,24 @@ Commands (not implemented yet):
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
MFM uses a YAML configuration file, typically found at `~/.config/mfm.yml`, to
|
MFM uses a YAML configuration file, typically found at `~/.config/mfm.yml`, to detail the filesystem names, types, and respective configurations.
|
||||||
detail the filesystem names, types, and respective configurations.
|
|
||||||
|
|
||||||
### YAML File Format
|
### YAML File Format
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
---
|
|
||||||
version: "1"
|
version: "1"
|
||||||
|
|
||||||
global:
|
global:
|
||||||
mountpoint: "{{env.HOME}}/mnt"
|
mountpoint: "/home/user/mnt/{{name}}"
|
||||||
|
|
||||||
filesystems:
|
filesystems:
|
||||||
- type: "gocryptfs"
|
- type: "gocryptfs"
|
||||||
name: "Work - SSH Keys"
|
name: "Work - SSH Keys"
|
||||||
encrypted_path: "/home/user/.ssh/keyring.work.vault"
|
encrypted_path: "/home/user/.ssh/keyring.work"
|
||||||
|
|
||||||
- type: "sshfs"
|
- type: "sshfs"
|
||||||
name: "Personal - Media Server"
|
name: "Personal - Media Server"
|
||||||
remote_user: "{{env.USER}}"
|
remote_user: "user"
|
||||||
remote_host: "mediaserver.local"
|
remote_host: "mediaserver.local"
|
||||||
remote_path: "/mnt/largedisk/music"
|
remote_path: "/mnt/largedisk/music"
|
||||||
remote_port: 22
|
remote_port: 22
|
||||||
|
@ -151,7 +86,7 @@ filesystems:
|
||||||
- type: httpdirfs
|
- type: httpdirfs
|
||||||
name: "Debian Repository"
|
name: "Debian Repository"
|
||||||
url: "http://ftp.debian.org/debian/"
|
url: "http://ftp.debian.org/debian/"
|
||||||
|
|
||||||
# Add more filesystems as needed
|
# Add more filesystems as needed
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -167,7 +102,7 @@ Contributing to MFM:
|
||||||
6. **Submit a Pull Request**: Begin a pull request to the main repository and explain your changes.
|
6. **Submit a Pull Request**: Begin a pull request to the main repository and explain your changes.
|
||||||
7. **Review**: Await feedback from the maintainers and respond as necessary.
|
7. **Review**: Await feedback from the maintainers and respond as necessary.
|
||||||
|
|
||||||
By contributing, you agree to our code of conduct and license terms.
|
By contributing, you agree to our code of conduct and GPL-2 license terms.
|
||||||
|
|
||||||
## Authors and Contributors
|
## Authors and Contributors
|
||||||
|
|
||||||
|
@ -180,5 +115,5 @@ By contributing, you agree to our code of conduct and license terms.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
GNU GPL-3
|
GNU GPL-3
|
||||||
|
|
||||||
|
|
2
Vagrantfile
vendored
2
Vagrantfile
vendored
|
@ -30,5 +30,5 @@ Vagrant.configure('2') do |config|
|
||||||
machine.vm.network 'forwarded_port', guest: 80, host: 1080, host_ip: '127.0.0.1'
|
machine.vm.network 'forwarded_port', guest: 80, host: 1080, host_ip: '127.0.0.1'
|
||||||
end
|
end
|
||||||
|
|
||||||
config.vm.provision 'shell', path: 'scripts/vagrant-provision/base.sh'
|
config.vm.provision 'shell', path: 'scripts/vagrant.provision.sh'
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
---
|
---
|
||||||
|
|
||||||
version: 1
|
version: 1
|
||||||
|
|
||||||
global:
|
global:
|
||||||
mountpoint: "{{env.HOME}}/mnt"
|
mountpoint: "~/mnt"
|
||||||
|
|
||||||
filesystems:
|
filesystems:
|
||||||
- type: gocryptfs
|
- type: gocryptfs
|
||||||
|
@ -15,7 +16,7 @@ filesystems:
|
||||||
|
|
||||||
- type: sshfs
|
- type: sshfs
|
||||||
name: "Personal - Remote Media Server"
|
name: "Personal - Remote Media Server"
|
||||||
remote_user: "{{env.USER}}"
|
remote_user: user
|
||||||
remote_host: mediaserver.local
|
remote_host: mediaserver.local
|
||||||
remote_port: 22
|
remote_port: 22
|
||||||
remote_path: "/remote/path/to/media"
|
remote_path: "/remote/path/to/media"
|
||||||
|
|
|
@ -1,64 +0,0 @@
|
||||||
#!/bin/sh -eu
|
|
||||||
# vim: set ts=2 sw=2 et:
|
|
||||||
|
|
||||||
LOCAL_PROJECT_PATH="${1-$PWD}"
|
|
||||||
|
|
||||||
TARGET_ARCH="${2-amd64}"
|
|
||||||
|
|
||||||
DOCKER_IMAGE=""
|
|
||||||
|
|
||||||
BUILD_COMMAND=" \
|
|
||||||
shards build --static --release \
|
|
||||||
&& chown 1000:1000 -R bin \
|
|
||||||
&& find bin -type f -maxdepth 1 -exec mv {} {}_${TARGET_ARCH} \; \
|
|
||||||
"
|
|
||||||
INSTALL_CRYSTAL=" \
|
|
||||||
echo '@edge http://dl-cdn.alpinelinux.org/alpine/edge/community' >>/etc/apk/repositories \
|
|
||||||
&& apk add --update --no-cache --force-overwrite \
|
|
||||||
crystal@edge \
|
|
||||||
g++ \
|
|
||||||
gc-dev \
|
|
||||||
libxml2-dev \
|
|
||||||
llvm16-dev \
|
|
||||||
llvm16-static \
|
|
||||||
make \
|
|
||||||
musl-dev \
|
|
||||||
openssl-dev \
|
|
||||||
openssl-libs-static \
|
|
||||||
pcre-dev \
|
|
||||||
shards@edge \
|
|
||||||
yaml-dev \
|
|
||||||
yaml-static \
|
|
||||||
zlib-dev \
|
|
||||||
zlib-static \
|
|
||||||
"
|
|
||||||
|
|
||||||
# setup arch
|
|
||||||
case "$TARGET_ARCH" in
|
|
||||||
amd64) DOCKER_IMAGE="alpine" ;;
|
|
||||||
arm64) DOCKER_IMAGE="multiarch/alpine:aarch64-edge" ;;
|
|
||||||
armel) DOCKER_IMAGE="multiarch/alpine:armv7-edge" ;;
|
|
||||||
# armhf) DOCKER_IMAGE="multiarch/alpine:armhf-edge" ;;
|
|
||||||
# i386) DOCKER_IMAGE="multiarch/alpine:x86-edge" ;;
|
|
||||||
mips) DOCKER_IMAGE="multiarch/alpine:mips-edge" ;;
|
|
||||||
mipsel) DOCKER_IMAGE="multiarch/alpine:mipsel-edge" ;;
|
|
||||||
powerpc) DOCKER_IMAGE="multiarch/alpine:powerpc-edge" ;;
|
|
||||||
ppc64el) DOCKER_IMAGE="multiarch/alpine:ppc64el-edge" ;;
|
|
||||||
s390x) DOCKER_IMAGE="multiarch/alpine:s390x-edge" ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
# Compile Crystal project statically for target architecture
|
|
||||||
docker pull multiarch/qemu-user-static:register
|
|
||||||
docker run \
|
|
||||||
--rm \
|
|
||||||
--privileged \
|
|
||||||
multiarch/qemu-user-static:register \
|
|
||||||
--reset
|
|
||||||
docker run \
|
|
||||||
-it \
|
|
||||||
-v "$LOCAL_PROJECT_PATH:/app" \
|
|
||||||
-w /app \
|
|
||||||
--rm \
|
|
||||||
"$DOCKER_IMAGE" \
|
|
||||||
/bin/sh -c "$INSTALL_CRYSTAL && $BUILD_COMMAND"
|
|
||||||
|
|
|
@ -1,82 +0,0 @@
|
||||||
#!/bin/sh -eu
|
|
||||||
# vim: set ts=2 sw=2 et:
|
|
||||||
|
|
||||||
LOCAL_PROJECT_PATH="${1-$PWD}"
|
|
||||||
|
|
||||||
TARGET_ARCH="${2-amd64}"
|
|
||||||
|
|
||||||
DOCKER_IMAGE=""
|
|
||||||
|
|
||||||
BUILD_COMMAND=" \
|
|
||||||
shards build --static --release \
|
|
||||||
&& chown 1000:1000 -R bin \
|
|
||||||
&& find bin -type f -maxdepth 1 -exec mv {} {}_${TARGET_ARCH} \; \
|
|
||||||
"
|
|
||||||
|
|
||||||
# crystal
|
|
||||||
INSTALL_CRYSTAL=" \
|
|
||||||
sed -i -e 's/Types: deb/Types: deb deb-src/' /etc/apt/sources.list.d/debian.sources \
|
|
||||||
&& echo 'deb http://deb.debian.org/debian unstable main' > /etc/apt/sources.list.d/sid.list \
|
|
||||||
&& echo 'deb-src http://deb.debian.org/debian unstable main' >> /etc/apt/sources.list.d/sid.list \
|
|
||||||
&& apt-get update \
|
|
||||||
&& apt-get install -y \
|
|
||||||
g++ \
|
|
||||||
libxml2-dev \
|
|
||||||
llvm-dev \
|
|
||||||
make \
|
|
||||||
libssl-dev \
|
|
||||||
libpcre3-dev \
|
|
||||||
libyaml-dev \
|
|
||||||
zlib1g-dev \
|
|
||||||
dpkg-dev \
|
|
||||||
debuild \
|
|
||||||
&& apt source crystal \
|
|
||||||
&& apt build-dep crystal \
|
|
||||||
&& ls -lF \
|
|
||||||
&& debuild -b -uc -us \
|
|
||||||
"
|
|
||||||
|
|
||||||
# setup arch
|
|
||||||
case "$TARGET_ARCH" in
|
|
||||||
amd64) DOCKER_IMAGE="debian" ;;
|
|
||||||
arm64) DOCKER_IMAGE="arm64v8/debian" ;;
|
|
||||||
armel) DOCKER_IMAGE="arm32v7/debian" ;;
|
|
||||||
armhf) DOCKER_IMAGE="armhf/debian" ;;
|
|
||||||
i386) DOCKER_IMAGE="x86/debian" ;;
|
|
||||||
mips) DOCKER_IMAGE="mips/debian" ;;
|
|
||||||
mipsel) DOCKER_IMAGE="mipsel/debian" ;;
|
|
||||||
powerpc) DOCKER_IMAGE="powerpc/debian" ;;
|
|
||||||
ppc64el) DOCKER_IMAGE="ppc64el/debian" ;;
|
|
||||||
s390x) DOCKER_IMAGE="s390x/debian" ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
# Compile Crystal project statically for target architecture
|
|
||||||
docker pull multiarch/qemu-user-static
|
|
||||||
|
|
||||||
docker run \
|
|
||||||
--rm \
|
|
||||||
--privileged \
|
|
||||||
multiarch/qemu-user-static \
|
|
||||||
--reset -p yes
|
|
||||||
|
|
||||||
set -x
|
|
||||||
docker run \
|
|
||||||
-it \
|
|
||||||
-v "$LOCAL_PROJECT_PATH:/app" \
|
|
||||||
-w /app \
|
|
||||||
--rm \
|
|
||||||
--platform linux/arm64 \
|
|
||||||
"$DOCKER_IMAGE"
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
|
|
||||||
set -x
|
|
||||||
docker run \
|
|
||||||
-it \
|
|
||||||
-v "$LOCAL_PROJECT_PATH:/app" \
|
|
||||||
-w /app \
|
|
||||||
--rm \
|
|
||||||
--platform linux/arm64 \
|
|
||||||
"$DOCKER_IMAGE" \
|
|
||||||
/bin/sh -c "$INSTALL_CRYSTAL && $BUILD_COMMAND"
|
|
||||||
|
|
|
@ -1,86 +0,0 @@
|
||||||
#!/bin/sh -eu
|
|
||||||
# vim: set ts=2 sw=2 et:
|
|
||||||
|
|
||||||
LOCAL_PROJECT_PATH="${1-$PWD}"
|
|
||||||
|
|
||||||
TARGET_ARCH="${2-arm64}"
|
|
||||||
|
|
||||||
DOCKER_IMAGE=""
|
|
||||||
|
|
||||||
BUILD_COMMAND=" \
|
|
||||||
shards build --static --release \
|
|
||||||
&& chown 1000:1000 -R bin \
|
|
||||||
&& find bin -type f -maxdepth 1 -exec mv {} {}_${TARGET_ARCH} \; \
|
|
||||||
"
|
|
||||||
|
|
||||||
# crystal
|
|
||||||
INSTALL_CRYSTAL=" \
|
|
||||||
sed -i -e '/^deb/d' /etc/apt/sources.list \
|
|
||||||
&& sed -i -e '/jessie.updates/d' /etc/apt/sources.list \
|
|
||||||
&& sed -i -e 's/^# deb/deb/' /etc/apt/sources.list \
|
|
||||||
&& apt-get update"
|
|
||||||
|
|
||||||
cat > /dev/null <<EOF
|
|
||||||
"
|
|
||||||
&& apt-get install -y \
|
|
||||||
g\+\+ \
|
|
||||||
gcc \
|
|
||||||
curl \
|
|
||||||
autoconf \
|
|
||||||
automake \
|
|
||||||
python2 \
|
|
||||||
libxml2-dev \
|
|
||||||
llvm-dev \
|
|
||||||
make \
|
|
||||||
libssl-dev \
|
|
||||||
libpcre2-dev \
|
|
||||||
libyaml-dev \
|
|
||||||
zlib1g-dev \
|
|
||||||
"
|
|
||||||
EOF
|
|
||||||
|
|
||||||
# setup arch
|
|
||||||
case "$TARGET_ARCH" in
|
|
||||||
amd64) DOCKER_IMAGE="debian:8" ;;
|
|
||||||
arm64) DOCKER_IMAGE="arm64v8/debian:8" ;;
|
|
||||||
armel) DOCKER_IMAGE="arm32v7/debian" ;;
|
|
||||||
armhf) DOCKER_IMAGE="armhf/debian" ;;
|
|
||||||
i386) DOCKER_IMAGE="x86/debian" ;;
|
|
||||||
mips) DOCKER_IMAGE="mips/debian" ;;
|
|
||||||
mipsel) DOCKER_IMAGE="mipsel/debian" ;;
|
|
||||||
powerpc) DOCKER_IMAGE="powerpc/debian" ;;
|
|
||||||
ppc64el) DOCKER_IMAGE="ppc64el/debian" ;;
|
|
||||||
s390x) DOCKER_IMAGE="s390x/debian" ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
# Compile Crystal project statically for target architecture
|
|
||||||
docker pull multiarch/qemu-user-static
|
|
||||||
|
|
||||||
docker run \
|
|
||||||
--rm \
|
|
||||||
--privileged \
|
|
||||||
multiarch/qemu-user-static \
|
|
||||||
--reset -p yes
|
|
||||||
|
|
||||||
set -x
|
|
||||||
docker run \
|
|
||||||
-it \
|
|
||||||
-v "$LOCAL_PROJECT_PATH:/app" \
|
|
||||||
-w /app \
|
|
||||||
--rm \
|
|
||||||
--platform linux/arm64 \
|
|
||||||
"$DOCKER_IMAGE" \
|
|
||||||
/bin/sh -c "$INSTALL_CRYSTAL && bash"
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
|
|
||||||
set -x
|
|
||||||
docker run \
|
|
||||||
-it \
|
|
||||||
-v "$LOCAL_PROJECT_PATH:/app" \
|
|
||||||
-w /app \
|
|
||||||
--rm \
|
|
||||||
--platform linux/arm64 \
|
|
||||||
"$DOCKER_IMAGE" \
|
|
||||||
/bin/sh -c "$INSTALL_CRYSTAL && $BUILD_COMMAND"
|
|
||||||
|
|
|
@ -1,32 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
|
|
||||||
set -e
|
|
||||||
set -u
|
|
||||||
|
|
||||||
USER="$(test -d /vagrant && echo "vagrant" || echo "debian")"
|
|
||||||
HOSTNAME="$(hostname)"
|
|
||||||
|
|
||||||
export DEBIAN_FRONTEND=noninteractive
|
|
||||||
|
|
||||||
echo "Installing required system packages"
|
|
||||||
apt-get update --allow-releaseinfo-change
|
|
||||||
apt-get install -y \
|
|
||||||
apt-transport-https \
|
|
||||||
ca-certificates \
|
|
||||||
git \
|
|
||||||
curl \
|
|
||||||
wget \
|
|
||||||
vim \
|
|
||||||
gnupg2 \
|
|
||||||
software-properties-common
|
|
||||||
|
|
||||||
# echo "Installing mfm requirements"
|
|
||||||
# apt-get install -y \
|
|
||||||
# fzf \
|
|
||||||
# sshfs \
|
|
||||||
# httpdirfs \
|
|
||||||
# libyaml-0-2 \
|
|
||||||
# libyaml-dev \
|
|
||||||
# libpcre3-dev \
|
|
||||||
# libevent-dev
|
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
|
|
||||||
# install crystal
|
|
||||||
set -e
|
|
||||||
set -u
|
|
||||||
|
|
||||||
USER="$(test -d /vagrant && echo "vagrant" || echo "debian")"
|
|
||||||
HOSTNAME="$(hostname)"
|
|
||||||
|
|
||||||
export DEBIAN_FRONTEND=noninteractive
|
|
||||||
|
|
||||||
echo "Installing required system packages"
|
|
||||||
apt-get update --allow-releaseinfo-change
|
|
||||||
|
|
||||||
echo "Installing recording requirements"
|
|
||||||
apt-get install -y \
|
|
||||||
tmux \
|
|
||||||
mdp \
|
|
||||||
bat \
|
|
||||||
asciinema \
|
|
||||||
termtosvg
|
|
||||||
|
|
|
@ -9,6 +9,26 @@ HOSTNAME="$(hostname)"
|
||||||
|
|
||||||
export DEBIAN_FRONTEND=noninteractive
|
export DEBIAN_FRONTEND=noninteractive
|
||||||
|
|
||||||
|
echo "Installing required system packages"
|
||||||
|
apt-get update --allow-releaseinfo-change
|
||||||
|
apt-get install -y \
|
||||||
|
apt-transport-https \
|
||||||
|
ca-certificates \
|
||||||
|
git \
|
||||||
|
curl \
|
||||||
|
wget \
|
||||||
|
vim \
|
||||||
|
gnupg2 \
|
||||||
|
software-properties-common
|
||||||
|
|
||||||
|
echo "Installing recording requirements"
|
||||||
|
apt-get install -y \
|
||||||
|
tmux \
|
||||||
|
mdp \
|
||||||
|
bat \
|
||||||
|
asciinema \
|
||||||
|
termtosvg
|
||||||
|
|
||||||
echo "Installing mfm requirements"
|
echo "Installing mfm requirements"
|
||||||
apt-get install -y \
|
apt-get install -y \
|
||||||
fzf \
|
fzf \
|
||||||
|
@ -19,6 +39,24 @@ apt-get install -y \
|
||||||
libpcre3-dev \
|
libpcre3-dev \
|
||||||
libevent-dev
|
libevent-dev
|
||||||
|
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -e
|
||||||
|
set -u
|
||||||
|
|
||||||
|
USER="$(test -d /vagrant && echo "vagrant" || echo "debian")"
|
||||||
|
CLUSTERS_DIR=/home/$USER/clusters
|
||||||
|
|
||||||
|
# Installation de kompose
|
||||||
|
if [ ! -f /usr/local/bin/kompose ]; then
|
||||||
|
DL="$(mktemp)"
|
||||||
|
curl \
|
||||||
|
-L https://github.com/kubernetes/kompose/releases/download/v1.22.0/kompose-linux-amd64 \
|
||||||
|
-o "$DL"
|
||||||
|
chmod +x "$DL"
|
||||||
|
mv "$DL" /usr/local/bin/kompose
|
||||||
|
fi
|
||||||
|
|
||||||
# Installing asdf
|
# Installing asdf
|
||||||
su - "$USER" -c "git config --global advice.detachedHead false"
|
su - "$USER" -c "git config --global advice.detachedHead false"
|
||||||
su - "$USER" -c "rm -rf ~/.asdf"
|
su - "$USER" -c "rm -rf ~/.asdf"
|
16
shard.lock
16
shard.lock
|
@ -1,22 +1,6 @@
|
||||||
version: 2.0
|
version: 2.0
|
||||||
shards:
|
shards:
|
||||||
ameba:
|
|
||||||
git: https://github.com/crystal-ameba/ameba.git
|
|
||||||
version: 1.6.1
|
|
||||||
|
|
||||||
crinja:
|
|
||||||
git: https://github.com/straight-shoota/crinja.git
|
|
||||||
version: 0.8.1
|
|
||||||
|
|
||||||
shellwords:
|
shellwords:
|
||||||
git: https://github.com/sztheory/shellwords-crystal.git
|
git: https://github.com/sztheory/shellwords-crystal.git
|
||||||
version: 0.1.0
|
version: 0.1.0
|
||||||
|
|
||||||
tablo:
|
|
||||||
git: https://github.com/hutou/tablo.git
|
|
||||||
version: 0.10.1
|
|
||||||
|
|
||||||
version_from_shard:
|
|
||||||
git: https://github.com/hugopl/version_from_shard.git
|
|
||||||
version: 1.2.5
|
|
||||||
|
|
||||||
|
|
27
shard.yml
27
shard.yml
|
@ -5,30 +5,29 @@
|
||||||
# Copyright © 2023 Glenn Y. Rolland <glenux@glenux.net>
|
# Copyright © 2023 Glenn Y. Rolland <glenux@glenux.net>
|
||||||
|
|
||||||
name: Minimalist FUSE Manager
|
name: Minimalist FUSE Manager
|
||||||
version: 0.2.0
|
version: 0.1.0
|
||||||
|
|
||||||
targets:
|
targets:
|
||||||
mfm:
|
mfm:
|
||||||
main: src/main.cr
|
main: src/main.cr
|
||||||
|
|
||||||
authors:
|
# authors:
|
||||||
- Glenn Y. Rolland <glenux@glenux.net>
|
# - name <email@example.com>
|
||||||
|
|
||||||
description: |
|
# description: |
|
||||||
FIXME. write description
|
# Short description of gx-vault
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
crinja:
|
|
||||||
github: straight-shoota/crinja
|
|
||||||
shellwords:
|
shellwords:
|
||||||
github: szTheory/shellwords-crystal
|
github: szTheory/shellwords-crystal
|
||||||
version_from_shard:
|
|
||||||
github: hugopl/version_from_shard
|
|
||||||
tablo:
|
|
||||||
github: hutou/tablo
|
|
||||||
|
|
||||||
development_dependencies:
|
# dependencies:
|
||||||
ameba:
|
# pg:
|
||||||
github: crystal-ameba/ameba
|
# github: will/crystal-pg
|
||||||
|
# version: "~> 0.5"
|
||||||
|
|
||||||
|
# development_dependencies:
|
||||||
|
# webmock:
|
||||||
|
# github: manastech/webmock.cr
|
||||||
|
|
||||||
license: GPL-3
|
license: GPL-3
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
require "spec"
|
|
|
@ -1,46 +0,0 @@
|
||||||
require "../spec_helper"
|
|
||||||
require "../../src/utils/breadcrumbs"
|
|
||||||
|
|
||||||
describe GX::Utils::BreadCrumbs do
|
|
||||||
context "Initialization" do
|
|
||||||
it "can initialize from array" do
|
|
||||||
# empty string
|
|
||||||
b1 = GX::Utils::BreadCrumbs.new([] of String)
|
|
||||||
b1.to_a.should be_empty
|
|
||||||
|
|
||||||
# simple string
|
|
||||||
b2 = GX::Utils::BreadCrumbs.new(["test1"])
|
|
||||||
b2.to_a.should eq(["test1"])
|
|
||||||
|
|
||||||
# array
|
|
||||||
b3 = GX::Utils::BreadCrumbs.new(["test1", "test2"])
|
|
||||||
b3.to_a.should eq(["test1", "test2"])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context "Functioning" do
|
|
||||||
it "can add values" do
|
|
||||||
# empty string
|
|
||||||
b1 = GX::Utils::BreadCrumbs.new([] of String)
|
|
||||||
b1.to_a.should be_empty
|
|
||||||
|
|
||||||
# simple string
|
|
||||||
b2 = b1 + "test1"
|
|
||||||
b2.to_a.should eq(["test1"])
|
|
||||||
|
|
||||||
b3 = b2 + "test2"
|
|
||||||
b3.to_a.should eq(["test1", "test2"])
|
|
||||||
end
|
|
||||||
|
|
||||||
it "can become a string" do
|
|
||||||
b1 = GX::Utils::BreadCrumbs.new([] of String)
|
|
||||||
b1.to_s.should eq("")
|
|
||||||
|
|
||||||
b2 = b1 + "test1"
|
|
||||||
b2.to_a.should eq("test1")
|
|
||||||
|
|
||||||
b3 = b2 + "test2"
|
|
||||||
b3.to_a.should eq("test1 test2")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
98
src/cli.cr
98
src/cli.cr
|
@ -5,38 +5,104 @@
|
||||||
|
|
||||||
require "option_parser"
|
require "option_parser"
|
||||||
require "./config"
|
require "./config"
|
||||||
require "./version"
|
require "./fzf"
|
||||||
require "./parsers/root_parser"
|
|
||||||
require "./utils/breadcrumbs"
|
|
||||||
require "./utils/fzf"
|
|
||||||
require "./file_system_manager"
|
|
||||||
require "./command_factory"
|
|
||||||
|
|
||||||
module GX
|
module GX
|
||||||
class Cli
|
class Cli
|
||||||
Log = ::Log.for("cli")
|
|
||||||
|
|
||||||
@config : GX::Config
|
@config : Config
|
||||||
|
|
||||||
def initialize
|
def initialize()
|
||||||
# Main execution starts here
|
# Main execution starts here
|
||||||
# # FIXME: add a method to verify that FZF is installed
|
|
||||||
@config = Config.new
|
@config = Config.new
|
||||||
|
|
||||||
|
## FIXME: check that FZF is installed
|
||||||
end
|
end
|
||||||
|
|
||||||
def parse_command_line(args)
|
def parse_command_line(args)
|
||||||
|
# update
|
||||||
|
add_args = { name: "", path: "" }
|
||||||
|
delete_args = { name: "" }
|
||||||
pparser = OptionParser.new do |parser|
|
pparser = OptionParser.new do |parser|
|
||||||
breadcrumbs = Utils::BreadCrumbs.new([] of String)
|
parser.banner = "Usage: #{PROGRAM_NAME} [options]\n\nGlobal options"
|
||||||
Parsers::RootParser.new.build(parser, breadcrumbs, @config)
|
|
||||||
|
parser.on("-c", "--config FILE", "Set configuration file") do |path|
|
||||||
|
@config.path = path
|
||||||
|
end
|
||||||
|
parser.on("-h", "--help", "Show this help") do |flag|
|
||||||
|
STDOUT.puts parser
|
||||||
|
exit(0)
|
||||||
|
end
|
||||||
|
|
||||||
|
parser.separator("\nCommands")
|
||||||
|
parser.on("create", "Create vault") do
|
||||||
|
@config.mode = Config::Mode::Add
|
||||||
|
|
||||||
|
parser.banner = "Usage: #{PROGRAM_NAME} create [options]\n\nGlobal options"
|
||||||
|
parser.separator("\nCommand options")
|
||||||
|
|
||||||
|
parser.on("-n", "--name", "Set vault name") do |name|
|
||||||
|
add_args = add_args.merge({ name: name })
|
||||||
|
end
|
||||||
|
parser.on("-p", "--path", "Set vault encrypted path") do |path|
|
||||||
|
add_args = add_args.merge({ path: path })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
parser.on("delete", "Delete vault") do
|
||||||
|
@config.mode = Config::Mode::Add
|
||||||
|
|
||||||
|
parser.banner = "Usage: #{PROGRAM_NAME} delete [options]\n\nGlobal options"
|
||||||
|
parser.separator("\nCommand options")
|
||||||
|
|
||||||
|
parser.on("-n", "--name", "Set vault name") do |name|
|
||||||
|
delete_args = delete_args.merge({ name: name })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
parser.on("edit", "Edit configuration") do |flag|
|
||||||
|
@config.mode = Config::Mode::Edit
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
pparser.parse(args)
|
pparser.parse(args)
|
||||||
end
|
end
|
||||||
|
|
||||||
def run
|
def run()
|
||||||
command = CommandFactory.create_command(@config, @config.mode)
|
@config.load_from_file
|
||||||
abort("ERROR: unknown command for mode #{@config.mode}") if command.nil?
|
|
||||||
|
names_display = {} of String => NamedTuple(filesystem: Filesystem, ansi_name: String)
|
||||||
|
@config.filesystems.each do |filesystem|
|
||||||
|
fs_str = filesystem.type.ljust(12,' ')
|
||||||
|
result_name =
|
||||||
|
if filesystem.mounted?
|
||||||
|
"#{fs_str} #{filesystem.name} [open]"
|
||||||
|
else
|
||||||
|
"#{fs_str} #{filesystem.name}"
|
||||||
|
end
|
||||||
|
ansi_name =
|
||||||
|
if filesystem.mounted?
|
||||||
|
"#{fs_str.colorize(:dark_gray)} #{filesystem.name} [#{ "open".colorize(:green) }]"
|
||||||
|
else
|
||||||
|
"#{fs_str.colorize(:dark_gray)} #{filesystem.name}"
|
||||||
|
end
|
||||||
|
|
||||||
|
names_display[result_name] = {
|
||||||
|
filesystem: filesystem,
|
||||||
|
ansi_name: ansi_name
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
result_filesystem_name = Fzf.run(names_display.values.map(&.[:ansi_name]).sort)
|
||||||
|
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
|
||||||
|
STDERR.puts "Vault not found: #{selected_filesystem}.".colorize(:red)
|
||||||
|
end
|
||||||
|
|
||||||
command.try &.execute
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
require "./commands"
|
|
||||||
|
|
||||||
module GX
|
|
||||||
class CommandFactory
|
|
||||||
def self.create_command(config : GX::Config, mode : GX::Types::Mode) : Commands::AbstractCommand?
|
|
||||||
classes = {{ Commands::AbstractCommand.all_subclasses }}
|
|
||||||
command_klass = classes.find { |klass| klass.handles_mode == mode }
|
|
||||||
command_klass.try &.new(config)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1 +0,0 @@
|
||||||
require "./commands/*"
|
|
|
@ -1,13 +0,0 @@
|
||||||
require "../config"
|
|
||||||
|
|
||||||
module GX::Commands
|
|
||||||
abstract class AbstractCommand
|
|
||||||
abstract def initialize(config : GX::Config)
|
|
||||||
|
|
||||||
abstract def execute
|
|
||||||
|
|
||||||
def self.mode
|
|
||||||
Gx::Types::Mode::None
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,15 +0,0 @@
|
||||||
require "./abstract_command"
|
|
||||||
|
|
||||||
module GX::Commands
|
|
||||||
class ConfigInit < AbstractCommand
|
|
||||||
def initialize(config : GX::Config) # FIXME
|
|
||||||
end
|
|
||||||
|
|
||||||
def execute
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.handles_mode
|
|
||||||
GX::Types::Mode::ConfigInit
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -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
|
|
|
@ -1,15 +0,0 @@
|
||||||
require "./abstract_command"
|
|
||||||
|
|
||||||
module GX::Commands
|
|
||||||
class GlobalConfig < AbstractCommand
|
|
||||||
def initialize(config : GX::Config) # FIXME
|
|
||||||
end
|
|
||||||
|
|
||||||
def execute
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.handles_mode
|
|
||||||
GX::Types::Mode::GlobalConfig
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,18 +0,0 @@
|
||||||
require "./abstract_command"
|
|
||||||
|
|
||||||
module GX::Commands
|
|
||||||
class GlobalHelp < AbstractCommand
|
|
||||||
def initialize(@config : GX::Config) # FIXME
|
|
||||||
end
|
|
||||||
|
|
||||||
def execute
|
|
||||||
STDOUT.puts ""
|
|
||||||
@config.help_options.try { |opts| puts opts.parser_snapshot }
|
|
||||||
exit(0)
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.handles_mode
|
|
||||||
GX::Types::Mode::GlobalHelp
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,16 +0,0 @@
|
||||||
require "./abstract_command"
|
|
||||||
|
|
||||||
module GX::Commands
|
|
||||||
class GlobalMapping < AbstractCommand
|
|
||||||
def initialize(config : GX::Config) # FIXME
|
|
||||||
end
|
|
||||||
|
|
||||||
def execute
|
|
||||||
# FIXME: implement
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.handles_mode
|
|
||||||
GX::Types::Mode::GlobalMapping
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,25 +0,0 @@
|
||||||
require "./abstract_command"
|
|
||||||
require "../file_system_manager"
|
|
||||||
|
|
||||||
module GX::Commands
|
|
||||||
class GlobalTui < AbstractCommand
|
|
||||||
@file_system_manager : FileSystemManager
|
|
||||||
|
|
||||||
def initialize(@config : GX::Config)
|
|
||||||
@config.load_from_env
|
|
||||||
@config.load_from_file
|
|
||||||
@file_system_manager = FileSystemManager.new(@config)
|
|
||||||
end
|
|
||||||
|
|
||||||
def execute
|
|
||||||
filesystem = @file_system_manager.choose_filesystem
|
|
||||||
raise Models::InvalidFilesystemError.new("Invalid filesystem") if filesystem.nil?
|
|
||||||
@file_system_manager.mount_or_umount(filesystem)
|
|
||||||
@file_system_manager.auto_open(filesystem) if filesystem.mounted? && @config.auto_open
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.handles_mode
|
|
||||||
GX::Types::Mode::GlobalTui
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,17 +0,0 @@
|
||||||
require "./abstract_command"
|
|
||||||
require "../config"
|
|
||||||
|
|
||||||
module GX::Commands
|
|
||||||
class GlobalVersion < AbstractCommand
|
|
||||||
def initialize(config : GX::Config) # FIXME
|
|
||||||
end
|
|
||||||
|
|
||||||
def execute
|
|
||||||
STDOUT.puts "#{File.basename PROGRAM_NAME} #{VERSION}"
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.handles_mode
|
|
||||||
GX::Types::Mode::GlobalVersion
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,16 +0,0 @@
|
||||||
require "./abstract_command"
|
|
||||||
|
|
||||||
module GX::Commands
|
|
||||||
class MappingCreate < AbstractCommand
|
|
||||||
def initialize(config : GX::Config) # FIXME
|
|
||||||
end
|
|
||||||
|
|
||||||
def execute
|
|
||||||
# FIXME: implement
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.handles_mode
|
|
||||||
GX::Types::Mode::MappingCreate
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,16 +0,0 @@
|
||||||
require "./abstract_command"
|
|
||||||
|
|
||||||
module GX::Commands
|
|
||||||
class MappingDelete < AbstractCommand
|
|
||||||
def initialize(config : GX::Config) # FIXME
|
|
||||||
end
|
|
||||||
|
|
||||||
def execute
|
|
||||||
# FIXME: implement
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.handles_mode
|
|
||||||
GX::Types::Mode::MappingDelete
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,16 +0,0 @@
|
||||||
require "./abstract_command"
|
|
||||||
|
|
||||||
module GX::Commands
|
|
||||||
class MappingEdit < AbstractCommand
|
|
||||||
def initialize(config : GX::Config) # FIXME
|
|
||||||
end
|
|
||||||
|
|
||||||
def execute
|
|
||||||
# FIXME: implement
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.handles_mode
|
|
||||||
GX::Types::Mode::MappingEdit
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,45 +0,0 @@
|
||||||
require "./abstract_command"
|
|
||||||
require "../file_system_manager"
|
|
||||||
require "tablo"
|
|
||||||
|
|
||||||
module GX::Commands
|
|
||||||
class MappingList < AbstractCommand
|
|
||||||
def initialize(@config : GX::Config)
|
|
||||||
@config.load_from_env
|
|
||||||
@config.load_from_file
|
|
||||||
@file_system_manager = FileSystemManager.new(@config)
|
|
||||||
end
|
|
||||||
|
|
||||||
def execute
|
|
||||||
filesystems = @config.root.try &.filesystems
|
|
||||||
return if filesystems.nil?
|
|
||||||
# pp filesystems
|
|
||||||
|
|
||||||
fsdata = [] of Array(String)
|
|
||||||
filesystems.each do |item|
|
|
||||||
fsdata << [
|
|
||||||
item.type,
|
|
||||||
item.name,
|
|
||||||
item.mounted?.to_s,
|
|
||||||
]
|
|
||||||
end
|
|
||||||
# pp fsdata
|
|
||||||
|
|
||||||
report = Tablo::Table.new(
|
|
||||||
fsdata,
|
|
||||||
# connectors: Tablo::CONNECTORS_SINGLE_ROUNDED
|
|
||||||
column_padding: 0,
|
|
||||||
style: "" # Tablo::STYLE_NO_MID_COL
|
|
||||||
) do |table|
|
|
||||||
table.add_column("TYPE") { |row| row[0] }
|
|
||||||
table.add_column("NAME", width: 40) { |row| row[1] }
|
|
||||||
table.add_column("MOUNTED") { |row| row[2] }
|
|
||||||
end
|
|
||||||
puts report
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.handles_mode
|
|
||||||
GX::Types::Mode::MappingList
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,26 +0,0 @@
|
||||||
require "./abstract_command"
|
|
||||||
require "../file_system_manager"
|
|
||||||
|
|
||||||
module GX::Commands
|
|
||||||
class MappingMount < AbstractCommand
|
|
||||||
@file_system_manager : FileSystemManager
|
|
||||||
|
|
||||||
def initialize(@config : GX::Config) # FIXME
|
|
||||||
@config.load_from_env
|
|
||||||
@config.load_from_file
|
|
||||||
@file_system_manager = FileSystemManager.new(@config)
|
|
||||||
end
|
|
||||||
|
|
||||||
def execute
|
|
||||||
filesystem = @file_system_manager.choose_filesystem
|
|
||||||
raise Models::InvalidFilesystemError.new("Invalid filesystem") if filesystem.nil?
|
|
||||||
# @file_system_manager.mount_or_umount(filesystem)
|
|
||||||
filesystem.mount
|
|
||||||
@file_system_manager.auto_open(filesystem) if filesystem.mounted? && @config.auto_open
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.handles_mode
|
|
||||||
GX::Types::Mode::MappingMount
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,24 +0,0 @@
|
||||||
require "./abstract_command"
|
|
||||||
require "../file_system_manager"
|
|
||||||
|
|
||||||
module GX::Commands
|
|
||||||
class MappingUmount < AbstractCommand
|
|
||||||
@file_system_manager : FileSystemManager
|
|
||||||
|
|
||||||
def initialize(@config : GX::Config) # FIXME
|
|
||||||
@config.load_from_env
|
|
||||||
@config.load_from_file
|
|
||||||
@file_system_manager = FileSystemManager.new(@config)
|
|
||||||
end
|
|
||||||
|
|
||||||
def execute
|
|
||||||
filesystem = @file_system_manager.choose_filesystem
|
|
||||||
raise Models::InvalidFilesystemError.new("Invalid filesystem") if filesystem.nil?
|
|
||||||
filesystem.umount
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.handles_mode
|
|
||||||
GX::Types::Mode::MappingUmount
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
111
src/config.cr
111
src/config.cr
|
@ -3,112 +3,61 @@
|
||||||
# SPDX-FileCopyrightText: 2023 Glenn Y. Rolland <glenux@glenux.net>
|
# SPDX-FileCopyrightText: 2023 Glenn Y. Rolland <glenux@glenux.net>
|
||||||
# Copyright © 2023 Glenn Y. Rolland <glenux@glenux.net>
|
# Copyright © 2023 Glenn Y. Rolland <glenux@glenux.net>
|
||||||
|
|
||||||
require "crinja"
|
require "./filesystems"
|
||||||
|
|
||||||
require "./models"
|
|
||||||
require "./types/modes"
|
|
||||||
require "./parsers/options/help_options"
|
|
||||||
require "./parsers/options/config_options"
|
|
||||||
require "./parsers/options/config_init_options"
|
|
||||||
require "./commands/abstract_command"
|
|
||||||
|
|
||||||
module GX
|
module GX
|
||||||
class Config
|
class Config
|
||||||
Log = ::Log.for("config")
|
enum Mode
|
||||||
|
Add
|
||||||
class MissingFileError < Exception
|
Edit
|
||||||
|
Run
|
||||||
end
|
end
|
||||||
|
|
||||||
record NoArgs
|
record NoArgs
|
||||||
record AddArgs, name : String, path : String
|
record AddArgs, name : String, path : String
|
||||||
record DelArgs, name : String
|
record DelArgs, name : String
|
||||||
|
|
||||||
# getter filesystems : Array(Models::AbstractFilesystemConfig)
|
getter filesystems : Array(Filesystem)
|
||||||
getter home_dir : String
|
getter home_dir : String
|
||||||
getter root : Models::RootConfig?
|
property mode : Mode
|
||||||
|
property path : String
|
||||||
property verbose : Bool
|
|
||||||
property mode : Types::Mode
|
|
||||||
property path : String?
|
|
||||||
property args : AddArgs.class | DelArgs.class | NoArgs.class
|
property args : AddArgs.class | DelArgs.class | NoArgs.class
|
||||||
property auto_open : Bool
|
|
||||||
|
|
||||||
# FIXME: refactor and remove these parts from here
|
DEFAULT_CONFIG_PATH = "mfm.yml"
|
||||||
property help_options : Parsers::Options::HelpOptions?
|
|
||||||
property config_init_options : Parsers::Options::ConfigInitOptions?
|
|
||||||
property config_options : Parsers::Options::ConfigOptions?
|
|
||||||
|
|
||||||
def initialize
|
def initialize()
|
||||||
raise Models::InvalidEnvironmentError.new("Home directory not found") if !ENV["HOME"]?
|
if !ENV["HOME"]?
|
||||||
|
raise "Home directory not found"
|
||||||
|
end
|
||||||
@home_dir = ENV["HOME"]
|
@home_dir = ENV["HOME"]
|
||||||
|
|
||||||
@verbose = false
|
@mode = Mode::Run
|
||||||
@auto_open = false
|
@filesystems = [] of Filesystem
|
||||||
|
@path = File.join(@home_dir, ".config", DEFAULT_CONFIG_PATH)
|
||||||
@mode = Types::Mode::GlobalTui
|
|
||||||
@filesystems = [] of Models::AbstractFilesystemConfig
|
|
||||||
@path = nil
|
|
||||||
|
|
||||||
@args = NoArgs
|
@args = NoArgs
|
||||||
end
|
end
|
||||||
|
|
||||||
private def detect_config_file
|
|
||||||
possible_files = [
|
|
||||||
File.join(@home_dir, ".config", "mfm", "config.yaml"),
|
|
||||||
File.join(@home_dir, ".config", "mfm", "config.yml"),
|
|
||||||
File.join(@home_dir, ".config", "mfm.yaml"),
|
|
||||||
File.join(@home_dir, ".config", "mfm.yml"),
|
|
||||||
File.join("/etc", "mfm", "config.yaml"),
|
|
||||||
File.join("/etc", "mfm", "config.yml"),
|
|
||||||
]
|
|
||||||
|
|
||||||
possible_files.each do |file_path|
|
|
||||||
if File.exists?(file_path)
|
|
||||||
Log.info { "Configuration file found: #{file_path}" }
|
|
||||||
return file_path if File.exists?(file_path)
|
|
||||||
else
|
|
||||||
Log.debug { "Configuration file not found: #{file_path}" }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
Log.error { "No configuration file found in any of the standard locations" }
|
|
||||||
raise MissingFileError.new("Configuration file not found")
|
|
||||||
end
|
|
||||||
|
|
||||||
def load_from_env
|
|
||||||
if !ENV["FZF_DEFAULT_OPTS"]?
|
|
||||||
# force defaults settings if none defined
|
|
||||||
ENV["FZF_DEFAULT_OPTS"] = "--height 40% --layout=reverse --border"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def load_from_file
|
def load_from_file
|
||||||
config_path = @path
|
@filesystems = [] of Filesystem
|
||||||
if config_path.nil?
|
|
||||||
config_path = detect_config_file()
|
|
||||||
end
|
|
||||||
@path = config_path
|
|
||||||
|
|
||||||
if !File.exists? config_path
|
if !File.exists? @path
|
||||||
Log.error { "File #{path} does not exist!".colorize(:red) }
|
STDERR.puts "Error: file #{@path} does not exist!".colorize(:red)
|
||||||
exit(1)
|
exit(1)
|
||||||
end
|
end
|
||||||
|
load_filesystems(@path)
|
||||||
|
end
|
||||||
|
|
||||||
file_data = File.read(config_path)
|
private def load_filesystems(config_path : String)
|
||||||
file_patched = Crinja.render(file_data, {"env" => ENV.to_h})
|
yaml_data = YAML.parse(File.read(config_path))
|
||||||
|
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
|
||||||
mount_point_base_safe = root.global.mount_point_base
|
name = filesystem_data["name"].as_s
|
||||||
raise Models::InvalidMountpointError.new("Invalid global mount point") if mount_point_base_safe.nil?
|
# encrypted_path = filesystem_data["encrypted_path"].as_s
|
||||||
|
@filesystems << Filesystem.from_yaml(filesystem_data.to_yaml)
|
||||||
root.filesystems.each do |selected_filesystem|
|
# @filesystems << Filesystem.new(name, encrypted_path, "#{name}.Open")
|
||||||
if !selected_filesystem.mount_point?
|
|
||||||
selected_filesystem.mount_point =
|
|
||||||
File.join(mount_point_base_safe, selected_filesystem.mounted_name)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
@root = root
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,142 +0,0 @@
|
||||||
# require "./models/abstract_filesystem_config"
|
|
||||||
require "./utils/fzf"
|
|
||||||
|
|
||||||
module GX
|
|
||||||
class FileSystemManager
|
|
||||||
Log = ::Log.for("file_system_manager")
|
|
||||||
|
|
||||||
def initialize(@config : Config)
|
|
||||||
end
|
|
||||||
|
|
||||||
# OBSOLETE:
|
|
||||||
# def mount_filesystem(filesystem : Models::AbstractFilesystemConfig)
|
|
||||||
# raise Models::InvalidFilesystemError.new("Invalid filesystem") if filesystem.nil?
|
|
||||||
# if filesystem.mounted?
|
|
||||||
# Log.info { "Filesystem already mounted." }
|
|
||||||
# return
|
|
||||||
# end
|
|
||||||
# filesystem.mount
|
|
||||||
# end
|
|
||||||
|
|
||||||
# OBSOLETE:
|
|
||||||
# def umount_filesystem(filesystem : Models::AbstractFilesystemConfig)
|
|
||||||
# raise Models::InvalidFilesystemError.new("Invalid filesystem") if filesystem.nil?
|
|
||||||
# unless filesystem.mounted?
|
|
||||||
# Log.info { "Filesystem is not mounted." }
|
|
||||||
# return
|
|
||||||
# end
|
|
||||||
# filesystem.umount
|
|
||||||
# end
|
|
||||||
|
|
||||||
def mount_or_umount(selected_filesystem)
|
|
||||||
if !selected_filesystem.mounted?
|
|
||||||
selected_filesystem.mount
|
|
||||||
else
|
|
||||||
selected_filesystem.umount
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def auto_open(filesystem)
|
|
||||||
# FIXME: detect xdg-open and use it if possible
|
|
||||||
# FIXME: detect mailcap and use it if no xdg-open found
|
|
||||||
# FIXME: support user-defined command in configuration
|
|
||||||
# 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 each(&)
|
|
||||||
config_root = @config.root
|
|
||||||
return if config_root.nil?
|
|
||||||
|
|
||||||
config_root.filesystems.each do |filesystem|
|
|
||||||
yield filesystem
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def filesystems
|
|
||||||
config_root = @config.root
|
|
||||||
return if config_root.nil?
|
|
||||||
|
|
||||||
config_root.filesystems
|
|
||||||
end
|
|
||||||
|
|
||||||
def choose_filesystem
|
|
||||||
names_display = {} of String => NamedTuple(filesystem: Models::AbstractFilesystemConfig, ansi_name: String)
|
|
||||||
|
|
||||||
config_root = @config.root
|
|
||||||
return if config_root.nil?
|
|
||||||
|
|
||||||
config_root.filesystems.each do |filesystem|
|
|
||||||
fs_str = filesystem.type.ljust(12, ' ')
|
|
||||||
|
|
||||||
suffix = ""
|
|
||||||
suffix_ansi = ""
|
|
||||||
if filesystem.mounted?
|
|
||||||
suffix = "[open]"
|
|
||||||
suffix_ansi = "[#{"open".colorize(:green)}]"
|
|
||||||
end
|
|
||||||
|
|
||||||
result_name = "#{fs_str} #{filesystem.name} #{suffix}".strip
|
|
||||||
ansi_name = "#{fs_str.colorize(:dark_gray)} #{filesystem.name} #{suffix_ansi}".strip
|
|
||||||
|
|
||||||
names_display[result_name] = {
|
|
||||||
filesystem: filesystem,
|
|
||||||
ansi_name: ansi_name,
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
# # FIXME: feat: allow to sort by name or by filesystem
|
|
||||||
sorted_values = names_display.values.sort_by { |item| item[: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)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
return selected_filesystem
|
|
||||||
end
|
|
||||||
|
|
||||||
private def generate_display_name(filesystem : Models::AbstractFilesystemConfig) : String
|
|
||||||
fs_str = filesystem.type.ljust(12, ' ')
|
|
||||||
suffix = filesystem.mounted? ? "[open]" : ""
|
|
||||||
"#{fs_str} #{filesystem.name} #{suffix}".strip
|
|
||||||
end
|
|
||||||
|
|
||||||
private def graphical_environment?
|
|
||||||
if ENV["DISPLAY"]? || ENV["WAYLAND_DISPLAY"]?
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
9
src/filesystems.cr
Normal file
9
src/filesystems.cr
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
# 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 "./filesystems/gocryptfs"
|
||||||
|
require "./filesystems/sshfs"
|
||||||
|
require "./filesystems/httpdirfs"
|
||||||
|
require "./filesystems/filesystem"
|
42
src/filesystems/filesystem.cr
Normal file
42
src/filesystems/filesystem.cr
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
# 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}")
|
||||||
|
puts "Filesystem #{name} is now closed.".colorize(:green)
|
||||||
|
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"
|
47
src/filesystems/gocryptfs.cr
Normal file
47
src/filesystems/gocryptfs.cr
Normal 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 "./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
|
48
src/filesystems/httpdirfs.cr
Normal file
48
src/filesystems/httpdirfs.cr
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
# 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
|
||||||
|
|
49
src/filesystems/sshfs.cr
Normal file
49
src/filesystems/sshfs.cr
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
# 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 = ""
|
||||||
|
|
||||||
|
@[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",
|
||||||
|
["#{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
|
|
@ -3,8 +3,9 @@
|
||||||
# SPDX-FileCopyrightText: 2023 Glenn Y. Rolland <glenux@glenux.net>
|
# SPDX-FileCopyrightText: 2023 Glenn Y. Rolland <glenux@glenux.net>
|
||||||
# Copyright © 2023 Glenn Y. Rolland <glenux@glenux.net>
|
# Copyright © 2023 Glenn Y. Rolland <glenux@glenux.net>
|
||||||
|
|
||||||
module GX::Utils
|
module GX
|
||||||
class Fzf
|
class Fzf
|
||||||
|
|
||||||
def self.run(list : Array(String)) : String
|
def self.run(list : Array(String)) : String
|
||||||
input = IO::Memory.new
|
input = IO::Memory.new
|
||||||
input.puts list.join("\n")
|
input.puts list.join("\n")
|
||||||
|
@ -28,7 +29,8 @@ module GX::Utils
|
||||||
exit(1)
|
exit(1)
|
||||||
end
|
end
|
||||||
|
|
||||||
result = output.to_s.strip # .split.first?
|
result = output.to_s.strip #.split.first?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
26
src/main.cr
26
src/main.cr
|
@ -6,31 +6,13 @@
|
||||||
require "yaml"
|
require "yaml"
|
||||||
require "colorize"
|
require "colorize"
|
||||||
require "json"
|
require "json"
|
||||||
require "log"
|
|
||||||
|
|
||||||
|
require "./filesystems/gocryptfs"
|
||||||
require "./config"
|
require "./config"
|
||||||
require "./cli"
|
require "./cli"
|
||||||
|
|
||||||
struct BaseFormat < Log::StaticFormatter
|
app = GX::Cli.new
|
||||||
def run
|
app.parse_command_line(ARGV)
|
||||||
string @entry.severity.label.downcase
|
app.run
|
||||||
string "("
|
|
||||||
source
|
|
||||||
string "): "
|
|
||||||
message
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
Log.setup do |config|
|
|
||||||
backend = Log::IOBackend.new(formatter: BaseFormat)
|
|
||||||
config.bind "*", Log::Severity::Info, backend
|
|
||||||
|
|
||||||
if ENV["LOG_LEVEL"]?
|
|
||||||
level = Log::Severity.parse(ENV["LOG_LEVEL"]) || Log::Severity::Info
|
|
||||||
config.bind "*", level, backend
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
cli = GX::Cli.new
|
|
||||||
cli.parse_command_line(ARGV)
|
|
||||||
cli.run
|
|
||||||
|
|
|
@ -1,11 +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 "./models/root_config"
|
|
||||||
require "./models/global_config"
|
|
||||||
require "./models/gocryptfs_config"
|
|
||||||
require "./models/sshfs_config"
|
|
||||||
require "./models/httpdirfs_config"
|
|
||||||
require "./models/abstract_filesystem_config"
|
|
|
@ -1,38 +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::Models
|
|
||||||
class InvalidFilesystemError < Exception
|
|
||||||
end
|
|
||||||
|
|
||||||
class InvalidMountpointError < Exception
|
|
||||||
end
|
|
||||||
|
|
||||||
abstract class AbstractFilesystemConfig
|
|
||||||
include YAML::Serializable
|
|
||||||
# include YAML::Serializable::Strict
|
|
||||||
|
|
||||||
use_yaml_discriminator "type", {
|
|
||||||
gocryptfs: GoCryptFSConfig,
|
|
||||||
sshfs: SshFSConfig,
|
|
||||||
httpdirfs: HttpDirFSConfig,
|
|
||||||
}
|
|
||||||
|
|
||||||
getter type : String
|
|
||||||
getter name : String
|
|
||||||
property mount_point : String?
|
|
||||||
|
|
||||||
abstract def _mount_wrapper(&block)
|
|
||||||
abstract def _mount_action
|
|
||||||
abstract def _mounted_prefix
|
|
||||||
abstract def mounted_name
|
|
||||||
abstract def mounted?
|
|
||||||
abstract def mount
|
|
||||||
abstract def umount
|
|
||||||
abstract def mount_point?
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,54 +0,0 @@
|
||||||
module GX::Models::Concerns
|
|
||||||
module Base
|
|
||||||
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
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
def mount_point?
|
|
||||||
!mount_point.nil?
|
|
||||||
end
|
|
||||||
|
|
||||||
def mount
|
|
||||||
_mount_wrapper() do
|
|
||||||
_mount_action
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def _mount_wrapper(&block) : Nil
|
|
||||||
mount_point_safe = mount_point
|
|
||||||
return if mount_point_safe.nil?
|
|
||||||
|
|
||||||
Dir.mkdir_p(mount_point_safe) unless Dir.exists?(mount_point_safe)
|
|
||||||
if mounted?
|
|
||||||
puts "Already mounted. Skipping.".colorize(:yellow)
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
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
|
|
|
@ -1,30 +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"
|
|
||||||
require "./abstract_filesystem_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
|
|
|
@ -1,38 +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 "./abstract_filesystem_config"
|
|
||||||
require "./concerns/base"
|
|
||||||
|
|
||||||
module GX::Models
|
|
||||||
class GoCryptFSConfig < AbstractFilesystemConfig
|
|
||||||
getter encrypted_path : String = ""
|
|
||||||
|
|
||||||
include Concerns::Base
|
|
||||||
|
|
||||||
def _mounted_prefix
|
|
||||||
"#{encrypted_path}"
|
|
||||||
end
|
|
||||||
|
|
||||||
def mounted_name
|
|
||||||
"#{@name}.Open"
|
|
||||||
end
|
|
||||||
|
|
||||||
def _mount_action
|
|
||||||
mount_point_safe = @mount_point
|
|
||||||
raise 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
|
|
|
@ -1,38 +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 "./abstract_filesystem_config"
|
|
||||||
require "./concerns/base"
|
|
||||||
|
|
||||||
module GX::Models
|
|
||||||
class HttpDirFSConfig < AbstractFilesystemConfig
|
|
||||||
getter url : String = ""
|
|
||||||
|
|
||||||
include Concerns::Base
|
|
||||||
|
|
||||||
def _mounted_prefix
|
|
||||||
"httpdirfs"
|
|
||||||
end
|
|
||||||
|
|
||||||
def mounted_name
|
|
||||||
@name
|
|
||||||
end
|
|
||||||
|
|
||||||
def _mount_action
|
|
||||||
mount_point_safe = @mount_point
|
|
||||||
raise 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
|
|
|
@ -1,40 +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"
|
|
||||||
require "./abstract_filesystem_config"
|
|
||||||
require "./global_config"
|
|
||||||
|
|
||||||
module GX::Models
|
|
||||||
# class CrinjaConverter
|
|
||||||
# def self.from_yaml(ctx : YAML::ParseContext , node : YAML::Nodes::Node)
|
|
||||||
# l_node = node
|
|
||||||
# if l_node.is_a?(YAML::Nodes::Scalar)
|
|
||||||
# value_patched = Crinja.render(l_node.value, {"env" => ENV.to_h})
|
|
||||||
# return value_patched
|
|
||||||
# end
|
|
||||||
|
|
||||||
# return "<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(AbstractFilesystemConfig)
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,53 +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 "./abstract_filesystem_config"
|
|
||||||
require "./concerns/base"
|
|
||||||
|
|
||||||
module GX::Models
|
|
||||||
class SshFSConfig < AbstractFilesystemConfig
|
|
||||||
getter remote_path : String = ""
|
|
||||||
getter remote_user : String = ""
|
|
||||||
getter remote_host : String = ""
|
|
||||||
getter remote_port : String = "22"
|
|
||||||
getter options : Array(String) = [] of String
|
|
||||||
|
|
||||||
include Concerns::Base
|
|
||||||
|
|
||||||
def _mounted_prefix
|
|
||||||
"#{@remote_user}@#{@remote_host}:#{@remote_path}"
|
|
||||||
end
|
|
||||||
|
|
||||||
def mounted_name
|
|
||||||
@name
|
|
||||||
end
|
|
||||||
|
|
||||||
def _mount_action
|
|
||||||
mount_point_safe = @mount_point
|
|
||||||
raise 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,
|
|
||||||
input: STDIN,
|
|
||||||
output: STDOUT,
|
|
||||||
error: STDERR
|
|
||||||
)
|
|
||||||
return process.wait
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
module GX::Parsers
|
|
||||||
abstract class AbstractParser
|
|
||||||
abstract def build(parser : OptionParser, ancestors : BreadCrumbs, config : Config)
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,26 +0,0 @@
|
||||||
require "./base.cr"
|
|
||||||
|
|
||||||
module GX::Parsers
|
|
||||||
class CompletionParser < AbstractParser
|
|
||||||
def build(parser, ancestors, config)
|
|
||||||
breadcrumbs = ancestors + "completion"
|
|
||||||
|
|
||||||
parser.banner = Utils.usage_line(
|
|
||||||
breadcrumbs,
|
|
||||||
"Manage #{PROGRAM_NAME} completion",
|
|
||||||
true
|
|
||||||
)
|
|
||||||
parser.separator("\nCompletion commands:")
|
|
||||||
|
|
||||||
parser.on("--bash", "Generate bash completion") do |flag|
|
|
||||||
Log.info { "Set bash completion" }
|
|
||||||
end
|
|
||||||
|
|
||||||
parser.on("--zsh", "Generate zsh completion") do |flag|
|
|
||||||
Log.info { "Set zsh completion" }
|
|
||||||
end
|
|
||||||
|
|
||||||
parser.separator Utils.help_line(breadcrumbs)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,38 +0,0 @@
|
||||||
require "./options/config_options"
|
|
||||||
require "./options/config_init_options"
|
|
||||||
require "./base"
|
|
||||||
require "../types/modes"
|
|
||||||
require "../utils/parser_lines"
|
|
||||||
|
|
||||||
module GX::Parsers
|
|
||||||
class ConfigParser < AbstractParser
|
|
||||||
def build(parser, ancestors, config)
|
|
||||||
breadcrumbs = ancestors + "config"
|
|
||||||
config.config_options = Parsers::Options::ConfigOptions.new
|
|
||||||
parser.banner = Utils.usage_line(
|
|
||||||
breadcrumbs,
|
|
||||||
"Helpers for #{PROGRAM_NAME}'s configuration file",
|
|
||||||
true
|
|
||||||
)
|
|
||||||
parser.separator("\nConfig commands")
|
|
||||||
|
|
||||||
parser.on("init", "Create initial mfm configuration") do
|
|
||||||
config.mode = Types::Mode::ConfigInit
|
|
||||||
config.config_init_options = Parsers::Options::ConfigInitOptions.new
|
|
||||||
|
|
||||||
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|
|
|
||||||
config.config_init_options.try do |opts|
|
|
||||||
opts.path = path
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
parser.separator(Utils.help_line(breadcrumbs + "init"))
|
|
||||||
end
|
|
||||||
|
|
||||||
parser.separator(Utils.help_line(breadcrumbs))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,73 +0,0 @@
|
||||||
require "./base.cr"
|
|
||||||
require "../utils/parser_lines"
|
|
||||||
|
|
||||||
module GX::Parsers
|
|
||||||
class MappingParser < AbstractParser
|
|
||||||
def build(parser, ancestors, config)
|
|
||||||
breadcrumbs = ancestors + "mapping"
|
|
||||||
add_args = {name: "", path: ""}
|
|
||||||
delete_args = {name: ""}
|
|
||||||
|
|
||||||
parser.banner = Utils.usage_line(
|
|
||||||
breadcrumbs,
|
|
||||||
"Manage FUSE filesystem mappings",
|
|
||||||
true
|
|
||||||
)
|
|
||||||
parser.separator("\nCommands")
|
|
||||||
|
|
||||||
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
|
|
||||||
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})
|
|
||||||
end
|
|
||||||
parser.on("-p", "--path", "Set vault encrypted path") do |path|
|
|
||||||
add_args = add_args.merge({path: path})
|
|
||||||
end
|
|
||||||
parser.separator(Utils.help_line(breadcrumbs + "create"))
|
|
||||||
end
|
|
||||||
|
|
||||||
parser.on("edit", "Edit configuration") do |flag|
|
|
||||||
config.mode = Types::Mode::MappingEdit
|
|
||||||
parser.separator(Utils.help_line(breadcrumbs + "edit"))
|
|
||||||
# abort("FIXME: Not implemented")
|
|
||||||
end
|
|
||||||
|
|
||||||
parser.on("mount", "Mount mapping") do |flag|
|
|
||||||
config.mode = Types::Mode::MappingMount
|
|
||||||
parser.separator(Utils.help_line(breadcrumbs + "mount"))
|
|
||||||
# abort("FIXME: Not implemented")
|
|
||||||
end
|
|
||||||
|
|
||||||
parser.on("umount", "Umount mapping") do |flag|
|
|
||||||
config.mode = Types::Mode::MappingUmount
|
|
||||||
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.on("-n", "--name", "Set vault name") do |name|
|
|
||||||
delete_args = delete_args.merge({name: name})
|
|
||||||
end
|
|
||||||
parser.separator(Utils.help_line(breadcrumbs + "delete"))
|
|
||||||
end
|
|
||||||
|
|
||||||
parser.separator Utils.help_line(breadcrumbs)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,7 +0,0 @@
|
||||||
require "option_parser"
|
|
||||||
|
|
||||||
module GX::Parsers::Options
|
|
||||||
class ConfigInitOptions
|
|
||||||
property path : String?
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,6 +0,0 @@
|
||||||
require "option_parser"
|
|
||||||
|
|
||||||
module GX::Parsers::Options
|
|
||||||
class ConfigOptions
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,7 +0,0 @@
|
||||||
require "option_parser"
|
|
||||||
|
|
||||||
module GX::Parsers::Options
|
|
||||||
class HelpOptions
|
|
||||||
property parser_snapshot : OptionParser? = nil
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,92 +0,0 @@
|
||||||
require "./base"
|
|
||||||
require "./config_parser"
|
|
||||||
require "./mapping_parser"
|
|
||||||
require "./completion_parser"
|
|
||||||
require "../utils/parser_lines"
|
|
||||||
require "../commands"
|
|
||||||
|
|
||||||
module GX::Parsers
|
|
||||||
class RootParser < AbstractParser
|
|
||||||
def build(parser, ancestors, config)
|
|
||||||
breadcrumbs = ancestors + (File.basename PROGRAM_NAME)
|
|
||||||
|
|
||||||
parser.banner = Utils.usage_line(
|
|
||||||
breadcrumbs,
|
|
||||||
"A management tool for your various FUSE filesystems",
|
|
||||||
true
|
|
||||||
)
|
|
||||||
|
|
||||||
parser.on("-c", "--config FILE", "Set configuration file") do |path|
|
|
||||||
Log.info { "Configuration set to #{path}" }
|
|
||||||
config.path = path
|
|
||||||
end
|
|
||||||
|
|
||||||
parser.on("-v", "--verbose", "Set more verbosity") do |flag|
|
|
||||||
Log.info { "Verbosity enabled" }
|
|
||||||
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 = Types::Mode::GlobalVersion
|
|
||||||
end
|
|
||||||
|
|
||||||
parser.on("-h", "--help", "Show this help") do |flag|
|
|
||||||
config.mode = Types::Mode::GlobalHelp
|
|
||||||
config.help_options = Parsers::Options::HelpOptions.new
|
|
||||||
config.help_options.try { |opts| opts.parser_snapshot = parser.dup }
|
|
||||||
end
|
|
||||||
|
|
||||||
parser.separator("\nGlobal commands:")
|
|
||||||
|
|
||||||
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.command = Commands::Config.new(config)
|
|
||||||
Parsers::ConfigParser.new.build(parser, breadcrumbs, config)
|
|
||||||
end
|
|
||||||
|
|
||||||
parser.on("tui", "Interactive text user interface (default)") do
|
|
||||||
config.mode = Types::Mode::GlobalTui
|
|
||||||
end
|
|
||||||
|
|
||||||
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 }
|
|
||||||
|
|
||||||
Parsers::MappingParser.new.build(parser, breadcrumbs, config)
|
|
||||||
end
|
|
||||||
|
|
||||||
# parser.on("interactive", "Interactive mapping mount/umount") do
|
|
||||||
# abort("FIXME: Not implemented")
|
|
||||||
# end
|
|
||||||
|
|
||||||
parser.on("completion", "Manage completion") do
|
|
||||||
config.mode = Types::Mode::GlobalCompletion
|
|
||||||
Parsers::CompletionParser.new.build(parser, breadcrumbs, config)
|
|
||||||
end
|
|
||||||
|
|
||||||
parser.separator(Utils.help_line(breadcrumbs))
|
|
||||||
|
|
||||||
# Manage errors
|
|
||||||
parser.unknown_args do |remaining_args, _|
|
|
||||||
next if remaining_args.size == 0
|
|
||||||
|
|
||||||
puts parser
|
|
||||||
abort("ERROR: Invalid arguments: #{remaining_args.join(" ")}")
|
|
||||||
end
|
|
||||||
|
|
||||||
parser.invalid_option do |ex|
|
|
||||||
puts parser
|
|
||||||
abort("ERROR: Invalid option: '#{ex}'!")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,21 +0,0 @@
|
||||||
module GX::Types
|
|
||||||
enum Mode
|
|
||||||
None
|
|
||||||
|
|
||||||
GlobalVersion
|
|
||||||
GlobalHelp
|
|
||||||
GlobalCompletion
|
|
||||||
GlobalTui
|
|
||||||
GlobalConfig
|
|
||||||
GlobalMapping
|
|
||||||
|
|
||||||
ConfigInit
|
|
||||||
|
|
||||||
MappingCreate
|
|
||||||
MappingDelete
|
|
||||||
MappingEdit
|
|
||||||
MappingList
|
|
||||||
MappingMount
|
|
||||||
MappingUmount
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,19 +0,0 @@
|
||||||
module GX::Utils
|
|
||||||
class BreadCrumbs
|
|
||||||
def initialize(base : Array(String))
|
|
||||||
@ancestors = base
|
|
||||||
end
|
|
||||||
|
|
||||||
def +(elem : String)
|
|
||||||
b = BreadCrumbs.new(@ancestors + [elem])
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_s
|
|
||||||
@ancestors.join(" ")
|
|
||||||
end
|
|
||||||
|
|
||||||
def to_a
|
|
||||||
@ancestors.clone
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,17 +0,0 @@
|
||||||
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]",
|
|
||||||
"",
|
|
||||||
description,
|
|
||||||
"",
|
|
||||||
"Global options:",
|
|
||||||
].join("\n")
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.help_line(breadcrumbs : BreadCrumbs)
|
|
||||||
"\nRun '#{breadcrumbs.to_s} COMMAND --help' for more information on a command."
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,5 +0,0 @@
|
||||||
require "version_from_shard"
|
|
||||||
|
|
||||||
module GX
|
|
||||||
VersionFromShard.declare
|
|
||||||
end
|
|
|
@ -1,45 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# mfm Bash completion script
|
|
||||||
|
|
||||||
_mfm() {
|
|
||||||
local cur prev opts
|
|
||||||
COMPREPLY=()
|
|
||||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
|
||||||
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
|
||||||
|
|
||||||
# Options globales pour 'mfm'
|
|
||||||
local mfm_global_opts="-c --config -v --verbose -o --open --version -h --help"
|
|
||||||
|
|
||||||
# Commandes pour 'mfm'
|
|
||||||
local mfm_cmds="config mapping completion"
|
|
||||||
|
|
||||||
# Options pour 'config' et 'mapping'
|
|
||||||
local config_opts="init"
|
|
||||||
local mapping_opts="list create edit mount umount delete"
|
|
||||||
|
|
||||||
# Ajouter les options globales à chaque cas
|
|
||||||
case "${prev}" in
|
|
||||||
mfm)
|
|
||||||
COMPREPLY=($(compgen -W "${mfm_cmds} ${mfm_global_opts}" -- ${cur}))
|
|
||||||
return 0
|
|
||||||
;;
|
|
||||||
config)
|
|
||||||
COMPREPLY=($(compgen -W "${config_opts} ${mfm_global_opts}" -- ${cur}))
|
|
||||||
return 0
|
|
||||||
;;
|
|
||||||
mapping)
|
|
||||||
COMPREPLY=($(compgen -W "${mapping_opts} ${mfm_global_opts}" -- ${cur}))
|
|
||||||
return 0
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
if [[ ${cur} == -* ]]; then
|
|
||||||
COMPREPLY=($(compgen -W "${mfm_global_opts}" -- ${cur}))
|
|
||||||
fi
|
|
||||||
return 0
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
# Appliquer la complétion à la fonction 'mfm'
|
|
||||||
complete -F _mfm mfm
|
|
Loading…
Reference in a new issue