refactor: added error checking and various features
This commit is contained in:
4
config/jade.conf
Normal file
4
config/jade.conf
Normal file
@@ -0,0 +1,4 @@
|
||||
REMOTE_HOST= # user@ip (Recommend SSH key & ssh_config.d entry)
|
||||
REMOTE_PATH= # /home/user/file
|
||||
LOCAL_PATH="/tmp/${REMOTE_PATH##*/}" # location of tmp file
|
||||
EDITOR= # codium
|
||||
11
firewall/mullvad_tailscale.conf
Normal file
11
firewall/mullvad_tailscale.conf
Normal file
@@ -0,0 +1,11 @@
|
||||
table inet mullvad_tailscale {
|
||||
chain output {
|
||||
type route hook output priority -100; policy accept;
|
||||
ip daddr 100.64.0.0/10 ct mark set 0x00000f41 meta mark set 0x6d6f6c65;
|
||||
}
|
||||
|
||||
chain input {
|
||||
type filter hook input priority -100; policy accept;
|
||||
ip saddr 100.64.0.0/10 ct mark set 0x00000f41 meta mark set 0x6d6f6c65;
|
||||
}
|
||||
}
|
||||
142
install.sh
142
install.sh
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
# ---
|
||||
# @file_name: jainstall.sh
|
||||
# @file_name: install.sh
|
||||
# @version: 1.0.0
|
||||
# @description: Install local scripts
|
||||
# @author: Jamie Albert (empty_produce)
|
||||
@@ -8,45 +8,135 @@
|
||||
# @license: GNU Affero General Public License v3.0 (Included in LICENSE)
|
||||
# Copyright (C) 2025, Jamie Albert
|
||||
# ---
|
||||
# Requires: Bash 4.0+
|
||||
set -euo pipefail
|
||||
INSTALL_DIR='/usr/local/bin'
|
||||
TOOLS=(japg.sh jascp.sh jau.sh)
|
||||
error() { printf 'Error: %s\n' "$*" >&2; exit 1; }
|
||||
info() { printf '%s\n' "$*"; }
|
||||
|
||||
# Global configuration variables
|
||||
declare -gr INSTALL_DIR="/usr/local/bin"
|
||||
declare -gr SCRIPTS_DIR="scripts"
|
||||
declare -gr CONFIG_DIR="config"
|
||||
declare -gr DICT_DIR="dict"
|
||||
declare -ga TOOLS=("japg.sh" "jade.sh" "jau.sh")
|
||||
declare -gr JADE_CONFIG_DIR="/usr/local/etc/jade.sh"
|
||||
|
||||
# ---
|
||||
# Description: Copy a single tool and make it executable.
|
||||
# Globals: INSTALL_DIR, SCRIPT_DIR
|
||||
# @usage: error <exit_code> <message>
|
||||
# @description: Print an error message to stderr and exit with a specific code.
|
||||
# @arg: $1 - The exit code to use.
|
||||
# @arg: $* - The error message to print.
|
||||
# @return_code: [N] The specified exit code.
|
||||
# ---
|
||||
error() {
|
||||
declare exit_code="$1"
|
||||
shift
|
||||
# Use printf -v for efficient string assignment (Bash 4+)
|
||||
declare error_msg
|
||||
# Include the exit code in the error message
|
||||
printf -v error_msg 'error[%d]: %s\n' "$exit_code" "$*"
|
||||
printf '%s' "$error_msg" >&2
|
||||
exit "${exit_code}"
|
||||
}
|
||||
|
||||
# ---
|
||||
# @description: Print an informational message to stdout.
|
||||
# @arg: $* - The message to print.
|
||||
# ---
|
||||
info() {
|
||||
# Use printf -v for efficient string assignment (Bash 4+)
|
||||
declare info_msg
|
||||
printf -v info_msg '%s\n' "$*"
|
||||
printf '%s' "$info_msg"
|
||||
}
|
||||
|
||||
# ---
|
||||
# @description: Perform initial setup checks. Verifies required directories exist.
|
||||
# @global: INSTALL_DIR - The target directory for installed scripts.
|
||||
# @global: SCRIPTS_DIR - Path to the local scripts directory.
|
||||
# @global: CONFIG_DIR - Path to the local config directory.
|
||||
# @global: DICT_DIR - Path to the local dict directory.
|
||||
# @return_code: [2] Scripts directory not found.
|
||||
# @return_code: [3] Config directory not found.
|
||||
# @return_code: [4] Dict directory not found.
|
||||
# ---
|
||||
setup() {
|
||||
[[ -d $INSTALL_DIR ]] || sudo mkdir -p "$INSTALL_DIR" || error 1 "Failed to create installation directory $INSTALL_DIR"
|
||||
[[ -d "${SCRIPTS_DIR}" ]] || error 2 "Scripts directory not found: ${SCRIPTS_DIR}"
|
||||
[[ -d "${CONFIG_DIR}" ]] || error 3 "Config directory not found: ${CONFIG_DIR}"
|
||||
[[ -d "${DICT_DIR}" ]] || error 4 "Dict directory not found: ${DICT_DIR}"
|
||||
}
|
||||
|
||||
# ---
|
||||
# @description: Copy a single tool and make it executable.
|
||||
# @arg: $1 - The name of the tool/script to install.
|
||||
# @global: INSTALL_DIR - The target directory for installed scripts.
|
||||
# @global: SCRIPTS_DIR - The source directory for scripts.
|
||||
# @global: CONFIG_DIR - The source directory for config files.
|
||||
# @global: DICT_DIR - The source directory for dict files.
|
||||
# @global: JADE_CONFIG_DIR - The target directory for jade.sh config.
|
||||
# @return_code: [1] General error (inherits from set -e).
|
||||
# @return_code: [5] Source file not found.
|
||||
# @return_code: [6] Copy failed for the tool.
|
||||
# @return_code: [7] chmod failed for the tool.
|
||||
# @return_code: [8] Failed to create jade config directory.
|
||||
# @return_code: [9] Failed to copy config for jade.sh.
|
||||
# @return_code: [10] Config template not found for jade.sh.
|
||||
# @return_code: [11] Copy failed for japg.list.
|
||||
# ---
|
||||
install_tool() {
|
||||
local tool=$1
|
||||
local src_path
|
||||
src_path=$(realpath "scripts/${tool}")
|
||||
declare tool="$1"
|
||||
declare src_path
|
||||
src_path=$(realpath "${SCRIPTS_DIR}/${tool}")
|
||||
|
||||
[[ -f $src_path ]] || error "Source file not found: $tool"
|
||||
[[ -f $src_path ]] || error 5 "Source file not found: $tool"
|
||||
|
||||
sudo cp "$src_path" "$INSTALL_DIR/" || error "Copy failed for $tool"
|
||||
sudo chmod 755 "$INSTALL_DIR/$tool" || error "chmod failed for $tool"
|
||||
info "Installed → $INSTALL_DIR/$tool"
|
||||
sudo cp "$src_path" "$INSTALL_DIR/" || error 6 "Copy failed for $tool"
|
||||
sudo chmod 755 "$INSTALL_DIR/$tool" || error 7 "chmod failed for $tool"
|
||||
info "Installed: $INSTALL_DIR/$tool"
|
||||
|
||||
if [[ ${tool} == "japg.sh" ]]; then
|
||||
[[ -f "dict/japg.list" ]] && sudo cp -f "dict/japg.list" /usr/share/dict/ && info "Installed → /usr/share/dict/japg.list"
|
||||
fi
|
||||
case ${tool} in
|
||||
japg.sh)
|
||||
declare dict_src="${DICT_DIR}/japg.list"
|
||||
if [[ -f "${dict_src}" ]]; then
|
||||
sudo cp -f "${dict_src}" /usr/share/dict/ || error 11 "Copy failed for japg.list"
|
||||
info " [info]: Installed /usr/share/dict/japg.list"
|
||||
else
|
||||
info " [warn]: Dict file not found at '${dict_src}', skipping installation of /usr/share/dict/japg.list"
|
||||
fi
|
||||
;;
|
||||
jade.sh)
|
||||
declare jade_config_src="${CONFIG_DIR}/jade.conf"
|
||||
declare jade_config_dest="${JADE_CONFIG_DIR}/jade.conf"
|
||||
if [[ -f "${jade_config_src}" ]]; then
|
||||
if [[ ! -f "${jade_config_dest}" ]]; then
|
||||
sudo mkdir -p "${JADE_CONFIG_DIR}" || error 8 "Failed to create jade config directory"
|
||||
sudo cp -f "${jade_config_src}" "${jade_config_dest}" || error 9 "Failed to copy config for jade.sh"
|
||||
info " Installed: ${jade_config_dest} - manually set variables within this file."
|
||||
else
|
||||
info " [info]: ${jade_config_dest} already exists, not overwriting."
|
||||
fi
|
||||
else
|
||||
error 10 " Config template not found: '${jade_config_src}'"
|
||||
fi
|
||||
;;
|
||||
*) info " [info]: No specific install steps for $tool" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# ---
|
||||
# Description: Main routine.
|
||||
# Globals: INSTALL_DIR, TOOLS
|
||||
# @description: Main routine to install all specified tools.
|
||||
# @arg: $@ - Command-line arguments (currently unused).
|
||||
# @global: TOOLS - Array of tool names to install.
|
||||
# @return_code: [N] Errors from called functions (e.g., setup, install_tool).
|
||||
# ---
|
||||
main() {
|
||||
setup
|
||||
|
||||
[[ -d $INSTALL_DIR ]] || mkdir -p "$INSTALL_DIR"
|
||||
declare tool
|
||||
for tool in "${TOOLS[@]}"; do
|
||||
install_tool "$tool"
|
||||
done
|
||||
|
||||
for tool in "${TOOLS[@]}"; do
|
||||
install_tool "$tool"
|
||||
done
|
||||
|
||||
info 'All tools installed successfully.'
|
||||
info "All tools installed successfully."
|
||||
}
|
||||
|
||||
main "$@"
|
||||
main "$@"
|
||||
165
scripts/jade.sh
Executable file
165
scripts/jade.sh
Executable file
@@ -0,0 +1,165 @@
|
||||
#!/usr/bin/env bash
|
||||
# ---
|
||||
# @file_name: jade.sh
|
||||
# @version: 1.3.1
|
||||
# @description: Lazy script for modifying docker files
|
||||
# @author: Jamie Albert (empty_produce)
|
||||
# @author_contact: <mailto:empty.produce@flatmail.me>
|
||||
# @license: GNU Affero General Public License v3.0 (Included in LICENSE)
|
||||
# Copyright (C) 2025, Jamie Albert
|
||||
# ---
|
||||
set -euo pipefail
|
||||
|
||||
# ---
|
||||
# @usage: error <exit_code> <message>
|
||||
# @description: Print an error message to stderr and exit with a specific code.
|
||||
# @arg: $1 - The exit code to use.
|
||||
# @arg: $* - The error message to print.
|
||||
# @return_code: [N] The specified exit code.
|
||||
# ---
|
||||
error() {
|
||||
declare error_msg exit_code="$1"
|
||||
shift
|
||||
printf -v error_msg 'error[%d]: %s\n' "$exit_code" "$*"
|
||||
printf '%s' "$error_msg" >&2
|
||||
exit "${exit_code}"
|
||||
}
|
||||
|
||||
# ---
|
||||
# @description: Print an informational message to stdout.
|
||||
# @arg: $* - The message to print.
|
||||
# ---
|
||||
info() {
|
||||
declare info_msg
|
||||
printf -v info_msg '%s\n' "$*"
|
||||
printf '%s' "$info_msg"
|
||||
}
|
||||
|
||||
# ---
|
||||
# @description: Load the jade configuration file.
|
||||
# @return_code: [2] Unable to source.
|
||||
# shellcheck source=/usr/local/etc/jade.sh/jade.conf
|
||||
# shellcheck disable=2015
|
||||
# ---
|
||||
setup() {
|
||||
declare -r conf="/usr/local/etc/jade.sh/jade.conf"
|
||||
[[ -f "${conf}" ]] && . "${conf}" || error 2 "unable to source '${conf}'"
|
||||
}
|
||||
|
||||
# ---
|
||||
# @description: Download a remote file to the local path.
|
||||
# @global: REMOTE_HOST - The remote host to connect to.
|
||||
# @global: REMOTE_PATH - The path to the file on the remote host.
|
||||
# @global: LOCAL_PATH - The local path to save the file to.
|
||||
# @return_code: [3] Download failed.
|
||||
# ---
|
||||
download_file() {
|
||||
scp -q "${REMOTE_HOST}:${REMOTE_PATH}" "$LOCAL_PATH" || error 3 'Download failed'
|
||||
info "Downloaded: $LOCAL_PATH"
|
||||
}
|
||||
|
||||
# ---
|
||||
# @description: Upload a local file to the remote host, backing up the remote file first.
|
||||
# @global: REMOTE_HOST - The remote host to connect to.
|
||||
# @global: REMOTE_PATH - The path to the file on the remote host.
|
||||
# @global: LOCAL_PATH - The local path of the file to upload.
|
||||
# @return_code: [4] Local file missing.
|
||||
# @return_code: [5] Failed to create remote backup.
|
||||
# @return_code: [6] Upload failed.
|
||||
# ---
|
||||
upload_file() {
|
||||
[[ -f "$LOCAL_PATH" ]] || error 4 "Local file missing: $LOCAL_PATH"
|
||||
ssh -q "${REMOTE_HOST}" "[[ -f '${REMOTE_PATH}' ]] && cp -f '${REMOTE_PATH}' '${REMOTE_PATH}.bak'" || error 5 'Failed to create remote backup'
|
||||
scp -q "$LOCAL_PATH" "${REMOTE_HOST}:${REMOTE_PATH}" || error 6 'Upload failed'
|
||||
info "Uploaded: $LOCAL_PATH"
|
||||
info "Remote file backed up to: ${REMOTE_PATH}.bak"
|
||||
}
|
||||
|
||||
# ---
|
||||
# @description: Download a file and open it in the configured editor.
|
||||
# @global: REMOTE_HOST - The remote host to connect to.
|
||||
# @global: REMOTE_PATH - The path to the file on the remote host.
|
||||
# @global: LOCAL_PATH - The local path to save the file to.
|
||||
# @global: EDITOR - The editor command to use.
|
||||
# @return_code: [7] Editor command not found.
|
||||
# @return_code: [3] Download failed (inherited from download_file).
|
||||
# ---
|
||||
edit_file() {
|
||||
download_file
|
||||
if ! command -v "$EDITOR" >/dev/null; then
|
||||
error 7 "Editor not found: '$EDITOR'"
|
||||
fi
|
||||
"$EDITOR" "$LOCAL_PATH"
|
||||
}
|
||||
|
||||
# ---
|
||||
# @description: Upload a file and run 'docker compose up -d' on the remote host.
|
||||
# @global: REMOTE_HOST - The remote host to connect to.
|
||||
# @global: REMOTE_PATH - The path to the docker-compose.yml file on the remote host.
|
||||
# @return_code: [6] Upload failed (inherited from upload_file).
|
||||
# @return_code: [8] Remote docker compose up failed.
|
||||
# ---
|
||||
upload_compose() {
|
||||
upload_file
|
||||
ssh -q "${REMOTE_HOST}" \
|
||||
"cd '$(dirname "$REMOTE_PATH")' && exec docker compose up -d" \
|
||||
|| error 8 'Remote docker compose up failed'
|
||||
info 'Remote docker compose up -d completed'
|
||||
}
|
||||
|
||||
# ---
|
||||
# @description: Run 'docker compose down', upload a file, then run 'docker compose up -d' on the remote host.
|
||||
# @global: REMOTE_HOST - The remote host to connect to.
|
||||
# @global: REMOTE_PATH - The path to the docker-compose.yml file on the remote host.
|
||||
# @return_code: [9] Remote docker compose down failed.
|
||||
# @return_code: [6] Upload failed (inherited from upload_file).
|
||||
# @return_code: [11] Remote docker compose up failed during restart.
|
||||
# ---
|
||||
upload_restart() {
|
||||
ssh -q "${REMOTE_HOST}" \
|
||||
"cd '$(dirname "$REMOTE_PATH")' && exec docker compose down" \
|
||||
|| error 9 'Remote docker compose down failed'
|
||||
upload_file
|
||||
ssh -q "${REMOTE_HOST}" \
|
||||
"cd '$(dirname "$REMOTE_PATH")' && exec docker compose up -d" \
|
||||
|| error 11 'Remote docker compose up failed during restart'
|
||||
info 'Remote docker compose restart completed'
|
||||
}
|
||||
|
||||
# ---
|
||||
# @description: Parse command-line flags and execute the chosen mode.
|
||||
# @arg: $@ - Command-line arguments.
|
||||
# @return_code: [2] Configuration file not found (inherited from setup).
|
||||
# @return_code: [10] Unknown command-line option.
|
||||
# @return_code: [12] Required tool 'scp' not found.
|
||||
# @return_code: [13] Required tool 'ssh' not found.
|
||||
# @return_code: [14] Unexpected execution mode.
|
||||
# @return_code: [N] Errors from called functions (e.g., download_file, upload_file, etc.).
|
||||
# ---
|
||||
main() {
|
||||
setup
|
||||
declare mode=''
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-d) mode='download' ; shift ;;
|
||||
-u) mode='upload' ; shift ;;
|
||||
-uc) mode='up' ; shift ;;
|
||||
-ur) mode='restart' ; shift ;;
|
||||
*) error 10 "Unknown option: $1 (use -d, -u, -uc, -ur)" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
command -v scp >/dev/null || error 12 "'scp' not found"
|
||||
command -v ssh >/dev/null || error 13 "'ssh' not found"
|
||||
|
||||
case "$mode" in
|
||||
download) download_file ;;
|
||||
upload) upload_file ;;
|
||||
up) upload_compose ;;
|
||||
restart) upload_restart ;;
|
||||
'') edit_file ;;
|
||||
*) error 14 "Unexpected mode: $mode" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
main "$@"
|
||||
@@ -9,46 +9,83 @@
|
||||
# Copyright (C) 2025, Jamie Albert
|
||||
# ---
|
||||
set -euo pipefail
|
||||
WORD_LIST='/usr/share/dict/japg.list'
|
||||
DEFAULT_WORDS=5
|
||||
DEFAULT_DELIM='-'
|
||||
error() { printf 'Error: %s\n' "$*" >&2; exit 1; }
|
||||
info() { printf '%s\n' "$*"; }
|
||||
|
||||
declare -gr WORD_LIST='/usr/share/dict/japg.list'
|
||||
declare -gr DEFAULT_DELIM='-'
|
||||
declare -gi DEFAULT_WORDS=5
|
||||
|
||||
# ---
|
||||
# Description: Main routine.
|
||||
# Globals: WORD_LIST, DEFAULT_WORDS, DEFAULT_DELIM
|
||||
# @usage: error <exit_code> <message>
|
||||
# @description: Print an error message to stderr and exit with a specific code.
|
||||
# @arg: $1 - The exit code to use.
|
||||
# @arg: $* - The error message to print.
|
||||
# @return_code: [N] The specified exit code.
|
||||
# ---
|
||||
error() {
|
||||
declare exit_code="$1"
|
||||
shift
|
||||
printf 'error: %s\n' "$*" >&2
|
||||
exit "${exit_code}"
|
||||
}
|
||||
|
||||
# ---
|
||||
# @description: Print an informational message to stdout.
|
||||
# @arg: $* - The message to print.
|
||||
# ---
|
||||
info() {
|
||||
printf '%s\n' "$*"
|
||||
}
|
||||
|
||||
# ---
|
||||
# @description: Perform initial setup checks.
|
||||
# Verifies the word list file exists and xclip is installed.
|
||||
# @global: WORD_LIST - Path to the word list file.
|
||||
# @return_code: [2] Word-list file not found.
|
||||
# @return_code: [3] Required tool 'xclip' not found.
|
||||
# ---
|
||||
setup() {
|
||||
[[ -f "${WORD_LIST}" ]] || error 2 "Word-list not found: $WORD_LIST"
|
||||
command -v xclip >/dev/null || error 3 "xclip not found (install xclip)"
|
||||
}
|
||||
|
||||
# ---
|
||||
# @description: Main routine to generate and copy the passphrase.
|
||||
# @arg: $1 - Number of words (optional, defaults to DEFAULT_WORDS).
|
||||
# @arg: $2 - Delimiter (optional, defaults to DEFAULT_DELIM).
|
||||
# @global: WORD_LIST - Path to the word list file.
|
||||
# @global: DEFAULT_WORDS - Default number of words.
|
||||
# @global: DEFAULT_DELIM - Default delimiter.
|
||||
# @return_code: [1] General error (inherits from set -e).
|
||||
# @return_code: [4] Invalid number of words provided.
|
||||
# @return_code: [2] Word-list not found (inherited from setup).
|
||||
# @return_code: [3] xclip not found (inherited from setup).
|
||||
# ---
|
||||
main() {
|
||||
local num_words=${1:-$DEFAULT_WORDS}
|
||||
local delim=${2:-$DEFAULT_DELIM}
|
||||
setup
|
||||
|
||||
[[ "$num_words" =~ ^[1-9][0-9]*$ ]] || error "num_words must be a positive integer"
|
||||
[[ -f "$WORD_LIST" ]] || error "Word-list not found: $WORD_LIST"
|
||||
command -v xclip >/dev/null || error "xclip not found (install xclip)"
|
||||
declare num_words="${1:-$DEFAULT_WORDS}"
|
||||
declare delim="${2:-$DEFAULT_DELIM}"
|
||||
|
||||
# 1. Pick words
|
||||
local -a words
|
||||
mapfile -t words < <(shuf -n "$num_words" "$WORD_LIST")
|
||||
[[ "${num_words}" =~ ^[1-9][0-9]*$ ]] || error 4 "num_words must be a positive integer"
|
||||
|
||||
# 2. Capitalise every word
|
||||
local i
|
||||
for i in "${!words[@]}"; do
|
||||
words[i]=$(printf '%s' "${words[i]}" | sed 's/^\(.\)/\U\1/')
|
||||
done
|
||||
declare -a words
|
||||
mapfile -t words < <(shuf -n "$num_words" "$WORD_LIST") || error 1 "Failed to read words from list"
|
||||
|
||||
# 3. Append digit to one random word
|
||||
local dig_idx=$(( RANDOM % num_words ))
|
||||
words[dig_idx]+=$(( RANDOM % 10 ))
|
||||
declare i
|
||||
for i in "${!words[@]}"; do
|
||||
words[i]="${words[i]^}"
|
||||
done
|
||||
|
||||
# 4. Join with delimiter
|
||||
local pass
|
||||
pass=$(IFS="$delim"; printf '%s' "${words[*]}")
|
||||
declare dig_idx=$(( RANDOM % num_words ))
|
||||
words[dig_idx]+=$(( RANDOM % 10 ))
|
||||
|
||||
# 5. Copy and report
|
||||
printf '%s' "$pass" | xclip -selection clipboard
|
||||
info "Generated password: $pass"
|
||||
info "Password copied to clipboard."
|
||||
declare pass
|
||||
IFS="$delim"
|
||||
printf -v pass '%s' "${words[*]}" || error 1 "Failed to construct passphrase"
|
||||
|
||||
printf '%s' "$pass" | xclip -selection clipboard || error 1 "Failed to copy passphrase to clipboard"
|
||||
info "Generated password: $pass"
|
||||
info "Password copied to clipboard."
|
||||
}
|
||||
|
||||
main "$@"
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# ---
|
||||
# @file_name: jascp.sh
|
||||
# @version: 1.2.0
|
||||
# @project_name: jascp (Just Another SCP)
|
||||
# @description: Download-only, upload-only, or download-edit-upload helper.
|
||||
# @author: Jamie Albert (empty_produce)
|
||||
# @author_contact: <mailto:empty.produce@flatmail.me>
|
||||
# @license: GNU Affero General Public License v3.0 (Included in LICENSE)
|
||||
# Copyright (C) 2025, Jamie Albert
|
||||
# ---
|
||||
set -euo pipefail
|
||||
REMOTE_HOST='root@hephaestus'
|
||||
REMOTE_PATH='/home/oc/docker_config/compose.yml'
|
||||
LOCAL_PATH='/tmp/compose.yml'
|
||||
EDITOR="codium"
|
||||
error() { printf 'Error: %s\n' "$*" >&2; exit 1; }
|
||||
info() { printf '%s\n' "$*"; }
|
||||
|
||||
# ---
|
||||
# Description: Download remote file to local path.
|
||||
# Globals: REMOTE_HOST, REMOTE_PATH, LOCAL_PATH
|
||||
# ---
|
||||
download_file() {
|
||||
scp -q "${REMOTE_HOST}:${REMOTE_PATH}" "$LOCAL_PATH" || error 'Download failed'
|
||||
info "Downloaded → $LOCAL_PATH"
|
||||
}
|
||||
|
||||
# ---
|
||||
# Description: Upload local file to remote host.
|
||||
# Globals: REMOTE_HOST, REMOTE_PATH, LOCAL_PATH
|
||||
# ---
|
||||
upload_file() {
|
||||
[[ -f $LOCAL_PATH ]] || error "Local file missing: $LOCAL_PATH"
|
||||
scp -q "$LOCAL_PATH" "${REMOTE_HOST}:${REMOTE_PATH}" || error 'Upload failed'
|
||||
info "Uploaded ← $LOCAL_PATH"
|
||||
}
|
||||
|
||||
# ---
|
||||
# Description: Download, edit, then upload.
|
||||
# Globals: REMOTE_HOST, REMOTE_PATH, LOCAL_PATH, EDITOR
|
||||
# ---
|
||||
edit_cycle() {
|
||||
download_file
|
||||
command -v "$EDITOR" >/dev/null || error "Editor '$EDITOR' not found"
|
||||
"$EDITOR" "$LOCAL_PATH"
|
||||
}
|
||||
|
||||
# ---
|
||||
# Description: Parse flags and run chosen mode.
|
||||
# Return Code: 0 on success
|
||||
# ---
|
||||
main() {
|
||||
local mode=''
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-d) mode='download'; shift ;;
|
||||
-u) mode='upload'; shift ;;
|
||||
*) error "Unknown option: $1 (use -d or -u)" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
command -v scp >/dev/null || error 'scp not found'
|
||||
|
||||
case "$mode" in
|
||||
download) download_file ;;
|
||||
upload) upload_file ;;
|
||||
'') edit_cycle ;;
|
||||
*) error "Unexpected mode: $mode" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
main "$@"
|
||||
174
scripts/jau.sh
174
scripts/jau.sh
@@ -5,108 +5,158 @@
|
||||
# @description: Full system update handler for Fedora-based systems with DNF and Flatpak
|
||||
# @author: Jamie Albert (empty_produce)
|
||||
# @author_contact: <mailto:empty.produce@flatmail.me>
|
||||
#
|
||||
# @license: GNU Affero General Public License v3.0 (Included in LICENSE)
|
||||
# Copyright (C) 2025, Jamie Albert
|
||||
# ---
|
||||
set -euo pipefail
|
||||
error() { printf 'Error: %s\n' "$*" >&2; exit 1; }
|
||||
info() { printf '%s\n' "$*"; }
|
||||
|
||||
# ---
|
||||
# Description: Install package if missing.
|
||||
# @usage: error <exit_code> <message>
|
||||
# @description: Print an error message to stderr and exit with a specific code.
|
||||
# @arg: $1 - The exit code to use.
|
||||
# @arg: $* - The error message to print.
|
||||
# @return_code: [N] The specified exit code.
|
||||
# ---
|
||||
error() {
|
||||
declare exit_code="$1"
|
||||
shift
|
||||
declare error_msg
|
||||
printf -v error_msg 'error[%d]: %s\n' "$exit_code" "$*"
|
||||
printf '%s' "$error_msg" >&2
|
||||
exit "${exit_code}"
|
||||
}
|
||||
|
||||
# ---
|
||||
# @description: Print an informational message to stdout.
|
||||
# @arg: $* - The message to print.
|
||||
# ---
|
||||
info() {
|
||||
declare info_msg
|
||||
printf -v info_msg '%s\n' "$*"
|
||||
printf '%s' "$info_msg"
|
||||
}
|
||||
|
||||
# ---
|
||||
# @description: Perform initial setup checks. Verifies required base commands (dnf, sudo) are available.
|
||||
# @return_code: [2] Required tool 'sudo' not found.
|
||||
# @return_code: [3] Required tool 'dnf' not found.
|
||||
# ---
|
||||
setup() {
|
||||
command -v sudo >/dev/null || error 2 "sudo not found"
|
||||
sudo command -v dnf >/dev/null || error 3 "dnf not found (script requires a DNF-based system)"
|
||||
}
|
||||
|
||||
# ---
|
||||
# @description: Install package if missing.
|
||||
# @arg: $1 - The name of the package/command to check and install.
|
||||
# @return_code: [1] General error (inherits from set -e).
|
||||
# @return_code: [4] Failed to install the specified package.
|
||||
# ---
|
||||
install_if_missing() {
|
||||
local pkg=$1
|
||||
if ! command -v "$pkg" &>/dev/null; then
|
||||
info "Installing missing dependency: $pkg"
|
||||
sudo dnf install -y "$pkg" || error "Failed to install $pkg"
|
||||
else
|
||||
info "Dependency satisfied: $pkg"
|
||||
fi
|
||||
declare pkg="$1"
|
||||
if ! command -v "$pkg" &>/dev/null; then
|
||||
info "Installing missing dependency: $pkg"
|
||||
sudo dnf install -y "$pkg" || error 4 "Failed to install $pkg"
|
||||
else
|
||||
info "Dependency satisfied: $pkg"
|
||||
fi
|
||||
}
|
||||
|
||||
# ---
|
||||
# Description: Refresh DNF cache and update all packages.
|
||||
# @description: Refresh DNF cache and update all packages.
|
||||
# @return_code: [1] General error (inherits from set -e).
|
||||
# @return_code: [5] Failed to refresh DNF cache.
|
||||
# @return_code: [6] DNF update failed.
|
||||
# ---
|
||||
run_dnf_update() {
|
||||
info "Refreshing DNF cache..."
|
||||
sudo dnf -y makecache --refresh || error "Failed to refresh DNF cache"
|
||||
info "Refreshing DNF cache..."
|
||||
sudo dnf -y makecache --refresh || error 5 "Failed to refresh DNF cache"
|
||||
|
||||
info "Updating all packages..."
|
||||
sudo dnf -y update || error "DNF update failed"
|
||||
info "Updating all packages..."
|
||||
sudo dnf -y update || error 6 "DNF update failed"
|
||||
}
|
||||
|
||||
# ---
|
||||
# Description: Handle leftover RPM configuration files.
|
||||
# @description: Handle leftover RPM configuration files.
|
||||
# @return_code: [1] General error (inherits from set -e).
|
||||
# @return_code: [7] rpmconf execution failed.
|
||||
# ---
|
||||
handle_rpmconf() {
|
||||
if command -v rpmconf &>/dev/null; then
|
||||
info "Handling leftover RPM configuration files..."
|
||||
sudo rpmconf -a || error "rpmconf execution failed"
|
||||
else
|
||||
info "rpmconf not available; skipping config file handling"
|
||||
fi
|
||||
if command -v rpmconf &>/dev/null; then
|
||||
info "Handling leftover RPM configuration files..."
|
||||
sudo rpmconf -a || error 7 "rpmconf execution failed"
|
||||
else
|
||||
info "rpmconf not available; skipping config file handling"
|
||||
fi
|
||||
}
|
||||
|
||||
# ---
|
||||
# Description: Install security updates if any exist.
|
||||
# @description: Install security updates if any exist.
|
||||
# @return_code: [1] General error (inherits from set -e).
|
||||
# @return_code: [8] Security update failed.
|
||||
# ---
|
||||
install_security_updates() {
|
||||
info "Checking for security updates..."
|
||||
if sudo dnf check-update --security &>/dev/null; then
|
||||
info "Installing security updates..."
|
||||
sudo dnf -y update --security || error "Security update failed"
|
||||
else
|
||||
info "No security updates available."
|
||||
fi
|
||||
info "Checking for security updates..."
|
||||
# dnf check-update returns 100 if updates are available, 1 on error, 0 if not.
|
||||
# We only want to proceed if it returns 100 (success with updates) or 0 (no updates).
|
||||
# Using || true prevents set -e from triggering on exit code 100.
|
||||
if sudo dnf check-update --security &>/dev/null || [[ $? -eq 100 ]]; then
|
||||
info "Installing security updates..."
|
||||
sudo dnf -y update --security || error 8 "Security update failed"
|
||||
else
|
||||
info "No security updates available."
|
||||
fi
|
||||
}
|
||||
|
||||
# ---
|
||||
# Description: Remove unused packages and clean cache.
|
||||
# @description: Remove unused packages and clean cache.
|
||||
# @return_code: [1] General error (inherits from set -e).
|
||||
# @return_code: [9] DNF autoremove failed.
|
||||
# @return_code: [10] DNF clean failed.
|
||||
# ---
|
||||
cleanup_packages() {
|
||||
info "Removing unused dependencies..."
|
||||
sudo dnf -y autoremove || error "DNF autoremove failed"
|
||||
info "Removing unused dependencies..."
|
||||
sudo dnf -y autoremove || error 9 "DNF autoremove failed"
|
||||
|
||||
info "Cleaning cached package data..."
|
||||
sudo dnf clean all || error "DNF clean failed"
|
||||
info "Cleaning cached package data..."
|
||||
sudo dnf clean all || error 10 "DNF clean failed"
|
||||
}
|
||||
|
||||
# ---
|
||||
# Description: Update Flatpak applications and remove unused runtimes.
|
||||
# @description: Update Flatpak applications and remove unused runtimes.
|
||||
# @return_code: [1] General error (inherits from set -e).
|
||||
# @return_code: [11] Flatpak update failed.
|
||||
# @return_code: [12] Flatpak cleanup failed.
|
||||
# ---
|
||||
update_flatpak() {
|
||||
if command -v flatpak &>/dev/null; then
|
||||
info "Updating Flatpak applications..."
|
||||
flatpak update -y || error "Flatpak update failed"
|
||||
if command -v flatpak &>/dev/null; then
|
||||
info "Updating Flatpak applications..."
|
||||
flatpak update -y || error 11 "Flatpak update failed"
|
||||
|
||||
info "Removing unused Flatpak runtimes..."
|
||||
flatpak uninstall --unused -y || error "Flatpak cleanup failed"
|
||||
else
|
||||
info "Flatpak not installed; skipping Flatpak updates"
|
||||
fi
|
||||
info "Removing unused Flatpak runtimes..."
|
||||
flatpak uninstall --unused -y || error 12 "Flatpak cleanup failed"
|
||||
else
|
||||
info "Flatpak not installed; skipping Flatpak updates"
|
||||
fi
|
||||
}
|
||||
|
||||
# ---
|
||||
# Description: Main routine.
|
||||
# Globals: FORCE_ROOT
|
||||
# @description: Main routine.
|
||||
# @arg: $@ - Command-line arguments (currently unused).
|
||||
# @return_code: [1] General error (inherits from set -e).
|
||||
# @return_code: [N] Errors from called functions (e.g., setup, install_if_missing, etc.).
|
||||
# ---
|
||||
main() {
|
||||
|
||||
info "Starting system updates..."
|
||||
|
||||
install_if_missing rpmconf
|
||||
install_if_missing flatpak
|
||||
|
||||
run_dnf_update
|
||||
handle_rpmconf
|
||||
install_security_updates
|
||||
cleanup_packages
|
||||
update_flatpak
|
||||
|
||||
info "System updates completed successfully."
|
||||
setup
|
||||
info "Starting system updates..."
|
||||
install_if_missing rpmconf
|
||||
install_if_missing flatpak
|
||||
run_dnf_update
|
||||
handle_rpmconf
|
||||
install_security_updates
|
||||
cleanup_packages
|
||||
update_flatpak
|
||||
info "System updates completed successfully."
|
||||
}
|
||||
|
||||
trap 'error "Command failed at line $LINENO"' ERR
|
||||
main "$@"
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user