diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..3210fb7 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,41 @@ +name: Lint Scripts + +on: + push: + branches: [ main, master, "feature/**" ] + pull_request: + branches: [ main, master ] + workflow_dispatch: + +jobs: + shellcheck: + name: ShellCheck (Bash) + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + persist-credentials: false + + + - name: Run ShellCheck + uses: ludeeus/action-shellcheck@master + with: + scandir: '.' + severity: style + + psscriptanalyzer: + name: PSScriptAnalyzer (PowerShell) + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Run PSScriptAnalyzer + uses: microsoft/psscriptanalyzer-action@v1.1 + with: + path: './PowerShell' + recurse: true + settings: PSGallery diff --git a/PowerShell/DeleteEmptyFolders.ps1 b/PowerShell/DeleteEmptyFolders.ps1 index 2f319ba..603d0b3 100644 --- a/PowerShell/DeleteEmptyFolders.ps1 +++ b/PowerShell/DeleteEmptyFolders.ps1 @@ -1,21 +1,11 @@ -๏ปฟ# DeleteEmptyFolders.ps1 - -[CmdletBinding()] -Param ( +<# +.SYNOPSIS + Deletes empty subdirectories within a specified path (wrapper for ManageEmptyFolders.ps1). +#> +[CmdletBinding(SupportsShouldProcess = $true)] +param ( [Parameter(Mandatory = $true, Position = 0, HelpMessage = "The root path to search for empty folders.")] [string]$Path ) -# Validate that the path exists and is a directory -if (-not (Test-Path -Path $Path -PathType Container)) { - Write-Error "The path '$Path' does not exist or is not a folder." - exit 1 -} - -Write-Verbose "Searching for empty folders under '$Path'..." - -# Get all directories recursively, then filter for those that have no child items (files or folders). -# The results are sorted by the length of the FullName in descending order to ensure subdirectories are deleted before their parents. -Get-ChildItem -Path $Path -Recurse -Directory | Where-Object { -not $_.GetFileSystemInfos() } | Sort-Object { $_.FullName.Length } -Descending | Remove-Item -Force -Verbose - -Write-Host "Finished deleting empty folders." \ No newline at end of file +& "$PSScriptRoot/ManageEmptyFolders.ps1" -Path $Path -Delete \ No newline at end of file diff --git a/PowerShell/DeleteFiles.ps1 b/PowerShell/DeleteFiles.ps1 index e69de29..9daaf28 100644 --- a/PowerShell/DeleteFiles.ps1 +++ b/PowerShell/DeleteFiles.ps1 @@ -0,0 +1,56 @@ +<# +.SYNOPSIS + Deletes files based on path, filter, and age with safety dry-run support (-WhatIf). + +.DESCRIPTION + Safely removes files matching specified patterns and optionally older than a given number of days. + Supports -WhatIf and -Confirm standard PowerShell switches. + +.EXAMPLE + .\DeleteFiles.ps1 -Path "C:\Logs" -Filter "*.log" -DaysOld 30 -Recurse -WhatIf +#> + +[CmdletBinding(SupportsShouldProcess = $true)] +param( + [Parameter(Mandatory = $false, Position = 0)] + [string]$Path = ".", + + [Parameter(Mandatory = $false)] + [string]$Filter = "*", + + [Parameter(Mandatory = $false)] + [int]$DaysOld = 0, + + [Parameter(Mandatory = $false)] + [switch]$Recurse +) + +if (-not (Test-Path -Path $Path)) { + Write-Error "Target path '$Path' does not exist." + exit 1 +} + +$cutoffDate = (Get-Date).AddDays(-$DaysOld) + +$getParams = @{ + Path = $Path + Filter = $Filter + File = $true + Recurse = $Recurse +} + +$filesToDelete = Get-ChildItem @getParams | Where-Object { $_.LastWriteTime -lt $cutoffDate } + +if ($filesToDelete.Count -eq 0) { + Write-Host "No files found matching criteria in '$Path'." -ForegroundColor Yellow + return +} + +Write-Host "Found $($filesToDelete.Count) file(s) to process." -ForegroundColor Cyan + +foreach ($file in $filesToDelete) { + if ($PSCmdlet.ShouldProcess($file.FullName, "Delete file")) { + Remove-Item -Path $file.FullName -Force + Write-Host "Deleted: $($file.FullName)" -ForegroundColor Green + } +} diff --git a/PowerShell/FindEmptySubDirectories.ps1 b/PowerShell/FindEmptySubDirectories.ps1 index 20e5ab4..503e3fb 100644 --- a/PowerShell/FindEmptySubDirectories.ps1 +++ b/PowerShell/FindEmptySubDirectories.ps1 @@ -1,15 +1,11 @@ -# findEmptySubDirectories.ps1 - +<# +.SYNOPSIS + Finds empty subdirectories within a specified path (wrapper for ManageEmptyFolders.ps1). +#> [CmdletBinding()] -Param( +param ( [Parameter(Mandatory = $true, Position = 0, HelpMessage = "The root path to search for empty directories.")] [string]$Path ) -if (-not (Test-Path -Path $Path -PathType Container)) { - Write-Error "The path '$Path' does not exist or is not a folder." - exit 1 -} - -# Get all directories recursively, then filter for those that have no child items (files or folders). -Get-ChildItem -Path $Path -Recurse -Directory | Where-Object { -not $_.GetFileSystemInfos() } | Select-Object -ExpandProperty FullName \ No newline at end of file +& "$PSScriptRoot/ManageEmptyFolders.ps1" -Path $Path \ No newline at end of file diff --git a/PowerShell/FindEmptySubDirectories.txt b/PowerShell/FindEmptySubDirectories.txt deleted file mode 100644 index 7fe85ca..0000000 --- a/PowerShell/FindEmptySubDirectories.txt +++ /dev/null @@ -1,6 +0,0 @@ -#FindEmptyDirectories.ps1 - -$Path = $_1; - -# Get-ChildItem '$Path' -File -Recurse | Where-Object {[System.IO.Directory]::GetFileSystemEntries($_.FullName).Count -eq 0} | ForEach-Object {$_.FullName} -Get-ChildItem 'C:\TonjeRenate\Tonje\Videoer\LivingRoom\2023\' -Recurse -File | \ No newline at end of file diff --git a/PowerShell/ManageEmptyFolders.ps1 b/PowerShell/ManageEmptyFolders.ps1 index 52908e9..2dbbeb4 100644 --- a/PowerShell/ManageEmptyFolders.ps1 +++ b/PowerShell/ManageEmptyFolders.ps1 @@ -14,15 +14,15 @@ A switch parameter that, if present, causes the script to delete the empty folders it finds. .EXAMPLE - .\Manage-EmptyFolders.ps1 -Path "C:\Users\Me\Documents" + .\ManageEmptyFolders.ps1 -Path "C:\Users\Me\Documents" Description: Lists all empty folders found under C:\Users\Me\Documents. .EXAMPLE - .\Manage-EmptyFolders.ps1 -Path "C:\Temp" -Delete + .\ManageEmptyFolders.ps1 -Path "C:\Temp" -Delete Description: Deletes all empty folders found under C:\Temp after prompting for confirmation. .EXAMPLE - .\Manage-EmptyFolders.ps1 -Path "C:\Temp" -Delete -WhatIf + .\ManageEmptyFolders.ps1 -Path "C:\Temp" -Delete -WhatIf Description: Shows which empty folders would be deleted under C:\Temp without actually deleting them. #> [CmdletBinding(SupportsShouldProcess = $true)] diff --git a/PowerShell/ManageEmptyFolders2.ps1 b/PowerShell/ManageEmptyFolders2.ps1 deleted file mode 100644 index 52908e9..0000000 --- a/PowerShell/ManageEmptyFolders2.ps1 +++ /dev/null @@ -1,59 +0,0 @@ -<# -.SYNOPSIS - Finds and optionally deletes empty subdirectories within a specified path. - -.DESCRIPTION - This script recursively searches a given directory path for any subdirectories that are empty (contain no files or other subdirectories). - By default, it lists the full paths of the empty folders found. - When the -Delete switch is used, it will remove these empty folders. The deletion process is done safely by removing the deepest nested folders first. - -.PARAMETER Path - The root path to search for empty folders. This parameter is mandatory. - -.PARAMETER Delete - A switch parameter that, if present, causes the script to delete the empty folders it finds. - -.EXAMPLE - .\Manage-EmptyFolders.ps1 -Path "C:\Users\Me\Documents" - Description: Lists all empty folders found under C:\Users\Me\Documents. - -.EXAMPLE - .\Manage-EmptyFolders.ps1 -Path "C:\Temp" -Delete - Description: Deletes all empty folders found under C:\Temp after prompting for confirmation. - -.EXAMPLE - .\Manage-EmptyFolders.ps1 -Path "C:\Temp" -Delete -WhatIf - Description: Shows which empty folders would be deleted under C:\Temp without actually deleting them. -#> -[CmdletBinding(SupportsShouldProcess = $true)] -Param ( - [Parameter(Mandatory = $true, Position = 0, HelpMessage = "The root path to search for empty folders.")] - [string]$Path, - - [Parameter(Mandatory = $false, HelpMessage = "If specified, the script will delete the empty folders found.")] - [switch]$Delete -) - -if (-not (Test-Path -Path $Path -PathType Container)) { - Write-Error "The path '$Path' does not exist or is not a folder." - exit 1 -} - -Write-Verbose "Searching for empty folders under '$Path'..." -$emptyFolders = Get-ChildItem -Path $Path -Recurse -Directory | Where-Object { -not $_.GetFileSystemInfos() } - -if ($Delete) { - Write-Host "Found $($emptyFolders.Count) empty folders to delete." -ForegroundColor Yellow - # Sort by path length descending to delete deepest folders first - $emptyFolders | Sort-Object { $_.FullName.Length } -Descending | ForEach-Object { - if ($PSCmdlet.ShouldProcess($_.FullName, "Delete Empty Folder")) { - Remove-Item -LiteralPath $_.FullName -Force -Verbose - } - } - Write-Host "Finished deleting empty folders." -} -else { - Write-Host "Found $($emptyFolders.Count) empty folders." - # Default action: List the folders - $emptyFolders | Select-Object -ExpandProperty FullName -} \ No newline at end of file diff --git a/README.md b/README.md index 6a87ddd..4c7ca6c 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,107 @@ -# scripts +# Scripts Repository -PowerShell (.ps1), python (.py), bash (.sh) +A curated collection of automation scripts, tools, and utility modules for **Bash**, **PowerShell**, and **Docker**. -## Bash +--- -Different bash scripts +## ๐Ÿ› ๏ธ Repository Overview -### NordVPN +``` +. +โ”œโ”€โ”€ .github/ +โ”‚ โ””โ”€โ”€ workflows/ +โ”‚ โ””โ”€โ”€ lint.yml # GitHub Actions CI workflow (ShellCheck & PSScriptAnalyzer) +โ”œโ”€โ”€ bash/ +โ”‚ โ”œโ”€โ”€ AutoRemoveSnapd.sh # Removes snapd and associated packages on Debian/Ubuntu +โ”‚ โ”œโ”€โ”€ countLines.sh # Counts lines in a text file with line numbers +โ”‚ โ”œโ”€โ”€ import-kali.sh # Imports Kali Linux package repositories and GPG keys +โ”‚ โ”œโ”€โ”€ docker/ +โ”‚ โ”‚ โ””โ”€โ”€ compose.yaml # Docker Compose setup +โ”‚ โ”œโ”€โ”€ encryption/ +โ”‚ โ”‚ โ”œโ”€โ”€ encrypt_file.sh # AES-256-CBC file encryption (OpenSSL + PBKDF2) +โ”‚ โ”‚ โ””โ”€โ”€ decrypt_file.sh # AES-256-CBC file decryption (OpenSSL) +โ”‚ โ””โ”€โ”€ nord/ # NordVPN Meshnet control scripts +โ”‚ โ”œโ”€โ”€ config.sh # NordVPN configuration helper +โ”‚ โ”œโ”€โ”€ connect.sh # Connects to NordVPN +โ”‚ โ”œโ”€โ”€ copy_scripts.sh # Deployment helper script +โ”‚ โ”œโ”€โ”€ exit_node.sh # Manages and sets Meshnet exit node routing +โ”‚ โ”œโ”€โ”€ list_peers.sh # Lists Meshnet peers filtered by status +โ”‚ โ”œโ”€โ”€ login.sh # Interactive NordVPN login +โ”‚ โ”œโ”€โ”€ logout.sh # NordVPN logout +โ”‚ โ”œโ”€โ”€ nord_watchdog.sh # NordVPN connection watchdog daemon +โ”‚ โ”œโ”€โ”€ reset.sh # NordVPN settings reset +โ”‚ โ””โ”€โ”€ set_nickname.sh # Sets peer nickname in Meshnet +โ””โ”€โ”€ PowerShell/ + โ”œโ”€โ”€ CreateFile.ps1 # Creates test files of a specified size + โ”œโ”€โ”€ DeleteEmptyFolders.ps1 # Wrapper to delete empty subdirectories + โ”œโ”€โ”€ DeleteFiles.ps1 # Safe file deletion with age filtering & -WhatIf dry-run + โ”œโ”€โ”€ FindEmptySubDirectories.ps1 # Wrapper to find empty subdirectories + โ”œโ”€โ”€ ManageEmptyFolders.ps1 # Primary script for listing and removing empty folders + โ”œโ”€โ”€ ManageEmptyFolders.Function.ps1 # Reusable PowerShell function module + โ””โ”€โ”€ NextPVR.ps1 # NextPVR media recorder helper +``` -Make me an exit_node god damn it! +--- + +## ๐Ÿš€ Usage Guide + +### Bash Scripts (`bash/`) + +#### ๐Ÿ” File Encryption & Decryption (`bash/encryption/`) +Encrypt and decrypt files using OpenSSL AES-256-CBC with PBKDF2 key derivation (100,000 iterations): + +```bash +# Encrypt a file (prompts securely for passphrase) +./bash/encryption/encrypt_file.sh document.pdf + +# Decrypt an encrypted file +./bash/encryption/decrypt_file.sh document.pdf.enc +``` + +#### ๐ŸŒ NordVPN Meshnet Tools (`bash/nord/`) +Manage NordVPN connections and Meshnet exit nodes: + +```bash +# List online Meshnet peers +./bash/nord/list_peers.sh online + +# Configure an exit node +./bash/nord/exit_node.sh + +# Run watchdog daemon to monitor and maintain VPN connectivity +./bash/nord/nord_watchdog.sh +``` + +--- + +### PowerShell Scripts (`PowerShell/`) + +#### ๐Ÿ“ Empty Folder Cleanup (`PowerShell/ManageEmptyFolders.ps1`) +Find or delete empty folder hierarchies (deepest nested folders deleted first): + +```powershell +# List all empty subdirectories under a path +.\PowerShell\ManageEmptyFolders.ps1 -Path "C:\Data" + +# Delete empty subdirectories safely with confirmation +.\PowerShell\ManageEmptyFolders.ps1 -Path "C:\Data" -Delete + +# Dry-run deletion using -WhatIf +.\PowerShell\ManageEmptyFolders.ps1 -Path "C:\Data" -Delete -WhatIf +``` + +#### ๐Ÿงน Safe File Cleanup (`PowerShell/DeleteFiles.ps1`) +Remove files matching a filter pattern and age threshold: + +```powershell +# Delete .log files older than 30 days under C:\Logs (dry-run mode) +.\PowerShell\DeleteFiles.ps1 -Path "C:\Logs" -Filter "*.log" -DaysOld 30 -Recurse -WhatIf +``` + +--- + +## ๐Ÿค– Code Quality & CI + +This repository uses [GitHub Actions](file:///.github/workflows/lint.yml) to ensure code quality on every push and pull request: +- **ShellCheck**: Static analysis for all `.sh` scripts. +- **PSScriptAnalyzer**: Best-practice rules for `.ps1` scripts. diff --git a/bash/AutoRemoveSnapd.sh b/bash/AutoRemoveSnapd.sh index d5f4dac..50d93b8 100755 --- a/bash/AutoRemoveSnapd.sh +++ b/bash/AutoRemoveSnapd.sh @@ -1,4 +1,4 @@ -#! /usr/bin/bash +#!/usr/bin/env bash sudo apt autoremove snapd diff --git a/bash/countLines.sh b/bash/countLines.sh index 7200294..2cdeee0 100755 --- a/bash/countLines.sh +++ b/bash/countLines.sh @@ -1,4 +1,4 @@ -#! /usr/bin/bash +#!/usr/bin/env bash FILENAME="$1" echo "$FILENAME" COUNTER=0 diff --git a/bash/encryption/decrypt_file.sh b/bash/encryption/decrypt_file.sh index e69de29..71a876d 100644 --- a/bash/encryption/decrypt_file.sh +++ b/bash/encryption/decrypt_file.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +set -euo pipefail + +usage() { + echo "Usage: $0 [output_file]" + echo "Encrypts a file using AES-256-CBC with PBKDF2 key derivation." + exit 1 +} + +if [[ $# -lt 1 ]] || [[ "$1" == "-h" ]] || [[ "$1" == "--help" ]]; then + usage +fi + +INPUT_FILE="$1" +if [[ ! -f "$INPUT_FILE" ]]; then + echo "Error: Encrypted file '$INPUT_FILE' does not exist." >&2 + exit 1 +fi + +if [[ $# -ge 2 ]]; then + OUTPUT_FILE="$2" +elif [[ "$INPUT_FILE" == *.enc ]]; then + OUTPUT_FILE="${INPUT_FILE%.enc}" +else + OUTPUT_FILE="${INPUT_FILE}.dec" +fi + +read -rsp "Enter decryption passphrase: " PASS +echo + +if [[ -z "$PASS" ]]; then + echo "Error: Passphrase cannot be empty." >&2 + exit 1 +fi + +if openssl enc -d -aes-256-cbc -pbkdf2 -iter 100000 -in "$INPUT_FILE" -out "$OUTPUT_FILE" -pass pass:"$PASS"; then + echo "File successfully decrypted to '$OUTPUT_FILE'." +else + echo "Error: Decryption failed. Incorrect passphrase or corrupted file." >&2 + rm -f "$OUTPUT_FILE" + exit 1 +fi diff --git a/bash/encryption/encrypt_file.sh b/bash/encryption/encrypt_file.sh index 3686f0f..3a8f1df 100644 --- a/bash/encryption/encrypt_file.sh +++ b/bash/encryption/encrypt_file.sh @@ -1,3 +1,39 @@ -#! /usr/bin/bash +#!/usr/bin/env bash +set -euo pipefail +usage() { + echo "Usage: $0 [output_file]" + echo "Encrypts a file using AES-256-CBC with PBKDF2 key derivation." + exit 1 +} +if [[ $# -lt 1 ]] || [[ "$1" == "-h" ]] || [[ "$1" == "--help" ]]; then + usage +fi + +INPUT_FILE="$1" +if [[ ! -f "$INPUT_FILE" ]]; then + echo "Error: Input file '$INPUT_FILE' does not exist." >&2 + exit 1 +fi + +OUTPUT_FILE="${2:-${INPUT_FILE}.enc}" + +read -rsp "Enter encryption passphrase: " PASS1 +echo +read -rsp "Confirm encryption passphrase: " PASS2 +echo + +if [[ "$PASS1" != "$PASS2" ]]; then + echo "Error: Passphrases do not match." >&2 + exit 1 +fi + +if [[ -z "$PASS1" ]]; then + echo "Error: Passphrase cannot be empty." >&2 + exit 1 +fi + +openssl enc -aes-256-cbc -pbkdf2 -iter 100000 -in "$INPUT_FILE" -out "$OUTPUT_FILE" -pass pass:"$PASS1" + +echo "File successfully encrypted to '$OUTPUT_FILE'." diff --git a/bash/nord/config.sh b/bash/nord/config.sh index 6180f70..0ae69c2 100755 --- a/bash/nord/config.sh +++ b/bash/nord/config.sh @@ -1,4 +1,4 @@ -#! /usr/bin/bash +#!/usr/bin/env bash set -euo pipefail # --- Dependency Checks --- diff --git a/bash/nord/connect.sh b/bash/nord/connect.sh index c7142f8..08f24b0 100755 --- a/bash/nord/connect.sh +++ b/bash/nord/connect.sh @@ -1,4 +1,4 @@ -#! /usr/bin/bash +#!/usr/bin/env bash # Assign the NO country code if there is no argument country=${1:-NO} diff --git a/bash/nord/copy_scripts.sh b/bash/nord/copy_scripts.sh index 22d26fa..d4f8d75 100755 --- a/bash/nord/copy_scripts.sh +++ b/bash/nord/copy_scripts.sh @@ -1,4 +1,4 @@ -#! /usr/bin/bash +#!/usr/bin/env bash # Copies scripts to the ~/.local/bin/ folder so they can be executed # everywhere. @@ -12,6 +12,7 @@ # All copied files will have nord_ prepended and the .sh removed in the new # filename. +cp peers.json ~/.local/bin/peers.json cp config.sh ~/.local/bin/nord_config cp connect.sh ~/.local/bin/nord_connect cp list_peers.sh ~/.local/bin/nord_list_peers diff --git a/bash/nord/exit_node.sh b/bash/nord/exit_node.sh index 8d1948c..c0b3323 100644 --- a/bash/nord/exit_node.sh +++ b/bash/nord/exit_node.sh @@ -1,4 +1,4 @@ -#! /usr/bin/bash +#!/usr/bin/env bash # Exit immediately if a command exits with a non-zero status. set -e diff --git a/bash/nord/list_peers.sh b/bash/nord/list_peers.sh index 591944f..0ebbd7b 100755 --- a/bash/nord/list_peers.sh +++ b/bash/nord/list_peers.sh @@ -1,7 +1,8 @@ -#! /usr/bin/bash +#!/usr/bin/env bash -filter=${1:-online} +filter="${1:-online}" -echo $filter +echo "$filter" + +nordvpn mesh peer list --filter="$filter" -nordvpn mesh peer list --filter=$filter diff --git a/bash/nord/login.sh b/bash/nord/login.sh index fe4290f..39b0873 100755 --- a/bash/nord/login.sh +++ b/bash/nord/login.sh @@ -1,3 +1,15 @@ -#! /usr/bin/bash +#!/usr/bin/env bash -nordvpn login --token e9f2abcc251e25317efb43eb05c4fe9a8771a1e7cc076b43ed59fe5ad9ba2115 +TOKEN="${1:-${NORDVPN_TOKEN:-}}" + +if [[ -z "$TOKEN" ]]; then + read -rsp "Enter NordVPN access token: " TOKEN + echo +fi + +if [[ -z "$TOKEN" ]]; then + echo "Error: NordVPN token cannot be empty." >&2 + exit 1 +fi + +nordvpn login --token "$TOKEN" diff --git a/bash/nord/logout.sh b/bash/nord/logout.sh index 965ab18..bcde173 100755 --- a/bash/nord/logout.sh +++ b/bash/nord/logout.sh @@ -1,3 +1,3 @@ -#! /usr/bin/bash +#!/usr/bin/env bash nordvpn logout --persist-token true diff --git a/bash/nord/nord_watchdog.sh b/bash/nord/nord_watchdog.sh index 0da1273..f9d37a9 100755 --- a/bash/nord/nord_watchdog.sh +++ b/bash/nord/nord_watchdog.sh @@ -1,4 +1,4 @@ -#!/usr/bin/bash +#!/usr/bin/env bash # Configuration - Add peer nicknames that should be allowed to route through this exit node. # You can find peer nicknames with `nordvpn meshnet peer list`. diff --git a/bash/nord/reset.sh b/bash/nord/reset.sh index f1b08e3..e30d3a5 100755 --- a/bash/nord/reset.sh +++ b/bash/nord/reset.sh @@ -1,4 +1,4 @@ -#! /usr/bin/bash +#!/usr/bin/env bash ./logout.sh ./login.sh ./connect.sh diff --git a/bash/nord/set_nickname.sh b/bash/nord/set_nickname.sh index 6ab42c2..e10c970 100755 --- a/bash/nord/set_nickname.sh +++ b/bash/nord/set_nickname.sh @@ -1,4 +1,4 @@ -#! /usr/bin/bash +#!/usr/bin/env bash if [ -z "$1" ]; then echo "Pass the nickname for the device"