From a46284b13aedd8582c93c087a4acb27989c0b407 Mon Sep 17 00:00:00 2001 From: Nuru Date: Wed, 19 Feb 2025 20:13:42 -0800 Subject: [PATCH 1/8] add terminal-theme-detector --- .dockerignore | 4 +- .gitignore | 4 +- os/debian/Dockerfile.debian | 8 + .../bin/terminal-colorsaurus-license.txt | 25 +++ src/colorsaurus/Cargo.lock | 157 ++++++++++++++++++ src/colorsaurus/Cargo.toml | 7 + src/colorsaurus/lib/display.rs | 15 ++ src/colorsaurus/src/main.rs | 27 +++ 8 files changed, 245 insertions(+), 2 deletions(-) create mode 100644 rootfs/usr/local/bin/terminal-colorsaurus-license.txt create mode 100644 src/colorsaurus/Cargo.lock create mode 100644 src/colorsaurus/Cargo.toml create mode 100644 src/colorsaurus/lib/display.rs create mode 100644 src/colorsaurus/src/main.rs diff --git a/.dockerignore b/.dockerignore index 1006d15c6..07f254037 100644 --- a/.dockerignore +++ b/.dockerignore @@ -24,6 +24,8 @@ aws-assumed-role/ **/*.terraform.auto.tfvars.json **/*.helmfile.vars.yaml +src/colorsaurus/target/** + # Module directory **/.terraform/ **/.module/ @@ -41,4 +43,4 @@ aws-assumed-role/ **/.DS_Store **/.CFUserTextEncoding **/.Trash/ -**/$RECYCLE.BIN/ \ No newline at end of file +**/$RECYCLE.BIN/ diff --git a/.gitignore b/.gitignore index d648a8bf3..023d4db34 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,8 @@ aws-assumed-role/ **/*.terraform.auto.tfvars.json **/*.helmfile.vars.yaml +src/colorsaurus/target/** + # Module directory **/.terraform/ **/.module/ @@ -35,4 +37,4 @@ aws-assumed-role/ **/.DS_Store **/.CFUserTextEncoding **/.Trash/ -**/$RECYCLE.BIN/YCLE.BIN/ \ No newline at end of file +**/$RECYCLE.BIN/YCLE.BIN/ diff --git a/os/debian/Dockerfile.debian b/os/debian/Dockerfile.debian index 761f3437d..a76bb21af 100644 --- a/os/debian/Dockerfile.debian +++ b/os/debian/Dockerfile.debian @@ -22,6 +22,11 @@ ARG HELM_DIFF_VERSION=3.9.13 # https://github.com/aslafy-z/helm-git/releases ARG HELM_GIT_VERSION=1.3.0 +FROM rust:1-${DEBIAN_CODENAME} AS rust +COPY /src/colorsaurus /src/colorsaurus +WORKDIR /src/colorsaurus +RUN cargo install --path . --root /usr/local + FROM python:${PYTHON_VERSION}-slim-${DEBIAN_CODENAME} AS python @@ -157,6 +162,9 @@ RUN echo 'set noswapfile' >> /etc/vim/vimrc WORKDIR /tmp +# Copy the Rust binary from the Rust build stage +COPY --from=rust /usr/local/bin/terminal-theme-detector /usr/local/bin/terminal-theme-detector + # Copy python dependencies COPY --from=python /usr/local/ /usr/local/ diff --git a/rootfs/usr/local/bin/terminal-colorsaurus-license.txt b/rootfs/usr/local/bin/terminal-colorsaurus-license.txt new file mode 100644 index 000000000..7078bb99f --- /dev/null +++ b/rootfs/usr/local/bin/terminal-colorsaurus-license.txt @@ -0,0 +1,25 @@ +The program terminal-theme-detector uses the terminal-colorsaurus library +from https://github.com/bash/terminal-colorsaurus/ , which is licensed +as follows: + +MIT License + +Copyright (c) 2024 Tau Gärtli + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/colorsaurus/Cargo.lock b/src/colorsaurus/Cargo.lock new file mode 100644 index 000000000..b0a4eef92 --- /dev/null +++ b/src/colorsaurus/Cargo.lock @@ -0,0 +1,157 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "libc" +version = "0.2.169" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.52.0", +] + +[[package]] +name = "terminal-colorsaurus" +version = "0.4.8" +source = "git+https://github.com/bash/terminal-colorsaurus?branch=main#f99ff455e2d3272c9accf3cee6b759c1702d7892" +dependencies = [ + "cfg-if", + "libc", + "memchr", + "mio", + "terminal-trx", + "windows-sys 0.59.0", + "xterm-color", +] + +[[package]] +name = "terminal-theme-detector" +version = "0.1.0" +dependencies = [ + "terminal-colorsaurus", +] + +[[package]] +name = "terminal-trx" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "975b4233aefa1b02456d5e53b22c61653c743e308c51cf4181191d8ce41753ab" +dependencies = [ + "cfg-if", + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "xterm-color" +version = "1.0.1" +source = "git+https://github.com/bash/terminal-colorsaurus?branch=main#f99ff455e2d3272c9accf3cee6b759c1702d7892" diff --git a/src/colorsaurus/Cargo.toml b/src/colorsaurus/Cargo.toml new file mode 100644 index 000000000..0a835df8a --- /dev/null +++ b/src/colorsaurus/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "terminal-theme-detector" +version = "0.1.0" +edition = "2021" + +[dependencies] +terminal-colorsaurus = { git = "https://github.com/bash/terminal-colorsaurus", branch = "main" } diff --git a/src/colorsaurus/lib/display.rs b/src/colorsaurus/lib/display.rs new file mode 100644 index 000000000..ddb39e8ea --- /dev/null +++ b/src/colorsaurus/lib/display.rs @@ -0,0 +1,15 @@ +use std::fmt; + +pub(crate) struct DisplayAsDebug(pub(crate) T); + +impl From for DisplayAsDebug { + fn from(value: T) -> Self { + DisplayAsDebug(value) + } +} + +impl fmt::Debug for DisplayAsDebug { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} diff --git a/src/colorsaurus/src/main.rs b/src/colorsaurus/src/main.rs new file mode 100644 index 000000000..a03f67713 --- /dev/null +++ b/src/colorsaurus/src/main.rs @@ -0,0 +1,27 @@ +//! This example shows how to detect if the terminal uses +//! a dark-on-light or a light-on-dark theme. + +use terminal_colorsaurus::{foreground_color, background_color, Error, QueryOptions}; + +fn main() -> Result<(), display::DisplayAsDebug> { +// let colors = color_palette(QueryOptions::default())?; +// +// let theme = match colors.color_scheme() { +// ColorScheme::Dark => "dark", +// ColorScheme::Light => "light", +// }; + + let fg = foreground_color(QueryOptions::default())?; + let bg = background_color(QueryOptions::default())?; + + println!( + "{:04x}/{:04x}/{:04x};{:04x}/{:04x}/{:04x}", + fg.r, fg.g, fg.b, + bg.r, bg.g, bg.b + ); + + Ok(()) +} + +#[path = "../lib/display.rs"] +mod display; From cf5e570e3bb3e6ad18b1970676fae46bd9c684b3 Mon Sep 17 00:00:00 2001 From: Nuru Date: Wed, 19 Feb 2025 20:14:19 -0800 Subject: [PATCH 2/8] Move high level summary out of PR description --- .coderabbit.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.coderabbit.yaml b/.coderabbit.yaml index 48b806773..d42075b82 100644 --- a/.coderabbit.yaml +++ b/.coderabbit.yaml @@ -5,9 +5,9 @@ enable_free_tier: true reviews: profile: chill request_changes_workflow: false - high_level_summary: true + high_level_summary: false high_level_summary_placeholder: '@coderabbitai summary' - high_level_summary_in_walkthrough: false + high_level_summary_in_walkthrough: true auto_title_placeholder: '@coderabbitai' auto_title_instructions: '' review_status: true From e5e2fd2ca47adb8b0749ae68d9461eff91fa78bd Mon Sep 17 00:00:00 2001 From: Nuru Date: Wed, 19 Feb 2025 20:17:07 -0800 Subject: [PATCH 3/8] Limit terminal theme detection to startup, use library for detection --- rootfs/etc/profile.d/_07-term-mode.sh | 298 ++++++++++++++++---------- rootfs/etc/profile.d/_10-colors.sh | 48 +++-- rootfs/etc/profile.d/prompt.sh | 6 +- 3 files changed, 218 insertions(+), 134 deletions(-) diff --git a/rootfs/etc/profile.d/_07-term-mode.sh b/rootfs/etc/profile.d/_07-term-mode.sh index 4881a73d5..9a2463665 100644 --- a/rootfs/etc/profile.d/_07-term-mode.sh +++ b/rootfs/etc/profile.d/_07-term-mode.sh @@ -6,13 +6,36 @@ # # This file has no dependencies and should come first. -# This function determines if the terminal is in dark mode. +# These function determine if the terminal is in light or dark mode. -# For now, we use OSC sequences to query the terminal's foreground and background colors. -# See https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands -# Adapted from https://bugzilla.gnome.org/show_bug.cgi?id=733423#c2 +# We have 2 different kinds of auto-detection to consider: +# +# 1. The initial auto-detection of the terminal color mode at startup. +# This is very important, because parts of the Geodesic colored prompt +# (among other things) can become invisible if the terminal is dark and +# Geodesic thinks it is light. +# +# 2. The auto-detection of terminal color mode changes during the session. +# This is less important for 2 reasons: +# +# a. There is no standard way for the terminal to notify the shell that the +# color mode has changed. Some terminals support SIGWINCH, but most do not. +# So the best we can do is poll the terminal periodically, which is not ideal, +# especially since it can be slow and can fail, and when it fails it causes +# garbage characters to appear on the terminal as input. +# +# b. The terminal color mode is not likely to change during a session, and if it does, +# the user can manually update the color mode with `update-terminal-theme`. +# +# +# So, the initial detection needs to be enabled by default, and if disabled, needs to set the mode. +# - Disable it by setting GEODESIC_TERM_THEME=light or =dark. +# +# The detection of changes during the session should be disabled by default, and if enabled, should +# be able to be disabled again. +# +# - Enable it by setting GEODESIC_TERM_THEME_AUTO=enabled. # -# At some point we may introduce other methods to determine the terminal's color scheme. # First, at startup, let's try an OSC query. If we get no response, we will assume light mode # and disable further queries. @@ -26,140 +49,117 @@ function _terminal_trace() { function _verify_terminal_queries_are_supported() { local colors + # It is possible that the terminal supports color, but `tput` does not know about it. + # Since we rely on `tput` to modify the colors, if `tput` does not support the terminal, we have to treat it as monochome. colors=$(tput colors 2>/dev/null) || colors=0 if ! { [[ -t 0 ]] && [[ "$colors" -ge 8 ]]; }; then # Do not use _terminal_trace here, because it uses color codes on terminals and we have just verified that is not supported here. [[ $GEODESIC_TRACE =~ "terminal" ]] && echo "* TERMINAL TRACE: Not a (color) terminal. Disabling color detection." >&2 - export GEODESIC_TERM_COLOR_AUTO=unsupported + export GEODESIC_TERM_THEME_AUTO=unsupported return 1 fi - if ! { [[ -w /dev/tty ]] && [[ -r /dev/tty ]]; }; then - _terminal_trace "Terminal is not writable or readable. Skipping color detection." - _terminal_trace "You may need to run 'chmod o+rw /dev/tty' to enable color detection." - _terminal_trace "You can disable color detection with 'export GEODESIC_TERM_COLOR_AUTO=disabled'." + if ! /usr/local/bin/terminal-theme-detector >/dev/null 2>&1; then + _terminal_trace "terminal-theme-detector could not determine terminal color." + export GEODESIC_TERM_THEME_AUTO=unsupported return 1 fi - [[ "${GEODESIC_TERM_COLOR_AUTO}" == "disabled" ]] || unset GEODESIC_TERM_COLOR_AUTO + # The following test is only relevant when we are using _raw_query_term. + # if ! { [[ -w /dev/tty ]] && [[ -r /dev/tty ]]; }; then + # _terminal_trace "Terminal is not writable or readable. Skipping color detection." + # _terminal_trace "You may need to run 'chmod o+rw /dev/tty' to enable color detection." + # _terminal_trace "You can disable color detection with 'export GEODESIC_TERM_COLOR_AUTO=disabled'." + # return 1 + # fi + return 0 } -_verify_terminal_queries_are_supported +[[ "${GEODESIC_TERM_THEME}" == "light" ]] || [[ "${GEODESIC_TERM_THEME}" == "dark" ]] || _verify_terminal_queries_are_supported -# Normally this function produces no output, but with -b, it outputs "true" or "false", -# with -bb it outputs "true", "false", or "unknown". (Otherwise, unknown assume light mode.) -# With -m it outputs "dark" or "light", with -mm it outputs "dark", "light", or "unknown". -# and always returns true. With -l it outputs integer luminance values for foreground -# and background colors. With -ll it outputs labels on the luminance values as well. -function _is_term_dark_mode() { - [[ ${GEODESIC_TERM_COLOR_AUTO} == "unsupported" ]] || [[ ${GEODESIC_TERM_COLOR_AUTO} == "disabled" ]] && case "$1" in - -b) echo "false" ;; - -bb) echo "unknown" ;; - -m) echo "light" ;; - -mm) echo "unknown" ;; - -l) echo "0 1000000000" ;; - -ll) echo "Foreground luminance: 0, Background luminance: 1000000000" ;; - *) return 1 ;; - esac && return 0 - - local x fg_rgb bg_rgb fg_lum bg_lum exit_code saved_state timeout_duration - - # Do not try to auto-detect if we are not in a terminal - # or if termcap does not think we are in a color terminal - if _verify_terminal_queries_are_supported; then - # Extract the RGB values of the foreground and background colors via OSC 10 and 11. - # Redirect output to `/dev/tty` in case we are in a subshell where output is a pipe, - # because this output has to go directly to the terminal. - saved_state=$(stty -g) - trap 'stty "$saved_state"' EXIT - _terminal_trace 'Checking terminal color scheme...' - # Timeout of 2 was not enough when waking for sleep. - # When in a signal handler, we might be waking from sleep or hibernation, so we give it a lot more time. - timeout_duration="0.6" - stty -echo - # Query the terminal for the foreground color. Use printf to ensure the string is output as a single block, - # without interference from other processes writing to the terminal. - printf '\e]10;?\a' >/dev/tty - IFS=: read -rs -t "$timeout_duration" -d $'\a' x fg_rgb /dev/tty - IFS=: read -rs -t "$timeout_duration" -d $'\a' x bg_rgb &2 - printf "\tTerminal automatic light/dark mode detection failed from shell prompt hook. Disabling automatic detection.\n" >&2 - printf "\tYou can manually change modes with\n\n\tupdate-terminal-color-mode [dark|light]\n\n" >&2 - printf "\tYou can re-enable automatic detection with\n\n\tunset GEODESIC_TERM_COLOR_AUTO\n\n" >&2 - printf "################# End Message from Geodesic ##################\n\n" >&2 - echo "auto-detect-failed" - return 9 - fi - - if [[ $exit_code -gt 128 ]] || [[ -z $fg_rgb ]] || [[ -z $bg_rgb ]]; then - _terminal_trace "Terminal did not respond to OSC 10 and 11 queries." - # If we cannot determine the color scheme, we assume light mode for historical reasons. - if [[ "$*" =~ -b ]] || [[ "$*" =~ -m ]]; then - if [[ "$*" =~ -bb ]] || [[ "$*" =~ -mm ]]; then - echo "unknown" - elif [[ "$*" =~ -m ]]; then - echo "light" - else - echo "false" - fi - return 0 # when returning text, always return success + if [[ "${GEODESIC_TERM_THEME}" == "light" ]] || [[ ${GEODESIC_TERM_THEME_AUTO} == "unsupported" ]]; then + if [[ "${GEODESIC_TERM_THEME}" == "light" ]]; then + _terminal_trace "Terminal mode forced to \"light\" by GEODESIC_TERM_THEME" + else + _terminal_trace "Terminal mode color detection is unsupported for this terminal." + _terminal_trace "Function stack is ${FUNCNAME[@]}." fi - return 1 # Assume light mode + echo "0 1000000000" + return fi - - if [[ "${x#*;}" != "rgb" ]]; then - # Always output this error, because we want to hear about - # other color formats users want us to support. - echo "$(tput set bold)$(tput setaf 1)Terminal reported unknown color format: ${x#*;}$(tput sgr0)" >&2 - return 1 + if [[ "${GEODESIC_TERM_THEME}" == "dark" ]]; then + _terminal_trace "Terminal mode forced to \"dark\" by GEODESIC_TERM_THEME" + echo "1000000000 0" + return + fi + if ! IFS=';' read -r -t 3 fg_rgb bg_rgb < <(terminal-theme-detector 2>/dev/null) || [[ -z $fg_rgb ]] || [[ -z $bg_rgb ]]; then + _terminal_trace "Terminal did not respond to color queries." + echo "0 0" + return fi - # Convert the RGB values to luminance by summing the values. + # Convert the RGB values to luminance using colormetric formula. + _terminal_trace "Foreground color: $fg_rgb, Background color: $bg_rgb" fg_lum=$(_srgb_to_luminance "$fg_rgb") bg_lum=$(_srgb_to_luminance "$bg_rgb") - if [[ "$*" =~ -l ]]; then - if [[ "$*" =~ -ll ]]; then - echo "Foreground luminance: $fg_lum, Background luminance: $bg_lum" - else - echo "$fg_lum $bg_lum" - fi - fi - # If the background luminance is less than the foreground luminance, we are in dark mode. - if ((bg_lum < fg_lum)); then - if [[ "$*" =~ -m ]]; then - echo "dark" - elif [[ "$*" =~ -b ]]; then - echo "true" - fi - return 0 + echo "$fg_lum $bg_lum" +} + +# Normally this function produces no output, but with -b, it outputs "true" or "false", +# with -bb it outputs "true", "false", or "unknown". (Otherwise, unknown assumes light mode.) +# With -m it outputs "dark" or "light", with -mm it outputs "dark", "light", or "unknown", +# and always returns true. With -l it outputs integer luminance values for foreground +# and background colors. With -ll it outputs labels on the luminance values as well. +function _is_term_dark_mode() { + local lum=($(_get_terminal_luminance)) + local fg=${lum[0]} bg=${lum[1]} + local theme response + + if [[ $fg -eq $bg ]]; then + theme="unknown" + elif [[ $fg -gt $bg ]]; then + theme="dark" + else + theme="light" fi - # Not in dark mode, must be in light mode. - if [[ "$*" =~ -m ]]; then - echo "light" - elif [[ "$*" =~ -b ]]; then - echo "false" + if [[ $theme == "light" ]]; then + case "$1" in + -b | -bb) response="false" ;; + -m | -mm) response="light" ;; + -l) response="$fg $bg" ;; + -ll) response="Foreground luminance: $fg, Background luminance: $bg" ;; + *) return 1 ;; + esac + elif [[ $theme == "dark" ]]; then + case "$1" in + -b | -bb) response="true" ;; + -m | -mm) response="dark" ;; + -l) response="$fg $bg" ;; + -ll) response="Foreground luminance: $fg, Background luminance: $bg" ;; + *) return 0 ;; + esac else - return 1 + # Default to light for historical compatibility + case "$1" in + -b) response="false" ;; + -bb) response="unknown" ;; + -m) response="light" ;; + -mm) response="unknown" ;; + -l) response="0 1000000000" ;; + -ll) response="Foreground luminance: 0, Background luminance: 1000000000" ;; + *) return 1 ;; + esac fi + echo "$response" } # Converting RGB to luminance is a lot more complex than summing the values. @@ -227,3 +227,75 @@ function _srgb_to_luminance() { # to get an integer and maintain precision. echo "scale=0; ($(echo "scale=10; $luminance * 1000000000" | bc) + 0.5) / 1" | bc } + +# _raw_query_term is a helper function that queries the terminal for the foreground and background colors. +# It uses OSC sequences to query the terminal's foreground and background colors. +# See https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands +# Adapted from https://bugzilla.gnome.org/show_bug.cgi?id=733423#c2 +# However, many terminals do not support OSC, and there are quirks (e.g. with final delimiter character) among those that do. +# See https://github.com/bash/terminal-colorsaurus/blob/main/doc/terminal-survey.md +# +# So we no longer use this, but it is here as a reference. We use the terminal-colorsaurus library +# because it is more thorough and still being maintained. +function _raw_query_term_mode() { + # Extract the RGB values of the foreground and background colors via OSC 10 and 11. + # Redirect output to `/dev/tty` in case we are in a subshell where output is a pipe, + # because this output has to go directly to the terminal. + saved_state=$(stty -g) + trap 'stty "$saved_state"' EXIT + _terminal_trace 'Checking terminal color scheme...' + # Timeout of 2 was not enough when waking for sleep and in a signal handler. + # We moved to the prompt hook, but IDE terminals still can be slow, so we give a generous timeout, + # now that is a rare event. + timeout_duration="2" + stty -echo + # Query the terminal for the foreground color. Use printf to ensure the string is output as a single block, + # without interference from other processes writing to the terminal. + printf '\e]10;?\a' >/dev/tty + IFS=: read -rs -t "$timeout_duration" -d $'\a' x fg_rgb /dev/tty + IFS=: read -rs -t "$timeout_duration" -d $'\a' x bg_rgb &2 + printf "\tTerminal automatic light/dark mode detection failed from shell prompt hook. Disabling automatic detection.\n" >&2 + printf "\tYou can manually change modes with\n\n\tupdate-terminal-theme [dark|light]\n\n" >&2 + printf "\tYou can re-enable automatic detection with\n\n\tunset GEODESIC_TERM_COLOR_AUTO\n\n" >&2 + printf "################# End Message from Geodesic ##################\n\n" >&2 + echo "auto-detect-failed" + return 9 + fi + + if [[ $exit_code -gt 128 ]] || [[ -z $fg_rgb ]] || [[ -z $bg_rgb ]]; then + _terminal_trace "Terminal did not respond to OSC 10 and 11 queries." + # If we cannot determine the color scheme, we assume light mode for historical reasons. + if [[ "$*" =~ -b ]] || [[ "$*" =~ -m ]]; then + if [[ "$*" =~ -bb ]] || [[ "$*" =~ -mm ]]; then + echo "unknown" + elif [[ "$*" =~ -m ]]; then + echo "light" + else + echo "false" + fi + return 0 # when returning text, always return success + fi + return 1 # Assume light mode + fi + + if [[ "${x#*;}" != "rgb" ]]; then + # Always output this error, because we want to hear about + # other color formats users want us to support. + echo "$(tput set bold)$(tput setaf 1)Terminal reported unknown color format: ${x#*;}$(tput sgr0)" >&2 + return 1 + fi +} diff --git a/rootfs/etc/profile.d/_10-colors.sh b/rootfs/etc/profile.d/_10-colors.sh index 2e56f49ab..158a00304 100755 --- a/rootfs/etc/profile.d/_10-colors.sh +++ b/rootfs/etc/profile.d/_10-colors.sh @@ -10,7 +10,7 @@ # The main change is that it uses the terminal's default colors for foreground and background, # whereas the previous version "reset" the color by setting it to black, which fails in dark mode. -function update-terminal-color-mode() { +function update-terminal-theme() { local new_mode="$1" local quiet=false case $new_mode in @@ -26,7 +26,7 @@ function update-terminal-color-mode() { ;; *) - echo "Usage: update-terminal-color-mode [dark|light]" >&2 + echo "Usage: update-terminal-theme [dark|light]" >&2 return 1 ;; esac @@ -50,6 +50,7 @@ function update-terminal-color-mode() { [[ "$quiet" == "true" ]] || echo "Terminal did not respond to color queries." >&2 fi # "light" is historical default for unknown terminals. + [[ "$quiet" == "true" ]] || echo "Terminal theme mode unknown, defaulting to \"light\"." new_mode="light" fi @@ -63,7 +64,18 @@ function update-terminal-color-mode() { fi } -function get-terminal-color-mode() { +# Function for user to manually update the terminal color mode. +function set-terminal-theme() { + if [[ "$1" == "dark" || "$1" == "light" ]]; then + GEODESIC_TERM_THEME="$1" + [[ "$1" == "$(get-terminal-theme)" ]] || update-terminal-theme "$1" + else + unset GEODESIC_TERM_THEME + update-terminal-theme + fi +} + +function get-terminal-theme() { echo "${_geodesic_tput_cache[dark_mode]:-light}" } @@ -97,7 +109,7 @@ function _geodesic_tput_cache_init() { # from here, so we need to tell the user to run the command to fix them. if [[ $BASH_SUBSHELL != 0 ]]; then printf "\n* Terminal mode settings have been lost (%s,%s).\n" "$SHLVL" "$BASH_SUBSHELL" >&2 - printf "* Please run: update-terminal-color-mode \n\n" >&2 + printf "* Please run: update-terminal-theme \n\n" >&2 fi local bold=$(tput bold) @@ -307,17 +319,17 @@ function reset_terminal_colors() { _geodesic_tput_cache_init -function auto-update-terminal-color-mode() { - [[ ${GEODESIC_TERM_COLOR_UPDATING} == "true" ]] || [[ ${GEODESIC_TERM_COLOR_AUTO:-true} != "true" ]] && return 0 +function auto-update-terminal-theme() { + [[ ${GEODESIC_TERM_THEME_UPDATING} == "true" ]] || [[ ${GEODESIC_TERM_THEME_AUTO} != "enabled" ]] && return 0 # Ignore repeated signals while a signal is being processed - export GEODESIC_TERM_COLOR_UPDATING=true - update-terminal-color-mode quiet + export GEODESIC_TERM_THEME_UPDATING=true + update-terminal-theme quiet if [[ $? -eq 9 ]]; then # If the color detection failed, we disable automatic detection. - export GEODESIC_TERM_COLOR_AUTO=disabled + export GEODESIC_TERM_THEME_AUTO=failed fi - unset GEODESIC_TERM_COLOR_UPDATING + unset GEODESIC_TERM_THEME_UPDATING } # Although SIGWINCH is a standard signal to indicate the window *size* has changed, @@ -326,27 +338,27 @@ function auto-update-terminal-color-mode() { # So we catch the signal to update the terminal colors, preserving any existing signal handlers. # However, we do the actual color update in a separate function called from the shell prompt command, # to avoid issues with async access to the TTY and other issues with running inside a signal handler. -function _update-terminal-color-mode-sigwinch() { +function _update-terminal-theme-sigwinch() { # Ignore repeated signals while a signal is being processed - [[ ${GEODESIC_TERM_COLOR_UPDATING} == "true" ]] || [[ ${GEODESIC_TERM_COLOR_AUTO:-true} != "true" ]] && return 0 - export GEODESIC_TERM_COLOR_UPDATING="needed" + [[ ${GEODESIC_TERM_THEME_UPDATING} == "true" ]] || [[ ${GEODESIC_TERM_THEME_AUTO} != "enabled" ]] && return 0 + export GEODESIC_TERM_THEME_UPDATING="needed" } -if [[ ${GEODESIC_TERM_COLOR_AUTO} != "unsupported" ]] && _is_color_term; then - # We install the trap handler whether GEODESIC_TERM_COLOR_AUTO is set to "disabled" or "false", +if [[ ${GEODESIC_TERM_THEME_AUTO} != "unsupported" ]] && _is_color_term; then + # We install the trap handler whether or not GEODESIC_TERM_THEME_AUTO is enabled, # because we will not be able to detect the change in that variable if # it started out disabled and then someone enables it. # Save existing trap (if any) existing_trap=$(trap -p WINCH) - # Set up new trap that runs both the existing trap and the update-terminal-color-mode function + # Set up new trap that runs both the existing trap and the update-terminal-theme function if [ -n "$existing_trap" ]; then # Extract the existing command from the trap output existing_cmd=$(echo "$existing_trap" | sed "s/trap -- '\(.*\)' SIGWINCH/\1/") - trap "${existing_cmd}; _update-terminal-color-mode-sigwinch" WINCH + trap "${existing_cmd}; _update-terminal-theme-sigwinch" WINCH else - trap _update-terminal-color-mode-sigwinch WINCH + trap _update-terminal-theme-sigwinch WINCH fi unset existing_trap existing_cmd diff --git a/rootfs/etc/profile.d/prompt.sh b/rootfs/etc/profile.d/prompt.sh index 96fb28318..7e8c3284b 100755 --- a/rootfs/etc/profile.d/prompt.sh +++ b/rootfs/etc/profile.d/prompt.sh @@ -51,12 +51,12 @@ function reload() { echo "* Screen resized to ${current_screen_size}" export SCREEN_SIZE=${current_screen_size} # Use this opportunity to see if the terminal color mode has changed - export GEODESIC_TERM_COLOR_UPDATING="needed" + [[ ${GEODESIC_TERM_THEME_AUTO} == "enabled" ]] && export GEODESIC_TERM_THEME_UPDATING="needed" # Instruct shell that window size has changed to ensure lines wrap correctly kill -WINCH $$ fi - if [[ $GEODESIC_TERM_COLOR_UPDATING == "needed" ]]; then - auto-update-terminal-color-mode + if [[ $GEODESIC_TERM_THEME_UPDATING == "needed" ]]; then + auto-update-terminal-theme fi } From 2b983005c1b5790de2c636e8102719332490c28f Mon Sep 17 00:00:00 2001 From: Nuru Date: Wed, 19 Feb 2025 20:17:46 -0800 Subject: [PATCH 4/8] Match docker shells to wrapper shells. Set envs per shell. --- .../profile.d/_60-register-stray-shells.sh | 23 ++ rootfs/templates/wrapper-body.sh | 254 +++++++++++------- rootfs/usr/local/sbin/list-wrapper-shells | 19 ++ rootfs/usr/local/sbin/shell-monitor | 74 ++++- 4 files changed, 261 insertions(+), 109 deletions(-) create mode 100644 rootfs/etc/profile.d/_60-register-stray-shells.sh create mode 100755 rootfs/usr/local/sbin/list-wrapper-shells diff --git a/rootfs/etc/profile.d/_60-register-stray-shells.sh b/rootfs/etc/profile.d/_60-register-stray-shells.sh new file mode 100644 index 000000000..9d14e9a2d --- /dev/null +++ b/rootfs/etc/profile.d/_60-register-stray-shells.sh @@ -0,0 +1,23 @@ +# Files in the profile.d directory are executed by the lexicographical order of their file names. +# This file is named _50-workspace.sh. The leading underscore is needed to ensure this file +# executes before other files that may depend on it. +# The number portion is to ensure proper ordering among the high-priority scripts. + +# This file depends on colors for colored output and must come after it. + +# We track shells launched by `docker exec` so that we can shut down the container when they exit, +# and so that we can signal them to quit when the container is shutting down. +# +# The wrapper script that launches the shell sets the environment variable `G_HOST_PID` to the PID of the +# shell process launching the shell, so it can track it when it exist. +# +# This script detects shells launched outside the wrapper and gives them a G_HOST_PID=0 so that they get tracked. +# + +# If the parent process ID ($PPID) is zero (0), then this shell was launched by Docker exec. +# If $$ = 1, then it was the shell launched when the container started, and we do not need to track it. + +if [[ $$ != 1 ]] && [[ $PPID == 0 ]] && [[ -z $G_HOST_PID ]]; then + export G_HOST_PID=0 + [[ -t 0 ]] && yellow '# Detected shell launched by `docker exec` without wrapper info, tracking as stray shell.' +fi diff --git a/rootfs/templates/wrapper-body.sh b/rootfs/templates/wrapper-body.sh index 82b7a4de1..799d7512e 100755 --- a/rootfs/templates/wrapper-body.sh +++ b/rootfs/templates/wrapper-body.sh @@ -117,6 +117,12 @@ function parse_args() { -v | --verbose) export VERBOSE=true ;; + --dark) + export GEODESIC_TERM_THEME="dark" + ;; + --light) + export GEODESIC_TERM_THEME="light" + ;; --solo) export ONE_SHELL=true ;; @@ -173,21 +179,24 @@ function parse_args() { } function help() { - echo "Usage: $0 [target] [options] [ARGS]" + echo "Usage: $0 [options | command] [ARGS]" echo "" - echo " Targets:" - echo " | use Enter into a shell, passing ARGS to the shell" + echo " commands:" + echo " | use Enter into a shell, passing ARGS to the shell" echo " help Show this help" echo " stop [container-name] Stop a running Geodesic container" echo "" - echo " Options:" + echo " Options when no command is supplied:" + echo " --dark Disable terminal color detection and set dark terminal theme" + echo " --light Disable terminal color detection and set light terminal theme" + echo " -h --help Show this help" echo " --no-custom Disable loading of custom configuration" echo " --no-motd Disable the MOTD" echo " --solo Launch a new container exclusively for this shell" echo " --no-solo Override the 'solo/ONE_SHELL' setting in your configuration" echo " --trace Enable tracing of shell customization within Geodesic" echo " --trace= Enable tracing of specific parts of shell configuration" - echo " -v --verbose Enable verbose output of launch configuration" + echo " -v --verbose Enable tracing of launch configuration outside of Geodesic" echo "" echo " trace options can be any of:" echo " custom Trace the loading of custom configuration in Geodesic" @@ -196,7 +205,10 @@ function help() { echo " You can specify multiple modes, separated by commas, e.g. --trace=custom,hist" echo "" echo " Options that only take effect when starting a container:" - echo " --workspace Set which host directory is used as the working directory in the container " + echo " --workspace Set which host directory is used as the working directory in the container" + echo "" + echo " You can also set environment variables with --=," + echo " but most are only effective when starting a container." echo "" } @@ -232,10 +244,17 @@ function debug() { fi } +function _running_shell_pids() { + docker exec "${DOCKER_NAME}" list-wrapper-shells 2>/dev/null +} + +function _our_shell_pid() { + docker exec "${DOCKER_NAME}" list-wrapper-shells "$WRAPPER_PID" 2>/dev/null || true +} + function _running_shell_count() { - local count=$(docker exec "${DOCKER_NAME}" pgrep -f "^/bin/(ba)?sh -l" 2>/dev/null | wc -l | tr -d " " || true) - [ -n "${count}" ] || count=0 - echo "${count}" + local count=($(_running_shell_pids || true)) + echo "${#count[@]}" } function _on_shell_exit() { @@ -249,6 +268,41 @@ function _on_container_exit() { [ -n "${ON_CONTAINER_EXIT}" ] && command -v "${ON_CONTAINER_EXIT}" >/dev/null && "${ON_CONTAINER_EXIT}" } +# Call this function to wait for the container to exit, after all other shells have exited. +function wait_for_container_exit() { + local i n shells + n=15 + + for i in {0..$n}; do + # Try n times to see if the container is still running, quit when it is no longer found + if [ -z "$(docker ps -q --filter "id=${CONTAINER_ID:0:12}")" ]; then + i=0 + break + fi + + # Wait for our shell to quit, regardless, because new shells might not be found until triggered by our shell quitting. + if [ -z "$(_our_shell_pid)" ] && [ "$(_running_shell_count)" -gt 0 ]; then + printf 'New shells started from other sources, docker container still running.\n' >&2 + printf 'Use `%s stop` to stop container gracefully, or\n force quit with `docker kill %s`\n' "$(basename $0)" "${DOCKER_NAME}" >&2 + _on_shell_exit + return 7 + fi + + [ $i -eq $n ] && break || sleep 0.4 + done + + if [ $i -eq $n ]; then + printf 'All shells terminated, but docker container still running.\n' >&2 + printf 'Forcibly kill it with:\n\n docker kill %s\n\n' "${DOCKER_NAME}" >&2 + _on_shell_exit + return 6 + else + echo Docker container exited >&2 + _on_container_exit + return 0 + fi +} + function run_exit_hooks() { # This runs as soon as the terminal is detached. It may take moments for the shell to actually exit. # It can then take at least a second for the init process to quit. @@ -261,54 +315,81 @@ function run_exit_hooks() { return 0 fi - # Initial count of running shells - shells=$(_running_shell_count) - if [ "$shells" -gt 1 ]; then - # Even if our shell is included in the count, we know there are extra shells running. - # Wait for our shell to quit and count again - sleep 1 - shells=$(_running_shell_count) - if [ "$shells" -eq 0 ]; then # coincidence, other shells quit too - echo Other shells quit, too, and Docker container exited - _on_container_exit - return 0 - fi - else # 1 or zero shells. The 1 might be ours, so we wait for it to quit. - for i in {1..6}; do - if [ $i -eq 6 ] || [ $(docker ps -q --filter "id=${CONTAINER_ID:0:12}" | wc -l | tr -d " ") -eq 0 ]; then - break - fi - [ $i -lt 5 ] && sleep 1 + local our_shell_pid=$(_our_shell_pid) + local shell_pids=($(_running_shell_pids)) + + # Best case scenario: no shells running + if [ "${#shell_pids[@]}" -eq 0 ]; then + wait_for_container_exit + return $? + fi + + # Are other shells running? + if [ -n "$our_shell_pid" ]; then + # remove our shell from the list + shell_pids=($(printf "%s\n" "${shell_pids[@]}" | grep -v "^$our_shell_pid\$")) + fi + + local shells=${#shell_pids[@]} + # Great, other shells running, so we do not have to track ours + if [ "$shells" -gt 0 ]; then + printf "Docker container still running. " >&2 + [ "$shells" -eq 1 ] && echo -n "Quit 1 other shell " >&2 || echo -n "Quit $shells other shells " >&2 + printf 'to terminate.\n Use `%s stop` to stop gracefully, or\n force quit with `docker kill %s`\n' "$(basename $0)" "${DOCKER_NAME}" >&2 + _on_shell_exit + return 0 + fi + + # No other shells running, so we wait for our shell and the container to exit. + # Our shell PID will disappear when the shell exits or when the container exits. + local i n + n=15 + if [ -n "$(_our_shell_pid)" ]; then + echo -n "Waiting for our shell to finish exiting..." >&2 + i=0 + sleep 0.3 + while [ -n "$(_our_shell_pid)" ]; do + i=$((i + 1)) + [ $i -lt $n ] && sleep 0.4 || break done - if [ $i -eq 6 ]; then - shells=$(_running_shell_count) - if [ "$shells" -eq 0 ]; then - printf 'All shells terminated, but docker container still running.\n' >&2 - printf 'Forcibly kill it with:\n\n docker kill %s\n\n' "${DOCKER_NAME}" >&2 - _on_shell_exit - return 6 - fi - else - echo Docker container exited - _on_container_exit - return 0 - fi + [ $i -lt $n ] && echo " Finished." >&2 || printf "\nTimeout waiting for container shell to exit.\n" >&2 fi - # If we get here, container is still running and shells != 0 - printf "Docker container still running. " >&2 - [ "$shells" -eq 1 ] && echo -n "Quit 1 other shell " >&2 || echo -n "Quit $shells other shells " >&2 - printf 'to terminate.\n Use `%s stop` to stop gracefully, or\n force quit with `docker kill %s`\n' "$(basename $0)" "${DOCKER_NAME}" >&2 - _on_shell_exit + wait_for_container_exit + return $? } function use() { - # TODO: Mark each shell with the wrapper's PID, so we can tell which shell is which. - # Then, when exiting, we can distinguish between this wrapper's shell still running - # and other shells still running, and be more efficient and informative in the exit message. [ "$1" = "use" ] && shift trap run_exit_hooks EXIT + export WRAPPER_PID=$$ + + DOCKER_EXEC_ARGS=(--env LS_COLORS --env TERM --env TERM_COLOR --env TERM_PROGRAM) + # Some settings from the host environment need to propagate into the container + # Set them explicitly so they do not have to be exported in `launch-options.sh` + for v in GEODESIC_HOST_CWD GEODESIC_CONFIG_HOME GEODESIC_MOTD_ENABLED GEODESIC_TERM_THEME GEODESIC_TERM_THEME_AUTO; do + # Test if variable is set in a way that works on bash 3.2, which is what macOS has. + if [ -n "${!v+x}" ]; then + DOCKER_EXEC_ARGS+=(--env "$v=${!v}") + fi + done + + if [[ ${GEODESIC_CUSTOMIZATION_DISABLED-false} == false ]]; then + if [ -n "${GEODESIC_TRACE}" ]; then + DOCKER_EXEC_ARGS+=(--env GEODESIC_TRACE) + fi + + if [ -n "${ENV_FILE}" ]; then + DOCKER_EXEC_ARGS+=(--env-file ${ENV_FILE}) + fi + else + echo "# Disabling user customizations: GEODESIC_CUSTOMIZATION_DISABLED is set and not 'false'" + DOCKER_EXEC_ARGS+=(--env GEODESIC_CUSTOMIZATION_DISABLED) + fi + + # If ONE_SHELL is false and a container is already running, exec into it with the configuration we have. + # We do not need the rest of the configuration, which is for launching a new container. if [ "$ONE_SHELL" != "true" ]; then CONTAINER_ID=$(docker ps --filter name="^/${DOCKER_NAME}\$" --format '{{ .ID }}') if [ -n "$CONTAINER_ID" ]; then @@ -316,29 +397,19 @@ function use() { if [ $# -eq 0 ]; then set -- "/bin/bash" "-l" "$@" fi + [ -t 0 ] && DOCKER_EXEC_ARGS+=(-it) # We set unusual detach keys because (a) the default first char is ctrl-p, which is used for command history, # and (b) if you detach from the shell, there is no way to reattach to it, so we want to effectively disable detach. - docker exec -it --detach-keys "ctrl-^,ctrl-[,ctrl-@" --env GEODESIC_HOST_CWD="${GEODESIC_HOST_CWD}" "${DOCKER_NAME}" $* + docker exec --env G_HOST_PID=$WRAPPER_PID --detach-keys "ctrl-^,ctrl-[,ctrl-@" "${DOCKER_EXEC_ARGS[@]}" "${DOCKER_NAME}" $* return 0 fi fi - DOCKER_ARGS=() - if [ -t 1 ]; then - # Running in terminal - DOCKER_ARGS+=(-it --rm --env LS_COLORS --env TERM --env TERM_COLOR --env TERM_PROGRAM --env GEODESIC_MOTD_ENABLED) - if [ -n "$SSH_AUTH_SOCK" ]; then - DOCKER_ARGS+=(--volume /run/host-services/ssh-auth.sock:/run/host-services/ssh-auth.sock - -e SSH_AUTH_SOCK=/run/host-services/ssh-auth.sock) - fi - # Some settings from the host environment need to propagate into the container - # Set them explicitly so they do not have to be exported in `launch-options.sh` - for v in GEODESIC_CONFIG_HOME GEODESIC_MOTD_ENABLED GEODESIC_TERM_COLOR_AUTO; do - # Test if variable is set in a way that works on bash 3.2, which is what macOS has. - if [ -n "${!v+x}" ]; then - DOCKER_ARGS+=(--env "$v=${!v}") - fi - done + DOCKER_LAUNCH_ARGS=(--rm) + + if [ -n "$SSH_AUTH_SOCK" ]; then + DOCKER_LAUNCH_ARGS+=(--volume /run/host-services/ssh-auth.sock:/run/host-services/ssh-auth.sock + -e SSH_AUTH_SOCK=/run/host-services/ssh-auth.sock) fi if [ "${WITH_DOCKER}" == "true" ]; then @@ -348,26 +419,13 @@ function use() { # socket in the Mac host OS, it is in the dockerd VM. # https://docs.docker.com/docker-for-mac/osxfs/#namespaces echo "# Enabling docker support. Be sure you install a docker CLI binary${docker_install_prompt}." - DOCKER_ARGS+=(--volume "/var/run/docker.sock:/var/run/docker.sock") + DOCKER_LAUNCH_ARGS+=(--volume "/var/run/docker.sock:/var/run/docker.sock") # NOTE: bind mounting the docker CLI binary is no longer recommended and usually does not work. # Use a docker image with a docker CLI binary installed that is appropriate to the image's OS. fi - if [[ ${GEODESIC_CUSTOMIZATION_DISABLED-false} == false ]]; then - if [ -n "${GEODESIC_TRACE}" ]; then - DOCKER_ARGS+=(--env GEODESIC_TRACE) - fi - - if [ -n "${ENV_FILE}" ]; then - DOCKER_ARGS+=(--env-file ${ENV_FILE}) - fi - else - echo "# Disabling user customizations: GEODESIC_CUSTOMIZATION_DISABLED is set and not 'false'" - DOCKER_ARGS+=(--env GEODESIC_CUSTOMIZATION_DISABLED) - fi - if [ -n "${DOCKER_DNS}" ]; then - DOCKER_ARGS+=("--dns=${DOCKER_DNS}") + DOCKER_LAUNCH_ARGS+=("--dns=${DOCKER_DNS}") fi # Mount the user's home directory into the container @@ -412,7 +470,7 @@ function use() { else echo "# Enabling explicit mapping of file owner and group ID between container and host." mount_dir="/.FS_HOST" - DOCKER_ARGS+=( + DOCKER_LAUNCH_ARGS+=( --env GEODESIC_HOST_UID="${USER_ID}" --env GEODESIC_HOST_GID="${GROUP_ID}" --env GEODESIC_BINDFS_OPTIONS @@ -423,13 +481,13 @@ function use() { # Although we call it "dirs", it can be files too export GEODESIC_HOMEDIR_MOUNTS="" - DOCKER_ARGS+=(--env GEODESIC_HOMEDIR_MOUNTS --env LOCAL_HOME="${local_home}") + DOCKER_LAUNCH_ARGS+=(--env GEODESIC_HOMEDIR_MOUNTS --env LOCAL_HOME="${local_home}") [ -z "${HOMEDIR_MOUNTS+x}" ] && HOMEDIR_MOUNTS=("${homedir_default_mounts[@]}") IFS=, read -ra HOMEDIR_MOUNTS <<<"${HOMEDIR_MOUNTS}" IFS=, read -ra HOMEDIR_ADDITIONAL_MOUNTS <<<"${HOMEDIR_ADDITIONAL_MOUNTS}" for dir in "${HOMEDIR_MOUNTS[@]}" "${HOMEDIR_ADDITIONAL_MOUNTS[@]}"; do if [ -d "${local_home}/${dir}" ] || [ -f "${local_home}/${dir}" ]; then - DOCKER_ARGS+=(--volume="${local_home}/${dir}:${mount_dir}${local_home}/${dir}") + DOCKER_LAUNCH_ARGS+=(--volume="${local_home}/${dir}:${mount_dir}${local_home}/${dir}") GEODESIC_HOMEDIR_MOUNTS+="${dir}|" debug "Mounting '${local_home}/${dir}' into container'" else @@ -482,13 +540,13 @@ function use() { echo "# Mounting '${WORKSPACE_MOUNT_HOST_DIR}' into container at '${WORKSPACE_MOUNT}'" echo "# Setting container working directory to '${WORKSPACE_FOLDER}'" - DOCKER_ARGS+=( + DOCKER_LAUNCH_ARGS+=( --volume="${WORKSPACE_MOUNT_HOST_DIR}:${mount_dir}${WORKSPACE_MOUNT_HOST_DIR}" --env WORKSPACE_MOUNT_HOST_DIR="${WORKSPACE_MOUNT_HOST_DIR}" --env WORKSPACE_MOUNT="${WORKSPACE_MOUNT}" --env WORKSPACE_FOLDER="${WORKSPACE_FOLDER}" ) - [ -n "${GEODESIC_HOST_SYMLINK}" ] && DOCKER_ARGS+=(--env GEODESIC_HOST_SYMLINK) + [ -n "${GEODESIC_HOST_SYMLINK}" ] && DOCKER_LAUNCH_ARGS+=(--env GEODESIC_HOST_SYMLINK) # Mount the host mounts wherever the users asks for them to be mounted. # However, if file ownership mapping is enabled, @@ -501,11 +559,11 @@ function use() { d="${dir%%:*}" if [ -d "${d}" ] || [ -f "${d}" ]; then if [ "${dir}" != "${d}" ]; then - DOCKER_ARGS+=(--volume="${d}:${mount_dir}${dir#*:}") + DOCKER_LAUNCH_ARGS+=(--volume="${d}:${mount_dir}${dir#*:}") debug "Mounting ${d} into container at ${dir#*:}" GEODESIC_HOST_MOUNTS+="${dir#*:}|" else - DOCKER_ARGS+=(--volume="${d}:${mount_dir}${d}") + DOCKER_LAUNCH_ARGS+=(--volume="${d}:${mount_dir}${d}") debug "Mounting ${d} into container at ${d}" GEODESIC_HOST_MOUNTS+="${d}|" fi @@ -514,12 +572,12 @@ function use() { fi done - DOCKER_ARGS+=(--env GEODESIC_HOST_MOUNTS) + DOCKER_LAUNCH_ARGS+=(--env GEODESIC_HOST_MOUNTS) - #echo "Computed DOCKER_ARGS:" - #printf " %s\n" "${DOCKER_ARGS[@]}" + #echo "Computed DOCKER_LAUNCH_ARGS:" + #printf " %s\n" "${DOCKER_LAUNCH_ARGS[@]}" - DOCKER_ARGS+=( + DOCKER_LAUNCH_ARGS+=( --privileged --publish ${GEODESIC_PORT}:${GEODESIC_PORT} --rm @@ -527,7 +585,6 @@ function use() { --env DOCKER_IMAGE="${DOCKER_IMAGE%:*}" --env DOCKER_NAME="${DOCKER_NAME}" --env DOCKER_TAG="${DOCKER_TAG}" - --env GEODESIC_HOST_CWD="${GEODESIC_HOST_CWD}" ) if [ "$ONE_SHELL" = "true" ]; then @@ -535,15 +592,22 @@ function use() { echo "# Starting single shell ${DOCKER_NAME} session from ${DOCKER_IMAGE}" echo "# Exposing port ${GEODESIC_PORT}" [ -z "${GEODESIC_DOCKER_EXTRA_ARGS}" ] || echo "# Launching with extra Docker args: ${GEODESIC_DOCKER_EXTRA_ARGS}" - docker run --name "${DOCKER_NAME}" "${DOCKER_ARGS[@]}" ${GEODESIC_DOCKER_EXTRA_ARGS} ${DOCKER_IMAGE} -l $* + docker run --name "${DOCKER_NAME}" "${DOCKER_LAUNCH_ARGS[@]}" "${DOCKER_EXEC_ARGS[@]}" ${GEODESIC_DOCKER_EXTRA_ARGS} ${DOCKER_IMAGE} -l $* else echo "# Running new ${DOCKER_NAME} container from ${DOCKER_IMAGE}" echo "# Exposing port ${GEODESIC_PORT}" [ -z "${GEODESIC_DOCKER_EXTRA_ARGS}" ] || echo "# Launching with extra Docker args: ${GEODESIC_DOCKER_EXTRA_ARGS}" # docker run "${DOCKER_ARGS[@]}" ${GEODESIC_DOCKER_EXTRA_ARGS} ${DOCKER_IMAGE} -l $* - CONTAINER_ID=$(docker run --detach --init --name "${DOCKER_NAME}" "${DOCKER_ARGS[@]}" ${GEODESIC_DOCKER_EXTRA_ARGS} ${DOCKER_IMAGE} /usr/local/sbin/shell-monitor) + CONTAINER_ID=$(docker run --detach --init --name "${DOCKER_NAME}" "${DOCKER_LAUNCH_ARGS[@]}" "${DOCKER_EXEC_ARGS[@]}" ${GEODESIC_DOCKER_EXTRA_ARGS} ${DOCKER_IMAGE} /usr/local/sbin/shell-monitor) echo "# Started session ${CONTAINER_ID:0:12}. Starting shell via \`docker exec\`..." - docker exec -it --detach-keys "ctrl-^,ctrl-[,ctrl-@" --env GEODESIC_HOST_CWD="${GEODESIC_HOST_CWD}" "${DOCKER_NAME}" /bin/bash -l $* + if [ $# -eq 0 ]; then + set -- "/bin/bash" "-l" "$@" + fi + + [ -t 0 ] && DOCKER_EXEC_ARGS+=(-it) + # We set unusual detach keys because (a) the default first char is ctrl-p, which is used for command history, + # and (b) if you detach from the shell, there is no way to reattach to it, so we want to effectively disable detach. + docker exec --env G_HOST_PID=$$ --detach-keys "ctrl-^,ctrl-[,ctrl-@" "${DOCKER_EXEC_ARGS[@]}" "${DOCKER_NAME}" $* fi true } diff --git a/rootfs/usr/local/sbin/list-wrapper-shells b/rootfs/usr/local/sbin/list-wrapper-shells new file mode 100755 index 000000000..8cddfd0f6 --- /dev/null +++ b/rootfs/usr/local/sbin/list-wrapper-shells @@ -0,0 +1,19 @@ +#!/bin/bash + +# Find all processes with PPID 0 that have HOST_PID set and print their PIDs. +# This spawns a lot of subshells, and is too expensive to run every second. +# With an argument, lists only the shell(s) that have the given G_HOST_PID. +function list-wrapper-shells() { + local pattern + pattern="^G_HOST_PID=${1:-[0-9]\+}\$" + for pid in $(ps -eo pid,ppid | awk '$2 == 0 {print $1}'); do + [[ $pid -eq 1 ]] && continue # Ignore the shell monitor + if grep -zq "$pattern" /proc/$pid/environ 2>/dev/null; then + echo $pid + fi + done +} + +# If this script is being sourced, do not execute the function +# If it is being executed, run the expensive function, as it is likely to have better failure modes +(return 0 2>/dev/null) && sourced=1 || list-wrapper-shells "$@" diff --git a/rootfs/usr/local/sbin/shell-monitor b/rootfs/usr/local/sbin/shell-monitor index fa7bfaab6..77c890b19 100755 --- a/rootfs/usr/local/sbin/shell-monitor +++ b/rootfs/usr/local/sbin/shell-monitor @@ -1,47 +1,93 @@ #!/bin/bash +source /usr/local/sbin/list-wrapper-shells + +wrapper_pids=() + # Function to count active shell sessions launched by wrapper -# Far from perfect: -# Does not account for shells with detached sessions which cannot be resumed -# Does not account for shells launched without -l flag in the first position -# Does not account for alternative shells like rbash or dash +# Expensive, so run sparingly. Only needed to find new shells when they launch. count_shells() { - pgrep -f "^(/(usr/)?bin/)?(ba)?sh -l" | wc -l + wrapper_pids=($(list-wrapper-shells)) + echo "${#wrapper_pids[@]}" +} + +# Function to check if any registered shell has exited. +# Super efficient, so run as often as needed. +shell_has_exited() { + local pid + + # If there are no shells left, then they have all exited. + [[ "${#wrapper_pids[@]}" -eq 0 ]] && return 0 + + # Check if each shell we know about is still running + for pid in "${wrapper_pids[@]}"; do + if ! [[ -d /proc/$pid ]]; then + return 0 + fi + done + return 1 } +# Function to kill all active shell sessions launched by wrapper. +# This is the shutdown procedure, so we do not care about hogging the CPU. kill_shells() { - pkill -HUP -f "^(/(usr/)?bin/)?(ba)?sh -l" + for pid in $(list_wrapper_shells); do + kill -HUP $pid + done + for i in {1..4}; do [ $(count_shells) -eq 0 ] && return 0 sleep 1 done - pkill -TERM -f "^(/(usr/)?bin/)?(ba)?sh -l" + for pid in $(list_wrapper_shells); do + kill -TERM $pid + done for i in {1..3}; do [ $(count_shells) -eq 0 ] && return 0 sleep 1 done - pkill -KILL -f "^(/(usr/)?bin/)?(ba)?sh -l" + + for pid in $(list_wrapper_shells); do + kill -KILL $pid + done + return 137 } trap 'kill_shells; exit $?' TERM HUP INT QUIT EXIT -# Wait up to 4 seconds for the first connection +# Wait up to 60 seconds for the first connection +# Since we are waiting for something to happen, we can afford burn +# up some CPU in order to be more responsive. i=0 while [ $(count_shells) -eq 0 ]; do - sleep 1 + sleep 0.5 i=$((i + 1)) - if [ $i -ge 4 ]; then - echo "No shell sessions detected after 4 seconds, exiting..." + if [ $i -ge 120 ]; then + echo "No shell sessions detected after 60 seconds, exiting..." >&2 exit 1 fi done +# Our goal here is to stay alive as long as a shell is running, +# but to exit as soon as all shells have exited. +# The mistakes to avoid are quitting while a shell is still running or +# not quitting after all shells have exited. +# An annoyance to avoid is to take a noticeable amount of time to detect that all shells have exited. + +# We do not really care how many shells are running, only if there are any running. +# Finding new shell sessions is too expensive to do every second while a shell is running, +# so we only check for new shells after detecting a shell we know about has exited. +# + # Monitor shell sessions and exit when none remain -while [ $(count_shells) -gt 0 ]; do - sleep 1 +while true; do + while ! shell_has_exited; do + sleep 0.67 + done + [[ $(count_shells) -eq 0 ]] && break done # Clean up and exit From d86f0eaf5e8ff41ed11e88e5d277f57bd871c969 Mon Sep 17 00:00:00 2001 From: Nuru Date: Wed, 19 Feb 2025 20:18:54 -0800 Subject: [PATCH 5/8] Update documentaion --- ReleaseNotes-v4.md | 113 ++++++++++++++++++++---------- docs/environment.md | 10 +-- rootfs/etc/profile.d/terraform.sh | 9 ++- 3 files changed, 89 insertions(+), 43 deletions(-) diff --git a/ReleaseNotes-v4.md b/ReleaseNotes-v4.md index b504606d6..0e205fd18 100644 --- a/ReleaseNotes-v4.md +++ b/ReleaseNotes-v4.md @@ -244,6 +244,16 @@ or you can replace it with the following, which works with both Geodesic v3 and either `$HOME`, `$WORKSPACE_MOUNT`, or `$WORKSPACE_FOLDER` as appropriate. As a temporary workaround, you can run `ln -s "$LOCAL_HOME" /localhost` in your customizations. +- By default, Geodesic automatically sets `TF_PLUGIN_CACHE_DIR` to `"${HOME}/.terraform.d/plugin-cache"` + to provide a location for the [Terraform Provider Plugin Cache](https://developer.hashicorp.com/terraform/cli/config/config-file#provider-plugin-cache). + This is a location on the host filesystem, allowing the cache to be shared between the host and the container, + and to persist between container runs. This is valuable saver of time and bandwidth, and we highly recommend + using the plugin cache, which is why we have been setting it for years. + However, the way this was implemented, it was difficult for users to opt-out of the plugin cache altogether. + To accommodate those users while maintaining backward compatibility, + Geodesic now checks, and if `TF_PLUGIN_CACHE_DIR` is set empty, or to "false" or "disabled", + Geodesic unsets the variable, allowing users to avoid having a cache directory. + - Previously, you could have Geodesic perform file ownership mapping between host and container by setting `GEODESIC_HOST_BINDFS_ENABLED=true`; this variable is now deprecated. Use `MAP_FILE_OWNERSHIP=true` instead. This feature is disabled by default and can @@ -528,7 +538,7 @@ the directories being searched. #### New Customization Command-Line Options -3 command line options regarding customization have been added: +Some line options regarding customization have been added: 1. `--no-custom` (or `--no-customization`, or `--geodesic-customization-disabled`) will disable all user-specific customizations. This is equivalent to setting `GEODESIC_CUSTOMIZATION_DISABLED=true`. This is useful for @@ -538,6 +548,8 @@ the directories being searched. 3. `--trace="custom terminal hist` will enable tracing of the customizations, terminal configuration (mainly with respect to light and dark mode support), and determining which Bash history file to use, respectively. You can use these options in any combination, for example, `--trace="hist"`. +4. `--dark` and `--light` will set the terminal theme to dark or light, respectively, disabling attempts to automatically detect it. + This is mainly useful if the automatic detection is not working. ## Dark mode support @@ -546,49 +558,69 @@ Support for terminals being in dark mode was introduced in Geodesic v2.10.0, but was not previously well documented. There have also been some enhancements since then. The following describes the state of support as of v4.0.0. -### Switching between light and dark mode +### Switching between light and dark themes -Geodesic provides basic support for terminal dark and light modes. +Geodesic provides basic support for terminal dark and light themes. Primarily, this is used to ensure Geodesic's colored output is readable in both modes, for example, black in light mode and white in dark mode. -There is no standard way to be notified of a terminal's color mode change. Geodesic -listens for SIGWINCH and updates the color mode when receiving it. Some terminals -send this when the color mode changes, but not all do. (For example, macOS Terminal does not.) - -There can be issues with the signal handler. For example, if your computer is -waking from sleep, the signal handler may be called multiple times, but -the terminal may take several seconds to respond to the query about its color mode. -This can result in long delays while Geodesic waits for the terminal to respond, -and if it times out, the response may eventually be written to the terminal -command line, looking something like `10;rgb:0000/0000/000011;rgb:ffff/ffff/ffff`. -This area of Geodesic is still new and under development, so there are likely to be subtle bugs. -If you want to disable this feature, you can set `GEODESIC_TERM_COLOR_AUTO=false`. -If Geodesic detects a problem with the terminal color mode, it will disable this feature -by setting `GEODESIC_TERM_COLOR_AUTO=disabled`. - -You can report issues with this, or any Geodesic feature, via the `#geodesic` -channel in the [Cloud Posse Slack workspace](https://cpco.io/slack?utm_source=github&utm_medium=release_notes&utm_campaign=cloudposse/geodesic&utm_content=slack). - -Geodesic provides a shell function called `update-terminal-color-mode` that can be used to manually -update the terminal mode. This function is called automatically when Geodesic starts, but -if you change the terminal color mode while Geodesic is running, you can call this function -to update the color mode. If your terminal supports calling a function when the color mode changes, -you can call this function from there. Alternately, you can trigger the function call -by resizing the terminal window, which triggers the SIGWINCH signal handler. - -The `update-terminal-color-mode` function takes one argument, which is the terminal color mode, -either `light` or `dark`. If you do not provide an argument, it will attempt to determine -the terminal color mode itself. - -You can query Geodesic for its cached color mode setting by running `get-terminal-color-mode`. - -Changing Geodesic's color mode does not change anything already on the screen. It only affects +While there are standard ways to detect the terminal color settings, they are not +universally supported, and various terminals have their quirks. As a result, color +detection is not always reliable, and you may see errors or 'garbage' output +during startup, especially if you are not using a widely popular terminal. + +#### Initial theme detection + +It is critical to detect the terminal theme when Geodesic starts. This is done +by default, and can only be disabled by specifying the desired theme, +"light" or "dark", via `--light` or `--dark` command line options, +or by setting the GEODESIC_TERM_THEME environment variable to "light" or "dark". This +must be set in the environment before Geodesic starts, e.g. in the `launch-options.sh` file. + +#### Detecting changes in theme while running (experimental) + +You may have your terminal set to automatically switch between light and dark themes +according to the time of day, or you may manually switch between themes. Unfortunately, +there is no standard way to be notified of a terminal's theme change. Furthermore, +because of the general issues around detecting the terminal theme, automatic +detection of changes in the terminal theme has too often injected disruption +into the user's workflow. As a result, Geodesic by default does not attempt to detect changes +in the terminal theme while running. + +If you want to manually notify Geodesic of the change, you can call + +```bash +set-terminal-theme [light|dark] +``` + +If you provide an argument, it will set the terminal theme to that argument. If you do not provide an argument, +it will attempt to determine the terminal theme itself. + +You can query Geodesic for its cached theme setting by running + +```bash +get-terminal-theme +``` + +Some terminals notify the shell of color changes by sending a `SIGWINCH` signal. +Geodesic also sends this signal to its shell when it detects change in the size of the window. +You can set the environment variable `GEODESIC_TERM_THEME_AUTO=enabled` to enable +Geodesic to listen for this signal and update the theme when it is received. +Beware that while updates are called from the prompt command hook, we have seen many +instances where the terminal does not respond to the query about its color settings +promptly, resulting in failed detection, and the response being written to the +terminal command line. It might look something like `^[]11;rgb:fdfd/f6f5/e3e3^G`. + +Be aware that this is not an area where good solutions exist, which is why +this feature is disabled by default. Unlike with the initial theme detection, +this is not an area where we are investing effort to improve. + +Changing Geodesic's theme does not change anything already on the screen. It only affects future output. #### Named text color helpers -To help you take advantage of the color mode, Geodesic provides a set of named text color helpers. +To help you take advantage of the terminal color theme support, Geodesic provides a set of named text color helpers. They are defined as functions that output all their arguments in the named mode. The named colors are @@ -600,6 +632,12 @@ The named colors are Note: yellow is problematic. To begin with, "yellow" is not necessarily yellow, it varies with the terminal theme, and would be better named "caution" or "info". In addition, it is too light to be used in light mode, so we substitute magenta instead. +We intentionally avoid `blue` and `magenta` because they are problematic in dark mode, +and because we use magenta as a substitute for yellow in light mode. +We also avoid `black` and `white` because they are completely unsuitable for use in +an environment where the terminal theme can switch between light and dark. Instead, +we take care to use the terminal's stated foreground and background colors, which +the terminal itself will change (including text already on the screen) when changing its theme. Each of these colors has 4 variations. Using "red" as an example, they would be: @@ -682,4 +720,5 @@ V4 changes: - `WORKSPACE_FOLDER` is the base directory of the project inside the container. Analogous to the target of Dev Container's `workspaceFolder`. Typically, this is the same as `WORKSPACE_MOUNT`, but may be a subdirectory if, for example, the Git repository is a "monorepo" containing multiple projects. It must be an absolute path either equal to or a subdirectory of `WORKSPACE_FOLDER_HOST_DIR`. -- `GEODESIC_TERM_COLOR_AUTO` is normally unset. Set it to "false" to disable attempts at automatic terminal light/dark mode detection. +- `GEODESIC_TERM_THEME` is normally unset. Set it to "light" or "dark" _before launching Geodesic_ to disable the initial attempt at automatic terminal light/dark them detection, and use the set value instead. +- `GEODESIC_TERM_THEME_AUTO` is normally unset. Set it to "enabled" to enable automatic attempts to detect changes in terminal light/dark them. This feature should be considered experimental and may not work as expected. diff --git a/docs/environment.md b/docs/environment.md index f7a2017d3..459124fc6 100644 --- a/docs/environment.md +++ b/docs/environment.md @@ -34,14 +34,15 @@ Geodesic version 4 additions and changes: - `WORKSPACE_MOUNT` is the container path where `WORKSPACE_MOUNT_HOST_DIR` will be mounted. Analogous to the target of Dev Container's `workspaceMount`. Defaults to `/workspace`, which is the default for a Dev Container, but you may want to set it to something like your project name or git repository name for visibility in the container. -- `GEODESIC_TERM_COLOR_AUTO` is normally unset. Set it to "false" to disable attempts at automatic terminal light/dark mode detection. +- `GEODESIC_TERM_THEME` is normally unset. Geodesic will attempt to detect the terminal light/dark mode and default to light. Set this to "light" or "dark" to disable the automatic detection at startup and force the theme. +- `GEODESIC_TERM_THEME_AUTO` is normally unset. Set it to "true" to enable attempts to automatically detect _changes_ in terminal light/dark theme. Use `set-terminal-theme` to manually switch between light and dark themes. - `GEODESIC_MOTD_ENABLED` can be set to "false" to disable printing the message of the day at shell startup. - `MAP_FILE_OWNERSHIP` replaces `GEODESIC_HOST_BINDFS_ENABLED`. If set to true, Geodesic will use `bindfs` to map file ownership between the host and container. This not normally needed, as it should be handled automatically by Docker. ### Geodesic Version 3 Environment Variables -Below is a list of environment variable that may be visible in the shell and were present in Geodesic v3. +Below is a list of environment variables that may be visible in the shell and were present in Geodesic v3. Many of these variables are only recognized if you explicitly set or export them prior to running the script. Others are set and read internally to control optional behaviors. @@ -107,7 +108,7 @@ and not Geodesic itself. | `PROMPT_STYLE` | Style of the prompt: plain, fancy, or unicode. | | `PS1` | `bash`: Primary prompt string (the final assembled Bash prompt). | | `SCREEN_SIZE` | Internal: Tracks the current terminal screen size as LINES x COLUMNS. | -| `SHLVL` | `bash`1: Shell nesting level (1 for the main shell, higher for subshells). | +| `SHLVL` | `bash`: Shell nesting level (1 for the main shell, higher for subshells). | | `SSH_AGENT_CONFIG`* | Obsolete: Path to the file storing SSH agent environment variables. | | `SSH_AUTH_SOCK` | `ssh`: Socket path for the running SSH agent. | | `SSH_KEY`* | Path to private SSH key file to automatically add to the SSH agent. | @@ -115,5 +116,6 @@ and not Geodesic itself. | `TELEPORT_LOGIN` | `teleport`: Username for Teleport-based SSH sessions. | | `TELEPORT_LOGIN_BIND_ADDR` | `teleport`: Local bind address for Teleport SAML-based login callbacks. | | `TELEPORT_PROXY` | `teleport`: Teleport proxy host (defaults to tele.). | +| `TF_PLUGIN_CACHE_DIR` | `terraform`: Location for the [Terraform Provider Plugin Cache][1]. | - +[1]: https://developer.hashicorp.com/terraform/cli/config/config-file#provider-plugin-cache diff --git a/rootfs/etc/profile.d/terraform.sh b/rootfs/etc/profile.d/terraform.sh index b47d8c82f..87b440645 100755 --- a/rootfs/etc/profile.d/terraform.sh +++ b/rootfs/etc/profile.d/terraform.sh @@ -47,5 +47,10 @@ done # Set default plugin cache dir (must not be one of the mirror directories) # https://www.terraform.io/docs/commands/cli-config.html#implied-local-mirror-directories) -export TF_PLUGIN_CACHE_DIR="${TF_PLUGIN_CACHE_DIR:-${HOME}/.terraform.d/plugin-cache}" -mkdir -p "$TF_PLUGIN_CACHE_DIR" || unset TF_PLUGIN_CACHE_DIR +# If TF_PLUGIN_CACHE_DIR is set to "false" or "disabled" or set to empty (not unset), disable the cache +if [[ -n "${TF_PLUGIN_CACHE_DIR+x}" ]] && [[ -z "$TF_PLUGIN_CACHE_DIR" || "$TF_PLUGIN_CACHE_DIR" == "false" || "$TF_PLUGIN_CACHE_DIR" == "disabled" ]]; then + unset TF_PLUGIN_CACHE_DIR +else + export TF_PLUGIN_CACHE_DIR="${TF_PLUGIN_CACHE_DIR:-${HOME}/.terraform.d/plugin-cache}" + mkdir -p "$TF_PLUGIN_CACHE_DIR" || unset TF_PLUGIN_CACHE_DIR +fi From 45907aa4bcab5cd00e903e0ed36807c5f0397895 Mon Sep 17 00:00:00 2001 From: Nuru Date: Wed, 19 Feb 2025 22:10:48 -0800 Subject: [PATCH 6/8] Reviewer feedback --- .gitignore | 2 +- ReleaseNotes-v4.md | 18 +++++++++--------- rootfs/etc/profile.d/_07-term-mode.sh | 5 +++++ 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index 023d4db34..d3ce4df09 100644 --- a/.gitignore +++ b/.gitignore @@ -37,4 +37,4 @@ src/colorsaurus/target/** **/.DS_Store **/.CFUserTextEncoding **/.Trash/ -**/$RECYCLE.BIN/YCLE.BIN/ +**/$RECYCLE.BIN/ diff --git a/ReleaseNotes-v4.md b/ReleaseNotes-v4.md index 0e205fd18..a6a035c97 100644 --- a/ReleaseNotes-v4.md +++ b/ReleaseNotes-v4.md @@ -247,7 +247,7 @@ or you can replace it with the following, which works with both Geodesic v3 and - By default, Geodesic automatically sets `TF_PLUGIN_CACHE_DIR` to `"${HOME}/.terraform.d/plugin-cache"` to provide a location for the [Terraform Provider Plugin Cache](https://developer.hashicorp.com/terraform/cli/config/config-file#provider-plugin-cache). This is a location on the host filesystem, allowing the cache to be shared between the host and the container, - and to persist between container runs. This is valuable saver of time and bandwidth, and we highly recommend + and to persist between container runs. This saves significant time and bandwidth, and we highly recommend using the plugin cache, which is why we have been setting it for years. However, the way this was implemented, it was difficult for users to opt-out of the plugin cache altogether. To accommodate those users while maintaining backward compatibility, @@ -546,8 +546,8 @@ Some line options regarding customization have been added: customizations or in the base image. Note that this does not disable changes made by `launch-options.sh`. 2. `--trace` will enable tracing the Geodesic script as it performs customizations. Equivalent to `--trace=custom`. 3. `--trace="custom terminal hist` will enable tracing of the customizations, terminal configuration (mainly with respect - to light and dark mode support), and determining which Bash history file to use, respectively. You can use these options - in any combination, for example, `--trace="hist"`. + to light and dark mode support), and the determination of which Bash history file to use, respectively. + You can use these options in any combination. 4. `--dark` and `--light` will set the terminal theme to dark or light, respectively, disabling attempts to automatically detect it. This is mainly useful if the automatic detection is not working. @@ -580,10 +580,10 @@ must be set in the environment before Geodesic starts, e.g. in the `launch-optio #### Detecting changes in theme while running (experimental) You may have your terminal set to automatically switch between light and dark themes -according to the time of day, or you may manually switch between themes. Unfortunately, -there is no standard way to be notified of a terminal's theme change. Furthermore, -because of the general issues around detecting the terminal theme, automatic -detection of changes in the terminal theme has too often injected disruption +based on the time of day, or you may manually switch between themes. Unfortunately, +there is no standard way for a shell to be notified when a terminal's theme changes. Furthermore, +due to the inherent challenges of detecting terminal themes, automatic +detection of changes in the terminal theme has too often injected disruptive characters and messages into the user's workflow. As a result, Geodesic by default does not attempt to detect changes in the terminal theme while running. @@ -720,5 +720,5 @@ V4 changes: - `WORKSPACE_FOLDER` is the base directory of the project inside the container. Analogous to the target of Dev Container's `workspaceFolder`. Typically, this is the same as `WORKSPACE_MOUNT`, but may be a subdirectory if, for example, the Git repository is a "monorepo" containing multiple projects. It must be an absolute path either equal to or a subdirectory of `WORKSPACE_FOLDER_HOST_DIR`. -- `GEODESIC_TERM_THEME` is normally unset. Set it to "light" or "dark" _before launching Geodesic_ to disable the initial attempt at automatic terminal light/dark them detection, and use the set value instead. -- `GEODESIC_TERM_THEME_AUTO` is normally unset. Set it to "enabled" to enable automatic attempts to detect changes in terminal light/dark them. This feature should be considered experimental and may not work as expected. +- `GEODESIC_TERM_THEME` is normally unset. Set it to "light" or "dark" _before launching Geodesic_ to disable the initial attempt at automatic terminal light/dark theme detection, and use the set value instead. +- `GEODESIC_TERM_THEME_AUTO` is normally unset. Set it to "enabled" to enable automatic attempts to detect changes in terminal light/dark theme. This feature should be considered experimental and may not work as expected. diff --git a/rootfs/etc/profile.d/_07-term-mode.sh b/rootfs/etc/profile.d/_07-term-mode.sh index 9a2463665..aaff91dad 100644 --- a/rootfs/etc/profile.d/_07-term-mode.sh +++ b/rootfs/etc/profile.d/_07-term-mode.sh @@ -81,7 +81,12 @@ function _verify_terminal_queries_are_supported() { # This is the worker function that gets the terminal foreground and background colors in RGB # and converts them to luminance values. If unknown, it returns 0 0. +# +# Luminance is on a scale of 0 to 1, but we want to be able to compare integers in bash, +# so our _srgb_to_luminance function multiplies by a big enough value to get an integer and maintain precision. +# Specifically, it multiplies by 1 000 000 000, which is why 1000000000 is used in the forced modes. # If forced "light" or "dark", it returns 0 1000000000 or 1000000000 0, respectively. + _get_terminal_luminance() { local fg_rgb bg_rgb fg_lum bg_lum From 90befb0725bcd35fec3a5fa78db84fec8f9472e7 Mon Sep 17 00:00:00 2001 From: Nuru Date: Wed, 19 Feb 2025 22:10:59 -0800 Subject: [PATCH 7/8] Fix for loop syntax --- rootfs/templates/wrapper-body.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rootfs/templates/wrapper-body.sh b/rootfs/templates/wrapper-body.sh index 799d7512e..d845eb993 100755 --- a/rootfs/templates/wrapper-body.sh +++ b/rootfs/templates/wrapper-body.sh @@ -273,7 +273,7 @@ function wait_for_container_exit() { local i n shells n=15 - for i in {0..$n}; do + for (( i=0; i<=n; i++ )); do # Try n times to see if the container is still running, quit when it is no longer found if [ -z "$(docker ps -q --filter "id=${CONTAINER_ID:0:12}")" ]; then i=0 From 44f59aec3c60310cf8aa8427aa9c7857f7d4bd7a Mon Sep 17 00:00:00 2001 From: Nuru Date: Wed, 19 Feb 2025 22:35:50 -0800 Subject: [PATCH 8/8] Better argument handling --- ReleaseNotes-v4.md | 4 ++-- rootfs/templates/wrapper-body.sh | 15 ++++++++------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/ReleaseNotes-v4.md b/ReleaseNotes-v4.md index a6a035c97..21f2b5636 100644 --- a/ReleaseNotes-v4.md +++ b/ReleaseNotes-v4.md @@ -546,7 +546,7 @@ Some line options regarding customization have been added: customizations or in the base image. Note that this does not disable changes made by `launch-options.sh`. 2. `--trace` will enable tracing the Geodesic script as it performs customizations. Equivalent to `--trace=custom`. 3. `--trace="custom terminal hist` will enable tracing of the customizations, terminal configuration (mainly with respect - to light and dark mode support), and the determination of which Bash history file to use, respectively. + to light/dark mode support), and the determination of which Bash history file to use, respectively. You can use these options in any combination. 4. `--dark` and `--light` will set the terminal theme to dark or light, respectively, disabling attempts to automatically detect it. This is mainly useful if the automatic detection is not working. @@ -603,7 +603,7 @@ get-terminal-theme ``` Some terminals notify the shell of color changes by sending a `SIGWINCH` signal. -Geodesic also sends this signal to its shell when it detects change in the size of the window. +Geodesic also sends this signal to its shell when it detects a change in the size of the window. You can set the environment variable `GEODESIC_TERM_THEME_AUTO=enabled` to enable Geodesic to listen for this signal and update the theme when it is received. Beware that while updates are called from the prompt command hook, we have seen many diff --git a/rootfs/templates/wrapper-body.sh b/rootfs/templates/wrapper-body.sh index d845eb993..cedf17974 100755 --- a/rootfs/templates/wrapper-body.sh +++ b/rootfs/templates/wrapper-body.sh @@ -395,12 +395,12 @@ function use() { if [ -n "$CONTAINER_ID" ]; then echo "# Starting shell in already running ${DOCKER_NAME} container ($CONTAINER_ID)" if [ $# -eq 0 ]; then - set -- "/bin/bash" "-l" "$@" + set -- "/bin/bash" "-l" fi [ -t 0 ] && DOCKER_EXEC_ARGS+=(-it) # We set unusual detach keys because (a) the default first char is ctrl-p, which is used for command history, # and (b) if you detach from the shell, there is no way to reattach to it, so we want to effectively disable detach. - docker exec --env G_HOST_PID=$WRAPPER_PID --detach-keys "ctrl-^,ctrl-[,ctrl-@" "${DOCKER_EXEC_ARGS[@]}" "${DOCKER_NAME}" $* + docker exec --env G_HOST_PID=$WRAPPER_PID --detach-keys "ctrl-^,ctrl-[,ctrl-@" "${DOCKER_EXEC_ARGS[@]}" "${DOCKER_NAME}" "$@" return 0 fi fi @@ -592,22 +592,23 @@ function use() { echo "# Starting single shell ${DOCKER_NAME} session from ${DOCKER_IMAGE}" echo "# Exposing port ${GEODESIC_PORT}" [ -z "${GEODESIC_DOCKER_EXTRA_ARGS}" ] || echo "# Launching with extra Docker args: ${GEODESIC_DOCKER_EXTRA_ARGS}" - docker run --name "${DOCKER_NAME}" "${DOCKER_LAUNCH_ARGS[@]}" "${DOCKER_EXEC_ARGS[@]}" ${GEODESIC_DOCKER_EXTRA_ARGS} ${DOCKER_IMAGE} -l $* + # GEODESIC_DOCKER_EXTRA_ARGS is not quoted because it is expected to be a list of arguments + docker run --name "${DOCKER_NAME}" "${DOCKER_LAUNCH_ARGS[@]}" "${DOCKER_EXEC_ARGS[@]}" ${GEODESIC_DOCKER_EXTRA_ARGS} "${DOCKER_IMAGE}" -l "$@" else echo "# Running new ${DOCKER_NAME} container from ${DOCKER_IMAGE}" echo "# Exposing port ${GEODESIC_PORT}" [ -z "${GEODESIC_DOCKER_EXTRA_ARGS}" ] || echo "# Launching with extra Docker args: ${GEODESIC_DOCKER_EXTRA_ARGS}" - # docker run "${DOCKER_ARGS[@]}" ${GEODESIC_DOCKER_EXTRA_ARGS} ${DOCKER_IMAGE} -l $* - CONTAINER_ID=$(docker run --detach --init --name "${DOCKER_NAME}" "${DOCKER_LAUNCH_ARGS[@]}" "${DOCKER_EXEC_ARGS[@]}" ${GEODESIC_DOCKER_EXTRA_ARGS} ${DOCKER_IMAGE} /usr/local/sbin/shell-monitor) + # GEODESIC_DOCKER_EXTRA_ARGS is not quoted because it is expected to be a list of arguments + CONTAINER_ID=$(docker run --detach --init --name "${DOCKER_NAME}" "${DOCKER_LAUNCH_ARGS[@]}" "${DOCKER_EXEC_ARGS[@]}" ${GEODESIC_DOCKER_EXTRA_ARGS} "${DOCKER_IMAGE}" /usr/local/sbin/shell-monitor) echo "# Started session ${CONTAINER_ID:0:12}. Starting shell via \`docker exec\`..." if [ $# -eq 0 ]; then - set -- "/bin/bash" "-l" "$@" + set -- "/bin/bash" "-l" fi [ -t 0 ] && DOCKER_EXEC_ARGS+=(-it) # We set unusual detach keys because (a) the default first char is ctrl-p, which is used for command history, # and (b) if you detach from the shell, there is no way to reattach to it, so we want to effectively disable detach. - docker exec --env G_HOST_PID=$$ --detach-keys "ctrl-^,ctrl-[,ctrl-@" "${DOCKER_EXEC_ARGS[@]}" "${DOCKER_NAME}" $* + docker exec --env G_HOST_PID=$$ --detach-keys "ctrl-^,ctrl-[,ctrl-@" "${DOCKER_EXEC_ARGS[@]}" "${DOCKER_NAME}" "$@" fi true }