Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 13 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,21 @@ Secrets Vault gateway.

- Python 3.10 or later
- A running Distributed Secrets Vault server
- `curl` (for install/uninstall scripts)
- `curl` and `sh` (for install/uninstall scripts)

## Install

Install directly from GitHub:
Install directly from GitHub (works whether your login shell is bash or zsh):

```bash
curl -fsSL https://raw.githubusercontent.com/S26-Distributed-Capstone/DSVClient/main/scripts/install.sh | bash
```sh
curl -fsSL https://raw.githubusercontent.com/S26-Distributed-Capstone/DSVClient/main/scripts/install.sh | sh
```

To download the script first instead of piping:

```sh
curl -fsSL https://raw.githubusercontent.com/S26-Distributed-Capstone/DSVClient/main/scripts/install.sh -o /tmp/install-dsvc.sh
sh /tmp/install-dsvc.sh
```

The installer:
Expand Down Expand Up @@ -185,8 +192,8 @@ python3 -m unittest discover -s tests -p "test_*.py"

## Uninstall

```bash
curl -fsSL https://raw.githubusercontent.com/S26-Distributed-Capstone/DSVClient/main/scripts/uninstall.sh | bash
```sh
curl -fsSL https://raw.githubusercontent.com/S26-Distributed-Capstone/DSVClient/main/scripts/uninstall.sh | sh
```

The uninstall script removes:
Expand Down
137 changes: 72 additions & 65 deletions scripts/install.sh
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
#!/usr/bin/env bash
set -euo pipefail
#!/bin/sh
set -eu
(set -o pipefail) 2>/dev/null && set -o pipefail

RUNTIME_ROOT="${XDG_DATA_HOME:-$HOME/.local/share}/dsvc"
SRC_DIR="$RUNTIME_ROOT/src"
RUNTIME_BIN_DIR="$RUNTIME_ROOT/bin"
if [[ -w /usr/local/bin ]]; then
if [ -w /usr/local/bin ]; then
BIN_DIR="/usr/local/bin"
else
BIN_DIR="$HOME/.local/bin"
Expand All @@ -13,6 +14,9 @@ CONFIG_DIR="$HOME/.dsv_client"
CONFIG_FILE="$CONFIG_DIR/config.json"
RAW_BASE_URL="https://raw.githubusercontent.com/S26-Distributed-Capstone/DSVClient/main"

cfg_read_source=""
cfg_prompt_out="/dev/stderr"

trim_whitespace() {
printf '%s' "$1" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//'
}
Expand All @@ -24,59 +28,57 @@ require_cmd() {
fi
}

