124 Commits

Author SHA1 Message Date
fatalerrors
15ef317029 themes adjustments 2026-04-21 16:07:16 +02:00
fatalerrors
a6e4d7a256 get ready for release 2026-04-21 15:24:04 +02:00
fatalerrors
6106ca7684 proper package manager detection 2026-04-21 14:18:37 +02:00
fatalerrors
1088029ae6 fix extra shift 2026-04-21 14:03:57 +02:00
fatalerrors
eb4c89759b fix display in kt 2026-04-21 14:03:22 +02:00
fatalerrors
1dc5d72ac6 fix display of return code in prompt 2026-04-21 14:02:45 +02:00
fatalerrors
d49703c5d5 fix themes right 2026-04-18 00:19:07 +02:00
fatalerrors
066f2e353e fix all spellsheck 2026-04-16 17:53:46 +02:00
fatalerrors
c011f03aee add missing help 2026-04-15 17:47:15 +02:00
fatalerrors
c4b0516c45 fix hardcoded setfr in main file 2026-04-15 17:36:15 +02:00
fatalerrors
4da29872fc refining themes 2026-04-15 17:00:04 +02:00
fatalerrors
d7a0e2c5f5 refining themes 2026-04-15 16:53:33 +02:00
fatalerrors
d60a93814b typo 2026-04-15 16:34:54 +02:00
fatalerrors
d27935eedd bugfix 2026-04-15 16:33:45 +02:00
fatalerrors
e77232b3ca bugfix 2026-04-15 16:29:04 +02:00
fatalerrors
d6dce2d91e bugfix 2026-04-15 16:08:34 +02:00
fatalerrors
4e3cccff64 bugfix 2026-04-15 16:03:10 +02:00
fatalerrors
91f033743d bugfix 2026-04-15 15:54:58 +02:00
fatalerrors
9fcf21c55e bugfix 2026-04-15 15:45:55 +02:00
fatalerrors
5300386941 bugfix 2026-04-15 15:39:45 +02:00
fatalerrors
dfad345be3 reset color definitions before loading a new theme 2026-04-15 15:16:11 +02:00
fatalerrors
1b16878ea8 allow dynamic theme change 2026-04-15 15:03:43 +02:00
fatalerrors
30b8b8241a bugfix 2026-04-15 14:54:49 +02:00
fatalerrors
a4056b9e82 fix line ending 2026-04-15 14:53:55 +02:00
fatalerrors
6696e0d05a fix help 2026-04-15 14:44:33 +02:00
fatalerrors
6d15af029e fix mod 2026-04-15 14:25:12 +02:00
fatalerrors
bc0a592fa1 version bump 2026-04-15 14:24:03 +02:00
fatalerrors
b3f909e287 added documentation 2026-04-15 14:09:34 +02:00
fatalerrors
89e20993da allow to chose upgrade branch 2026-04-15 13:44:44 +02:00
fatalerrors
9a006883b8 make defaults configurable 2026-04-15 13:38:08 +02:00
fatalerrors
85f02f4498 themable prompt and some proposed themes 2026-04-15 13:35:33 +02:00
fatalerrors
bb9bbfda16 Version bump 2026-04-15 08:56:04 +02:00
fatalerrors
9ea1cc3f72 Use color declared in disp.sh 2026-04-15 08:55:23 +02:00
fatalerrors
b79103a0a6 bugfix 2026-04-15 08:13:52 +02:00
fatalerrors
e5bafe1721 increased reliability 2026-04-15 08:13:17 +02:00
fatalerrors
322d03ed4c hardening 2026-04-15 08:11:58 +02:00
fatalerrors
60a159c3ea code quality 2026-04-08 18:46:30 +02:00
fatalerrors
84e6fdd429 added entropy and password source to pwdscore 2026-04-01 18:15:11 +02:00
fatalerrors
8fe11776cb add --all-users to rmhost, hardening 2026-04-01 17:54:23 +02:00
fatalerrors
0737d0c647 reworked genpwd / introduce pwdscore 2026-04-01 17:52:09 +02:00
fatalerrors
d72fa1a712 hardening 2026-04-01 17:21:54 +02:00
fatalerrors
08e9e6c799 greatly improved upgrade system 2026-04-01 17:20:49 +02:00
fatalerrors
ac66e896dd exit -> return 2026-04-01 15:56:48 +02:00
fatalerrors
0712be626b wording and other minor improvments 2026-04-01 15:53:44 +02:00
fatalerrors
3f8b81562b sorted alias out 2026-04-01 15:53:10 +02:00
fatalerrors
96d1dc695d added missing function in help 2026-04-01 15:28:12 +02:00
fatalerrors
c039ab6ea0 improved and modernised file functions 2026-04-01 15:27:45 +02:00
fatalerrors
6b85556a53 typo 2026-03-26 17:19:06 +01:00
fatalerrors
cf9a85e61b update default conf with new values 2026-03-26 17:13:24 +01:00
fatalerrors
75e047d70e update help 2026-03-26 16:48:21 +01:00
fatalerrors
742ec484a7 made langage definition shortcuts configurable 2026-03-26 16:47:16 +01:00
fatalerrors
1b7262c0cd few minor update fix 2026-03-26 15:12:18 +01:00
fatalerrors
e387209c10 busy can properly exit, pattern can not inject code 2026-03-26 14:43:12 +01:00
fatalerrors
f5d59ec194 git parameter conflict, better matrix rendering, better speed unit handling 2026-03-25 18:11:01 +01:00
fatalerrors
2ee1c935ac make the rain able to be matrix 2026-03-25 17:48:27 +01:00
fatalerrors
e41c1a4c51 harden rmspc, better median calculation in file_stats 2026-03-25 16:10:41 +01:00
fatalerrors
60dfe19049 message type case unsensitive 2026-03-25 15:39:05 +01:00
fatalerrors
c32771a4ff default configuration updated 2026-03-25 15:28:08 +01:00
fatalerrors
080511d0bd add NO_COLOR support 2026-03-25 15:27:23 +01:00
fatalerrors
d8bdfefdf1 typo 2026-03-25 15:26:39 +01:00
fatalerrors
5f5f9c0e71 add trap protection, and option to force remplacement 2026-03-25 15:14:10 +01:00
fatalerrors
30387a4f08 update copyright info, uniformize sheebang 2026-03-25 14:39:58 +01:00
fatalerrors
0c51363d86 revert factorisation attempt 2026-03-25 14:37:20 +01:00
fatalerrors
043fbaef0b protect against code injection, interpret vars 2026-03-25 14:35:53 +01:00
fatalerrors
ed5587712e support for spaced filename 2026-03-25 14:19:08 +01:00
fatalerrors
58cc76c317 pre generate sections of the configuration file 2026-03-11 17:20:50 +01:00
fatalerrors
e82ee06e1d secured some implementation, check bash version 2026-03-11 11:41:56 +01:00
fatalerrors
bc8cb4a237 updated readme and final fixes for net.sh 2026-03-10 18:06:30 +01:00
fatalerrors
ae90a9f4c4 fix conf file comments, version bump 2026-03-10 17:46:30 +01:00
fatalerrors
7e661ca2de add dwl (downloader wrapper) and make use of it, fixed myextip 2026-03-10 17:45:26 +01:00
fatalerrors
9f22ed4304 fix show_as initialization 2026-03-10 15:23:52 +01:00
fatalerrors
1484b004be implemented showextip 2026-03-10 12:01:45 +01:00
fatalerrors
0a4206b890 fix getopt behavior 2026-03-10 11:40:37 +01:00
fatalerrors
02a1e25df2 fix profile.conf line endind 2026-03-10 11:02:31 +01:00
fatalerrors
7ca0a6fb88 fixed README, version bump 2026-03-10 10:55:02 +01:00
fatalerrors
25df408e37 implemented ini style parsed configuration file 2026-03-10 10:53:31 +01:00
fatalerrors
3eab1f98d5 fix parameter detection 2026-03-10 10:52:45 +01:00
fatalerrors
6c895b509a pursue --help implementation 2026-03-10 10:52:17 +01:00
fatalerrors
2ece711e1a huge longrun improvements 2026-03-06 17:46:26 +01:00
fatalerrors
39a7e7b40f version bump 2026-03-05 11:56:15 +01:00
fatalerrors
6d5d872b71 add a missing fi 2026-03-05 11:49:10 +01:00
fatalerrors
128cfe8c87 improved upgrade system, version bump 2026-03-05 11:25:05 +01:00
fatalerrors
e1c2705fdd added ppu and ppn 2026-03-05 11:20:23 +01:00
fatalerrors
368bc11acf add many compression format to utaz 2026-03-05 10:55:17 +01:00
fatalerrors
a068d57ba5 utaz finally work with many formats, with optimizations 2026-03-05 10:24:25 +01:00
fatalerrors
ffee8c2e47 added help to rain, fixed french comments 2026-03-04 16:17:36 +01:00
geoffray.levasseur
9ff5792790 configurable rain 2026-03-04 15:55:11 +01:00
root
6a2d9b0fee adaptation to trixie and excalibur 2025-11-19 14:35:40 +01:00
Geoffray Levasseur-Brandin
9b2c764181 typos 2025-06-19 14:50:46 +02:00
Geoffray Levasseur-Brandin
3d5a5e7718 typos 2025-06-19 14:50:21 +02:00
Geoffray Levasseur-Brandin
47e89b3b09 compress.sh: multiple correction and securisation 2025-06-19 14:49:49 +02:00
Geoffray Levasseur-Brandin
f0f80e2924 better display implementation 2025-06-19 14:47:14 +02:00
Geoffray Levasseur-Brandin
b08e457146 version bump 2025-06-19 14:42:14 +02:00
Geoffray Levasseur-Brandin
56e34bc346 few varaible securisation, new alias hdu 2025-06-19 14:41:31 +02:00
Geoffray Levasseur-Brandin
e9e9993dfc finished profile_uprade implementation 2025-06-19 14:40:02 +02:00
Geoffray Levasseur-Brandin
ff4c6702b7 secured rmhost 2025-06-19 14:39:10 +02:00
Geoffray Levasseur-Brandin
87dea45295 fixed some variable declarations 2025-06-19 14:38:18 +02:00
Geoffray Levasseur-Brandin
0abf481cf6 various improvement and securisation 2025-06-19 14:37:32 +02:00
Geoffray Levasseur-Brandin
a75299f7b4 non gnu date compatible, use local vars where possible 2025-06-19 14:36:13 +02:00
Geoffray Levasseur-Brandin
eeb87c5bfc make delay parametrable 2025-06-19 14:34:22 +02:00
Geoffray Levasseur-Brandin
4879b418db factorized code / thiner implementation 2025-06-19 14:33:36 +02:00
Geoffray Levasseur-Brandin
f944271488 added file_stats function 2025-06-19 14:32:29 +02:00
Geoffray Levasseur-Brandin
4be2e5ea87 add url encode / secure isipv4 2025-06-19 14:31:19 +02:00
Geoffray Levasseur-Brandin
9d528a6491 fix kt function 2025-06-19 14:30:38 +02:00
Geoffray Levasseur-Brandin
bef205ae84 bugfix, esthetic cleanup, better comments, version bump 2024-06-21 16:19:44 +02:00
Geoffray Levasseur
9e49e3e4d7 fixed url and redirection 2023-10-06 12:52:15 +02:00
94e7e79c76 Merge branch 'master' of https://git.geoffray-levasseur.org/fatalerrors/profile 2023-10-06 11:36:14 +02:00
55e88bd018 partial commit for profile upgrade 2023-10-06 11:35:40 +02:00
Geoffray Levasseur
18f1bc1543 fix bash/zsh test, some cosmetics 2023-10-06 11:22:10 +02:00
fatalerrors
0d7c7e9ab7 minor correction 2023-09-08 20:25:04 +02:00
root
64fecf16fb make profile path configurable as autodetection is too unprecise so far 2023-04-19 21:15:33 +00:00
c2ca5f659c Version bumped to 3.3.1 (3.3.0 untagged) 2023-02-24 19:00:23 +01:00
3248327e56 [fix] don't use library functions before it's loaded 2023-02-23 14:43:44 +01:00
fd984c4a16 fixed version detection, added busy function 2023-02-03 08:09:20 +01:00
c2a0ef0bd2 Initial version update support, changed versioning code, add installation path detection 2023-02-03 07:55:06 +01:00
dc64123fd9 dpkgs -> pkgs, corrected package manager detection 2023-02-03 07:24:09 +01:00
affe6e1487 cpkgs: corrected invalid character in code 2022-11-30 21:00:13 +01:00
0237f8bf70 fixed rights on last commited files 2022-11-28 16:28:50 +01:00
d4db72260a completed basic readme file 2022-11-28 16:27:51 +01:00
e980198c08 taz: fixed compression level, dpkgs: typo 2022-11-21 16:13:28 +01:00
21c12865f5 license fixes, version history, minor fixes, dpkgs rpm aware 2022-11-20 12:59:51 +01:00
5b9d7a983e disp(): fixed display of the first parameter not being part of the message 2022-11-18 14:07:41 +01:00
d7e6d81126 forgot to change version info accordingly 2022-11-18 14:02:35 +01:00
4a975db1c0 added disp function and use it 2022-11-18 13:43:10 +01:00
39 changed files with 7088 additions and 930 deletions

15
.gitignore vendored Normal file
View File

@@ -0,0 +1,15 @@
# User-specific configuration — never commit personal settings.
# Copy doc/profile.conf.example to profile.conf and customise it.
profile.conf
# Compiled / generated artefacts
*.pyc
__pycache__/
# macOS noise
.DS_Store
# Editor swap / backup files
*~
*.swp
*.swo

30
LICENSE
View File

@@ -1,11 +1,29 @@
Copyright (c) <year> <owner>.
Copyright 2013-2026 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
This is distributed with BSD-3-Clause license with the following terms and
condition:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

346
README.md
View File

