From 71d0538db7d4c2b89de99a4c17e7760f76c707c8 Mon Sep 17 00:00:00 2001 From: Vance Raiti Date: Mon, 14 Jul 2025 19:25:08 -0400 Subject: [PATCH 1/3] Use PROMPT_COMMAND to set jmp shell prompt --- packages/jumpstarter/jumpstarter/common/utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/jumpstarter/jumpstarter/common/utils.py b/packages/jumpstarter/jumpstarter/common/utils.py index f732c942a..cbb0366cf 100644 --- a/packages/jumpstarter/jumpstarter/common/utils.py +++ b/packages/jumpstarter/jumpstarter/common/utils.py @@ -76,11 +76,11 @@ def launch_shell( return process.wait() if shell_name.endswith("bash"): + PS1=f"{ANSI_GRAY}{PROMPT_CWD} {ANSI_YELLOW}⚡{ANSI_WHITE}{context} {ANSI_YELLOW}➤{ANSI_RESET} " env = common_env | { - "PS1": f"{ANSI_GRAY}{PROMPT_CWD} {ANSI_YELLOW}⚡{ANSI_WHITE}{context} {ANSI_YELLOW}➤{ANSI_RESET} ", + 'PROMPT_COMMAND': f'PS1="{PS1}"', } - cmd = [shell, "--norc", "--noprofile"] - process = Popen(cmd, stdin=sys.stdin, stdout=sys.stdout, stderr=sys.stderr, env=env) + process = Popen(shell, stdin=sys.stdin, stdout=sys.stdout, stderr=sys.stderr, env=env) return process.wait() elif shell_name == "fish": From e40225b02d129f53755733ac6da8229864076f0b Mon Sep 17 00:00:00 2001 From: vraiti Date: Wed, 16 Jul 2025 10:34:28 -0400 Subject: [PATCH 2/3] Make jmp shell profile usage configurable --- packages/jumpstarter-cli/jumpstarter_cli/shell.py | 3 +++ packages/jumpstarter/jumpstarter/common/utils.py | 10 +++++++--- .../jumpstarter/jumpstarter/common/utils_test.py | 15 +++++++++++++-- packages/jumpstarter/jumpstarter/config/client.py | 2 ++ .../jumpstarter/config/client_config_test.py | 6 ++++++ 5 files changed, 31 insertions(+), 5 deletions(-) diff --git a/packages/jumpstarter-cli/jumpstarter_cli/shell.py b/packages/jumpstarter-cli/jumpstarter_cli/shell.py index 64e682035..338d60120 100644 --- a/packages/jumpstarter-cli/jumpstarter_cli/shell.py +++ b/packages/jumpstarter-cli/jumpstarter_cli/shell.py @@ -51,6 +51,7 @@ def shell(config, command: tuple[str, ...], lease_name, selector, duration, expo "remote", config.drivers.allow, config.drivers.unsafe, + config.shell_use_profiles, command=command, ) else: @@ -59,6 +60,7 @@ def shell(config, command: tuple[str, ...], lease_name, selector, duration, expo "remote", config.drivers.allow, config.drivers.unsafe, + config.shell_use_profiles, command=command, ) @@ -72,5 +74,6 @@ def shell(config, command: tuple[str, ...], lease_name, selector, duration, expo "local", allow=[], unsafe=True, + use_profiles=False, command=command, ) diff --git a/packages/jumpstarter/jumpstarter/common/utils.py b/packages/jumpstarter/jumpstarter/common/utils.py index cbb0366cf..7450aa764 100644 --- a/packages/jumpstarter/jumpstarter/common/utils.py +++ b/packages/jumpstarter/jumpstarter/common/utils.py @@ -51,6 +51,7 @@ def launch_shell( context: str, allow: list[str], unsafe: bool, + use_profiles: bool, *, command: tuple[str, ...] | None = None, ) -> int: @@ -76,11 +77,14 @@ def launch_shell( return process.wait() if shell_name.endswith("bash"): - PS1=f"{ANSI_GRAY}{PROMPT_CWD} {ANSI_YELLOW}⚡{ANSI_WHITE}{context} {ANSI_YELLOW}➤{ANSI_RESET} " env = common_env | { - 'PROMPT_COMMAND': f'PS1="{PS1}"', + "PS1": f"{ANSI_GRAY}{PROMPT_CWD} {ANSI_YELLOW}⚡{ANSI_WHITE}{context} {ANSI_YELLOW}➤{ANSI_RESET} ", } - process = Popen(shell, stdin=sys.stdin, stdout=sys.stdout, stderr=sys.stderr, env=env) + + cmd = [shell] + if not use_profiles: + cmd.extend(['--norc','--noprofile']) + process = Popen(cmd, stdin=sys.stdin, stdout=sys.stdout, stderr=sys.stderr, env=env) return process.wait() elif shell_name == "fish": diff --git a/packages/jumpstarter/jumpstarter/common/utils_test.py b/packages/jumpstarter/jumpstarter/common/utils_test.py index ece4c3239..86cd52dfd 100644 --- a/packages/jumpstarter/jumpstarter/common/utils_test.py +++ b/packages/jumpstarter/jumpstarter/common/utils_test.py @@ -5,9 +5,20 @@ def test_launch_shell(tmp_path, monkeypatch): monkeypatch.setenv("SHELL", shutil.which("true")) - exit_code = launch_shell(host=str(tmp_path / "test.sock"), context="remote", allow=["*"], unsafe=False) + exit_code = launch_shell( + host=str(tmp_path / "test.sock"), + context="remote", + allow=["*"], + unsafe=False, + use_profiles=False + ) assert exit_code == 0 monkeypatch.setenv("SHELL", shutil.which("false")) - exit_code = launch_shell(host=str(tmp_path / "test.sock"), context="remote", allow=["*"], unsafe=False) + exit_code = launch_shell( + host=str(tmp_path / "test.sock"), + context="remote", allow=["*"], + unsafe=False, + use_profiles=False + ) assert exit_code == 1 diff --git a/packages/jumpstarter/jumpstarter/config/client.py b/packages/jumpstarter/jumpstarter/config/client.py index 0661c5019..2c58cce39 100644 --- a/packages/jumpstarter/jumpstarter/config/client.py +++ b/packages/jumpstarter/jumpstarter/config/client.py @@ -103,6 +103,8 @@ class ClientConfigV1Alpha1(BaseSettings): drivers: ClientConfigV1Alpha1Drivers = Field(default_factory=ClientConfigV1Alpha1Drivers) + shell_use_profiles: bool = False + async def channel(self): if self.endpoint is None or self.token is None: raise ConfigurationError("endpoint or token not set in client config") diff --git a/packages/jumpstarter/jumpstarter/config/client_config_test.py b/packages/jumpstarter/jumpstarter/config/client_config_test.py index af9bd3735..387038676 100644 --- a/packages/jumpstarter/jumpstarter/config/client_config_test.py +++ b/packages/jumpstarter/jumpstarter/config/client_config_test.py @@ -212,6 +212,7 @@ def test_client_config_save(monkeypatch: pytest.MonkeyPatch): - jumpstarter.drivers.* - vendorpackage.* unsafe: false +shell_use_profiles: false """ config = ClientConfigV1Alpha1( alias="testclient", @@ -219,6 +220,7 @@ def test_client_config_save(monkeypatch: pytest.MonkeyPatch): endpoint="jumpstarter.my-lab.com:1443", token="dGhpc2lzYXRva2VuLTEyMzQxMjM0MTIzNEyMzQtc2Rxd3Jxd2VycXdlcnF3ZXJxd2VyLTEyMzQxMjM0MTIz", drivers=ClientConfigV1Alpha1Drivers(allow=["jumpstarter.drivers.*", "vendorpackage.*"], unsafe=False), + shell_use_profiles=False, ) with tempfile.NamedTemporaryFile(mode="w", delete=False) as f: with patch.object(ClientConfigV1Alpha1, "_get_path", return_value=Path(f.name)) as _get_path_mock: @@ -248,6 +250,7 @@ def test_client_config_save_explicit_path(): - jumpstarter.drivers.* - vendorpackage.* unsafe: false +shell_use_profiles: false """ config = ClientConfigV1Alpha1( alias="testclient", @@ -255,6 +258,7 @@ def test_client_config_save_explicit_path(): endpoint="jumpstarter.my-lab.com:1443", token="dGhpc2lzYXRva2VuLTEyMzQxMjM0MTIzNEyMzQtc2Rxd3Jxd2VycXdlcnF3ZXJxd2VyLTEyMzQxMjM0MTIz", drivers=ClientConfigV1Alpha1Drivers(allow=["jumpstarter.drivers.*", "vendorpackage.*"], unsafe=False), + shell_use_profiles=False, ) with tempfile.NamedTemporaryFile(mode="w", delete=False) as f: with patch.object(ClientConfigV1Alpha1, "ensure_exists"): @@ -280,6 +284,7 @@ def test_client_config_save_unsafe_drivers(): drivers: allow: [] unsafe: true +shell_use_profiles: false """ config = ClientConfigV1Alpha1( alias="testclient", @@ -287,6 +292,7 @@ def test_client_config_save_unsafe_drivers(): endpoint="jumpstarter.my-lab.com:1443", token="dGhpc2lzYXRva2VuLTEyMzQxMjM0MTIzNEyMzQtc2Rxd3Jxd2VycXdlcnF3ZXJxd2VyLTEyMzQxMjM0MTIz", drivers=ClientConfigV1Alpha1Drivers(allow=[], unsafe=True), + shell_use_profiles=False, ) with tempfile.NamedTemporaryFile(mode="w", delete=False) as f: with patch.object(ClientConfigV1Alpha1, "ensure_exists"): From 1c5e62cb4a70dc3371a08509cd9e5ee14086dc19 Mon Sep 17 00:00:00 2001 From: Vance Raiti Date: Fri, 18 Jul 2025 08:38:40 -0400 Subject: [PATCH 3/3] Create shell config dictionary --- .../jumpstarter-cli/jumpstarter_cli/shell.py | 2 +- .../jumpstarter/jumpstarter/config/client.py | 3 ++- .../jumpstarter/config/client_config_test.py | 17 ++++++++++------- .../jumpstarter/jumpstarter/config/shell.py | 5 +++++ 4 files changed, 18 insertions(+), 9 deletions(-) create mode 100644 packages/jumpstarter/jumpstarter/config/shell.py diff --git a/packages/jumpstarter-cli/jumpstarter_cli/shell.py b/packages/jumpstarter-cli/jumpstarter_cli/shell.py index 338d60120..521a5b2ce 100644 --- a/packages/jumpstarter-cli/jumpstarter_cli/shell.py +++ b/packages/jumpstarter-cli/jumpstarter_cli/shell.py @@ -60,7 +60,7 @@ def shell(config, command: tuple[str, ...], lease_name, selector, duration, expo "remote", config.drivers.allow, config.drivers.unsafe, - config.shell_use_profiles, + config.shell.use_profiles, command=command, ) diff --git a/packages/jumpstarter/jumpstarter/config/client.py b/packages/jumpstarter/jumpstarter/config/client.py index 2c58cce39..332587ff1 100644 --- a/packages/jumpstarter/jumpstarter/config/client.py +++ b/packages/jumpstarter/jumpstarter/config/client.py @@ -24,6 +24,7 @@ from .common import CONFIG_PATH, ObjectMeta from .env import JMP_LEASE from .grpc import call_credentials +from .shell import ShellConfigV1Alpha1 from .tls import TLSConfigV1Alpha1 from jumpstarter.client.grpc import ClientService, WithLease, WithLeaseList from jumpstarter.common.exceptions import ( @@ -103,7 +104,7 @@ class ClientConfigV1Alpha1(BaseSettings): drivers: ClientConfigV1Alpha1Drivers = Field(default_factory=ClientConfigV1Alpha1Drivers) - shell_use_profiles: bool = False + shell: ShellConfigV1Alpha1 = Field(default_factory=ShellConfigV1Alpha1) async def channel(self): if self.endpoint is None or self.token is None: diff --git a/packages/jumpstarter/jumpstarter/config/client_config_test.py b/packages/jumpstarter/jumpstarter/config/client_config_test.py index 387038676..3dbcdabf1 100644 --- a/packages/jumpstarter/jumpstarter/config/client_config_test.py +++ b/packages/jumpstarter/jumpstarter/config/client_config_test.py @@ -8,7 +8,7 @@ from pydantic import ValidationError from jumpstarter.common.exceptions import FileNotFoundError -from jumpstarter.config.client import ClientConfigV1Alpha1, ClientConfigV1Alpha1Drivers +from jumpstarter.config.client import ClientConfigV1Alpha1, ClientConfigV1Alpha1Drivers, ShellConfigV1Alpha1 from jumpstarter.config.common import ObjectMeta from jumpstarter.config.env import JMP_DRIVERS_ALLOW, JMP_ENDPOINT, JMP_NAME, JMP_NAMESPACE, JMP_TOKEN @@ -212,7 +212,8 @@ def test_client_config_save(monkeypatch: pytest.MonkeyPatch): - jumpstarter.drivers.* - vendorpackage.* unsafe: false -shell_use_profiles: false +shell: + use_profiles: false """ config = ClientConfigV1Alpha1( alias="testclient", @@ -220,7 +221,7 @@ def test_client_config_save(monkeypatch: pytest.MonkeyPatch): endpoint="jumpstarter.my-lab.com:1443", token="dGhpc2lzYXRva2VuLTEyMzQxMjM0MTIzNEyMzQtc2Rxd3Jxd2VycXdlcnF3ZXJxd2VyLTEyMzQxMjM0MTIz", drivers=ClientConfigV1Alpha1Drivers(allow=["jumpstarter.drivers.*", "vendorpackage.*"], unsafe=False), - shell_use_profiles=False, + shell=ShellConfigV1Alpha1(use_profiles=False), ) with tempfile.NamedTemporaryFile(mode="w", delete=False) as f: with patch.object(ClientConfigV1Alpha1, "_get_path", return_value=Path(f.name)) as _get_path_mock: @@ -250,7 +251,8 @@ def test_client_config_save_explicit_path(): - jumpstarter.drivers.* - vendorpackage.* unsafe: false -shell_use_profiles: false +shell: + use_profiles: false """ config = ClientConfigV1Alpha1( alias="testclient", @@ -258,7 +260,7 @@ def test_client_config_save_explicit_path(): endpoint="jumpstarter.my-lab.com:1443", token="dGhpc2lzYXRva2VuLTEyMzQxMjM0MTIzNEyMzQtc2Rxd3Jxd2VycXdlcnF3ZXJxd2VyLTEyMzQxMjM0MTIz", drivers=ClientConfigV1Alpha1Drivers(allow=["jumpstarter.drivers.*", "vendorpackage.*"], unsafe=False), - shell_use_profiles=False, + shell=ShellConfigV1Alpha1(use_profiles=False), ) with tempfile.NamedTemporaryFile(mode="w", delete=False) as f: with patch.object(ClientConfigV1Alpha1, "ensure_exists"): @@ -284,7 +286,8 @@ def test_client_config_save_unsafe_drivers(): drivers: allow: [] unsafe: true -shell_use_profiles: false +shell: + use_profiles: false """ config = ClientConfigV1Alpha1( alias="testclient", @@ -292,7 +295,7 @@ def test_client_config_save_unsafe_drivers(): endpoint="jumpstarter.my-lab.com:1443", token="dGhpc2lzYXRva2VuLTEyMzQxMjM0MTIzNEyMzQtc2Rxd3Jxd2VycXdlcnF3ZXJxd2VyLTEyMzQxMjM0MTIz", drivers=ClientConfigV1Alpha1Drivers(allow=[], unsafe=True), - shell_use_profiles=False, + shell=ShellConfigV1Alpha1(use_profiles=False), ) with tempfile.NamedTemporaryFile(mode="w", delete=False) as f: with patch.object(ClientConfigV1Alpha1, "ensure_exists"): diff --git a/packages/jumpstarter/jumpstarter/config/shell.py b/packages/jumpstarter/jumpstarter/config/shell.py new file mode 100644 index 000000000..c0109a574 --- /dev/null +++ b/packages/jumpstarter/jumpstarter/config/shell.py @@ -0,0 +1,5 @@ +from pydantic import BaseModel + + +class ShellConfigV1Alpha1(BaseModel): + use_profiles: bool = False