From 97212b20ad242fb6fee656e9f4494fd84092569d Mon Sep 17 00:00:00 2001 From: Glenn Date: Fri, 20 Oct 2023 11:29:54 +0200 Subject: [PATCH] Initial import --- .gitignore | 2 + Makefile | 5 ++ _attic/gx-vault.bash | 106 +++++++++++++++++++++++++++++++++++++++++++ shard.yml | 23 ++++++++++ src/main.cr | 93 +++++++++++++++++++++++++++++++++++++ 5 files changed, 229 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100755 _attic/gx-vault.bash create mode 100644 shard.yml create mode 100644 src/main.cr diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..57b480f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +bin +lib diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..aa9dbfe --- /dev/null +++ b/Makefile @@ -0,0 +1,5 @@ + +all: build + +build: + shards build diff --git a/_attic/gx-vault.bash b/_attic/gx-vault.bash new file mode 100755 index 0000000..4436398 --- /dev/null +++ b/_attic/gx-vault.bash @@ -0,0 +1,106 @@ +#!/bin/sh +# vim: set ft=sh : + +set -e +set -u + +CONFIG_FILE="$HOME/.config/gx-vault.yml" +MNTDIR="$HOME/mnt" + +gxvault_gocryptfs_is_mounted() { + src="$1" + + if LANG=C mount |grep -q "^$src on " ; then + # >&2 echo "W: $src is already mounted" + return 0 + fi + return 1 +} + +gxvault_gocryptfs_mount() { + name="$1" + src="$2" + dst="$3" + mkdir -p "$dst" + + if [ ! -e "$src" ]; then + printf '\033[31m-> Missing input directory. Skipping.\033[0m\n' + return + fi + if LANG=C mount |grep -q "$src on $dst" ; then + printf '\033[33m-> Already mounted. Skipping.\033[0m\n' + return + fi + gocryptfs -idle 15m "$src" "$dst" + printf '\033[32mVault %s is now available on %s\033[0m\n' "$name" "$(echo "$dst" |sed -e "s|^$HOME|~|")" +} + +# Ensure that yq is installed with the right version +gxvault_ensure_dependency_yq() { + if ! hash yq >/dev/null 2>&1 ; then + >&2 echo "ERROR: unable to find yq (yaml util)" + exit 1 + fi + + YQ_VERSION="$(yq --version |sed 's/.*version //' |cut -d '.' -f1 |sed -e 's/^v//' )" + if [ "$YQ_VERSION" -lt 4 ]; then + >&2 echo "ERROR: installed version of yq is too old (found $YQ_VERSION instead of 4+)" + exit 1 + fi +} + +# Ensure that all dependencies are installed +gxvault_ensure_dependency_yq + + +# Get list of sources +SRC_LIST="$(mktemp)" +SRC_COUNT="$(yq eval '.vaults[].name' "$CONFIG_FILE" |wc -l )" + +{ + for INDEX1 in $(seq 1 "$SRC_COUNT") ; do + INDEX0=$((INDEX1 - 1)) + CUR_NAME="$(yq eval ".vaults[$INDEX0].name" "$CONFIG_FILE")" + CUR_DIR="$(yq eval ".vaults[$INDEX0].encrypted_path" "$CONFIG_FILE")" + if gxvault_gocryptfs_is_mounted "$CUR_DIR" ; then + printf "%s\v [\e[32mopen\e[0m]\n" "$CUR_NAME" + else + echo "$CUR_NAME" + fi + done +} | sort > "$SRC_LIST" + +# cat "$SRC_LIST" +SRC_NAME="$(fzf --ansi < "$SRC_LIST")" +# echo "fzf: $SRC_NAME" +SRC_NAME="$(echo "$SRC_NAME" |sed -e "s/\v.*//")" +# echo "sed: $SRC_NAME" +rm -f "$SRC_LIST" + +if [ -z "$SRC_NAME" ]; then + echo "All vaults already mounted or no vaults defined" + exit 0 +fi + +SRC_DIR="$(yq eval ".vaults[] | select(.name == \"$SRC_NAME\").encrypted_path" "$CONFIG_FILE")" +DST_DIR="$MNTDIR/$SRC_NAME.Open" +if [ -z "$SRC_DIR" ]; then + >&2 echo "ERROR: Unable to detect encrypted_path for $SRC_NAME" + exit 1 +fi + +if ! gxvault_gocryptfs_is_mounted "$SRC_DIR" ; then + echo "Opening vault $SRC_NAME..." + # echo " src_name=$SRC_NAME" + # echo " src_dir=$SRC_DIR" + # echo " dst_dir=$DST_DIR" + gxvault_gocryptfs_mount \ + "$SRC_NAME" \ + "$SRC_DIR" \ + "$DST_DIR" +else + echo "Closing vault $SRC_NAME..." + fusermount -u "$DST_DIR" + printf '\033[32mVault %s is now closed\033[0m\n' "$SRC_NAME" +fi + diff --git a/shard.yml b/shard.yml new file mode 100644 index 0000000..ee8a586 --- /dev/null +++ b/shard.yml @@ -0,0 +1,23 @@ +name: gx-vault +version: 0.1.0 + +targets: + gx-vault: + main: src/main.cr + +# authors: +# - name + +# description: | +# Short description of gx-vault + +# dependencies: +# pg: +# github: will/crystal-pg +# version: "~> 0.5" + +# development_dependencies: +# webmock: +# github: manastech/webmock.cr + +# license: MIT diff --git a/src/main.cr b/src/main.cr new file mode 100644 index 0000000..c6de864 --- /dev/null +++ b/src/main.cr @@ -0,0 +1,93 @@ +require "yaml" +require "colorize" +require "json" + +# Vault class +class Vault + getter name : String + getter encrypted_path : String + getter mount_dir : String + + def initialize(@name, @encrypted_path, mount_name : String) + home_dir = ENV["HOME"] || raise "Home directory not found" + @mount_dir = File.join(home_dir, "mnt/#{mount_name}") + end + + def mounted? : Bool + `mount`.includes?("#{encrypted_path} on #{mount_dir}") + end + + def mount + Dir.mkdir_p(mount_dir) unless Dir.exists?(mount_dir) + + if mounted? + puts "Already mounted. Skipping.".colorize(:yellow) + else + 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 + puts "Vault #{name} is now available on #{mount_dir}".colorize(:green) + end + end + def unmount + `fusermount -u #{mount_dir}` + puts "Vault #{name} is now closed.".colorize(:green) + end +end + +# Config class +class Config + getter vaults : Array(Vault) + + def initialize(config_path : String) + @vaults = [] of Vault + load_vaults(config_path) + end + + private def load_vaults(config_path : String) + yaml_data = YAML.parse(File.read(config_path)) + vaults_data = yaml_data["vaults"].as_a + + vaults_data.each do |vault_data| + name = vault_data["name"].as_s + encrypted_path = vault_data["encrypted_path"].as_s + @vaults << Vault.new(name, encrypted_path, "#{name}.Open") + end + end +end + +# Main execution starts here +home_dir = ENV["HOME"] || raise "Home directory not found" +config = Config.new(File.join(home_dir, ".config/gx-vault.yml")) + +# Correcting the fzf interaction part +input = IO::Memory.new +config.vaults.each do |vault| + name_display = vault.mounted? ? "#{vault.name} [#{ "open".colorize(:green) }]" : vault.name + input.puts name_display +end +input.rewind + +output = IO::Memory.new +error = STDERR +process = Process.new("fzf", ["--ansi"], input: input, output: output, error: error) + +unless process.wait.success? + puts "Error executing fzf: #{error.to_s.strip}".colorize(:red) + exit(1) +end + +selected_vault_name = output.to_s.strip.split.first? +selected_vault = config.vaults.find { |v| v.name == selected_vault_name } + +if selected_vault + selected_vault.mounted? ? selected_vault.unmount : selected_vault.mount +else + puts "Vault not found.".colorize(:red) +end +