@@ -1,3 +1,347 @@
# profile
This project aims to create an advanced bash profile. It includes aliases,
a customized prompt and several functions for different purposes. It's mostly
targeted to system administrators but might satisfy some regular users.
bash profile repository
## 1. Requirements
profile requires **Bash 4.3 or higher** (for associative arrays and namerefs).
It will refuse to load on older versions and will also refuse to load if the
current shell is not bash.
## 2. Getting started
Download and extract (or use git clone) the profile archive into your home
directory. You will have to modify your `~/.bashrc` and/or `~/.profile` file to
add at the end (preferably):
```bash
source <installpath>/profile/profile.sh
```
You may also set the `PROFILE_PATH` environment variable before sourcing if you
want to override the automatic path detection:
```bash
export PROFILE_PATH=/opt/profile
source /opt/profile/profile.sh
```
It's not recommended to load that profile in `/etc/profile` as users' `.bashrc`
files might interfere with some aliases and functions defined in profile.
### 2.1. Initial configuration
Copy the example configuration file and customise it to your needs:
```bash
cp <installpath>/profile/doc/profile.conf.example <installpath>/profile/profile.conf
```
`profile.conf` is git-ignored so your personal settings will never be
accidentally committed. All keys are optional — sensible defaults apply when
unset. See [section 4](#4-configuration) for the full reference.
## 3. What's the purpose?
profile gives access to numerous functions, aliases and to an advanced prompt.
All functions are organized into modules under the `profile.d/` directory and
are loaded automatically at startup.
### 3.1. Prompt
A bar-style prompt showing current time, execution time of the last command
(with sub-millisecond precision), and the exit code of the last command.
### 3.2. Functions reference
| Function | Module | Description |
|---|---|---|
| `busy` | fun | Monitor /dev/urandom for a hex pattern — look busy |
| `check_updates` | updates | Check whether a newer profile version is available online |
| `clean` | filefct | Erase backup files in given directories, optionally recursive |
| `disp` | disp | Display formatted info / warning / error / debug messages |
| `dwl` | net | Download a URL using curl, wget, or fetch transparently |
| `expandlist` | filefct | Expand glob expressions into a quoted, separated list |
| `file_stats` | filefct | Display file size statistics for a path |
| `findbig` | filefct | Find the biggest files in the given or current directory |
| `finddead` | filefct | Find dead symbolic links in the given or current directory |
| `findzero` | filefct | Find empty files in the given or current directory |
| `genpwd` | pwd | Generate one or more random secure passwords with configurable constraints |
| `gpid` | processes | Give the list of PIDs matching the given process name(s) |
| `help` | help | Display the list of available functions and basic usage |
| `isipv4` | net | Tell if the given parameter is a valid IPv4 address |
| `isipv6` | net | Tell if the given parameter is a valid IPv6 address |
| `ku` | processes | Kill all processes owned by the given user name or ID |
| `matrix` | rain | Console screensaver with Matrix-style digital rain (binary, kana, ascii charset) |
| `mcd` | filefct | Create a directory and immediately move into it |
| `meteo` | info | Display weather forecast for the configured or given city |
| `myextip` | net | Get information about your public IP address |
| `get_pkgmgr` | packages | Detect the active package manager of the running distribution (`apt`, `dnf`, `yum`, `zypper`, `pacman`, `apk`, `portage`, `xbps`, `nix`) |
| `pkgs` | packages | Search for a pattern in installed package names (distro-aware via `get_pkgmgr`, supports `-i`) |
| `ppg` | processes | Look for the given pattern in running processes |
| `ppn` | processes | List processes matching an exact command name |
| `ppu` | processes | List processes owned by a specific user |
| `profile_upgrade` | updates | Upgrade profile to the latest version (git pull or archive) |
| `pwdscore` | pwd | Calculate the strength score of a given password |
| `rain` | rain | Console screensaver with falling-rain effect (multiple color themes) |
| `rmhost` | ssh | Remove host (name and IP) from SSH known_hosts; supports `--all-users` as root |
| `rmspc` | filefct | Replace spaces in filenames with underscores (or a custom character) |
| `setc` | lang | Set locale to standard C (POSIX) |
| `setlocale` | lang | Set console locale to any installed locale |
| `settrace` | debug | Activate or deactivate ERR trap to display backtrace on script errors |
| `showinfo` | info | Display welcome banner and system information (figlet + neofetch/fastfetch) |
| `ssr` | ssh | SSH into a server as root, forwarding extra ssh options |
| `taz` | compress | Compress files and directories into a chosen archive format |
| `urlencode` | net | URL-encode a string |
| `utaz` | compress | Smartly uncompress archives (zip, tar.gz/bz2/xz/lz, rar, arj, lha, ace, 7z, zst, cpio, cab, deb, rpm) |
| `ver` | info | Display the installed profile version |
Locale shortcut functions (`setfr`, `setus`, etc.) are dynamically generated at
startup from the `SET_LOCALE` configuration key (see section 4).
## 4. Configuration
profile uses an INI-style configuration file (`profile.conf`) located in the
same directory as `profile.sh`. Sections are declared with `[section_name]` and
keys follow `key = value` syntax. Each module calls `load_conf "<section>"` at
load time, which exports every key in that section as an environment variable.
Unknown keys are silently ignored; all keys are optional — sensible defaults
apply when unset.
`profile.conf` is listed in `.gitignore` so personal values (API keys, cities,
compiler flags, …) are never accidentally staged. Start from the annotated
template at `doc/profile.conf.example` (see [section 2.1](#21-initial-configuration)).
### 4.1. Core sections
| Section | Purpose |
|---|---|
| `[system]` | Bash history size, pager, and other shell behaviours |
| `[general]` | General-purpose variables (e.g. compilation flags, `MAKEFLAGS`) |
| `[aliases]` | User command aliases, loaded for interactive shells only |
### 4.2. Module defaults
Each module exposes its hardcoded defaults as configuration keys. Set a key to
change the default without having to pass flags every time.
**`[compress]`** — `taz` / `utaz`
| Key | Default | Description |
|---|---|---|
| `TAZ_DEFAULT_FORMAT` | `tar.gz` | Archive format for `taz` (`tar.gz`, `tar.bz2`, `tar.xz`, `zip`, …) |
| `TAZ_DEFAULT_THREADS` | `0` | Compression threads (0 = auto-detect) |
| `TAZ_DEFAULT_LEVEL` | `6` | Compression level (19) |
| `UTAZ_DEFAULT_DELETE` | `0` | Set to `1` to delete the source archive after extraction |
| `UTAZ_DEFAULT_DIR_MODE` | `0` | Set to `1` to always extract into a subdirectory |
**`[filefct]`** — file utilities
| Key | Default | Description |
|---|---|---|
| `EXPANDLIST_DEFAULT_SEPARATOR` | ` ` (space) | Separator used by `expandlist` |
| `CLEAN_DEFAULT_RECURSIVE` | `0` | Set to `1` to make `clean` recurse into subdirectories |
| `RMSPC_DEFAULT_CHAR` | `_` | Replacement character used by `rmspc` |
| `FINDBIG_DEFAULT_LIMIT` | `10` | Number of results returned by `findbig` |
**`[rain]`** — screensavers
| Key | Default | Description |
|---|---|---|
| `RAIN_DEFAULT_SPEED` | `0.1` | Falling speed for `rain` |
| `RAIN_DEFAULT_COLOR` | `Green` | Colour for `rain` |
| `MATRIX_DEFAULT_SPEED` | `0.05` | Falling speed for `matrix` |
| `MATRIX_DEFAULT_COLOR` | `Green` | Colour for `matrix` |
| `MATRIX_DEFAULT_CHARSET` | `binary` | Character set for `matrix` (`binary`, `kana`, `ascii`) |
**`[ssh]`**
| Key | Default | Description |
|---|---|---|
| `SSH_DEFAULT_OPT` | _(empty)_ | Extra options passed to `ssr` (e.g. `-Y` for X forwarding) |
**`[pwd]`** — password tools
| Key | Default | Description |
|---|---|---|
| `GENPWD_DEFAULT_LENGTH` | `16` | Generated password length |
| `GENPWD_DEFAULT_OCCURS` | `1` | Number of character class occurrences |
| `GENPWD_DEFAULT_COUNT` | `1` | Number of passwords to generate |
| `GENPWD_DEFAULT_SYMBOLS` | `1` | Include symbols (0/1) |
| `GENPWD_DEFAULT_UPPERCASE` | `1` | Include uppercase letters (0/1) |
| `GENPWD_DEFAULT_LOWERCASE` | `1` | Include lowercase letters (0/1) |
| `GENPWD_DEFAULT_NUMBERS` | `1` | Include digits (0/1) |
| `PWDSCORE_DEFAULT_VERBOSE` | `0` | Set to `1` for detailed scoring output from `pwdscore` |
**`[fun]`**
| Key | Default | Description |
|---|---|---|
| `BUSY_DEFAULT_PATTERN` | `[0-9a-f]` | Hex pattern matched by `busy` |
| `BUSY_DEFAULT_DELAY` | `0.1` | Polling delay (seconds) for `busy` |
**`[info]`**
| Key | Default | Description |
|---|---|---|
| `METEO_DEFAULT_CITY` | _(empty)_ | Default city for `meteo` when no argument is given |
**`[net]`**
| Key | Default | Description |
|---|---|---|
| `DWL_PREFERRED_TOOL` | _(empty)_ | Force `dwl` to use `curl`, `wget`, or `fetch` (auto-detected when unset) |
| `MYEXTIP_DEFAULT_URL` | `https://ip-api.com/json` | API endpoint used by `myextip` |
**`[packages]`**
| Key | Default | Description |
|---|---|---|
| `PKGS_DEFAULT_IGNORE_CASE` | `0` | Set to `1` to make `pkgs` case-insensitive by default |
**`[processes]`**
| Key | Default | Description |
|---|---|---|
| `PPU_DEFAULT_FORMAT` | `pid,user,comm,args` | `ps` output format used by `ppu` |
| `KU_DEFAULT_SIGNAL` | `TERM` | Default signal sent by `ku` |
**`[updates]`**
| Key | Default | Description |
|---|---|---|
| `UPDT_DEFAULT_BRANCH` | `master` | Git branch used for update checks and `profile_upgrade`. Changing this value causes `profile_upgrade` to automatically switch the local checkout to the new branch on the next run and display a warning. |
### 4.3. Locale shortcuts
The `[lang]` key `SET_LOCALE` accepts a comma-separated list of
`alias:locale` pairs. Each pair generates a function of that name at startup:
```ini
SET_LOCALE = fr:fr_FR.UTF-8, us:en_US.UTF-8
```
This creates `setfr` and `setus`. Use `setlocale <locale>` to switch to any
installed locale directly.
Set `DEFAULT_LANG` to one of the defined aliases to activate that locale
automatically at login:
```ini
DEFAULT_LANG = fr
```
If `DEFAULT_LANG` is set but does not match any alias in `SET_LOCALE`, a
warning is displayed and no locale change is applied.
### 4.4. Prompt theming
The prompt appearance is controlled by two mechanisms that are applied in order
(later values win):
1. **Theme file** — sets a base colour palette.
2. **`[prompt]` section** in `profile.conf` — per-key overrides on top of the theme.
**Selecting a theme:**
```ini
[prompt]
PROMPT_THEME = dark # bare name → profile.d/themes/dark.theme
PROMPT_THEME_DIR = ~/.mythemes # optional: custom search directory
```
Built-in themes: `default`, `dark`, `light`, `solarized`, `solarized-light`,
`monokai`, `monochrome`, `abyss`, `plasma`, `adwaita`.
**Overriding individual prompt colour slots:**
```ini
[prompt]
PROMPT_COLOR_USER_FG = $ICyan
PROMPT_COLOR_DIR_FG = $IYellow
```
The eleven available `PROMPT_COLOR_*` keys are:
| Key | Role |
|---|---|
| `PROMPT_COLOR_TIME_FG` / `TIME_BG` | Clock foreground / background |
| `PROMPT_COLOR_BAR_BG` | Main bar background |
| `PROMPT_COLOR_OK_FG` / `OK_MARK` | Exit-code text / checkmark on success |
| `PROMPT_COLOR_ERR_BG` / `ERR_FG` / `ERR_MARK` | Error bar background / text / X mark |
| `PROMPT_COLOR_ROOT_FG` | Username colour when running as root |
| `PROMPT_COLOR_USER_FG` | Username@host colour for normal users |
| `PROMPT_COLOR_DIR_FG` | Working directory colour |
**Writing a custom theme file:**
Theme files live in `profile.d/themes/` and use the `.theme` extension. They
are **parsed, not executed** — no shell code runs. Only two value forms are
accepted:
```ini
# Colour variable reference (resolved from profile.d/disp.sh)
PROMPT_COLOR_DIR_FG = "$ICyan"
# Raw ANSI escape sequence (single block, 16-colour or 24-bit true-colour)
PROMPT_COLOR_BAR_BG = "\e[48;2;7;54;66m"
```
Any unknown key, unsafe value, or shell construct is discarded with a warning.
Theme files may also override the standard colour variables (`Black`, `Blue`,
`On_IBlack`, etc.) to remap the entire terminal palette used by `ls`, `grep`,
and other colour-aware tools.
True-colour themes (`solarized`, `solarized-light`) require a terminal with
24-bit colour support (Konsole, iTerm2, kitty, Alacritty, Windows Terminal).
Verify support with:
```bash
printf '\e[38;2;38;139;210mTrue colour test\e[0m\n'
```
## 5. Contact and more information
### 5.1. New users
If you use (or plan to use) `profile`, I'll be happy if you simply mail me to
let me know, especially if you don't plan to contribute. If you plan to
contribute, I'll be twice happier for sure!
### 5.2. Bugs
**profile** bug tracker is hosted on its Gitea instance. Check the
https://git.geoffray-levasseur.org/fatalerrors/profile page. If you find a bug,
you can also submit a bug report to the maintainer mail address mentioned at
the end of that document. A bug report may contain the command line parameters
where the bug happens, OS details, the module that triggered it, if any, and the
log file containing the error. Cygwin users: please note that the bash
implementation in Cygwin regularly triggers bugs on advanced code that works
fine on Linux or BSD. Please do not send syntax error bug reports if you
have not tested the same code under a real Unix environment.
Check the [FAQ](./doc/FAQ.md) and the [to-do list](./doc/todo.md) before
sending any feature request or bug report, as it might already be documented.
### 5.3. How to contribute?
You are free to improve and contribute as you wish. If you have no idea what to
do or want some direction, you can check the [to-do list](./doc/todo.md),
containing desired future improvements. Make sure you always have the latest
development version before starting your work.
Read [CONTRIBUTING.md](./doc/CONTRIBUTING.md) for code style conventions,
branch workflow, and how to submit a patch or pull request.
It's heavily recommended to use git to obtain the latest copy of the profile
tree. Make sure your git configuration is correct in order to contribute.
Please contact me to obtain push authorizations, or, if you want to submit a
patch, you can send it by mail to the maintainer of profile.
Code written in Python or Perl might be accepted as long as it is not
mobilizing a lot of dependencies (forget big frameworks). Anything that
requires installing packages not provided in a minimal Debian or CentOS
installation will probably be rejected.
If you want to make a financial contribution, please contact me by mail.
### 5.4. License, website, and maintainer
Everything except configuration files is licensed under the BSD-3 license.
Please check the license file alongside this one.
Please check [https://www.geoffray-levasseur.org/profile](https://www.geoffray-levasseur.org/profile).
You can mail the author at fatalerrors \<at\> geoffray-levasseur \<dot\> org.
-----------------------------------------------------------------------------
Documentation (c) 2021-2026 Geoffray Levasseur.
This file is distributed under the 3-clause BSD license. The complete license
agreement can be obtained at: https://opensource.org/licenses/BSD-3-Clause

97
doc/CHANGELOG.md Executable file
View File

@@ -0,0 +1,97 @@
# Changelog
All notable changes to this project will be documented in this file.
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
Versions follow `MAJOR.MINOR.PATCH-REVISION_STAGE_N` (e.g. `3.99.1-4_rc_1`).
---
## [3.99.2-4_rc_2] — 2026-04-21
### Fixed
- **`prompt.sh`** — `\$Last_Command` in PS1 was escaped, preventing the exit
code from ever appearing in the prompt (the local variable no longer exists
when PS1 is rendered by bash). Removed the backslash so the value is embedded
at `set_prompt` build time.
- **`filefct.sh``file_stats()`** — a stray unconditional `shift` after
`esac` doubled-shifted arguments already shifted by each `case` branch;
successive options such as `-H -d` were silently skipped.
- **`packages.sh``pkgs()`** — replaced the unreliable binary-presence test
(`command -v dpkg / rpm`) with the new `get_pkgmgr` function. Also corrected
a typo in the "no package manager" error message (`avialable``available`).
- **`processes.sh``kt()`** — copy-paste error: usage error message read
`"Usage: ppg <string>"` instead of `"Usage: kt <pid>"`.
### Added
- **`packages.sh``get_pkgmgr()`** — new exported helper that detects the
active package manager of the running distribution. Detection first reads
`/etc/os-release` (`ID` then `ID_LIKE`), then falls back to a
fixed-priority binary scan. Supported families: `apt`, `dnf`, `yum`,
`zypper`, `pacman`, `apk`, `portage`, `xbps`, `nix`. Returns 1 when
nothing is identified. Available to all future commands in `packages.sh`.
---
## [3.99.1-4_rc_1] — 2026
### Added
- **Theming system** — `load_theme` in `profile.d/prompt.sh` loads `.theme`
files from `profile.d/themes/` (or a custom directory set via
`PROMPT_THEME_DIR`). Theme files are **parsed, not executed** — no shell code
ever runs from a theme.
- **Built-in themes** — `default`, `dark`, `light`, `solarized` (24-bit
true-colour), `solarized-light` (24-bit), `monokai`, `monochrome`, `abyss`,
`plasma`, `adwaita`.
- **`[prompt]` section** — `PROMPT_THEME` and `PROMPT_THEME_DIR` keys to select
a theme; eleven `PROMPT_COLOR_*` keys for per-key overrides in `profile.conf`.
- **Standard colour variables as theme keys** — theme files may also override
the `disp.sh` colour variables (`Black`, `Blue`, `On_IBlack`, …) to remap the
terminal palette used by `ls`, `grep`, and colour-aware tools.
- **Module configuration defaults** — all modules now expose their hardcoded
defaults as `profile.conf` keys: `TAZ_*`, `UTAZ_*`, `EXPANDLIST_*`,
`CLEAN_*`, `RMSPC_*`, `FINDBIG_*`, `RAIN_*`, `MATRIX_*`, `SSH_DEFAULT_OPT`,
`GENPWD_*`, `PWDSCORE_*`, `BUSY_*`, `METEO_DEFAULT_CITY`, `DWL_*`,
`MYEXTIP_*`, `PKGS_*`, `PPU_*`, `KU_*`, `UPDT_DEFAULT_BRANCH`.
- **`UPDT_DEFAULT_BRANCH`** — configures the git branch used by `check_updates`
and `profile_upgrade`. Changing the key causes `profile_upgrade` to
automatically switch the local checkout to the new branch and display a
warning.
- **`doc/` directory** — `CONTRIBUTING.md`, `FAQ.md`, `profile.conf.example`
(annotated template), `todo.md`.
- **`.gitignore`** — `profile.conf` is now git-ignored so personal settings are
never accidentally staged.
### Changed
- README §2 now explains how to create `profile.conf` from
`doc/profile.conf.example` (new section 2.1 "Initial configuration").
- README §4 updated with full module-defaults tables, theming reference, and a
note about `profile.conf` being git-ignored.
- Theme values no longer carry the `export` keyword (they are not shell
variables, only data).
### Security
- `load_theme` uses a strict allowlist (no `eval`, no sourcing). Only
`PROMPT_COLOR_*` keys and known `disp.sh` colour variable names are accepted.
Values must match `\$[A-Za-z_][A-Za-z0-9_]*` or `\\e\[[0-9;]*m`; any other
value is discarded with a warning.
---
---
> **Note:** Versions prior to `3.95.x-4_beta` did not maintain a formal
> changelog. The full history of earlier changes is available through the git
> log (`git log --oneline`).
## [3.95.3-4_beta_3] — 2024
### Added
- Initial public release candidate series.
- Core modules: `compress`, `disp`, `filefct`, `fun`, `help`, `info`, `lang`,
`net`, `packages`, `processes`, `prompt`, `pwd`, `rain`, `ssh`, `updates`.
- Bar-style prompt with time, command duration, and exit code.
- `taz` / `utaz` archive helpers.
- `genpwd` / `pwdscore` password tools.
- `matrix` / `rain` screensavers.
- `profile_upgrade` with git and archive download support.

210
doc/CONTRIBUTING.md Executable file
View File

@@ -0,0 +1,210 @@
# Contributing to profile
Thank you for your interest in contributing. This document explains how to get
set up, what the conventions are, and how to submit work.
---
## 1. Before you start
- Check the [to-do list](./todo.md) to see if your idea is already planned.
- Check the [issue tracker](https://git.geoffray-levasseur.org/fatalerrors/profile/issues)
to avoid duplicate work.
- For significant changes, open an issue or contact the maintainer before
writing code — alignment on design saves everyone time.
---
## 2. Getting the source
A Git clone is mandatory for contributions:
```bash
git clone https://git.geoffray-levasseur.org/fatalerrors/profile.git
cd profile
```
Always work from the **latest commit on `master`** (or the branch you intend
to target). Stale forks cause avoidable merge conflicts.
---
## 3. Branch policy
| Branch | Purpose |
|---|---|
| `master` | Main development branch — new features and enhancements go here |
| `<version>` (e.g. `3.x`) | Maintenance branch for a released version — bugfixes backported from `master` |
**New functionality** must always target `master`.
**Bugfixes** must target the branch where the bug was introduced:
- If the bug exists in a released version, open the fix against that version's
maintenance branch first, then cherry-pick onto `master`.
- If the bug is only in `master` (unreleased), fix it directly on `master`.
- During a release-candidate cycle, bugfixes go on the `x.*` branch and are
merged back into `master` before the final release.
Do **not** add new features to a maintenance branch.
---
## 4. Versioning scheme
Versions follow the format **`MAJOR.MINOR.PATCH`** where the `MINOR` number
conveys the development stage of the next major release:
| Minor range | Stage | Rules |
|---|---|---|
| `x.90.y` | **Alpha** toward `x+1` | Stays on `master`. Development is open: new features are welcome, regressions are acceptable. |
| `x.95.y` | **Beta** toward `x+1` | The `x+1.*` maintenance branch is created at this point. No regression unless absolutely necessary; new features still allowed. |
| `x.99.y` | **RC** toward `x+1` | Bugfixes only. No new features. No regression allowed. Becomes `x+1.0.0` when stable. |
Examples: `3.90.1` is the first alpha toward `4.0`, `3.99.2` is the second
release candidate for `4.0`.
The `PATCH` number increments freely within a stage. A bump in `MINOR`
(e.g. `90``95`) always indicates a stage promotion in development phase.
Any experimental version must have it's dedicated branch.
---
## 5. Development environment
|---|---|---|
| Bash | 4.3 | Namerefs (`local -n`) required |
| shellcheck | any recent | Run before every commit |
| git | any | For contributing patches |
| bats-core | 1.x | Optional — for running the test suite |
Install shellcheck:
```bash
# Debian / Ubuntu
apt-get install shellcheck
# Fedora / RHEL
dnf install ShellCheck
# macOS
brew install shellcheck
```
---
## 6. Code style
### General rules
- **Bash only** — no external interpreters in core modules. Python or Perl is
acceptable for completely self-contained, optional utilities that have no
dependencies beyond a minimal Debian or CentOS installation.
- **4-space indentation** — no tabs.
- **`[[ … ]]`** for all conditionals — not `[ … ]`.
- **`(( … ))`** for arithmetic — not `$(( … ))` in conditionals.
- **`local`** for all function-internal variables — avoid polluting the
environment. Prefer upper case for global and lowercase for local.
- **`printf`** instead of `echo` all the time.
- **Never `eval`** — use namerefs (`local -n`), `${!varname}` indirection, or
`declare -g` instead.
- **No hardcoded defaults** — wire every configurable value through
`${VAR:-default}` and document the key in `profile.conf` and `README.md §4`.
### Function conventions
- Public functions **must** be exported: `export -f funcname`.
- Every public function **must** support `-h` / `--help` and print usage to
stdout, returning 0.
- Use `getopt` (not `getopts`) for option parsing — it handles long options and
`--` correctly.
- Follow existing error-return conventions: 0 = success, 1 = usage error,
2 = bad options, 3 = missing dependency, 4+ = runtime failure.
- Prefix all local helper variables with a short unique prefix (e.g. `_taz_`)
to prevent collisions with caller-scope variables.
### Module structure
Every new module should follow this pattern:
```bash
#!/usr/bin/env bash
# <copyright block identical to existing modules>
load_conf "<module_name>"
# --- functions ---
export -f my_function
# EOF
```
Add the `load_conf` call near the top after any variable declarations.
---
## 7. Configuration keys
When adding a configurable default:
1. Use `${MY_VAR:-hardcoded_default}` in the function body.
2. Add a commented-out entry with a description to `profile.conf`.
3. Document the key in the matching table in `README.md §4.2`.
---
## 8. Theming
New theme files go in `profile.d/themes/` with a `.theme` extension.
They are **parsed, not executed** — do not add shell logic.
See the existing themes and `README.md §4.4` for the allowed syntax.
---
## 9. Running shellcheck
```bash
shellcheck -x profile.sh profile.d/*.sh
```
All warnings must be resolved before a patch will be accepted. Accepted
suppression directives (`# shellcheck disable=SCxxxx`) require an inline
comment explaining why the suppression is necessary.
---
## 10. Submitting a contribution
### Via Git (preferred)
1. Contact the maintainer to obtain push access, or fork on the Gitea instance.
2. Create a branch: `git checkout -b feature/my-feature`.
3. Commit with a clear subject line: `module: short description (≤ 72 chars)`.
4. Push and open a pull request against `master`.
### Via patch
If you do not have push access:
```bash
git format-patch origin/master
```
Send the resulting `.patch` file(s) to
`fatalerrors <at> geoffray-levasseur <dot> org`.
### Commit message format
```
module: imperative short description
Optional longer explanation of what changed and why. Wrap at 72 characters.
Reference issue numbers if applicable: closes #42.
```
---
## 11. What will be rejected
- Code requiring packages not in a minimal Debian or CentOS install.
- Use of `eval`, `source`-based config loading, or other code-injection vectors.
- Changes that break Bash 4.3 compatibility.
- Patches without a passing `shellcheck` run.
- New functions without `--help` support.
---
## 12. Financial contributions
Contact the maintainer by mail if you wish to make a financial contribution.

208
doc/FAQ.md Executable file
View File

@@ -0,0 +1,208 @@
# Frequently Asked Questions
---
## Installation & loading
**Q: profile refuses to load and prints "This profile requires Bash 4.3 or higher."**
Your system's default shell is an older Bash (common on macOS, which ships
Bash 3.x for licensing reasons). Install a newer Bash:
```bash
# macOS
brew install bash
# then add /opt/homebrew/bin/bash to /etc/shells and chsh
```
Or point your terminal emulator at the newer binary explicitly.
---
**Q: I sourced `profile.sh` but functions are not available in sub-shells or scripts.**
All public functions are exported with `export -f`. They are available in
child Bash processes, but **not** in POSIX `sh` sub-shells. Make sure your
scripts start with `#!/usr/bin/env bash`.
---
**Q: I set `PROFILE_PATH` but profile still can't find its modules.**
`PROFILE_PATH` must be exported *before* you source `profile.sh`:
```bash
export PROFILE_PATH=/opt/profile
source /opt/profile/profile.sh
```
If set after sourcing, `MYPATH` is already locked in and the variable has
no effect.
---
**Q: Can I load profile system-wide via `/etc/profile`?**
It is not recommended. User `.bashrc` files frequently set variables that
conflict with the aliases and locale functions defined here, leading to
surprising behaviour. Per-user sourcing from `~/.bashrc` is the supported
method.
---
## Configuration
**Q: I edited `profile.conf` but my changes have no effect.**
`profile.conf` is parsed once per shell session at load time. Open a new
terminal (or `exec bash`) to pick up the changes. There is no live-reload.
---
**Q: How do I find out which configuration keys a module supports?**
Every supported key is documented with a comment in `profile.conf`.
See also `README.md §4.2` for a consolidated table.
---
**Q: A key I set in `profile.conf` is being ignored.**
Check that:
1. The key is inside the correct `[section]` header.
2. There is no leading space before the section name (`[section]` not
`[ section ]`).
3. The key is not commented out (no leading `#`).
4. The value contains no backticks or `$(…)` — these are stripped by the
parser as a security measure.
---
## Prompt & theming
**Q: How do I change the prompt theme?**
Add to `profile.conf`:
```ini
[prompt]
PROMPT_THEME = dark
```
Built-in names: `default`, `dark`, `light`, `solarized`, `solarized-light`,
`monokai`, `monochrome`, `abyss`, `plasma`, `adwaita`.
---
**Q: The solarized or solarized-light theme shows wrong colours.**
Those themes use 24-bit / true-colour ANSI sequences (`\e[38;2;R;G;Bm`).
Test your terminal:
```bash
printf '\e[38;2;38;139;210mTrue colour test\e[0m\n'
```
If you see a solid blue word your terminal supports true colour.
If you see garbage or plain text, switch to a 16-colour theme
(`dark`, `default`, etc.) or upgrade your terminal emulator.
---
**Q: I created a custom theme but `load_theme` emits "key not allowed" warnings.**
Theme files are parsed, not executed. Only `PROMPT_COLOR_*` keys and the
standard colour variable names from `disp.sh` (`Black`, `Blue`, `On_IBlack`,
…) are accepted. Any other key — including custom variables — is rejected.
See `README.md §4.4` for the full list of accepted keys and value forms.
---
**Q: Can a theme file contain shell logic or `$(…)` command substitutions?**
No, and intentionally so. Theme files are parsed line-by-line; shell
constructs are never evaluated. This is a security boundary — a malicious
theme file cannot execute code. Values must be a colour variable reference
(`$Blue`) or a raw ANSI escape literal (`\e[0;34m`).
---
## Functions
**Q: `meteo` prints "No city specified" even though I set a default.**
The key is `METEO_DEFAULT_CITY` (not `DEFAULT_CITY`), and it must be in the
`[info]` section:
```ini
[info]
METEO_DEFAULT_CITY = Paris
```
---
**Q: `dwl` fails with "no download tool found".**
`dwl` requires one of `curl`, `wget`, or `fetch` to be installed.
Install curl:
```bash
# Debian / Ubuntu
apt-get install curl
# Fedora / RHEL
dnf install curl
```
Or set `DWL_PREFERRED_TOOL` in `[net]` to whichever tool you have.
---
**Q: `pkgs` does not find packages I know are installed.**
`pkgs` delegates to `dpkg -l` (Debian/Ubuntu) or `rpm -qa` (RHEL/Fedora).
If your distribution uses a different package manager (pacman, apk, brew …)
it is not yet supported. See `doc/todo.md` for the tracking issue.
---
**Q: `profile_upgrade` says "no update available" but I know there is one.**
`check_updates` compares the content of the remote `version` file against
`$PROFVERSION`. If `UPDT_DEFAULT_BRANCH` in `[updates]` points to a different
branch than your installation, the version files may not match. Check:
```bash
cat "$MYPATH/version"
```
and make sure `UPDT_DEFAULT_BRANCH` matches the branch you track.
---
## Compatibility
**Q: Some functions misbehave on macOS / Cygwin.**
Both environments ship non-GNU userland utilities with different flags and
behaviour. profile is primarily developed and tested on Linux (Debian and
RHEL families). macOS and Cygwin bugs are low priority; patches that add
compatibility without breaking Linux support are welcome.
---
**Q: Can I use profile with ZSH?**
Not officially. Blockers include `local -A` (ZSH requires `typeset -A`) and
`local -n` namerefs. A compatibility layer is listed in `doc/todo.md` but
has not been implemented yet.
---
## Miscellaneous
**Q: How do I completely disable profile for one session?**
```bash
PROFILE_DISABLED=1 bash --norc
```
Or simply open a shell without sourcing `~/.bashrc` (`bash --norc`).
---
**Q: How do I report a bug?**
Open an issue on the
[Gitea tracker](https://git.geoffray-levasseur.org/fatalerrors/profile/issues)
or send a mail to `fatalerrors <at> geoffray-levasseur <dot> org` with:
- The exact command that triggered the bug
- Your OS and Bash version (`bash --version`)
- The module involved
- Any relevant error output

29
doc/LICENSE Executable file
View File

@@ -0,0 +1,29 @@
Copyright 2021-2026 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
This software is distributed under the BSD-3-Clause license with the
following terms and conditions:
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

252
doc/profile.conf.example Executable file
View File

@@ -0,0 +1,252 @@
# profile.conf — example / reference configuration
# Copy this file to the profile installation directory as "profile.conf"
# and uncomment / edit the keys you want to change.
#
# Format:
# [section_name] — starts a section
# key = value — sets a key (whitespace around = is optional)
# # comment — line comment
#
# All keys are optional. Sensible defaults apply when unset.
# Values must not contain backticks or $(…) — those are stripped for security.
# ------------------------------------------------------------------------------
# ==============================================================================
[system]
# Bash history settings
HISTSIZE=50000
HISTFILESIZE=100000
HISTIGNORE="&:[bf]g:exit"
# Default pager
PAGER=less
# Terminal colour capability
TERM=xterm-256color
# ==============================================================================
[compress]
# taz: Default archive format (-f/--format).
# Supported: lz (default), xz, bz2, gz, lzo, tar, zip, zst
#TAZ_DEFAULT_FORMAT=lz
# taz: Number of compression threads (0 = auto-detect CPU count).
#TAZ_DEFAULT_THREADS=0
# taz: Compression level 1 (fast/large) … 9 (slow/small).
#TAZ_DEFAULT_LEVEL=6
# utaz: Delete source archive after successful extraction (0=no, 1=yes).
#UTAZ_DEFAULT_DELETE=0
# utaz: Subdirectory creation policy.
# auto — create one only when the archive has multiple top-level entries
# always — always extract into a new subdirectory
# never — always flatten into the current directory
#UTAZ_DEFAULT_DIR_MODE=auto
# ==============================================================================
[debug]
# (no configurable keys yet)
# ==============================================================================
[disp]
# Uncomment to disable ANSI colours in profile's own output messages.
#NO_COLOR=1
# ==============================================================================
[filefct]
# expandlist: Separator between items (default: space). Use \n for newline.
#EXPANDLIST_DEFAULT_SEPARATOR=" "
# clean: Recurse into subdirectories by default (0=no, 1=yes).
#CLEAN_DEFAULT_RECURSIVE=0
# rmspc: Replacement character for spaces in filenames (default: underscore).
#RMSPC_DEFAULT_CHAR=_
# findbig: Number of results to display (default: 10).
#FINDBIG_DEFAULT_LIMIT=10
# ==============================================================================
[fun]
# busy: Hex pattern searched in /dev/urandom hexdump.
#BUSY_DEFAULT_PATTERN=ca fe
# busy: Delay between matched lines in milliseconds (0 = no delay).
#BUSY_DEFAULT_DELAY=0
# ==============================================================================
[info]
# meteo: Default city when no argument is given. Leave unset to require an
# explicit city argument every time.
#METEO_DEFAULT_CITY=Paris
# ==============================================================================
[lang]
# Comma-separated alias:locale pairs. One function is generated per entry.
# Example: SET_LOCALE="fr:fr_FR.UTF-8,us:en_US.UTF-8,es:es_ES.UTF-8"
# creates setfr, setus, setes.
#SET_LOCALE=fr:fr_FR.UTF-8,us:en_US.UTF-8
# Alias to activate at login. Must match one of the aliases defined in SET_LOCALE above.
# Example: DEFAULT_LANG=fr → calls setfr at startup.
# Leave unset to keep the system default locale.
#DEFAULT_LANG=fr
# ==============================================================================
[net]
# dwl: Force a specific download tool (curl, wget, fetch).
# Unset = auto-detect (curl preferred, then wget, then fetch).
#DWL_PREFERRED_TOOL=curl
# myextip: API endpoint for external IP lookup.
# Alternatives: https://ipinfo.io/json, https://ip-api.com/json/
#MYEXTIP_DEFAULT_URL=https://ip-api.com/json/
# ==============================================================================
[packages]
# pkgs: Case-insensitive search by default (0=no, 1=yes).
#PKGS_DEFAULT_IGNORE_CASE=0
# ==============================================================================
[processes]
# ppu: ps output columns (comma-separated ps field names).
#PPU_DEFAULT_FORMAT=pid,user,%cpu,%mem,start,time,command
# ku: Signal sent when killing a user's processes (without SIG prefix).
#KU_DEFAULT_SIGNAL=TERM
# ==============================================================================
[prompt]
# Name of the prompt theme to load, or an explicit path to a .theme file.
# Built-in themes: default, dark, light, solarized, solarized-light,
# monokai, monochrome, abyss, plasma, adwaita
# Solarized variants require a terminal with 24-bit true-colour support.
#PROMPT_THEME=default
# Directory searched for bare theme names. Defaults to profile.d/themes/.
#PROMPT_THEME_DIR=/path/to/my/themes
# Individual colour overrides — these always win over the loaded theme.
# Values must be colour variable names from disp.sh (e.g. $Blue, $On_IBlack)
# or raw ANSI escape sequences (e.g. \e[0;34m).
#
# Clock segment
#PROMPT_COLOR_TIME_FG=$Blue
#PROMPT_COLOR_TIME_BG=$On_IBlack
#
# Main bar background
#PROMPT_COLOR_BAR_BG=$On_Blue
#
# Exit-code segment — success
#PROMPT_COLOR_OK_FG=$White
#PROMPT_COLOR_OK_MARK=$Green
#
# Exit-code segment — failure
#PROMPT_COLOR_ERR_BG=$On_Red
#PROMPT_COLOR_ERR_FG=$White
#PROMPT_COLOR_ERR_MARK=$BYellow
#
# User / host
#PROMPT_COLOR_ROOT_FG=$Red
#PROMPT_COLOR_USER_FG=$Green
#
# Working directory
#PROMPT_COLOR_DIR_FG=$ICyan
# ==============================================================================
[pwd]
# genpwd: Default password length.
#GENPWD_DEFAULT_LENGTH=16
# genpwd: Maximum occurrences of any single character.
#GENPWD_DEFAULT_OCCURS=2
# genpwd: Number of passwords generated per invocation.
#GENPWD_DEFAULT_COUNT=1
# genpwd: Include symbols (1=yes, 0=no).
#GENPWD_DEFAULT_SYMBOLS=1
# genpwd: Include uppercase letters (1=yes, 0=no).
#GENPWD_DEFAULT_UPPERCASE=1
# genpwd: Include lowercase letters (1=yes, 0=no).
#GENPWD_DEFAULT_LOWERCASE=1
# genpwd: Include digits (1=yes, 0=no).
#GENPWD_DEFAULT_NUMBERS=1
# pwdscore: Show verbose breakdown by default (1=yes, 0=no).
#PWDSCORE_DEFAULT_VERBOSE=0
# ==============================================================================
[rain]
# rain: Falling speed — integer/100 gives seconds (5 → 0.05 s).
# Values < 1 are used as raw seconds.
#RAIN_DEFAULT_SPEED=5
# rain: Colour theme. Supported: white (default), green, blue, red, yellow, cyan
#RAIN_DEFAULT_COLOR=white
# matrix: Falling speed.
#MATRIX_DEFAULT_SPEED=3.5
# matrix: Colour theme. Supported: green (default), blue, red, yellow, cyan, white
#MATRIX_DEFAULT_COLOR=green
# matrix: Character set. Supported: binary (default), kana, ascii
#MATRIX_DEFAULT_CHARSET=binary
# ==============================================================================
[ssh]
# ssr: Default SSH options prepended to every ssr call.
# Examples:
# SSH_DEFAULT_OPT=-Y # X11 forwarding
# SSH_DEFAULT_OPT=-Y -o StrictHostKeyChecking=accept-new
# SSH_DEFAULT_OPT= # no default options
#SSH_DEFAULT_OPT=-Y
# ==============================================================================
[updates]
# Git branch used for update checks and profile_upgrade.
# Changing this causes profile_upgrade to automatically switch the local
# checkout to the new branch on the next run and display a warning.
#UPDT_DEFAULT_BRANCH=master
# ==============================================================================
[general]
# General-purpose section — set any environment variable you need globally.
# Good place for compilation flags, personal PATH additions, etc.
#CFLAGS=-O2 -pipe -march=native
#CXXFLAGS=$CFLAGS
#MAKEFLAGS=-j4
#PKGSOURCES=/usr/local/src
# ==============================================================================
[aliases]
# Command aliases loaded for interactive shells only.
# The value is the full command string; the key becomes the alias name.
# The base command must be executable; if not, the alias is silently skipped.
#
# ls
ll=ls -laFh --color=auto
la=ls -Ah --color=auto
l=ls -CF --color=auto
ls=ls --color=auto
#
# grep
grep=grep --color=auto
egrep=egrep --color=auto
fgrep=fgrep --color=auto
#
# disk usage
df=df -H
du=du -ch
#
# make shortcuts
#mk=make
#mkin=make install
# End of profile.conf.example

274
doc/profile.conf.fatalerrors Executable file
View File

@@ -0,0 +1,274 @@
[system]
# System section is used to set Bash behavior and other system related
# variables, such as the default pager, the terminal type, etc.
# Set bash history
HISTSIZE=50000
HISTIGNORE="&:[bf]g:exit"
# Set default pager
PAGER=less
# Set terminal colors behavior
TERM=xterm-256color
[compress]
# Section used by compress.sh (taz and utaz functions).
# taz: Default archive format when -f/--format is not specified.
# Supported values: lz (default), xz, bz2, gz, lzo, tar
#TAZ_DEFAULT_FORMAT=lz
# taz: Default number of compression threads when -p/--parallel is not specified.
# Set to the number of CPU cores to use parallel compression where supported.
#TAZ_DEFAULT_THREADS=1
# taz: Default compression level when none is given (1=fast/big .. 9=slow/small).
#TAZ_DEFAULT_LEVEL=6
# utaz: Delete source archives after successful extraction (0=no, 1=yes).
#UTAZ_DEFAULT_DELETE=0
# utaz: Default directory creation mode when neither --create-dir nor --no-dir is given.
# Supported values:
# auto (default) — create a subdirectory only when the archive contains
# multiple top-level entries or a bare file
# always — always extract into a new subdirectory
# never — always flatten extraction into the current directory
#UTAZ_DEFAULT_DIR_MODE=auto
[debug]
# Section used by debug.sh (nothing yet)
[disp]
# Section used by disp.sh
# Set to any value to disable colors in internal profile output (not controling binary output)
# NO_COLOR=1
[filefct]
# Section used by filefct.sh
# expandlist: Default output separator between items (default: space).
# Use \n for newline, or any other character/string.
#EXPANDLIST_DEFAULT_SEPARATOR=" "
# clean: Enable recursive mode by default (0=no, 1=yes).
#CLEAN_DEFAULT_RECURSIVE=0
# rmspc: Default character used to replace spaces in filenames (default: underscore).
# Set to empty to concatenate words without separator (equivalent to --subst-char with no value).
#RMSPC_DEFAULT_CHAR=_
# findbig: Default number of results to return (default: 10).
#FINDBIG_DEFAULT_LIMIT=10
[fun]
# Section used by fun.sh (busy function).
# busy: Default hex pattern to search for in /dev/urandom hexdump (default: "ca fe").
#BUSY_DEFAULT_PATTERN=ca fe
# busy: Default delay between matched lines in milliseconds (default: 0 = no delay).
#BUSY_DEFAULT_DELAY=0
[info]
# Section used by info.sh
# meteo: Default city used when no city argument is given.
# Unset or empty disables the fallback and requires an explicit city argument.
METEO_DEFAULT_CITY="Toulouse"
[lang]
# Section used by lang.sh
# List of locale shortcuts to build, in the form "shortcut:locale,...".
# Generate a function setXX for each shortcut defined.
SET_LOCALE="fr:fr_FR.UTF-8,us:en_US.UTF-8"
[net]
# Section used by net.sh (dwl, myextip functions).
# dwl: Force a specific download tool instead of auto-detecting (curl → wget → fetch).
# Supported values: curl, wget, fetch. Unset uses auto-detection (default).
#DWL_PREFERRED_TOOL=curl
# myextip: API endpoint URL used to retrieve external IP information.
# Default: https://ip-api.com/json/
# Compatible alternatives: https://ipinfo.io/json, https://ip-api.com/json/
#MYEXTIP_DEFAULT_URL=https://ip-api.com/json/
[packages]
# Section used by packages.sh
# pkgs: Enable case-insensitive search by default (1=yes, 0=no; default: 0).
#PKGS_DEFAULT_IGNORE_CASE=0
[processes]
# Section used by processes.sh
# ppu: Output columns passed to ps -o when listing processes for a user.
# Comma-separated list of ps field names. Default: pid,user,%cpu,%mem,start,time,command
#PPU_DEFAULT_FORMAT=pid,user,%cpu,%mem,start,time,command
# ku: Signal sent to processes when killing a user's session (default: TERM).
# Use signal names without the SIG prefix (e.g. TERM, KILL, HUP).
#KU_DEFAULT_SIGNAL=TERM
[prompt]
# Section used by prompt.sh
# Name of the theme to load, or an explicit path to a .theme file.
# Bare names are resolved as $PROMPT_THEME_DIR/<name>.theme.
# When unset, no theme is loaded and the hardcoded fallback colours are used.
#PROMPT_THEME=default
# Directory that contains .theme files. Defaults to profile.d/themes/ inside
# the profile installation directory.
#PROMPT_THEME_DIR=/path/to/themes
# Individual colour overrides. These always win over the loaded theme.
# Values must be valid ANSI escape sequences as exported by disp.sh, e.g.:
# \e[0;34m (Blue) \e[42m (On_Green) \e[1;32m (BGreen)
# All keys listed below correspond to variables exported by disp.sh.
# Clock segment
#PROMPT_COLOR_TIME_FG=$Blue
#PROMPT_COLOR_TIME_BG=$On_IBlack
# Main bar background (success and info)
#PROMPT_COLOR_BAR_BG=$On_Blue
# Exit-code segment — success state
#PROMPT_COLOR_OK_FG=$White
#PROMPT_COLOR_OK_MARK=$Green
# Exit-code segment — failure state
#PROMPT_COLOR_ERR_BG=$On_Red
#PROMPT_COLOR_ERR_FG=$White
#PROMPT_COLOR_ERR_MARK=$BYellow
# User/host colours
#PROMPT_COLOR_ROOT_FG=$Red
#PROMPT_COLOR_USER_FG=$Green
# Working directory
#PROMPT_COLOR_DIR_FG=$ICyan
[pwd]
# Section used by pwd.sh (genpwd and pwdscore functions).
# genpwd: Default password length (default: 16).
#GENPWD_DEFAULT_LENGTH=16
# genpwd: Maximum occurrences of any single character (default: 2).
#GENPWD_DEFAULT_OCCURS=2
# genpwd: Number of passwords generated when no count argument is given (default: 1).
#GENPWD_DEFAULT_COUNT=1
# genpwd: Include symbols in the character pool (1=yes, 0=no; default: 1).
#GENPWD_DEFAULT_SYMBOLS=1
# genpwd: Include uppercase letters in the character pool (1=yes, 0=no; default: 1).
#GENPWD_DEFAULT_UPPERCASE=1
# genpwd: Include lowercase letters in the character pool (1=yes, 0=no; default: 1).
#GENPWD_DEFAULT_LOWERCASE=1
# genpwd: Include digits in the character pool (1=yes, 0=no; default: 1).
#GENPWD_DEFAULT_NUMBERS=1
# pwdscore: Show verbose breakdown by default (1=yes, 0=no; default: 0).
#PWDSCORE_DEFAULT_VERBOSE=0
[rain]
# Section used by rain.sh (rain and matrix functions).
# rain: Default speed value, using the /100 scale (5 => 0.050s, 10 => 0.100s).
# Values < 1 are interpreted as raw seconds.
#RAIN_DEFAULT_SPEED=5
# rain: Default color theme.
# Supported values: white (default), green, blue, red, yellow, cyan
#RAIN_DEFAULT_COLOR=white
# matrix: Default speed value, using the /100 scale (3.5 => 0.035s).
#MATRIX_DEFAULT_SPEED=3.5
# matrix: Default color theme.
# Supported values: green (default), blue, red, yellow, cyan, white
#MATRIX_DEFAULT_COLOR=green
# matrix: Default character set.
# Supported values: binary (default), kana, ascii
MATRIX_DEFAULT_CHARSET=kana
[ssh]
# Section used by ssh.sh
# ssr: Default SSH options prepended to every ssr invocation.
# Options are word-split, so space-separated flags are supported.
# The default behaviour without this key is equivalent to: SSH_DEFAULT_OPT=-Y
# Set to an empty value to pass no default options.
# Examples:
# SSH_DEFAULT_OPT=-Y # X11 forwarding (original default)
# SSH_DEFAULT_OPT=-Y -o StrictHostKeyChecking=accept-new
# SSH_DEFAULT_OPT= # no default options
#SSH_DEFAULT_OPT=-Y
[updates]
# Section used by updates.sh
#
# UPDT_DEFAULT_BRANCH — Git branch used for update checks and upgrades.
# Defaults to 'master' when unset. Changing this value will cause
# profile_upgrade to automatically switch the local checkout to the new
# branch on the next upgrade and display a warning.
#UPDT_DEFAULT_BRANCH=master
[general]
# General section allow to set any variable that can be used by the user.
# It is also a good place to set freely global variables for personal use.
# Set some compiling values
CFLAGS="-O2 -pipe -march=native"
CXXFLAGS="$CFLAGS"
MAKEFLAGS='-j12'
PKGSOURCES='/share/src/archives'
[aliases]
# Aliases section is used to set user aliases, it is loaded only for
# interactive shells.
# Various ls aliases
ll='ls -laFh --color=auto'
la='ls -Ah --color=auto'
l='ls -CF --color=auto'
ls='ls --color=auto'
# Add color to grep output
grep='grep --color=auto'
egrep='egrep --color=auto'
fgrep='fgrep --color=auto'
# Quick find alias
qfind="find . -name "
# Some alias for compiling
mk='make'
mkck='make check'
mkin='make install'
mkdin='make DESTDIR=$PWD/dest-install install'
# ssh alias with X11 forwarding, without right restriction
ssh='ssh -Y'
# Resume mode for wget
wget='wget -c' # resume mode by default
# Human readable by default
df='df -H'
du='du -ch'
sdu='du -sk ./* | sort -n'
hdu='du -hs ./* | sort -H'
# Readable dmesg timestamps
dmesg='dmesg -T'
# End of profile.conf

114
doc/todo.md Executable file
View File

@@ -0,0 +1,114 @@
# profile — to-do list
Items marked **[easy]**, **[medium]**, or **[hard]** give a rough effort hint.
Items marked **[breaking]** would change existing behaviour and require a
version-bump.
---
## Shell / compatibility
- [ ] **ZSH compatibility layer** — most functions work already; the remaining
blockers are `local -A` (no associative arrays in ZSH without `typeset -A`)
and `local -n` namerefs. A thin compatibility shim would open the project to
ZSH users. **[hard]**
- [ ] **Bash completion** — add a `profile.d/completion/` directory and write
`_profile_upgrade`, `_taz`, `_utaz`, `_meteo`, etc. completions so that
`<Tab>` works on all public functions. **[medium]**
---
## Prompt & theming
- [ ] **Git branch in prompt** — show the current branch name (and dirty
indicator) in the PS1 bar when inside a Git repository. Should be
gated behind a `[prompt]` config key so it can be disabled. **[medium]**
- [ ] **Virtual-env / conda indicator** — detect `$VIRTUAL_ENV` / `$CONDA_DEFAULT_ENV`
and display the name in the prompt bar. **[easy]**
- [ ] **True-colour terminal auto-detection** — query `$COLORTERM` and
`$TERM` at load time; automatically fall back from a 24-bit theme to its
16-colour equivalent when the terminal does not support true colour. **[medium]**
- [ ] **True-colour variants of other themes** — create `monokai-tc.theme`,
`abyss-tc.theme`, etc. using the same `\e[38;2;R;G;Bm` approach as the
Solarized themes. **[easy]** _(per theme)_
- [ ] **Theme preview command** — add a `theme_preview` (or `profile_theme`)
function that renders a colour swatch and a sample prompt line for the
currently loaded theme, so users can evaluate themes without reloading
the session. **[medium]**
- [ ] **`load_theme` — DEFAULTCOL rebuild** — after overriding `DEFAULTFG` and
`DEFAULTBG`, automatically recompute `DEFAULTCOL` from the new values instead
of requiring the theme file to set it explicitly. **[easy]**
---
## Module improvements
### compress
- [ ] **`taz` progress bar** — show a `pv` / `dd`-based progress indicator when
compressing large trees, gated behind a `-p` flag. **[medium]**
- [ ] **`utaz` integrity check** — run `tar -tOf` / `unzip -t` / `7z t` before
extracting and abort if the archive is corrupt. **[easy]**
### filefct
- [ ] **`findbig` / `findzero` / `finddead``fd` integration** — optionally
use `fd` instead of `find` when available for faster traversal. **[easy]**
- [ ] **`file_stats` — human-readable totals** — add `--human` flag to emit
sizes in K/M/G instead of bytes. **[easy]**
### info
- [ ] **`showinfo` fallback** — when neither `neofetch` nor `fastfetch` is
installed, print a minimal sysinfo block (hostname, OS, kernel, uptime,
CPU, RAM) using pure Bash + `/proc`. **[medium]**
### net
- [ ] **`dwl` resume support** — pass `-C -` to curl / `--continue-at -` to
wget for interrupted downloads; gate behind a `-r` flag. **[easy]**
- [ ] **`myextip` multiple providers** — fall back to a secondary URL
(configurable via `MYEXTIP_FALLBACK_URL`) when the primary times out.
**[easy]**
### processes
- [ ] **`ku` dry-run flag** — add `-n` / `--dry-run` to print what would be
killed without acting. **[easy]**
### pwd
- [ ] **`genpwd` passphrase mode** — add `-w` / `--words N` to generate
word-based passphrases (diceware-style) from `/usr/share/dict/words`.
**[medium]**
### ssh
- [ ] **SSH agent management** — add `ssh_agent_start` / `ssh_agent_stop` helpers
that start a persistent `ssh-agent`, add configured keys, and survive
re-login via a socket stored in `~/.ssh/agent.env`. **[medium]**
- [ ] **`rmhost` glob support** — allow `rmhost '*.example.com'` to remove all
matching entries in one call. **[easy]**
### updates
- [ ] **Automatic update check age** — store a timestamp in `~/.cache/profile_last_check`;
skip the network request in `check_updates -q` if the last check was less
than `UPDT_CHECK_INTERVAL` hours ago (configurable, default 24). **[medium]**
- [ ] **Changelog display** — after a successful `profile_upgrade`, fetch and
display `CHANGELOG.md` entries newer than the previously installed version.
**[medium]**
---
## Infrastructure
- [ ] **Test suite** — add a `test/` directory with `bats` (Bash Automated
Testing System) unit tests for pure functions (`expandlist`, `genpwd`,
`pwdscore`, `isipv4`, `isipv6`, `parse_conf`, `load_theme`, …). Target for
CI. **[hard]**
- [ ] **CI pipeline** — Gitea Actions (or similar) job that runs `shellcheck`
and the `bats` suite on every push. **[medium]**
- [ ] **`profile_status` function** — print a diagnostic summary: installed
version, active theme, loaded modules, detected Bash version, interactive /
login shell flags, and `profile.conf` path. **[easy]**
- [ ] **`profile_uninstall` function** — remove the `source` line from
`~/.bashrc` / `~/.profile` and optionally delete the install directory,
with a dry-run mode. **[medium]**
- [ ] **`disp` syslog integration** — add a `DISP_SYSLOG=1` config key that
additionally pipes E/W messages to `logger`. **[easy]**
- [ ] **XDG base-dir support** — honour `$XDG_CONFIG_HOME` as an alternative
location for `profile.conf` so users can keep `~` tidy. **[medium]**
**[breaking]**

147
history.txt Normal file
View File

@@ -0,0 +1,147 @@
------------------------------------------------------------------------------
Initial version from Beyond Linux From Scratch by
* James Robertson <jameswrobertson@earthlink.net>
* Dagmar d'Surreal <rivyqntzne@pbzpnfg.arg>
------------------------------------------------------------------------------
Current version from Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
------------------------------------------------------------------------------
Version history:
------------------------------------------------------------------------------
# 05/03/2026 v3.6.1
Fix a typo in compress.sh
# 05/03/2026 v3.6.0
Improved utaz to make it multiformat with lot of it
Introduced ppu and ppn
Improved update system
# 04/03/2026 v3.5.0
rain has now configurable speed and color
showinfo adapted to fastfetch, replacing neofetch
# 24/02/2022 v3.3.1
Fixed version detection
Added "busy" function
Fixed use of library functions before it's loaded
# 28/11/2022 v3.3.0
Initial version update support
Changed versioning code
Added installation path detection
# 28/11/2022 v3.2.3
Made proper readme file, to improve
# 21/11/2022 v3.2.2
Fixed taz compression level analysis
Fixed typo in dpkgs
# 20/11/2022 v3.2.1
Fix some messages
Make dpkgs rpm aware (more to come)
Removed version history from main script and revert declaration order
Added required license information in all files
Completed LICENSE file
# 18/11/2022 v3.2.0
Created disp command for display and make use of it
# 10/11/2022 v3.1.1
genpwd: test if password is doable
# 08/11/2022 v3.1.0
Added password generator
# 07/11/2022 v3.0.1
Added concatenation to rmspc
Added ku
Error managed in meteo
# 27/08/2022 v3.0.0
Splitted everything in several files
Added rain screensaver
# 29/07/2022 v2.8.2
Added warning for non bash or zsh users
# 19/07/2022 v2.8.1
Few cleanups, fixes and optimizations
# 24/06/2022 v2.8.0
Added backtrace, error and settrace
[bugfix] corrected showinfo
# 22/06/2022 v2.7.1
[bugfix] few minor corrections
Added help command
# 21/06/2022 v2.7.0
Added isipv4 and isipv6 and use it in rmhost as an improvement
Removed konsole save and restore not working
# 18/10/2021 v2.6.3
Changed PS1 for status bar style version
Few minor improvements
# 26/02/2021 v2.6.2
[bugfix] taz: corrected bug with trailing slash on directories
# 25/12/2020 v2.6.1
Add check on rmhost
Improvements rmspc
Created expendlist
# 24/10/2020 v2.6.0
Added session save and restore for Konsole
# 11/09/2020 v2.5.3
Few more aliases, improved code consistancy and typo,
Improved utaz, removed showdiskmap, removed remaining French,
Added license information for future publication
# 06/03/2020 v2.5.2
Few aliases sorted out
# 05/03/2020 v2.5.1
Language consistancy fix
Added pigz support in taz
# 03/03/2020 v2.5.0
Added command taz and rmspc
Renamed auzip => utaz and improved it
# 02/03/2020 v2.4.0
Added command auzip
# 31/01/2020 v2.3.2
Figlet: changed default font to ansi_shadow
# 16/01/2020 v2.3.1
[bugfix] non-interactive were blocked with some functions
# 08/01/2020 v2.3.0
Added use of figlet and neofetch as a motd replace
# 16/12/2019 v2.2.0
Added showinfo
Primary write of showdiskmap
# 24/09/2019 v2.1.2
[bugfix] bug in profile version display
# 23/09/2019 v2.1.1
[bugfix] dpkgs
# 16/09/2018 v2.1.0
Added rmhost, setc, setfr
More locales management
# 04/02/2017 v2.0.1
clean improvements (--shell)
# 24/10/2015 v2.0.0
Added advanced functionnalities (clean, srr, etc.)
# 16/02/2013 v1.0.0
Initial version

View File

@@ -1,127 +1,433 @@
#!/usr/bin/env bash
# ------------------------------------------------------------------------------
# Smartly uncompress archives (zip only)
# Copyright (c) 2013-2026 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
# Protected by the BSD3 license. Please read bellow for details.
#
# * Redistribution and use in source and binary forms,
# * with or without modification, are permitted provided
# * that the following conditions are met:
# *
# * Redistributions of source code must retain the above
# * copyright notice, this list of conditions and the
# * following disclaimer.
# *
# * Redistributions in binary form must reproduce the above
# * copyright notice, this list of conditions and the following
# * disclaimer in the documentation and/or other materials
# * provided with the distribution.
# *
# * Neither the name of the copyright holder nor the names
# * of any other contributors may be used to endorse or
# * promote products derived from this software without
# * specific prior written permission.
# *
# * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
# * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
# * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
# * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# * OF SUCH DAMAGE.
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Smartly uncompress archives
# Usage: utaz [option] [directorie(s)|file(s)]
# Options:
# -h, --help Display that help screen
# -d, --delete If decompression succeeded, delete the source file
# -c, --create-dir Always create a host directory
# -n, --no-dir Never create a host directory
utaz()
{
for opt in $@ ; do
case $opt in
"-h"|"--help")
echo "utaz: uncompress all the given files and/or the ones found in the given"
echo " directories creating an host directory where needed."
echo
echo "Usage: utaz [option] [directorie(s)|file(s)]"
echo
echo "Options:"
echo " -h, --help Display that help screen"
echo " -d, --delete If decompression succeeded, delete the source file"
echo " -c, --create-dir Always create a host directory"
echo " -n, --no-dir Never create a host directory"
echo
# shellcheck disable=SC2329
_ununzip()
{
unzip -o "$1" -d "$2" >/dev/null 2>&1
}
# shellcheck disable=SC2329
_untar()
{
tar -xf "$1" -C "$2"
}
# shellcheck disable=SC2329
_ungzip()
{
tar -xzf "$1" -C "$2"
}
# shellcheck disable=SC2329
_unbzip2()
{
tar -xjf "$1" -C "$2"
}
# shellcheck disable=SC2329
_unxz()
{
tar -xJf "$1" -C "$2"
}
# shellcheck disable=SC2329
_unlzop()
{
lzop -d "$1" -o "$2/$(basename "${1%.*}")"
}
# shellcheck disable=SC2329
_unlzip()
{
if command -v plzip >/dev/null 2>&1; then
plzip -d -c "$1" > "$2/$(basename "${1%.*}")"
else
lzip -d -c "$1" > "$2/$(basename "${1%.*}")"
fi
}
# shellcheck disable=SC2329
_ununrar()
{
unrar x -o+ "$1" "$2/" >/dev/null 2>&1
}
# shellcheck disable=SC2329
_ununarj()
{
unarj e "$1" "$2/" >/dev/null 2>&1
}
# shellcheck disable=SC2329
_unlha()
{
# lha typically extracts into the current directory
# We ensure it hits the target directory
(cd "$2" && lha -x "../$1") >/dev/null 2>&1
}
# shellcheck disable=SC2329
_ununace()
{
unace x "$1" "$2/" >/dev/null 2>&1
}
# shellcheck disable=SC2329
_un7z()
{
7z x "$1" -o"$2/" >/dev/null 2>&1
}
# shellcheck disable=SC2329
_unzstd()
{
# Zstd decompresses files directly, often requiring tar for archives
tar --zstd -xf "$1" -C "$2"
}
# shellcheck disable=SC2329
_uncpio()
{
# CPIO requires careful directory handling
(cd "$2" && cpio -id < "../$1") >/dev/null 2>&1
}
# shellcheck disable=SC2329
_uncabextract()
{
# Requires 'cabextract' package
cabextract "$1" -d "$2/" >/dev/null 2>&1
}
# shellcheck disable=SC2329
_undeb()
{
# Extracts data content from a Debian package
dpkg-deb -x "$1" "$2/" >/dev/null 2>&1
}
# shellcheck disable=SC2329
_unrpm()
{
# Extracts CPIO-based payload from an RPM package
# Needs rpm2cpio and cpio
rpm2cpio "$1" | (cd "$2/" && cpio -idmv) >/dev/null 2>&1
}
local PARSED
PARSED=$(getopt -o hdcn --long help,delete,create-dir,no-dir -n 'utaz' -- "$@")
# shellcheck disable=SC2181 # getopt return code is checked immediately after
if [ $? -ne 0 ]; then
disp E "Invalid options, use \"utaz --help\" to display usage."
return 1
fi
eval set -- "$PARSED"
while true; do
case "$1" in
-h|--help)
printf "utaz: uncompress all the given files and/or the ones found in the given\n"
printf " directories creating an host directory where needed.\n\n"
printf "Usage: utaz [option] [directorie(s)|file(s)]\n\n"
printf "Options:\n"
printf "\t-h, --help\t\tDisplay that help screen\n"
printf "\t-d, --delete\t\tIf decompression succeeded, delete the source file\n"
printf "\t-c, --create-dir\tAlways create a host directory\n"
printf "\t-n, --no-dir\t\tNever create a host directory\n\n"
printf "Supported archive format:\n"
printf "\t- zip\n"
printf "\t- tar.gz, .tgz\n"
printf "\t- tar.bz2, .tbz2\n"
printf "\t- tar.xz, .txz\n"
printf "\t- tar.lz, .tlz\n"
printf "\t- rar\n"
printf "\t- arj\n"
printf "\t- lha, lzh\n"
printf "\t- ace\n"
printf "\t- 7z, p7z\n"
printf "\t- zst\n"
printf "\t- cpio\n"
printf "\t- cab\n"
printf "\t- deb\n"
printf "\t- rpm\n"
return 0
;;
"-d"|"--delete")
-d|--delete)
local willrm=1
shift
;;
"-c"|"--create-dir")
-c|--create-dir)
local createdir=1
shift
;;
"-n"|"--no-dir")
-n|--no-dir)
local nodir=1
shift
;;
"-"*)
echo "Invalid option, use \"utaz --help\" to display options list"
echo
return 1
--)
shift
break
;;
*)
# The ${opt%/} writing is to remove trailing / if any
local LIST="$LIST ${opt%/}"
disp E "Invalid option, use \"utaz --help\" to display options list"
return 1
;;
esac
done
# The remaining arguments after -- are the files/directories to process,
# "." is used if none is given
local FILES=("$@")
[[ ${#FILES[@]} -eq 0 ]] && FILES=(".")
[[ $createdir && $nodir ]] && echo "*** Error: --create-dir and --no-dir options are mutually exclusive."
# Apply defaults from [compress] configuration if not overridden by flags
[[ -z ${willrm+x} ]] && [[ ${UTAZ_DEFAULT_DELETE:-0} -eq 1 ]] && willrm=1
if [[ -z ${createdir+x} && -z ${nodir+x} ]]; then
case "${UTAZ_DEFAULT_DIR_MODE:-auto}" in
always) createdir=1 ;;
never) nodir=1 ;;
esac
fi
[[ ! $LIST ]] && local LIST="."
[[ -n ${createdir} && -n ${nodir} ]] && \
disp E "The --create-dir and --no-dir options are mutually exclusive."
for zitem in $LIST; do
[[ $(ls $zitem/*.zip 2> /dev/null | wc -l) -eq 0 ]] &&
echo "$zitem contains no supported archive file, skipping." &&
for zitem in "${FILES[@]}"; do
# Build list of input files to process, with whitespace-safe handling.
local targets=()
if [[ -f "$zitem" ]]; then
targets=("$zitem")
elif [[ -d "$zitem" ]]; then
mapfile -d '' -t targets < <(find "$zitem" -mindepth 1 -maxdepth 1 -print0 2>/dev/null)
if [[ ${#targets[@]} -eq 0 ]]; then
disp I "Directory ${zitem} is empty, skipping."
continue
fi
else
disp W "Path ${zitem} is not a file or directory, skipping."
continue
fi
for f in $zitem/*.zip; do
echo -n "Processing archive $zitem/$f... "
local dir=${f::-4}
mkdir -p $dir
[[ $? -gt 0 ]] &&
echo "[ filesystem can't create directories, exit ]" &&
return 1
unzip -o $f -d $dir > /dev/null 2>&1
case $? in
0)
[[ $willrm ]] && rm -f $f && echo -n "Deleted ! "
for f in "${targets[@]}"; do
local dir="${f%.*}"
local extractor=""
case "$f" in
*.zip)
extractor="_ununzip"
;;
1)
echo "No deletion on warnings "
*.tar.gz|*.tgz)
extractor="_ungzip"
;;
*.tar.bz2|*.tbz2)
extractor="_unbzip2"
;;
*.tar.xz|*.txz)
extractor="_unxz"
;;
*.tar.lz|*.tlz)
extractor="_unlzop"
;;
*.tar)
extractor="_untar"
;;
*.rar)
extractor="_ununrar"
;;
*.arj)
extractor="_ununarj"
;;
*.lzh|*.lha)
extractor="_unlha"
;;
*.ace)
extractor="_ununace"
;;
*.7z|*.p7z)
extractor="_un7z"
;;
*.zst)
extractor="_unzstd"
;;
*.cpio)
extractor="_uncpio"
;;
*.cab)
extractor="_uncabextract"
;;
*.deb)
extractor="_undeb"
;;
*.rpm)
extractor="_unrpm"
;;
*)
echo "[ zip file corrupted, failed ]"
rm -rf $dir > /dev/null 2>&1
disp I "File ${f} is not a supported archive, skipping."
continue
;; # Skip non-archive files
esac
# Verify binary existence
local cmd=${extractor//_un/}
if [[ $cmd == "deb" ]]; then
command -v -- dpkg-deb >/dev/null 2>&1 || {
disp E "The program 'dpkg-deb' is not installed, aborting."
continue
}
elif [[ $cmd == "rpm" ]]; then
command -v -- rpm2cpio >/dev/null 2>&1 || {
disp E "The program 'rpm2cpio' is not installed, aborting."
continue
}
command -v -- cpio >/dev/null 2>&1 || {
disp E "The program 'cpio' is not installed, aborting."
continue
}
else
command -v -- "${cmd}" >/dev/null 2>&1 || {
disp E "Binary ${cmd} necessary to extract ${f} is missing."
continue
}
fi
disp I "Processing archive ${f} with ${extractor}..."
if ! mkdir -p "${dir}"; then
disp E "The filesystem can't create directories, exit!" &&
return 1
fi
${extractor} "${f}" "${dir}"
case $? in
0)
[[ -n ${willrm} ]] &&
rm -f "${f}" && disp I "File ${zitem}/${f} deleted."
;;
1)
if [[ -n ${willrm} ]]; then
disp W "Compression program returned a warning: deletion canceled."
else
disp W "Compression program returned a warning."
fi
;;
*)
disp E "The compressed file ${f} seems corrupted, failed."
rm -rf "${dir}" >/dev/null 2>&1
continue
;;
esac
if [[ $createdir ]]; then
echo -n "[ subdir created, "
elif [[ $nodir ]]; then
mv ./$dir/* ./ && rmdir $dir
echo -n "[ No subdir, "
if [[ -n ${createdir} ]]; then
disp I "Archive extracted successfully in subdirectory."
elif [[ -n ${nodir} ]]; then
shopt -s nullglob
for child in "${dir}"/*; do
mv -- "$child" .
done
shopt -u nullglob
rmdir -- "${dir}"
disp I "Archive extracted successfully, no subdirectory needed."
else
subdirs=$(find $dir -maxdepth 1 | wc -l)
if [[ $subdirs -eq 2 ]]; then
mv ./$dir/* ./ && rmdir $dir
echo -n "[ No subdir, "
# Set nullglob to ensure the array is empty if no files match
shopt -s nullglob
local contents=( "${dir}"/* )
# Check if exactly one item exists and if that item is a directory
if [[ ${#contents[@]} -eq 1 ]] && [[ -d "${contents[0]}" ]]; then
# Single directory detected
shopt -s nullglob
for child in "${contents[0]}"/*; do
mv -- "$child" .
done
shopt -u nullglob
rmdir -- "${dir}"
disp I "Archive extracted successfully, no subdirectory needed."
else
echo -n "[ subdir created, "
disp I "Archive extracted successfully in subdirectory."
fi
shopt -u nullglob
fi
echo " OK ]"
done
done
}
export -f utaz
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Compress directories or files into one or more archive
# ------------------------------------------------------------------------------
# Usage: taz [option] [--parallel=<n>] [--format=<format>] [directory1 ... directoryN]
# Options:
# -h, --help Display that help screen
# -d, --delete Delete source file or directory after success
# -f, --format Chose archive format in the given list. If several format are
# given, the smalest is kept
# -p, --parallel Number of threads to use (if allowed by underlying utility)
# -v, --verbose Display progress where possible
# -q, --quiet Display less messages (only errors and warnings)
# -1, .., -9 Compression level to use [1=fast/biggest, 9=slow/smallest]
taz()
{
# shellcheck disable=SC2329
_doxz()
{
command -v xz >/dev/null 2>&1 || {
echo -e >&2 "\t*** The program 'xz' is not installed, aborting."
disp E "The program 'xz' is not installed, aborting."
return 127
}
[[ $4 ]] && local verb='-v'
local verb=()
[[ $4 ]] && verb=('-v')
# Display a warning for this format
echo -e "\t! Warning: xz format is not suited for long term archiving."
echo -e "\t See https://www.nongnu.org/lzip/xz_inadequate.html for details."
disp W "xz format is not suited for long term archiving."
disp I "See https://www.nongnu.org/lzip/xz_inadequate.html for details."
# Compresse to xz (lzma2) - Deprecated
xz $verb --compress --keep -$3 -T $2 $1
xz "${verb[@]}" --compress --keep "-$3" -T "$2" "$1"
return $?
}
# shellcheck disable=SC2329
_dolz()
{
local procopt="--threads $2"
@@ -129,23 +435,26 @@ taz ()
command -v plzip >/dev/null 2>&1 || {
command -v lzip >/dev/null 2>&1 || {
echo -e >&2 "\t*** Program 'plzip' or 'lzip' are not installed, aborting."
disp E "Program 'plzip' or 'lzip' are not installed, aborting."
return 127
}
local command=lzip
command=lzip
local procopt=""
[[ $2 -gt 1 ]] &&
echo -e "\t! Warning: lzip doesn't support multithreading, falling back to 1 thread." &&
echo -e "\t* Consitder installing plzip to obtain multithreading abilities."
disp W "lzip doesn't support multithreading, falling back to 1 thread." &&
disp W "Consider installing plzip to obtain multithreading abilities."
}
[[ $4 ]] && local verb="-vv"
local opt=()
[[ $4 ]] && opt=('-vv')
opt+=("$procopt")
# Compresse au format lzip (lzma)
$command $verb $procopt --keep -$3 $1
$command "${opt[@]}" --keep "-$3" "$1"
return $?
}
# shellcheck disable=SC2329
_dogz()
{
local procopt="--processes $2"
@@ -153,23 +462,26 @@ taz ()
command -v pigz >/dev/null 2>&1 || {
command -v gzip >/dev/null 2>&1 || {
echo -e >&2 "\t*** Programs 'pigz' or 'gzip' are not installed, aborting."
disp E "Programs 'pigz' or 'gzip' are not installed, aborting."
return 127
}
local command="gzip --compress"
local procopt=""
[[ $2 -gt 1 ]] &&
echo -e "\t! Warning: gzip doesn't support multithreading, falling back to 1 thread." &&
echo -e "\t* Consitder installing pigz to obtain multithreading abilities."
disp W "gzip doesn't support multithreading, falling back to 1 thread." &&
disp W "Consider installing pigz to obtain multithreading abilities."
}
[[ $4 ]] && local verb="--verbose"
local opt=()
[[ $4 ]] && opt=('--verbose')
opt+=("$procopt")
# Compresse au format bz2
$command $verb $procopt --keep -$3 $1
$command "${opt[@]}" --keep "-$3" "$1"
return $?
}
# shellcheck disable=SC2329
_dobz2()
{
local procopt="-p$2"
@@ -177,118 +489,141 @@ taz ()
command -v pbzip2 >/dev/null 2>&1 || {
command -v bzip2 >/dev/null 2>&1 || {
echo -e >&2 "\t*** The program 'pbzip2' or 'bzip2' are not installed, aborting."
disp E "The program 'pbzip2' or 'bzip2' are not installed, aborting."
return 127
}
local command=bzip2
local procopt=""
[[ $2 -gt 1 ]] &&
echo -e "\t! Warning: bzip2 doesn't support multithreading, falling back to 1 thread." &&
echo -e "\t* Consitder installing pbzip2 to obtain multithreading abilities."
disp W "bzip2 doesn't support multithreading, falling back to 1 thread." &&
disp W "Consider installing pbzip2 to obtain multithreading abilities."
}
[[ $4 ]] && local verb="-v"
local opt=()
[[ $4 ]] && opt=('-v')
opt+=("$procopt")
# Compresse au format bz2
$command $verb --compress $procopt --keep -$3 $1
$command "${opt[@]}" --compress --keep "-$3" "$1"
return $?
}
# shellcheck disable=SC2329
_dolzo()
{
command -v lzop >/dev/null 2>&1 || {
echo -e >&2 "\t*** The program 'lzop' is not installed, aborting."
disp E "The program 'lzop' is not installed, aborting."
return 127
}
[[ $4 ]] && local verb='-v'
[[ $2 -gt 1 ]] && echo -e "\t! Warning: lzop doesn't support multithreading, falling back to 1 thread."
local verb=()
[[ $4 ]] && verb=('-v')
[[ $2 -gt 1 ]] && disp W "lzop doesn't support multithreading, falling back to 1 thread."
# Compresse au format lzo
lzop --keep -$3 $1
lzop "${verb[@]}" --keep "-$3" "$1"
return $?
}
for opt in $@ ; do
case $opt in
"-h"|"--help")
echo "taz: archive all files of a directory."
echo
echo "Usage: taz [option] [--parallel=<n>] [--format=<format>] [directory1 ... directoryN]"
echo
echo "Options:"
echo " -h, --help Display that help screen"
echo " -d, --delete Delete source file or directory after success"
echo " -f, --format Chose archive format in the given list. If several format are"
echo " given, the smalest is kept"
echo " -p, --parallel Number of threads to use (if allowed by underlying utility)"
echo " -v, --verbose Display progress where possible"
echo " -1, .., -9 Compression level to use [1=fast/big, 9=slow/small]"
echo
echo "Supported archive format:"
echo " Param.| programs | Algo. | Description"
echo " ------+---------------+-------+----------------------------------------"
echo " lz | plzip, lzip | lzma | Safe efficient default format"
echo " xz | xz | lzma2 | Unsafe, not for long term"
echo " bz2 | pbzip2, bzip2 | bzip2 | Historical but less efficient than lz"
echo " gz | pigz, gzip | lz77 | Historical, safe, fast"
echo " lzo | lzop | lzo | Very fast but no multithread"
echo " tar | tar | tar | No compression"
echo
local PARSED
PARSED=$(getopt -o hdf:p:vq123456789 --long help,delete,format:,parallel:,verbose,quiet --name "taz" -- "$@")
# shellcheck disable=SC2181 # getopt return code is checked immediately after
if [ $? -ne 0 ]; then
disp E "Invalid options, use \"taz --help\" to display usage."
return 1
fi
eval set -- "$PARSED"
while true; do
case "$1" in
-h|--help)
printf "taz: archive all files of a directory.\n\n"
printf "Usage: taz [option] [--parallel=<n>] [--format=<format>] [directory1 ... directoryN]\n\n"
printf "Options:\n"
printf "\t-h, --help\tDisplay that help screen\n"
printf "\t-d, --delete\tDelete source file or directory after success\n"
printf "\t-f, --format\tChose archive format in the given list. If several format are"
printf "\t\t\tgiven, the smalest is kept\n"
printf "\t-p, --parallel\tNumber of threads to use (if allowed by underlying utility)\n"
printf "\t-v, --verbose\tDisplay progress where possible\n"
printf "\t-q, --quiet\tDisplay less messages (only errors and warnings)\n"
printf "\t-1, .., -9\tCompression level to use [1=fast/biggest, 9=slow/smallest]\n\n"
printf "Supported archive format:\n"
printf "\tParam.| programs | Algo. | Description\n"
printf "\t------+---------------+-------+----------------------------------------\n"
printf "\t lz | plzip, lzip | lzma | Safe efficient default format\n"
printf "\t xz | xz | lzma2 | Unsafe, not for long term\n"
printf "\t bz2 | pbzip2, bzip2 | bzip2 | Historical but less efficient than lz\n"
printf "\t gz | pigz, gzip | lz77 | Historical, safe, fast\n"
printf "\t lzo | lzop | lzo | Very fast but no multithread\n"
printf "\t tar | tar | tar | No compression\n"
printf "\n"
return 0
;;
"-d"|"--delete")
-d|--delete)
local willrm=1
shift
;;
"-f"?*|"--format"?*)
local compform=$(echo "$opt" | cut -f 2- -d '=')
-f|--format)
local compform=$2
shift 2
;;
"-p"?*|"--parallel"?*)
local nproc=$(echo "$opt" | cut -f 2- -d '=')
-p|--parallel)
local nproc=$2
shift 2
;;
"-v"|"--verbose")
-v|--verbose)
local verbose=1
shift
;;
"-"[1..9])
local complevel=$(echo $opt | sed 's/-//')
-q|--quiet)
local quiet=1
shift
;;
"-"*)
echo "Invalid option, use taz --help to display options list"
echo
return 1
-[1-9])
complevel="${1#-}"
shift
;;
--)
shift
break
;;
*)
local LIST="$LIST ${opt%/}"
disp E "Invalid option, use \"taz --help\" to display options list"
return 1
;;
esac
done
[[ ! $compform ]] && compform=lz # safe and efficient (unless data are already compressed)
[[ ! $nproc ]] && nproc=1
[[ ! $complevel ]] && complevel=6
# The remaining arguments after -- are the files/directories to process,
# "." is used if none is given
local FILES=("$@")
[[ ${#FILES[@]} -eq 0 ]] && FILES=(".")
for item in $LIST; do
[[ ! $compform ]] && compform=${TAZ_DEFAULT_FORMAT:-lz}
[[ ! $nproc ]] && nproc=${TAZ_DEFAULT_THREADS:-1}
[[ ! $complevel ]] && complevel=${TAZ_DEFAULT_LEVEL:-6}
[[ $verbose -gt 1 && $quiet -gt 1 ]] &&
disp E "The --verbose and --quiet options can't be used together."
for item in "${FILES[@]}"; do
local donetar=0
echo "--- Processing $item..."
disp I "Processing $item..."
if [[ -d $item ]]; then
echo -ne "\t* Creating $item.tar... "
if [[ -d "$item" ]]; then
disp I "\t Creating $item.tar... "
tar -cf $item{.tar,}
if [[ ! $? -eq 0 ]]; then
echo "[ failed, skipping ]"
if ! tar -cf "$item.tar" "$item"; then
disp E "tar file creation failed, skipping to next item."
continue
fi
local donetar=1
echo "[ OK ]"
fi
local fname=$item
@@ -296,29 +631,34 @@ taz ()
# Skip compression part if tar is asked
if [[ $compform != "tar" ]]; then
echo -e "\t* Compressing archive..."
_do$compform $fname $nproc $complevel $verbose
[[ ! $? -eq 0 ]] && case $? in
disp I "\t Compressing archive..."
local exec_code=0
"_do$compform" "$fname" "$nproc" "$complevel" "$verbose" || exec_code=$?
[[ ! $exec_code -eq 0 ]] && case $exec_code in
127)
echo -e "\t*** Compression program unavailable, aborting."
disp E "Compression program unavailable, aborting."
return 127
;;
*)
echo -e "\t*** Compression program returned an error, not deleting anything if asked, skipping to next item."
disp E "Compression program returned an error, not deleting anything if asked, skipping to next item."
continue
;;
esac
[[ $donetar -gt 0 ]] && rm $fname
[[ $donetar -gt 0 ]] && rm "$fname"
fi
if [[ $willrm ]]; then
echo -en "\t* Deleting original source as asked... "
rm -r $item && echo '[ OK ]' || echo '[ failed ]'
disp I "\t Deleting original source as asked... "
rm -r "$item"
fi
echo "--- Done"
done
unset quiet
}
export -f taz
# ------------------------------------------------------------------------------
load_conf "compress"
# EOF

View File

@@ -1,26 +1,57 @@
#!/usr/bin/env bash
# ------------------------------------------------------------------------------
# Copyright (c) 2013-2026 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
# Protected by the BSD3 license. Please read bellow for details.
#
# * Redistribution and use in source and binary forms,
# * with or without modification, are permitted provided
# * that the following conditions are met:
# *
# * Redistributions of source code must retain the above
# * copyright notice, this list of conditions and the
# * following disclaimer.
# *
# * Redistributions in binary form must reproduce the above
# * copyright notice, this list of conditions and the following
# * disclaimer in the documentation and/or other materials
# * provided with the distribution.
# *
# * Neither the name of the copyright holder nor the names
# * of any other contributors may be used to endorse or
# * promote products derived from this software without
# * specific prior written permission.
# *
# * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
# * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
# * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
# * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# * OF SUCH DAMAGE.
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Display a backtrace
# ------------------------------------------------------------------------------
# Usage: backtrace
function backtrace()
{
echo "========= Call stack ========="
typeset -i i=0
local func=
for func in "${FUNCNAME[@]}"; do
if [[ $i -ne 0 ]]; then
printf "========= Call stack =========\n"
local i=1 # We begin at 1 to ignore backtrace itself
while [[ $i -lt ${#FUNCNAME[@]} ]]; do
printf '%15s() %s:%d\n' \
"$func" "${BASH_SOURCE[$i]}" "${BASH_LINENO[ (( $i - 1)) ]}"
fi
let i++ || true
"${FUNCNAME[$i]}" "${BASH_SOURCE[$i]}" "${BASH_LINENO[$(( i-1 ))]}"
((i++))
done
unset func i
echo "=============================="
unset i
printf "==============================\n"
}
# ------------------------------------------------------------------------------
# Function to be trapped for errors investigation
# ------------------------------------------------------------------------------
function error()
{
local errcode=$?
@@ -28,45 +59,73 @@ function error ()
return $errcode
}
# ------------------------------------------------------------------------------
# Activate or deactivate error trapping to display backtrace
# ------------------------------------------------------------------------------
# Usage: settrace <--on|--off|--status>
settrace()
{
local status="off"
[[ $(trap -p ERR) ]] && status="on"
#trap -p ERR
for opt in $@ ; do
case $opt in
"-h"|"--help")
echo "Try to activate backtrace display for script debugging."
echo
echo "Options:"
echo " --on Activate backtrace generation"
echo " --off Deactivate backtrace generation"
echo
echo "That function active a trap event on error. If the script you want to"
echo "debug overload the ERR bash trap, it will not work."
echo
local PARSED
PARSED=$(getopt -oh --long help,on,off,status,force -- "$@")
# shellcheck disable=SC2181 # getopt return code is checked immediately after
if [[ $? -ne 0 ]]; then
disp E "Invalid options, use \"settrace --help\" to display usage."
return 1
fi
eval set -- "$PARSED"
local force=0
while true; do
case $1 in
-h|--help)
printf "Try to activate backtrace display for script debugging.\n\n"
printf "Options:\n"
printf "\t--on\t\tActivate backtrace generation\n"
printf "\t--force\t\tForce replacement of existing trap (use with --on)\n"
printf "\t--off\t\tDeactivate backtrace generation\n\n"
printf "That function active a trap event on error. If the script you want to\n"
printf "debug overload the ERR bash trap, it will not work.\n"
return 0
;;
"--on")
if [[ $status == "on" ]]; then
echo "Warning: ERR signal trap is already set, replacing previous trap!"
--on)
if [[ ${status} == "on" ]] && [[ $force -eq 0 ]]; then
disp E "ERR signal trap is already set. Use --force to replace it."
return 1
fi
trap "error" ERR
shift
;;
"--off")
if [[ $status != "on" ]]; then
echo "Warning: ERR signal trap is already unset!"
--force)
force=1
shift
;;
--off)
if [[ ${status} != "on" ]]; then
disp W "ERR signal trap is already unset!"
fi
trap - ERR
shift
;;
"--status")
echo "ERR trap signal is ${status}."
--status)
disp I "Trap signal is ${status}."
shift
;;
--)
shift
break
;;
*)
disp E "Invalid options, use \"settrace --help\" to display usage."
return 1
;;
esac
done
unset status
unset status force
}
export -f settrace
# ------------------------------------------------------------------------------
# EOF

188
profile.d/disp.sh Normal file
View File

@@ -0,0 +1,188 @@
#!/usr/bin/env bash
# ------------------------------------------------------------------------------
# Copyright (c) 2013-2026 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
# Protected by the BSD3 license. Please read bellow for details.
#
# * Redistribution and use in source and binary forms,
# * with or without modification, are permitted provided
# * that the following conditions are met:
# *
# * Redistributions of source code must retain the above
# * copyright notice, this list of conditions and the
# * following disclaimer.
# *
# * Redistributions in binary form must reproduce the above
# * copyright notice, this list of conditions and the following
# * disclaimer in the documentation and/or other materials
# * provided with the distribution.
# *
# * Neither the name of the copyright holder nor the names
# * of any other contributors may be used to endorse or
# * promote products derived from this software without
# * specific prior written permission.
# *
# * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
# * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
# * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
# * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# * OF SUCH DAMAGE.
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Color definitions
set_colors()
{
# Standard 16 colors display declaration
export DEFAULTFG='\e[0;39m'
export DEFAULTBG='\e[0;49m'
export DEFAULTCOL="${DEFAULTBG}${DEFAULTFG}"
export RESETCOL=$'\e[0m'
# Regular Colors
export Black='\e[0;30m'
export Red='\e[0;31m'
export Green='\e[0;32m'
export Yellow='\e[0;33m'
export Blue='\e[0;34m'
export Purple='\e[0;35m'
export Cyan='\e[0;36m'
export White='\e[0;37m'
# Bold
export BBlack='\e[1;30m'
export BRed='\e[1;31m'
export BGreen='\e[1;32m'
export BYellow='\e[1;33m'
export BBlue='\e[1;34m'
export BPurple='\e[1;35m'
export BCyan='\e[1;36m'
export BWhite='\e[1;37m'
# Underline
export UBlack='\e[4;30m'
export URed='\e[4;31m'
export UGreen='\e[4;32m'
export UYellow='\e[4;33m'
export UBlue='\e[4;34m'
export UPurple='\e[4;35m'
export UCyan='\e[4;36m'
export UWhite='\e[4;37m'
# Background
export On_Black='\e[40m'
export On_Red='\e[41m'
export On_Green='\e[42m'
export On_Yellow='\e[43m'
export On_Blue='\e[44m'
export On_Purple='\e[45m'
export On_Cyan='\e[46m'
export On_White='\e[47m'
# High Intensity
export IBlack='\e[0;90m'
export IRed='\e[0;91m'
export IGreen='\e[0;92m'
export IYellow='\e[0;93m'
export IBlue='\e[0;94m'
export IPurple='\e[0;95m'
export ICyan='\e[0;96m'
export IWhite='\e[0;97m'
# Bold High Intensity
export BIBlack='\e[1;90m'
export BIRed='\e[1;91m'
export BIGreen='\e[1;92m'
export BIYellow='\e[1;93m'
export BIBlue='\e[1;94m'
export BIPurple='\e[1;95m'
export BICyan='\e[1;96m'
export BIWhite='\e[1;97m'
# High Intensity backgrounds
export On_IBlack='\e[0;100m'
export On_IRed='\e[0;101m'
export On_IGreen='\e[0;102m'
export On_IYellow='\e[0;103m'
export On_IBlue='\e[0;104m'
export On_IPurple='\e[0;105m'
export On_ICyan='\e[0;106m'
export On_IWhite='\e[0;107m'
}
export -f set_colors
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Display a message
# Usage: disp <type> <message>
# Types:
# I : info (green)
# W : warning (yellow)
# E : error (red)
# D : debug (cyan)
disp()
{
# Handle NO_COLOR: disable colors if set
local color_enabled=1
[[ -n $NO_COLOR ]] && color_enabled=0
case ${1^^} in
"I")
if [[ $color_enabled -eq 1 ]]; then
local heads="[ ${IGreen}info${DEFAULTFG} ]"
else
local heads="[ info ]"
fi
shift
[[ -z $QUIET || $QUIET -ne 1 ]] && \
printf "%b\n" "${heads} $*${RESETCOL}"
;;
"W")
if [[ $color_enabled -eq 1 ]]; then
local heads="[ ${IYellow}Warning${DEFAULTFG} ]"
else
local heads="[ Warning ]"
fi
shift
printf "%b\n" "${heads} $*${RESETCOL}" >&2
;;
"E")
if [[ $color_enabled -eq 1 ]]; then
local heads="[ ${IRed}ERROR${DEFAULTFG} ]"
else
local heads="[ ERROR ]"
fi
shift
printf "%b\n" "${heads} $*${RESETCOL}" >&2
;;
"D")
if [[ $color_enabled -eq 1 ]]; then
local heads="[ ${ICyan}debug${DEFAULTFG} ]"
else
local heads="[ debug ]"
fi
shift
[[ -n $DEBUG && $DEBUG -gt 1 ]] && \
printf "%b\n" "${heads} $*${RESETCOL}"
;;
* )
[[ -z $QUIET || $QUIET -ne 1 ]] && \
printf "%b\n" "$*"
;;
esac
}
export -f disp
# ------------------------------------------------------------------------------
# Load disp section variables
load_conf disp
set_colors
# EOF

View File

@@ -1,179 +1,805 @@
#!/usr/bin/env bash
# ------------------------------------------------------------------------------
# expandlist : treat wildcards in a file/directory list
# Copyright (c) 2013-2026 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
# Protected by the BSD3 license. Please read bellow for details.
#
# * Redistribution and use in source and binary forms,
# * with or without modification, are permitted provided
# * that the following conditions are met:
# *
# * Redistributions of source code must retain the above
# * copyright notice, this list of conditions and the
# * following disclaimer.
# *
# * Redistributions in binary form must reproduce the above
# * copyright notice, this list of conditions and the following
# * disclaimer in the documentation and/or other materials
# * provided with the distribution.
# *
# * Neither the name of the copyright holder nor the names
# * of any other contributors may be used to endorse or
# * promote products derived from this software without
# * specific prior written permission.
# *
# * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
# * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
# * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
# * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# * OF SUCH DAMAGE.
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Expand wildcards in a file/directory list and quote the results
# Usage: expandlist [options] <item1 [item2 ... itemN]>
expandlist()
{
local result=""
for item in "$1"; do
for content in "$item"; do
result+="\"$content\" "
done
done
echo $result
}
local separator="${EXPANDLIST_DEFAULT_SEPARATOR:- }"
local PARSED
PARSED=$(getopt -o hs:n --long help,separator:,newline -n 'expandlist' -- "$@")
# shellcheck disable=SC2181 # getopt return code is checked immediately after
if [[ $? -ne 0 ]]; then
disp E "Invalid options, use \"expandlist --help\" to display usage."
return 1
fi
eval set -- "$PARSED"
# ------------------------------------------------------------------------------
# Clean a directory or a tree from temporary or backup files
# ------------------------------------------------------------------------------
clean ()
{
for opt in $@ ; do
case $opt in
"-r"|"--recurs")
local recursive=1
;;
"-h"|"--help")
echo "clean: erase backup files in the given directories."
echo
echo "Usage: clean [option] [directory1] [...[directoryX]]"
echo
echo "Options:"
echo " -h, --help Display that help screen"
echo " -r, --recurs Do a recursive cleaning"
echo " -f, --force Do not ask for confirmation (use with care)"
echo " -s, --shell Do nothing and display what will be executed"
echo
while true; do
case "$1" in
-h|--help)
printf "expandlist: expand globs and wrap matched items in double quotes.\n\n"
printf "Usage: expandlist [options] <item1 [item2 ... itemN]>\n\n"
printf "Options:\n"
printf "\t-h, --help\t\tDisplay this help screen\n"
printf "\t-s, --separator SEP\tSet output separator (default: space)\n"
printf "\t-n, --newline\t\tUse a newline as separator\n"
return 0
;;
"-s"|"--shell")
local outshell=1
-s|--separator)
separator="$2"
shift 2
;;
"-f"|"--force")
local force=1
-n|--newline)
separator=$'\n'
shift
;;
"-"*)
echo "Invalid option, use \"clean --help\" to display usage."
echo
return 1
--)
shift
break
;;
*)
local dirlist="$dirlist $opt"
disp E "Invalid options, use \"expandlist --help\" to display usage."
return 1
;;
esac
done
[[ ! $dirlist ]] && local dirlist=$(pwd)
local item="" result="" matched=0
shopt -s nullglob
[[ ! $recursive ]] && local findopt="-maxdepth 1"
[[ ! $force ]] && local rmopt="-i"
unset recursive force
for item in "$@"; do
local expanded=()
for dir in $dirlist; do
local dellist=$(find $dir $findopt -type f -name "*~" -o -name "#*#" \
-o -name "*.bak" -o -name ".~*#")
for f in $dellist; do
if [[ ! $outshell ]]; then
rm $rmopt $f
# True glob expansion when wildcards are present.
if [[ "$item" == *'*'* || "$item" == *'?'* || "$item" == *'['* ]]; then
# shellcheck disable=SC2206 # We actually want the word splitting
expanded=( $item )
else
echo "rm $rmopt $f"
expanded=( "$item" )
fi
if [[ ${#expanded[@]} -eq 0 ]]; then
continue
fi
for content in "${expanded[@]}"; do
if (( matched )); then
result+="$separator"
fi
result+="\"$content\""
matched=1
done
done
shopt -u nullglob
printf '%s\n' "$result"
}
export -f expandlist
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Clean a directory tree from temporary or backup files
# Usage: clean [options] [directory1] [...[directoryX]]
# Options:
# -h, --help: display help screen
# -r, --recurs: do a recursive cleaning
# -f, --force: do not ask for confirmation (use with care)
# -s, --shell: do nothing and display what will be executed
clean()
{
local recursive=${CLEAN_DEFAULT_RECURSIVE:-0} force=0 outshell=0
# Define short and long options
local PARSED
PARSED=$(getopt -o hrsf --long help,recurs,shell,force -n 'clean' -- "$@")
# shellcheck disable=SC2181 # getopt return code is checked immediately after
if [[ $? -ne 0 ]]; then
disp E "Invalid options, use \"clean --help\" to display usage."
return 1
fi
eval set -- "$PARSED"
while true; do
case "$1" in
-r|--recurs)
recursive=1
shift
;;
-h|--help)
printf "clean: erase backup files in the given directories.\n\n"
printf "Usage: clean [option] [directory1] [...[directoryX]]\n"
printf "Options:\n"
printf "\t-h, --help\t\tDisplay that help screen\n"
printf "\t-r, --recurs\t\tDo a recursive cleaning\n"
printf "\t-f, --force\t\tDo not ask for confirmation (use with care)\n"
printf "\t-s, --shell\t\tDo nothing and display what will be executed\n"
printf "\n"
return 0
;;
-s|--shell)
outshell=1
shift
;;
-f|--force)
force=1
shift
;;
--)
shift
break
;;
*)
disp E "Invalid parameter, use \"clean --help\" to display options list"
return 1
;;
esac
done
# Handle remaining arguments as directories
local dirlist=("$@")
[[ ${#dirlist[@]} -eq 0 ]] && dirlist=(".")
local findopt=() rmopt=()
(( ! recursive )) && findopt=(-maxdepth 1)
(( ! force )) && rmopt=(-i)
for dir in "${dirlist[@]}"; do
find "$dir" "${findopt[@]}" -type f \( -name "*~" -o -name "#*#" -o -name "*.bak" -o -name ".~*#" \) -print0 |
while IFS= read -r -d '' f; do
if (( outshell )); then
if (( ${#rmopt[@]} )); then
printf 'rm %s -- "%s"\n' "${rmopt[*]}" "$f"
else
printf 'rm -- "%s"\n' "$f"
fi
else
rm "${rmopt[@]}" -- "$f"
fi
done
done
unset outshell dirlist dellist findopt rmopt
}
export -f clean
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Create a directory then goes inside
# ------------------------------------------------------------------------------
# Usage: mcd <directory>
mcd()
{
if [[ "$1" == "-h" || "$1" == "--help" ]]; then
printf "mcd: Create a directory and enter it.\n\n"
printf "Usage: mcd <directory>\n"
printf "Options:\n"
printf "\t-h, --help\t\tDisplay this help screen\n"
return 0
fi
if [[ ! $# -eq 1 ]]; then
echo "Create a directory then goes inside."
echo "Usage: mcd <directory>"
disp E "Missing parameter. Use \"mcd --help\" to display usage."
return 1
fi
if ! mkdir -pv "$1"; then
disp E "Failed to create directory \"$1\"."
return 1
fi
if ! cd "$1"; then
disp E "Failed to change to directory \"$1\"."
return 1
fi
mkdir -pv $1 && cd $1
}
export -f mcd
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Rename all files in current directory to replace spaces with _
# ------------------------------------------------------------------------------
# Rename files and directories to replace spaces with another character
# Usage: rmspc [options]
# Options:
# -h, --help: display help screen
# -r, --recursive: treat subdirectories of the given directory
# -c, --subst-char: change the replacement character (default is underscore)
# -v, --verbose: display more details (recursive mode only)
# -s, --shell: do nothing and display commands that would be executed
rmspc()
{
local lst=""
for opt in $@ ; do
case $opt in
"-h"|"--help")
echo "rmspc: remove spaces from all filenames in current directories"
echo
echo "Usage: rmspc [option]"
echo
echo "Options:"
echo " -h, --help Display that help screen"
echo " -r, --recursive Treat subdirectories of the given directory"
echo " -c, --subst-char Change the replacement character (default is underscore)"
echo " -v, --verbose Display what is being done"
echo " -s, --shell Do nothing and display commands that would be executed"
echo
echo "Note: if the --subst-char option is given without parameters, spaces will be"
echo " replaced with nothing (concatenation)."
echo
local recurs=0 verb=0 shell=0
local substchar="${RMSPC_DEFAULT_CHAR:-_}" substchar_set=0
local mvopt=()
local PARSED
PARSED=$(getopt -o hr:c::vs --long help,recursive,subst-char::,verbose,shell -n 'rmspc' -- "$@")
# shellcheck disable=SC2181 # getopt return code is checked immediately after
if [[ $? -ne 0 ]]; then
disp E "Invalid options, use \"rmspc --help\" to display usage."
return 1
fi
eval set -- "$PARSED"
while true; do
case "$1" in
-h|--help)
printf "rmspc: remove spaces from all filenames in current directories\n\n"
printf "Usage: rmspc [option]\n\n"
printf "Options:\n"
printf "\t-h, --help\t\tDisplay that help screen\n"
printf "\t-r, --recursive\t\tTreat subdirectories of the given directory\n"
printf "\t-c, --subst-char\tChange the replacement character (default is underscore)\n"
printf "\t-v, --verbose\t\tDisplay more details (recursive mode only)\n"
printf "\t-s, --shell\t\tDo nothing and display commands that would be executed\n\n"
printf "Note: if the --subst-char option is given without parameters, spaces will be\n"
printf " replaced with nothing (concatenation).\n"
return 0
;;
"-r"|"--recursive")
local recurs=1
-r|--recursive)
recurs=1
shift
;;
"-c"?*|"--subst-char"?*)
if [[ $(echo $opt | grep "=") ]]; then
local substchar=$(echo "$opt" | cut -f 2- -d '=')
else
local substchar='none'
fi
-c|--subst-char)
substchar_set=1
substchar="$2"
shift 2
;;
"-v"|"--verbose")
local verb=1
-v|--verbose)
verb=1
shift
;;
"-s"|"--shell")
local shell=1
-s|--shell)
shell=1
shift
;;
--)
shift
break
;;
*)
echo "Invalid parameter, use \"rmspc --help\" to display options list"
echo
disp E "Invalid parameter, use \"rmspc --help\" to display options list"
return 1
;;
esac
done
[[ ! $substchar ]] && substchar="_"
[[ $substchar == "none" ]] && local substchar=""
[[ $verb ]] && local mvopt="-v"
[[ "$substchar" == "none" ]] && substchar=""
(( verb )) && mvopt=(-v)
shopt -s nullglob
for f in *; do
[[ $recurs ]] && [[ -d "$f" ]] && (
[[ $verb ]] && echo "-- Entering directory $(pwd)/$f ..."
if (( recurs )) && [[ -d "$f" ]]; then
(
local lastdir=$f
pushd "$f" > /dev/null
rmspc $@
popd > /dev/null
[[ $verb ]] && echo "-- Leaving directory $(pwd)/$lastdir"
unset lastdir
)
(( verb )) && disp I "Entering directory $(pwd)/$f ..."
pushd "$f" >/dev/null || return 1
if [[ $(echo $f | grep " ") ]]; then
local newf="${f// /${substchar}}"
local command="mv $mvopt \"$f\" \"$newf\""
if [[ $shell ]]; then
echo $command
if (( substchar_set )); then
rmspc ${recurs:+-r} -c "$substchar" ${verb:+-v} ${shell:+-s}
else
$command
rmspc ${recurs:+-r} ${verb:+-v} ${shell:+-s}
fi
popd >/dev/null || return 1
(( verb )) && disp I "Leaving directory $(pwd)/$lastdir"
)
fi
if [[ "$f" == *" "* ]]; then
local newf="${f// /${substchar}}"
[[ "$f" == "$newf" ]] && continue
if (( shell )); then
if (( ${#mvopt[@]} )); then
printf 'mv %s -- "%s" "%s"\n' "${mvopt[*]}" "$f" "$newf"
else
printf 'mv -- "%s" "%s"\n' "$f" "$newf"
fi
else
mv "${mvopt[@]}" -- "$f" "$newf" || {
disp E "Failed renaming \"$f\" to \"$newf\"."
continue
}
fi
fi
done
unset lst substchar verb shell newf command mvopt
shopt -u nullglob
}
export -f rmspc
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Display statistics about a file tree
# Usage: file_stats [options] [path]
# Options:
# -H, --human Human readable sizes\n"
# -d, --details Display details (min/max/average/median)
# -m, --average Display only average size
# -M, --median Display only median size
# -c, --count Display only count of files
# -t, --total Display only total size
# -a, --all Display all stats in human readable format (shortcut for -H -d)
# -x, --ext [ext] Filter by extension (e.g. -x log for .log files)
# -X, --ext-list [list] Filter by multiple extensions (e.g. -X log,txt)
# --min [size] Minimum size (e.g., 10M)
# --max [size] Maximum size (e.g., 100M)
file_stats()
{
local human=0 details=0 only_avg=0 only_med=0 only_count=0 only_total=0
local path="." show_all=1 ext_filter="" ext_list="" min_size="" max_size=""
local PARSED
# Short: H, d, m, M, c, t, a, x:, X:
# Long: human, details, average, median, count, total, all, ext:, ext-list:, min:, max:, help
PARSED=$(getopt -o HdmMctax:X:h --long human,details,average,median,count,total,all,ext:,ext-list:,min:,max:,help -n 'file_stats' -- "$@")
# shellcheck disable=SC2181 # getopt return code is checked immediately after
if [[ $? -ne 0 ]]; then
disp E "Invalid options, use \"file_stats --help\" to display usage."
return 1
fi
eval set -- "$PARSED"
while true; do
case "$1" in
-h|--help)
printf "Usage: file_stats [options] [path]\n\n"
printf "Options:\n"
printf "\t-H, --human\t\tHuman readable sizes\n"
printf "\t-d, --details\t\tShow detailed histogram\n"
printf "\t-m, --average\t\tShow only average size\n"
printf "\t-M, --median\t\tShow only median size\n"
printf "\t-c, --count\t\tShow only file count\n"
printf "\t-t, --total\t\tShow only total size\n"
printf "\t-a, --all\t\tShow all (human + details)\n"
printf "\t-x, --ext [ext]\t\tFilter by extension\n"
printf "\t-X, --ext-list [list]\tFilter by comma-separated list\n"
printf "\t--min [size]\t\tMinimum size (e.g., 10M)\n"
printf "\t--max [size]\t\tMaximum size (e.g., 100M)\n"
return 0 ;;
-H|--human)
human=1
shift
;;
-d|--details)
details=1
shift
;;
-m|--average)
only_avg=1
show_all=0
shift
;;
-M|--median)
only_med=1
show_all=0
shift
;;
-c|--count)
only_count=1
show_all=0
shift
;;
-t|--total)
only_total=1
show_all=0
shift
;;
-a|--all)
human=1
details=1
shift
;;
-x|--ext)
ext_filter="${2#.}"
shift 2
;;
-X|--ext-list)
ext_list="$2"
shift 2
;;
--min)
min_size="$2"
shift 2
;;
--max)
max_size="$2"
shift 2
;;
--)
shift
break
;;
*)
disp E "Invalid option: $1"
return 1
;;
esac
done
[[ -n "$1" ]] && path="$1"
# Prepare find filters
local find_cmd=(find "$path" -type f)
# Single extension filter
if [[ -n "$ext_filter" ]]; then
find_cmd+=(-iname "*.$ext_filter")
fi
# Extension list filter
if [[ -n "$ext_list" ]]; then
IFS=',' read -ra exts <<< "$ext_list"
find_cmd+=('(')
for i in "${!exts[@]}"; do
[[ $i -ne 0 ]] && find_cmd+=(-o)
find_cmd+=(-iname "*.${exts[$i]}")
done
find_cmd+=(')')
fi
# Minimum/maximum size filters (evaluated in bytes)
if [[ -n "$min_size" ]]; then
find_cmd+=(-size +"$(numfmt --from=iec "$min_size")"c)
fi
if [[ -n "$max_size" ]]; then
find_cmd+=(-size -"$(( $(numfmt --from=iec "$max_size") + 1 ))"c)
fi
# Execution
"${find_cmd[@]}" -printf "%s\n" 2>/dev/null | sort -n | \
awk -v human="$human" -v details="$details" -v only_avg="$only_avg" \
-v only_med="$only_med" -v only_count="$only_count" \
-v only_total="$only_total" -v show_all="$show_all" -v path="$path" '
# Convert function
function human_readable(x) {
split("B KiB MiB GiB TiB", units)
i = 1
while (x >= 1024 && i < 5) {
x /= 1024
i++
}
return sprintf("%.2f %s", x, units[i])
}
# Display function
function out(label, val, is_size) {
if (human == 1 && is_size == 1) val = human_readable(val)
printf "%-20s : %s\n", label, val
}
{
sizes[NR] = $1
total += $1
if (min == "" || $1 < min) min = $1
if (max == "" || $1 > max) max = $1
if ($1 == 0) bucket[0]++
else {
b = int(log($1)/log(1024))
bucket[b]++
}
}
END {
count = NR
if (count == 0) {
print "No files found."
exit
}
average = total / count
# Median calculation: exact using sorted array values
if (count % 2 == 1) {
median = sizes[(count + 1) / 2]
} else {
idx = count / 2
median = (sizes[idx] + sizes[idx + 1]) / 2
}
if (only_avg) out("Average size", average, 1)
else if (only_med) out("Median size", median, 1)
else if (only_count) out("Number of files", count, 0)
else if (only_total) out("Total size", total, 1)
else {
if (show_all || human || details) {
printf "Statistics for \"%s\"\n", path
printf "-------------------------\n"
}
out("Number of files", count, 0)
out("Total size", total, 1)
out("Average size", average, 1)
out("Median size", median, 1)
out("Minimum size", min, 1)
out("Maximum size", max, 1)
}
if (details) {
print "\nSize histogram:"
# Use a separate array for the loop to avoid collision
for (b in bucket) {
# Pre-calculate label parts
# 1024^0 = 1 (B), 1024^1 = 1K, etc.
low = (b == 0) ? 0 : (1024^b)
high = 1024^(b+1)
label = sprintf("%-9s %-9s",
(b == 0) ? "0" : human_readable(low),
human_readable(high))
# We store buckets in an array, access them by index b
printf "%-25s : %6d fichiers\n", label, bucket[b]
}
}
}'
}
export -f file_stats
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Find the biggest files in a directory tree
# Usage: findbig [options] [directory]
# Options:
# -h : display help screen
# -d : display details (ls -l) for each file
# -x : do not cross filesystem boundaries
# -l : limit : number of files to return (default is 10)
findbig()
{
local details=0 limit=${FINDBIG_DEFAULT_LIMIT:-10} one_fs=0
local PARSED
PARSED=$(getopt -o hdl:x --long help,details,limit:,one-fs -n 'findbig' -- "$@")
# shellcheck disable=SC2181 # getopt return code is checked immediately after
if [[ $? -ne 0 ]]; then
disp E "Invalid options, use \"findbig --help\" to display usage."
return 1
fi
eval set -- "$PARSED"
while true; do
case "$1" in
-h|--help)
printf "findbig: Find the N biggest files in a directory tree.\n\n"
printf "Usage: findbig [options] [directory]\n\n"
printf "Options:\n"
printf "\t-h, --help\t\tDisplay this help screen\n"
printf "\t-d, --details\t\tShow detailed file info (ls -ld)\n"
printf "\t-l, --limit N\t\tNumber of files to return (default: 10)\n"
printf "\t-x, --one-fs\t\tDo not cross filesystem boundaries\n"
return 0
;;
-d|--details)
details=1
shift
;;
-l|--limit)
limit="$2"
[[ "$limit" =~ ^[0-9]+$ ]] || {
disp E "Invalid limit: must be a positive integer."
return 1
}
shift 2
;;
-x|--one-fs)
one_fs=1
shift
;;
--)
shift
break
;;
*)
disp E "Invalid option: $1"
return 1
;;
esac
done
local dir="${1:-.}"
# Prepare find arguments in an array for cleaner handling
local find_args=(-L "$dir")
(( one_fs )) && find_args+=(-xdev)
find_args+=(-type f)
# Logic: find files, print size and path, sort numeric reverse, take N
if (( details )); then
find "${find_args[@]}" -printf "%s %p\n" 2>/dev/null | sort -rn | head -n "$limit" |
while IFS= read -r line; do
local path="${line#* }"
ls -ld -- "$path"
done
else
find "${find_args[@]}" -printf "%s %p\n" 2>/dev/null | sort -rn | head -n "$limit"
fi
}
export -f findbig
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Find empty files in a directory tree
# Usage: findzero [options] [directory]
# Options:
# -h : display help screen
# -d : display details (ls -l) for each file
# -x : do not cross filesystem boundaries
# --delete : delete empty files and display their paths
findzero()
{
local delete=0 details=0 one_fs=0
local PARSED
# o: options, long: long equivalents
PARSED=$(getopt -o hdx --long help,details,one-fs,delete -n 'findzero' -- "$@")
# shellcheck disable=SC2181 # getopt return code is checked immediately after
if [[ $? -ne 0 ]]; then
disp E "Invalid options, use \"findzero --help\" to display usage."
return 1
fi
eval set -- "$PARSED"
while true; do
case "$1" in
-h|--help)
printf "findzero: Find or delete empty files in a directory tree.\n\n"
printf "Usage: findzero [options] [directory]\n\n"
printf "Options:\n"
printf "\t-h, --help\t\tDisplay this help screen\n"
printf "\t-d, --details\t\tShow detailed file info (ls -ls)\n"
printf "\t-x, --one-fs\t\tDo not cross filesystem boundaries\n"
printf "\t--delete\t\tActually remove the empty files\n"
return 0 ;;
-d|--details)
details=1
shift
;;
-x|--one-fs)
one_fs=1
shift
;;
--delete)
delete=1
shift
;;
--)
shift
break
;;
*)
disp E "Invalid option: $1"
return 1
;;
esac
done
local dir="${1:-.}"
local find_args=("-L" "$dir" "-type" "f" "-empty")
(( one_fs )) && find_args+=("-xdev")
# Execution logic
if (( delete )); then
disp W "Deleting empty files in $dir..."
find "${find_args[@]}" -delete -print
elif (( details )); then
find "${find_args[@]}" -ls
else
find "${find_args[@]}"
fi
}
export -f findzero
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Find dead symbolic links in a directory tree
# Usage: finddead [options] [directory]
# Options:
# -h : display help screen
# -d : display details (ls -l) for each link
# -x : do not cross filesystem boundaries
# --delete : delete dead links and display their paths
finddead()
{
local delete=0 details=0 one_fs=0
local PARSED
PARSED=$(getopt -o hdx --long help,details,one-fs,delete -n 'finddead' -- "$@")
# shellcheck disable=SC2181 # getopt return code is checked immediately after
if [[ $? -ne 0 ]]; then
disp E "Invalid options, use \"finddead --help\" to display usage."
return 1
fi
eval set -- "$PARSED"
while true; do
case "$1" in
-h|--help)
printf "finddead: Find or delete dead/broken symbolic links.\n\n"
printf "Usage: finddead [options] [directory]\n\n"
printf "Options:\n"
printf "\t-h, --help\t\tDisplay this help screen\n"
printf "\t-d, --details\t\tShow detailed symlink info (ls -ls)\n"
printf "\t-x, --one-fs\t\tDo not cross filesystem boundaries\n"
printf "\t--delete\t\tActually remove the dead links\n"
return 0 ;;
-d|--details)
details=1
shift
;;
-x|--one-fs)
one_fs=1
shift
;;
--delete)
delete=1
shift
;;
--)
shift
break
;;
*)
disp E "Invalid option: $1"
return 1
;;
esac
done
local dir="${1:-.}"
# -xtype l searches for links that do not point to an existing file
local find_args=("$dir" "-xtype" "l")
(( one_fs )) && find_args+=("-xdev")
# Execution logic
if (( delete )); then
disp W "Deleting dead symlinks in $dir..."
find "${find_args[@]}" -delete -print
elif (( details )); then
find "${find_args[@]}" -ls
else
find "${find_args[@]}"
fi
}
export -f finddead
# ------------------------------------------------------------------------------
load_conf "filefct"
# EOF

116
profile.d/fun.sh Normal file
View File

@@ -0,0 +1,116 @@
#!/usr/bin/env bash
# ------------------------------------------------------------------------------
# Copyright (c) 2013-2026 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
# Protected by the BSD3 license. Please read bellow for details.
#
# * Redistribution and use in source and binary forms,
# * with or without modification, are permitted provided
# * that the following conditions are met:
# *
# * Redistributions of source code must retain the above
# * copyright notice, this list of conditions and the
# * following disclaimer.
# *
# * Redistributions in binary form must reproduce the above
# * copyright notice, this list of conditions and the following
# * disclaimer in the documentation and/or other materials
# * provided with the distribution.
# *
# * Neither the name of the copyright holder nor the names
# * of any other contributors may be used to endorse or
# * promote products derived from this software without
# * specific prior written permission.
# *
# * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
# * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
# * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
# * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# * OF SUCH DAMAGE.
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Make non-IT peoples think you're busy doing something hard
# Usage: busy [options] [pattern]
# Options:
# --delay=<ms> : add a delay between each line output (milliseconds)
# pattern : the string to search for in the hexdump output (default is "ca fe")
busy()
{
local pattern="${BUSY_DEFAULT_PATTERN:-ca fe}" delay_ms="${BUSY_DEFAULT_DELAY:-0}"
local PARSED
# Short: h, p:, d:
# Long: help, pattern:, delay:
PARSED=$(getopt -o hp:d: --long help,pattern:,delay: -n 'busy' -- "$@")
# shellcheck disable=SC2181 # getopt return code is checked immediately after
if [[ $? -ne 0 ]]; then
disp E "Invalid options, use \"busy --help\" to display usage."
return 1
fi
eval set -- "$PARSED"
while true; do
case "$1" in
-h|--help)
printf "busy: Monitor /dev/urandom for a specific pattern.\n\n"
printf "Usage: busy [options] [pattern]\n\n"
printf "Options:\n"
printf "\t-h, --help\t\tDisplay this help screen\n"
printf "\t-p, --pattern PATTERN\tHex pattern to search (default: \"ca fe\")\n"
printf "\t-d, --delay MS\t\tDelay between matches in milliseconds\n"
return 0
;;
-p|--pattern)
pattern="$2"
shift 2
;;
-d|--delay)
delay_ms="$2"
if ! [[ "$delay_ms" =~ ^[0-9]+$ ]]; then
disp E "Invalid delay: must be an integer (milliseconds)."
return 1
fi
shift 2
;;
--)
shift
break
;;
*)
disp E "Invalid option: $1"
return 1
;;
esac
done
# Convert milliseconds to seconds for 'sleep'
local delay_s
delay_s=$(awk "BEGIN{
printf \"%.3f\", $delay_ms / 1000 }")
# Monitor /dev/urandom
(
hexdump -C < /dev/urandom | grep -iF --line-buffered "$pattern" | \
while read -r line; do
echo "$line"
[[ $delay_ms -gt 0 ]] && sleep "$delay_s"
done
) & local sub_pid=$!
IFS= read -r -n 1 -s _ </dev/tty
kill -- -"$sub_pid" 2>/dev/null || kill "$sub_pid" 2>/dev/null
wait "$sub_pid" 2>/dev/null
return 0
}
# ------------------------------------------------------------------------------
load_conf "fun"
# EOF

View File

@@ -1,33 +1,91 @@
#!/usr/bin/env bash
# ------------------------------------------------------------------------------
# Copyright (c) 2013-2026 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
# Protected by the BSD3 license. Please read bellow for details.
#
# * Redistribution and use in source and binary forms,
# * with or without modification, are permitted provided
# * that the following conditions are met:
# *
# * Redistributions of source code must retain the above
# * copyright notice, this list of conditions and the
# * following disclaimer.
# *
# * Redistributions in binary form must reproduce the above
# * copyright notice, this list of conditions and the following
# * disclaimer in the documentation and/or other materials
# * provided with the distribution.
# *
# * Neither the name of the copyright holder nor the names
# * of any other contributors may be used to endorse or
# * promote products derived from this software without
# * specific prior written permission.
# *
# * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
# * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
# * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
# * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# * OF SUCH DAMAGE.
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Display list of commands and general informations
# ------------------------------------------------------------------------------
# Usage: help
help()
{
cat <<EOF
clean Erase backup files
dpkgs Search for the given package in the installed ones
gpid Give the list of PIDs for the given process name
isipv4 Tell if the given IPv4 is valid
isipv6 Tell if the given IPv6 is valid
ku Kill process owned by users in parameter
mcd Create a directory and go inside
meteo Display curent weather forecast for the configured city
ppg Display process matching the given parameter
rain Let the rain fall
rmhost Remove host (IP and/or DNS name) for current known_host
rmspc Remove spaces from all the files in working directory
setc Set console language to C
setfr Set console language to French
settrace Activate/deactivate call trace for script debugging
setus Set console language to US English
showinfo Show the welcoming baner with basic system information
ssr Do a root login to the given address
taz Compress smartly the given files or directory
utaz Uncompress all zip files in the given (or current) directory
ver Display version of your copy of profile
# shellcheck disable=SC2154 # color code in disp.sh
# shellcheck disable=SC2059 # printf format is a color variable
printf "${BIWhite}Welcome to your profile! Here is a list of available commands:${DEFAULTCOL}\n\n"
printf "busy\t\tMonitor /dev/urandom for a hex pattern — look busy\n"
printf "check_updates\tCheck for new versions of profile\n"
printf "clean\t\tErase backup files in given directories, optionally recursive\n"
printf "disp\t\tDisplay formatted info/warning/error/debug messages\n"
printf "dwl\t\tDownload a URL using curl, wget, or fetch transparently\n"
printf "expandlist\tExpand glob expressions into a quoted, separated list\n"
printf "file_stats\tDisplay file size statistics for a path\n"
printf "findbig\t\tFind the biggest files in the given or current directory\n"
printf "finddead\tFind dead symbolic links in the given or current directory\n"
printf "findzero\tFind empty files in the given or current directory\n"
printf "genpwd\t\tGenerate one or more random secure passwords with configurable constraints\n"
printf "gpid\t\tGive the list of PIDs matching the given process name(s)\n"
printf "isipv4\t\tTell if the given parameter is a valid IPv4 address\n"
printf "isipv6\t\tTell if the given parameter is a valid IPv6 address\n"
printf "ku\t\tKill all processes owned by the given user name or ID\n"
printf "matrix\t\tConsole screensaver with Matrix-style digital rain (binary, kana, ascii charset)\n"
printf "mcd\t\tCreate a directory and immediately move into it\n"
printf "meteo\t\tDisplay weather forecast for the configured or given city\n"
printf "myextip\t\tGet information about your public IP address\n"
printf "pkgs\t\tSearch for a pattern in installed package names (dpkg/rpm, supports -i)\n"
printf "ppg\t\tLook for the given pattern in running processes\n"
printf "ppn\t\tList processes matching an exact command name\n"
printf "ppu\t\tList processes owned by a specific user\n"
printf "profile_upgrade\tUpgrade profile to the latest version (git pull or archive)\n"
printf "pwdscore\tCalculate the strength score of a given password\n"
printf "rain\t\tConsole screensaver with falling-rain effect (multiple color themes)\n"
printf "rmhost\t\tRemove host (name and IP) from SSH known_hosts; supports --all-users as root\n"
printf "rmspc\t\tReplace spaces in filenames with underscores (or a custom character)\n"
printf "setlocale\tSet console locale to any installed locale\n"
printf " * setc\tSet locale to standard C (POSIX)\n"
printf " * set*\tLocale shortcuts generated from SET_LOCALE in profile.conf\n"
printf "settrace\tActivate or deactivate ERR trap to display backtrace on script errors\n"
printf "set_theme\tSwitch the prompt colour theme; no argument lists available themes\n"
printf "showinfo\tDisplay welcome banner and system information (figlet + neofetch/fastfetch)\n"
printf "ssr\t\tSSH into a server as root, forwarding extra ssh options\n"
printf "taz\t\tCompress files and directories into a chosen archive format\n"
printf "urlencode\tURL-encode a string\n"
printf "utaz\t\tSmartly uncompress archives (zip, tar.gz/bz2/xz/lz, rar, arj, lha, ace, 7z, zst, cpio, cab, deb, rpm)\n"
printf "ver\t\tDisplay the installed profile version\n\n"
Please use <command> --help to obtain usage details.
EOF
printf "\nPlease use <command> --help to obtain usage details.\n"
}
export -f help
# ------------------------------------------------------------------------------
# EOF

View File

@@ -1,55 +1,199 @@
#!/usr/bin/env bash
# ------------------------------------------------------------------------------
# Copyright (c) 2013-2026 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
# Protected by the BSD3 license. Please read bellow for details.
#
# * Redistribution and use in source and binary forms,
# * with or without modification, are permitted provided
# * that the following conditions are met:
# *
# * Redistributions of source code must retain the above
# * copyright notice, this list of conditions and the
# * following disclaimer.
# *
# * Redistributions in binary form must reproduce the above
# * copyright notice, this list of conditions and the following
# * disclaimer in the documentation and/or other materials
# * provided with the distribution.
# *
# * Neither the name of the copyright holder nor the names
# * of any other contributors may be used to endorse or
# * promote products derived from this software without
# * specific prior written permission.
# *
# * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
# * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
# * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
# * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# * OF SUCH DAMAGE.
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Show profile version
# ------------------------------------------------------------------------------
# Usage: ver
ver()
{
echo "Profile version $PROFVERSION."
local PARSED
PARSED=$(getopt -o h --long help -n 'ver' -- "$@")
# shellcheck disable=SC2181 # getopt return code is checked immediately after
if [[ $? -ne 0 ]]; then
disp E "Invalid options, use \"ver --help\" to display usage."
return 1
fi
eval set -- "$PARSED"
while true; do
case "$1" in
-h|--help)
printf "ver: Display the current profile version.\nUsage: ver\n"
return 0
;;
--)
shift
break
;;
*)
disp E "Invalid options, use \"ver --help\" to display usage."
return 1
;;
esac
done
[[ -z $PROFVERSION ]] && \
disp W "No version defined. Profile is probably badly installed." && \
return 1
disp "Profile version $PROFVERSION."
}
export -f ver
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Display weather of the given city (or default one)
# ------------------------------------------------------------------------------
# Display weather for the given city (or the default one)
# Usage: meteo [city1 city2 ...]
meteo()
{
cities=$@
[[ $# -eq 0 ]] && local cities=$DEFAULT_CITY
local PARSED
for city in $cities; do
curl https://wttr.in/$city || echo "Failed fetching datas for $city."
PARSED=$(getopt -o h --long help -n 'meteo' -- "$@")
# shellcheck disable=SC2181 # getopt return code is checked immediately after
if [[ $? -ne 0 ]]; then
disp E "Invalid options, use \"meteo --help\" to display usage."
return 1
fi
eval set -- "$PARSED"
while true; do
case "$1" in
-h|--help)
printf "meteo: Fetch weather data.\n"
printf "Usage: meteo [city1 city2 ...]\n"
printf "If no city is provided, the default city from configuration will be used.\n"
return 0
;;
--)
shift
break
;;
*)
disp E "Invalid options, use \"meteo --help\" to display usage."
return 1
;;
esac
done
local cities=("$@")
local city="" encoded=""
[[ $# -eq 0 ]] && cities=("${METEO_DEFAULT_CITY:-}")
if [[ ${#cities[@]} -eq 0 || -z "${cities[0]}" ]]; then
disp E "No city given and METEO_DEFAULT_CITY is not set. Use 'meteo <city>'."
return 1
fi
for city in "${cities[@]}"; do
encoded=$(urlencode "$city")
dwl "https://wttr.in/$encoded" || \
disp E "Failed to fetch weather data for $city."
done
}
export -f meteo
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Display system general information
# ------------------------------------------------------------------------------
# Usage: showinfo
showinfo()
{
echo -e "\n"
local PARSED
PARSED=$(getopt -o h --long help -n 'showinfo' -- "$@")
# shellcheck disable=SC2181 # getopt return code is checked immediately after
if [[ $? -ne 0 ]]; then
disp E "Invalid options, use \"showinfo --help\" to display usage."
return 1
fi
eval set -- "$PARSED"
while true; do
case "$1" in
-h|--help)
printf "showinfo: Display system information (hostname, kernel, uptime and fetch output when available).\n"
printf "Usage: showinfo\n"
return 0
;;
--)
shift
break
;;
*)
disp E "Invalid options, use \"showinfo --help\" to display usage."
return 1
;;
esac
done
local hostname_str
local figopt=()
hostname_str="$(hostname)"
printf "\n"
if command -v figlet >/dev/null 2>&1; then
if [[ -s /usr/share/figlet/ansi_shadow.flf ]]; then
local figopt="-f ansi_shadow"
fi
figlet -k $(hostname) $figopt
[[ -s /usr/share/figlet/ansi_shadow.flf ]] && \
figopt=(-f ansi_shadow)
figlet -k "${figopt[@]}" "$hostname_str"
else
echo "$(hostname -f)"
printf "%s\n" "$hostname_str"
fi
echo ""
printf "\n"
if command -v neofetch >/dev/null 2>&1; then
neofetch
elif command -v fastfetch >/dev/null 2>&1; then
fastfetch
else
(
if [[ -s /etc/os-release ]]; then
# shellcheck disable=SC1091
. /etc/os-release
echo "$NAME $VERSION"
printf "%s %s\n" "$NAME" "$VERSION"
else
cat /proc/version
fi
echo "Uptime: $(uptime)"
printf "Uptime: %s\n" "$(uptime -p)"
)
fi
}
export -f showinfo
# ------------------------------------------------------------------------------
load_conf info
# EOF

View File

@@ -1,35 +1,182 @@
#!/usr/bin/env bash
# ------------------------------------------------------------------------------
# Change locale to French
# Copyright (c) 2013-2026 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
# Protected by the BSD3 license. Please read bellow for details.
#
# * Redistribution and use in source and binary forms,
# * with or without modification, are permitted provided
# * that the following conditions are met:
# *
# * Redistributions of source code must retain the above
# * copyright notice, this list of conditions and the
# * following disclaimer.
# *
# * Redistributions in binary form must reproduce the above
# * copyright notice, this list of conditions and the following
# * disclaimer in the documentation and/or other materials
# * provided with the distribution.
# *
# * Neither the name of the copyright holder nor the names
# * of any other contributors may be used to endorse or
# * promote products derived from this software without
# * specific prior written permission.
# *
# * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
# * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
# * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
# * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# * OF SUCH DAMAGE.
# ------------------------------------------------------------------------------
setfr ()
locale_check()
{
# Set fr locale definitions
export LANG=fr_FR.UTF-8
export LC_MESSAGES=fr_FR.UTF-8
export LC_ALL=fr_FR.UTF-8
locale -a | grep -qx "$1" || {
disp W "Locale '$1' is not installed on this system."
return 1
}
export -f setfr
return 0
}
# ------------------------------------------------------------------------------
# Change locale to C standard
# Change locale to the given one in parameter
# Usage: setlocale <locale>
setlocale()
{
local PARSED
PARSED=$(getopt -o h --long help -n 'setlocale' -- "$@")
# shellcheck disable=SC2181 # getopt return code is checked immediately after
if [[ $? -ne 0 ]]; then
disp E "Invalid options, use \"setlocale --help\" to display usage."
return 1
fi
eval set -- "$PARSED"
while true; do
case "$1" in
-h|--help)
printf "setlocale: Configure system environment locale variables.\n\n"
printf "Usage: setlocale <locale>\n\n"
printf "Options:\n"
printf " -h, --help Display this help screen\n"
return 0
;;
--)
shift
break
;;
*)
disp E "Invalid options, use \"setlocale --help\" to display usage."
return 1
;;
esac
done
local loc=$1
[[ -z $loc ]] && disp E "No locale specified." && return 1
locale_check "$loc" || return 1
export LANG=$loc
export LC_MESSAGES=$loc
export LC_TIME=$loc
export LC_NUMERIC=$loc
export LC_MONETARY=$loc
export LC_COLLATE=$loc
export LC_CTYPE=$loc
disp I "Locale set to $loc."
}
export -f setlocale
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Special case : change locale to C standard
# Usage: setc
setc()
{
# Locale definitions
export LANG=C
export LC_MESSAGES=C
export LC_ALL=C
disp I "Locale changed to standard C (POSIX)."
}
export -f setc
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Change locale to US (needed by Steam)
# ------------------------------------------------------------------------------
setus ()
# Build dynamic locale shortcuts from SET_LOCALE
# Expected format:
# SET_LOCALE="fr:fr_FR.UTF-8,us:en_US.UTF-8,es:es_ES.UTF-8"
# This creates functions:
# setfr, setus, setes, ...
build_locale_shortcuts()
{
# Locale definitions
export LANG=en_US.UTF-8
export LC_MESSAGES=en_US.UTF-8
export LC_ALL=en_US.UTF-8
local cfg="${SET_LOCALE:-}"
local item="" alias="" loc="" fname=""
local -a locale_items=()
[[ -z "$cfg" ]] && return 0
IFS=',' read -r -a locale_items <<< "$cfg"
for item in "${locale_items[@]}"; do
# Trim surrounding spaces
item="${item#"${item%%[![:space:]]*}"}"
item="${item%"${item##*[![:space:]]}"}"
[[ -z "$item" ]] && continue
if [[ "$item" != *:* ]]; then
disp W "Ignoring invalid SET_LOCALE entry: '$item' (expected alias:locale)."
continue
fi
alias="${item%%:*}"
loc="${item#*:}"
# Trim alias/locale spaces
alias="${alias#"${alias%%[![:space:]]*}"}"
alias="${alias%"${alias##*[![:space:]]}"}"
loc="${loc#"${loc%%[![:space:]]*}"}"
loc="${loc%"${loc##*[![:space:]]}"}"
# Validate alias for safe function names
if [[ ! "$alias" =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]]; then
disp W "Ignoring unsafe locale alias '$alias' in SET_LOCALE."
continue
fi
[[ -z "$loc" ]] && {
disp W "Ignoring empty locale for alias '$alias' in SET_LOCALE."
continue
}
export -f setus
fname="set${alias}"
# Optional collision warning
if declare -F "$fname" >/dev/null 2>&1; then
disp W "Overriding existing function '$fname'."
fi
# Build function dynamically
# shellcheck disable=SC2016
eval "${fname}() { setlocale \"$loc\"; }"
# shellcheck disable=SC2163
export -f "$fname"
done
unset cfg item alias loc fname locale_items
}
export -f build_locale_shortcuts
# ------------------------------------------------------------------------------
load_conf lang
build_locale_shortcuts
# ------------------------------------------------------------------------------
# EOF

View File

@@ -1,49 +1,322 @@
#!/usr/bin/env bash
# ------------------------------------------------------------------------------
# Copyright (c) 2013-2026 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
# Protected by the BSD3 license. Please read bellow for details.
#
# * Redistribution and use in source and binary forms,
# * with or without modification, are permitted provided
# * that the following conditions are met:
# *
# * Redistributions of source code must retain the above
# * copyright notice, this list of conditions and the
# * following disclaimer.
# *
# * Redistributions in binary form must reproduce the above
# * copyright notice, this list of conditions and the following
# * disclaimer in the documentation and/or other materials
# * provided with the distribution.
# *
# * Neither the name of the copyright holder nor the names
# * of any other contributors may be used to endorse or
# * promote products derived from this software without
# * specific prior written permission.
# *
# * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
# * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
# * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
# * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# * OF SUCH DAMAGE.
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Download a resource using curl, wget, or fetch.
# Usage: dwl <url> [output_file]
dwl()
{
case "$1" in
--help|-h)
echo "Usage: dwl <url> [output_file]"
echo "Downloads a resource using curl, wget, or fetch."
echo ""
echo "Arguments:"
echo " url The full URL to download (http/https/ftp)."
echo " output_file (Optional) Path to save the file. If omitted, prints to stdout."
return 0
;;
"")
echo "Error: URL argument is missing." >&2
echo "Try 'get_resource --help' for usage." >&2
return 1
;;
esac
case "$1" in
http://*|https://*|ftp://*) ;;
*)
echo "Error: '$1' does not look like a valid URL. Must start with http://, https://, or ftp://" >&2
return 1
;;
esac
local url="$1"
local output="$2"
# Honour preferred tool from configuration; fall back to auto-detection.
local preferred="${DWL_PREFERRED_TOOL:-}"
_try_curl()
{
if [[ -z "$output" ]]; then
curl -sL "$url"
else
curl -sL -o "$output" "$url"
fi
}
_try_wget()
{
if [[ -z "$output" ]]; then
wget -qO- "$url"
else
wget -q -O "$output" "$url"
fi
}
_try_fetch()
{
if [[ -z "$output" ]]; then
fetch -o - "$url"
else
fetch -o "$output" "$url"
fi
}
if [[ -n "$preferred" ]]; then
command -v "$preferred" >/dev/null 2>&1 || {
echo "Error: preferred download tool '$preferred' is not installed." >&2
return 1
}
case "$preferred" in
curl) _try_curl ;;
wget) _try_wget ;;
fetch) _try_fetch ;;
*)
echo "Error: DWL_PREFERRED_TOOL '$preferred' is not supported (use curl, wget or fetch)." >&2
return 1
;;
esac
elif command -v curl >/dev/null 2>&1; then
_try_curl
elif command -v wget >/dev/null 2>&1; then
_try_wget
elif command -v fetch >/dev/null 2>&1; then
_try_fetch
else
echo "Error: No download utility (curl, wget, or fetch) found." >&2
return 1
fi
unset -f _try_curl _try_wget _try_fetch
}
export -f dwl
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Determine if parameter is a valid IPv4 address
# ------------------------------------------------------------------------------
# Usage: isipv4 <ip_address>
isipv4()
{
# Set up local variables
local ip=$1
[[ -z $ip ]] && return 1
# Start with a regex format test
if [[ $ip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
# Start with a regex format test (four octets)
if [[ $ip =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then
local old_ifs=$IFS
IFS="."
ip=($ip)
IFS='.'
read -r -a ip_arr <<< "$ip"
IFS=$old_ifs
if [[ ${ip[0]} -le 255 && ${ip[1]} -le 255 \
&& ${ip[2]} -le 255 && ${ip[3]} -le 255 ]]; then
if [[ -t 1 ]]; then
echo "The given IPv4 is valid."
# Ensure each octet is between 0 and 255
local oct
for oct in "${ip_arr[@]}"; do
# Reject leading plus/minus or empty entries
if [[ -z $oct || $oct =~ [^0-9] ]]; then
[[ -t 1 ]] && disp "The given parameter is NOT a valid IPv4."
return 1
fi
if (( oct > 255 )); then
[[ -t 1 ]] && disp "The given parameter is NOT a valid IPv4."
return 1
fi
done
[[ -t 1 ]] && disp "The given IPv4 is valid."
return 0
fi
fi
if [[ -t 1 ]]; then
echo "The given parameter is NOT a valid IPv4."
fi
[[ -t 1 ]] && disp "The given parameter is NOT a valid IPv4."
return 1
}
export -f isipv4
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Determine if parameter is a valid IPv4 address
# ------------------------------------------------------------------------------
# Determine if parameter is a valid IPv6 address
# Usage: isipv6 <ip_address>
isipv6()
{
local ip="$1"
local regex='^([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4}$'
if [[ $ip =~ $regex ]]; then
if [[ -t 1 ]]; then
echo "The given IPv6 is valid."
disp "The given IPv6 is valid."
fi
return 0
fi
if [[ -t 1 ]]; then
echo "The given parameter is not a valid IPv6."
disp "The given parameter is not a valid IPv6."
fi
return 1
}
export -f isipv6
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Encode a string so it can be used as a URL parameter
# Usage: urlencode <string>
urlencode()
{
local LANG=C
local str="$*"
local length="${#str}"
for (( i = 0; i < length; i++ )); do
local c="${str:i:1}"
case "$c" in
[a-zA-Z0-9.~_-]) printf '%s' "$c" ;;
' ') printf '+' ;;
*) printf '%%%02X' "'$c" #| cut -d' ' -f2 ;;
esac
done
}
export -f urlencode
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Fetch and display external IP information
# Usage: myextip [-i|--ip] [-s|--isp] [-l|--loc] [-c|--coord]
# If no option is provided, all information will be displayed.
# Options:
# -h, --help Display help screen
# -i, --ip Display only the external IP address
# -s, --isp Display only the ISP name
# -l, --loc Display only the location (city, region, country)
# -c, --coord Display only the coordinates (latitude, longitude)
# -a, --as Display only the Autonomous System (AS) information
# -R, --raw Display raw JSON response
myextip()
{
local show_ip=false show_isp=false show_loc=false
local show_coord=false show_as=false show_raw=false
local all=true
# Parse arguments
while [[ "$#" -gt 0 ]]; do
case "$1" in
-i|--ip)
show_ip=true
all=false
;;
-s|--isp)
show_isp=true
all=false
;;
-l|--loc)
show_loc=true
all=false
;;
-c|--coord)
show_coord=true
all=false
;;
-a|--as)
show_as=true
all=false
;;
-R|--raw)
all=false
show_raw=true
;;
-h|--help)
printf "Fetch and display external IP information.\n\n"
printf "Usage: myextip [-i|--ip] [-s|--isp] [-l|--loc] [-c|--coord] [-a|--as] [-R|--raw]\n\n"
printf "Options:\n"
printf "\t-h, --help\tDisplay this help screen\n"
printf "\t-i, --ip\tDisplay only the external IP address\n"
printf "\t-s, --isp\tDisplay only the ISP name\n"
printf "\t-l, --loc\tDisplay only the location (city, region, country)\n"
printf "\t-c, --coord\tDisplay only the coordinates (latitude, longitude)\n"
printf "\t-a, --as\tDisplay only the Autonomous System (AS) information\n"
printf "\t-R, --raw\tDisplay raw JSON response\n"
return 0
;;
--)
shift
break
;;
*)
disp E "Unknown option: $1, use \"myextip --help\" to display usage."
return 1
;;
esac
shift
done
# Fetch data. Allow overriding endpoint via MYEXTIP_DEFAULT_URL config key.
local api_url="${MYEXTIP_DEFAULT_URL:-https://ip-api.com/json/}"
local response
if ! response=$(dwl "$api_url"); then
disp E "Failed to fetch external IP information from $api_url"
return 2
fi
# Parse with jq when available and when raw wasn't requested. The jq filter
# is tolerant to field-name differences between providers (ip-api / ipinfo).
if command -v jq >/dev/null 2>&1 && [[ "$show_raw" != true ]]; then
echo "$response" | jq -r --argjson all "$all" --argjson ip "$show_ip" \
--argjson isp "$show_isp" --argjson loc "$show_loc" \
--argjson coord "$show_coord" --argjson as "$show_as" '
[
(if $all or $ip then "IP Address : \(.query // .ip)" else empty end),
(if $all or $isp then "ISP : \(.isp // .org)" else empty end),
(if $all or $loc then
("Location : " + ((.city // "") + (if .city then ", " else "" end) + (if .regionName then .regionName else .region end) + (if .country then ", " + .country else "" end)))
else empty end),
(if $all or $coord then (if (.lat and .lon) then "Coordinates: \(.lat), \(.lon)" elif .loc then "Coordinates: \(.loc)" else empty end) else empty end),
(if $all or $as then "AS : \(.as // .org)" else empty end)
] | .[]'
else
[[ "$show_raw" != true ]] && disp W "jq is not installed, displaying raw JSON response."
echo "$response"
fi
}
export -f myextip
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# EOF
load_conf "net"
# EOF

View File

@@ -1,42 +1,181 @@
#!/usr/bin/env bash
# ------------------------------------------------------------------------------
# Copyright (c) 2013-2026 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
# Protected by the BSD3 license. Please read bellow for details.
#
# * Redistribution and use in source and binary forms,
# * with or without modification, are permitted provided
# * that the following conditions are met:
# *
# * Redistributions of source code must retain the above
# * copyright notice, this list of conditions and the
# * following disclaimer.
# *
# * Redistributions in binary form must reproduce the above
# * copyright notice, this list of conditions and the following
# * disclaimer in the documentation and/or other materials
# * provided with the distribution.
# *
# * Neither the name of the copyright holder nor the names
# * of any other contributors may be used to endorse or
# * promote products derived from this software without
# * specific prior written permission.
# *
# * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
# * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
# * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
# * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# * OF SUCH DAMAGE.
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Detect the active package manager of the current distribution.
# Detection is based on /etc/os-release (ID / ID_LIKE), then falls back to
# checking available binaries in a fixed priority order.
# Echoes one of: apt dnf yum zypper pacman apk portage xbps nix
# Returns 1 if no known package manager could be identified.
_get_pkgmgr()
{
local distro_id="" distro_like=""
if [[ -r /etc/os-release ]]; then
# shellcheck disable=SC1091
distro_id=$( . /etc/os-release 2>/dev/null; printf '%s' "${ID:-}" )
# shellcheck disable=SC1091
distro_like=$( . /etc/os-release 2>/dev/null; printf '%s' "${ID_LIKE:-}" )
fi
# Map distro IDs/families to a package manager.
# ID_LIKE is space-separated and may list multiple families.
local id
for id in $distro_id $distro_like; do
case "${id,,}" in
debian|ubuntu|linuxmint|raspbian|pop|kali|elementary|zorin|neon|parrot)
echo "apt"; return 0 ;;
fedora)
echo "dnf"; return 0 ;;
rhel|centos|rocky|almalinux|ol|scientific|amzn)
command -v dnf >/dev/null 2>&1 && { echo "dnf"; return 0; }
echo "yum"; return 0 ;;
opensuse*|sles|sled)
echo "zypper"; return 0 ;;
arch|manjaro|endeavouros|garuda|artix|cachyos)
echo "pacman"; return 0 ;;
alpine)
echo "apk"; return 0 ;;
gentoo)
echo "portage"; return 0 ;;
void)
echo "xbps"; return 0 ;;
nixos)
echo "nix"; return 0 ;;
esac
done
# Fallback: check for binaries in priority order.
local bin
for bin in apt-get dnf yum zypper pacman apk emerge xbps-install nix-env; do
command -v "$bin" >/dev/null 2>&1 && {
case "$bin" in
apt-get) echo "apt" ;;
emerge) echo "portage" ;;
xbps-install) echo "xbps" ;;
nix-env) echo "nix" ;;
*) echo "$bin" ;;
esac
return 0
}
done
return 1
}
export -f _get_pkgmgr
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Look for a package within installed one
# ------------------------------------------------------------------------------
dpkgs ()
# Usage: pkgs <string>
pkgs()
{
local count=0
for opt in $@ ; do
case $opt in
"-h"|"--help")
echo "dpkgs: look for an installed package by it's name."
echo
echo "Usage: dpkgs <string>"
local ignore_case=${PKGS_DEFAULT_IGNORE_CASE:-0}
local PARSED
PARSED=$(getopt -o hi --long help,ignore-case -n 'pkgs' -- "$@")
# shellcheck disable=SC2181 # getopt return code is checked immediately after
if [[ $? -ne 0 ]]; then
disp E "Invalid options, use \"pkgs --help\" to display usage."
return 1
fi
eval set -- "$PARSED"
while true; do
case "$1" in
-h|--help)
printf "pkgs: Look for an installed package by its name.\n\n"
printf "Usage: pkgs [options] <string>\n\n"
printf "Options:\n"
printf "\t-h, --help\tDisplay this help screen\n"
printf "\t-i, --ignore-case\tIgnore case distinctions\n"
return 0
;;
"-"*)
echo "Invalid option, use \"dpkgs --help\" to display usage."
echo
return 1
-i|--ignore-case)
ignore_case=1
shift
;;
--)
shift
break
;;
*)
local pkg=$1 && shift
count=$(( $count + 1 ))
[[ $count -gt 1 ]] &&
echo "*** Error: Please specify a package name, without space, eventually partial." &&
disp E "Invalid option: $1"
return 1
;;
esac
done
[[ $count -lt 1 ]] &&
echo "*** Error: Please specify a package name, without space, eventually partial." &&
local pkg="$1"
[[ -z "$pkg" ]] && {
disp E "Please specify a package name, without space, eventually partial."
return 1
[[ -x /usr/sbin/dpkg ]] &&
echo "*** Error: dpkg command seems unavialable." &&
return 2
dpkg -l | grep $pkg
}
export -f dpkgs
# Build grep command
local grep_opt=""
(( ignore_case )) && grep_opt="-i"
local pkgmgr
pkgmgr=$(_get_pkgmgr) || {
disp E "No usable package manager could be detected on this system."
return 2
}
local -a list_cmd
case "$pkgmgr" in
apt) list_cmd=(dpkg-query -l) ;;
dnf|yum|zypper) list_cmd=(rpm -qa) ;;
pacman) list_cmd=(pacman -Q) ;;
apk) list_cmd=(apk list --installed) ;;
portage) list_cmd=(qlist -I) ;;
xbps) list_cmd=(xbps-query -l) ;;
nix) list_cmd=(nix-env -q) ;;
*)
disp E "Package manager '$pkgmgr' is not supported by pkgs."
return 2
;;
esac
"${list_cmd[@]}" | grep ${grep_opt:+"$grep_opt"} "$pkg"
}
export -f pkgs
# ------------------------------------------------------------------------------
load_conf "packages"
# EOF

View File

@@ -1,40 +1,256 @@
#!/usr/bin/env bash
# ------------------------------------------------------------------------------
# Copyright (c) 2013-2022 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
# Protected by the BSD3 license. Please read bellow for details.
#
# * Redistribution and use in source and binary forms,
# * with or without modification, are permitted provided
# * that the following conditions are met:
# *
# * Redistributions of source code must retain the above
# * copyright notice, this list of conditions and the
# * following disclaimer.
# *
# * Redistributions in binary form must reproduce the above
# * copyright notice, this list of conditions and the following
# * disclaimer in the documentation and/or other materials
# * provided with the distribution.
# *
# * Neither the name of the copyright holder nor the names
# * of any other contributors may be used to endorse or
# * promote products derived from this software without
# * specific prior written permission.
# *
# * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
# * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
# * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
# * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# * OF SUCH DAMAGE.
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Search processes matching the given string
# ------------------------------------------------------------------------------
# Usage: ppg <string>
ppg()
{
ps -edf | grep $@ | grep -v "grep $@"
if [[ "$1" == "-h" || "$1" == "--help" ]]; then
printf "ppg: Search processes matching the given string.\n\n"
printf "Usage: ppg <string>\n\n"
printf "Options:\n"
printf "\t-h, --help\t\tDisplay this help screen\n"
return 0
fi
if [[ -z "$1" ]]; then
disp E "Usage: ppg <string>"
return 1
fi
local pattern="$*"
if command -v pgrep >/dev/null 2>&1; then
pgrep -af -- "$pattern"
return $?
fi
ps -ef | awk -v pattern="$pattern" '
NR == 1 {
print
next
}
index($0, pattern) {
print
matched = 1
}
END {
exit matched ? 0 : 1
}
'
}
export -f ppg
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# List processes owned by a specific user
# Usage: ppu <username>
ppu()
{
if [[ "$1" == "-h" || "$1" == "--help" ]]; then
printf "ppu: List processes owned by a specific user.\n\n"
printf "Usage: ppu <username>\n\n"
printf "Options:\n"
printf "\t-h, --help\t\tDisplay this help screen\n"
return 0
fi
if [[ -z "$1" ]]; then
disp E "Usage: ppu <username>"
return 1
fi
# -u lists processes for a specific user
# -o provides a clean, standard output format
ps -u "$1" -o "${PPU_DEFAULT_FORMAT:-pid,user,%cpu,%mem,start,time,command}"
}
export -f ppu
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# List processes by exact command name (no path/parameters)
# Usage: ppn <command_name>
ppn()
{
if [[ "$1" == "-h" || "$1" == "--help" ]]; then
printf "ppn: List processes by exact command name (no path/parameters).\n\n"
printf "Usage: ppn <command_name>\n\n"
printf "Options:\n"
printf "\t-h, --help\t\tDisplay this help screen\n"
return 0
fi
if [[ -z "$1" ]]; then
disp E "Usage: ppn <command_name>"
return 1
fi
# -e: select all processes
# -o: specify custom output columns (PID and Command name)
# grep -w: ensures exact word matching so 'bash' doesn't match 'dbash'
# shellcheck disable=SC2009 # pgrep do not offer the -w switch
ps -eo pid,comm | grep -w "$1"
}
export -f ppn
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Get PID list of the given process name
# ------------------------------------------------------------------------------
# Usage: ppid <process_name [process_name2 ...]>
gpid()
{
[[ $UID -eq 0 ]] && local psopt="-A"
[[ $# -eq 1 ]] && local single=1
for pid in $@; do
local result=$(ps $psopt | grep $pid | awk '{print $1}' | sed "s/\n/ /")
if [[ $single ]]; then
[[ $result ]] && echo "${result//$'\n'/ }"
if [[ "$1" == "-h" || "$1" == "--help" ]]; then
printf "gpid: Get PID list of the given process name.\n\n"
printf "Usage: gpid <process_name [process_name2 ...]>\n\n"
printf "Options:\n"
printf "\t-h, --help\t\tDisplay this help screen\n"
return 0
fi
if [[ -z "$1" ]]; then
disp E "Usage: gpid <process_name [process_name2 ...]>"
return 1
fi
local single=0
local found=0
local proc_name result
[[ $# -eq 1 ]] && single=1
for proc_name in "$@"; do
result=""
if command -v pgrep >/dev/null 2>&1; then
result=$(pgrep -d ' ' -x -- "$proc_name")
else
[[ $result ]] && echo "$pid: ${result//$'\n'/ }"
result=$(ps -eo pid=,comm= | awk -v proc="$proc_name" '
$2 == proc {
if (out != "") {
out = out " "
}
out = out $1
}
END {
print out
}
')
fi
[[ -z "$result" ]] && continue
found=1
if (( single )); then
echo "$result"
else
echo "$proc_name: $result"
fi
done
[[ $result ]] || return 1
(( found )) || return 1
}
export -f gpid
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Kill all processes owned by the given users
# ------------------------------------------------------------------------------
# Kill all processes owned by the given users (kill user)
# Usage: ku <username1 [username2 ...]>
ku()
{
for u in $@; do
killall -u $u
if [[ "$1" == "-h" || "$1" == "--help" ]]; then
printf "ku: Kill all processes owned by the given users.\n\n"
printf "Usage: ku <username1 [username2 ...]>\n\n"
printf "Options:\n"
printf "\t-h, --help\t\tDisplay this help screen\n"
return 0
fi
if [[ -z "$1" ]]; then
disp E "Usage: ku <username1 [username2 ...]>"
return 1
fi
for u in "$@"; do
if ! id "$u" >/dev/null 2>&1; then
disp E "User '$u' does not exist."
return 1
else
killall ${KU_DEFAULT_SIGNAL:+-${KU_DEFAULT_SIGNAL}} -u "$u"
fi
done
}
export -f ku
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Kill all children of a process then the process (kill tree)
# Usage: kt <pid> [kill_options]
kt()
{
if [[ "$1" == "-h" || "$1" == "--help" ]]; then
printf "kt: Kill all children of a process then the process (kill tree).\n\n"
printf "Usage: kt <pid> [kill_options]\n\n"
printf "Options:\n"
printf "\t-h, --help\t\tDisplay this help screen\n"
return 0
fi
if [[ -z "$1" ]]; then
disp E "Usage: kt <pid>"
return 1
fi
local parent_pid="$1"
shift
if [[ "$parent_pid" == "0" || "$parent_pid" == "1" ]]; then
disp E "Safety abort: Refusing to kill PID $parent_pid (system critical)."
return 1
fi
local children_pids
children_pids=$(pgrep -P "$parent_pid")
for pid in $children_pids; do
kt "$pid" "$@" || break
done
kill "$@" "$parent_pid"
}
export -f kt
# ------------------------------------------------------------------------------
load_conf "processes"
# EOF

View File

@@ -1,19 +1,258 @@
#!/usr/bin/env bash
# ------------------------------------------------------------------------------
# Copyright (c) 2013-2026 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
# Protected by the BSD3 license. Please read bellow for details.
#
# * Redistribution and use in source and binary forms,
# * with or without modification, are permitted provided
# * that the following conditions are met:
# *
# * Redistributions of source code must retain the above
# * copyright notice, this list of conditions and the
# * following disclaimer.
# *
# * Redistributions in binary form must reproduce the above
# * copyright notice, this list of conditions and the following
# * disclaimer in the documentation and/or other materials
# * provided with the distribution.
# *
# * Neither the name of the copyright holder nor the names
# * of any other contributors may be used to endorse or
# * promote products derived from this software without
# * specific prior written permission.
# *
# * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
# * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
# * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
# * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# * OF SUCH DAMAGE.
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Parse a prompt theme file safely — it is NEVER sourced or executed.
# Two categories of keys are accepted:
# PROMPT_COLOR_* — prompt slot colours (TIME_FG, BAR_BG, …)
# Standard colour variables from disp.sh (Blue, On_IBlack, …) — allows a
# theme to redefine the palette used everywhere in the shell session.
# Allowed value forms:
# $ColorName or ${ColorName} — colour variable from disp.sh (resolved by
# indirection via ${!varname})
# \e[...m or \033[...m — raw ANSI escape literal (single block)
# Any other key or value is rejected with a warning.
# Usage: load_theme <theme_name_or_path> [theme_dir]
# theme_name_or_path : bare name (e.g. "dark") or an explicit path.
# theme_dir : directory to search for bare names; defaults to
# $MYPATH/profile.d/themes. Overridable via
# PROMPT_THEME_DIR.
load_theme()
{
local theme_name="$1"
local theme_dir="${2:-${PROMPT_THEME_DIR:-$MYPATH/profile.d/themes}}"
local theme_file=""
[[ -z "$theme_name" ]] && return 0
if [[ "$theme_name" == /* || "$theme_name" == */* ]]; then
theme_file="$theme_name"
else
theme_file="$theme_dir/${theme_name}.theme"
fi
if [[ ! -f "$theme_file" || ! -r "$theme_file" ]]; then
printf "[ Warning ] load_theme: theme file not found: %s\n" "$theme_file" >&2
return 1
fi
# ---- Key whitelist: prompt slots ----------------------------------------
local -A _lth_allowed=(
[PROMPT_COLOR_TIME_FG]=1 [PROMPT_COLOR_TIME_BG]=1
[PROMPT_COLOR_BAR_BG]=1
[PROMPT_COLOR_OK_FG]=1 [PROMPT_COLOR_OK_MARK]=1
[PROMPT_COLOR_ERR_BG]=1 [PROMPT_COLOR_ERR_FG]=1 [PROMPT_COLOR_ERR_MARK]=1
[PROMPT_COLOR_ROOT_FG]=1 [PROMPT_COLOR_USER_FG]=1
[PROMPT_COLOR_DIR_FG]=1
)
# ---- Colour variable names exported by disp.sh --------------------------
local _lth_color_re
_lth_color_re='Black|Red|Green|Yellow|Blue|Purple|Cyan|White'
_lth_color_re+='|BBlack|BRed|BGreen|BYellow|BBlue|BPurple|BCyan|BWhite'
_lth_color_re+='|UBlack|URed|UGreen|UYellow|UBlue|UPurple|UCyan|UWhite'
_lth_color_re+='|On_Black|On_Red|On_Green|On_Yellow|On_Blue|On_Purple|On_Cyan|On_White'
_lth_color_re+='|IBlack|IRed|IGreen|IYellow|IBlue|IPurple|ICyan|IWhite'
_lth_color_re+='|BIBlack|BIRed|BIGreen|BIYellow|BIBlue|BIPurple|BICyan|BIWhite'
_lth_color_re+='|On_IBlack|On_IRed|On_IGreen|On_IYellow|On_IBlue|On_IPurple|On_ICyan|On_IWhite'
_lth_color_re+='|DEFAULTFG|DEFAULTBG|DEFAULTCOL|RESETCOL'
# ---- Key whitelist: standard colour vars (same list as above) -----------
local _lth_cn
for _lth_cn in \
Black Red Green Yellow Blue Purple Cyan White \
BBlack BRed BGreen BYellow BBlue BPurple BCyan BWhite \
UBlack URed UGreen UYellow UBlue UPurple UCyan UWhite \
On_Black On_Red On_Green On_Yellow On_Blue On_Purple On_Cyan On_White \
IBlack IRed IGreen IYellow IBlue IPurple ICyan IWhite \
BIBlack BIRed BIGreen BIYellow BIBlue BIPurple BICyan BIWhite \
On_IBlack On_IRed On_IGreen On_IYellow On_IBlue On_IPurple On_ICyan On_IWhite \
DEFAULTFG DEFAULTBG DEFAULTCOL RESETCOL; do
_lth_allowed[$_lth_cn]=1
done
unset _lth_cn
# ERE: safe colour reference $Name or ${Name}
local _lth_ref_re='^\$\{?('"$_lth_color_re"')\}?$'
# ERE: raw ANSI escape literal \e[...m or \033[...m
local _lth_ansi_re='^(\\e|\\033)\[[0-9;]*m$'
# ---- Line parser ---------------------------------------------------------
local _lth_line _lth_key _lth_value _lth_varname _lth_lineno=0
while IFS= read -r _lth_line || [[ -n "$_lth_line" ]]; do
((_lth_lineno++))
_lth_line="${_lth_line%$'\r'}" # strip CR
_lth_line="${_lth_line#"${_lth_line%%[![:space:]]*}"}" # ltrim
_lth_line="${_lth_line%"${_lth_line##*[![:space:]]}"}" # rtrim
[[ -z "$_lth_line" || "$_lth_line" == '#'* ]] && continue # blank/comment
[[ "$_lth_line" == 'export '* ]] && _lth_line="${_lth_line#export }" # strip prefix
if [[ "$_lth_line" != *=* ]]; then
printf "[ Warning ] load_theme: %s:%d: not a key=value pair, ignoring.\n" \
"$theme_file" "$_lth_lineno" >&2
continue
fi
_lth_key="${_lth_line%%=*}"
_lth_value="${_lth_line#*=}"
_lth_key="${_lth_key#"${_lth_key%%[![:space:]]*}"}"
_lth_key="${_lth_key%"${_lth_key##*[![:space:]]}"}" # trim key
if [[ -z "${_lth_allowed[$_lth_key]+x}" ]]; then
printf "[ Warning ] load_theme: %s:%d: key '%s' is not allowed, ignoring.\n" \
"$theme_file" "$_lth_lineno" "$_lth_key" >&2
continue
fi
# Strip surrounding quotes (handles inline trailing comments like KEY="val" # note)
if [[ "$_lth_value" == '"'* ]]; then
_lth_value="${_lth_value#\"}"
_lth_value="${_lth_value%%\"*}"
elif [[ "$_lth_value" == "'"* ]]; then
_lth_value="${_lth_value#\'}"
_lth_value="${_lth_value%%\'*}"
fi
if [[ "$_lth_value" =~ $_lth_ref_re ]]; then
# Safe colour variable reference — resolve via indirection
_lth_varname="${_lth_value#\$}"
_lth_varname="${_lth_varname#\{}"
_lth_varname="${_lth_varname%\}}"
export "$_lth_key"="${!_lth_varname}"
elif [[ "$_lth_value" =~ $_lth_ansi_re ]]; then
# Raw ANSI escape literal — accept as-is
export "$_lth_key"="$_lth_value"
else
printf "[ Warning ] load_theme: %s:%d: invalid value for '%s', ignoring.\n" \
"$theme_file" "$_lth_lineno" "$_lth_key" >&2
fi
done < "$theme_file"
}
# Not exported, it remains private
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Dynamically switch the prompt theme for the current shell session.
# Calls load_theme to apply the new colour values immediately, then updates
# PROMPT_THEME so subshells and the set_prompt fallback chain reflect the
# change. PROMPT_THEME_DIR is honoured when set.
# Usage: set_theme [theme_name_or_path]
# With no argument (or -l / --list), lists available .theme files.
set_theme()
{
local theme_dir="${PROMPT_THEME_DIR:-${MYPATH}/profile.d/themes}"
# -- help mode -----------------------------------------------------------
if [[ "$1" == "-h" || "$1" == "--help" ]]; then
printf "set_theme: Switch the prompt colour theme for the current shell session.\n\n"
printf "Usage: set_theme [options] [theme]\n\n"
printf "Options:\n"
printf " -h, --help Display this help screen\n"
printf " -l, --list List available themes (default when no argument is given)\n\n"
printf "Arguments:\n"
printf " theme Bare theme name (e.g. 'dark') or an explicit path to a .theme file.\n"
printf " Themes are searched in: %s\n" "$theme_dir"
printf " Override with PROMPT_THEME_DIR in profile.conf [prompt].\n\n"
printf "Examples:\n"
printf " set_theme — list available themes\n"
printf " set_theme dark — apply the dark theme\n"
printf " set_theme ~/my.theme — apply a theme by path\n"
return 0
fi
# -- list mode -----------------------------------------------------------
if [[ $# -eq 0 || "$1" == "-l" || "$1" == "--list" ]]; then
printf "Available themes in %s:\n" "$theme_dir"
local f name
for f in "$theme_dir"/*.theme; do
[[ -f "$f" ]] || continue
name="${f##*/}"
name="${name%.theme}"
if [[ "$name" == "${PROMPT_THEME:-}" ]]; then
printf " * %s (active)\n" "$name"
else
printf " %s\n" "$name"
fi
done
return 0
fi
# -- apply mode ----------------------------------------------------------
local theme_name="$1"
# Reset colours to defaults before loading the new theme
set_colors
load_theme "$theme_name" || return 1
export PROMPT_THEME="$theme_name"
disp I "Prompt theme set to $theme_name."
}
export -f set_theme
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# timer_* functions : internal timing function for prompt
# ------------------------------------------------------------------------------
# Usage: timer_now
# This function returns the current time in nanoseconds since the epoch. It
# first tries to use the %N format specifier for nanoseconds, but if that is
# not supported (e.g., on older systems), it falls back to seconds.
function timer_now
{
date +%s%N
date +%s%N 2>/dev/null || date +%s
}
# Usage: timer_start
# This function initializes the timer_start variable with the current time in
# nanoseconds. It is used to measure the elapsed time for the prompt.
function timer_start
{
timer_start=${timer_start:-$(timer_now)}
}
# Usage: timer_stop
# This function calculates the elapsed time since timer_start and formats it
# into a human-readable string with appropriate units (us, ms, s, m, h
function timer_stop
{
local delta_us=$((($(timer_now) - $timer_start) / 1000))
local delta_us=$((($(timer_now) - timer_start) / 1000))
local us=$((delta_us % 1000))
local ms=$(((delta_us / 1000) % 1000))
local s=$(((delta_us / 1000000) % 60))
@@ -39,52 +278,84 @@ function timer_stop
}
# ------------------------------------------------------------------------------
# Function triguered internaly by bash : defining prompt
# ------------------------------------------------------------------------------
# Function triggered internally by bash : defining prompt
# Usage: set_prompt
# This function is called by bash before displaying the prompt. It sets the
# PS1 variable to a custom prompt that includes the exit status of the last
# command, the elapsed time of the last command, and the current user and host.
set_prompt()
{
Last_Command=$? # Must come first!
Blue='\[\e[0;34m\]'
White='\[\e[01;37m\]'
Yellow='\[\e[01;93m\]'
Red='\[\e[01;31m\]'
Green='\[\e[01;32m\]'
OnGrey='\[\e[47m\]'
OnRed='\[\e[41m\]'
OnBlue='\[\e[44m\]'
ICyan='\[\e[0;96m\]'
Default='\[\e[00m\]'
FancyX='\342\234\227'
Checkmark='\342\234\223'
local Last_Command=$? # Must come first!
local FancyX='\342\234\227'
local Checkmark='\342\234\223'
# Begin with time
PS1="\[\e[s$Blue$OnGrey [ \t ] $OnBlue"
# Resolve theme/config colours with hardcoded fallbacks
local _time_fg="${PROMPT_COLOR_TIME_FG:-$Blue}"
local _time_bg="${PROMPT_COLOR_TIME_BG:-$On_White}"
local _bar_bg="${PROMPT_COLOR_BAR_BG:-$On_Blue}"
local _ok_fg="${PROMPT_COLOR_OK_FG:-$BWhite}"
local _ok_mark="${PROMPT_COLOR_OK_MARK:-$BGreen}"
local _err_bg="${PROMPT_COLOR_ERR_BG:-$On_Red}"
local _err_fg="${PROMPT_COLOR_ERR_FG:-$White}"
local _err_mark="${PROMPT_COLOR_ERR_MARK:-$BYellow}"
local _root_fg="${PROMPT_COLOR_ROOT_FG:-$Red}"
local _user_fg="${PROMPT_COLOR_USER_FG:-$BGreen}"
local _dir_fg="${PROMPT_COLOR_DIR_FG:-$ICyan}"
# Add a bright white exit status for the last command
# Begin with time (cursor-save is non-printing; all ANSI sequences wrapped
# in \[...\] so bash does not count them toward the visible line width).
# Every fg colour is combined with its section bg in the same \[...\] block
# so that even "reset" colours (0;Xm) cannot strip the background.
PS1="\[\e[s\]\[${_time_fg}${_time_bg}\] [ \t ] \[${_bar_bg}\]"
# If it was successful, print a green check mark. Otherwise, print
# a red X.
# Add exit status of the last command.
# If it was successful, print a green check mark. Otherwise, print a red X.
if [[ $Last_Command == 0 ]]; then
PS1+="$White$OnBlue [ \$Last_Command "
PS1+="$Green$Checkmark "
else
PS1+="$White$OnRed [ \$Last_Command "
PS1+="$Yellow$FancyX "
fi
# Add the ellapsed time and current date
PS1+="\[${_ok_fg}${_bar_bg}\] [ $Last_Command "
PS1+="\[${_ok_mark}${_bar_bg}\]${Checkmark} "
# Add the elapsed time, then close the status section and return to bar bg.
timer_stop
PS1+="($timer_show)$White ] $OnBlue "
# If root, just print the host in red. Otherwise, print the current user
# and host in green.
if [[ $EUID -eq 0 ]]; then
PS1+="$Red\\u$Green@\\h"
PS1+="($timer_show)\[${_ok_fg}${_bar_bg}\] ] "
else
PS1+="$Green\\u@\\h"
PS1+="\[${_err_fg}${_err_bg}\] [ $Last_Command "
PS1+="\[${_err_mark}${_err_bg}\]${FancyX} "
timer_stop
PS1+="($timer_show)\[${_err_fg}${_err_bg}\] ] "
fi
PS1+="\e[K\e[u$Default\n"
# Print the working directory and prompt marker in blue, and reset
# the text color to the default.
PS1+="$ICyan\\w \\\$$Default "
# If root, print the host in root colour. Otherwise use user colour.
if [[ $EUID -eq 0 ]]; then
PS1+="\[${_root_fg}${_bar_bg}\] \\u\[${_user_fg}${_bar_bg}\]@\\h"
else
PS1+="\[${_user_fg}${_bar_bg}\] \\u@\\h"
fi
PS1+="\[\e[K\e[u\]\[$RESETCOL\]\n"
# Print the working directory and prompt marker, then reset colour.
PS1+="\[${_dir_fg}\]\\w \\\$\[$RESETCOL\] "
}
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Theme and configuration loading.
# Precedence (lowest → highest):
# 1. Hardcoded fallbacks in set_prompt
# 2. Theme file (PROMPT_THEME key from [prompt] section)
# 3. Individual PROMPT_COLOR_* overrides in [prompt] section
#
# CONF_prompt is already populated by parse_conf (run in profile.sh before
# modules are sourced). We extract PROMPT_THEME and PROMPT_THEME_DIR from the
# raw associative array now so load_theme can run before load_conf "prompt"
# exports remaining keys. That way any PROMPT_COLOR_* value set explicitly in
# [prompt] wins over the same variable set by the theme file.
_pt_theme="${CONF_prompt[PROMPT_THEME]:-}"
_pt_dir="${CONF_prompt[PROMPT_THEME_DIR]:-}"
[[ -n "$_pt_theme" ]] && load_theme "$_pt_theme" ${_pt_dir:+"$_pt_dir"}
unset _pt_theme _pt_dir
load_conf "prompt"
# ------------------------------------------------------------------------------
# EOF

560
profile.d/pwd.sh Executable file → Normal file
View File

@@ -1,151 +1,483 @@
#!/usr/bin/env bash
# ------------------------------------------------------------------------------
# Copyright (c) 2013-2026 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
# Protected by the BSD3 license. Please read bellow for details.
#
# * Redistribution and use in source and binary forms,
# * with or without modification, are permitted provided
# * that the following conditions are met:
# *
# * Redistributions of source code must retain the above
# * copyright notice, this list of conditions and the
# * following disclaimer.
# *
# * Redistributions in binary form must reproduce the above
# * copyright notice, this list of conditions and the following
# * disclaimer in the documentation and/or other materials
# * provided with the distribution.
# *
# * Neither the name of the copyright holder nor the names
# * of any other contributors may be used to endorse or
# * promote products derived from this software without
# * specific prior written permission.
# *
# * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
# * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
# * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
# * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# * OF SUCH DAMAGE.
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# genpwd : generate a password with different criteria
# default 16 car with up and low car, symbol and number
# Usage: genpwd [options] [--extracars=<cars>] [--length=<n>] [nb_passwd]
# Options:
# -h, --help Display that help screen
# -s, --nosymbols Exclude symbols
# -n, --nonumbers Exclude numbers
# -u, --noup Exclude uppercase letters
# -l, --nolow Exclude lowercase letters
# -e=<c>, --extracars=<c>
# Add the given caracters to the possible caracter list
# -L=<n>, --length=<n>
# Set length of the password (default is 16)
# -o=<n>, --occurences=<n>
# Set the maximum occurences of a same caracter (default is 2)
# The function is very slow on Windows
# ------------------------------------------------------------------------------
genpwd()
{
local length=16
local occurs=2 # Bug, if set to 1, seems to be ignored
local symb=1 maj=1 min=1 numb=1
local nbpwd=1
local length=${GENPWD_DEFAULT_LENGTH:-16}
local occurs=${GENPWD_DEFAULT_OCCURS:-2}
local symb=${GENPWD_DEFAULT_SYMBOLS:-1}
local maj=${GENPWD_DEFAULT_UPPERCASE:-1}
local min=${GENPWD_DEFAULT_LOWERCASE:-1}
local numb=${GENPWD_DEFAULT_NUMBERS:-1}
local nbpwd=${GENPWD_DEFAULT_COUNT:-1}
local extcar=""
for opt in $@; do
case $opt in
"-h"|"--help")
echo "genpwd: generate a secure random password."
echo
echo "Usage: genpwd [options] [--extracars=<cars>] [--length=<n>] [nb_passwd]"
echo
echo "Options:"
echo " -h, --help Display that help screen"
echo " -s, --nosymbols Exclude symbols"
echo " -n, --nonumbers Exclude numbers"
echo " -u, --noup Exclude uppercase letters"
echo " -l, --nolow Exclude lowercase letters"
echo " -e=<c>, --extracars=<c>"
echo " Add the given caracters to the possible caracter list"
echo " -L=<n>, --length=<n>"
echo " Set length of the password (default is $length)"
echo " -o=<n>, --occurences=<n>"
echo " Set the maximum occurences of a same caracter (default is $occurs)"
echo
echo "If the --extracars parameter is given, at least one of the given caracter will"
echo "be used in the final password."
echo
echo "Please note that some caracters might be interpreted by Bash or Awk programs,"
echo "and thus, cannot be used without provoquing errors. Those identified caracters"
echo "are :"
echo ' * ? \ $ { }'
echo
local PARSED
PARSED=$(getopt -o hsnule:L:o: --long \
help,nosymbols,nonumbers,noup,nolow,extracars:,length:,occurences:,occurrences: \
-n 'genpwd' -- "$@")
# shellcheck disable=SC2181 # getopt return code is checked immediately after
if [[ $? -ne 0 ]]; then return 1; fi
eval set -- "$PARSED"
while true; do
case "$1" in
-h|--help)
printf "genpwd: Generate random password(s).\n\n"
printf "Usage: genpwd [options] [nb_passwd]\n\n"
printf "Options:\n"
printf "\t-h, --help\t\tDisplay this help screen\n"
printf "\t-s, --nosymbols\t\tExclude symbols\n"
printf "\t-n, --nonumbers\t\tExclude numbers\n"
printf "\t-u, --noup\t\tExclude uppercase letters\n"
printf "\t-l, --nolow\t\tExclude lowercase letters\n"
printf "\t-e, --extracars <c>\tAdd characters to the pool\n"
printf "\t-L, --length <n>\tSet password length (default: 16)\n"
printf "\t-o, --occurences <n>\tMax occurrences per character (default: 2)\n"
return 0
;;
"-s"|"--nosymbols")
-s|--nosymbols)
symb=0
shift
;;
"-n"|"--nonumbers")
-n|--nonumbers)
numb=0
shift
;;
"-u"|"--noup")
-u|--noup)
maj=0
shift
;;
"-l"|"--nolow")
-l|--nolow)
min=0
shift
;;
"-e"?*|"--extracars"?*)
local extcar=$(echo "$opt" | cut -f 2- -d '=')
-e|--extracars)
extcar="$2"
shift 2
;;
"-L"?*|"--length"?*)
local length=$(echo "$opt" | cut -f 2- -d '=')
if ! [[ $length =~ ^[0-9]+$ ]]; then
echo "The --length parameter requires a number."
-L|--length)
length="$2"
if ! [[ $length =~ ^[1-9][0-9]*$ ]]; then
disp E "The --length parameter requires a positive integer."
return 1
fi
shift 2
;;
"-o"?*|"--occurences"?*)
local occurs=$(echo "$opt" | cut -f 2- -d '=')
if ! [[ $occurs =~ ^[1-9]+$ ]]; then
echo "The --occurs parameter requires a number from 1 to 9."
-o|--occurences|--occurrences)
occurs="$2"
if ! [[ $occurs =~ ^[1-9][0-9]*$ ]]; then
disp E "The --occurences parameter requires a positive integer."
return 1
fi
shift 2
;;
"-*")
echo "Unknow parameter ${opt}."
return 1
--)
shift
break
;;
*)
if ! [[ $opt =~ ^[1-9]+$ ]]; then
echo "Unknow parameter ${opt}."
disp E "Invalid options, use \"genpwd --help\" to display usage."
return 1
else
local nbpwd=$opt
fi
;;
esac
done
# Function selecting a random caracter from the list in parameter
pickcar()
{
# When a character is picked we check if it's not appearing already twice
# elsewhere, we choose an other char, to compensate weak bash randomizer
while [[ -z $char ]]; do
local char=$(echo ${1:RANDOM%${#1}:1} $RANDOM)
if [[ $(awk -F"$char" '{print NF-1}' <<< "$picked") -gt $occurs ]]; then
unset char
fi
done
picked+="$char"
echo "$char"
}
echo "Generating $nbpwd passwords, please wait..."
for n in $( seq 1 $nbpwd ); do
{
local carset='' # store final caracter set to use
local picked='' # store already used caracter
local rlength=0 # store already assigned length of caracters
# ?, *, $ and \ impossible to use to my knowledge as it would be interpreted
if [[ $symb == 1 ]]; then
pickcar '!.@#&%/^-_'
carset+='!.@#&%/^-_'
(( rlength++ ))
fi
if [[ $numb == 1 ]]; then
pickcar '0123456789'
carset+='0123456789'
(( rlength++ ))
fi
if [[ $maj == 1 ]]; then
pickcar 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
carset+='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
(( rlength++ ))
fi
if [[ $min == 1 ]]; then
pickcar 'abcdefghijklmnopqrstuvwxyz'
carset+='abcdefghijklmnopqrstuvwxyz'
(( rlength++ ))
fi
if [[ -n $extcar ]]; then
pickcar "$extcar"
carset+=$extcar
(( rlength++ ))
fi
# Check if we have enough car to have something viable
if [[ ${#carset} -lt $length ]]; then
echo 'Error: not enought caracters are authorised for the password length.'
echo 'Please allow more caracter (preferably) or reduce password lentgh.'
if [[ $# -gt 1 ]]; then
disp E "Too many positional arguments. Use only [nb_passwd]."
return 1
fi
for i in $( seq 1 $(( $length - $rlength )) ); do
pickcar "$carset"
if [[ $# -eq 1 ]]; then
nbpwd="$1"
if ! [[ $nbpwd =~ ^[1-9][0-9]*$ ]]; then
disp E "The number of passwords to generate must be a positive integer."
return 1
fi
fi
local carset=""
local unique_carset=""
local ch=""
local i=0
local n=0
local idx=0
local attempts=0
local count=0
local max_attempts=0
local set=""
local char=""
local -a required_sets=()
declare -A seen_chars=()
(( symb )) && {
required_sets+=('!.@#&%/^-_')
carset+='!.@#&%/^-_'
}
(( numb )) && {
required_sets+=('0123456789')
carset+='0123456789'
}
(( maj )) && {
required_sets+=('ABCDEFGHIJKLMNOPQRSTUVWXYZ')
carset+='ABCDEFGHIJKLMNOPQRSTUVWXYZ'
}
(( min )) && {
required_sets+=('abcdefghijklmnopqrstuvwxyz')
carset+='abcdefghijklmnopqrstuvwxyz'
}
if [[ -n $extcar ]]; then
required_sets+=("$extcar")
carset+="$extcar"
fi
if [[ -z $carset ]]; then
disp E "No characters are available. Re-enable at least one character class."
return 1
fi
for (( i=0; i<${#carset}; i++ )); do
ch=${carset:i:1}
if [[ -z ${seen_chars["$ch"]+x} ]]; then
seen_chars["$ch"]=1
unique_carset+="$ch"
fi
done
} | sort -R | awk '{printf "%s", $1}'
unset picked carset rlength
echo
unset seen_chars
carset="$unique_carset"
if (( ${#required_sets[@]} > length )); then
disp E "The selected character classes require a longer password."
return 1
fi
if (( length > ${#carset} * occurs )); then
disp E "The occurrence limit is too strict for the selected length."
disp E "Please allow more characters or increase --occurences."
return 1
fi
disp I "Generating $nbpwd password(s), please wait..."
for (( n=1; n<=nbpwd; n++ )); do
local -a password_chars=()
local -A char_count=()
max_attempts=$(( ${#carset} * (occurs + 1) + 32 ))
for set in "${required_sets[@]}"; do
attempts=0
while :; do
if (( attempts >= max_attempts )); then
disp E "Unable to satisfy the occurrence limit with the current settings."
return 1
fi
idx=$(( RANDOM % ${#set} ))
char=${set:idx:1}
count=${char_count["$char"]:-0}
if (( count < occurs )); then
char_count["$char"]=$(( count + 1 ))
password_chars+=("$char")
break
fi
((attempts++))
done
done
while (( ${#password_chars[@]} < length )); do
attempts=0
while :; do
if (( attempts >= max_attempts )); then
disp E "Unable to satisfy the occurrence limit with the current settings."
return 1
fi
idx=$(( RANDOM % ${#carset} ))
char=${carset:idx:1}
count=${char_count["$char"]:-0}
if (( count < occurs )); then
char_count["$char"]=$(( count + 1 ))
password_chars+=("$char")
break
fi
((attempts++))
done
done
for (( i=${#password_chars[@]} - 1; i>0; i-- )); do
idx=$(( RANDOM % (i + 1) ))
char=${password_chars[i]}
password_chars[i]=${password_chars[idx]}
password_chars[idx]=$char
done
printf '%s' "${password_chars[@]}"
printf '\n'
done
}
export -f genpwd
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# pwdscore : score a password quality from 1 to 100
# Usage: pwdscore [options] <password>
pwdscore()
{
local verbose=${PWDSCORE_DEFAULT_VERBOSE:-0}
local read_stdin=0
local password=""
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help)
printf "pwdscore: Score a password from 1 to 100.\n\n"
printf "Usage: pwdscore [options] <password>\n"
printf " pwdscore [options] --stdin\n"
printf " pwdscore [options] # prompt on terminal\n\n"
printf "Options:\n"
printf "\t-h, --help\t\tDisplay this help screen\n"
printf "\t-v, --verbose\t\tShow details about the computed score\n"
printf "\t-i, --stdin\t\tRead the password from standard input\n\n"
printf "Note:\n"
printf " Passwords containing '!' should be quoted, or passed via --stdin.\n"
return 0
;;
-v|--verbose)
verbose=1
shift
;;
-i|--stdin)
read_stdin=1
shift
;;
--)
shift
break
;;
-*)
disp E "Invalid option '$1'. Use \"pwdscore --help\" to display usage."
return 1
;;
*)
break
;;
esac
done
if (( read_stdin )); then
[[ $# -eq 0 ]] || {
disp E "Do not pass a positional password when using --stdin."
return 1
}
IFS= read -r password || true
elif [[ $# -eq 0 ]]; then
if [[ -t 0 ]]; then
read -r -s -p 'Password: ' password < /dev/tty || true
printf '\n' > /dev/tty
else
IFS= read -r password || true
fi
else
[[ $# -eq 1 ]] || {
disp E "Please provide exactly one password to score."
return 1
}
password="$1"
fi
local lower=${password,,}
local length=${#password}
local score=0
local rating="very weak"
local unique_count=0
local i=0 idx=0
local c1=0 c2=0 c3=0
local ch=""
local has_lower=0 has_upper=0 has_digit=0 has_symbol=0
local pool_size=0
local entropy_bits="0.0"
local entropy_score=0
local -A seen=()
if [[ -z $password ]]; then
printf '1\n'
return 0
fi
if (( length >= 20 )); then
score=40
elif (( length >= 16 )); then
score=34
elif (( length >= 12 )); then
score=28
elif (( length >= 8 )); then
score=18
else
score=$(( length * 2 ))
fi
if [[ $password =~ [a-z] ]]; then
has_lower=1
pool_size=$(( pool_size + 26 ))
score=$(( score + 12 ))
fi
if [[ $password =~ [A-Z] ]]; then
has_upper=1
pool_size=$(( pool_size + 26 ))
score=$(( score + 12 ))
fi
if [[ $password =~ [0-9] ]]; then
has_digit=1
pool_size=$(( pool_size + 10 ))
score=$(( score + 12 ))
fi
if [[ $password =~ [^[:alnum:]] ]]; then
has_symbol=1
pool_size=$(( pool_size + 33 ))
score=$(( score + 14 ))
fi
for (( i=0; i<length; i++ )); do
ch=${password:i:1}
if [[ -z ${seen["$ch"]+x} ]]; then
seen["$ch"]=1
unique_count=$(( unique_count + 1 ))
fi
done
score=$(( score + (unique_count * 10) / length ))
if (( pool_size > 1 )); then
entropy_bits=$(awk -v len="$length" -v pool="$pool_size" \
'BEGIN { printf "%.1f", len * (log(pool) / log(2)) }')
entropy_score=$(awk -v bits="$entropy_bits" 'BEGIN {
if (bits < 28) print -35;
else if (bits < 36) print -25;
else if (bits < 60) print -10;
else if (bits < 80) print 0;
else if (bits < 100) print 5;
else print 10;
}')
score=$(( score + entropy_score ))
fi
if [[ $lower =~ (password|admin|root|qwerty|azerty|welcome|letmein|secret|changeme) ]]; then
score=$(( score - 25 ))
fi
if [[ $lower =~ (1234|abcd|qwer|0000|1111|aaaa) ]]; then
score=$(( score - 15 ))
fi
if [[ $password =~ (.)\1\1 ]]; then
score=$(( score - 10 ))
fi
if (( length < 8 )); then
score=$(( score - 10 ))
fi
if (( unique_count * 2 < length )); then
score=$(( score - 10 ))
fi
for (( idx=0; idx<length-2; idx++ )); do
printf -v c1 '%d' "'${lower:idx:1}"
printf -v c2 '%d' "'${lower:idx+1:1}"
printf -v c3 '%d' "'${lower:idx+2:1}"
if (( (c2 == c1 + 1 && c3 == c2 + 1) || \
(c2 == c1 - 1 && c3 == c2 - 1) )); then
score=$(( score - 10 ))
break
fi
done
if (( score < 1 )); then
score=1
elif (( score > 100 )); then
score=100
fi
if (( score >= 90 )); then
rating='excellent'
elif (( score >= 75 )); then
rating='strong'
elif (( score >= 60 )); then
rating='good'
elif (( score >= 40 )); then
rating='fair'
elif (( score >= 20 )); then
rating='weak'
fi
if (( verbose )); then
printf 'Score: %d/100\n' "$score"
printf 'Rating: %s\n' "$rating"
printf 'Length: %d\n' "$length"
printf 'Lowercase: %s\n' "$([[ $has_lower -eq 1 ]] && echo yes || echo no)"
printf 'Uppercase: %s\n' "$([[ $has_upper -eq 1 ]] && echo yes || echo no)"
printf 'Digits: %s\n' "$([[ $has_digit -eq 1 ]] && echo yes || echo no)"
printf 'Symbols: %s\n' "$([[ $has_symbol -eq 1 ]] && echo yes || echo no)"
printf 'Unique chars: %d\n' "$unique_count"
printf 'Entropy: ~%s bits\n' "$entropy_bits"
printf 'Entropy modifier: %+d\n' "$entropy_score"
else
printf '%d\n' "$score"
fi
}
export -f pwdscore
# ------------------------------------------------------------------------------
load_conf "pwd"
# EOF

View File

@@ -1,91 +1,247 @@
#!/usr/bin/env bash
# ------------------------------------------------------------------------------
# Let the rain fall
# Copyright (c) 2013-2026 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
# Protected by the BSD3 license. Please read bellow for details.
#
# * Redistribution and use in source and binary forms,
# * with or without modification, are permitted provided
# * that the following conditions are met:
# *
# * Redistributions of source code must retain the above
# * copyright notice, this list of conditions and the
# * following disclaimer.
# *
# * Redistributions in binary form must reproduce the above
# * copyright notice, this list of conditions and the following
# * disclaimer in the documentation and/or other materials
# * provided with the distribution.
# *
# * Neither the name of the copyright holder nor the names
# * of any other contributors may be used to endorse or
# * promote products derived from this software without
# * specific prior written permission.
# *
# * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
# * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
# * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
# * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# * OF SUCH DAMAGE.
# ------------------------------------------------------------------------------
rain()
# ------------------------------------------------------------------------------
# Generic rain-like engine and presets
_rain_build_colors()
{
local exit_st=0
local rain_cars=("|" "│" "┃" "┆" "┇" "┊" "┋" "╽" "╿")
local rain_colors=("\e[37m" "\e[37;1m")
# More from 256 color mode
for i in {244..255}; do
rain_colors=( "${rain_colors[@]}" "\e[38;5;${i}m" )
done
local rain_tab=${#rain_cars[@]}
local base_color="$1"
RAIN_ENGINE_COLORS=()
case $base_color in
green)
for i in {22..28} {34..40} {46..48}; do RAIN_ENGINE_COLORS+=("\e[38;5;${i}m"); done ;;
blue)
for i in {17..21} {27..33} {39..45}; do RAIN_ENGINE_COLORS+=("\e[38;5;${i}m"); done ;;
red)
for i in {52..52} {88..88} {124..124} {160..160} {196..201}; do RAIN_ENGINE_COLORS+=("\e[38;5;${i}m"); done ;;
yellow)
for i in {58..58} {100..100} {142..142} {184..184} {226..229}; do RAIN_ENGINE_COLORS+=("\e[38;5;${i}m"); done ;;
cyan)
for i in {30..31} {37..38} {44..45} {50..51}; do RAIN_ENGINE_COLORS+=("\e[38;5;${i}m"); done ;;
*)
RAIN_ENGINE_COLORS=("\e[37m" "\e[37;1m")
for i in {244..255}; do RAIN_ENGINE_COLORS+=("\e[38;5;${i}m"); done ;;
esac
}
_rain_build_chars()
{
local mode="$1"
local charset="$2"
RAIN_ENGINE_CHARS=()
case "$mode" in
matrix)
case "$charset" in
""|binary)
RAIN_ENGINE_CHARS=("0" "1")
;;
kana|kanji)
# Half-width katakana set, generally rendered as single-cell glyphs.
RAIN_ENGINE_CHARS=("ア" "イ" "ウ" "エ" "オ" "カ" "キ" "ク" "ケ" "コ" "サ" "シ" "ス" "セ" "ソ" "タ" "チ" "ツ" "テ" "ト" "ナ" "ニ" "ヌ" "ネ" "ノ" "ハ" "ヒ" "フ" "ヘ" "ホ" "マ" "ミ" "ム" "メ" "モ" "ヤ" "ユ" "ヨ" "ラ" "リ" "ル" "レ" "ロ" "ワ" "ン")
;;
ascii)
RAIN_ENGINE_CHARS=("0" "1" "2" "3" "4" "5" "6" "7" "8" "9" "A" "B" "C" "D" "E" "F")
;;
*)
disp E "Unknown charset: ${charset} (supported: binary, kana, ascii)."
return 1
;;
esac
;;
*)
RAIN_ENGINE_CHARS=("|" "│" "┃" "┆" "┇" "┊" "┋" "╽" "╿")
;;
esac
return 0
}
_rain_normalize_speed()
{
local raw_speed="$1"
# Accept integer/floating values. UI scale is centiseconds by default:
# 5 -> 0.05s, 2.5 -> 0.025s. Values < 1 are treated as direct seconds
# for backward compatibility (e.g. 0.03).
if [[ ! "$raw_speed" =~ ^[0-9]+([.][0-9]+)?$ ]]; then
return 1
fi
if awk -v s="$raw_speed" 'BEGIN { exit !(s < 1) }'; then
printf "%s" "$raw_speed"
else
awk -v s="$raw_speed" 'BEGIN { printf "%.3f", s / 100 }'
fi
}
_rain_engine()
{
local step_duration="$1"
local base_color="$2"
local mode="$3"
local charset="$4"
command -v tput >/dev/null 2>&1 || {
disp E "The program 'tput' is required but not installed."
return 1
}
_rain_build_colors "$base_color"
_rain_build_chars "$mode" "$charset" || return 1
local rain_colors=("${RAIN_ENGINE_COLORS[@]}")
local rain_chars=("${RAIN_ENGINE_CHARS[@]}")
local rain_color_tab=${#rain_colors[@]}
local rain_tab=${#rain_chars[@]}
local matrix_head_color=$'\e[1;97m'
local exit_st=0
local num_rain_metadata=5
local term_height=$(tput lines)
local term_width=$(tput cols)
local step_duration=0.050
local term_height=0 term_width=0
local X=0 Y=0 drop_length=0 rain_drop=0
local max_rain_width=0 new_rain_odd=0 falling_odd=0
local max_rain_width=0 max_rain_height=0
local new_rain_odd=0 falling_odd=0
local term_area=0
local frame_sleep="$step_duration"
sigwinch() {
sigwinch()
{
term_width=$(tput cols)
term_height=$(tput lines)
#step_duration=0.025
(( max_rain_width = term_width * term_height / 4 ))
((term_area = term_width * term_height))
case "$mode" in
matrix)
((max_rain_width = term_area / 3))
((max_rain_height = term_height < 8 ? 1 : term_height / 6))
((new_rain_odd = term_height > 50 ? 100 : term_height * 2))
((new_rain_odd = new_rain_odd * 85 / 100))
((falling_odd = 100))
# Adapt cadence and density to terminal size for smoother rendering.
if ((term_area < 1200)); then
((max_rain_width = term_area / 4))
frame_sleep=$(awk -v s="$step_duration" 'BEGIN { printf "%.3f", s * 1.15 }')
elif ((term_area > 5000)); then
((max_rain_width = term_area / 2))
frame_sleep=$(awk -v s="$step_duration" 'BEGIN { printf "%.3f", s * 0.85 }')
else
frame_sleep="$step_duration"
fi
;;
*)
((max_rain_width = term_area / 4))
((max_rain_height = term_height < 10 ? 1 : term_height / 10))
# In percentage
((new_rain_odd = term_height > 50 ? 100 : term_height * 2))
((new_rain_odd = new_rain_odd * 75 / 100))
((falling_odd = term_height > 25 ? 100 : term_height * 4))
((falling_odd = falling_odd * 90 / 100))
frame_sleep="$step_duration"
;;
esac
}
do_exit() {
do_exit()
{
exit_st=1
}
do_render() {
# Clean screen first
local idx=0
do_render()
{
local idx=0 y=0 drop_color="" current_char="" render_color=""
for ((idx = 0; idx < num_rains * num_rain_metadata; idx += num_rain_metadata)); do
X=${rains[idx]}
Y=${rains[idx + 1]}
drop_length=${rains[idx + 4]}
for ((y = Y; y < Y + drop_length; y++)); do
((y < 1 || y > term_height)) && continue
echo -ne "\e[${y};${X}H "
printf "\e[%d;%dH " "$y" "$X"
done
done
for ((idx = 0; idx < num_rains * num_rain_metadata; idx += num_rain_metadata)); do
if ((100 * RANDOM / 32768 < falling_odd)); then
# Falling
if ((++rains[idx + 1] > term_height)); then
# Out of screen, bye sweet <3
rains=("${rains[@]:0:idx}"
"${rains[@]:idx+num_rain_metadata:num_rains*num_rain_metadata}")
rains=("${rains[@]:0:idx}" "${rains[@]:idx+num_rain_metadata:num_rains*num_rain_metadata}")
((num_rains--))
continue
fi
fi
X=${rains[idx]}
Y=${rains[idx + 1]}
rain_drop=${rains[idx + 2]}
drop_color=${rains[idx + 3]}
drop_length=${rains[idx + 4]}
for ((y = Y; y < Y + drop_length; y++)); do
((y < 1 || y > term_height)) && continue
echo -ne "\e[${y};${X}H${drop_color}${rain_drop}"
if [[ "$mode" == "matrix" ]]; then
current_char="${rain_chars[rain_tab * RANDOM / 32768]}"
if ((y == Y + drop_length - 1)); then
render_color="$matrix_head_color"
else
render_color="$drop_color"
fi
else
current_char="$rain_drop"
render_color="$drop_color"
fi
printf "\e[%d;%dH%b%s" "$y" "$X" "$render_color" "$current_char"
done
done
}
trap do_exit TERM INT
trap sigwinch WINCH
# No echo stdin and hide the cursor
stty -echo
echo -ne "\e[?25l"
printf "\e[?25l"
printf "\e[2J"
echo -ne "\e[2J"
local rains=()
local num_rains=0
local ch=""
sigwinch
while ((exit_st <= 0)); do
if (( $exit_st <=0 )); then
read -n 1 -t $step_duration ch
read -r -n 1 -t "$frame_sleep" ch
case "$ch" in
q|Q)
do_exit
@@ -93,8 +249,7 @@ rain()
esac
if ((num_rains < max_rain_width)) && ((100 * RANDOM / 32768 < new_rain_odd)); then
# Need new |, 1-based
rain_drop="${rain_cars[rain_tab * RANDOM / 32768]}"
rain_drop="${rain_chars[rain_tab * RANDOM / 32768]}"
drop_color="${rain_colors[rain_color_tab * RANDOM / 32768]}"
drop_length=$((max_rain_height * RANDOM / 32768 + 1))
X=$((term_width * RANDOM / 32768 + 1))
@@ -103,17 +258,183 @@ rain()
((num_rains++))
fi
# Let rain fall!
do_render
fi
done
echo -ne "\e[${term_height};1H\e[0K"
# Show cursor and echo stdin
echo -ne "\e[?25h"
printf "\e[%d;1H\e[0K" "$term_height"
printf "\e[?25h"
stty echo
unset exit_st
trap - TERM INT
trap - WINCH
}
# ------------------------------------------------------------------------------
# Let the rain fall (current style)
# Usage: rain [OPTIONS]
rain()
{
_rain_show_usage()
{
printf "Usage: rain [OPTIONS]\n"
printf "Options:\n"
printf "\t-s, --speed NUM Set speed value (default: 5 => 0.050s).\n"
printf "\t Values >=1 use a /100 scale (5 => 0.05s).\n"
printf "\t Values <1 are interpreted as raw seconds.\n"
printf "\t-c, --color COLOR Set the color theme (default: white).\n"
printf "\t-h, --help Display this help message and exit.\n\n"
printf "Available Colors:\n"
printf "\t\e[32mgreen\e[0m\t: Matrix-like green shades\n"
printf "\t\e[34mblue\e[0m\t: Deep ocean blue gradients\n"
printf "\t\e[31mred\e[0m\t: Crimson/Blood rain\n"
printf "\t\e[33myellow\e[0m\t: Amber and gold tones\n"
printf "\t\e[36mcyan\e[0m\t: Electric cyan/turquoise\n"
printf "\twhite\t: Greyscale and white (original style)\n\n"
printf "Example: rain --color green --speed 3\n"
}
local _raw_speed="${RAIN_DEFAULT_SPEED:-5}"
local step_duration
step_duration=$(_rain_normalize_speed "$_raw_speed") || step_duration=0.050
local base_color="${RAIN_DEFAULT_COLOR:-white}"
while [[ "$#" -gt 0 ]]; do
case $1 in
-s|--speed)
if [[ -n "$2" && ! "$2" =~ ^- ]]; then
step_duration=$(_rain_normalize_speed "$2") || {
disp E "--speed requires a numeric value."
_rain_show_usage
return 1
}
shift
else
disp E "--speed requires a numeric value."
_rain_show_usage
return 1
fi
;;
-c|--color)
if [[ -n "$2" && ! "$2" =~ ^- ]]; then
base_color="$2"
shift
else
disp E "--color requires a color name."
_rain_show_usage
return 1
fi
;;
-h|--help)
_rain_show_usage
return 0
;;
--)
shift
break
;;
*)
disp E "Unknown option: $1"
_rain_show_usage
return 1
;;
esac
shift
done
_rain_engine "$step_duration" "$base_color" "rain" ""
}
export -f rain
# ------------------------------------------------------------------------------
# Matrix style digital rain
# Usage: matrix [OPTIONS]
matrix()
{
_matrix_show_usage()
{
printf "Usage: matrix [OPTIONS]\n"
printf "Options:\n"
printf "\t-s, --speed NUM Set speed value (default: 3.5 => 0.035s).\n"
printf "\t Values >=1 use a /100 scale (3.5 => 0.035s).\n"
printf "\t Values <1 are interpreted as raw seconds.\n"
printf "\t-c, --color COLOR Set color theme (default: green).\n"
printf "\t-C, --charset SET Character set: binary, kana, ascii (default: binary).\n"
printf "\t-h, --help Display this help message and exit.\n\n"
printf "Example: matrix -C kana -c green --speed 2\n"
}
local _raw_speed="${MATRIX_DEFAULT_SPEED:-3.5}"
local step_duration
step_duration=$(_rain_normalize_speed "$_raw_speed") || step_duration=0.035
local base_color="${MATRIX_DEFAULT_COLOR:-green}"
local charset="${MATRIX_DEFAULT_CHARSET:-binary}"
while [[ "$#" -gt 0 ]]; do
case $1 in
-s|--speed)
if [[ -n "$2" && ! "$2" =~ ^- ]]; then
step_duration=$(_rain_normalize_speed "$2") || {
disp E "--speed requires a numeric value."
_matrix_show_usage
return 1
}
shift
else
disp E "--speed requires a numeric value."
_matrix_show_usage
return 1
fi
;;
-c|--color)
if [[ -n "$2" && ! "$2" =~ ^- ]]; then
case "${2,,}" in
binary|kana|kanji|ascii)
disp W "'${2}' looks like a charset value. Use -C/--charset for clarity."
charset="${2,,}"
;;
*)
base_color="$2"
;;
esac
shift
else
disp E "--color requires a color name."
_matrix_show_usage
return 1
fi
;;
-C|--charset)
if [[ -n "$2" && ! "$2" =~ ^- ]]; then
charset="${2,,}"
shift
else
disp E "--charset requires a value."
_matrix_show_usage
return 1
fi
;;
-h|--help)
_matrix_show_usage
return 0
;;
--)
shift
break
;;
*)
disp E "Unknown option: $1"
_matrix_show_usage
return 1
;;
esac
shift
done
_rain_engine "$step_duration" "$base_color" "matrix" "$charset"
}
export -f matrix
# ------------------------------------------------------------------------------
load_conf "rain"
# EOF

View File

@@ -1,70 +1,207 @@
#!/usr/bin/env bash
# ------------------------------------------------------------------------------
# Remove host from know_host (name and IP) for the active user
# Copyright (c) 2013-2026 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
# Protected by the BSD3 license. Please read bellow for details.
#
# * Redistribution and use in source and binary forms,
# * with or without modification, are permitted provided
# * that the following conditions are met:
# *
# * Redistributions of source code must retain the above
# * copyright notice, this list of conditions and the
# * following disclaimer.
# *
# * Redistributions in binary form must reproduce the above
# * copyright notice, this list of conditions and the following
# * disclaimer in the documentation and/or other materials
# * provided with the distribution.
# *
# * Neither the name of the copyright holder nor the names
# * of any other contributors may be used to endorse or
# * promote products derived from this software without
# * specific prior written permission.
# *
# * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
# * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
# * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
# * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# * OF SUCH DAMAGE.
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Remove host entries (name and IP) from ~/.ssh/known_hosts for the active user
# Usage: rmhost <hostname|ip> [hostname2|ip2 [...]]
rmhost()
{
if [[ "$#" -lt 1 ]]; then
echo "Error: incorrect number of parameters."
echo "Usage: rmhost <hostname|ip> [hostname2|ip2 [...]]"
return 1
fi
local PARSED
local all_users=0
local -a known_hosts_files=()
while [[ $1 ]]; do
local hst=$1 && shift
isipv4 $hst > /dev/null
local v4=$?
isipv6 $hst > /dev/null
local v6=$?
PARSED=$(getopt -o ha --long help,all-users -n 'rmhost' -- "$@")
# shellcheck disable=SC2181 # getopt return code is checked immediately after
if [[ $? -ne 0 ]]; then return 1; fi
eval set -- "$PARSED"
if [[ $v4 -eq 0 || $v6 -eq 0 ]]; then
local ip=$hst
unset hst
fi
unset v4 v6
if [[ ! $ip && $hst ]]; then
ip=$(host $hst | grep "has address" | awk '{print $NF}')
[[ ! $? ]] &&
echo "*** rmhost(): Error extracting IP from hostname." &&
return 1
fi
if [[ $hst ]]; then
echo "Removing host $hst from ssh known_host..."
ssh-keygen -R $hst > /dev/null
fi
if [[ $ip ]]; then
echo "Removing IP $ip from ssh known_host..."
ssh-keygen -R $ip > /dev/null
fi
unset hst ip
done
}
export -f rmhost
# ------------------------------------------------------------------------------
# Login root via SSH on the given machine
# ------------------------------------------------------------------------------
ssr ()
{
for opt in $@ ; do
case $opt in
"-h"|"--help")
echo "ssr: do a root user ssh login."
echo
echo "Usage: ssr <server [ssh options]>"
while true; do
case "$1" in
-h|--help)
printf "rmhost: Remove host/IP from known_hosts files.\n\n"
printf "Usage: rmhost [--all-users] <hostname|ip> [hostname2|ip2 ...]\n\n"
printf "Options:\n"
printf " -a, --all-users Remove entries from all local users when run as root\n"
printf " -h, --help Display this help screen\n"
return 0
;;
-a|--all-users)
all_users=1
shift
;;
--)
shift
break
;;
*)
disp E "Invalid options, use \"rmhost --help\" to display usage."
return 1
;;
esac
done
[[ ! $1 ]] &&
echo "Please specify the server you want to log in." &&
[[ $# -eq 0 ]] && {
disp E "Missing argument. Use 'rmhost --help' for usage."
return 1
}
local srv=$1 && shift
command -v ssh-keygen >/dev/null 2>&1 || {
disp E "ssh-keygen is not installed."
return 127
}
ssh -Y root@$srv $@
if (( all_users )); then
[[ ${EUID:-$(id -u)} -eq 0 ]] || {
disp E "Option --all-users is only available when run as root."
return 1
}
while IFS=: read -r _ _ _ _ _ home _; do
[[ -n $home && -f $home/.ssh/known_hosts ]] || continue
known_hosts_files+=("$home/.ssh/known_hosts")
done < /etc/passwd
[[ -f /etc/ssh/ssh_known_hosts ]] && \
known_hosts_files+=("/etc/ssh/ssh_known_hosts")
[[ ${#known_hosts_files[@]} -gt 0 ]] || {
disp W "No known_hosts files found for local users."
return 0
}
else
known_hosts_files=("${HOME}/.ssh/known_hosts")
fi
for target in "$@"; do
local hst="$target"
local ip=""
local v4=1
local v6=1
isipv4 "$hst" >/dev/null 2>&1; v4=$?
isipv6 "$hst" >/dev/null 2>&1; v6=$?
if [[ $v4 -eq 0 || $v6 -eq 0 ]]; then
ip="$hst"
hst=""
fi
if [[ -z ${ip:-} && -n ${hst:-} ]]; then
if command -v host >/dev/null 2>&1; then
ip=$(host "$hst" 2>/dev/null |
awk '/has address|has IPv6 address/ {print $NF; exit}')
elif command -v getent >/dev/null 2>&1; then
ip=$(getent ahosts "$hst" 2>/dev/null | awk 'NR == 1 {print $1; exit}')
else
disp W "No resolver tool found; removing hostname only for '$hst'."
fi
[[ -z ${ip:-} ]] && \
disp W "Could not resolve IP for '$hst'; removing hostname only."
fi
local known_hosts_file=""
for known_hosts_file in "${known_hosts_files[@]}"; do
if [[ -n ${hst:-} ]]; then
disp I "Removing host $hst from $known_hosts_file..."
if ! ssh-keygen -R "$hst" -f "$known_hosts_file" >/dev/null 2>&1; then
disp W "No known_hosts entry found for '$hst' in '$known_hosts_file'."
fi
fi
if [[ -n ${ip:-} ]]; then
disp I "Removing IP $ip from $known_hosts_file..."
if ! ssh-keygen -R "$ip" -f "$known_hosts_file" >/dev/null 2>&1; then
disp W "No known_hosts entry found for '$ip' in '$known_hosts_file'."
fi
fi
done
done
}
export -f rmhost
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Login root via SSH on the given machine
# Usage: ssr <server [ssh options]>
ssr()
{
case "${1:-}" in
-h|--help)
printf "ssr: SSH into a server as root.\n\n"
printf "Usage: ssr <server> [ssh_options...]\n\n"
printf "Notes:\n"
printf " The first argument is the target server.\n"
printf " All remaining arguments are passed directly to ssh.\n\n"
printf "Examples:\n"
printf " ssr srv01\n"
printf " ssr srv01 -p 2222\n"
printf " ssr srv01 -i ~/.ssh/id_ed25519 -J bastion\n"
return 0
;;
esac
command -v ssh >/dev/null 2>&1 || {
disp E "ssh is not installed."
return 127
}
[[ $# -eq 0 || -z ${1:-} ]] && {
disp E "Please specify the server you want to log in."
return 1
}
local srv=$1
shift
# Build default options array from config, falling back to -Y (X11 forwarding)
local -a ssh_default_opts=()
if [[ -n "${SSH_DEFAULT_OPT:-}" ]]; then
read -ra ssh_default_opts <<< "${SSH_DEFAULT_OPT}"
else
ssh_default_opts=(-Y)
fi
# shellcheck disable=SC2029
ssh "${ssh_default_opts[@]}" root@"$srv" "$@"
}
export -f ssr
# ------------------------------------------------------------------------------
load_conf "ssh"
# EOF

View File

@@ -0,0 +1,42 @@
# Abyss prompt theme — deep ocean navy, electric teal, golden accents
# ------------------------------------------------------------------------------
# Theme files are NOT executed as shell scripts. load_theme() parses them
# line by line. Only the following value forms are accepted:
#
# KEY="$ColorVarName" — reference to a colour variable from disp.sh
# KEY="${ColorVarName}" — same with braces
# KEY="\e[...m" — raw ANSI escape sequence (single block)
#
# Accepted keys:
# PROMPT_COLOR_* — prompt slot colours (see profile.conf [prompt])
# Standard colour vars — Black, Blue, On_IBlack, … (overrides the palette
# from disp.sh for the whole shell session)
#
# Any unknown key, unsafe value, or shell construct will be ignored with a
# warning — theme files cannot execute code.
# ------------------------------------------------------------------------------
# Inspired by the VS Code "Abyss" theme: deep navy/black backdrop, electric
# teal highlights, golden-yellow accents, cool electric blue for identifiers.
# Palette overrides shift the cooler hues to their high-intensity (electric)
# equivalents — blue becomes IBlue, green shifts toward teal (ICyan).
# ------------------------------------------------------------------------------
# Palette overrides — shift colours toward cool electric hues
Blue="\e[0;94m" # electric blue (IBlue — abyss identifier colour)
Green="\e[0;96m" # teal (ICyan — abyss string colour)
Yellow="\e[0;93m" # bright gold (IYellow — abyss constant colour)
PROMPT_COLOR_TIME_FG="$ICyan" # electric teal clock
PROMPT_COLOR_TIME_BG="\e[48;2;0;40;45m" # very dark cyan bg for time
PROMPT_COLOR_BAR_BG="\e[48;2;0;30;70m" # deep navy bar (24-bit)
PROMPT_COLOR_OK_FG="$ICyan" # teal on success
PROMPT_COLOR_OK_MARK="$IGreen" # bright teal-green checkmark
PROMPT_COLOR_ERR_BG="\e[48;2;180;20;20m" # vivid crimson background (24-bit)
PROMPT_COLOR_ERR_FG="\e[1;97m" # bold bright white — maximum contrast
PROMPT_COLOR_ERR_MARK="$IYellow" # golden X
PROMPT_COLOR_ROOT_FG="$IRed" # red for root
PROMPT_COLOR_USER_FG="$IBlue" # electric blue for user
PROMPT_COLOR_DIR_FG="$ICyan" # teal path

View File

@@ -0,0 +1,42 @@
# Adwaita prompt theme — clean GNOME defaults, blue accent, green/red status
# ------------------------------------------------------------------------------
# Theme files are NOT executed as shell scripts. load_theme() parses them
# line by line. Only the following value forms are accepted:
#
# KEY="$ColorVarName" — reference to a colour variable from disp.sh
# KEY="${ColorVarName}" — same with braces
# KEY="\e[...m" — raw ANSI escape sequence (single block)
#
# Accepted keys:
# PROMPT_COLOR_* — prompt slot colours (see profile.conf [prompt])
# Standard colour vars — Black, Blue, On_IBlack, … (overrides the palette
# from disp.sh for the whole shell session)
#
# Any unknown key, unsafe value, or shell construct will be ignored with a
# warning — theme files cannot execute code.
# ------------------------------------------------------------------------------
# Follows the GNOME HIG colour intent: a single calm blue accent (#3584e4),
# pleasant green for success (#33d17a), clear red for errors (#e01b24).
# No intense saturation, no heavy remapping — legibility over spectacle.
# Palette overrides gently shift Blue and Green to their IBlue/IGreen
# variants to better match Adwaita's brighter, flatter tones.
# ------------------------------------------------------------------------------
# Palette overrides — align to Adwaita's brighter accent tones
Blue="\e[0;94m" # IBlue → closer to Adwaita blue (#3584e4)
Green="\e[0;92m" # IGreen → closer to Adwaita green (#33d17a)
PROMPT_COLOR_TIME_FG="$Cyan" # calm cyan clock text
PROMPT_COLOR_TIME_BG="$On_IBlack" # dark grey clock background
PROMPT_COLOR_BAR_BG="$On_Blue" # blue main bar (Adwaita accent)
PROMPT_COLOR_OK_FG="$White" # clean white on success
PROMPT_COLOR_OK_MARK="$Green" # Adwaita green checkmark
PROMPT_COLOR_ERR_BG="$On_Red" # Adwaita red on failure
PROMPT_COLOR_ERR_FG="$BIWhite" # bold bright white for maximum legibility
PROMPT_COLOR_ERR_MARK="$Yellow" # yellow X (warning intent)
PROMPT_COLOR_ROOT_FG="$Red" # Adwaita red for root
PROMPT_COLOR_USER_FG="$BBlue" # darker bold blue — readable on blue bar
PROMPT_COLOR_DIR_FG="$IGreen" # Adwaita green for path

View File

@@ -0,0 +1,32 @@
# Dark prompt theme — dark grey bar, cyan user, magenta path
# ------------------------------------------------------------------------------
# Theme files are NOT executed as shell scripts. load_theme() parses them
# line by line. Only the following value forms are accepted:
#
# KEY="$ColorVarName" — reference to a colour variable from disp.sh
# KEY="${ColorVarName}" — same with braces
# KEY="\e[...m" — raw ANSI escape sequence (single block)
#
# Accepted keys:
# PROMPT_COLOR_* — prompt slot colours (see profile.conf [prompt])
# Standard colour vars — Black, Blue, On_IBlack, … (overrides the palette
# from disp.sh for the whole shell session)
#
# Any unknown key, unsafe value, or shell construct will be ignored with a
# warning — theme files cannot execute code.
# ------------------------------------------------------------------------------
PROMPT_COLOR_TIME_FG="$ICyan" # Clock text
PROMPT_COLOR_TIME_BG="$On_IBlack" # Clock background (black)
PROMPT_COLOR_BAR_BG="$On_Black" # Main bar background (dark grey)
PROMPT_COLOR_OK_FG="$IGreen" # Exit-code text on success
PROMPT_COLOR_OK_MARK="$BGreen" # Checkmark colour on success
PROMPT_COLOR_ERR_BG="$On_Red" # Exit-code background on failure
PROMPT_COLOR_ERR_FG="$BIWhite" # Exit-code text on failure
PROMPT_COLOR_ERR_MARK="$BIYellow" # X mark colour on failure
PROMPT_COLOR_ROOT_FG="$BIRed" # Username colour when root
PROMPT_COLOR_USER_FG="$ICyan" # Username@host colour for normal users
PROMPT_COLOR_DIR_FG="$IPurple" # Working directory colour

View File

@@ -0,0 +1,32 @@
# Default prompt theme — blue bar, green user, cyan path
# ------------------------------------------------------------------------------
# Theme files are NOT executed as shell scripts. load_theme() parses them
# line by line. Only the following value forms are accepted:
#
# KEY="$ColorVarName" — reference to a colour variable from disp.sh
# KEY="${ColorVarName}" — same with braces
# KEY="\e[...m" — raw ANSI escape sequence (single block)
#
# Accepted keys:
# PROMPT_COLOR_* — prompt slot colours (see profile.conf [prompt])
# Standard colour vars — Black, Blue, On_IBlack, … (overrides the palette
# from disp.sh for the whole shell session)
#
# Any unknown key, unsafe value, or shell construct will be ignored with a
# warning — theme files cannot execute code.
# ------------------------------------------------------------------------------
PROMPT_COLOR_TIME_FG="$Blue" # Clock text
PROMPT_COLOR_TIME_BG="$On_White" # Clock background (dark grey)
PROMPT_COLOR_BAR_BG="$On_Blue" # Main bar background
PROMPT_COLOR_OK_FG="$White" # Exit-code text on success
PROMPT_COLOR_OK_MARK="$Green" # Checkmark colour on success
PROMPT_COLOR_ERR_BG="$On_Red" # Exit-code background on failure
PROMPT_COLOR_ERR_FG="$White" # Exit-code text on failure
PROMPT_COLOR_ERR_MARK="$BYellow" # X mark colour on failure
PROMPT_COLOR_ROOT_FG="$Red" # Username colour when root
PROMPT_COLOR_USER_FG="$Green" # Username@host colour for normal users
PROMPT_COLOR_DIR_FG="$ICyan" # Working directory colour

View File

@@ -0,0 +1,35 @@
# Light prompt theme — white bar, blue user, purple path
# ------------------------------------------------------------------------------
# Theme files are NOT executed as shell scripts. load_theme() parses them
# line by line. Only the following value forms are accepted:
#
# KEY="$ColorVarName" — reference to a colour variable from disp.sh
# KEY="${ColorVarName}" — same with braces
# KEY="\e[...m" — raw ANSI escape sequence (single block)
#
# Accepted keys:
# PROMPT_COLOR_* — prompt slot colours (see profile.conf [prompt])
# Standard colour vars — Black, Blue, On_IBlack, … (overrides the palette
# from disp.sh for the whole shell session)
#
# Any unknown key, unsafe value, or shell construct will be ignored with a
# warning — theme files cannot execute code.
# ------------------------------------------------------------------------------
# Opposite of dark.theme: backgrounds flip to bright whites, foregrounds
# shift to their dark/regular equivalents for contrast on a light terminal.
# ------------------------------------------------------------------------------
PROMPT_COLOR_TIME_FG="$BBlack" # Clock text (bold black — forces true black on terminals that render Black as dark grey)
PROMPT_COLOR_TIME_BG="$On_IWhite" # Clock background (On_Black → On_IWhite)
PROMPT_COLOR_BAR_BG="$On_White" # Main bar background (On_IBlack → On_White)
PROMPT_COLOR_OK_FG="$Green" # Exit-code text on success (IGreen → Green)
PROMPT_COLOR_OK_MARK="$BGreen" # Checkmark colour on success (unchanged — bold green reads on both)
PROMPT_COLOR_ERR_BG="$On_Red" # Exit-code background on failure (unchanged)
PROMPT_COLOR_ERR_FG="$BIWhite" # Exit-code text on failure (unchanged — white on red works on both)
PROMPT_COLOR_ERR_MARK="$BYellow" # X mark on failure (BIYellow → BYellow, less glaring on light)
PROMPT_COLOR_ROOT_FG="$Red" # Username when root (BIRed → Red)
PROMPT_COLOR_USER_FG="$Blue" # Username@host normal user (ICyan → Blue)
PROMPT_COLOR_DIR_FG="$Purple" # Working directory (IPurple → Purple)

View File

@@ -0,0 +1,64 @@
# Monochrome prompt theme — strict greyscale, no hue at all
# ------------------------------------------------------------------------------
# Theme files are NOT executed as shell scripts. load_theme() parses them
# line by line. Only the following value forms are accepted:
#
# KEY="$ColorVarName" — reference to a colour variable from disp.sh
# KEY="${ColorVarName}" — same with braces
# KEY="\e[...m" — raw ANSI escape sequence (single block)
#
# Accepted keys:
# PROMPT_COLOR_* — prompt slot colours (see profile.conf [prompt])
# Standard colour vars — Black, Blue, On_IBlack, … (overrides the palette
# from disp.sh for the whole shell session)
#
# Any unknown key, unsafe value, or shell construct will be ignored with a
# warning — theme files cannot execute code.
# ------------------------------------------------------------------------------
# All hues are silenced — colour variables are remapped to greyscale ANSI
# codes so that ls, man, grep, etc. also lose their colour cues. Contrast
# is achieved entirely through brightness: dark grey / light grey / white.
# Error state uses an inverted (white background, black text) bar so it
# remains visually distinct without relying on red.
# ------------------------------------------------------------------------------
# Palette overrides — replace every hue with a grey equivalent
Red="\e[0;37m" # light grey
Green="\e[0;97m" # bright white (success intent kept as brightest)
Yellow="\e[0;90m" # dark grey
Blue="\e[0;90m" # dark grey
Purple="\e[0;37m" # light grey
Cyan="\e[0;37m" # light grey
BRed="\e[1;37m" # bold light grey
BGreen="\e[1;97m" # bold bright white
BYellow="\e[1;90m" # bold dark grey
BBlue="\e[1;90m" # bold dark grey
BPurple="\e[1;37m" # bold light grey
BCyan="\e[1;37m" # bold light grey
IRed="\e[0;97m" # bright white
IGreen="\e[0;97m" # bright white
IYellow="\e[0;90m" # dark grey
IBlue="\e[0;90m" # dark grey
IPurple="\e[0;37m" # light grey
ICyan="\e[0;37m" # light grey
BIRed="\e[1;97m" # bold bright white
BIGreen="\e[1;97m" # bold bright white
BIYellow="\e[1;90m" # bold dark grey
BIBlue="\e[1;90m" # bold dark grey
BIPurple="\e[1;37m" # bold light grey
BICyan="\e[1;37m" # bold light grey
PROMPT_COLOR_TIME_FG="$IBlack" # dark grey clock text (subtle)
PROMPT_COLOR_TIME_BG="$On_IBlack" # dark grey clock background
PROMPT_COLOR_BAR_BG="$On_IBlack" # dark grey main bar
PROMPT_COLOR_OK_FG="$IWhite" # bright white on success
PROMPT_COLOR_OK_MARK="$BIWhite" # bold bright white checkmark
PROMPT_COLOR_ERR_BG="$On_White" # inverted: bright white bar on error
PROMPT_COLOR_ERR_FG="$Black" # black text on white background
PROMPT_COLOR_ERR_MARK="$BBlack" # bold black X
PROMPT_COLOR_ROOT_FG="$BIWhite" # bold bright white for root warning
PROMPT_COLOR_USER_FG="$IWhite" # bright white for normal user
PROMPT_COLOR_DIR_FG="$White" # standard white for path

View File

@@ -0,0 +1,45 @@
# Monokai prompt theme — high-saturation, vivid hues on near-black
# ------------------------------------------------------------------------------
# Theme files are NOT executed as shell scripts. load_theme() parses them
# line by line. Only the following value forms are accepted:
#
# KEY="$ColorVarName" — reference to a colour variable from disp.sh
# KEY="${ColorVarName}" — same with braces
# KEY="\e[...m" — raw ANSI escape sequence (single block)
#
# Accepted keys:
# PROMPT_COLOR_* — prompt slot colours (see profile.conf [prompt])
# Standard colour vars — Black, Blue, On_IBlack, … (overrides the palette
# from disp.sh for the whole shell session)
#
# Any unknown key, unsafe value, or shell construct will be ignored with a
# warning — theme files cannot execute code.
# ------------------------------------------------------------------------------
# Monokai's signature: lime green, orange-yellow, hot pink/red, bright violet,
# electric cyan — all on a near-black (#272822) background.
# Palette overrides remap the dim ANSI regulars to their vivid high-intensity
# equivalents so that ls, grep colour output, etc. also look more "Monokai".
# ------------------------------------------------------------------------------
# Palette overrides — boost regular colours to Monokai-vivid equivalents
Red="\e[0;91m" # hot pink/red (#F92672)
Green="\e[0;92m" # lime green (#A6E22E)
Yellow="\e[0;93m" # orange-yellow (#E6DB74 / #FD971F)
Blue="\e[0;94m" # electric blue (#66D9E8 → shift blue)
Purple="\e[0;95m" # bright violet (#AE81FF)
Cyan="\e[0;96m" # electric cyan (#66D9E8)
PROMPT_COLOR_TIME_FG="$IYellow" # orange clock text
PROMPT_COLOR_TIME_BG="$On_IBlack" # near-black Monokai background
PROMPT_COLOR_BAR_BG="$On_IBlack" # flat dark bar
PROMPT_COLOR_OK_FG="$IGreen" # lime green on success
PROMPT_COLOR_OK_MARK="$IGreen" # lime green checkmark
PROMPT_COLOR_ERR_BG="$On_IBlack" # keep dark — use colour for contrast
PROMPT_COLOR_ERR_FG="$IRed" # hot pink on failure
PROMPT_COLOR_ERR_MARK="$IRed" # hot pink X
PROMPT_COLOR_ROOT_FG="$IRed" # hot pink for root
PROMPT_COLOR_USER_FG="$IYellow" # orange-yellow for user
PROMPT_COLOR_DIR_FG="$ICyan" # electric cyan for path

View File

@@ -0,0 +1,42 @@
# Plasma prompt theme — vivid KDE Plasma purples and electric cyan
# ------------------------------------------------------------------------------
# Theme files are NOT executed as shell scripts. load_theme() parses them
# line by line. Only the following value forms are accepted:
#
# KEY="$ColorVarName" — reference to a colour variable from disp.sh
# KEY="${ColorVarName}" — same with braces
# KEY="\e[...m" — raw ANSI escape sequence (single block)
#
# Accepted keys:
# PROMPT_COLOR_* — prompt slot colours (see profile.conf [prompt])
# Standard colour vars — Black, Blue, On_IBlack, … (overrides the palette
# from disp.sh for the whole shell session)
#
# Any unknown key, unsafe value, or shell construct will be ignored with a
# warning — theme files cannot execute code.
# ------------------------------------------------------------------------------
# Captures the charged, vivid energy of KDE Plasma: dominant bright magenta/
# purple, electric cyan highlights, all on a dark background. Palette overrides
# boost Blue, Purple, and Cyan to their high-intensity equivalents so that
# shell colour output reflects the same vivid palette.
# ------------------------------------------------------------------------------
# Palette overrides — vivid charged hues
Blue="\e[0;94m" # electric blue (IBlue)
Purple="\e[0;95m" # vivid magenta (IPurple — Plasma's signature colour)
Cyan="\e[0;96m" # electric cyan (ICyan)
PROMPT_COLOR_TIME_FG="$IPurple" # vivid purple clock text
PROMPT_COLOR_TIME_BG="\e[48;2;30;30;35m" # deep charcoal (darker than On_IBlack)
PROMPT_COLOR_BAR_BG="\e[48;2;75;0;130m" # deep indigo-purple (24-bit) — darker than On_Purple
PROMPT_COLOR_OK_FG="$ICyan" # electric cyan on success
PROMPT_COLOR_OK_MARK="$IGreen" # green checkmark
PROMPT_COLOR_ERR_BG="$On_Red" # red bar on failure
PROMPT_COLOR_ERR_FG="$IWhite" # bright white text
PROMPT_COLOR_ERR_MARK="$IYellow" # yellow X
PROMPT_COLOR_ROOT_FG="$IRed" # red for root
PROMPT_COLOR_USER_FG="$BIPurple" # bold vivid purple for user
PROMPT_COLOR_DIR_FG="$ICyan" # electric cyan path

View File

@@ -0,0 +1,127 @@
# Solarized Light prompt theme — exact 24-bit / true-colour palette
# ------------------------------------------------------------------------------
# Theme files are NOT executed as shell scripts. load_theme() parses them
# line by line. Only the following value forms are accepted:
#
# KEY="$ColorVarName" — reference to a colour variable from disp.sh
# KEY="${ColorVarName}" — same with braces
# KEY="\e[...m" — raw ANSI escape sequence (single block)
#
# Accepted keys:
# PROMPT_COLOR_* — prompt slot colours (see profile.conf [prompt])
# Standard colour vars — Black, Blue, On_IBlack, … (overrides the palette
# from disp.sh for the whole shell session)
#
# Any unknown key, unsafe value, or shell construct will be ignored with a
# warning — theme files cannot execute code.
# ------------------------------------------------------------------------------
# Requires a terminal with true-colour / 24-bit support.
# Check with: printf '\e[38;2;220;50;47mred\e[0m\n'
# If you see solid red text, your terminal supports this theme.
# (konsole, iTerm2, kitty, alacritty, Windows Terminal all do)
#
# Solarized Light base tones (inverted vs Dark):
# Base3 #fdf6e3 →253 246 227 (main background — lightest)
# Base2 #eee8d5 →238 232 213 (background highlights)
# Base1 #93a1a1 →147 161 161 (comments / secondary content)
# Base0 #839496 →131 148 150 (body text — secondary)
# Base00 #657b83 →101 123 131 (body text — main on light bg)
# Base01 #586e75 → 88 110 117 (emphasis)
# Base02 #073642 → 7 54 66 (darkest — used for strong contrast)
#
# Accent colours are identical to Solarized Dark:
# Yellow #b58900 →181 137 0
# Orange #cb4b16 →203 75 22
# Red #dc322f →220 50 47
# Magenta #d33682 →211 54 130
# Violet #6c71c4 →108 113 196
# Blue #268bd2 → 38 139 210
# Cyan #2aa198 → 42 161 152
# Green #859900 →133 153 0
# ------------------------------------------------------------------------------
# ---- Foreground palette overrides -------------------------------------------
# The accent fg colours are identical to Dark — only the base/neutral roles flip.
# Regular
Black="\e[38;2;253;246;227m" # Base3 — lightest (fg on light bg = invisible; used as bg fg pair complement)
Red="\e[38;2;220;50;47m" # Red
Green="\e[38;2;133;153;0m" # Green
Yellow="\e[38;2;181;137;0m" # Yellow — primary accent
Blue="\e[38;2;38;139;210m" # Blue
Purple="\e[38;2;211;54;130m" # Magenta
Cyan="\e[38;2;42;161;152m" # Cyan
White="\e[38;2;101;123;131m" # Base00 — main body text on light bg
# Bold
BBlack="\e[1;38;2;238;232;213m" # Base2 bold
BRed="\e[1;38;2;220;50;47m" # Red bold
BGreen="\e[1;38;2;133;153;0m" # Green bold
BYellow="\e[1;38;2;181;137;0m" # Yellow bold
BBlue="\e[1;38;2;38;139;210m" # Blue bold
BPurple="\e[1;38;2;211;54;130m" # Magenta bold
BCyan="\e[1;38;2;42;161;152m" # Cyan bold
BWhite="\e[1;38;2;88;110;117m" # Base01 bold — emphasis text
# High intensity (emphasis / I* family)
IBlack="\e[38;2;147;161;161m" # Base1 — secondary/comments
IRed="\e[38;2;203;75;22m" # Orange — Solarized's "bright red"
IGreen="\e[38;2;133;153;0m" # Green (no brighter variant)
IYellow="\e[38;2;181;137;0m" # Yellow (no brighter variant)
IBlue="\e[38;2;108;113;196m" # Violet — Solarized's "bright blue"
IPurple="\e[38;2;211;54;130m" # Magenta (no brighter variant)
ICyan="\e[38;2;42;161;152m" # Cyan (no brighter variant)
IWhite="\e[38;2;88;110;117m" # Base01 — emphasis
# Bold high intensity
BIBlack="\e[1;38;2;147;161;161m" # Base1 bold
BIRed="\e[1;38;2;203;75;22m" # Orange bold
BIGreen="\e[1;38;2;133;153;0m" # Green bold
BIYellow="\e[1;38;2;181;137;0m" # Yellow bold
BIBlue="\e[1;38;2;108;113;196m" # Violet bold
BIPurple="\e[1;38;2;211;54;130m" # Magenta bold
BICyan="\e[1;38;2;42;161;152m" # Cyan bold
BIWhite="\e[1;38;2;88;110;117m" # Base01 bold
# ---- Background palette overrides -------------------------------------------
# Light bases flip: On_Black → Base3 (lightest), On_IBlack → Base2 (highlights)
On_Black="\e[48;2;253;246;227m" # Base3 — main light background
On_Red="\e[48;2;220;50;47m" # Red
On_Green="\e[48;2;133;153;0m" # Green
On_Yellow="\e[48;2;181;137;0m" # Yellow
On_Blue="\e[48;2;38;139;210m" # Blue
On_Purple="\e[48;2;211;54;130m" # Magenta
On_Cyan="\e[48;2;42;161;152m" # Cyan
On_White="\e[48;2;101;123;131m" # Base00
On_IBlack="\e[48;2;238;232;213m" # Base2 — background highlights (slightly darker than Base3)
On_IRed="\e[48;2;203;75;22m" # Orange
On_IGreen="\e[48;2;133;153;0m" # Green
On_IYellow="\e[48;2;181;137;0m" # Yellow
On_IBlue="\e[48;2;108;113;196m" # Violet
On_IPurple="\e[48;2;211;54;130m" # Magenta
On_ICyan="\e[48;2;42;161;152m" # Cyan
On_IWhite="\e[48;2;88;110;117m" # Base01
# ---- Special codes ----------------------------------------------------------
DEFAULTFG="\e[38;2;101;123;131m" # Base00 — default foreground on light bg
DEFAULTBG="\e[48;2;253;246;227m" # Base3 — default background
RESETCOL="\e[0m"
# ---- Prompt colour slots ----------------------------------------------------
# All specified as direct ANSI sequences to avoid ordering dependencies
# with the palette overrides above.
PROMPT_COLOR_TIME_FG="\e[38;2;181;137;0m" # Yellow — primary accent
PROMPT_COLOR_TIME_BG="\e[48;2;238;232;213m" # Base2 — slightly darker than bg
PROMPT_COLOR_BAR_BG="\e[48;2;238;232;213m" # Base2 — warm light bar
PROMPT_COLOR_OK_FG="\e[38;2;7;54;66m" # Base02 — dark text for contrast on light
PROMPT_COLOR_OK_MARK="\e[38;2;133;153;0m" # Green — checkmark
PROMPT_COLOR_ERR_BG="\e[48;2;220;50;47m" # Red — error background
PROMPT_COLOR_ERR_FG="\e[38;2;253;246;227m" # Base3 — light text on red
PROMPT_COLOR_ERR_MARK="\e[38;2;253;246;227m" # Base3 — X mark (bright on red)
PROMPT_COLOR_ROOT_FG="\e[38;2;220;50;47m" # Red — root warning
PROMPT_COLOR_USER_FG="\e[38;2;42;161;152m" # Cyan — normal user
PROMPT_COLOR_DIR_FG="\e[38;2;38;139;210m" # Blue — working directory

View File

@@ -0,0 +1,122 @@
# Solarized Dark prompt theme — exact 24-bit / true-colour palette
# ------------------------------------------------------------------------------
# Theme files are NOT executed as shell scripts. load_theme() parses them
# line by line. Only the following value forms are accepted:
#
# KEY="$ColorVarName" — reference to a colour variable from disp.sh
# KEY="${ColorVarName}" — same with braces
# KEY="\e[...m" — raw ANSI escape sequence (single block)
#
# Accepted keys:
# PROMPT_COLOR_* — prompt slot colours (see profile.conf [prompt])
# Standard colour vars — Black, Blue, On_IBlack, … (overrides the palette
# from disp.sh for the whole shell session)
#
# Any unknown key, unsafe value, or shell construct will be ignored with a
# warning — theme files cannot execute code.
# ------------------------------------------------------------------------------
# Requires a terminal with true-colour / 24-bit support.
# Check with: printf '\e[38;2;220;50;47mred\e[0m\n'
# If you see solid red text, your terminal supports this theme.
# (konsole, iTerm2, kitty, alacritty, Windows Terminal all do)
#
# Solarized Dark exact hex → RGB mapping used below:
# Base03 #002b36 → 0 43 54 (darkest background)
# Base02 #073642 → 7 54 66 (background highlights)
# Base01 #586e75 → 88 110 117 (comments / secondary content)
# Base00 #657b83 →101 123 131 (body text — dark background)
# Base0 #839496 →131 148 150 (body text — main)
# Base1 #93a1a1 →147 161 161 (optional emphasis)
# Yellow #b58900 →181 137 0
# Orange #cb4b16 →203 75 22
# Red #dc322f →220 50 47
# Magenta #d33682 →211 54 130
# Violet #6c71c4 →108 113 196
# Blue #268bd2 → 38 139 210
# Cyan #2aa198 → 42 161 152
# Green #859900 →133 153 0
# ------------------------------------------------------------------------------
# ---- Foreground palette overrides -------------------------------------------
# Regular
Black="\e[38;2;0;43;54m" # Base03 — darkest content fg
Red="\e[38;2;220;50;47m" # Red
Green="\e[38;2;133;153;0m" # Green
Yellow="\e[38;2;181;137;0m" # Yellow — primary accent
Blue="\e[38;2;38;139;210m" # Blue
Purple="\e[38;2;211;54;130m" # Magenta
Cyan="\e[38;2;42;161;152m" # Cyan
White="\e[38;2;131;148;150m" # Base0 — body text
# Bold
BBlack="\e[1;38;2;7;54;66m" # Base02 bold
BRed="\e[1;38;2;220;50;47m" # Red bold
BGreen="\e[1;38;2;133;153;0m" # Green bold
BYellow="\e[1;38;2;181;137;0m" # Yellow bold
BBlue="\e[1;38;2;38;139;210m" # Blue bold
BPurple="\e[1;38;2;211;54;130m" # Magenta bold
BCyan="\e[1;38;2;42;161;152m" # Cyan bold
BWhite="\e[1;38;2;147;161;161m" # Base1 bold
# High intensity (brighter / emphasis roles in Solarized)
IBlack="\e[38;2;88;110;117m" # Base01 — secondary/comments
IRed="\e[38;2;203;75;22m" # Orange — Solarized's "bright red"
IGreen="\e[38;2;133;153;0m" # Green (no brighter variant)
IYellow="\e[38;2;181;137;0m" # Yellow (no brighter variant)
IBlue="\e[38;2;108;113;196m" # Violet — Solarized's "bright blue"
IPurple="\e[38;2;211;54;130m" # Magenta (no brighter variant)
ICyan="\e[38;2;42;161;152m" # Cyan (no brighter variant)
IWhite="\e[38;2;147;161;161m" # Base1 — optional emphasis
# Bold high intensity
BIBlack="\e[1;38;2;88;110;117m" # Base01 bold
BIRed="\e[1;38;2;203;75;22m" # Orange bold
BIGreen="\e[1;38;2;133;153;0m" # Green bold
BIYellow="\e[1;38;2;181;137;0m" # Yellow bold
BIBlue="\e[1;38;2;108;113;196m" # Violet bold
BIPurple="\e[1;38;2;211;54;130m" # Magenta bold
BICyan="\e[1;38;2;42;161;152m" # Cyan bold
BIWhite="\e[1;38;2;147;161;161m" # Base1 bold
# ---- Background palette overrides -------------------------------------------
On_Black="\e[48;2;0;43;54m" # Base03
On_Red="\e[48;2;220;50;47m" # Red
On_Green="\e[48;2;133;153;0m" # Green
On_Yellow="\e[48;2;181;137;0m" # Yellow
On_Blue="\e[48;2;38;139;210m" # Blue
On_Purple="\e[48;2;211;54;130m" # Magenta
On_Cyan="\e[48;2;42;161;152m" # Cyan
On_White="\e[48;2;131;148;150m" # Base0
On_IBlack="\e[48;2;7;54;66m" # Base02 — background highlights
On_IRed="\e[48;2;203;75;22m" # Orange
On_IGreen="\e[48;2;133;153;0m" # Green
On_IYellow="\e[48;2;181;137;0m" # Yellow
On_IBlue="\e[48;2;108;113;196m" # Violet
On_IPurple="\e[48;2;211;54;130m" # Magenta
On_ICyan="\e[48;2;42;161;152m" # Cyan
On_IWhite="\e[48;2;147;161;161m" # Base1
# ---- Special codes ----------------------------------------------------------
DEFAULTFG="\e[38;2;131;148;150m" # Base0 — default foreground
DEFAULTBG="\e[48;2;0;43;54m" # Base03 — default background
RESETCOL="\e[0m"
# ---- Prompt colour slots ----------------------------------------------------
# All specified as direct ANSI sequences to avoid ordering dependencies
# with the palette overrides above.
PROMPT_COLOR_TIME_FG="\e[38;2;181;137;0m" # Yellow — primary accent
PROMPT_COLOR_TIME_BG="\e[48;2;0;43;54m" # Base03 — darkest background
PROMPT_COLOR_BAR_BG="\e[48;2;88;110;117m" # Base01 — slightly brighter bar (was Base02)
PROMPT_COLOR_OK_FG="\e[38;2;131;148;150m" # Base0 — body text on success
PROMPT_COLOR_OK_MARK="\e[38;2;133;153;0m" # Green — checkmark
PROMPT_COLOR_ERR_BG="\e[48;2;180;20;15m" # deeper crimson — more contrast than Solarized Red
PROMPT_COLOR_ERR_FG="\e[1;38;2;255;255;255m" # bold pure white — maximum contrast on dark red
PROMPT_COLOR_ERR_MARK="\e[1;38;2;253;246;227m" # Base3 bold — bright warm mark stands out on crimson
PROMPT_COLOR_ROOT_FG="\e[38;2;220;50;47m" # Red — root warning
PROMPT_COLOR_USER_FG="\e[38;2;42;161;152m" # Cyan — normal user
PROMPT_COLOR_DIR_FG="\e[38;2;38;139;210m" # Blue — working directory

419
profile.d/updates.sh Normal file
View File

@@ -0,0 +1,419 @@
#!/usr/bin/env bash
# ------------------------------------------------------------------------------
# Copyright (c) 2013-2026 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
# Protected by the BSD3 license. Please read bellow for details.
#
# * Redistribution and use in source and binary forms,
# * with or without modification, are permitted provided
# * that the following conditions are met:
# *
# * Redistributions of source code must retain the above
# * copyright notice, this list of conditions and the
# * following disclaimer.
# *
# * Redistributions in binary form must reproduce the above
# * copyright notice, this list of conditions and the following
# * disclaimer in the documentation and/or other materials
# * provided with the distribution.
# *
# * Neither the name of the copyright holder nor the names
# * of any other contributors may be used to endorse or
# * promote products derived from this software without
# * specific prior written permission.
# *
# * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
# * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
# * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
# * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
# * OF SUCH DAMAGE.
# ------------------------------------------------------------------------------
export BASE_URL="https://git.geoffray-levasseur.org/fatalerrors/profile"
# Load [updates] configuration before building the URLs so that
# UPDT_DEFAULT_BRANCH (if set in profile.conf) is already available.
load_conf "updates"
export UPDT_DEFAULT_BRANCH="${UPDT_DEFAULT_BRANCH:-master}"
export UPDT_URL="$BASE_URL/raw/branch/$UPDT_DEFAULT_BRANCH"
export ARCH_URL="$BASE_URL/archive/$UPDT_DEFAULT_BRANCH.tar.gz"
# ------------------------------------------------------------------------------
# Check whether a newer profile version is available
# Usage: check_updates [-q]
# If -q is specified, the function will operate in quiet mode (internal use only)
check_updates()
{
local quiet=0 result=5 PARSED
local vfile="" lastver=""
PARSED=$(getopt -o hq --long help,quiet -n 'check_updates' -- "$@")
# shellcheck disable=SC2181 # getopt return code is checked immediately after
if [[ $? -ne 0 ]]; then
disp E "Invalid options, use \"check_updates --help\" to display usage."
return 2
fi
eval set -- "$PARSED"
while true; do
case "$1" in
-h|--help)
printf "check_updates: Check whether a newer profile version is available.\n\n"
printf "Usage: check_updates [-q|--quiet]\n"
printf "This command only checks availability; it does not modify the installation.\n"
return 0
;;
-q|--quiet)
quiet=1
shift
;;
--)
shift
break
;;
*)
break
;;
esac
done
(( quiet != 1 )) && disp I "Checking for updates..."
vfile=$(mktemp /tmp/profile_version.XXXXXX) || {
disp E "Failed to create a temporary file."
return 4
}
dwl "$UPDT_URL/version" "$vfile" >/dev/null 2>&1 || {
rm -f "$vfile"
disp E "Cannot download version file; unable to continue."
return 5
}
if [[ -s $vfile ]]; then
lastver=$(<"$vfile")
if [[ "$lastver" != "$PROFVERSION" ]]; then
disp I "Installed: $PROFVERSION. Available: $lastver."
(( quiet != 1 )) && disp I "You should upgrade when possible."
result=1
else
(( quiet != 1 )) && disp I "Your version is up-to-date."
result=0
fi
rm -f "$vfile"
else
rm -f "$vfile"
disp E "Temporary file is unreadable; unable to continue."
fi
return $result
}
export -f check_updates
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Apply the available profile upgrade
# Usage: profile_upgrade [options]
profile_upgrade()
{
local PARSED
local check_rc=0 dry_run=0 force_git=0 switch_to_git=0
local archive_file="" tmpbase="" use_archive=0 branch=""
local tmpdir="" archive="" extracted_root=""
PARSED=$(getopt -o hf:t:nFb:g --long help,file:,tmpdir:,dry-run,force,branch:,switch-to-git -n 'profile_upgrade' -- "$@")
# shellcheck disable=SC2181 # getopt return code is checked immediately after
if [[ $? -ne 0 ]]; then
disp E "Invalid options, use \"profile_upgrade --help\" to display usage."
return 2
fi
eval set -- "$PARSED"
while true; do
case "$1" in
-h|--help)
printf "profile_upgrade: Apply the available profile upgrade.\n\n"
printf "Usage: profile_upgrade [options]\n\n"
printf "Options:\n"
printf "\t-h, --help\t\tDisplay this help screen\n"
printf "\t-f, --file ARCHIVE\tUse a local archive file for the upgrade\n"
printf "\t-t, --tmpdir DIR\tCreate the temporary working directory under DIR\n"
printf "\t-b, --branch NAME\tUse NAME as the target Git branch\n"
printf "\t-g, --switch-to-git\tReplace current install with a fresh Git clone\n"
printf "\t-n, --dry-run\t\tDisplay what would be done without changing anything\n"
printf "\t-F, --force\t\tDiscard local changes before upgrading\n\n"
printf "If the profile is installed from Git, the upgrade uses 'git pull'.\n"
printf "Otherwise, it downloads or applies an archive and refreshes the files.\n"
return 0
;;
-f|--file)
archive_file="$2"
use_archive=1
shift 2
;;
-t|--tmpdir)
tmpbase="$2"
shift 2
;;
-b|--branch)
branch="$2"
shift 2
;;
-g|--switch-to-git)
switch_to_git=1
shift
;;
-n|--dry-run)
dry_run=1
shift
;;
-F|--force)
force_git=1
shift
;;
--)
shift
break
;;
*)
disp E "Invalid options, use \"profile_upgrade --help\" to display usage."
return 1
;;
esac
done
if (( ! use_archive && ! switch_to_git )); then
check_updates -q
check_rc=$?
if (( check_rc == 0 )); then
disp I "No update available."
return 0
elif (( check_rc > 1 )); then
disp E "Unable to check whether an update is available."
return "$check_rc"
fi
fi
if [[ ! -s $MYPATH/profile.sh ]]; then
disp E "Install path detection failed; cannot upgrade automatically."
return 1
fi
if [[ -d $MYPATH/.git ]] && (( use_archive )) && (( ! force_git )); then
disp E "Refusing archive upgrade on a Git install without --force."
return 1
fi
if (( switch_to_git )); then
command -v git >/dev/null 2>&1 || {
disp E "Git is required to switch this install to a Git clone."
return 3
}
if (( dry_run )); then
disp I "[dry-run] rm -rf \"$MYPATH\"/.git"
disp I "[dry-run] git clone $BASE_URL \"$MYPATH\""
[[ -n "$branch" ]] && disp I "[dry-run] git -C \"$MYPATH\" checkout $branch"
return 0
fi
if [[ -d $MYPATH/.git ]]; then
disp W "Git repository already present; no switch is needed."
else
local backup_dir="${MYPATH}.pre-git.$$.bak"
mv "$MYPATH" "$backup_dir" || {
disp E "Failed to move current install out of the way."
return 3
}
git clone "$BASE_URL" "$MYPATH" || {
disp E "Git clone failed; previous install kept in $backup_dir."
mv "$backup_dir" "$MYPATH" 2>/dev/null || true
return 3
}
[[ -n "$branch" ]] && (
cd "$MYPATH" && git checkout "$branch"
) || true
disp I "Switched installation to Git source."
disp I "Previous install kept in $backup_dir."
return 0
fi
fi
if [[ -d $MYPATH/.git ]] && (( ! use_archive )); then
disp I "Git installation detected, applying git pull."
command -v git >/dev/null 2>&1 || {
disp E "Git is required for this upgrade but is not available."
return 3
}
pushd "$MYPATH" >/dev/null || {
disp E "Failed to change directory to $MYPATH."
return 3
}
git rev-parse --is-inside-work-tree >/dev/null 2>&1 || {
disp E "Install directory is not a valid Git working tree."
popd >/dev/null || return 1
return 3
}
# Resolve the target branch: explicit -b flag > config default > master
local target_branch="${branch:-${UPDT_DEFAULT_BRANCH:-master}}"
branch="$target_branch"
# Detect current local branch and warn+switch when it differs
local current_branch
current_branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null)
if [[ -n "$current_branch" && "$current_branch" != "$branch" ]]; then
disp W "Branch mismatch: currently on '$current_branch', configured branch is '$branch'."
if (( dry_run )); then
disp I "[dry-run] git fetch origin $branch"
disp I "[dry-run] git checkout $branch"
disp W "[dry-run] Branch would be changed from '$current_branch' to '$branch'."
else
git fetch origin "$branch" || {
disp E "Git fetch failed for branch $branch."
popd >/dev/null || return 1
return 2
}
git checkout "$branch" || {
disp E "Git checkout failed for branch $branch."
popd >/dev/null || return 1
return 2
}
disp W "Branch has been changed from '$current_branch' to '$branch'."
fi
fi
if ! git diff --quiet || ! git diff --cached --quiet || [[ -n $(git ls-files --others --exclude-standard) ]]; then
if (( force_git )); then
disp W "Force mode: local Git changes and untracked files will be lost."
if (( dry_run )); then
disp I "[dry-run] git fetch --all --prune"
disp I "[dry-run] git reset --hard HEAD"
disp I "[dry-run] git clean -fd"
else
git fetch --all --prune || {
disp E "Git fetch failed, upgrade not applied."
popd >/dev/null || return 1
return 4
}
git reset --hard HEAD || {
disp E "Git reset failed, upgrade not applied."
popd >/dev/null || return 1
return 4
}
git clean -fd || {
disp E "Git clean failed, upgrade not applied."
popd >/dev/null || return 1
return 4
}
fi
else
disp W "The Git working tree contains local changes."
disp W "Consider committing or stashing them before upgrading, or use --force."
disp W "Upgrade may fail if the changes conflict with the upgrade."
fi
fi
if (( dry_run )); then
disp I "[dry-run] git pull origin $branch"
else
git pull origin "$branch" || {
disp E "Git pull failed, upgrade not applied."
popd >/dev/null || return 1
return 2
}
disp I "Successfully upgraded using git."
fi
popd >/dev/null || return 1
else
if (( use_archive )); then
[[ -r "$archive_file" ]] || {
disp E "Local archive '$archive_file' is missing or unreadable."
return 4
}
disp I "Using local archive $archive_file."
else
disp W "No Git repo found. Git is the recommended source."
disp I "Applying upgrade from archive..."
fi
if [[ -n "$tmpbase" ]]; then
if (( dry_run )); then
disp I "[dry-run] mkdir -p \"$tmpbase\""
disp I "[dry-run] mktemp -d \"$tmpbase/profile_upg.XXXXXX\""
tmpdir="$tmpbase/profile_upg.DRYRUN"
else
mkdir -p "$tmpbase" || {
disp E "Failed to create temporary directory base $tmpbase."
return 5
}
tmpdir=$(mktemp -d "$tmpbase/profile_upg.XXXXXX") || {
disp E "Failed to create temp working directory under $tmpbase."
return 5
}
fi
else
if (( dry_run )); then
disp I "[dry-run] mktemp -d /tmp/profile_upg.XXXXXX"
tmpdir="/tmp/profile_upg.DRYRUN"
else
tmpdir=$(mktemp -d /tmp/profile_upg.XXXXXX) || {
disp E "Failed to create temporary directory."
return 5
}
fi
fi
if (( use_archive )); then
archive="$archive_file"
else
archive="$tmpdir/profile.tar.gz"
if (( dry_run )); then
disp I "[dry-run] dwl \"$ARCH_URL\" \"$archive\""
else
dwl "$ARCH_URL" "$archive" || {
disp E "Failed to download archive."
rm -rf "$tmpdir"
return 6
}
fi
fi
if (( dry_run )); then
disp I "[dry-run] tar -xzf \"$archive\" -C \"$tmpdir\""
disp I "[dry-run] cp -a <extracted_profile>/. \"$MYPATH\"/"
else
tar -xzf "$archive" -C "$tmpdir" || {
disp E "Archive extraction failed."
rm -rf "$tmpdir"
return 7
}
extracted_root=$(find "$tmpdir" -mindepth 1 -maxdepth 1 -type d ! -name '.*' | head -n 1)
if [[ -z "$extracted_root" ]]; then
disp E "Could not find extracted profile files."
rm -rf "$tmpdir"
return 8
fi
disp I "Installing new version..."
cp -a "$extracted_root"/. "$MYPATH"/ || {
disp E "Failed to copy new files into $MYPATH."
rm -rf "$tmpdir"
return 9
}
disp I "Upgrade complete. Please log out and log in again."
rm -rf "$tmpdir"
fi
fi
}
export -f profile_upgrade
# ------------------------------------------------------------------------------
# EOF

