Repository navigation
Expand file tree
/
Copy pathsafe-env.sh
More file actions
executable file
·187 lines (178 loc) · 7.41 KB
/
Copy pathsafe-env.sh
File metadata and controls
executable file
·187 lines (178 loc) · 7.41 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
#!/usr/bin/env bash
# ============================================================================
# ODS — Safe environment loading (no eval)
# ============================================================================
# Scripts that need to load .env should use load_env_file from this script.
# Do not use eval or "export $(grep ... .env | xargs)" — they allow injection.
#
# - load_env_file <path> — parse a .env file and export vars (safe keys, no eval)
# - load_env_from_output — parse KEY="value" lines from stdin (for script output)
# ============================================================================
# Load a .env file safely: comments and empty lines skipped; key names must be
# valid identifiers; values may be unquoted or quoted; no eval or word-splitting.
# A quoted value followed by whitespace and '#': group 1 is the quoted part.
_SAFE_ENV_DQ_COMMENT_RE='^("(\\.|[^"\\])*")[[:space:]]+#'
_SAFE_ENV_SQ_COMMENT_RE="^('[^']*')[[:space:]]+#"
load_env_file() {
local path="$1"
[[ -f "$path" ]] || return 0
local line key value
while IFS= read -r line || [[ -n "$line" ]]; do
# Strip a trailing CR so CRLF .env files (Windows editors, the Windows
# installer) don't leave carriage returns on every value — which would
# otherwise corrupt ports/paths (e.g. 8080\r) and leave the closing
# quote unstripped on quoted values. Matches load_env_from_output.
line="${line%$'\r'}"
# Skip comments and blank lines
[[ "$line" =~ ^[[:space:]]*# ]] && continue
[[ "$line" =~ ^[[:space:]]*$ ]] && continue
# Lines without '=' are not valid KEY=VALUE pairs
[[ "$line" == *=* ]] || continue
# Split on first '=' only, preserve '=' in values (e.g. base64 padding)
key="${line%%=*}"
value="${line#*=}"
# Trim whitespace from key
key="${key#"${key%%[![:space:]]*}"}"
key="${key%"${key##*[![:space:]]}"}"
[[ -z "$key" ]] && continue
[[ "$key" =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]] || continue
# Bash exposes UID as a readonly shell variable. A .env line such as
# UID=1000 is valid for Docker Compose, but exporting it here aborts
# lifecycle commands under set -e before they can reach compose.
[[ "$key" == "UID" ]] && continue
_safe_env_decode_value "$value"
export "$key=$_SAFE_ENV_VALUE"
done < "$path"
}
# Decode one raw .env value (the text after the first '=') into its literal
# value and store it in _SAFE_ENV_VALUE. The result is returned in a variable,
# not printed, so load_env_file does not fork a subshell for every line.
_safe_env_decode_value() {
local value="${1%$'\r'}"
# Apply Docker Compose's value grammar before looking at quotes: these
# values are exported into the environment Compose interpolates, and
# Compose gives the environment precedence over .env. Surrounding
# whitespace is trimmed; an unquoted value ends at the first " #"; a
# quoted value may carry a comment after its closing quote.
# KEY=VAL#x and KEY="a # b" keep their '#'.
value="${value#"${value%%[![:space:]]*}"}"
value="${value%"${value##*[![:space:]]}"}"
case "$value" in
\"*) [[ "$value" =~ $_SAFE_ENV_DQ_COMMENT_RE ]] && value="${BASH_REMATCH[1]}" ;;
\'*) [[ "$value" =~ $_SAFE_ENV_SQ_COMMENT_RE ]] && value="${BASH_REMATCH[1]}" ;;
*)
# Cut at the first " #" of the already-trimmed value, then trim
# the exposed trailing whitespace. Compose trims leading
# whitespace before this cut, so "KEY= # x" becomes "# x"
# (the "#" is no longer preceded by a space) and stays literal.
value="${value%% #*}"
value="${value%"${value##*[![:space:]]}"}"
;;
esac
# Then strip a single matching pair of surrounding quotes. Only strip
# when both ends carry the SAME quote character — stripping each quote
# type independently corrupts values whose content legitimately begins
# or ends with the other quote (e.g. a double-quoted "'literal'" would
# otherwise lose its inner single quotes, and KEY="'" would collapse
# to empty).
if [[ "$value" == '"'*'"' ]]; then
value="${value#\"}"
value="${value%\"}"
value="$(_safe_env_unescape_double_quoted "$value")"
elif [[ "$value" == "'"*"'" ]]; then
value="${value#\'}"
value="${value%\'}"
fi
_SAFE_ENV_VALUE="$value"
}
# Print the literal value of one raw .env value, decoded exactly as
# load_env_file decodes it. Use this when a single existing value is read and
# written back, so quoting and inline comments survive the round trip.
safe_env_decode_value() {
_safe_env_decode_value "$1"
printf '%s' "$_SAFE_ENV_VALUE"
}
_safe_env_unescape_double_quoted() {
local value="$1"
# Decode the small shell-compatible escape set emitted by repository
# helper scripts. This is parsing, not eval: no command substitution,
# expansion, globbing, or word splitting is performed.
value="${value//\\\"/\"}"
value="${value//\\\$/\$}"
value="${value//\\\`/\`}"
value="${value//\\\\/\\}"
printf '%s' "$value"
}
_safe_env_key_allowed() {
local key="$1"
shift || true
local allowed
for allowed in "$@"; do
[[ "$key" == "$allowed" ]] && return 0
done
return 1
}
load_env_from_output() {
local line key value
while IFS= read -r line; do
line="${line%$'\r'}"
[[ -z "$line" || "$line" =~ ^[[:space:]]*# ]] && continue
if [[ "$line" =~ ^([A-Za-z_][A-Za-z0-9_]*)=\"(.*)\"$ ]]; then
key="${BASH_REMATCH[1]}"
value="$(_safe_env_unescape_double_quoted "${BASH_REMATCH[2]}")"
export "$key=$value"
fi
done
}
load_env_from_output_allowlist() {
local line key value
[[ "$#" -gt 0 ]] || return 0
while IFS= read -r line; do
line="${line%$'\r'}"
[[ -z "$line" || "$line" =~ ^[[:space:]]*# ]] && continue
if [[ "$line" =~ ^([A-Za-z_][A-Za-z0-9_]*)=\"(.*)\"$ ]]; then
key="${BASH_REMATCH[1]}"
_safe_env_key_allowed "$key" "$@" || continue
value="$(_safe_env_unescape_double_quoted "${BASH_REMATCH[2]}")"
export "$key=$value"
fi
done
}
load_model_selector_env_from_output() {
load_env_from_output_allowlist \
LLM_MODEL \
GGUF_FILE \
GGUF_URL \
GGUF_SHA256 \
MAX_CONTEXT \
LLM_MODEL_SIZE_MB \
MODEL_RECOMMENDATION_SOURCE \
MODEL_RECOMMENDATION_POLICY \
MODEL_RECOMMENDATION_CONFIDENCE \
MODEL_RECOMMENDATION_REASON \
MODEL_RECOMMENDED_ALTERNATIVES \
MODEL_RUNTIME_PROFILE \
MODEL_RUNTIME_PROFILE_LABEL \
MODEL_RUNTIME_PROFILE_SOURCE \
MODEL_SELECTION_SOURCE \
PIXEL_AGENT_MODEL_READY \
ODS_ACTIVE_MODEL_STORE \
LLAMA_SERVER_IMAGE \
LLAMA_SERVER_MEMORY_LIMIT \
LLAMA_CPP_RELEASE_TAG_OVERRIDE \
LLAMA_CPP_SERVER_BINARY \
LLAMA_ARG_FLASH_ATTN \
LLAMA_ARG_CACHE_TYPE_K \
LLAMA_ARG_CACHE_TYPE_V \
LLAMA_ARG_N_CPU_MOE \
LLAMA_ARG_NO_CACHE_PROMPT \
LLAMA_ARG_CHECKPOINT_EVERY_NT \
LLAMA_ARG_CTX_CHECKPOINTS \
LLAMA_ARG_CACHE_RAM \
LLAMA_ARG_SPEC_TYPE \
LLAMA_ARG_SPEC_DRAFT_N_MAX \
LLAMA_ARG_SPEC_DRAFT_TYPE_K \
LLAMA_ARG_SPEC_DRAFT_TYPE_V \
LLAMA_ARG_SPLIT_MODE \
LLAMA_ARG_TENSOR_SPLIT
}