From 3fa3daacba88ff95fdf8f40cb30a4df66ec4c429 Mon Sep 17 00:00:00 2001 From: Sam Hardeman Date: Mon, 30 Mar 2026 01:48:18 +0200 Subject: [PATCH] Complete overhaul of how daisy core tools are called, as well as changes to the help system --- README.md | 486 +++++++++++++++++++++++++------------------ check.sh | 48 +++++ daisy | 351 +++++++++++++++++++++++++++++++ daisy.command.source | 424 +++++++++++++++++++++++++++++++++++++ daisy.source | 379 +++------------------------------ scriptbox | 3 +- shrc | 2 +- squasher | 249 +++++++++++----------- sshp | 3 +- 9 files changed, 1263 insertions(+), 682 deletions(-) create mode 100755 check.sh create mode 100755 daisy create mode 100755 daisy.command.source diff --git a/README.md b/README.md index aa0dc3d..d02bdda 100755 --- a/README.md +++ b/README.md @@ -17,211 +17,287 @@ At first use, `lackadaisical` will provide you with information via `daisy_help` ## Getting started ``` ---- BEGIN OF DAISY HELP --- -=============================================================================== -Thanks for installing LACKADAISICAL! -This project aims to provide useful utilities as well as learning material. - -It is still under heavy development, not all of the things on this -list are present/implemented. Utils marked with * are incomplete. - -This suite provides a number of functions, aliases and scripts. -They are all aimed at enhancing your efficiency. - -To uninstall LACKADAISICAL, simply remove the source line from your -shell RC, and reload it. This does not remove the files! -You will also need to manually clear the configuration data in '/.config/ -lackadaisical` if you so desire. - -To read this notice again, call the function 'daisy_help'. + =============================================================================== These are the included binaries and utilities: - - cdz: - This utility extracts an archive to /tmp and changes - directory to it in a new shell instance. Upon exit, - the files are wiped. If `archivemount` is present, - it will be used to mount the archive instead! You can - bypass this behavior by specifying an env value of; - NO_ARCHIVEMOUNT=1. The standard script supports zip, - tarballs, and rar archives. We recommend relying on - archivemount` if you have it installed. - Use "--check" to only check if a file is an archive. - It returns 0 if it is, 1 otherwise. - - squasher: - These convenient set of tools allow you to easily create - XZ-compressed SquashFS images from existing folders to save - disk space. The resulting folder is still writable since it is - mounted using an 'overlay' system. You can use 'squasher make' - to compresses an existing folder. These folders are automatically - mounted when you use 'cd' to navigate to them (via an alias). - When using 'make-squash-image' on an already mounted folder, it will - instead update the existing image. As of writing, we do not have a - SystemD service to auto-mount, however, you cam easily add auto-mount - as a cron job. Here is a list of tools, they all take the same - folder argument: - > squasher make: Converts the folder into a hidden image - on the same disk as the folder. - > squasher mount: Sets up a mount for the XZ image alongside - directories for changes - > squasher umount: Self-explanatory. - > squasher destroy: Extracts the image and essentially reverts - everything. File changes are kept, however. - - editx: - Uses your standard CLI editor to create/modify a - file and make it executable. - - filewait: - This tool is given a filename of a file that does - not exist yet. When the file appears on disk, the - tool quits and simply returns the filename. This - can be used in personal workflows to stall a longer - command that relies on the existence of said file. - - agenda: - Sets up a folder that is backed by a date-based tree - directory structure. Requires an argument for the name - of the folder to generate. Generates a symlink in this - name to a date sub-folder in a local '.daisy' folder. - Format is -> .daisy////. - Recommended to run via crontab - automatically cleans - up empty folders. - A symbolic link to the base of the folder's tree, ".tree", - is created in the root of the specified directly. - Can be used for everything you'd like to sort by date. - For example; a diary, browser downloads, backups, code. - - own: - A simple utility. It's effectively an alias for - "sudo chown -R user:user" on the target dir/file. - Root permissions required! - - sshp: - This is a wrapper for `ssh`, the meaning of the 'p' is "Plus". - Integrates SSHFS support. If both client and host have SSHFS, - this wrapper can be used to connect their file systems. - For example, if you need to move files from one machine to - another, you could do something like this: - "sshp -m /:/mnt/pc -m /home/claire:/home/claire claire@pyon.net" - If privilege escalation is necessary for FS access, you will - be asked for a password. - - shrc: - This tool allows you to edit the RC file for your - shell in your preferred editor. After saving, the - file is sourced by your shell if modified. - - sw: - A basic function that swaps two files by content. - Useful for restoring backups. - - what: - This is a tool similar to which and others, the key - difference is that it returns partial matches. It can - be used to search for binaries. - - scripbox: - This tool can be used to pack bash scripts into one - big megascript, much like how `busybox` works. - You can also make symlinks to it to invoke a specific - script. - - bak/unbak: - These small utilities make backups of files by making - a copy with a .bak suffix. Unbak reverses the process - by using sw and removes the backup. - - lsa: - A simple alias for `ls -lah`. - - lsn: - A simple alias for `ls -lah --sort=time --reverse`. - - lss: - A simple alias for `ls -lah --sort=size --reverse`. - - editbin: - An alias for `editx $\(which \)`. Saves on typing. - - editpeco: - This function uses peco+tree like `cdp`, but opens - your editor on the selected file(s). After you exit - your editor(s), you are returned to peco where you left - off. - - ched: - Like chsh but for your editor (EDITOR env). A list - from which you can choose an installed editor - (CLI or GUI) is shown. This list is by no means complete. - The editor for LACKADAISICAL and the global editor are - separate. If the EDITOR variable is already defined, - only LD_EDITOR will be changed. LD_EDITOR is the editor - used by LACKADAISICAL utilities. - To override the global EDITOR variable, pass "-g". - To restore the normal behavior of checking for an earlier - definition of EDITOR after passing "-g", run `ched` without - arguments. - - cdf: - Use fzf to find a file and then cd to its location. - - cdp: - Similar to `cdf` but uses tree+peco for the query. - - clip: - An extremely simple utility that functions as a clipboard of sorts. - To set the variable, run "clip " or provide data via stdin. - To get the variable, simply run clip without any arguments. - The variable is stored locally in the shell as "LD_CLIP". - - ldrc: - Edits daisy.source and re-sources it, similarly to shrc. - Append "-e" to edit "extra.src", to add custom functions in the - lackadaisical namespace. - - daisy_reload: - Re-sources daisy.source. Essentially `ldrc` without - editing. - - grab: - Alias for `awk '{print $x}'`, where x is a number. - E.g. `echo 'a b c' | grab 2` returns 'b'. - - daisy_cbin: - Contains the name of the current LACKADAISICAL - binary being run. - - daisy_enc: - Converts a file/stdin to a base64 block that can be - decoded by passing the output(s) to `daisy_dec`. - The output of `daisy_enc` can be concatenated with the - output of another encoded file to create a multi-file - base64 archive similar to `daisy_enc_multi`. - Does not support symlinks yet, and will instead treat it - as a full input file (reads the data of the linked file). - When using stdin, please provide a filename as argument. - - daisy_enc_multi: - A version of `daisy_enc` that encodes multiple - files and outputs `daisy_base64_data` blocks to a file - or stdout. These outputs can be concatenated as well. - - daisy_dec: - Converts `daisy_base64_data` blocks back to the form - it was in originally. - - daisy_dec_multi: - A version of daisy_dec that runs on multiple input - blocks that are either stored in a file or stdin. - - daisy_alias: - This utility sets persistent user aliases stored in - "~/.config/lackadaisical/aliases.src". - They will remain persistent until unaliased with - daisy_unalias. - Call daisy_alias using '' instead of "". - E.g. `daisy_alias hello='echo "Hello!"' - instead of - `daisy_alias hello="echo \"Hello!\"". - This prevents an early invocation of possible nested - aliases. - Call this function without arguments to get a list of - registered aliases as well as indices for easy unaliasing - using `daisy_unalias`. - - daisy_unalias: - This utility removes an alias from those registered with - daisy_alias. It accepts either an alias name, or an index - given by calling `daisy_alias` without arguments. - In case of a mistake, a backup is made under the filename - "~/.config/lackadaisical/aliases.src.bak". - To restore this file, you can use these commands: - `unbak ~/.config/lackadaisical/aliases.src' - `daisy_reload` - - daisy_list: - List all available commands without description. - - daisy_clear: - Removes all configuration, including aliases. - A backup is made and can be restored using "daisy_restore". - Triggers a reload. - - daisy_backup: - Backs up all config files. These can be restored using - "daisy_restore". - - daisy_restore: - Undoes "daisy_clear" by restoring config files. - Triggers a reload. - - ld_*: - All functions prefixed by "daisy_" are also available with - the prefix "ld_" via aliases provided in daisy.source. ---- END OF DAISY HELP --- +················································································ +< cdz > +················································································ +This utility extracts an archive to /tmp and changes directory to it in a new +shell instance. Upon exit, the files are wiped. If `archivemount` is present, +it will be used to mount the archive instead! You can bypass this behavior by +specifying an env value of; NO_ARCHIVEMOUNT=1. The standard script supports +zip, tarballs, and rar archives. We recommend relying on archivemount` if you +have it installed. Use "--check" to only check if a file is an archive. It +returns 0 if it is, 1 otherwise. +················································································ + +< squasher > +················································································ +These convenient set of tools allow you to easily create XZ-compressed SquashFS +images from existing folders to save disk space. The resulting folder is still +writable since it is mounted using an 'overlay' system. You can use 'squasher +make' to compresses an existing folder. These folders are automatically mounted +when you use 'cd' to navigate to them (via the 'multicd' alias). When using +'make-squash-image' on an already mounted folder, it will update the existing +image. As of writing, we do not have a SystemD service to auto-mount, however, +you cam easily add auto-mount as a cron job. Here is a list of tools, they all +take the same folder argument: + - squasher make: Converts the folder into an image on the same disk as the + folder. + - squasher mount: Sets up a mount for the XZ image alongside + OverlayFS for changes. + - squasher umount: Fully unmounts the image and overlay filesystem. + - squasher destroy: Extracts the image and essentially reverts everything. + All file changes are kept and applied to the unextracted + data. +················································································ + +< editx > +················································································ +Uses your standard CLI editor to create/modify a file and make it executable. +················································································ + +< filewait > +················································································ +This tool is given a filename of a file that does not exist yet. When the file +appears on disk, the tool quits and simply returns the filename. This can be +used in personal workflows to stall a longer command that relies on the +existence of said file. +················································································ + +< agenda > +················································································ +Sets up a folder that is backed by a date-based tree directory structure. +Requires an argument for the name of the folder to generate. Generates a +symlink in this name to a date sub-folder in a local '.daisy' folder. Format is + -> .daisy////. Recommended to run via crontab - +automatically cleans up empty folders. A symbolic link to the base of the +folder's tree, ".tree", is created in the root of the specified directly. Can +be used for everything you'd like to sort by date. For example; a diary, +browser downloads, backups, code. +················································································ + +< own > +················································································ +A simple utility. It's effectively an alias for "sudo chown -R user:user" on +the target dir/file. Root permissions required! +················································································ + +< sshp > +················································································ +This is a wrapper for `ssh`, the meaning of the 'p' is "Plus". Integrates SSHFS +support. If both client and host have SSHFS, this wrapper can be used to +connect their file systems. For example, if you need to move files from one +machine to another, you could do something like this: "sshp -m /:/mnt/pc -m +/home/claire:/home/claire claire@pyon.net" If privilege escalation is necessary +for FS access, you will be asked for a password. +················································································ + +< shrc > +················································································ +This tool allows you to edit the RC file for your shell in your preferred +editor. After saving, the file is sourced by your shell if modified. +················································································ + +< sw > +················································································ +A basic function that swaps two files by content. Useful for restoring backups. +················································································ + +< what > +················································································ +This is a tool similar to which and others, the key difference is that it +returns partial matches. It can be used to search for binaries. +················································································ + +< scriptbox > +················································································ +This tool can be used to pack bash scripts into one big megascript, much like +how `busybox` works. You can also make symlinks to it to invoke a specific +script. +················································································ + +< bak/unbak > +················································································ +These small utilities make backups of files by making a copy with a .bak +suffix. Unbak reverses the process by using sw and removes the backup. +················································································ + +< lsa > +················································································ +A simple alias for `ls -lah`. +················································································ + +< lsn > +················································································ +A simple alias for `ls -lah --sort=time --reverse`. +················································································ + +< lss > +················································································ +A simple alias for `ls -lah --sort=size --reverse`. +················································································ + +< editbin > +················································································ +An alias for `editx $\(which \)`. Saves on typing. +················································································ + +< editpeco > +················································································ +This function uses peco+tree like `cdp`, but opens your editor on the selected +file(s). After you exit your editor(s), you are returned to peco where you left +off. +················································································ + +< ched > +················································································ +Like chsh but for your editor (EDITOR env). A list from which you can choose an +installed editor (CLI or GUI) is shown. This list is by no means complete. The +editor for LACKADAISICAL and the global editor are separate. If the EDITOR +variable is already defined, only LD_EDITOR will be changed. LD_EDITOR is the +editor used by LACKADAISICAL utilities. To override the global EDITOR variable, +pass "-g". To restore the normal behavior of checking for an earlier definition +of EDITOR after passing "-g", run `ched` without arguments. +················································································ + +< cdf > +················································································ +Use fzf to find a file and then cd to its location. +················································································ + +< cdp > +················································································ +Similar to `cdf` but uses tree+peco for the query. +················································································ + +< clip > +················································································ +An extremely simple utility that functions as a clipboard of sorts. To set the +variable, run "clip " or provide data via stdin. To get the variable, +simply run clip without any arguments. The variable is stored locally in the +shell as "LD_CLIP". +················································································ + +< ldrc > +················································································ +Edits daisy.source and re-sources it, similarly to shrc. Append "-e" to edit +"extra.src", to add custom functions in the lackadaisical namespace. +················································································ + +< daisy reload > +················································································ +Re-sources daisy.source. Essentially `ldrc` without editing. +················································································ + +< grab > +················································································ +Alias for `awk '{print $x}'`, where x is a number. E.g. `echo 'a b c' | grab 2` +returns 'b'. +················································································ + +< daisy cbin > +················································································ +Contains the name of the current LACKADAISICAL binary being run. +················································································ + +< daisy enc > +················································································ +Converts a file/stdin to a base64 block that can be decoded by passing the +output(s) to `daisy_dec`. The output of `daisy_enc` can be concatenated with +the output of another encoded file to create a multi-file base64 archive +similar to `daisy_enc_multi`. Does not support symlinks yet, and will instead +treat it as a full input file (reads the data of the linked file). When using +stdin, please provide a filename as argument. +················································································ + +< daisy enc multi > +················································································ +A version of `daisy_enc` that encodes multiple files and outputs +`daisy_base64_data` blocks to a file or stdout. These outputs can be +concatenated as well. +················································································ + +< daisy enc folder > +················································································ +A version of `daisy_enc` that encodes a whole folder into a series of +`daisy_base64_data` blocks. Requires the `tree` utility. +················································································ + +< daisy dec > +················································································ +Converts `daisy_base64_data` blocks back to the form it was in originally. +················································································ + +< daisy dec multi > +················································································ +A version of daisy_dec that runs on multiple input blocks that are either +stored in a file or stdin. +················································································ + +< daisy alias > +················································································ +This utility sets persistent user aliases stored in +"~/.config/lackadaisical/aliases.src". They will remain persistent until +unaliased with daisy_unalias. Call daisy_alias using '' instead of "". E.g. +`daisy_alias hello='echo "Hello!"' - instead of `daisy_alias hello="echo +\"Hello!\"". This prevents an early invocation of possible nested aliases. Call +this function without arguments to get a list of registered aliases as well as +indices for easy unaliasing using `daisy_unalias`. +················································································ + +< daisy unalias > +················································································ +This utility removes an alias from those registered with daisy_alias. It +accepts either an alias name, or an index given by calling `daisy_alias` +without arguments. In case of a mistake, a backup is made under the filename +"~/.config/lackadaisical/aliases.src.bak". To restore this file, you can use +these commands: `unbak ~/.config/lackadaisical/aliases.src' `daisy_reload` +················································································ + +< daisy list > +················································································ +List all available commands, utilities and tools - without description. +················································································ + +< daisy clear > +················································································ +Removes all configuration, including aliases. A backup is made and can be +restored using "daisy_restore". Triggers a reload. +················································································ + +< daisy backup > +················································································ +Backs up all config files. These can be restored using "daisy_restore". +················································································ + +< daisy restore > +················································································ +Undoes "daisy_clear" by restoring config files. Triggers a reload. +················································································ + +< daisy_* /ld_* > +················································································ +All functions prefixed by "daisy" are also available as alias with the prefixes +"daisy_" and "ld_". +················································································ + +< daisy help > +················································································ +Shows this exact interface for information about different utilities and +functions provided by `lackadaisical`. + - help : Shows a help entry for a particular command, + including allternative function names. + - help Shows fully formatted help entries for every + function listed by "daisy list" (recommended + for first-time users). +················································································ + +< daisy welcome > +················································································ +Showws some basic post-installation information about LACKADAISICAL and how to +use it. +················································································ + ``` diff --git a/check.sh b/check.sh new file mode 100755 index 0000000..d5d62d9 --- /dev/null +++ b/check.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +# Lackadaisical Dependency Checker + +echo "Checking dependencies for LACKADAISICAL..." +echo "==========================================" + +check_bin() +{ + if [[ $1 ]]; + then echo -e "[ \e[32mOK\e[0m ] $1 ($2)" + return 0 + else + echo -e "[ \e[31m--\e[0m ] $1 ($2)" + return 1 + fi +} + +echo "--- Core (Essential) ---" +check_bin bash "Shell environment" +check_bin awk "Text processing" +check_bin sed "Stream editing" +check_bin grep "Pattern matching" +check_bin base64 "Data encoding" +check_bin realpath "Path resolution" + +echo -e "\n--- Enhanced UI & Navigation ---" +check_bin fzf "Used by cdf" +check_bin peco "Used by cdp, editpeco" +check_bin tree "Used by cdp, editpeco, daisy_enc_folder" +check_bin dialog "Used by ched" + +echo -e "\n--- Archives & Filesystems ---" +check_bin archivemount "Used by cdz (mounting)" +check_bin tar "Used by cdz (extraction)" +check_bin unzip "Used by cdz (extraction)" +check_bin unrar "Used by cdz (extraction)" +check_bin mksquashfs "Used by squasher make" +check_bin rsync "Used by squasher destroy" +check_bin sshfs "Used by sshp" +check_bin fusermount "Used by sshp, cdz" + +echo -e "\n--- System ---" +check_bin sudo "Privilege escalation" +check_bin systemctl "Service management" +check_bin perl "Advanced error reporting" + +echo "==========================================" +echo "Check complete." diff --git a/daisy b/daisy new file mode 100755 index 0000000..0c059ca --- /dev/null +++ b/daisy @@ -0,0 +1,351 @@ +#!/usr/bin/env bash + +# LACKADAISICAL CLI + +# Environment Setup +LD_CONFIG_FOLDER="${LD_CONFIG_FOLDER:-$HOME/.config/lackadaisical}" +LD_ALIASFILE="${LD_ALIASFILE:-$LD_CONFIG_FOLDER/aliases.src}" +LD_EDITORFILE="${LD_EDITORFILE:-$LD_CONFIG_FOLDER/editor.src}" +LD_ESOURCEFILE="${LD_ESOURCEFILE:-$LD_CONFIG_FOLDER/extra.src}" + +if [[ -z "$LD_FOLDER" ]]; then + LD_FOLDER=$(dirname "$(realpath "$0")") +fi + +ld_dbg() +{ + if [[ $_LD_DEBUG == 1 ]]; then + echo "DEBUG: $*" >&2 + fi +} + +# --- Internal Utils --- + +_daisy_wait_for_editor() +{ + local pname="$1" + local fname="$2" + sleep 1 + while true; do + alive=$(pgrep -f "$pname.*$fname") + if [[ $alive == "" ]]; then + break + fi + sleep 1 + done +} + +_daisy_editor() +{ + local editor=${LD_EDITOR:-$EDITOR} + ld_dbg "Opening $editor to edit file: $1" + $editor "$1" + sleep 1 + _daisy_wait_for_editor "$editor" "$1" +} + +_daisy_enc() +{ + local enc_is_folder="${ENC_IS_FOLDER:-0}" + if [ -t 0 ] && [ -z "$1" ]; then + echo "# $0: No arguments or stdin specified!" + return 1 + fi + + if [ ! -t 0 ] && [ -z "$1" ]; then + echo "# $0: Please provide a filename as argument when using stdin" + return 1 + fi + + if [ -n "$1" ] && [ -d "$1" ]; then + echo -e "daisy_create_folder=$1" + else + local file_dir="" + local file_name="" + local perms=755 + local target=$1 + + if [[ ! -t 0 ]] && [[ $enc_is_folder == 0 ]]; then + file_dir="." + file_name="$1" + elif [ -f "$1" ]; then + file_dir=$(dirname "$1") + file_name=$(basename "$1") + perms=$(stat -c %a "$1") + else + echo "# $0: An error occured during encoding." + return 1 + fi + + local base64_inner + base64_inner=$(cat "${1:-/dev/stdin}" | base64 | tr -d '\n') + echo -e "daisy_folder_$file_name=$file_dir" + echo -e "daisy_data_base64_$file_name=\"$base64_inner\"" + echo -e "daisy_perms_$file_name=$perms" + fi +} + +_daisy_dec() +{ + if [ -t 0 ] && [ -z "$1" ]; then + echo "$0: No arguments or stdin specified!" + return 1 + fi + local data + data=$(cat "${1:-/dev/stdin}" | grep -v "#") + echo -e "$data" | cut -d "=" -f 2- | cut -b 2- | head -c -2 | base64 -d +} + +_daisy_unalias() +{ + local unalias_param="$1" + if [[ $unalias_param =~ ^[0-9]+$ ]]; then + unalias_param=$(head -"$unalias_param" "$LD_ALIASFILE" 2>/dev/null | tail -1 | cut -d "=" -f 1 | awk '{print $2}') + fi + if [[ -z "$unalias_param" ]]; then + return + fi + if [[ -f "$LD_ALIASFILE" ]]; then + local newdata + newdata=$(grep -v "alias $unalias_param" "$LD_ALIASFILE" 2>/dev/null) + cp "$LD_ALIASFILE" "$LD_ALIASFILE.bak" 2>/dev/null + echo -e "$newdata" > "$LD_ALIASFILE" + fi +} + +# --- Command Functions --- + +cmd_help() +{ + local target_tool="$1" + local file="$LD_FOLDER/README.md" + if [[ ! -f "$file" ]]; then + echo "README.md not found at $file" + exit 1 + fi + sed -n '/--- BEGIN OF DAISY HELP ---/,/--- END OF DAISY HELP ---/{//!p;}' "$file" | + if [ -z "$target_tool" ]; then + cat + else + awk -v query="$target_tool" ' + BEGIN { found=0; printing=0 } + $0 ~ /^[[:space:]]*- / { + printing=0 + sig = $0 + sub(/^[[:space:]]*- /, "", sig) + sub(/:[[:space:]]*$/, "", sig) + split(sig, names, "/") + is_match = 0 + if (sig == query) is_match = 1 + else { + for (i in names); + do + if (names[i] == query) { is_match = 1; break } + done + } + if (is_match) { + printing=1 + found=1 + print $0 + next + } + } + printing { + if ($0 ~ /^[[:space:]]*- /) { printing=0; next } + print + } + END { + if (found == 0) { + print "Tool '\''" query "'\'' not found in README.md." + } + } + ' + fi +} + +cmd_list() +{ + local file="$LD_FOLDER/README.md" + if [[ ! -f "$file" ]]; then + echo "README.md not found at $file" + exit 1 + fi + echo "Available LACKADAISICAL commands:" + sed -n '/--- BEGIN OF DAISY HELP ---/,/--- END OF DAISY HELP ---/{//!p;}' "$file" | + awk ' + /^[[:space:]]*- / { + sub(/^[[:space:]]*- /, ""); + sub(/:[[:space:]]*$/, ""); + print " " $0 + } + ' | sort +} + +cmd_enc() +{ + case "$1" in + multi) + shift + for f in "$@"; do _daisy_enc "$f"; done + ;; + folder) + if ! command -v tree >/dev/null 2>&1; then + echo "This function requires the utility 'tree'. Please install it." + exit 1 + fi + shift + local dir="$1" + if [[ ! -d "$dir" ]]; then + echo "Directory not found: $dir" + exit 1 + fi + cd "$dir" || exit 1 + tree -fia --noreport . | sed 1d | while read -r item; do + ENC_IS_FOLDER=1 _daisy_enc "$item" + done + ;; + *) + _daisy_enc "$@" + ;; + esac +} + +cmd_dec() +{ + case "$1" in + multi) + shift + local arg1=$1 + local arg2=$2 + if [ ! -t 0 ]; then + arg2=$1 + arg1=/dev/stdin + fi + [[ -t 0 ]] && [[ ! -f "$arg1" ]] && echo "daisy dec multi: No input file specified" && exit 1 + [[ ! -d "$arg2" ]] && echo "daisy dec multi: No output directory specified" && exit 1 + + local folder="" + while IFS= read -r line; do + if [[ "$line" == "daisy_create_folder="* ]]; then + folder=$(echo "$line" | cut -d "=" -f 2) + mkdir -p "$arg2/$folder" + elif [[ "$line" == "daisy_folder"* ]]; then + folder=$(echo "$line" | cut -d "=" -f 2) + elif [[ "$line" == "daisy_data_base64"* ]]; then + local file=$(echo "$line" | cut -d "_" -f 4- | cut -d "=" -f 1) + mkdir -p "$arg2/$folder" + _daisy_dec <(echo "$line") > "$arg2/$folder/$file" + elif [[ "$line" == "daisy_perms"* ]]; then + local file=$(echo "$line" | cut -d "_" -f 3- | cut -d "=" -f 1) + local perms=$(echo "$line" | cut -d "_" -f 3- | cut -d "=" -f 2) + chmod "$perms" "$arg2/$folder/$file" + fi + done < "$arg1" + ;; + *) + _daisy_dec "$@" + ;; + esac +} + +cmd_alias() +{ + local alias_param="$*" + if [[ -z "$alias_param" ]]; then + echo "Active lackadaisical alias lines:" + local linenum=1 + if [[ -f "$LD_ALIASFILE" ]]; then + while IFS= read -r line; do + line=$(echo "$line" | sed 's/alias //g') + echo "$linenum: $line" + ((linenum++)) + done < "$LD_ALIASFILE" + fi + else + local alias_name=$(echo "$alias_param" | grep -o ".*=" | tr -d =) + if [[ $alias_name =~ ^[0-9]+$ ]]; then + echo "An alias cannot start with a number! Exiting." + exit 1 + fi + _daisy_unalias "$alias_name" + echo "alias ${alias_param%=*}=\"${alias_param#*=}\"" >> "$LD_ALIASFILE" + fi +} + +cmd_clear() +{ + find "$LD_CONFIG_FOLDER" -name "*.src" -type f | while read -r f; do + cp -R "$f" "$f.bak" + echo "Removing config file: $f" + rm -rf "$f" + done + echo "Config cleared. Use 'daisy restore' if you would like to undo this." +} + +cmd_restore() +{ + find "$LD_CONFIG_FOLDER" -name "*.src.bak" -type f | while read -r f; do + local target="${f%.bak}" + cp -R "$f" "$target" + rm -rf "$f" + echo "Restored backup: $target <-- $f" + done + echo "Config restored. Backups have been retained." +} + +cmd_combine() +{ + local cmds=() + local args=() + local separator_found=false + + for item in "$@"; do + if [[ "$item" == "--" ]]; then + separator_found=true + continue + fi + + if $separator_found; then + args+=("$item") + else + cmds+=("$item") + fi + done + + for cmd in "${cmds[@]}"; do + set -- "${args[@]}" + eval "$cmd" + done +} + +# --- Dispatcher --- + +case "$1" in + help) shift; cmd_help "$@" ;; + list) shift; cmd_list "$@" ;; + enc) shift; cmd_enc "$@" ;; + dec) shift; cmd_dec "$@" ;; + alias) shift; cmd_alias "$@" ;; + unalias) shift; _daisy_unalias "$@" ;; + backup) shift; find "$LD_CONFIG_FOLDER" -name "*.src" -type f | while read -r f; do cp -R "$f" "$f.bak"; echo "Backup made: $f --> $f.bak"; done ;; + clear) shift; cmd_clear ;; + restore) shift; cmd_restore ;; + combine) shift; cmd_combine ;; + editor) shift; _daisy_editor "$@" ;; + wait) + if [[ "$2" == "for" && "$3" == "editor" ]]; then + shift 3 + _daisy_wait_for_editor "$@" + else + echo "Unknown daisy command: wait $*" + exit 1 + fi + ;; + reload) + LD_INTERNAL=0 source "$LD_SOURCE_FILE" + ;; + *) + echo "Unknown daisy command: $1" + exit 1 + ;; +esac diff --git a/daisy.command.source b/daisy.command.source new file mode 100755 index 0000000..144be92 --- /dev/null +++ b/daisy.command.source @@ -0,0 +1,424 @@ +#!/usr/bin/env bash + +# LACKADAISICAL CLI + +# Environment Setup +LD_CONFIG_FOLDER="${LD_CONFIG_FOLDER:-$HOME/.config/lackadaisical}" +LD_ALIASFILE="${LD_ALIASFILE:-$LD_CONFIG_FOLDER/aliases.src}" +LD_EDITORFILE="${LD_EDITORFILE:-$LD_CONFIG_FOLDER/editor.src}" +LD_ESOURCEFILE="${LD_ESOURCEFILE:-$LD_CONFIG_FOLDER/extra.src}" + +if [[ -z "$LD_FOLDER" ]]; then + LD_FOLDER=$(dirname "$(realpath "$0")") +fi + +ld_dbg() +{ + if [[ $_LD_DEBUG == 1 ]]; then + echo "DEBUG: $*" >&2 + fi +} + +cmd_editor() +{ + local editor=${LD_EDITOR:-$EDITOR} + ld_dbg "Opening $editor to edit file: $1" + $editor "$1" +} + +_daisy_enc() +{ + local enc_is_folder="${ENC_IS_FOLDER:-0}" + if [ -t 0 ] && [ -z "$1" ]; then + echo "# $0: No arguments or stdin specified!" + return 1 + fi + + if [ ! -t 0 ] && [ -z "$1" ]; then + echo "# $0: Please provide a filename as argument when using stdin" + return 1 + fi + + if [ -n "$1" ] && [ -d "$1" ]; then + echo -e "daisy_create_folder=$1" + else + local file_dir="" + local file_name="" + local perms=755 + local target=$1 + + if [[ ! -t 0 ]] && [[ $enc_is_folder == 0 ]]; then + file_dir="." + file_name="$1" + elif [ -f "$1" ]; then + file_dir=$(dirname "$1") + file_name=$(basename "$1") + perms=$(stat -c %a "$1") + else + echo "# $0: An error occured during encoding." + return 1 + fi + + local base64_inner + base64_inner=$(cat "${1:-/dev/stdin}" | base64 | tr -d '\n') + echo -e "daisy_folder_$file_name=$file_dir" + echo -e "daisy_data_base64_$file_name=\"$base64_inner\"" + echo -e "daisy_perms_$file_name=$perms" + fi +} + +_daisy_dec() +{ + if [ -t 0 ] && [ -z "$1" ]; then + echo "$0: No arguments or stdin specified!" + return 1 + fi + local data + data=$(cat "${1:-/dev/stdin}" | grep -v "#") + echo -e "$data" | cut -d "=" -f 2- | cut -b 2- | head -c -2 | base64 -d +} + +_daisy_unalias() +{ + local unalias_param="$1" + if [[ $unalias_param =~ ^[0-9]+$ ]]; then + unalias_param=$(head -"$unalias_param" "$LD_ALIASFILE" 2>/dev/null | tail -1 | cut -d "=" -f 1 | awk '{print $2}') + fi + if [[ -z "$unalias_param" ]]; then + return + fi + if [[ -f "$LD_ALIASFILE" ]]; then + local newdata + newdata=$(grep -v "alias $unalias_param" "$LD_ALIASFILE" 2>/dev/null) + cp "$LD_ALIASFILE" "$LD_ALIASFILE.bak" 2>/dev/null + echo -e "$newdata" > "$LD_ALIASFILE" + fi + + source "$LD_ALIASFILE" + echo "Sourcing..." +} + +# --- Command Functions --- + +cmd_help() +{ + local target_tool="$1" + local file="$LD_FOLDER/README.md" + + # 1. Extract the block between the help markers + sed -n '/--- BEGIN OF DAISY HELP ---/,/--- END OF DAISY HELP ---/{//!p;}' "$file" | + if [ -z "$target_tool" ]; then + # If no argument, print the whole help text + cat + else + # 2. Parse specific tool + awk -v query="$target_tool" ' + BEGIN { found=0; printing=0 } + + # Match lines defining tools (e.g., "< cdz >" or "< daisy welcome >") + $0 ~ /^[[:space:]]*< .* >/ { + printing=0 # Stop printing previous tool + + # Clean the line to get the "signature" + # "< daisy welcome >" becomes "daisy welcome" + sig = $0 + sub(/^[[:space:]]*< /, "", sig) + sub(/ >[[:space:]]*$/, "", sig) + + # Check for exact match OR match within a slash/space-separated list + split(sig, names, "[ /]") + + is_match = 0 + if (sig == query) is_match = 1 + else { + for (i in names) + { + if (names[i] == query) + { is_match = 1; break } + } + } + + if (is_match) + { + printing=1 + found=1 + print $0 + next + } + } + + # Print description lines if we are in a "found" block + printing { + # Stop if we hit the start of the NEXT tool + if ($0 ~ /^[[:space:]]*< /) + { printing=0; next } + print + } + + END { + if (found == 0) + { + print "Tool '"'"'" query "'"'"' not found in README.md." + } + } + ' + fi +} + +cmd_list() +{ + local file="$LD_FOLDER/README.md" + if [[ ! -f "$file" ]]; then + echo "README.md not found at $file" + exit 1 + fi + echo "Available LACKADAISICAL commands:" + sed -n '/--- BEGIN OF DAISY HELP ---/,/--- END OF DAISY HELP ---/{//!p;}' "$file" | + awk ' + /^[[:space:]]*< / { + sub(/^[[:space:]]*< /, ""); + sub(/ >[[:space:]]*$/, ""); + print " " $0 + } + ' | sort +} + +cmd_enc() +{ + case "$1" in + multi) + shift + for f in "$@"; do _daisy_enc "$f"; done + ;; + folder) + if ! command -v tree >/dev/null 2>&1; then + echo "This function requires the utility 'tree'. Please install it." + exit 1 + fi + shift + local dir="$1" + if [[ ! -d "$dir" ]]; then + echo "Directory not found: $dir" + exit 1 + fi + cd "$dir" || exit 1 + tree -fia --noreport . | sed 1d | while read -r item; do + ENC_IS_FOLDER=1 _daisy_enc "$item" + done + ;; + *) + _daisy_enc "$@" + ;; + esac +} + +cmd_dec() +{ + case "$1" in + multi) + shift + local arg1=$1 + local arg2=$2 + if [ ! -t 0 ]; then + arg2=$1 + arg1=/dev/stdin + fi + [[ -t 0 ]] && [[ ! -f "$arg1" ]] && echo "daisy dec multi: No input file specified" && exit 1 + [[ ! -d "$arg2" ]] && echo "daisy dec multi: No output directory specified" && exit 1 + + local folder="" + while IFS= read -r line; do + if [[ "$line" == "daisy_create_folder="* ]]; then + folder=$(echo "$line" | cut -d "=" -f 2) + mkdir -p "$arg2/$folder" + elif [[ "$line" == "daisy_folder"* ]]; then + folder=$(echo "$line" | cut -d "=" -f 2) + elif [[ "$line" == "daisy_data_base64"* ]]; then + local file=$(echo "$line" | cut -d "_" -f 4- | cut -d "=" -f 1) + mkdir -p "$arg2/$folder" + _daisy_dec <(echo "$line") > "$arg2/$folder/$file" + elif [[ "$line" == "daisy_perms"* ]]; then + local file=$(echo "$line" | cut -d "_" -f 3- | cut -d "=" -f 1) + local perms=$(echo "$line" | cut -d "_" -f 3- | cut -d "=" -f 2) + chmod "$perms" "$arg2/$folder/$file" + fi + done < "$arg1" + ;; + *) + _daisy_dec "$@" + ;; + esac +} + +cmd_alias() +{ + local alias_param="$*" + if [[ -z "$alias_param" ]]; then + echo "Active lackadaisical alias lines:" + local linenum=1 + if [[ -f "$LD_ALIASFILE" ]]; then + while IFS= read -r line; do + line=$(echo "$line" | sed 's/alias //g') + echo "$linenum: $line" + ((linenum++)) + done < "$LD_ALIASFILE" + fi + else + local alias_name=$(echo "$alias_param" | grep -o ".*=" | tr -d =) + if [[ $alias_name =~ ^[0-9]+$ ]]; then + echo "An alias cannot start with a number! Exiting." + exit 1 + fi + _daisy_unalias "$alias_name" + echo "alias ${alias_param%=*}=\"${alias_param#*=}\"" >> "$LD_ALIASFILE" + fi + source "$LD_ALIASFILE" + echo "Sourcing..." +} + +cmd_clear() +{ + find "$LD_CONFIG_FOLDER" -name "*.src" -type f | while read -r f; do + cp -R "$f" "$f.bak" + echo "Removing config file: $f" + rm -rf "$f" + done + echo "Config cleared. Use 'daisy restore' if you would like to undo this." +} + +cmd_restore() +{ + find "$LD_CONFIG_FOLDER" -name "*.src.bak" -type f | while read -r f; do + local target="${f%.bak}" + cp -R "$f" "$target" + rm -rf "$f" + echo "Restored backup: $target <-- $f" + done + echo "Config restored. Backups have been retained." +} + +cmd_check() +{ + local failed=0 + local output="" + + check_bin() + { + str="" + if command -v "$1" >/dev/null 2>&1; then + str="[ \e[32mOK\e[0m ] $1\n" + else + str="[ \e[31m--\e[0m ] $1 ($2)\n" + failed=1 + fi + output+=$str + echo -e -n "$str" + } + + echo "--- Core (Essential) ---" + check_bin bash "Shell environment" + check_bin awk "Text processing" + check_bin sed "Stream editing" + check_bin grep "Pattern matching" + check_bin base64 "Data encoding" + check_bin realpath "Path resolution" + + echo -e "\n--- Enhanced UI & Navigation ---" + check_bin fzf "Used by cdf" + check_bin peco "Used by cdp, editpeco" + check_bin tree "Used by cdp, editpeco, daisy_enc_folder" + check_bin dialog "Used by ched" + + echo -e "\n--- Archives & Filesystems ---" + check_bin archivemount "Used by cdz (mounting)" + check_bin tar "Used by cdz (extraction)" + check_bin unzip "Used by cdz (extraction)" + check_bin unrar "Used by cdz (extraction)" + check_bin mksquashfs "Used by squasher make" + check_bin rsync "Used by squasher destroy" + check_bin sshfs "Used by sshp" + check_bin fusermount "Used by sshp, cdz" + + echo -e "\n--- System ---" + check_bin sudo "Privilege escalation" + check_bin systemctl "Service management" + check_bin perl "Advanced error reporting" + + echo "==========================================" + echo "Check complete." + echo "==========================================" + + [[ DAISY_IGNORE_DEPS -eq 1 ]] && return 0 + + [[ $failed -eq 1 ]] && ex_msg="Missing packages. DAISY_IGNORE_DEPS=1 to override" + + if [ $failed -eq 1 ] ; then + echo "Dependency report for LACKADAISICAL..." + echo "==========================================" + echo -e "$output" + echo "==========================================" + echo "Missing packages. DAISY_IGNORE_DEPS=1 to override" + return 1 + fi + return 0 +} + +cmd_combine() +{ + local cmds=() + local args=() + local separator_found=false + + for item in "$@"; do + if [[ "$item" == "--" ]]; then + separator_found=true + continue + fi + + if $separator_found; then + args+=("$item") + else + cmds+=("$item") + fi + done + + for cmd in "${cmds[@]}"; do + set -- "${args[@]}" + eval "$cmd" + done +} + +# --- Dispatcher --- +daisy () +{ + case "$1" in + help) shift; cmd_help "$@" ;; + list) shift; cmd_list "$@" ;; + enc) shift; cmd_enc "$@" ;; + dec) shift; cmd_dec "$@" ;; + alias) shift; cmd_alias "$@" ;; + unalias) shift; _daisy_unalias "$@" ;; + backup) shift; find "$LD_CONFIG_FOLDER" -name "*.src" -type f | while read -r f; do cp -R "$f" "$f.bak"; echo "Backup made: $f --> $f.bak"; done ;; + clear) shift; cmd_clear ;; + check) shift; cmd_check ;; + restore) shift; cmd_restore ;; + combine) shift; cmd_combine ;; + editor) shift; cmd_editor "$@" ;; + wait) + if [[ "$2" == "for" && "$3" == "editor" ]]; then + shift 3 + _daisy_wait_for_editor "$@" + else + echo "Unknown daisy command: wait $*" + return 1 + fi + ;; + reload) + LD_INTERNAL=0 source "$LD_SOURCE_FILE" + ;; + *) + echo "Unknown daisy command: $1" + return 1 + ;; + esac +} diff --git a/daisy.source b/daisy.source index 915ab1b..71b5d60 100755 --- a/daisy.source +++ b/daisy.source @@ -1,3 +1,4 @@ +#!/usr/bin/env bash #!/usr/bin/env -S echo "This file can only be sourced, not run stand-alone." # LACKADAISICAL SOURCE-ABLE FILE @@ -63,21 +64,20 @@ export LD_AVAILABLE=0 # Config folder setup export LD_CONFIG_FOLDER="$HOME/.config/lackadaisical" -new_install=0 +export LD_NEW_INSTALL= if [[ ! -d "$LD_CONFIG_FOLDER" ]]; then # Create the folder with its basics mkdir -p "$LD_CONFIG_FOLDER" - daisy_help new_install=1 fi # Multiple default source files # [LEA.TODO] Turn these into arrays -LD_ALIASFILE="$LD_CONFIG_FOLDER/aliases.src" -LD_EDITORFILE="$LD_CONFIG_FOLDER/editor.src" -LD_ESOURCEFILE="$LD_CONFIG_FOLDER/extra.src" +export LD_ALIASFILE="$LD_CONFIG_FOLDER/aliases.src" +export LD_EDITORFILE="$LD_CONFIG_FOLDER/editor.src" +export LD_ESOURCEFILE="$LD_CONFIG_FOLDER/extra.src" touch $LD_ALIASFILE touch $LD_EDITORFILE touch $LD_ESOURCEFILE @@ -90,8 +90,8 @@ ld_dbg cat $LD_ESOURCEFILE # Source everything in the config folder function _daisy_source_configs { -while IFS= read -r -d '' f; do - source "$f" + while IFS= read -r -d '' f; do + source "$f" done < <(find "$LD_CONFIG_FOLDER" -name "*.src" -type f -print0) } @@ -101,7 +101,6 @@ then export PATH="$PATH:$LD_FOLDER" fi - # Set up the basic alias for `shrc` # Do not set these up if LD_INTERNAL=1 is set, or infinite recursion could # occur! @@ -114,26 +113,6 @@ fi # FUNCTIONS and ALIASES ####################################################### ############################################################################### -# Undocumented but internally used -function daisy_wait_for_editor -{ - pname="$1" - fname="$2" - - # Give some time for a process to launch - sleep 1 - - while true; - do - alive=$(pgrep -f "$pname.*$fname") - if [[ $alive == "" ]] - then - break - fi - sleep 1 - done -} - function multicd { cdpath="$@" @@ -158,16 +137,6 @@ function multicd alias cd=multicd -# Undocumented but internally used -function daisy_editor -{ - editor=${LD_EDITOR:-$EDITOR}; - ld_dbg echo Opening $editor to edit file: $1 - $editor "$1" - sleep 1 - daisy_wait_for_editor $editor "$1" -} - # bak and unbak function bak { @@ -319,158 +288,16 @@ function ched source "$LD_EDITORFILE" } -function daisy_reload -{ - LD_INTERNAL=0 source "$LD_SOURCE_FILE" -} - function ldrc { ARG=$1 SOURCE="$LD_SOURCE_FILE" [[ "$ARG" == "-e" ]] && SOURCE="$LD_ESOURCEFILE" - - daisy_editor "$SOURCE" + + daisy editor "$SOURCE" LD_INTERNAL=0 source "$SOURCE" } -enc_is_folder=0 -function daisy_enc -{ - if [ -t 0 ] && [ -z "$1" ]; - then - echo "# $0: No arguments or stdin specified!" - return 1; - fi - - if [ ! -t 0 ] && [ -z "$1" ]; - then - echo "# $0: Please provide a filename as argument when using stdin" - return 1; - fi - - if [ -n "$1" ] && [ -d "$1" ]; - then - echo -e "daisy_create_folder=$1" - else - file_dir="" - file_name="" - perms=755 - - target=$1 - - # [TODO, FIX: An unknown bug is causing daisy_enc_folder to misbehave if stdin is accessed, so we disable it here.] - if [[ ! -t 0 ]] && [[ $enc_is_folder == 0 ]]; - then - file_dir="." - file_name="$1" - shift - elif [ -f "$1" ]; - then - file_dir=$(dirname "$1") - file_name=$(basename "$1") - perms=$(stat -c %a "$1") - else - echo "# $0: An error occured during encoding." - return 1 - fi - - base64_inner=$(cat ${1:-/dev/stdin} | base64 | tr -d '\n') - - # Print out our block - echo -e "daisy_folder_$file_name=$file_dir" - echo -e "daisy_data_base64_$file_name=\"$base64_inner\"" - echo -e "daisy_perms_$file_name=$perms" - fi -} - -# Will only take input files, always outputs to stdout -function daisy_enc_multi -{ - for file in "$@"; do - daisy_enc "$file" - done -} - -function daisy_enc_folder -{ - if [[ $LD_HAS_tree != 1 ]]; - then - echo "This function requires the utiliy 'tree'. Please install it." - return 1 - fi - - dir="$1" - cd "$dir" - tree -fia --noreport . | sed 1d | while read -r item; - do - enc_is_folder=1 - daisy_enc "$item" - enc_is_folder=0 - done -} - - -function daisy_dec -{ - if [ -t 0 ] && [ -z "$1" ]; - then - echo "$0: No arguments or stdin specified!" - return 1; - fi - - data=$(cat ${1:-/dev/stdin} | grep -v "#" ) - echo -e "$data" | cut -d "=" -f 2- | cut -b 2- | head -c -2 | base64 -d -} - -# Will only take a file and directory, sources it to find all encoded data -# Extracts to the directory -function daisy_dec_multi -{ - arg1=$1 - arg2=$2 - - # Handle stdin support - if [ ! -t 0 ]; - then - arg2=$1 - arg1=/dev/stdin - fi - - [[ -t 0 ]] && [[ ! -f $arg1 ]] && echo "daisy_dec_multi: No input file specified" && return - [[ ! -d $arg2 ]] && echo "daisy_dec_multi: No output directory specified" && return - - folder= - while IFS= read -r line; do - if [[ "$line" == "daisy_create_folder="* ]]; - then - folder=$(echo $line | cut -d "=" -f 2) - echo $folder - mkdir -p "$arg2/$folder" - fi - - if [[ "$line" == "daisy_folder"* ]]; - then - folder=$(echo -e $line | cut -d "=" -f 2) - continue - fi - - if [[ "$line" == "daisy_data_base64"* ]]; - then - file=$(echo -e $line | cut -d "_" -f 4- | cut -d "=" -f 1) - mkdir -p "$arg2/$folder" - daisy_dec <(echo $line) > "$arg2/$folder/$file" - fi - - if [[ "$line" == "daisy_perms"* ]]; - then - file=$(echo -e $line | cut -d "_" -f 3- | cut -d "=" -f 1) - perms=$(echo -e $line | cut -d "_" -f 3- | cut -d "=" -f 2) - chmod $perms "$arg2/$folder/$file" - fi - done <<< $(cat "$arg1") -} - # Saves a bit on typing function grab { @@ -496,95 +323,13 @@ function clip echo "Variable set to \"$LD_CLIP\"." } -function daisy_unalias -{ - unalias_param=$@ - - if [[ $unalias_param =~ '^[0-9]+$' ]]; then - selection=$(head -$unalias_param "$LD_ALIASFILE" | tail -1 | cut -d "=" -f 1 | grab 2) - daisy_unalias $selection - return - fi - - if [[ -z $unalias_param ]]; then - return - fi - - unalias $@ 2>/dev/null - - # Remove from aliases list - newdata=$(cat "$LD_ALIASFILE" | grep -v "alias $unalias_param") - [[ NO_BAK -lt 1 ]] && bak "$LD_ALIASFILE" 1>/dev/null - echo -e $newdata > "$LD_ALIASFILE" -} - -function daisy_alias -{ - alias_param="$@" - - if [[ -z $alias_param ]]; then - linenum=1 - echo "Active lackadaisical alias lines:" - while IFS= read -r line; do - line=$(echo "$line" | sed 's/alias / /g') - echo "$linenum: $line" - linenum=$(($linenum + 1)) - done < "$LD_ALIASFILE" - return - fi - - # Plain name and contents - alias_name=$(echo -e $alias_param | grep -o ".*=" | tr --delete =) - - if [[ $alias_name =~ '^[0-9]+$' ]]; then - echo "An alias cannot start with a number! Exiting." - return 1 - fi - - # Make persistent - daisy_unalias $alias_name - echo alias ${alias_param%=*}"="\"${alias_param#*=}\" >> $LD_ALIASFILE - source $LD_ALIASFILE -} - -function daisy_backup -{ - for f in `find "$LD_CONFIG_FOLDER" -name "*.src" -type f`; - do - bak "$f" - done -} - -function daisy_clear -{ - daisy_backup - - for f in `find "$LD_CONFIG_FOLDER" -name "*.src" -type f`; - do - echo "Removing config file: $f" - rm -rf "$f" - done - - echo "Config cleared. Use 'daisy_restore' if you would like to undo this." - daisy_reload -} - -function daisy_restore -{ - for f in `find "$LD_CONFIG_FOLDER" -name "*.src" -type f`; - do - unbak "$f" - bak "$f" 1>/dev/null - done - - echo "Config restored. Backups have been retained." - daisy_reload -} - -# Aliases for front-facing daisy_ functions +# Aliases for front-facing daisy commands function _daisy_def_alias { - alias ld_$1=daisy_$1 + # Map underscores in name to spaces in command if needed + local cmd=$(echo $1 | tr '_' ' ') + alias ld_$1="daisy $cmd" + alias daisy_$1="daisy $cmd" } _daisy_def_alias reload @@ -598,97 +343,31 @@ _daisy_def_alias unalias _daisy_def_alias backup _daisy_def_alias clear _daisy_def_alias restore +_daisy_def_alias combine _daisy_def_alias help _daisy_def_alias list _daisy_source_configs +############################################################################### +# check for dependencies @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ +############################################################################### +if [[ $new_install -eq 1 ]]; +then + daisy check +fi + ############################################################################### # end of FUNCTIONS and ALIASES ################################################ ############################################################################### +source "/etc/lackadaisical/daisy.command.source" + +############################################################################### +# Autocomplete for `daisy` command ############################################ +############################################################################### + # End of user section! export LD_AVAILABLE=1 [ -d "$LD_FOLDER" ] && export LD_AVAILABLE=1 - -# Help function, courtesy of Google Gemini -function daisy_help() -{ - local target_tool="$1" - local file="$LD_FOLDER/README.md" - - # 1. Extract the block between the new headers - sed -n '/--- BEGIN OF DAISY HELP ---/,/--- END OF DAISY HELP ---/{//!p;}' "$file" | - if [ -z "$target_tool" ]; then - # If no argument, print the whole help text - cat - else - # 2. Parse specific tool - awk -v query="$target_tool" ' - BEGIN { found=0; printing=0 } - - # Match lines defining tools (e.g., " - calm:" or " - bak/unbak:") - $0 ~ /^[[:space:]]*- / { - printing=0 # Stop printing previous tool - - # Clean the line to get the "signature" - # " - bak/unbak:" becomes "bak/unbak" - sig = $0 - sub(/^[[:space:]]*- /, "", sig) - sub(/:[[:space:]]*$/, "", sig) - - # Check for exact match OR match within a slash-separated list - # This handles "bak", "unbak", and "daisy_alias" - split(sig, names, "/") - - is_match = 0 - if (sig == query) is_match = 1 - else { - for (i in names) { - if (names[i] == query) { is_match = 1; break } - } - } - - if (is_match) { - printing=1 - found=1 - print $0 # Print the header line (e.g., " - bak/unbak:") - next - } - } - - # Print description lines if we are in a "found" block - printing { - # Stop if we hit the start of the NEXT tool - if ($0 ~ /^[[:space:]]*- /) { printing=0; next } - print - } - - END { - if (found == 0) { - print "Tool '"'"'" query "'"'"' not found in README.md." - } - } - ' - fi -} - -# Courtesy of Google Gemini -daisy_list() -{ - local file="$LD_FOLDER/README.md" - echo "Available LACKADAISICAL commands:" - - # Extract block -> Find tool lines -> Clean formatting -> Print - sed -n '/--- BEGIN OF DAISY HELP ---/,/--- END OF DAISY HELP ---/{//!p;}' "$file" | - awk ' - /^[[:space:]]*- / { - # Remove indentation and "- " - sub(/^[[:space:]]*- /, ""); - # Remove trailing ":" - sub(/:[[:space:]]*$/, ""); - print " " $0 - } - ' | sort -} diff --git a/scriptbox b/scriptbox index 6aeb03d..6ddaeb0 100755 --- a/scriptbox +++ b/scriptbox @@ -36,7 +36,8 @@ case_cm2=" exec help_fn" case_cm3=" ;;" case_p2="esac" -func_p1="function $OPTION_fn() {" +func_p1="function $OPTION_fn() +{" func_p2=" exit($?)" func_p3="}" diff --git a/shrc b/shrc index 485bfc7..c12777b 100755 --- a/shrc +++ b/shrc @@ -14,5 +14,5 @@ shellname=$(basename $SHELL) rc_name="."$shellname"rc" rc_path="$HOME/$rc_name" -daisy_editor "$rc_path" +daisy editor "$rc_path" source "$rc_path" diff --git a/squasher b/squasher index 213181d..ec9a84d 100755 --- a/squasher +++ b/squasher @@ -6,13 +6,14 @@ BIN_DIR=$(dirname "$(readlink -f "$0")") COMMAND="$1" DIR="$2" -usage() { - echo "Usage: $0 {make|mount|umount|destroy} " - exit 1 +usage() +{ + echo "Usage: $0 {make|mount|umount|destroy} " + exit 1 } if [[ -z "$COMMAND" || -z "$DIR" ]]; then - usage + usage fi DIR=$(readlink -f "$DIR") @@ -24,156 +25,156 @@ OVERLAY_WORK="$OVERLAY_ROOT/work" OVERLAY_TARG="$DIR" case "$COMMAND" in - make) - if [[ ! -d "$DIR" ]]; then - echo "Error: Directory \"$DIR\" does not exist!" - exit 1 - fi + make) + if [[ ! -d "$DIR" ]]; then + echo "Error: Directory \"$DIR\" does not exist!" + exit 1 + fi - echo "Checking system requirements (FUSE, SquashFS, OverlayFS)..." - for fs in fuse squashfs overlay; do - if ! grep -q "$fs" /proc/filesystems; then - echo "Attempting to load $fs module..." - sudo modprobe "$fs" || { echo "Error: $fs is not supported."; exit 1; } - fi - done + echo "Checking system requirements (FUSE, SquashFS, OverlayFS)..." + for fs in fuse squashfs overlay; do + if ! grep -q "$fs" /proc/filesystems; then + echo "Attempting to load $fs module..." + sudo modprobe "$fs" || { echo "Error: $fs is not supported."; exit 1; } + fi + done - DIRSIZE=$(du -sh "$DIR" | cut -f1) - RECREATE=false - mkdir -p "$OVERLAY_ROOT" + DIRSIZE=$(du -sh "$DIR" | cut -f1) + RECREATE=false + mkdir -p "$OVERLAY_ROOT" - if [[ -f "${OVERLAY_ROOT}.img" ]]; then - echo "Existing image found, updating..." - "$0" mount "$DIR" - DIRSIZE=$(du -sh "$DIR" | cut -f1) - RECREATE=true - fi + if [[ -f "${OVERLAY_ROOT}.img" ]]; then + echo "Existing image found, updating..." + "$0" mount "$DIR" + DIRSIZE=$(du -sh "$DIR" | cut -f1) + RECREATE=true + fi - echo "Compressing \"$DIR\"..." - sudo mksquashfs "$DIR" "${OVERLAY_ROOT}.img.1" -noappend -comp xz || exit 1 + echo "Compressing \"$DIR\"..." + sudo mksquashfs "$DIR" "${OVERLAY_ROOT}.img.1" -noappend -comp xz || exit 1 - if [[ "$RECREATE" == "true" ]]; then - echo "Cleaning up old layers..." - "$0" umount "$DIR" - if [[ -n "$OVERLAY_ROOT" && "$OVERLAY_ROOT" != "/" ]]; then - sudo rm -rf "$OVERLAY_UPPER" "$OVERLAY_LOWER" "$OVERLAY_WORK" - rm -f "${OVERLAY_ROOT}.img" - fi - fi + if [[ "$RECREATE" == "true" ]]; then + echo "Cleaning up old layers..." + "$0" umount "$DIR" + if [[ -n "$OVERLAY_ROOT" && "$OVERLAY_ROOT" != "/" ]]; then + sudo rm -rf "$OVERLAY_UPPER" "$OVERLAY_LOWER" "$OVERLAY_WORK" + rm -f "${OVERLAY_ROOT}.img" + fi + fi - mv "${OVERLAY_ROOT}.img.1" "${OVERLAY_ROOT}.img" - mkdir -p "$OVERLAY_UPPER" "$OVERLAY_LOWER" "$OVERLAY_WORK" "$OVERLAY_TARG" + mv "${OVERLAY_ROOT}.img.1" "${OVERLAY_ROOT}.img" + mkdir -p "$OVERLAY_UPPER" "$OVERLAY_LOWER" "$OVERLAY_WORK" "$OVERLAY_TARG" - sudo rm -rf "$DIR" - mkdir -p "$DIR" - touch "$DIR/.needs_mount" + sudo rm -rf "$DIR" + mkdir -p "$DIR" + touch "$DIR/.needs_mount" - echo "-------------------------------------------------------------------------" - echo "Storage Stats:" - echo " Original size: $DIRSIZE" - echo " Compressed: $(du -sh "${OVERLAY_ROOT}.img" | cut -f1)" - echo "-------------------------------------------------------------------------" + echo "-------------------------------------------------------------------------" + echo "Storage Stats:" + echo " Original size: $DIRSIZE" + echo " Compressed: $(du -sh "${OVERLAY_ROOT}.img" | cut -f1)" + echo "-------------------------------------------------------------------------" - SERVICE_CONTENT="[Unit] - Description=SquashFS Mount for %I - After=local-fs.target + SERVICE_CONTENT="[Unit] + Description=SquashFS Mount for %I + After=local-fs.target - [Service] - Type=oneshot - RemainAfterExit=yes - ExecStart=${BIN_DIR}/squasher mount %I - ExecStop=${BIN_DIR}/squasher umount %I + [Service] + Type=oneshot + RemainAfterExit=yes + ExecStart=${BIN_DIR}/squasher mount %I + ExecStop=${BIN_DIR}/squasher umount %I - [Install] - WantedBy=multi-user.target" + [Install] + WantedBy=multi-user.target" - echo "$SERVICE_CONTENT" | sudo tee /etc/systemd/system/squash-mount@.service > /dev/null - sudo systemctl daemon-reload + echo "$SERVICE_CONTENT" | sudo tee /etc/systemd/system/squash-mount@.service > /dev/null + sudo systemctl daemon-reload - ESC_PATH=$(systemd-escape -p "$DIR") - SERVICE_NAME="squash-mount@$ESC_PATH.service" + ESC_PATH=$(systemd-escape -p "$DIR") + SERVICE_NAME="squash-mount@$ESC_PATH.service" - read -p "Enable auto-mount service ($SERVICE_NAME)? [y/N] " yn + read -p "Enable auto-mount service ($SERVICE_NAME)? [y/N] " yn - if [[ "$yn" =~ ^[Yy]$ ]]; then - sudo systemctl enable --now "$SERVICE_NAME" - sudo systemctl stop "$SERVICE_NAME" - sudo systemctl start "$SERVICE_NAME" - else - echo "Manual mount command: sudo systemctl start $SERVICE_NAME" - fi - ;; + if [[ "$yn" =~ ^[Yy]$ ]]; then + sudo systemctl enable --now "$SERVICE_NAME" + sudo systemctl stop "$SERVICE_NAME" + sudo systemctl start "$SERVICE_NAME" + else + echo "Manual mount command: sudo systemctl start $SERVICE_NAME" + fi + ;; - mount) - if [[ ! -f "${OVERLAY_ROOT}.img" ]]; then - echo "Error: SquashFS image \"${OVERLAY_ROOT}.img\" not found." >&2 - exit 1 - fi + mount) + if [[ ! -f "${OVERLAY_ROOT}.img" ]]; then + echo "Error: SquashFS image \"${OVERLAY_ROOT}.img\" not found." >&2 + exit 1 + fi - "$0" umount "$DIR" 2>/dev/null - mkdir -p "$OVERLAY_LOWER" - sudo mount "${OVERLAY_ROOT}.img" "$OVERLAY_LOWER" -t squashfs -o loop + "$0" umount "$DIR" 2>/dev/null + mkdir -p "$OVERLAY_LOWER" + sudo mount "${OVERLAY_ROOT}.img" "$OVERLAY_LOWER" -t squashfs -o loop - if [[ $? -ne 0 ]]; then - echo "Error: Failed to mount squashfs image." >&2 - exit 1 - fi + if [[ $? -ne 0 ]]; then + echo "Error: Failed to mount squashfs image." >&2 + exit 1 + fi - sudo mount -t overlay none "$OVERLAY_TARG" \ - -o lowerdir="$OVERLAY_LOWER",upperdir="$OVERLAY_UPPER",workdir="$OVERLAY_WORK" + sudo mount -t overlay none "$OVERLAY_TARG" \ + -o lowerdir="$OVERLAY_LOWER",upperdir="$OVERLAY_UPPER",workdir="$OVERLAY_WORK" - if [[ $? -ne 0 ]]; then - echo "Error: Failed to mount overlay." >&2 - sudo umount "$OVERLAY_LOWER" 2>/dev/null - exit 1 - fi + if [[ $? -ne 0 ]]; then + echo "Error: Failed to mount overlay." >&2 + sudo umount "$OVERLAY_LOWER" 2>/dev/null + exit 1 + fi - echo "SquashFS filesystem is mounted and ready." - ;; + echo "SquashFS filesystem is mounted and ready." + ;; - umount) - sudo umount -l -R "$OVERLAY_TARG" 2>/dev/null - sudo umount -l -R "$OVERLAY_LOWER" 2>/dev/null + umount) + sudo umount -l -R "$OVERLAY_TARG" 2>/dev/null + sudo umount -l -R "$OVERLAY_LOWER" 2>/dev/null - if mountpoint -q "$OVERLAY_TARG" || mountpoint -q "$OVERLAY_LOWER"; then - echo "Warning: Filesystem is still mounted. Check for open processes." - exit 1 - fi + if mountpoint -q "$OVERLAY_TARG" || mountpoint -q "$OVERLAY_LOWER"; then + echo "Warning: Filesystem is still mounted. Check for open processes." + exit 1 + fi - echo "SquashFS filesystem has been unmounted." - ;; + echo "SquashFS filesystem has been unmounted." + ;; - destroy) - if [[ ! -f "$DIR/.needs_mount" ]]; then - if ! mountpoint -q "$DIR"; then - echo "Error: $DIR is not a SquashFS directory." - exit 1 - fi - fi + destroy) + if [[ ! -f "$DIR/.needs_mount" ]]; then + if ! mountpoint -q "$DIR"; then + echo "Error: $DIR is not a SquashFS directory." + exit 1 + fi + fi - ESC_PATH=$(systemd-escape -p "$DIR") - SERVICE_NAME="squash-mount@$ESC_PATH.service" + ESC_PATH=$(systemd-escape -p "$DIR") + SERVICE_NAME="squash-mount@$ESC_PATH.service" - echo "Disabling service ($SERVICE_NAME)..." - sudo systemctl stop "$SERVICE_NAME" 2>/dev/null - sudo systemctl disable "$SERVICE_NAME" 2>/dev/null + echo "Disabling service ($SERVICE_NAME)..." + sudo systemctl stop "$SERVICE_NAME" 2>/dev/null + sudo systemctl disable "$SERVICE_NAME" 2>/dev/null - echo "Ensuring image is mounted to preserve data..." - "$0" mount "$DIR" 1>/dev/null 2>/dev/null + echo "Ensuring image is mounted to preserve data..." + "$0" mount "$DIR" 1>/dev/null 2>/dev/null - echo "Destroying image and restoring data..." - TEMP_DIR=$(mktemp -d /tmp/squash-dest.XXXXXXXX) - sudo rsync -aX "$DIR/" "$TEMP_DIR/" || { echo "Error: Failed to copy data."; exit 1; } + echo "Destroying image and restoring data..." + TEMP_DIR=$(mktemp -d /tmp/squash-dest.XXXXXXXX) + sudo rsync -aX "$DIR/" "$TEMP_DIR/" || { echo "Error: Failed to copy data."; exit 1; } - "$0" umount "$DIR" - sudo rm -rf "$DIR" - sudo mv "$TEMP_DIR" "$DIR" - sudo rm -rf "$OVERLAY_ROOT" "${OVERLAY_ROOT}.img" + "$0" umount "$DIR" + sudo rm -rf "$DIR" + sudo mv "$TEMP_DIR" "$DIR" + sudo rm -rf "$OVERLAY_ROOT" "${OVERLAY_ROOT}.img" - echo "Success: SquashFS image destroyed and data restored to \"$DIR\"." - ;; + echo "Success: SquashFS image destroyed and data restored to \"$DIR\"." + ;; - *) - usage - ;; + *) + usage + ;; esac diff --git a/sshp b/sshp index 8f5c5c5..bc50bb6 100755 --- a/sshp +++ b/sshp @@ -6,7 +6,8 @@ ssh_args=() remote_port=$((10000 + RANDOM % 10000)) local_user=$(whoami) -usage() { +usage() +{ echo "Usage: sshp -m : [user@]host [ssh_options]" exit 1 }