diff options
| -rw-r--r-- | .gitmodules | 3 | ||||
| -rwxr-xr-x | archive/bridge-sync.sh.bak | 31 | ||||
| -rwxr-xr-x | archive/kvm-lab-create.bak | 110 | ||||
| -rwxr-xr-x | archive/vm-new | 58 | ||||
| -rwxr-xr-x | archive/vm-rm | 44 | ||||
| -rwxr-xr-x | backup_shield.sh | 135 | ||||
| -rwxr-xr-x | backup_usb.sh | 155 | ||||
| -rwxr-xr-x | bashmount | 855 | ||||
| -rwxr-xr-x | bookmarkthis | 61 | ||||
| -rwxr-xr-x | borg-backup.sh | 24 | ||||
| -rwxr-xr-x | bridge-sync | 88 | ||||
| -rwxr-xr-x | create-lab | 206 | ||||
| -rwxr-xr-x | dwm-status.sh | 2 | ||||
| -rwxr-xr-x | firefox | 4 | ||||
| -rwxr-xr-x | firefox-raw | 2 | ||||
| -rwxr-xr-x | kvm-lab-create | 160 | ||||
| -rwxr-xr-x | kvm-lab-create.bak | 160 | ||||
| -rwxr-xr-x | kvm-lab-destroy | 52 | ||||
| -rwxr-xr-x | kvm-lab-status | 67 | ||||
| -rwxr-xr-x | kvm-promote-to-base | 87 | ||||
| m--------- | lib/bashsimplecurses | 0 | ||||
| -rwxr-xr-x | luks-shield | 142 | ||||
| -rw-r--r-- | nextcloud | 1 | ||||
| -rwxr-xr-x | officium | 214 | ||||
| -rw-r--r-- | officium_readme.md | 135 | ||||
| -rwxr-xr-x | passmenu | 35 | ||||
| -rwxr-xr-x | passmenu-otp | 24 | ||||
| -rwxr-xr-x | proton-bridge-cert-refresh | 39 | ||||
| -rwxr-xr-x | reset-gpt | 17 | ||||
| l--------- | rexi | 1 | ||||
| -rwxr-xr-x | rofi-vim-power | 26 | ||||
| -rwxr-xr-x | sharepoint-mount | 40 | ||||
| -rwxr-xr-x | sharepoint-status | 18 | ||||
| -rwxr-xr-x | sharepoint-umount | 33 | ||||
| -rwxr-xr-x | typebookmarks | 5 | ||||
| -rwxr-xr-x | usb-safe | 132 | ||||
| l--------- | yt-dlp | 1 |
37 files changed, 3166 insertions, 1 deletions
diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..0f1a254 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "lib/bashsimplecurses"] + path = lib/bashsimplecurses + url = https://github.com/metal3d/bashsimplecurses diff --git a/archive/bridge-sync.sh.bak b/archive/bridge-sync.sh.bak new file mode 100755 index 0000000..7aecb75 --- /dev/null +++ b/archive/bridge-sync.sh.bak @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +set -euo pipefail + +CERT_FILE="$HOME/.config/isync/proton-bridge.pem" + +echo "[1] Restart Bridge" +systemctl --user restart protonmail-bridge.service + +echo "[2] Wait for Bridge IMAP port" +for i in {1..30}; do + ss -ltn '( sport = :1143 )' | grep -q 1143 && break + sleep 1 +done + +ss -ltn '( sport = :1143 )' | grep -q 1143 || { + echo "Bridge did not start"; exit 1; } + +echo "[3] Refresh cert" +systemctl --user start proton-bridge-cert-refresh.service + +echo "[4] Wait for cert" +for i in {1..10}; do + [ -s "$CERT_FILE" ] && break + sleep 1 +done + +[ -s "$CERT_FILE" ] || { echo "Cert not created"; exit 1; } + +echo "[5] Run mbsync" +mbsync -a -V + diff --git a/archive/kvm-lab-create.bak b/archive/kvm-lab-create.bak new file mode 100755 index 0000000..d0d5865 --- /dev/null +++ b/archive/kvm-lab-create.bak @@ -0,0 +1,110 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Libvirt context (system) +URI="qemu:///system" + +# Base/template (immutable parent qcow2) +BASE="/var/lib/libvirt/images/base/debian13-template.qcow2" +TEMPLATE_DOMAIN="debian13-template" + +# Lab overlays directory (dedicated) +LABDIR="/var/lib/libvirt/images/lab" + +usage() { + echo "Usage: kvm-lab-create <lab-name> [--start]" + echo "Example: kvm-lab-create lab02 --start" +} + +NAME="${1:-}" +START="${2:-}" + +if [[ -z "$NAME" ]]; then + usage + exit 1 +fi + +if [[ "${START:-}" != "" && "${START:-}" != "--start" ]]; then + usage + exit 1 +fi + +DISK="${LABDIR}/${NAME}.qcow2" +XML_TMP="$(mktemp "/tmp/${NAME}.xml.XXXXXX")" + +cleanup() { rm -f "$XML_TMP"; } +trap cleanup EXIT + +# ---- Preconditions (read-only checks) ---- +if ! sudo test -r "$BASE"; then + echo "ERROR: Base image not readable: $BASE" + exit 1 +fi + +if [[ ! -d "$LABDIR" ]]; then + echo "ERROR: Lab directory missing: $LABDIR" + exit 1 +fi + +if [[ -e "$DISK" ]]; then + echo "ERROR: Disk already exists: $DISK" + exit 1 +fi + +if virsh -c "$URI" dominfo "$NAME" &>/dev/null; then + echo "ERROR: Domain already exists: $NAME" + exit 1 +fi + +if ! virsh -c "$URI" dominfo "$TEMPLATE_DOMAIN" &>/dev/null; then + echo "ERROR: Template domain not found in $URI: $TEMPLATE_DOMAIN" + exit 1 +fi + +# ---- Create overlay disk ---- +echo "Creating overlay disk: $DISK" +sudo qemu-img create -f qcow2 -F qcow2 -b "$BASE" "$DISK" >/dev/null + +# Ensure qemu/libvirt can open it +sudo chown libvirt-qemu:libvirt "$DISK" +sudo chmod 0660 "$DISK" + +# Quick sanity: confirm backing file is correct +echo "Validating backing file..." +sudo qemu-img info "$DISK" | grep -E "backing file:|file format:|virtual size:" || true + +# ---- Clone domain XML ---- +echo "Cloning domain XML from: $TEMPLATE_DOMAIN" +virsh -c "$URI" dumpxml "$TEMPLATE_DOMAIN" > "$XML_TMP" + +# Replace <name> and the disk path, and force NAT-only network "default" +# Notes: +# - disk path substitution assumes template points at /var/lib/libvirt/images/base/debian13-template.qcow2 +# - network replacement assumes a standard <interface type='network'> exists +sed -i \ + -e "s|<name>${TEMPLATE_DOMAIN}</name>|<name>${NAME}</name>|" \ + -e "s|${BASE}|${DISK}|g" \ + -e "s|<source network='[^']*'|<source network='default'|g" \ + "$XML_TMP" + +# Extra safety: remove any hostfs passthrough (if any) to prevent host access via mounts +# (If none exist, this does nothing.) +perl -0777 -i -pe 's/<filesystem[\s\S]*?<\/filesystem>\n?//g' "$XML_TMP" +# +# Remove UUID to avoid collision with template +sed -i -E '/<uuid>[^<]*<\/uuid>/d' "$XML_TMP" +# ---- Define domain ---- +echo "Defining domain: $NAME" +virsh -c "$URI" define "$XML_TMP" >/dev/null + +# ---- Optional start ---- +if [[ "$START" == "--start" ]]; then + echo "Starting domain: $NAME" + virsh -c "$URI" start "$NAME" >/dev/null +fi + +echo "OK: Lab '${NAME}' created (disk: ${DISK})." +if [[ "$START" != "--start" ]]; then + echo "To start: virsh -c $URI start ${NAME}" +fi + diff --git a/archive/vm-new b/archive/vm-new new file mode 100755 index 0000000..3955c6c --- /dev/null +++ b/archive/vm-new @@ -0,0 +1,58 @@ +#!/usr/bin/env bash +set -euo pipefail + +NAME="${1:-}" +RAM="${2:-4096}" # MiB +VCPUS="${3:-2}" + +BASE="/var/lib/libvirt/images/base/work01-base.qcow2" +DIR="/var/lib/libvirt/images/overlays" +DISK="${DIR}/${NAME}.qcow2" + +usage() { + echo "Usage: vm-new <name> [ramMiB=4096] [vcpus=2]" >&2 + exit 2 +} + +[[ -n "$NAME" ]] || usage + +case "$NAME" in + debian13-template|work01|Whonix-Gateway|Whonix-Workstation) + echo "Refusing: protected VM name '$NAME'." >&2 + exit 1 + ;; +esac + +if sudo virsh dominfo "$NAME" >/dev/null 2>&1; then + echo "Refusing: VM '$NAME' already exists in libvirt." >&2 + exit 1 +fi + +if [[ ! -f "$BASE" ]]; then + echo "Base image not found: $BASE" >&2 + exit 1 +fi + +if [[ -e "$DISK" ]]; then + echo "Refusing: disk already exists: $DISK" >&2 + exit 1 +fi + +# Create overlay +sudo qemu-img create -f qcow2 -F qcow2 -b "$BASE" "$DISK" >/dev/null + +# Define + install (import existing disk) +sudo virt-install --connect qemu:///system \ + --name "$NAME" \ + --memory "$RAM" --vcpus "$VCPUS" --cpu host-model \ + --disk "path=$DISK,format=qcow2,bus=virtio" \ + --network network=default,model=virtio \ + --graphics none \ + --import \ + --osinfo linux2024 \ + --noautoconsole + +echo "Created: $NAME" +echo "Disk: $DISK" +echo "Next: sudo virsh start $NAME && sudo virsh domifaddr $NAME" + diff --git a/archive/vm-rm b/archive/vm-rm new file mode 100755 index 0000000..3cbeb7d --- /dev/null +++ b/archive/vm-rm @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +set -euo pipefail + +NAME="${1:-}" +usage() { echo "Usage: vm-rm <name>" >&2; exit 2; } +[[ -n "$NAME" ]] || usage + +case "$NAME" in + debian13-template|work01|Whonix-Gateway|Whonix-Workstation) + echo "Refusing: protected VM name '$NAME'." >&2 + exit 1 + ;; +esac + +if ! sudo virsh dominfo "$NAME" >/dev/null 2>&1; then + echo "VM not found in libvirt: $NAME" >&2 + exit 1 +fi + +# Find associated disk(s) from XML, delete only qcow2 under overlays/ +mapfile -t DISKS < <(sudo virsh domblklist "$NAME" --details \ + | awk '$3=="disk" && $4 ~ /^\// {print $4}') + +echo "Will remove VM: $NAME" +for d in "${DISKS[@]}"; do + echo "Disk: $d" +done + +sudo virsh destroy "$NAME" >/dev/null 2>&1 || true +sudo virsh undefine "$NAME" --nvram >/dev/null 2>&1 || sudo virsh undefine "$NAME" >/dev/null + +for d in "${DISKS[@]}"; do + case "$d" in + /var/lib/libvirt/images/overlays/*.qcow2) + sudo rm -f -- "$d" + ;; + *) + echo "Skipping non-overlay disk: $d" >&2 + ;; + esac +done + +echo "Removed: $NAME" + diff --git a/backup_shield.sh b/backup_shield.sh new file mode 100755 index 0000000..96f9269 --- /dev/null +++ b/backup_shield.sh @@ -0,0 +1,135 @@ +#!/usr/bin/env bash +set -euo pipefail + +#################### CONFIG – EDIT THESE #################### + +# LUKS2 block device +LUKS_DEVICE="/dev/disk/by-uuid/6659e638-d52a-4c32-9781-9dcedd44db35" + +# Name for the mapper device (will appear as /dev/mapper/${LUKS_NAME}) +LUKS_NAME="shield" + +# Mount point for the decrypted filesystem +LUKS_MOUNTPOINT="/mnt/shield" + +# Local folders to back up with rsync → LUKS volume +# Each folder will be mirrored into ${LUKS_MOUNTPOINT}/<basename-of-folder> +BACKUP_ITEMS=( + "$HOME/" + "/media/.secrets/" +) + +# Machine identifier (destination subdir on the LUKS volume) +MACHINE_DIR="T480" + +#################### INTERNALS – NO NEED TO EDIT ############ + +need_cmd() { command -v "$1" >/dev/null 2>&1 || { echo "ERROR: missing command: $1" >&2; exit 1; }; } + +cleanup() { + echo + echo ">>> Cleaning up..." + + # Try to unmount, if mounted + if mountpoint -q "${LUKS_MOUNTPOINT}"; then + echo ">>> Unmounting ${LUKS_MOUNTPOINT}..." + sudo umount "${LUKS_MOUNTPOINT}" || true + fi + + # Close LUKS mapping if it exists + if [ -e "/dev/mapper/${LUKS_NAME}" ]; then + echo ">>> Closing LUKS mapping ${LUKS_NAME}..." + sudo cryptsetup close "${LUKS_NAME}" || true + fi +} +trap cleanup EXIT + +need_cmd cryptsetup +need_cmd rclone +need_cmd rsync +need_cmd mountpoint + +# Cache sudo credentials early to avoid surprises mid-run +sudo -v + +echo ">>> Ensuring mountpoint exists: ${LUKS_MOUNTPOINT}" +sudo mkdir -p "${LUKS_MOUNTPOINT}" + +# Check if already open +if [ -e "/dev/mapper/${LUKS_NAME}" ]; then + echo ">>> WARNING: /dev/mapper/${LUKS_NAME} already exists, assuming already open." +else + echo ">>> Opening LUKS volume (interactive prompt)..." + sudo cryptsetup open "${LUKS_DEVICE}" "${LUKS_NAME}" +fi + +# At this point /dev/mapper/${LUKS_NAME} must exist +if [ ! -e "/dev/mapper/${LUKS_NAME}" ]; then + echo "ERROR: /dev/mapper/${LUKS_NAME} not found after cryptsetup open." >&2 + exit 1 +fi + +# Mount if not already mounted +if mountpoint -q "${LUKS_MOUNTPOINT}"; then + echo ">>> WARNING: ${LUKS_MOUNTPOINT} already mounted." +else + echo ">>> Mounting /dev/mapper/${LUKS_NAME} on ${LUKS_MOUNTPOINT}..." + sudo mount "/dev/mapper/${LUKS_NAME}" "${LUKS_MOUNTPOINT}" +fi + +echo ">>> LUKS volume mounted at ${LUKS_MOUNTPOINT}" + +# Ensure SharePoint destinations exist in root of shield +sudo mkdir -p \ + "${LUKS_MOUNTPOINT}/SP_Administration" \ + "${LUKS_MOUNTPOINT}/SP_Operations" \ + "${LUKS_MOUNTPOINT}/SP_Skyscale" + +echo ">>> Starting rclone syncs (SharePoint -> ${LUKS_MOUNTPOINT}/SP_*)..." +rclone sync 'SP_Administration:' "${LUKS_MOUNTPOINT}/SP_Administration" --create-empty-src-dirs --fast-list --progress +rclone sync 'SP_Operations:' "${LUKS_MOUNTPOINT}/SP_Operations" --create-empty-src-dirs --fast-list --progress +rclone sync 'SP_SkyscaleCommerce:' "${LUKS_MOUNTPOINT}/SP_Skyscale" --create-empty-src-dirs --fast-list --progress +echo ">>> All rclone syncs finished." + +echo ">>> Starting rsync backups of local folders -> ${LUKS_MOUNTPOINT}/${MACHINE_DIR}/..." + +# Ensure machine root exists +sudo mkdir -p "${LUKS_MOUNTPOINT}/${MACHINE_DIR}" + +# Explicit destination mapping while keeping your BACKUP_ITEMS sources as-is +# - "$HOME/" -> /mnt/shield/T480/HOME +# - "/media/.secrets/"-> /mnt/shield/T480/secrets +for SRC in "${BACKUP_ITEMS[@]}"; do + if [ ! -d "$SRC" ]; then + echo ">>> WARNING: source folder does not exist, skipping: $SRC" + continue + fi + + case "$SRC" in + "$HOME/"|"$HOME") + RELDEST="${MACHINE_DIR}/HOME" + ;; + "/media/.secrets/"|"/media/.secrets") + RELDEST="${MACHINE_DIR}/secrets" + ;; + *) + # Fallback (shouldn't happen with your current BACKUP_ITEMS) + # Note: basename "$SRC" with trailing slash becomes "home"; avoid relying on it. + NAME="$(basename "${SRC%/}")" + RELDEST="${MACHINE_DIR}/${NAME}" + ;; + esac + + DEST="${LUKS_MOUNTPOINT}/${RELDEST}" + sudo mkdir -p "${DEST}" + + echo ">>> rsync: ${SRC} -> ${DEST}" + rsync -a --delete --progress \ + "${SRC%/}/" \ + "${DEST}/" +done + +echo ">>> All rsync backups finished." +echo ">>> Unmounting and closing LUKS (via trap)..." +# cleanup() will run automatically on script exit + diff --git a/backup_usb.sh b/backup_usb.sh new file mode 100755 index 0000000..8c81b69 --- /dev/null +++ b/backup_usb.sh @@ -0,0 +1,155 @@ +#!/usr/bin/env bash +set -euo pipefail + +#################### CONFIG #################### + +# LUKS2 container UUID (LUKS header UUID) +LUKS_SOURCE="/dev/disk/by-uuid/bdd2a016-b6db-493d-ad0a-e8dd8a93dfbd" + +# Name for /dev/mapper/<name> +MAPPER_NAME="usb_safe" + +# Mountpoint for decrypted filesystem +MOUNTPOINT="/mnt/usb_safe" + +# Optional underlying block device for power-off (set "" to disable) +RAW_DEVICE="" + +# Items to mirror from $HOME to the encrypted drive (files and directories) +BACKUP_ITEMS=( + "$HOME/.abook" + "$HOME/.config" + "$HOME/Documents" + "$HOME/dotfiles" + "$HOME/.password-store" + "$HOME/secure" + "$HOME/vimwiki" + "$HOME/.password.tomb" + "$HOME/.secrets.tomb" +) + +RSYNC_OPTS=( + -a + --delete + --no-perms + --no-owner + --no-group + --no-xattrs + --omit-dir-times + --progress +) + +LOGFILE="$HOME/backup_flash_rsync.log" + +#################### HELPERS #################### + +die() { echo "ERROR: $*" >&2; exit 1; } + +is_mapper_open() { [[ -e "/dev/mapper/${MAPPER_NAME}" ]]; } +is_mounted() { mountpoint -q "${MOUNTPOINT}"; } + +busy_report() { + echo ">>> Busy report for ${MOUNTPOINT}:" + echo ">>> lsof:" + sudo lsof +f -- "${MOUNTPOINT}" || true + echo ">>> fuser:" + sudo fuser -vm "${MOUNTPOINT}" || true +} + +cleanup() { + set +e + + if is_mounted; then + echo ">>> Unmounting ${MOUNTPOINT}..." + sudo umount "${MOUNTPOINT}" || { + echo ">>> Unmount failed (device busy)." + busy_report + echo ">>> Last resort: sudo umount -l '${MOUNTPOINT}'" + } + fi + + if is_mapper_open; then + echo ">>> Closing LUKS mapper ${MAPPER_NAME}..." + sudo cryptsetup close "${MAPPER_NAME}" || true + fi + + if [[ -n "${RAW_DEVICE}" ]]; then + echo ">>> Powering off ${RAW_DEVICE}..." + udisksctl power-off -b "${RAW_DEVICE}" >/dev/null 2>&1 || true + fi +} + +trap cleanup EXIT + +#################### OPEN + MOUNT #################### + +echo ">>> Checking LUKS source exists: ${LUKS_SOURCE}" +[[ -e "${LUKS_SOURCE}" ]] || die "LUKS source not found: ${LUKS_SOURCE}" + +echo ">>> Ensuring mountpoint exists: ${MOUNTPOINT}" +sudo mkdir -p "${MOUNTPOINT}" + +is_mounted && die "Mountpoint already in use: ${MOUNTPOINT}" +is_mapper_open && die "Mapper already open: /dev/mapper/${MAPPER_NAME}" + +echo ">>> Opening LUKS2 container (prompt for passphrase)..." +sudo cryptsetup open --type luks2 "${LUKS_SOURCE}" "${MAPPER_NAME}" + +echo ">>> Mounting decrypted filesystem..." +sudo mount "/dev/mapper/${MAPPER_NAME}" "${MOUNTPOINT}" + +echo ">>> Mounted at ${MOUNTPOINT}" +echo ">>> Logging rsync output to: ${LOGFILE}" +echo "[$(date)] Backup run started" >> "${LOGFILE}" + +#################### RSYNC LOOP #################### + +ANY_WARNING=0 + +for SRC in "${BACKUP_ITEMS[@]}"; do + [[ -e "${SRC}" ]] || die "Missing source item: ${SRC}" + + NAME="$(basename "${SRC}")" + DEST="${MOUNTPOINT}/${NAME}" + + echo ">>> rsync: ${SRC} -> ${DEST}" + echo "[$(date)] rsync: ${SRC} -> ${DEST}" >> "${LOGFILE}" + + set +e + if [[ -d "${SRC}" ]]; then + rsync "${RSYNC_OPTS[@]}" "${SRC}/" "${DEST}/" 2>> "${LOGFILE}" + else + # file + rsync "${RSYNC_OPTS[@]}" "${SRC}" "${DEST}" 2>> "${LOGFILE}" + fi + RS=$? + set -e + + if [[ "${RS}" -eq 0 ]]; then + echo ">>> rsync OK: ${SRC}" + echo "[$(date)] rsync OK: ${SRC}" >> "${LOGFILE}" + elif [[ "${RS}" -eq 23 || "${RS}" -eq 24 ]]; then + echo ">>> WARNING: rsync non-critical issues (code ${RS}) for: ${SRC}" + echo ">>> See ${LOGFILE}." + echo "[$(date)] WARNING: rsync code ${RS} for: ${SRC}" >> "${LOGFILE}" + ANY_WARNING=1 + else + echo "[$(date)] ERROR: rsync failed code ${RS} for: ${SRC}" >> "${LOGFILE}" + exit "${RS}" + fi +done + +echo ">>> Backup loop finished." +if [[ "${ANY_WARNING}" -eq 1 ]]; then + echo ">>> Backup completed WITH WARNINGS. See ${LOGFILE}." + echo "[$(date)] Backup completed WITH WARNINGS" >> "${LOGFILE}" +else + echo ">>> Backup completed OK." + echo "[$(date)] Backup completed OK" >> "${LOGFILE}" +fi + +echo ">>> Syncing..." +sync + +echo ">>> Done. (Auto-cleanup will unmount + close mapper.)" + diff --git a/bashmount b/bashmount new file mode 100755 index 0000000..331ebe4 --- /dev/null +++ b/bashmount @@ -0,0 +1,855 @@ +#!/bin/bash +set -u + +declare -r VERSION="4.3.2" + +#=============================================================================# +# FILE: bashmount # +# WEBSITE: https://github.com/jamielinux/bashmount # +# DESCRIPTION: bashmount is a menu-driven bash script that can use different # +# backends to easily mount, unmount or eject removable devices # +# without dependencies on any GUI. An extensive configuration # +# file allows many aspects of the script to be modified and # +# custom commands to be run on devices. # +# LICENSE: GPLv2 # +# AUTHORS: Jamie Nguyen <j@jamielinux.com> # +# Lukas B. # +#=============================================================================# + +# Copyright (C) 2013-2020 Jamie Nguyen <j@jamielinux.com> +# Copyright (C) 2014 Lukas B. +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License v2 as published by the +# Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + +if (( $# > 0 )) && [[ "$1" == "-v" || "$1" == "--version" ]]; then + printf '%s\n' "$VERSION" + exit 0 +fi + +declare -ri EXIT_CMDNOTFOUND=127 +declare -ri EXIT_CONFIG=78 + +# {{{ ··· CONFIGURATION +# Make sure that user defined options will not interfere with grep. +unset GREP_OPTIONS + +# Set defaults. +declare udisks="auto" +declare mount_options="" +declare -i show_internal=1 +declare -i show_removable=1 +declare -i show_optical=1 +declare -i show_commands=1 +declare -i colourize=1 +declare -i pretty=1 +declare -i custom4_show=0 +declare -i custom5_show=0 +declare -i custom6_show=0 +declare custom4_desc="" +declare custom5_desc="" +declare custom6_desc="" +declare -i run_post_mount=0 +declare -i run_post_unmount=0 +declare -a exclude=() + +# Backwards compat +declare default_mount_options="" +declare -a blacklist=() + +filemanager() { + ( cd "$1" && "$SHELL" ) +} + +post_mount() { + error "No 'post_mount' command specified in bashmount configuration file." + return 1 +} + +post_unmount() { + error "No 'post_unmount' command specified in bashmount configuration file." + return 1 +} + +declare CONFIG_FILE="${XDG_CONFIG_HOME:-$HOME/.config}/bashmount/config" +[[ ! -e "$CONFIG_FILE" ]] && CONFIG_FILE="/etc/bashmount.conf" +if [[ -e "$CONFIG_FILE" ]]; then + if ! . "$CONFIG_FILE"; then + printf '%s\n' "bashmount: Failed to source configuration file." + exit $EXIT_CONFIG + fi +fi + +if [[ "$udisks" != 0 ]]; then + if type -p udisksctl >/dev/null 2>&1; then + [[ "$udisks" == "auto" ]] && udisks=1 + elif [[ "$udisks" == "auto" ]]; then + udisks=0 + else + printf '%s\n' "bashmount: 'udisksctl': command not found" + exit $EXIT_CMDNOTFOUND + fi +fi + +if ! type -p lsblk >/dev/null 2>&1; then + printf '%s\n' "bashmount: 'lsblk': command not found" + exit $EXIT_CMDNOTFOUND +fi + +# Backwards compat +[[ -n "$default_mount_options" ]] && mount_options="$default_mount_options" +(( ${#exclude[@]} == 0 )) && exclude=("${blacklist[@]}") +# }}} + +# {{{ ··· PRINTING FUNCTIONS +if (( colourize )); then + if tput setaf 0 >/dev/null 2>&1; then + declare -r ALL_OFF="$(tput sgr0)" + declare -r BOLD="$(tput bold)" + declare -r BLUE="${BOLD}$(tput setaf 4)" + declare -r GREEN="${BOLD}$(tput setaf 2)" + declare -r RED="${BOLD}$(tput setaf 1)" + else + declare -r ALL_OFF="\e[1;0m" + declare -r BOLD="\e[1;1m" + declare -r BLUE="${BOLD}\e[1;34m" + declare -r GREEN="${BOLD}\e[1;32m" + declare -r RED="${BOLD}\e[1;31m" + fi +else + declare -r ALL_OFF="" BOLD="" BLUE="" GREEN="" RED="" +fi +declare -r ARROW="==>" + +msg() { + printf '\n%b\n\n' "${GREEN}${ARROW}${ALL_OFF}${BOLD} ${*}${ALL_OFF}" >&2 +} +error() { + printf '\n%b\n\n' "${RED}${ARROW}${BOLD} ERROR: ${*}${ALL_OFF}" >&2 + enter_to_continue +} +enter_to_continue() { + printf '\n' + read -r -e -p "Press [${BLUE}enter${ALL_OFF}] to continue: " +} +invalid_command() { + printf '\n' + error "Invalid command. See the help menu." +} + +print_commands() { + printf '\n\n' + print_separator_commands + printf '%s' " ${BLUE}e${ALL_OFF}: eject" + printf '%s' " ${BLUE}i${ALL_OFF}: info" + printf '%s' " ${BLUE}m${ALL_OFF}: mount" + printf '%s' " ${BLUE}o${ALL_OFF}: open" + printf '%s' " ${BLUE}u${ALL_OFF}: unmount" + printf '\n\n' + printf '%s' " ${BLUE}[Enter]${ALL_OFF}: refresh" + printf '%s' " ${BLUE}a${ALL_OFF}: unmount all" + printf '%s' " ${BLUE}q${ALL_OFF}: quit" + printf '%s' " ${BLUE}?${ALL_OFF}: help" + printf '\n\n' +} + +print_submenu_commands() { + printf '\n\n' + print_separator_commands + printf '%s' " ${BLUE}e${ALL_OFF}: eject" + printf '%s' " ${BLUE}i${ALL_OFF}: info" + if check_mounted "$devname"; then + printf '%s' " ${BLUE}u${ALL_OFF}: unmount" + else + printf '%s' " ${BLUE}m${ALL_OFF}: mount " + fi + printf '%s' " ${BLUE}o${ALL_OFF}: open" + printf '\n\n' + printf '%s' " ${BLUE}[Enter]${ALL_OFF}: refresh" + printf '%s' " ${BLUE}b${ALL_OFF}: back" + printf '%s' " ${BLUE}q${ALL_OFF}: quit" + printf '%s' " ${BLUE}?${ALL_OFF}: help" + printf '\n\n' + printf '%s' " ${BLUE}1${ALL_OFF}: luksDump" + printf '%s' " ${BLUE}2${ALL_OFF}: luksOpen" + printf '%s' " ${BLUE}3${ALL_OFF}: luksClose" + printf '\n' + + if (( custom4_show )) || (( custom5_show )) || (( custom6_show )); then + local -i col_width=18 + printf '\n' + (( custom4_show )) && [[ -n "$custom4_desc" ]] \ + && printf '%s' " ${BLUE}4${ALL_OFF}: $custom4_desc" + local -i custom4_desc_len="${#custom4_desc}" + if (( custom4_desc_len < col_width )); then + for (( i=18; i>custom4_desc_len; i-- )); do + printf '%s' " " + done + fi + (( custom5_show )) && [[ -n "$custom5_desc" ]] \ + && printf '%s' " ${BLUE}5${ALL_OFF}: $custom5_desc" + local -i custom5_desc_len="${#custom5_desc}" + if (( custom5_desc_len < col_width )); then + for (( i=18; i>custom5_desc_len; i-- )); do + printf '%s' " " + done + fi + (( custom6_show )) && [[ -n "$custom6_desc" ]] \ + && printf '%s' " ${BLUE}6${ALL_OFF}: $custom6_desc" + printf '\n' + fi +} + +__print() { + printf "${BOLD}" + if (( pretty )); then + printf '%s\n\n' "$1" | sed -e "s/-/━/g" + else + printf '%s\n\n' "$1" + fi + printf "${ALL_OFF}" +} +print_separator() { + __print "-----------------------------------------------------------------------------" +} +print_separator_commands() { + __print "-------------------------------- Commands ---------------------------------" +} +print_separator_device() { + __print "------------------------------- Device Menu -------------------------------" +} +print_separator_optical() { + __print "------------------------------ Optical Media ------------------------------" +} +print_separator_removable() { + __print "----------------------------- Removable Media -----------------------------" +} +print_separator_internal() { + __print "----------------------------- Internal Media ------------------------------" +} + +print_help() { + clear + print_commands + print_separator + printf '%b' " ${GREEN}${ARROW}${ALL_OFF} " + printf '%s' "${BOLD}To mount the first device, enter ${ALL_OFF}" + printf '%s' "${BLUE}1m${ALL_OFF}${BOLD}.${ALL_OFF}" + printf '\n\n' + printf '%b' " ${GREEN}${ARROW}${ALL_OFF} " + printf '%s' "${BOLD}To open the mountpath directory of the first${ALL_OFF}" + printf '\n\n' + printf '%s' " ${BOLD}device (mounting if required), enter " + printf '%s' "${BLUE}1o${ALL_OFF}${BOLD}.${ALL_OFF}" + printf '\n\n' + printf '%b' " ${GREEN}${ARROW}${ALL_OFF} " + printf '%s' "${BOLD}To view a device sub-menu, just enter the number.${ALL_OFF}" + printf '\n\n' + printf '%b' " ${GREEN}${ARROW}${ALL_OFF} " + printf '%s' "${BLUE}[Enter]${ALL_OFF}" + printf '%s' "${BOLD}, " + printf '%s' "${BLUE}a${ALL_OFF}" + printf '%s' "${BOLD}, " + printf '%s' "${BLUE}q${ALL_OFF} " + printf '%s' "${BOLD}and " + printf '%s' "${BLUE}?${ALL_OFF} " + printf '%s' "${BOLD}do not require a number.${ALL_OFF}" + printf '\n\n' + print_separator + enter_to_continue +} + +print_help_submenu() { + clear + print_submenu_commands + printf '\n' + print_separator + printf '%b' " ${GREEN}${ARROW}${ALL_OFF} " + printf '%s' "${BOLD}To perform a command, enter a character and press ${ALL_OFF}" + printf '%s' "${BLUE}[Enter]${ALL_OFF}${BOLD}.${ALL_OFF}" + printf '\n\n' + printf '%b' " ${GREEN}${ARROW}${ALL_OFF} " + printf '%s' "${BOLD}For example, to mount this device, type ${ALL_OFF}" + printf '%s' "${BLUE}m${ALL_OFF} and press ${BLUE}[Enter]${ALL_OFF}" + printf '%s' "${BOLD}.${ALL_OFF}" + printf '\n\n' + print_separator + enter_to_continue +} + +print_device() { + local -i padding_name=13 + local -i padding_label=18 + local -i padding_size=6 + + local label="$(info_fslabel "$devname")" + local fstype="$(info_fstype "$devname")" + + [[ -z "$label" ]] && (( $# == 1 )) && [[ "$1" == "optical" ]] \ + && label="$(lsblk -dno MODEL "$devname")" + [[ -z "$label" ]] && label="$(info_partlabel "$devname")" + [[ -z "$label" ]] && [[ "$fstype" == "crypto_LUKS" ]] && label="crypto_LUKS" + [[ -z "$label" ]] && label="-" + + label="$(printf '%s' "$label" | sed -e 's/\\x20/ /g')" + + listed[device_number]="$devname" + (( device_number++ )) + + printf '%s' " ${BLUE}${device_number})${ALL_OFF}" + + devname_short="${devname##*/}" + label_short="$label" + if (( ${#devname_short} > padding_name )); then + len=$(( padding_name - 4 )) + devname_short="${devname_short:0:len}..." + elif (( ${#label} > padding_label )); then + label_len=$(( padding_label - 4 )) + label_short="${label:0:label_len}..." + fi + + printf '%s' " ${devname_short}:" + for (( i=padding_name; i>${#devname_short}; i-- )); do + printf '%s' " " + done + + printf '%s' " $label_short" + for (( i=padding_label; i>${#label_short}; i-- )); do + printf '%s' " " + done + + size="$(info_size "$devname")" + printf '%s' " $size" + if (( ${#size} < padding_size )); then + for (( i=padding_size; i>${#size}; i-- )); do + printf '%s' " " + done + fi + + if [[ "$fstype" == "crypto_LUKS" ]]; then + local uuid="$(info_uuid "$devname")" + if [[ -n "$uuid" ]]; then + for dev in "${all[@]}"; do + if [[ "$dev" == "/dev/mapper/luks-$uuid" ]]; then + printf '%s' " ${GREEN}decrypted [luks-${uuid:0:4}...]${ALL_OFF}" + fi + done + fi + elif check_mounted "$devname"; then + mountpath="$(info_mountpath "$devname")" + printf '%s' " ${GREEN}[$mountpath]${ALL_OFF}" + mounted[${#mounted[*]}]="$devname" + fi + printf '\n' +} +# }}} + +# {{{ ··· INFORMATION RETRIEVAL +# Returns 0 (ie, success) if the device still exists. Otherwise it returns 1. +check_device() { + if [[ ! -b "$1" ]]; then + error "$1 is no longer available." + return 1 + fi +} +# Returns 0 (ie, success) if the device is mounted. Otherwise it returns 1. +check_mounted() { + findmnt -no TARGET "$1" >/dev/null 2>&1 +} +# Returns 0 (ie, success) if the device is registered as a removable device in +# the kernel. Otherwise it returns 1. +check_removable() { + [[ "$(lsblk -drno RM "$1")" == "1" ]] +} + +info_fslabel() { + lsblk -drno LABEL "$1" 2>/dev/null +} +info_fstype() { + lsblk -drno FSTYPE "$1" 2>/dev/null +} +info_mountpath() { + findmnt -no TARGET "$1" 2>/dev/null +} +info_partlabel() { + lsblk -drno PARTLABEL "$1" 2>/dev/null +} +info_size() { + lsblk -drno SIZE "$1" 2>/dev/null +} +info_type() { + lsblk -drno TYPE "$1" 2>/dev/null +} +info_uuid() { + lsblk -drno UUID "$1" 2>/dev/null +} +info_used() { + lsblk -drno FSUSE% "$1" 2>/dev/null +} + +get_luks_child() { + local uuid="$(info_uuid "$1")" + printf '%s' "/dev/mapper/luks-$uuid" +} +# }}} + +# {{{ ··· DEVICE MANIPULATION +__mount() { + msg "Mounting $1 ..." + if (( udisks )); then + udisksctl mount $mount_options --block-device "$1" + else + read -r -e -p "Choose the mountpoint directory: " dir + [[ -z "$dir" ]] && return 1 + if [[ ! -e "$dir" ]]; then + if ! mkdir -p "$dir"; then + error "'$dir': Could not create directory." + return 1 + fi + fi + sudo mount $mount_options "$1" "$dir" + fi +} + +__unmount() { + msg "Unmounting $1 ..." + if (( udisks )); then + udisksctl unmount --block-device "$1" + else + sudo umount "$1" + fi +} + +action_eject() { + check_device "$1" || return 1 + + if [[ "$(info_fstype "$1")" == "crypto_LUKS" ]]; then + action_unmount "$1" || return 1 + else + check_mounted "$1" && action_unmount "$1" + fi + + local -i retval=0 + if ! check_mounted "$1"; then + msg "Ejecting $1 ..." + device_type=$(info_type "$1") + if (( udisks )) && [[ "$device_type" != "rom" ]]; then + udisksctl power-off -b "$1" + retval=$? + else + sudo eject "$1" + retval=$? + fi + if (( retval == 0 )); then + # Give the device some time to eject. + sleep 1.5s + else + enter_to_continue + fi + fi +} + +action_info() { + check_device "$1" || return 1 + if (( udisks )); then + udisksctl info -b "$devname" | less + else + lsblk -po NAME,FSTYPE,SIZE,FSUSE%,MOUNTPOINT "$1" | less + fi +} + +action_mount() { + check_device "$1" || return 1 + if check_mounted "$1"; then + error "$1 is already mounted." + return 1 + fi + + if [[ "$(info_fstype "$1")" == "crypto_LUKS" ]]; then + luks_child="$(get_luks_child "$1")" + if [[ ! -b "$luks_child" ]]; then + action_unlock "$1" || return 1 + fi + action_mount "$luks_child" + return $? + fi + + if __mount "$1"; then + msg "$1 mounted successfully." + (( run_post_mount )) && post_mount "$1" + sleep 0.1s + return 0 + fi + error "$1 could not be mounted." + return 1 +} + +action_open() { + check_device "$1" || return 1 + if [[ "$(info_fstype "$1")" == "crypto_LUKS" ]]; then + luks_child="$(get_luks_child "$1")" + if [[ ! -b "$luks_child" ]]; then + action_mount "$1" || return 1 + fi + action_open "$luks_child" + return $? + elif ! check_mounted "$1"; then + action_mount "$1" || return 1 + fi + msg "Opening $1 ..." + filemanager "$(info_mountpath "$1")" || enter_to_continue +} + +action_unmount() { + check_device "$1" || return 1 + + if [[ "$(info_fstype "$1")" == "crypto_LUKS" ]]; then + luks_child="$(get_luks_child "$1")" + if [[ -b "$luks_child" ]]; then + if check_mounted "$luks_child"; then + action_unmount "$luks_child" || return 1 + fi + action_lock "$1" || return 1 + fi + return $? + fi + + if ! check_mounted "$1"; then + error "$1 is already unmounted." + return 1 + fi + + if __unmount "$1"; then + msg "$1 unmounted successfully." + (( run_post_unmount )) && post_unmount "$1" + sleep 0.1s + return 0 + fi + error "$1 could not be unmounted." + return 1 +} + +action_unlock() { + msg "Opening luks volume ..." + local -i retval=0 + if (( udisks )); then + udisksctl unlock --block-device "$devname" + retval=$? + else + sudo cryptsetup open --type luks -v "$devname" "luks-${devname##*/}" + retval=$? + fi + (( retval != 0 )) && enter_to_continue + return $retval +} + +action_lock() { + msg "Closing luks volume ..." + local -i retval=0 + if (( udisks )); then + udisksctl lock --block-device "$devname" + retval=$? + else + sudo cryptsetup close --type luks "$devname" + retval=$? + fi + (( retval != 0 )) && enter_to_continue + return $retval +} +# }}} + +# {{{ ··· MENU FUNCTIONS +list_devices() { + all=() # all devices + listed=() # all devices that are shown (ie, not hidden) + mounted=() # all devices that are shown and mounted + device_number=0 + + local -a removable=() + local -a internal=() + local -a optical=() + + while IFS='' read -r device; do + all+=( "$device" ) + done < <(lsblk -plno NAME) + + for devname in "${all[@]}"; do + # Hide excluded devices. + for string in "${exclude[@]}"; do + lsblk -dPno NAME,TYPE,FSTYPE,LABEL,MOUNTPOINT,PARTLABEL,UUID "$devname" \ + | grep -E "$string" >/dev/null 2>&1 + (( $? )) || continue 2 + done + + # Sort devices into arrays removable, internal, and optical. + local device_type=$(info_type "$devname") + if [[ "$device_type" == "part" ]]; then + if check_removable "$devname"; then + removable[${#removable[*]}]="$devname" + else + internal[${#internal[*]}]="$devname" + fi + elif [[ "$device_type" == "crypt" ]]; then + # luks-xxxxx devices are never marked as removable, so we judge + # whether they are removable by their parent crypto_LUKS device. + local -i parent_found=0 + for parent_devname in "${all[@]}"; do + local parent_uuid="$(info_uuid "$parent_devname")" + [[ -z "$parent_uuid" ]] && continue + if [[ "/dev/mapper/luks-$parent_uuid" == "$devname" ]]; then + if check_removable "$parent_devname"; then + removable[${#removable[*]}]="$devname" + parent_found=1 + break + else + internal[${#internal[*]}]="$devname" + parent_found=1 + break + fi + fi + done + (( !parent_found )) && internal[${#internal[*]}]="$devname" + # Normally we don't want to see a "disk", but if it has no partitions + # (eg, internal storage on some portable media devices) then it should + # be visible. + elif [[ "$device_type" == "disk" ]]; then + for (( i=0; i<${#all[@]}; i++ )); do + [[ "${all[$i]}" =~ "$devname".+ ]] && continue 2 + done + if check_removable "$devname"; then + removable[${#removable[*]}]="$devname" + else + internal[${#internal[*]}]="$devname" + fi + elif [[ "$device_type" == "rom" ]]; then + optical[${#optical[*]}]="$devname" + else + continue + fi + done + + clear + # List internal media. + if (( show_internal )) && (( ${#internal[*]} )); then + print_separator_internal + for devname in "${internal[@]}"; do + print_device + done + printf '\n' + fi + # List removable media. + if (( show_removable )) && (( ${#removable[*]} )); then + print_separator_removable + for devname in "${removable[@]}"; do + print_device + done + printf '\n' + fi + # List optical media. + if (( show_optical )) && (( ${#optical[*]} )); then + print_separator_optical + for devname in "${optical[@]}"; do + print_device optical + done + printf '\n' + fi + (( !device_number )) && printf '%s\n' "No devices." +} + +submenu() { + # Make sure device is still valid. + check_device "$devname" || return 1 + + # Try to use a useful label to identify the device. + local label="$(info_fslabel "$devname")" + [[ -z "$label" ]] && label="$(info_partlabel "$devname")" + [[ -z "$label" ]] && label="-" + + local fstype="$(info_fstype "$devname")" + local size="$(info_size "$devname")" + local used="$(info_used "$devname")" + local uuid="$(info_uuid "$devname")" + + local -i mounted=0 + check_mounted "$devname" && mounted=1 + + # Display the user interface. + clear + print_separator_device + printf '%s\n' " device : $devname" + printf '%s\n' " label : $label" + if [[ "$fstype" == "crypto_LUKS" ]]; then + printf '%s' " status : " + local -i unlocked=0 + if [[ -n "$uuid" ]]; then + for dev in "${all[@]}"; do + if [[ "$dev" == "/dev/mapper/luks-$uuid" ]]; then + printf '%s\n' "${GREEN}decrypted [luks-${uuid:0:4}...]${ALL_OFF}" + unlocked=1 + break + fi + done + fi + (( !unlocked )) && printf '%s\n' "encrypted" + else + printf '%s' " mounted : " + if (( mounted )); then + printf '%s\n' "${GREEN}yes${ALL_OFF}" + printf '%s\n' " mountpath : $(info_mountpath "$devname")" + else + printf '%s\n' "${RED}no${ALL_OFF}" + fi + fi + printf '%s\n' " fstype : $fstype" + printf '%s\n' " uuid : $uuid" + printf '%s\n' " size : $size" + (( mounted )) && printf '%s\n' " used : $used" + if (( show_commands == 1 )); then + printf '\n' + print_submenu_commands + fi + printf '\n' + print_separator + + # Receive user input. + local -i retval + if ! read -r -e -p "${BOLD}Command:${ALL_OFF} " action; then + # Exit on ctrl-d. + printf '\n' + exit 0 + fi + case "$action" in + "e") action_eject "$devname" || true;; + "i") action_info "$devname" || true;; + "m") action_mount "$devname" || true;; + "o") action_open "$devname" || true;; + "u") action_unmount "$devname" || true;; + "b") return 1;; + ""|"r") return 0;; + "q") exit;; + "?") print_help_submenu; return 0;; + "1") sudo sh -c "cryptsetup luksDump '$devname' | less"; return 0;; + "2") action_unlock "$devname"; return 1;; + "3") action_lock "$devname"; return 1;; + "4") + if (( custom4_show )); then + msg "Running custom command: $custom4_desc ..." + custom4_command "$devname" + enter_to_continue + else + invalid_command + fi + return 0;; + "5") + if (( custom5_show )); then + msg "Running custom command: $custom5_desc ..." + custom5_command "$devname" + enter_to_continue + else + invalid_command + fi + return 0;; + "6") + if (( custom6_show )); then + msg "Running custom command: $custom6_desc ..." + custom6_command "$devname" + enter_to_continue + else + invalid_command + fi + return 0;; + *) invalid_command; return 0;; + esac +} + +select_action() { + local devname + local letter + print_separator + if ! read -r -e -p "${BOLD}Command:${ALL_OFF} " action; then + # Exit on ctrl-d. + printf '\n' + exit 0 + fi + if [[ "$action" =~ ^[1-9] ]]; then + if [[ "$action" =~ ^[1-9][0-9]*$ ]]; then + # Zero-based numbering for array elements, so subtract one. + local number="$(( action - 1 ))" + if (( number >= device_number )); then + invalid_command + return 1 + fi + devname=${listed[number]} + while true; do + submenu || break + done + elif [[ "$action" =~ ^[1-9][0-9]*[eimou]$ ]]; then + # Zero-based numbering for array elements, so subtract one. + local number="$(( ${action%?} - 1 ))" + + if (( number >= device_number )); then + invalid_command + return 1 + fi + devname="${listed[number]}" + + letter="${action: -1}" + case "$letter" in + "e") action_eject "$devname";; + "i") action_info "$devname";; + "m") action_mount "$devname";; + "o") action_open "$devname";; + "u") action_unmount "$devname";; + *) return 1;; + esac + return 0 + else + invalid_command + return 1 + fi + else + case "$action" in + "a") + if (( ! ${#mounted[*]} )); then + error "No devices mounted." + return 1 + fi + printf '\n' + read -r -e -p "Unmount all devices [y/N]?: " unmount + [[ "$unmount" != "y" ]] && [[ "$unmount" != "Y" ]] && return 0 + clear + for devname in "${mounted[@]}"; do + action_unmount "$devname" || continue + done + enter_to_continue + return 1;; + "r"|"") return 0;; + "q"|"b") exit 0;; + "?") print_help; return 0;; + *) invalid_command; return 1;; + esac + fi +} +# }}} + +declare -i device_number=0 +declare -a all=() +declare -a listed=() +declare -a mounted=() + +while true; do + list_devices + (( show_commands )) && print_commands + select_action +done + diff --git a/bookmarkthis b/bookmarkthis new file mode 100755 index 0000000..22aff66 --- /dev/null +++ b/bookmarkthis @@ -0,0 +1,61 @@ +#!/bin/sh +set -eu + +file="$HOME/.snippets" +mkdir -p "$(dirname "$file")" +touch "$file" + +# wybierz JEDNO: +bookmark="$(xclip -o -selection primary 2>/dev/null || true)" # zaznaczenie myszką +# bookmark="$(xclip -o -selection clipboard 2>/dev/null || true)" # Ctrl+C + +# usuń końcowe nowe linie (częste przy kopiowaniu) +bookmark="$(printf '%s' "$bookmark" | sed -e 's/[[:space:]]\+$//')" + +if [ -z "$bookmark" ]; then + notify-send "No string highlighted!" "Please select/copy a string to bookmark" + exit 1 +fi + +# literalne dopasowanie całej linii +if grep -Fxq -- "$bookmark" "$file"; then + notify-send "Oops" "Already Bookmarked!" + exit 0 +fi + +# edycja w dmenu: domyślnie pokazuje już wartość +bookmarkEdit="$(printf '%s' "$bookmark" | dmenu -p "Edit bookmark:")" + +# jeśli ESC albo pusto -> użyj oryginału +if [ -z "${bookmarkEdit:-}" ]; then + out="$bookmark" +else + out="$bookmarkEdit" +fi + +# zapisz jako nową linię +printf '%s\n' "$out" >> "$file" +notify-send "Bookmark added!" "$out is now saved to the file" + +# bookmark="$(xclip -o)" +# file="${HOME}/.snippets" +# +# if [ -z "$bookmark" ] +# then +# notify-send "No string highlighted!" "Please select a string to bookmark" +# exit 1 +# else +# if grep -q "^$bookmark$" "$file"; then +# notify-send "Oops" "Already Bookmarked!" +# else +# bookmarkEdit=$(:|dmenu -p "Make changes to the bookmark: $bookmark" & sleep 0.1 && xdotool type $bookmark) +# if [ -z "$bookmarkEdit" ] +# then +# notify-send "Bookmark added!" "$bookmark is now saved to the file" +# echo "$bookmark" >> $file +# else +# notify-send "Bookmark added!" "$bookmarkEdit is now saved to the file" +# echo "$bookmarkEdit" >> "$file" +# fi +# fi +# fi diff --git a/borg-backup.sh b/borg-backup.sh new file mode 100755 index 0000000..b9572f6 --- /dev/null +++ b/borg-backup.sh @@ -0,0 +1,24 @@ +#!/bin/bash +set -euo pipefail + +REPO="borg@192.168.33.5:/tank/backups/borg" +NAME="home-$(date +%F_%H-%M)" +SRC="/home/lukasz" + +export BORG_RSH="ssh -p 57385 -i $HOME/.ssh/sacrum.key -o IdentitiesOnly=yes" + +LOGDIR="$HOME/.local/state/borg" +mkdir -p "$LOGDIR" +LOGFILE="$LOGDIR/backup-$(date +%F).log" + +# Show progress if stdout is a terminal (manual run) +ARGS=(--compression lz4 --exclude-from "$HOME/.config/borg/excludes") +if [ -t 1 ]; then + ARGS+=(--progress --list --stats) +else + ARGS+=(--stats) +fi + +# Always log; show on screen when interactive +borg create "${ARGS[@]}" "$REPO"::"$NAME" "$SRC" 2>&1 | tee -a "$LOGFILE" + diff --git a/bridge-sync b/bridge-sync new file mode 100755 index 0000000..b92f849 --- /dev/null +++ b/bridge-sync @@ -0,0 +1,88 @@ +#!/usr/bin/env bash +set -euo pipefail + +CERT_FILE="${HOME}/.config/isync/proton-bridge.pem" + +BRIDGE_UNIT="protonmail-bridge.service" +IMAP_PORT="1143" + +# These paths match what your ps output shows. +BRIDGE_CHILD_PATH="/usr/lib/protonmail/bridge/bridge" +BRIDGE_LAUNCHER_PATH="/usr/bin/protonmail-bridge" + +CERT_REFRESH_UNIT="proton-bridge-cert-refresh.service" + +port_listening() { + ss -ltn "( sport = :${IMAP_PORT} )" | grep -q ":${IMAP_PORT}" +} + +echo "[0] Preconditions" +command -v ss >/dev/null +command -v mbsync >/dev/null +command -v systemctl >/dev/null + +echo "[1] Stop Bridge (systemd user service)" +systemctl --user stop "${BRIDGE_UNIT}" || true + +echo "[1.1] Kill any remaining processes in unit cgroup" +# If stop didn't fully terminate, this forces remaining processes in the unit's cgroup to die. +systemctl --user kill -s SIGKILL "${BRIDGE_UNIT}" || true + +echo "[1.2] Hard kill Bridge child (the one that tends to linger)" +pkill -u "${USER}" -f "${BRIDGE_CHILD_PATH}" || true + +echo "[1.3] Optional: kill launcher if it lingers" +pkill -u "${USER}" -f "${BRIDGE_LAUNCHER_PATH}" || true + +echo "[1.4] Wait until IMAP port ${IMAP_PORT} is closed" +for i in {1..15}; do + port_listening || break + sleep 1 +done + +if port_listening; then + echo "ERROR: Port ${IMAP_PORT} still open after stop/kill." + echo "Diagnostics:" + ss -ltnp | grep ":${IMAP_PORT}" || true + ps -u "${USER}" -o pid,comm,args | grep -E 'protonmail|/usr/lib/protonmail/bridge/bridge' | grep -v grep || true + exit 1 +fi + +echo "[2] Start Bridge" +systemctl --user start "${BRIDGE_UNIT}" + +echo "[2.1] Wait for Bridge IMAP port ${IMAP_PORT}" +for i in {1..30}; do + port_listening && break + sleep 1 +done + +if ! port_listening; then + echo "ERROR: Bridge did not start (port ${IMAP_PORT} not listening)." + echo "Diagnostics:" + systemctl --user status "${BRIDGE_UNIT}" --no-pager || true + journalctl --user -u "${BRIDGE_UNIT}" -n 80 --no-pager || true + exit 1 +fi + +echo "[3] Refresh cert" +systemctl --user start "${CERT_REFRESH_UNIT}" + +echo "[3.1] Wait for cert: ${CERT_FILE}" +for i in {1..10}; do + [ -s "${CERT_FILE}" ] && break + sleep 1 +done + +if [ ! -s "${CERT_FILE}" ]; then + echo "ERROR: Cert not created at ${CERT_FILE}" + echo "Diagnostics:" + systemctl --user status "${CERT_REFRESH_UNIT}" --no-pager || true + journalctl --user -u "${CERT_REFRESH_UNIT}" -n 80 --no-pager || true + ls -la "$(dirname "${CERT_FILE}")" || true + exit 1 +fi + +echo "[4] Run mbsync" +mbsync -a -V + diff --git a/create-lab b/create-lab new file mode 100755 index 0000000..8640ed6 --- /dev/null +++ b/create-lab @@ -0,0 +1,206 @@ +#!/usr/bin/env bash +set -euo pipefail + +URI="qemu:///system" + +IMGROOT="/var/lib/libvirt/images" +BASEDIR="$IMGROOT/base" +LABDIR="$IMGROOT/lab" + +# Template domains (XML donors) +TEMPLATE_DOMAIN_DEBIAN="debian13-template" +TEMPLATE_DOMAIN_NIXOS="nixos-template" + +# Base qcow2 images (backing files) +TEMPLATE_BASE_DEBIAN="$BASEDIR/debian13-template.qcow2" +TEMPLATE_BASE_NIXOS="$BASEDIR/nixos-base.qcow2" + +usage() { + cat <<'EOF' +Usage: kvm-lab-create <lab-name> [--base template|work01|nixos|/abs/path.qcow2] [--start] + +Examples: + kvm-lab-create lab10 --base template --start + kvm-lab-create lab11 --base work01 --start + kvm-lab-create lab20 --base nixos --start + kvm-lab-create lab12 --base /var/lib/libvirt/images/base/custom.qcow2 --start +EOF +} + +# --- Parse args --- +NAME="${1:-}" +shift || true + +BASESEL="template" +START="no" + +while [[ $# -gt 0 ]]; do + case "$1" in + --base) + BASESEL="${2:-}" + shift 2 + ;; + --start) + START="yes" + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "ERROR: Unknown argument: $1" + usage + exit 1 + ;; + esac +done + +if [[ -z "${NAME:-}" ]]; then + usage + exit 1 +fi + +DISK="${LABDIR}/${NAME}.qcow2" +XML_TMP="$(mktemp "/tmp/${NAME}.xml.XXXXXX")" +cleanup() { rm -f "$XML_TMP"; } +trap cleanup EXIT + +# --- Resolve base selection (qcow2 backing file) --- +resolve_base() { + local sel="$1" + + if [[ "$sel" == "template" ]]; then + echo "$TEMPLATE_BASE_DEBIAN" + return 0 + fi + + if [[ "$sel" == "nixos" ]]; then + echo "$TEMPLATE_BASE_NIXOS" + return 0 + fi + + if [[ "$sel" == "work01" ]]; then + # Pick newest promoted work01 base (must exist first) + local latest + latest="$(ls -1t "$BASEDIR"/work01-base-*.qcow2 2>/dev/null | head -n 1 || true)" + if [[ -z "$latest" ]]; then + echo "ERROR: No work01 base found in $BASEDIR (expected work01-base-*.qcow2)." + echo "Action: run your promote script to create one." + return 1 + fi + echo "$latest" + return 0 + fi + + # Absolute path base + if [[ "$sel" == /* ]]; then + echo "$sel" + return 0 + fi + + echo "ERROR: Invalid --base '$sel' (use template|work01|nixos|/abs/path.qcow2)" + return 1 +} + +# --- Select template domain (XML donor) based on base selection --- +resolve_template_domain() { + local sel="$1" + + if [[ "$sel" == "nixos" ]]; then + echo "$TEMPLATE_DOMAIN_NIXOS" + return 0 + fi + + # If user gave an absolute qcow2, we default to Debian template unless they explicitly chose nixos. + # (You can change this policy later by adding a separate --os flag.) + echo "$TEMPLATE_DOMAIN_DEBIAN" + return 0 +} + +BASE="$(resolve_base "$BASESEL")" +TEMPLATE_DOMAIN="$(resolve_template_domain "$BASESEL")" + +# --- Preconditions (read-only checks) --- +if [[ ! -d "$LABDIR" ]]; then + echo "ERROR: Lab directory missing: $LABDIR" + exit 1 +fi + +if ! sudo test -r "$BASE"; then + echo "ERROR: Base image not readable (via sudo): $BASE" + exit 1 +fi + +if [[ -e "$DISK" ]]; then + echo "ERROR: Disk already exists: $DISK" + exit 1 +fi + +if virsh -c "$URI" dominfo "$NAME" &>/dev/null; then + echo "ERROR: Domain already exists: $NAME" + exit 1 +fi + +if ! virsh -c "$URI" dominfo "$TEMPLATE_DOMAIN" &>/dev/null; then + echo "ERROR: Template domain not found in $URI: $TEMPLATE_DOMAIN" + echo "Action:" + if [[ "$TEMPLATE_DOMAIN" == "$TEMPLATE_DOMAIN_NIXOS" ]]; then + echo " - Define a nixos-template domain (UEFI/OVMF) and keep it powered off." + else + echo " - Ensure debian13-template exists." + fi + exit 1 +fi + +# --- Create overlay disk --- +echo "Using template domain: $TEMPLATE_DOMAIN" +echo "Using base: $BASE" +echo "Creating overlay disk: $DISK" +sudo qemu-img create -f qcow2 -F qcow2 -b "$BASE" "$DISK" >/dev/null +sudo chown libvirt-qemu:libvirt "$DISK" +sudo chmod 0660 "$DISK" + +echo "Validating backing file..." +sudo qemu-img info "$DISK" | grep -E "file format:|virtual size:|backing file:" || true + +# --- Clone domain XML --- +echo "Cloning domain XML from: $TEMPLATE_DOMAIN" +virsh -c "$URI" dumpxml "$TEMPLATE_DOMAIN" > "$XML_TMP" + +# Detect original disk path inside the template XML and replace it robustly +ORIG_DISK_PATH="$(grep -oP "(?<=<source file=')[^']+" "$XML_TMP" | head -n 1 || true)" +if [[ -z "$ORIG_DISK_PATH" ]]; then + echo "ERROR: Could not find <source file='...'> disk path in template XML ($TEMPLATE_DOMAIN)." + echo "Action: ensure the template VM uses a file-backed qcow2 disk." + exit 1 +fi + +# Set name, disk path, and force NAT-only network "default" +sed -i \ + -e "s|<name>${TEMPLATE_DOMAIN}</name>|<name>${NAME}</name>|" \ + -e "s|${ORIG_DISK_PATH}|${DISK}|g" \ + -e "s|<source network='[^']*'|<source network='default'|g" \ + "$XML_TMP" + +# Remove UUID to avoid collision with template +sed -i -E '/<uuid>[^<]*<\/uuid>/d' "$XML_TMP" + +# Remove any host filesystem passthrough (if present) +perl -0777 -i -pe 's/<filesystem[\s\S]*?<\/filesystem>\n?//g' "$XML_TMP" + +# --- Define domain --- +echo "Defining domain: $NAME" +virsh -c "$URI" define "$XML_TMP" >/dev/null + +# --- Optional start --- +if [[ "$START" == "yes" ]]; then + echo "Starting domain: $NAME" + virsh -c "$URI" start "$NAME" >/dev/null +fi + +echo "OK: Lab '$NAME' created (disk: $DISK)." +if [[ "$START" != "yes" ]]; then + echo "To start: virsh -c $URI start $NAME" +fi + diff --git a/dwm-status.sh b/dwm-status.sh index b41dc5c..7674482 100755 --- a/dwm-status.sh +++ b/dwm-status.sh @@ -1,7 +1,7 @@ #!/bin/sh while true; do - datetime="$(date '+%a %m-%d %H:%M')" + datetime="$(date '+%d %a %H:%M')" # Volume if command -v pamixer >/dev/null 2>&1; then @@ -0,0 +1,4 @@ +#!/bin/sh +exec firejail --profile=/etc/firejail/firefox-esr.profile \ + /usr/bin/firefox-esr --no-remote "$@" + diff --git a/firefox-raw b/firefox-raw new file mode 100755 index 0000000..e29362a --- /dev/null +++ b/firefox-raw @@ -0,0 +1,2 @@ +!/bin/sh +exec /usr/bin/firefox-esr --no-remote "$@" diff --git a/kvm-lab-create b/kvm-lab-create new file mode 100755 index 0000000..43d2926 --- /dev/null +++ b/kvm-lab-create @@ -0,0 +1,160 @@ +#!/usr/bin/env bash +set -euo pipefail + +URI="qemu:///system" + +IMGROOT="/var/lib/libvirt/images" +BASEDIR="$IMGROOT/base" +LABDIR="$IMGROOT/lab" + +TEMPLATE_DOMAIN="debian13-template" +TEMPLATE_BASE="$BASEDIR/debian13-template.qcow2" + +usage() { + echo "Usage: kvm-lab-create <lab-name> [--base template|work01|/abs/path.qcow2] [--start]" + echo "Examples:" + echo " kvm-lab-create lab10 --base template --start" + echo " kvm-lab-create lab11 --base work01 --start" + echo " kvm-lab-create lab12 --base /var/lib/libvirt/images/base/custom.qcow2 --start" +} + +# --- Parse args --- +NAME="${1:-}" +shift || true + +BASESEL="template" +START="no" + +while [[ $# -gt 0 ]]; do + case "$1" in + --base) + BASESEL="${2:-}" + shift 2 + ;; + --start) + START="yes" + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "ERROR: Unknown argument: $1" + usage + exit 1 + ;; + esac +done + +if [[ -z "$NAME" ]]; then + usage + exit 1 +fi + +DISK="${LABDIR}/${NAME}.qcow2" +XML_TMP="$(mktemp "/tmp/${NAME}.xml.XXXXXX")" +cleanup() { rm -f "$XML_TMP"; } +trap cleanup EXIT + +# --- Resolve base selection --- +resolve_base() { + local sel="$1" + if [[ "$sel" == "template" ]]; then + echo "$TEMPLATE_BASE" + return 0 + fi + + if [[ "$sel" == "work01" ]]; then + # Pick newest promoted work01 base (must exist first) + local latest + latest="$(ls -1t "$BASEDIR"/work01-base-*.qcow2 2>/dev/null | head -n 1 || true)" + if [[ -z "$latest" ]]; then + echo "ERROR: No work01 base found in $BASEDIR (expected work01-base-*.qcow2)." + echo "Action: run your promote script to create one." + return 1 + fi + echo "$latest" + return 0 + fi + + # Absolute path base + if [[ "$sel" == /* ]]; then + echo "$sel" + return 0 + fi + + echo "ERROR: Invalid --base '$sel' (use template|work01|/abs/path.qcow2)" + return 1 +} + +BASE="$(resolve_base "$BASESEL")" + +# --- Preconditions (read-only checks) --- +if [[ ! -d "$LABDIR" ]]; then + echo "ERROR: Lab directory missing: $LABDIR" + exit 1 +fi + +if ! sudo test -r "$BASE"; then + echo "ERROR: Base image not readable (via sudo): $BASE" + exit 1 +fi + +if [[ -e "$DISK" ]]; then + echo "ERROR: Disk already exists: $DISK" + exit 1 +fi + +if virsh -c "$URI" dominfo "$NAME" &>/dev/null; then + echo "ERROR: Domain already exists: $NAME" + exit 1 +fi + +if ! virsh -c "$URI" dominfo "$TEMPLATE_DOMAIN" &>/dev/null; then + echo "ERROR: Template domain not found in $URI: $TEMPLATE_DOMAIN" + exit 1 +fi + +# --- Create overlay disk --- +echo "Using base: $BASE" +echo "Creating overlay disk: $DISK" +sudo qemu-img create -f qcow2 -F qcow2 -b "$BASE" "$DISK" >/dev/null +sudo chown libvirt-qemu:libvirt "$DISK" +sudo chmod 0660 "$DISK" + +echo "Validating backing file..." +sudo qemu-img info "$DISK" | grep -E "file format:|virtual size:|backing file:" || true + +# --- Clone domain XML --- +echo "Cloning domain XML from: $TEMPLATE_DOMAIN" +virsh -c "$URI" dumpxml "$TEMPLATE_DOMAIN" > "$XML_TMP" + +# Set name, disk path, and force NAT-only network "default" +sed -i \ + -e "s|<name>${TEMPLATE_DOMAIN}</name>|<name>${NAME}</name>|" \ + -e "s|${TEMPLATE_BASE}|${DISK}|g" \ + -e "s|<source network='[^']*'|<source network='default'|g" \ + "$XML_TMP" + +# Remove UUID to avoid collision with template +sed -i -E '/<uuid>[^<]*<\/uuid>/d' "$XML_TMP" + +# Remove any host filesystem passthrough (if present) +perl -0777 -i -pe 's/<filesystem[\s\S]*?<\/filesystem>\n?//g' "$XML_TMP" + +# --- Define domain --- +echo "Defining domain: $NAME" +virsh -c "$URI" define "$XML_TMP" >/dev/null + +# --- Optional start --- +if [[ "$START" == "yes" ]]; then + echo "Starting domain: $NAME" + virsh -c "$URI" start "$NAME" >/dev/null +fi + +echo "OK: Lab '$NAME' created (disk: $DISK)." +if [[ "$START" != "yes" ]]; then + echo "To start: virsh -c $URI start $NAME" +fi + diff --git a/kvm-lab-create.bak b/kvm-lab-create.bak new file mode 100755 index 0000000..43d2926 --- /dev/null +++ b/kvm-lab-create.bak @@ -0,0 +1,160 @@ +#!/usr/bin/env bash +set -euo pipefail + +URI="qemu:///system" + +IMGROOT="/var/lib/libvirt/images" +BASEDIR="$IMGROOT/base" +LABDIR="$IMGROOT/lab" + +TEMPLATE_DOMAIN="debian13-template" +TEMPLATE_BASE="$BASEDIR/debian13-template.qcow2" + +usage() { + echo "Usage: kvm-lab-create <lab-name> [--base template|work01|/abs/path.qcow2] [--start]" + echo "Examples:" + echo " kvm-lab-create lab10 --base template --start" + echo " kvm-lab-create lab11 --base work01 --start" + echo " kvm-lab-create lab12 --base /var/lib/libvirt/images/base/custom.qcow2 --start" +} + +# --- Parse args --- +NAME="${1:-}" +shift || true + +BASESEL="template" +START="no" + +while [[ $# -gt 0 ]]; do + case "$1" in + --base) + BASESEL="${2:-}" + shift 2 + ;; + --start) + START="yes" + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "ERROR: Unknown argument: $1" + usage + exit 1 + ;; + esac +done + +if [[ -z "$NAME" ]]; then + usage + exit 1 +fi + +DISK="${LABDIR}/${NAME}.qcow2" +XML_TMP="$(mktemp "/tmp/${NAME}.xml.XXXXXX")" +cleanup() { rm -f "$XML_TMP"; } +trap cleanup EXIT + +# --- Resolve base selection --- +resolve_base() { + local sel="$1" + if [[ "$sel" == "template" ]]; then + echo "$TEMPLATE_BASE" + return 0 + fi + + if [[ "$sel" == "work01" ]]; then + # Pick newest promoted work01 base (must exist first) + local latest + latest="$(ls -1t "$BASEDIR"/work01-base-*.qcow2 2>/dev/null | head -n 1 || true)" + if [[ -z "$latest" ]]; then + echo "ERROR: No work01 base found in $BASEDIR (expected work01-base-*.qcow2)." + echo "Action: run your promote script to create one." + return 1 + fi + echo "$latest" + return 0 + fi + + # Absolute path base + if [[ "$sel" == /* ]]; then + echo "$sel" + return 0 + fi + + echo "ERROR: Invalid --base '$sel' (use template|work01|/abs/path.qcow2)" + return 1 +} + +BASE="$(resolve_base "$BASESEL")" + +# --- Preconditions (read-only checks) --- +if [[ ! -d "$LABDIR" ]]; then + echo "ERROR: Lab directory missing: $LABDIR" + exit 1 +fi + +if ! sudo test -r "$BASE"; then + echo "ERROR: Base image not readable (via sudo): $BASE" + exit 1 +fi + +if [[ -e "$DISK" ]]; then + echo "ERROR: Disk already exists: $DISK" + exit 1 +fi + +if virsh -c "$URI" dominfo "$NAME" &>/dev/null; then + echo "ERROR: Domain already exists: $NAME" + exit 1 +fi + +if ! virsh -c "$URI" dominfo "$TEMPLATE_DOMAIN" &>/dev/null; then + echo "ERROR: Template domain not found in $URI: $TEMPLATE_DOMAIN" + exit 1 +fi + +# --- Create overlay disk --- +echo "Using base: $BASE" +echo "Creating overlay disk: $DISK" +sudo qemu-img create -f qcow2 -F qcow2 -b "$BASE" "$DISK" >/dev/null +sudo chown libvirt-qemu:libvirt "$DISK" +sudo chmod 0660 "$DISK" + +echo "Validating backing file..." +sudo qemu-img info "$DISK" | grep -E "file format:|virtual size:|backing file:" || true + +# --- Clone domain XML --- +echo "Cloning domain XML from: $TEMPLATE_DOMAIN" +virsh -c "$URI" dumpxml "$TEMPLATE_DOMAIN" > "$XML_TMP" + +# Set name, disk path, and force NAT-only network "default" +sed -i \ + -e "s|<name>${TEMPLATE_DOMAIN}</name>|<name>${NAME}</name>|" \ + -e "s|${TEMPLATE_BASE}|${DISK}|g" \ + -e "s|<source network='[^']*'|<source network='default'|g" \ + "$XML_TMP" + +# Remove UUID to avoid collision with template +sed -i -E '/<uuid>[^<]*<\/uuid>/d' "$XML_TMP" + +# Remove any host filesystem passthrough (if present) +perl -0777 -i -pe 's/<filesystem[\s\S]*?<\/filesystem>\n?//g' "$XML_TMP" + +# --- Define domain --- +echo "Defining domain: $NAME" +virsh -c "$URI" define "$XML_TMP" >/dev/null + +# --- Optional start --- +if [[ "$START" == "yes" ]]; then + echo "Starting domain: $NAME" + virsh -c "$URI" start "$NAME" >/dev/null +fi + +echo "OK: Lab '$NAME' created (disk: $DISK)." +if [[ "$START" != "yes" ]]; then + echo "To start: virsh -c $URI start $NAME" +fi + diff --git a/kvm-lab-destroy b/kvm-lab-destroy new file mode 100755 index 0000000..3548ef9 --- /dev/null +++ b/kvm-lab-destroy @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +set -euo pipefail + +URI="qemu:///system" +LABDIR="/var/lib/libvirt/images/lab" + +NAME="${1:-}" + +if [[ -z "$NAME" ]]; then + echo "Usage: kvm-lab-destroy <lab-name>" + exit 1 +fi + +if [[ "$NAME" == "work01" || "$NAME" == "debian13-template" ]]; then + echo "Refusing to destroy protected VM: $NAME" + exit 1 +fi + +if ! virsh -c "$URI" dominfo "$NAME" &>/dev/null; then + echo "Domain not found: $NAME" + exit 1 +fi + +DISK="$(virsh -c "$URI" domblklist "$NAME" --details | awk '$3=="vda"{print $4}')" + +if [[ -z "$DISK" || "$DISK" != "$LABDIR/"* ]]; then + echo "Refusing to destroy: disk not under $LABDIR" + echo "Detected disk: $DISK" + exit 1 +fi + +STATE="$(virsh -c "$URI" domstate "$NAME")" + +if [[ "$STATE" == "running" ]]; then + echo "Shutting down $NAME..." + virsh -c "$URI" shutdown "$NAME" + sleep 3 +fi + +if [[ "$(virsh -c "$URI" domstate "$NAME")" == "running" ]]; then + echo "Force stopping $NAME..." + virsh -c "$URI" destroy "$NAME" +fi + +echo "Undefining $NAME..." +virsh -c "$URI" undefine "$NAME" + +echo "Deleting disk: $DISK" +sudo rm -v -- "$DISK" + +echo "Lab '$NAME' destroyed." + diff --git a/kvm-lab-status b/kvm-lab-status new file mode 100755 index 0000000..2303995 --- /dev/null +++ b/kvm-lab-status @@ -0,0 +1,67 @@ +#!/usr/bin/env bash +set -euo pipefail + +URI="qemu:///system" +IMGROOT="/var/lib/libvirt/images" +BASEDIR="$IMGROOT/base" +LABDIR="$IMGROOT/lab" + +hr() { printf '%*s\n' "${COLUMNS:-80}" '' | tr ' ' '-'; } + +echo "KVM LAB STATUS" +hr +echo "Time: $(date -Is)" +echo "Host: $(hostname)" +echo + +echo "Libvirt domains ($URI):" +hr +virsh -c "$URI" list --all || true +echo + +echo "Lab VMs (names starting with 'lab'):" +hr +virsh -c "$URI" list --all | awk 'NR==1||NR==2{print;next} $2 ~ /^lab/ {print}' || true +echo + +echo "Lab disk inventory ($LABDIR):" +hr +if [[ -d "$LABDIR" ]]; then + sudo ls -lh --time-style=long-iso "$LABDIR" 2>/dev/null || ls -lh "$LABDIR" +else + echo "Missing: $LABDIR" +fi +echo + +echo "Base images ($BASEDIR):" +hr +if [[ -d "$BASEDIR" ]]; then + ls -lh --time-style=long-iso "$BASEDIR" 2>/dev/null || ls -lh "$BASEDIR" +else + echo "Missing: $BASEDIR" +fi +echo + +echo "Selected base candidates:" +hr +TEMPLATE="$BASEDIR/debian13-template.qcow2" +LATEST_WORK01="$(ls -1t "$BASEDIR"/work01-base-*.qcow2 2>/dev/null | head -n 1 || true)" + +if [[ -f "$TEMPLATE" ]]; then + echo "template: $TEMPLATE" +else + echo "template: MISSING ($TEMPLATE)" +fi + +if [[ -n "$LATEST_WORK01" ]]; then + echo "work01 (latest): $LATEST_WORK01" +else + echo "work01 (latest): none found ($BASEDIR/work01-base-*.qcow2)" +fi +echo + +echo "Libvirt networks:" +hr +virsh -c "$URI" net-list --all || true +echo + diff --git a/kvm-promote-to-base b/kvm-promote-to-base new file mode 100755 index 0000000..93e703e --- /dev/null +++ b/kvm-promote-to-base @@ -0,0 +1,87 @@ +#!/usr/bin/env bash +set -euo pipefail + +URI="qemu:///system" +IMGROOT="/var/lib/libvirt/images" +BASEDIR="$IMGROOT/base" +SNAPROOT="$HOME/.backup/kvm-labs" + +usage() { + echo "Usage: kvm-promote-to-base <vm-name> [--disk vda]" + echo "Creates: $BASEDIR/<vm-name>-base-YYYY-MM-DD_HHMMSS.qcow2" + exit 1 +} + +VM="${1:-}" +DISKTGT="vda" + +shift || true +while [[ $# -gt 0 ]]; do + case "$1" in + --disk) + DISKTGT="${2:-}" + shift 2 + ;; + -h|--help) + usage + ;; + *) + echo "ERROR: Unknown arg: $1" + usage + ;; + esac +done + +if [[ -z "$VM" ]]; then + usage +fi + +if [[ ! -d "$BASEDIR" ]]; then + echo "ERROR: Base directory missing: $BASEDIR" + exit 1 +fi + +state="$(virsh -c "$URI" domstate "$VM" 2>/dev/null | tr -d '\r' || true)" +if [[ "$state" != "shut off" ]]; then + echo "ERROR: VM must be shut off for consistent promotion." + echo "VM: $VM" + echo "State: ${state:-unknown}" + exit 1 +fi + +DISK="$(virsh -c "$URI" domblklist "$VM" --details | awk -v t="$DISKTGT" '$3==t{print $4}')" +if [[ -z "$DISK" || ! -f "$DISK" ]]; then + echo "ERROR: Cannot locate disk for $VM target=$DISKTGT" + echo "Detected: '$DISK'" + exit 1 +fi + +TS="$(date +%F_%H%M%S)" +SNAPDIR="$SNAPROOT/${TS}-promote-${VM}" +NEWBASE="$BASEDIR/${VM}-base-${TS}.qcow2" + +mkdir -p "$SNAPDIR" + +# Snapshot metadata (read-only) +virsh -c "$URI" dumpxml "$VM" > "$SNAPDIR/${VM}.xml" +sudo qemu-img info --backing-chain "$DISK" > "$SNAPDIR/${VM}.${DISKTGT}.backing-chain.txt" || true +sudo ls -l "$DISK" > "$SNAPDIR/${VM}.${DISKTGT}.ls.txt" || true + +echo "Creating base image:" +echo " VM: $VM" +echo " Disk: $DISK (target: $DISKTGT)" +echo " Target: $NEWBASE" + +sudo qemu-img convert -O qcow2 "$DISK" "$NEWBASE" +sudo chown libvirt-qemu:libvirt "$NEWBASE" +sudo chmod 0660 "$NEWBASE" + +echo "Verifying new base has no backing file..." +if sudo qemu-img info "$NEWBASE" | grep -q '^backing file:'; then + echo "ERROR: New base unexpectedly has a backing file. Aborting." + exit 1 +fi + +echo "SUCCESS: $NEWBASE" +echo "Snapshot: $SNAPDIR" + diff --git a/lib/bashsimplecurses b/lib/bashsimplecurses new file mode 160000 +Subproject 006b9cafd4d381b693c5e4f55140f2834cf3087 diff --git a/luks-shield b/luks-shield new file mode 100755 index 0000000..9080f35 --- /dev/null +++ b/luks-shield @@ -0,0 +1,142 @@ +#!/usr/bin/env bash +set -euo pipefail + +UUID="6659e638-d52a-4c32-9781-9dcedd44db35" +NAME="shield" +MNT="/mnt/shield" + +DEV_BY_UUID="/dev/disk/by-uuid/${UUID}" +MAPPER="/dev/mapper/${NAME}" + +die() { echo "Error: $*" >&2; exit 1; } + +need_cmd() { + command -v "$1" >/dev/null 2>&1 || die "Missing command: $1" +} + +resolve_blockdev() { + # Resolve the UUID symlink to an absolute /dev/... node + local target + target="$(readlink -f "$DEV_BY_UUID")" || return 1 + [[ -b "$target" ]] || return 1 + echo "$target" +} + +is_mapped() { + [[ -e "$MAPPER" ]] +} + +is_mounted() { + mountpoint -q "$MNT" +} + +show_busy() { + echo "Mount appears busy. Processes using $MNT:" + # lsof is optional but useful; show what we can. + if command -v lsof >/dev/null 2>&1; then + sudo lsof +f -- "$MNT" || true + else + echo "(Install lsof: sudo apt install lsof)" + sudo fuser -vm "$MNT" || true + fi +} + +do_open() { + need_cmd cryptsetup + need_cmd mount + need_cmd mountpoint + need_cmd readlink + + [[ -e "$DEV_BY_UUID" ]] || die "UUID device not found: $DEV_BY_UUID (is the drive plugged in?)" + + local dev + dev="$(resolve_blockdev)" || die "Could not resolve block device for UUID: $UUID" + + if is_mapped; then + echo "Mapper already exists: $MAPPER" + else + sudo cryptsetup open "$dev" "$NAME" + fi + + if [[ ! -d "$MNT" ]]; then + sudo mkdir -p "$MNT" + fi + + if is_mounted; then + echo "Already mounted: $MNT" + else + sudo mount "$MAPPER" "$MNT" + fi + + sudo sync + echo "OK: unlocked and mounted at $MNT" +} + +do_close() { + need_cmd cryptsetup + need_cmd umount + need_cmd mountpoint + need_cmd readlink + + if is_mounted; then + sudo sync + if ! sudo umount "$MNT"; then + show_busy + die "Unmount failed" + fi + fi + + if is_mapped; then + sudo cryptsetup close "$NAME" + fi + + # Best-effort power-off + if [[ -e "$DEV_BY_UUID" ]]; then + local dev + dev="$(resolve_blockdev)" || true + if [[ -n "${dev:-}" ]]; then + if command -v udisksctl >/dev/null 2>&1; then + sudo udisksctl power-off -b "$dev" || true + fi + fi + fi + + echo "OK: unmounted, closed, and powered off (if supported)" +} + +do_status() { + echo "UUID device: $DEV_BY_UUID" + if [[ -e "$DEV_BY_UUID" ]]; then + echo "Resolved: $(readlink -f "$DEV_BY_UUID")" + else + echo "Resolved: (not present)" + fi + + echo -n "Mapper: " + if is_mapped; then echo "OPEN ($MAPPER)"; else echo "CLOSED"; fi + + echo -n "Mount: " + if is_mounted; then echo "MOUNTED ($MNT)"; else echo "NOT MOUNTED"; fi +} + +usage() { + cat <<EOF +Usage: $0 {open|close|status} + +open - unlock LUKS by UUID and mount to $MNT +close - sync, unmount, close mapper, power-off block device (best-effort) +status - show current state +EOF +} + +main() { + case "${1:-}" in + open) do_open ;; + close) do_close ;; + status) do_status ;; + *) usage; exit 2 ;; + esac +} + +main "$@" + diff --git a/nextcloud b/nextcloud new file mode 100644 index 0000000..fae9bdb --- /dev/null +++ b/nextcloud @@ -0,0 +1 @@ +aHR0cHM6Ly9jbG91ZC5sYWJ1bml4Lnh5ejo5NDQzCmxvZ2luOiBhZGFtCm1haWw6IGFkYW0uZ29tdWxrYTk0QGdtYWlsLmNvbSAKaGFzxYJvOiAgYk9PP2BAZD15S1Q5XmNWYAoKdXN0YXcgc29iaWUgMkZBIHBvdGVtIGRsYSBiZXpwaWVjemXFhHN0d2E= diff --git a/officium b/officium new file mode 100755 index 0000000..fae202f --- /dev/null +++ b/officium @@ -0,0 +1,214 @@ +#!/bin/bash + +# Scriptum ad preces e Divinum Officium extrahendas +# Auctor: Adam Kasprzak +# Data: 2026-01-13 + +set -o pipefail + +if [ $# -eq 0 ]; then + echo "Usus: $0 [laudes|prima|tertia|sexta|nona|vesperae]" + exit 1 +fi + +HORA_INPUT="$1" +HORA="$(echo "${HORA_INPUT:0:1}" | tr '[:lower:]' '[:upper:]')$(echo "${HORA_INPUT:1}" | tr '[:upper:]' '[:lower:]')" + +case "$HORA" in + Laudes|Prima|Tertia|Sexta|Nona|Vesperae) ;; + *) + echo "Error: Argumentum invalidum: '$HORA_INPUT'" + echo "Valida: laudes, prima, tertia, sexta, nona, vesperae" + exit 1 + ;; +esac + +BASE_URL="https://www.divinumofficium.com/cgi-bin/horas/officium.pl" +URL="${BASE_URL}?command=pray${HORA}" + +USE_COLOR=1 +if [ ! -t 1 ] || [ "${NO_COLOR:-0}" = "1" ]; then + USE_COLOR=0 +fi + +WRAP="${WRAP:-80}" # word-wrap width (no justify) + +echo +echo +echo " ?8888P" +echo " \`88'" +echo " 8b,_ 88 _,d8" +echo " 88888SEAL88888" +echo " 8P~ 88 ~?8" +echo " ,88." +echo " d8888b" + +echo "+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-" +echo "Hora: $HORA" +echo "+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-" +echo + +HTML="$(curl -s "$URL")" +if [ $? -ne 0 ] || [ -z "$HTML" ]; then + echo "Error: Pagina recipi non potuit (curl error vel responsum vacuum)." + exit 1 +fi + +echo "$HTML" | awk ' + /<FONT SIZE=.+1. COLOR="red"><B><I>Incipit<\/I><\/B><\/FONT>/ { printing=1 } + printing { + if (/<TD VALIGN=.TOP. WIDTH=.50%.><DIV ALIGN=.right.><FONT SIZE=.1. COLOR=.green.>[0-9]+/) in_english=1 + if (/<\/TR>/) { in_english=0; print "" } + if (!in_english) print + } + /<\/TABLE>/ && printing { exit } +' | \ + sed 's/<[^>]*>//g' | \ + sed -E 's/^[0-9]+:[0-9]+[[:space:]]*//' | \ + sed 's/ / /g' | \ + sed 's/ / /g' | \ + sed 's/</</g' | \ + sed 's/>/>/g' | \ + sed 's/&/\&/g' | \ + sed 's/&#[0-9]*;//g' | \ + # remove parentheses ranges after Psalm number: Psalmus 54(17-24) -> Psalmus 54 + sed -E 's/^(Psalmus[[:space:]]+[0-9]+)\([^)]*\)/\1/' | \ + cat -s | \ + grep -v "^Top" | \ + grep -v "^Next" | \ + grep -v "^[0-9]$" | \ + # blank lines between antiphon and psalm title; and before closing antiphon after V/. R/. + awk ' + function is_ant(s) { return (s ~ /^Ant\./) } + function is_psalm(s) { return (s ~ /^Psalmus[[:space:]]+[0-9]+/) } + function is_vr(s) { return (s ~ /^[VR]\.\//) } + + { + cur = $0 + if (is_psalm(cur) && is_ant(prev) && prev != "") { + print prev + print "" + print cur + prev="" + next + } + if (is_ant(cur) && is_vr(prev) && prev != "") { + print prev + print "" + print cur + prev="" + next + } + if (prev != "") print prev + prev = cur + } + END { if (prev != "") print prev } + ' | \ + # word-wrap (no justify), preserve indentation + awk -v wrap="$WRAP" ' + function ltrim(s){ sub(/^[[:space:]]+/, "", s); return s } + function rtrim(s){ sub(/[[:space:]]+$/, "", s); return s } + function trim(s){ return rtrim(ltrim(s)) } + + function wrap_line(indent, text, limit, n,i,words,line,tmp){ + text = trim(text) + if (text == "") { print ""; return } + gsub(/[[:space:]]+/, " ", text) + + n = split(text, words, " ") + line = "" + for (i=1; i<=n; i++){ + tmp = (line == "" ? words[i] : line " " words[i]) + if (length(tmp) <= limit) { + line = tmp + } else { + print indent line + line = words[i] + } + } + if (line != "") print indent line + } + + { + raw = $0 + if (raw ~ /^[[:space:]]*$/) { print ""; next } + + # keep existing indentation + indent = "" + match(raw, /^[[:space:]]*/) + indent = substr(raw, RSTART, RLENGTH) + text = substr(raw, RLENGTH+1) + + # wrap only if needed + if (length(raw) > wrap) { + lim = wrap - length(indent) + if (lim < 20) lim = 20 + wrap_line(indent, text, lim) + } else { + print raw + } + } + ' | \ + # pretty print (colors + indentation unchanged; grey for [] and {}) + awk -v use_color="$USE_COLOR" ' + BEGIN{ + reset="\033[0m"; bold="\033[1m"; dim="\033[2m" + red="\033[31m"; green="\033[32m"; yellow="\033[33m" + blue="\033[34m"; mag="\033[35m"; cyan="\033[36m" + gray="\033[90m" + if (!use_color) { reset=bold=dim=red=green=yellow=blue=mag=cyan=gray="" } + } + + function trim(s){ gsub(/^[[:space:]]+|[[:space:]]+$/, "", s); return s } + + function gray_blocks(s, cur, rep){ + rep = gray "&" cur + gsub(/\[[^]]+\]/, rep, s) + gsub(/\{[^}]*\}/, rep, s) + return s + } + + function blank_before_if_needed(t){ + if (t ~ /^(Oremus\.?|Conclusio)$/ && prev_blank == 0) print "" + } + + { + raw = $0 + + if (raw ~ /^[[:space:]]*$/) { print ""; prev_blank=1; next } + + t = trim(raw) + blank_before_if_needed(t) + prev_blank=0 + + prefix="" + + # headings in RED + if (t == "Incipit" || t == "Hymnus") { + prefix = bold red + } else if (t ~ /^Psalmus[[:space:]]+[0-9]+/) { + prefix = bold red + } else if (t ~ /^Psalmi[[:space:]]*\{/) { + prefix = dim gray + } else if (t == "Conclusio") { + prefix = bold red + } else if (t ~ /^Ant\./) { + prefix = mag + } else if (t ~ /^[VR]\.\//) { + prefix = green + } else if (t ~ /^Gl(ó|o)ria Patri,/) { + prefix = blue + } else if (t ~ /^Sicut erat/) { + prefix = blue + } + + line = gray_blocks(raw, prefix) + print prefix line reset + } + ' + +echo +echo "+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-" +echo "Finis Horae: $HORA" +echo "+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-" + diff --git a/officium_readme.md b/officium_readme.md new file mode 100644 index 0000000..b4e00a5 --- /dev/null +++ b/officium_readme.md @@ -0,0 +1,135 @@ +# Log Projektu: Skrypt Bash do Divinumofficium.com + +## Zapytanie użytkownika: +Utworzyć skrypt bash który: +- Przyjmuje jeden argument ze zbioru: [Prima, Tertia, Sexta, Nona, Vesperae] +- Pobiera dane ze strony https://www.divinumofficium.com/cgi-bin/horas/officium.pl +- Znajduje linki ukryte pod hover dla wybranej modlitwy (Laudes, Prima, Tertia, Sexta, Nona, Vesperae) +- Pobiera tekst od części "Incipit" aż do końca części "Conclusio" +- Wyświetla zawartość w terminalu + +## Krok 1: Analiza struktury strony +Data: 2026-01-13 22:12 + +Pobieram główną stronę aby zrozumieć strukturę HTML i znaleźć linki do poszczególnych modlitw. + +### Wyniki analizy: +- Strona używa parametru `command=pray{NazwaModlitwy}` w URL +- Przykład: `officium.pl?command=prayPrima` +- Treść modlitwy znajduje się w tabeli HTML +- Sekcja "Incipit" to początek (oznaczona znacznikiem `<FONT SIZE='+1' COLOR="red"><B><I>Incipit</I></B></FONT>`) +- Sekcja "Conclusio" to koniec (analogicznie oznaczona) +- Treść jest w formacie HTML z tagami FONT, BR, itd. + +## Krok 2: Tworzenie skryptu bash +Data: 2026-01-13 22:12 + +Tworzę skrypt który: +1. Waliduje argument wejściowy +2. Buduje URL z odpowiednim parametrem command +3. Pobiera stronę za pomocą curl +4. Ekstrahuje treść od "Incipit" do końca "Conclusio" +5. Czyści HTML i formatuje dla terminala + +### Pierwsze podejście: +- Utworzono plik `divinumofficium.sh` +- Użyto sed do ekstrakcji, ale nie działało prawidłowo + +### Poprawki: +- Zmieniono na awk, który czyta od Incipit do zamknięcia </TABLE> +- Dodano filtry do usunięcia linków nawigacyjnych (Top, Next) +- Dodano dekodowanie encji HTML + +## Krok 3: Testowanie skryptu +Data: 2026-01-13 22:12 + +### Testy przeprowadzone: +1. ✅ `./divinumofficium.sh Prima` - działa poprawnie +2. ✅ `./divinumofficium.sh Vesperae` - działa poprawnie +3. ✅ `./divinumofficium.sh Sexta` - działa poprawnie +4. ✅ Brak argumentu - wyświetla instrukcję użycia +5. ✅ Nieprawidłowy argument (Laudes) - wyświetla błąd + +### Wnioski: +Skrypt działa zgodnie z wymaganiami: +- Przyjmuje argumenty: Prima, Tertia, Sexta, Nona, Vesperae +- Pobiera treść od "Incipit" do końca "Conclusio" +- Wyświetla czytelnie w terminalu (z tekstem łacińskim i angielskim) +- Waliduje dane wejściowe +- Obsługuje błędy połączenia + +## Podsumowanie +Data: 2026-01-13 22:13 + +Utworzono działający skrypt bash `divinumofficium.sh`, który: +- Lokalizacja: `/home/adam/copilot_workspace/kasprzak_project/divinumofficium.sh` +- Rozmiar: ~1.8KB +- Uprawnienia: wykonywalne (chmod +x) +- Status: UKOŃCZONE ✅ + +## Krok 4: Modyfikacja obsługi argumentów +Data: 2026-01-13 22:17 + +### Zapytanie użytkownika: +"popraw aby argumenty pisać małymi literami" + +### Zmiany wprowadzone: +- Argumenty przyjmowane są teraz małymi literami: prima, tertia, sexta, nona, vesperae +- Dodano automatyczną konwersję: pierwsza litera na wielką (bo API wymaga Prima, Tertia, etc.) +- Zaktualizowano komunikaty pomocy i błędów +- Zachowano zgodność z API (nadal wysyła "Prima", "Tertia", etc.) + +### Test: +Teraz użytkownik może pisać: `./divinumofficium.sh prima` zamiast `./divinumofficium.sh Prima` + +## Krok 5: Filtrowanie tylko tekstu łacińskiego +Data: 2026-01-13 22:22 + +### Zapytanie użytkownika: +"wynik jest po łacinie i angielsku. Ma być tylko część po łacinie" + +### Analiza problemu: +Strona HTML zawiera tabelę z dwiema kolumnami: +- Pierwsza kolumna (`<TD WIDTH='50%'>`): tekst łaciński +- Druga kolumna (`<TD WIDTH='50%'>`): tekst angielski + +### Zmiany wprowadzone: +- Zmodyfikowano skrypt awk aby pomijać drugą kolumnę (angielską) +- Dodano wykrywanie początku drugiej kolumny (po numerze sekcji) +- Dodano filtr usuwający pojedyncze cyfry (numery sekcji) +- Zachowano tylko tekst łaciński w wyniku + +### Test: +```bash +./divinumofficium.sh prima # Wyświetla tylko tekst łaciński +./divinumofficium.sh vesperae # Wyświetla tylko tekst łaciński +``` + +Wynik zawiera teraz wyłącznie modlitwy w języku łacińskim. + +## Krok 6: Dodanie odstępów między sekcjami +Data: 2026-01-13 22:28 + +### Zapytanie użytkownika: +"może dodaj jeszcze wolną linię między częściami <td>" + +### Zmiany wprowadzone: +- Dodano pustą linię po każdym `</TR>` (koniec sekcji w tabeli) +- Użyto `cat -s` aby zredukować wielokrotne puste linie do maksymalnie dwóch +- Sekcje są teraz lepiej wizualnie oddzielone + +### Efekt: +Każda sekcja modlitwy (Incipit, Hymnus, Psalmi, Lectio brevis, Conclusio, etc.) jest oddzielona pustą linią, co poprawia czytelność w terminalu. + +Przykład: +``` +Incipit + +℣. Deus ✠ in adiutórium... + +Hymnus + +Iam lucis orto sídere... + +Psalmi {ex Psalterio... +``` diff --git a/passmenu b/passmenu new file mode 100755 index 0000000..76d92ab --- /dev/null +++ b/passmenu @@ -0,0 +1,35 @@ +#!/usr/bin/env bash + +shopt -s nullglob globstar + +typeit=0 +if [[ $1 == "--type" ]]; then + typeit=1 + shift +fi + +if [[ -n $WAYLAND_DISPLAY ]]; then + dmenu=dmenu-wl + xdotool="ydotool type --file -" +elif [[ -n $DISPLAY ]]; then + dmenu=dmenu + xdotool="xdotool type --clearmodifiers --file -" +else + echo "Error: No Wayland or X11 display detected" >&2 + exit 1 +fi + +prefix=${PASSWORD_STORE_DIR-~/.password-store} +password_files=( "$prefix"/**/*.gpg ) +password_files=( "${password_files[@]#"$prefix"/}" ) +password_files=( "${password_files[@]%.gpg}" ) + +password=$(printf '%s\n' "${password_files[@]}" | "$dmenu" "$@") + +[[ -n $password ]] || exit + +if [[ $typeit -eq 0 ]]; then + pass show -c "$password" 2>/dev/null +else + pass show "$password" | { IFS= read -r pass; printf %s "$pass"; } | $xdotool +fi diff --git a/passmenu-otp b/passmenu-otp new file mode 100755 index 0000000..d511944 --- /dev/null +++ b/passmenu-otp @@ -0,0 +1,24 @@ +#!/bin/sh +set -eu + +: "${PASSWORD_STORE_DIR:=$HOME/.password-store}" +DMENU="${DMENU:-dmenu}" + +# Build list of pass entry paths (relative to PASSWORD_STORE_DIR) that contain an otpauth:// line. +entries=$( + find "$PASSWORD_STORE_DIR" -type f -name '*.gpg' ! -path '*/.git/*' -print 2>/dev/null | + while IFS= read -r f; do + entry="${f#$PASSWORD_STORE_DIR/}" + entry="${entry%.gpg}" + if pass show "$entry" 2>/dev/null | grep -q '^otpauth://'; then + printf '%s\n' "$entry" + fi + done +) + +[ -n "${entries:-}" ] || exit 0 + +choice=$(printf '%s\n' "$entries" | "$DMENU" -i -p "OTP:") +[ -n "${choice:-}" ] || exit 0 + +pass otp -c "$choice" diff --git a/proton-bridge-cert-refresh b/proton-bridge-cert-refresh new file mode 100755 index 0000000..dc3be73 --- /dev/null +++ b/proton-bridge-cert-refresh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +set -euo pipefail + +CERT_DIR="$HOME/.config/isync" +CERT_FILE="$CERT_DIR/proton-bridge.pem" + +mkdir -p "$CERT_DIR" +chmod 700 "$CERT_DIR" + +# Ensure Bridge port is up (wait up to 30s) +for i in $(seq 1 30); do + if ss -ltn '( sport = :1143 )' | grep -q 1143; then + break + fi + sleep 1 +done + +if ! ss -ltn '( sport = :1143 )' | grep -q 1143; then + echo "Bridge not listening on 127.0.0.1:1143" >&2 + exit 1 +fi + +tmp="$(mktemp)" + +openssl s_client -starttls imap -connect 127.0.0.1:1143 -showcerts </dev/null 2>/dev/null \ +| awk ' + /BEGIN CERTIFICATE/ {c++} + c==1 {print} + /END CERTIFICATE/ && c==1 {exit} +' > "$tmp" + +# sanity check +openssl x509 -in "$tmp" -noout >/dev/null 2>&1 + +mv "$tmp" "$CERT_FILE" +chmod 600 "$CERT_FILE" + +echo "Updated: $CERT_FILE" + diff --git a/reset-gpt b/reset-gpt new file mode 100755 index 0000000..69131a7 --- /dev/null +++ b/reset-gpt @@ -0,0 +1,17 @@ +#!/bin/bash + +# Close ChatGPT tabs only +pkill -f "firefox.*chatgpt.com" 2>/dev/null + +PROFILE=$(ls -d ~/.mozilla/firefox/*.default*) + +# Delete all ChatGPT site storage +rm -rf "$PROFILE/storage/default/https+++chatgpt.com" +rm -rf "$PROFILE/storage/default/https+++chat.openai.com" + +# Clear Firefox HTTP cache +rm -rf "$PROFILE/cache2/*" + +# Relaunch Firefox +nohup firefox-esr >/dev/null 2>&1 & + @@ -0,0 +1 @@ +/home/lukasz/.local/share/pipx/venvs/rexi/bin/rexi
\ No newline at end of file diff --git a/rofi-vim-power b/rofi-vim-power new file mode 100755 index 0000000..3673119 --- /dev/null +++ b/rofi-vim-power @@ -0,0 +1,26 @@ +#!/bin/sh +set -eu + +DB="$HOME/.local/share/cheat-sheets/vim-power.tsv" + +[ -f "$DB" ] || exit 1 + +choice="$( + cat "$DB" | + rofi -dmenu -i -p 'Vim power ›' +)" + +[ -n "${choice:-}" ] || exit 0 + +# First column = keystrokes +keys="$(printf '%s' "$choice" | cut -f1)" + +# Clipboard (Wayland/X11) +if command -v wl-copy >/dev/null 2>&1; then + printf '%s' "$keys" | wl-copy +elif command -v xclip >/dev/null 2>&1; then + printf '%s' "$keys" | xclip -selection clipboard +else + printf '%s\n' "$keys" +fi + diff --git a/sharepoint-mount b/sharepoint-mount new file mode 100755 index 0000000..cd894cf --- /dev/null +++ b/sharepoint-mount @@ -0,0 +1,40 @@ +#!/usr/bin/env bash +set -euo pipefail + +BASE="/mnt/sharepoint" + +declare -A REMOTES=( + ["Administration"]="SP_Administration:" + ["Operations"]="SP_Operations:" + ["SkyscaleCommerce"]="SP_SkyscaleCommerce:" +) + +RCLONE_FLAGS=( + --vfs-cache-mode writes + --dir-cache-time 1h + --poll-interval 1m + --umask 022 + --log-level INFO +) + +is_mounted() { findmnt -rn "$1" >/dev/null 2>&1; } + +# Ensure mountpoints exist +sudo mkdir -p "$BASE"/{Administration,Operations,SkyscaleCommerce} +sudo chown -R "$USER:$USER" "$BASE" + +for name in "${!REMOTES[@]}"; do + mp="$BASE/$name" + remote="${REMOTES[$name]}" + + if is_mounted "$mp"; then + echo "Already mounted: $mp" + continue + fi + + echo "Mounting: $remote -> $mp" + nohup rclone mount "$remote" "$mp" "${RCLONE_FLAGS[@]}" \ + --log-file "${XDG_RUNTIME_DIR:-/run/user/$UID}/rclone-${name}.log" \ + >/dev/null 2>&1 & +done + diff --git a/sharepoint-status b/sharepoint-status new file mode 100755 index 0000000..0404959 --- /dev/null +++ b/sharepoint-status @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +set -euo pipefail + +BASE="/mnt/sharepoint" +MPS=( + "$BASE/Administration" + "$BASE/Operations" + "$BASE/SkyscaleCommerce" +) + +for mp in "${MPS[@]}"; do + if findmnt -rn "$mp" >/dev/null 2>&1; then + echo "MOUNTED $mp" + else + echo "OFF $mp" + fi +done + diff --git a/sharepoint-umount b/sharepoint-umount new file mode 100755 index 0000000..ccac033 --- /dev/null +++ b/sharepoint-umount @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +set -euo pipefail + +BASE="/mnt/sharepoint" +MPS=( + "$BASE/Administration" + "$BASE/Operations" + "$BASE/SkyscaleCommerce" +) + +is_mounted() { findmnt -rn "$1" >/dev/null 2>&1; } + +# Do not stay inside any mount +cd / + +for mp in "${MPS[@]}"; do + if ! is_mounted "$mp"; then + echo "Not mounted: $mp" + continue + fi + + echo "Unmounting: $mp" + if ! fusermount3 -u "$mp" 2>/dev/null; then + fusermount3 -uz "$mp" 2>/dev/null || sudo umount -l "$mp" 2>/dev/null || { + echo "ERROR: failed to unmount $mp" + exit 1 + } + fi +done + +# Optional: kill any leftover rclone mount processes still referencing /mnt/sharepoint +pkill -f "rclone mount.*${BASE}" 2>/dev/null || true + diff --git a/typebookmarks b/typebookmarks new file mode 100755 index 0000000..559075a --- /dev/null +++ b/typebookmarks @@ -0,0 +1,5 @@ +#! /bin/sh + +# map this script as a shortcut key for dwm in config.h + +xdotool type "$(grep -v '^#' ${HOME}/.snippets | dmenu -i -l 50 | sed 's/\s*#.*//')" diff --git a/usb-safe b/usb-safe new file mode 100755 index 0000000..e983360 --- /dev/null +++ b/usb-safe @@ -0,0 +1,132 @@ +#!/usr/bin/env bash +set -euo pipefail + +UUID="bdd2a016-b6db-493d-ad0a-e8dd8a93dfbd" +NAME="usb_safe" +MNT="/mnt/usb_safe" + +DEV_BY_UUID="/dev/disk/by-uuid/${UUID}" +MAPPER="/dev/mapper/${NAME}" + +die() { echo "Error: $*" >&2; exit 1; } + +need_cmd() { + command -v "$1" >/dev/null 2>&1 || die "Missing command: $1" +} + +resolve_blockdev() { + local target + target="$(readlink -f "$DEV_BY_UUID")" || return 1 + [[ -b "$target" ]] || return 1 + echo "$target" +} + +is_mapped() { [[ -e "$MAPPER" ]]; } +is_mounted() { mountpoint -q "$MNT"; } + +show_busy() { + echo "Mount appears busy. Processes using $MNT:" + if command -v lsof >/dev/null 2>&1; then + sudo lsof +f -- "$MNT" || true + else + echo "(Install lsof: sudo apt install lsof)" + sudo fuser -vm "$MNT" || true + fi +} + +do_open() { + need_cmd cryptsetup + need_cmd mount + need_cmd mountpoint + need_cmd readlink + + [[ -e "$DEV_BY_UUID" ]] || die "UUID device not found: $DEV_BY_UUID (is the drive plugged in?)" + + local dev + dev="$(resolve_blockdev)" || die "Could not resolve block device for UUID: $UUID" + + if is_mapped; then + echo "Mapper already exists: $MAPPER" + else + sudo cryptsetup open "$dev" "$NAME" + fi + + if [[ ! -d "$MNT" ]]; then + sudo mkdir -p "$MNT" + fi + + if is_mounted; then + echo "Already mounted: $MNT" + else + sudo mount "$MAPPER" "$MNT" + fi + + sudo sync + echo "OK: unlocked and mounted at $MNT" +} + +do_close() { + need_cmd cryptsetup + need_cmd umount + need_cmd mountpoint + need_cmd readlink + + if is_mounted; then + sudo sync + if ! sudo umount "$MNT"; then + show_busy + die "Unmount failed" + fi + fi + + if is_mapped; then + sudo cryptsetup close "$NAME" + fi + + if [[ -e "$DEV_BY_UUID" ]]; then + local dev + dev="$(resolve_blockdev)" || true + if [[ -n "${dev:-}" ]] && command -v udisksctl >/dev/null 2>&1; then + sudo udisksctl power-off -b "$dev" || true + fi + fi + + echo "OK: unmounted, closed, and powered off (if supported)" +} + +do_status() { + echo "UUID device: $DEV_BY_UUID" + if [[ -e "$DEV_BY_UUID" ]]; then + echo "Resolved: $(readlink -f "$DEV_BY_UUID")" + else + echo "Resolved: (not present)" + fi + + echo -n "Mapper: " + if is_mapped; then echo "OPEN ($MAPPER)"; else echo "CLOSED"; fi + + echo -n "Mount: " + if is_mounted; then echo "MOUNTED ($MNT)"; else echo "NOT MOUNTED"; fi +} + +usage() { + cat <<EOF +Usage: $0 {open|close|status} + +open - unlock LUKS by UUID and mount to $MNT +close - sync, unmount, close mapper, power-off block device (best-effort) +status - show current state +EOF +} + +main() { + case "${1:-}" in + open) do_open ;; + close) do_close ;; + status) do_status ;; + *) usage; exit 2 ;; + esac +} + +main "$@" + @@ -0,0 +1 @@ +/home/lukasz/.local/share/pipx/venvs/yt-dlp/bin/yt-dlp
\ No newline at end of file |