download_python_sources() {
local files=("cli.py" "client.py" "config.py")
local file=""
prompt_and_read() {
_prompt="$1"
_resultvar="$2"
_value=""
printf "%s" "$_prompt" >"$cfg_prompt_out"
if [ "$cfg_read_source" = "tty" ]; then
if ! IFS= read -r _value < /dev/tty; then
_value=""
fi
elif ! IFS= read -r _value; then
_value=""
fi
eval "$_resultvar=\$_value"
}

download_python_sources() {
mkdir -p "$SRC_DIR"
for file in "${files[@]}"; do
for file in cli.py client.py config.py; do
curl -fsSL "${RAW_BASE_URL}/${file}" -o "${SRC_DIR}/${file}"
done
}

configure_client() {
local default_base_url="http://localhost:8080"
local existing_base_url=""
local existing_username=""
local entered_base_url=""
local entered_username=""
local entered_username_trimmed=""
local prompt_fd=0
local has_prompt_tty=0
local prompt_out="/dev/stderr"

prompt_and_read() {
local prompt="$1"
local __resultvar="$2"
local value=""

printf "%s" "$prompt" > "$prompt_out"
if ! IFS= read -r -u "$prompt_fd" value; then
value=""
fi
printf -v "$__resultvar" '%s' "$value"
}

# When installer is piped (curl | bash), stdin is not interactive.
default_base_url="http://localhost:8080"
existing_base_url=""
existing_username=""
entered_base_url=""
entered_username=""
entered_username_trimmed=""
cfg_has_prompt_tty=0

# When installer is piped (curl | sh), stdin is not interactive.
# Use the controlling terminal directly if available.
if [[ -t 0 ]]; then
has_prompt_tty=1
elif [[ -r /dev/tty ]]; then
if exec 3</dev/tty 2>/dev/null; then
prompt_fd=3
prompt_out="/dev/tty"
has_prompt_tty=1
fi
if [ -t 0 ]; then
cfg_has_prompt_tty=1
cfg_read_source="stdin"
elif [ -e /dev/tty ] && { printf '' > /dev/tty; } 2>/dev/null; then
cfg_has_prompt_tty=1
cfg_read_source="tty"
cfg_prompt_out="/dev/tty"
fi

if [[ "$has_prompt_tty" -eq 0 ]]; then
if [ "$cfg_has_prompt_tty" -eq 0 ]; then
echo "No interactive terminal detected."
echo "Please run install again in an interactive terminal."
exit 1
fi

if [[ -f "$CONFIG_FILE" ]]; then
read -r existing_base_url existing_username < <(python3 - <<'PY' "$CONFIG_FILE"
if [ -f "$CONFIG_FILE" ]; then
IFS= read -r existing_base_url existing_username <<EOF
$(python3 - <<'PY' "$CONFIG_FILE"
import json
import pathlib
import sys
Expand All @@ -91,29 +93,38 @@ username = str(data.get("username", "")).strip()
print(base_url, username)
PY
)
EOF
fi

entered_base_url="$existing_base_url"
entered_username="$existing_username"

local placeholder="$default_base_url"
if [[ -n "$existing_base_url" ]]; then
placeholder="$default_base_url"
if [ -n "$existing_base_url" ]; then
placeholder="$existing_base_url"
fi

prompt_and_read "Server URL [${placeholder}]: " entered_base_url

entered_base_url="${entered_base_url:-$placeholder}"
entered_base_url="${entered_base_url%/}"
entered_base_url="$(trim_whitespace "$entered_base_url")"

local username_placeholder="${existing_username:-}"
while true; do
prompt_and_read "Username${username_placeholder:+ [${username_placeholder}]}: " entered_username
if [ -z "$entered_base_url" ]; then
entered_base_url="$placeholder"
fi
entered_base_url=$(printf '%s' "$entered_base_url" | sed 's#/*$##')
entered_base_url=$(trim_whitespace "$entered_base_url")

username_placeholder="${existing_username:-}"
while :; do
if [ -n "$username_placeholder" ]; then
prompt_and_read "Username [${username_placeholder}]: " entered_username
else
prompt_and_read "Username: " entered_username
fi

entered_username="${entered_username:-$username_placeholder}"
entered_username_trimmed="$(trim_whitespace "$entered_username")"
if [[ -n "$entered_username_trimmed" ]]; then
if [ -z "$entered_username" ]; then
entered_username="$username_placeholder"
fi
entered_username_trimmed=$(trim_whitespace "$entered_username")
if [ -n "$entered_username_trimmed" ]; then
entered_username="$entered_username_trimmed"
break
fi
Expand All @@ -122,13 +133,9 @@ PY
username_placeholder=""
done

entered_username_trimmed="$(trim_whitespace "${entered_username:-}")"
entered_username_trimmed=$(trim_whitespace "${entered_username:-}")
entered_username="$entered_username_trimmed"

if [[ "$prompt_fd" -eq 3 ]]; then
exec 3<&-
fi

mkdir -p "$CONFIG_DIR"
python3 - <<'PY' "$CONFIG_FILE" "$entered_base_url" "$entered_username"
import json
Expand All @@ -154,16 +161,15 @@ main() {
rm -rf "$SRC_DIR"
download_python_sources

if [[ ! -f "$SRC_DIR/cli.py" || ! -f "$SRC_DIR/client.py" || ! -f "$SRC_DIR/config.py" ]]; then
if [ ! -f "$SRC_DIR/cli.py" ] || [ ! -f "$SRC_DIR/client.py" ] || [ ! -f "$SRC_DIR/config.py" ]; then
echo "Error: downloaded source does not contain expected Python client files." >&2
echo "Unable to fetch expected sources from GitHub." >&2
exit 1
fi

mkdir -p "$RUNTIME_BIN_DIR"
cat > "$RUNTIME_BIN_DIR/dsvc" <<EOF
#!/usr/bin/env bash
set -euo pipefail
#!/bin/sh
exec python3 "$SRC_DIR/cli.py" "\$@"
EOF
chmod +x "$RUNTIME_BIN_DIR/dsvc"
Expand All @@ -176,10 +182,11 @@ EOF

echo
echo "Installed dsvc to $BIN_DIR/dsvc"
if [[ ":$PATH:" != *":$BIN_DIR:"* ]]; then
echo "Add $BIN_DIR to your PATH to run 'dsvc' globally."
fi
if [[ "$BIN_DIR" == "/usr/local/bin" ]]; then
case ":$PATH:" in
*":$BIN_DIR:"*) ;;
*) echo "Add $BIN_DIR to your PATH to run 'dsvc' globally." ;;
esac
if [ "$BIN_DIR" = "/usr/local/bin" ]; then
echo "Command is available system-wide as 'dsvc'."
fi
echo "Run: dsvc --help"
Expand Down
19 changes: 9 additions & 10 deletions scripts/uninstall.sh
Original file line number Diff line number Diff line change
@@ -1,31 +1,30 @@
#!/usr/bin/env bash
set -euo pipefail
#!/bin/sh
set -eu
(set -o pipefail) 2>/dev/null && set -o pipefail

RUNTIME_ROOT="${XDG_DATA_HOME:-$HOME/.local/share}/dsvc"
DEFAULT_USER_BIN="$HOME/.local/bin"
CONFIG_DIR="$HOME/.dsv_client"

remove_launchers() {
local -a candidates=("/usr/local/bin/dsvc" "${DEFAULT_USER_BIN}/dsvc")
local candidate=""
local removed_count=0
removed_count=0

for candidate in "${candidates[@]}"; do
if [[ -e "$candidate" || -L "$candidate" ]]; then
for candidate in /usr/local/bin/dsvc "$DEFAULT_USER_BIN/dsvc"; do
if [ -e "$candidate" ] || [ -L "$candidate" ]; then
rm -f "$candidate" && {
echo "Removed launcher: $candidate"
removed_count=$((removed_count + 1))
}
fi
done

if (( removed_count == 0 )); then
if [ "$removed_count" -eq 0 ]; then
echo "Launcher not found in expected paths."
fi
}

remove_config() {
if [[ -d "$CONFIG_DIR" ]]; then
if [ -d "$CONFIG_DIR" ]; then
rm -rf "$CONFIG_DIR"
echo "Removed config directory: $CONFIG_DIR"
else
Expand All @@ -36,7 +35,7 @@ remove_config() {
main() {
remove_launchers

if [[ -d "$RUNTIME_ROOT" ]]; then
if [ -d "$RUNTIME_ROOT" ]; then
rm -rf "$RUNTIME_ROOT"
echo "Removed runtime: $RUNTIME_ROOT"
else
Expand Down
Loading
Loading