Dietpi banner upgrades#8172
Conversation
|
Outstanding items I know of:
|
| code=$? | ||
| else | ||
| # Running as non-root: Fail silently without NOPASSWD to avoid password prompt | ||
| COUNTIPS="$(sudo -n dash -c "$(declare -f Get_Fail2Ban_Status_Raw); Get_Fail2Ban_Status_Raw 2>&1" 2> /dev/null || return 2)" |
There was a problem hiding this comment.
I'm not sure if things that (potentially) need root need to be run this way. There are other instances at line 591, 358, and indirectly on lines 530-565. Does G_CHECK_ROOT_USER handle this? Is there a better way to detect root access and manage non-root errors?
There was a problem hiding this comment.
Yes, sudo -n is the best I could find if the script is called as non-root user. One needs to take care that sudo is returning the error code, rather than the called command, else the interpretation of the error can be wrong. Print_Cert_Status previously used does an echo as last command, hence any error was assured to be a failing sudo. Get_Fail2Ban_Status_Raw however returns an error as well if iptables and nft are both not installed (expected default on DietPi which uses a blackhole route by default as blocking action. I guess it should not return an error code, but just an empty string, which can then be interpreted as "no status", while any error is taken as "sudo failed"?
There was a problem hiding this comment.
I re-worked to use fail2ban-client banned and cleaned this up quite a bit.
…y to accept multiple commands. Added systemd and fail2ban status. Added network usage by namespace. Added disk usage by pattern matching. Added G_TRUNCATE_MID.
MichaIng
left a comment
There was a problem hiding this comment.
A great extension! But there is some cleanup/polishing needed to reduce the overhead (in code an UI), and make it work for expected use cases.
| [TRUE_GREEN]='\e[38;2;46;204;113m' | ||
| [TRUE_BG]='\e[48;2;44;62;80m' | ||
|
|
||
| ) |
There was a problem hiding this comment.
Here are (almost?) all 3+4 bit ANSI color codes stored in the array + duplicate indexed entries, which are then stored back to the named entries, which means the named indexes can be wrong, if users store different colors in the config file. And actually, the script makes still only use of the same 4 + 2 (green, yellow) colors, rendering all the named indexes redundant.
I do understand that this was intended to be used by users in their custom commands? But if someone really needs to make use of like 50 colors, then them can also lookup the raw 8 bit + 24 bit color codes, and have the choice between all 256 + everything the terminal supports: https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit
Defining this large array here looks like there are two contradicting ideas mixed:
- The original idea was to have the colors as indexed array, to not associate them with particular colors, so it does not cause confusing if users change them. Changing the colors has however never been added to the GUI yet.
- Using an associative array with (color-)named indexes, so the script is easier to read. This however conflicts with the first idea, as now
aCOLOR[RED]can be green.
To solve this cleanly, I suggest the following: Use an associative array, but not with color names, instead using names which indicate their use in the script. What comes to my mind in 1 a.m. (just an example to clarify the general idea 😉):
aCOLOR[ACCENT]for the green lines and bullet pointsaCOLOR[STRONG]for the bold whiteaCOLOR[WEAK]for the dark greyaCOLOR[ALERT]for the bright red currently used for all highlighted notifications, while this PR changes it to be used almost only for errors and immediate required actionaCOLOR[HIGHLIGHT]for the yellow color introduced here for notifications which were previously bright redaCOLOR[ALT]for the blue introduced here as ... not really a specific meaning/purpose but just as alternative color to give some contrast in certain outputs? I guess this could be replaced with the accent or highlight color as well, to not have too many different colors in the output overall. But I did not actually review the banner with all its features yet.- Remove every other index/value that is not explicitly used in this script.
Due to the dropped /boot/dietpi/.dietpi-banner_custom, we need to do a migration in dietpi-update patches anyway, hence we can migrate this color array as well.
There was a problem hiding this comment.
I was also thinking of these issues, and even started on a script for users to configure their own colors and formats, which would be useful for devices with abnormal/monochrome/etc displays.
A simple change that gets us to the better indexing scheme is renaming the indexes here and then using those new index names throughout. Then the saving/loading should be adjusted for the use-case names rather than index 0...6
On a larger scale, I had started to separate out all the color/formatting to a utility script that the end-user could also take advantage of, especially for the custom commands. This is why I started down the path of including formats and such.
Does it make sense to try to keep .dietpi-banner_custom?
There was a problem hiding this comment.
useful for devices with abnormal/monochrome/etc displays.
Right, cases where the darker gray is hardly readable (too dark) are not even so uncommon, which was the reason I turned those colors into config file variables some time ago, but never found the time to finish this theming topic properly: #2651
There is another one for whiptail dialog theming: #7516
separate out all the color/formatting to a utility script
But do you really think this is such a benefit, to use aCOLOR[SOME_COLOR_NAME] rather than looking up from the full set of possible color codes e.g. on the Wikipedia page I linked? What I thought about was even using moreless the scripts on the Wikipedia page to print all possible 3/4/8 bit color codes, as text + applied color to the console, to users can see live how it looks on their terminal, and can copy and paste the code to apply. But a huge array seems unnecessary to me, and cannot really contain all colors anyway, with meaningful array key names. Hence my suggestion to focus on the colors used in this script. We can add a link to the config file to support looking up color codes for now, and add a menu to change them in TUI later.
Does it make sense to try to keep .dietpi-banner_custom?
IMO it is not needed, as it is superseded by the support for multiple commands added here. As said, we can migrate existing custom scripts, practically by just copying the call of the existing custom script into the new custom command array. That would be also the new way if uses need to run a more complex script that does not fit into a single inputbox line.
There was a problem hiding this comment.
I re-worked the colors for semantic indexing and updated save/load. patching on updates is beyond me.
| 'Word-wrap lines on small screens' | ||
| 'Kernel' | ||
| 'Network Usage' | ||
| 'Disk Usage' |
There was a problem hiding this comment.
- dynamic disk space usage is independent of the pre-existing disk space entries (what do we do with the existing options?)
I suggest we merge "Disk usage (RootFS)" and "Disk usage (userdata)" into the new single option. Otherwise it is confusing to have 3 different options all showing some disk usage, and hence also 3 code sections to keep in sync. Maybe / and /mnt/dietpi_userdata (if a mount point) can be dedicated fixed quick-select options in a parent menu. Or, maybe simpler, all findmnt -no SOURCE --real mount points could be part of that menu. So when adding a disk, there is a menu showing all mount points to select + custom, and custom shows the inputbox? The fixed mount points can still be treated like a glob when printing the banner, of course.
There was a problem hiding this comment.
So a dynamic checkbox array that has findmnt -no SOURCE --real options listed, plus a checkbox to then enter custom matching? And the first array item is always /mnt/dietpi_userdata.
There was a problem hiding this comment.
Right, that would be my suggestion. Little correction: findmnt -nro TARGET --real, to list them by mount point rather than block device, and without the tree view.
The hardcoded /mnt/dietpi_userdata wouldn't even be needed then, as its true mount point would show up in the list already, if it is a dedicated mount. And / shows up at the top, which is good as well.
| if command -v iptables >/dev/null 2>&1; then | ||
| COUNTIPS=$(iptables -L -n 2>/dev/null | awk '/REJECT/ {print $0}' | uniq -c | wc -l) | ||
| elif command -v nft >/dev/null 2>&1; then | ||
| COUNTIPS=$(nft list ruleset 2>/dev/null | awk '/reject/ {print $0}' | uniq -c | wc -l) | ||
| else | ||
| return 1 | ||
| fi |
There was a problem hiding this comment.
The DietPi default block action is actually a blackhole route, neither iptables nor nft. Hence this would currently not work or show false info, if iptables is installed for other reasons. Not sure how to solve this properly, parsing the Fail2Ban jail/actions config is difficult, due to the unlimited different ways about how/where this can be set/overridden.
... actually fail2ban-client banned is the right command to use here, as it shows the number of blocks regardless which method was used for each of them. Not trivial to parse the number of banned IPs from that output, but we can figure something out using IPv4 + IPv6 match pattern wrapped into single quotes.
There was a problem hiding this comment.
I updated to use fail2ban-client banned, which also cleaned up the sudo complications, since it is only needed for a single command in this context.
| code=$? | ||
| else | ||
| # Running as non-root: Fail silently without NOPASSWD to avoid password prompt | ||
| COUNTIPS="$(sudo -n dash -c "$(declare -f Get_Fail2Ban_Status_Raw); Get_Fail2Ban_Status_Raw 2>&1" 2> /dev/null || return 2)" |
There was a problem hiding this comment.
Yes, sudo -n is the best I could find if the script is called as non-root user. One needs to take care that sudo is returning the error code, rather than the called command, else the interpretation of the error can be wrong. Print_Cert_Status previously used does an echo as last command, hence any error was assured to be a failing sudo. Get_Fail2Ban_Status_Raw however returns an error as well if iptables and nft are both not installed (expected default on DietPi which uses a blackhole route by default as blocking action. I guess it should not return an error code, but just an empty string, which can then be interpreted as "no status", while any error is taken as "sudo failed"?
| fi | ||
|
|
||
| # Query interface list once inside the (optional) namespace and match names | ||
| mapfile -t observed_ifaces < <("${sudo_cmd_prefix[@]}" "${ns_cmd_prefix[@]}" ip -o link show 2>/dev/null | awk -F': ' '{print $2}') |
There was a problem hiding this comment.
ip should generally not require root permissions. EDIT: Or does the namespace change something about this?
There was a problem hiding this comment.
I think the specifying of a netns required root, that's where the prefix stuff comes from. It may need more testing to see what is needed.
There was a problem hiding this comment.
Okay, if that is the case, let's use sudo only if a namespace was actually selected, i.e. apply it as part of ns_cmd_prefix for non-root calls of the script.
There was a problem hiding this comment.
I combined the sudo and ns prefixes, since they were only ever used together anyway.
| local RX="N/A" TX="N/A" rx tx ip_addr ip_addr_cidr | ||
|
|
||
| # Get RX data | ||
| if "${sudo_cmd_prefix[@]}" "${ns_cmd_prefix[@]}" test -r "/sys/class/net/$ifname/statistics/rx_bytes"; then |
There was a problem hiding this comment.
This API should be world-readably by default. Else, instead of using the (expensive) sudo call by default, do so if the file is really not readable. But I think it always is, unless in containers, where however root does not help either.
EDIT: Same as in the other cases, maybe the namespace changes something. If so, sudo should only be used if an actual namespace is used, to avoid the overhead otherwise.
There was a problem hiding this comment.
So
if file is readable
read rx_bytes
else
read rx_bytes with sudo
There was a problem hiding this comment.
Right. Since the namespace selection happens in two places, maybe it makes sense to move generating the ns_cmd_prefix array into a dedicated function, which leaves it empty if run as root or no namespace selected, else adds sudo -n ip netns exec to the array? Or the call via sudo -n, applying the STDERR redirect for the raw command and error capture for sudo itself is done in a dedicated function, which returns the command output directly. That sudo wrapper function could then be conditionally called only if sudo is needed, i.e. not root and namespace. Not sure which way makes it most compact, but the latter would basically keep the previous way with Print_Cert_Status being called conditionally with or without sudo wrapper.
| # Running as non-root: Fail silently without NOPASSWD to avoid password prompt | ||
| # Get public IP, sourcing globals inside the sudo'd shell | ||
| # Pass namespace as positional arg to avoid expansion issues inside the subshell | ||
| IP=$(sudo -n bash -c '. /boot/dietpi/func/dietpi-globals >/dev/null 2>&1; G_GET_WAN_IP -t 1 -n "$1" 2>&1' _ "$ns") |
There was a problem hiding this comment.
G_GET_WAN_IP does a simple curl call, hence no sudo needed. Of does the namespace change something about that?
EDIT: If the namespace requires root permissions, it should be better handled in G_GET_WAN_IP itself, rather than here, and only if an actual namespace is passed.
There was a problem hiding this comment.
I believe any of the netns stuff requires root access.
There was a problem hiding this comment.
Okay in this particular case, a G_CHECK_ROOT_USER "$@" || return 0 call in the dietpi-globals G_GET_WAN_IP function would probably make most sense (if a namespace was passed). It re-exec itself with sudo -n if needed. That way, this works as well outside of dietpi-banner. ... thought it currently prints an additional info line about that, which is not wanted in the banner. So we'd need to add a -q/--quiet flag to suppress this or so.
Hmm, actually it might still add output the wrong way. Probably easier to keep the sudo wrapper in dietpi-banner for now, to have full and consistent control of how STDERR and STDOUT are handled for showing them nicely inline in the banner.
For script-internal (non-dietpi-globals) functions G_CHECK_ROOT_USER is not a good option either, since it works only within functions name G_*, it always uses bash as shell (adding unnecessary overhead/slowdown compared to dash bourne shell). It really is meant/designed for interactive G_* function calls from login console, while dietpi-* script which require root re-exec themselves with sudo as a whole.
There was a problem hiding this comment.
I made a dedicated BANNER_GET_WAN_IP so the handling is separate from the global version. It needs more testing and eyes on it, though. IPv6 is not yet included, and the sudo may still have problems.
| 1) stat="${aCOLOUR[RED]}No certificate found${COLOUR_RESET}" ;; | ||
| 2) stat="${aCOLOUR[YELLOW]}NOPASSWD sudo required${COLOUR_RESET}" ;; | ||
| *) stat="${aCOLOUR[YELLOW]}Unable to obtain cert status${COLOUR_RESET}" ;; |
There was a problem hiding this comment.
Is there a particular reason for this restructure? The downside is that the actual error messages reading the cert or from openssl are now masked, while they were previously transparently shown in the banner, replacing the cert status text. The separation of the sudo call block into an own function, and _Raw sub function is fine, but the exit code handling adds quite some more complexity, without a benefit that I can see.
The sudo wrapper could be actually a dedicated function, if needed multiple times, taking (now) _Raw function as input, returning either the NOPASSWD sudo required error, or whichever output/error the raw function echos?
There was a problem hiding this comment.
I don't remember any particular reason I rewrote this. I like the idea of a sudo-wrapper, but am not sure I could put one together. How would it be different than G_SUDO?
There was a problem hiding this comment.
G_SUDO is meant for G_* functions, and hence G_CHECK_ROOT_USER "$@" works only for whole scripts or G_* functions. It is not designed for/suitable for script-internal functions, and it is especially not designed for this particular case where we want the wrapped function's STDOUT + STDERR + sudo error all inline in the banner output line. And we want to use the lightweight dash where possible, while G_SUDO always calls bash as subshell, to be able to load dietpi-globals (which is another major overhead).
Thinking about the overhead of loading dietpi-globals again within a subshell: Since a delay in the banner can be quite annoying, maybe it is better to not use G_GET_WAN_IP for calls with namespace at all. The function is small, especially since it wouldn't require options handling in the banner, so we can inline the sudo -n ip netns exec curl call in this case. This also avoids the implicit change you are currently doing, where G_GET_WAN_IP merges STDERR into STDOUT, which is currently not the case. This is something which should happen in the banner only, nowhere else, as the banner is a special case where inlineing error messages has a visual benefit compared to throwing errors as dedicated alien lines without indentation, messing with the banner format.
…lors inside custom commands
…UR saves just the relevant indexes. added some shellcheck bypasses and documentation.
…ead tests from network data usage. Get_Fail2Ban_Status is cleaner without the complicated sudo handling.
Enhancing dietpi-banner.