View File

@@ -1,43 +1,7 @@
#!/bin/bash
#!/usr/bin/env bash
# Begin profile
# ------------------------------------------------------------------------------
# Initial version from Beyond Linux From Scratch by
# * James Robertson <jameswrobertson@earthlink.net>
# * Dagmar d'Surreal <rivyqntzne@pbzpnfg.arg>
# ------------------------------------------------------------------------------
# Current version from Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
# 16/02/2013 v1.0.0 : Initial version
# 24/10/2015 v2.0.0 : Added advanced functionnalities (clean, srr, etc.)
# 04/02/2017 v2.0.1 : clean improvements (--shell)
# 16/09/2018 v2.1.0 : Added rmhost, setc, setfr, more locales management
# 23/09/2019 v2.1.1 : [bugfix] dpkgs
# 24/09/2019 v2.1.2 : [bugfix] bug in profile version display
# 16/12/2019 v2.2.0 : Added showinfo, primary write of showdiskmap
# 08/01/2020 v2.3.0 : Added use of figlet and neofetch as a motd replace
# 16/01/2020 v2.3.1 : [bugfix] non-interactive were blocked with some functions
# 31/01/2020 v2.3.2 : Figlet: changed default font to ansi_shadow
# 02/03/2020 v2.4.0 : Added command auzip
# 03/03/2020 v2.5.0 : Added command taz and rmspc, auzip => utaz improved
# 05/03/2020 v2.5.1 : Language consistancy fix, added pigz support in taz
# 06/03/2020 v2.5.2 : Few aliases sorted out
# 11/09/2020 v2.5.3 : Few more aliases, improved code consistancy and typo,
# : improved utaz, removed showdiskmap, removed remaining French,
# : added license information for future publication
# 24/10/2020 v2.6.0 : Added session save and restore for Konsole
# 25/12/2020 v2.6.1 : Add check on rmhost, improvements rmspc, created expendlist
# 26/02/2021 v2.6.2 : [bugfix] taz: corrected bug with trailing slash on directories
# 18/10/2021 v2.6.3 : changed PS1 for status bar style version, few minor improvements
# 21/06/2022 v2.7.0 : added isipv4 and isipv6, use it in rmhost as an improvement
# 22/06/2022 v2.7.1 : [bugfix] few minor corrections, added help command
# 24/06/2022 v2.8.0 : Added backtrace, error and settrace, corrected showinfo
# 19/07/2022 v2.8.1 : few cleanup, fixes and optimizations
# 29/07/2022 v2.8.2 : added warning for non bash users
# 27/08/2022 v3.0.0 : splitted everything, added rain screensaver
# 07/11/2022 v3.0.1 : added concatenation to rmspc, added ku, error managed in meteo
# 08/11/2022 v3.1.0 : added password generator
# 10/11/2022 v3.1.1 : genpwd: test if password is doable
# ------------------------------------------------------------------------------
# Copyright (c) 2013-2022 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
# Copyright (c) 2013-2026 Geoffray Levasseur <fatalerrors@geoffray-levasseur.org>
# Protected by the BSD3 license. Please read bellow for details.
#
# * Redistribution and use in source and binary forms,
@@ -71,46 +35,165 @@
# * OF SUCH DAMAGE.
# ------------------------------------------------------------------------------
export PROFVERSION="3.1.1"
export DEFAULT_CITY="Toulouse"
if [[ ! $(echo $SHELL | grep bash) ]]; then
echo "That script is designed to be used with bash as being the shell."
echo "Please consider using bash instead, or patch me ;) !"
if [[ ! $SHELL =~ bash ]]; then
echo "That environment script is designed to be used with bash being the shell."
echo "Please consider using bash to enjoy our features!"
return 1
fi
# Required for associative arrays (4.0+) and namerefs (4.3+)
if ((BASH_VERSINFO[0] < 4)) || [[ ${BASH_VERSINFO[0]} -eq 4 && ${BASH_VERSINFO[1]} -lt 3 ]]; then
echo "[ Error ] This profile requires Bash 4.3 or higher."
echo "Current version: $BASH_VERSION"
(return 0 2>/dev/null) && return 1 || exit 1
fi
# ------------------------------------------------------------------------------
# path* : private functions for PATH variable management
# ------------------------------------------------------------------------------
pathremove()
{
local ifs=':'
local newpath
local dir
local pathvar=${2:-PATH}
[[ -z "$1" ]] && return 0
local IFS=':'
local newpath dir
local pathvar="${2:-PATH}"
[[ "$pathvar" =~ ^[a-zA-Z_][a-zA-Z0-9_]*$ ]] || {
printf "pathremove: unsafe variable name '%s'\n" "$pathvar" >&2
return 1
}
for dir in ${!pathvar}; do
if [ "$dir" != "$1" ] ; then
newpath=${newpath:+$newpath:}$dir
fi
[[ "$dir" != "$1" ]] && newpath="${newpath:+$newpath:}$dir"
done
export $pathvar="$newpath"
export "$pathvar=$newpath"
}
pathprepend ()
{
pathremove $1 $2
local pathvar=${2:-PATH}
export $pathvar="$1${!pathvar:+:${!pathvar}}"
}
#pathprepend() # Unused for now, but might be useful in the future
#{
# [[ -z "$1" ]] && return 0
# local pathvar="${2:-PATH}"
# [[ "$pathvar" =~ ^[a-zA-Z_][a-zA-Z0-9_]*$ ]] || {
# printf "pathprepend: unsafe variable name '%s'\n" "$pathvar" >&2
# return 1
# }
# pathremove "$1" "$pathvar"
# export "$pathvar=$1${!pathvar:+:${!pathvar}}"
#}
pathappend()
{
pathremove $1 $2
local pathvar=${2:-PATH}
export $pathvar="${!pathvar:+${!pathvar}:}$1"
[[ -z "$1" ]] && return 0
local pathvar="${2:-PATH}"
[[ "$pathvar" =~ ^[a-zA-Z_][a-zA-Z0-9_]*$ ]] || {
printf "pathappend: unsafe variable name '%s'\n" "$pathvar" >&2
return 1
}
pathremove "$1" "$pathvar"
export "$pathvar=${!pathvar:+${!pathvar}:}$1"
}
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Configuration file parser
parse_conf()
{
local config_file="$1"
local current_section=""
local key value
[[ ! -f "$config_file" ]] && return 1
while IFS='=' read -r key value || [[ -n "$key" ]]; do
# Internal trimming (removes leading/trailing whitespace & CR)
key="${key%"${key##*[![:space:]]}"}"
key="${key#"${key%%[![:space:]]*}"}"
key="${key%$'\r'}" # Strip potential Windows line endings
# Skip comments and empty lines
[[ -z "$key" || "$key" =~ ^[#\;] ]] && continue
# Section Detection: [section_name]
if [[ "$key" =~ ^\[([a-zA-Z0-9_]+)\]$ ]]; then
current_section="${BASH_REMATCH[1]}"
declare -g -A "CONF_$current_section"
continue
fi
# Secure Assignment (if inside a section)
if [[ -n "$current_section" ]]; then
# Clean the value
value="${value%"${value##*[![:space:]]}"}"
value="${value#"${value%%[![:space:]]*}"}"
value="${value%$'\r'}"
# Protect against command injection by disallowing certain characters in keys
value="${value//\`/}"
value="${value//\$\(/}"
# Correctly interpretet internal variables (e.g. $HOME)
if [[ "$value" == *\$* ]]; then
value=$(envsubst <<< "$value")
fi
# Strip quotes (handling both " and ')
value="${value%\"}"; value="${value#\"}"
value="${value%\'}"; value="${value#\'}"
# Use a nameref for safe, eval-free assignment
local -n current_array="CONF_$current_section"
# shellcheck disable=SC2034 # Dynamic var creation
current_array["$key"]="$value"
fi
done < "$config_file"
}
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Load command aliases from configuration
load_alias()
{
local section_name="CONF_$1"
# Check if the associative array exists using declare -p
[[ "$(declare -p "$section_name" 2>/dev/null)" != "declare -A"* ]] && return 1
# Create a nameref to the section array
local -n current_aliases="$section_name"
# Iterate safely over the keys of the associative array
for key in "${!current_aliases[@]}"; do
local cmd="${current_aliases[$key]}"
# Extract the base command (first word) safely without awk
local base_cmd="${cmd%% *}"
# Only alias if the base command is executable
if command -v "$base_cmd" >/dev/null 2>&1; then
# shellcheck disable=SC2139 # Dynamic alias creation
alias "$key"="$cmd"
fi
done
}
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Load configuration values as environment variables
load_conf()
{
local section_name="CONF_$1"
[[ "$(declare -p "$section_name" 2>/dev/null)" != "declare -A"* ]] && return 1
local -n current_vars="$section_name"
for key in "${!current_vars[@]}"; do
# Export the key/value pair as a standard shell variable
# We use 'export' directly; Bash handles the assignment safely here
export "$key"="${current_vars[$key]}"
done
}
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
@@ -119,84 +202,81 @@ pathappend ()
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Store script's path (realpath -s resolve symlinks if profile.sh is a symlink)
# Because we're more likely to be sourced, we use BASH_SOURCE to get the path
# of the sourced file instead of $0
if [[ -z "$PROFILE_PATH" ]]; then
MYPATH=$(dirname "$(realpath -s "${BASH_SOURCE[0]}")")
else
MYPATH="$PROFILE_PATH"
fi
export MYPATH
if [[ ! -e "$MYPATH/profile.sh" ]]; then
echo "[ Warning ] Path detection failed, trying to use pwd..."
MYPATH=$(pwd)
if [[ ! -e "$MYPATH/profile.sh" ]]; then
echo "[ Error ] Impossible to determine installation path, pretty much nothing will work."
fi
fi
if [[ ! -s "$MYPATH/version" ]]; then
echo "[ Warning ] Impossible to determine running version of profile, your installation might be broken."
fi
PROFVERSION=$(cat "$MYPATH"/version)
export PROFVERSION
# Build PATH environment variable
if [[ $EUID -eq 0 ]]; then
pathappend /sbin:/usr/sbin
fi
[[ -d /share/services/gestparc ]] && pathappend /share/services/gestparc
[[ -d ~/bin ]] && pathappend ~/bin
[[ -d ~/.local/bin ]] && pathappend ~/.local/bin
# Set bash history
export HISTSIZE=50000
export HISTIGNORE="&:[bf]g:exit"
# Parse and load general configuration
export PROFILE_CONF="$MYPATH/profile.conf"
parse_conf "$PROFILE_CONF"
load_conf system # Load Bash system behavior configuration (history, pager, etc.)
load_conf general # General purpose configuration (compilation flags, etc.)
# Set default pager
export PAGER=less
# More colors
export TERM=xterm-256color
# Set some compiling values
export CFLAGS="-O2 -pipe -march=native"
export MAKEFLAGS='-j12'
export PKGSOURCES='/share/src/archives'
# ------------------------------------------------------------------------------
# Default values could be altered after this line
# ------------------------------------------------------------------------------
# Load personal configuration
[[ -f ~/.profile.conf ]] && . ~/.profile.conf
# Execute optionnal config script if any
for script in ~/profile.d/*.sh ; do
if [ -r $script ] ; then
. $script
# Load module scripts
shopt -s nullglob
for script in "$MYPATH/profile.d/"*.sh; do
if [[ -f "$script" && -r "$script" ]]; then
# shellcheck source=/dev/null
. "$script" || printf "[ Warning ] Failed to source module: %s\n" "$script" >&2
fi
done
shopt -u nullglob
# Interactive shell detection, two methods available each one of those might have different result
# depending on distribution
#shopt -q login_shell && INTERACTIVE=1
[[ $- == *i* ]] && INTERACTIVE=1
[[ $- == *i* ]] && export INTERACTIVE=1
if [[ $INTERACTIVE ]]; then
# For compiling (as we often compile with LFS/0linux...)
#Aliases
alias ll='ls -laFh --color=auto'
alias la='ls -Ah --color=auto'
alias l='ls -CF --color=auto'
alias ls='ls --color=auto'
alias grep='grep --color=auto'
alias egrep='egrep --color=auto'
alias fgrep='fgrep --color=auto'
alias qfind="find . -name "
alias mkck='make check'
alias mkin='make install'
alias mkdin='make DESTDIR=$PWD/dest-install install'
alias ssh='ssh -Y'
alias wget='wget -c' # resume mode by default
alias myip='curl ip.appspot.com'
# Human readable by default
alias df='df -H'
alias du='du -ch'
alias sdu='du -sk ./* | sort -n'
load_alias aliases
# Define PS1
trap 'timer_start' DEBUG
PROMPT_COMMAND='set_prompt'
# Set default language
setfr
showinfo
echo "Profile version $PROFVERSION chargé..."
# Set default language from DEFAULT_LANG config key (set in [general]).
# The value must match one of the alias names defined in SET_LOCALE so that
# the corresponding set<alias> function exists after build_locale_shortcuts.
if [[ -n "${DEFAULT_LANG:-}" ]]; then
_lang_fn="set${DEFAULT_LANG}"
if declare -F "$_lang_fn" >/dev/null 2>&1; then
"$_lang_fn"
else
disp W "DEFAULT_LANG '$DEFAULT_LANG' has no matching locale shortcut (check SET_LOCALE in profile.conf)."
fi
unset _lang_fn
fi
showinfo && printf "\n"
check_updates -q
disp I "Profile version $PROFVERSION chargé..."
fi
# Cleanup

1
version Normal file
View File

@@ -0,0 +1 @@
3.99.2-4_rc_2