|
90 | 90 | fi |
91 | 91 | _log "mios-bootstrap repo: $BOOT_REPO" |
92 | 92 |
|
93 | | -# ── Identity ───────────────────────────────────────────────────────────────── |
94 | | -# /etc/mios/install.env is written by the Windows side BEFORE handoff |
95 | | -# (Phase 7 in build-mios.ps1) so the prompts already happened on the |
96 | | -# Windows side this round. Future migration chunks move the prompts |
97 | | -# in here so the operator answers them in the MiOS-DEV terminal -- |
98 | | -# NOT in Windows -- per the architecture memo. |
99 | | -# ── Identity capture ───────────────────────────────────────────────────────── |
100 | | -# Per the self-replication architecture (memo: |
101 | | -# project_mios_self_replication_vision.md), identity prompts run INSIDE |
102 | | -# MiOS-DEV's tty -- not on the Windows side. This is migration chunk 3: |
103 | | -# port what was build-mios.ps1's Phase 6 (Read-Line / Read-Password / |
104 | | -# Read-Model) into bash here. |
105 | | -# |
106 | | -# If /etc/mios/install.env already has all the required keys (set by |
107 | | -# either a previous driver invocation or, transitionally, by the |
108 | | -# Windows-side Phase 7 code that's still in place pending its removal |
109 | | -# in chunk 6), source it and skip prompts. Otherwise capture |
110 | | -# interactively. |
111 | | -_capture_identity() { |
112 | | - echo |
113 | | - echo " +-- MiOS Identity ----------------------------------------+" |
114 | | - echo " | Settings persist to /etc/mios/install.env. Defaults |" |
115 | | - echo " | in [brackets]; press Enter to accept. |" |
116 | | - echo " +---------------------------------------------------------+" |
117 | | - |
118 | | - read -r -p " Linux username [mios]: " MIOS_USER |
119 | | - : "${MIOS_USER:=mios}" |
120 | | - |
121 | | - read -r -p " Hostname [mios]: " MIOS_HOSTNAME |
122 | | - : "${MIOS_HOSTNAME:=mios}" |
123 | | - |
124 | | - # Password capture: silent (-s) read, default to literal 'mios' if blank. |
125 | | - read -rsp " Password (sha512crypt-hashed) [mios]: " _pw_plain |
126 | | - echo |
127 | | - : "${_pw_plain:=mios}" |
128 | | - |
129 | | - # Hash via mkpasswd if available, else python3 (always present on |
130 | | - # FCOS-based MiOS-DEV), else last-resort openssl. The result is what |
131 | | - # /usr/lib/sysusers.d/10-mios.conf reads at first boot to set the |
132 | | - # mios user's password without ever materializing the cleartext |
133 | | - # anywhere on disk. |
134 | | - if command -v mkpasswd >/dev/null 2>&1; then |
135 | | - MIOS_USER_PASSWORD_HASH=$(mkpasswd -m sha-512 "$_pw_plain") |
136 | | - elif command -v python3 >/dev/null 2>&1; then |
137 | | - MIOS_USER_PASSWORD_HASH=$(python3 -c 'import crypt,sys;print(crypt.crypt(sys.argv[1], crypt.METHOD_SHA512))' "$_pw_plain") |
138 | | - elif command -v openssl >/dev/null 2>&1; then |
139 | | - MIOS_USER_PASSWORD_HASH=$(openssl passwd -6 "$_pw_plain") |
140 | | - else |
141 | | - _log "WARN: no password-hash tool available (mkpasswd / python3 / openssl all missing) -- storing plaintext" |
142 | | - MIOS_USER_PASSWORD_HASH="$_pw_plain" |
143 | | - fi |
144 | | - unset _pw_plain |
145 | | - |
146 | | - # AI model menu -- default set sized for the 12 GB system-RAM |
147 | | - # baseline. The 14b option is for hosts with 24+ GB RAM that can |
148 | | - # comfortably run a larger code model in CPU-only inference. |
149 | | - echo |
150 | | - echo " AI model:" |
151 | | - echo " 1) qwen2.5-coder:7b [default; ~5 GB on disk, 12 GB host RAM]" |
152 | | - echo " 2) qwen2.5-coder:14b [~10 GB on disk, 24 GB host RAM]" |
153 | | - echo " 3) llama3.2:3b [low-RAM / fast-response profile]" |
154 | | - echo " 4) custom [type your own ollama model id]" |
155 | | - read -r -p " Choice [1]: " _model_choice |
156 | | - case "${_model_choice:-1}" in |
157 | | - 2) MIOS_AI_MODEL="qwen2.5-coder:14b" ;; |
158 | | - 3) MIOS_AI_MODEL="llama3.2:3b" ;; |
159 | | - 4) read -r -p " Custom model id (e.g. mistral:7b): " MIOS_AI_MODEL |
160 | | - : "${MIOS_AI_MODEL:=qwen2.5-coder:7b}" ;; |
161 | | - *) MIOS_AI_MODEL="qwen2.5-coder:7b" ;; |
162 | | - esac |
163 | | - |
164 | | - read -r -p " AI embedding model [nomic-embed-text]: " MIOS_AI_EMBED_MODEL |
165 | | - : "${MIOS_AI_EMBED_MODEL:=nomic-embed-text}" |
166 | | - |
167 | | - MIOS_OLLAMA_BAKE_MODELS="${MIOS_AI_MODEL},${MIOS_AI_EMBED_MODEL}" |
168 | | - |
169 | | - # Persist. install.env is sourced by /etc/profile.d/mios-env.sh |
170 | | - # at every interactive login + by automation/build.sh during the |
171 | | - # OCI build, so writing it here propagates to every downstream |
172 | | - # consumer without further action. |
173 | | - sudo install -d -m 0755 /etc/mios |
174 | | - { |
175 | | - printf 'MIOS_USER="%s"\n' "$MIOS_USER" |
176 | | - printf 'MIOS_HOSTNAME="%s"\n' "$MIOS_HOSTNAME" |
177 | | - printf 'MIOS_USER_PASSWORD_HASH="%s"\n' "$MIOS_USER_PASSWORD_HASH" |
178 | | - printf 'MIOS_AI_MODEL="%s"\n' "$MIOS_AI_MODEL" |
179 | | - printf 'MIOS_AI_EMBED_MODEL="%s"\n' "$MIOS_AI_EMBED_MODEL" |
180 | | - printf 'MIOS_OLLAMA_BAKE_MODELS="%s"\n' "$MIOS_OLLAMA_BAKE_MODELS" |
181 | | - } | sudo tee /etc/mios/install.env >/dev/null |
182 | | - sudo chmod 0640 /etc/mios/install.env |
183 | | - _log "identity persisted: user=$MIOS_USER host=$MIOS_HOSTNAME ai=$MIOS_AI_MODEL embed=$MIOS_AI_EMBED_MODEL" |
184 | | - |
185 | | - export MIOS_USER MIOS_HOSTNAME MIOS_USER_PASSWORD_HASH \ |
186 | | - MIOS_AI_MODEL MIOS_AI_EMBED_MODEL MIOS_OLLAMA_BAKE_MODELS |
187 | | -} |
188 | | - |
189 | | -if [[ -f /etc/mios/install.env ]] \ |
190 | | - && grep -q '^MIOS_USER=' /etc/mios/install.env \ |
191 | | - && grep -q '^MIOS_HOSTNAME=' /etc/mios/install.env \ |
192 | | - && grep -q '^MIOS_AI_MODEL=' /etc/mios/install.env; then |
193 | | - _log "identity loaded from /etc/mios/install.env" |
194 | | - # shellcheck disable=SC1091 |
195 | | - source /etc/mios/install.env |
196 | | -else |
197 | | - _log "no usable /etc/mios/install.env -- prompting for identity" |
198 | | - if [[ "${MIOS_NONINTERACTIVE:-0}" == "1" ]]; then |
199 | | - _log "MIOS_NONINTERACTIVE=1 -- using vendor defaults instead of prompting" |
200 | | - MIOS_USER='mios' |
201 | | - MIOS_HOSTNAME='mios' |
202 | | - MIOS_AI_MODEL='qwen2.5-coder:7b' |
203 | | - MIOS_AI_EMBED_MODEL='nomic-embed-text' |
204 | | - MIOS_OLLAMA_BAKE_MODELS="${MIOS_AI_MODEL},${MIOS_AI_EMBED_MODEL}" |
205 | | - export MIOS_USER MIOS_HOSTNAME MIOS_AI_MODEL MIOS_AI_EMBED_MODEL MIOS_OLLAMA_BAKE_MODELS |
206 | | - else |
207 | | - _capture_identity |
208 | | - fi |
209 | | -fi |
| 93 | +# CRITICAL ORDERING: Configurator FIRST (operator edits the toml in |
| 94 | +# Epiphany), then Resolve (sources the edited toml). Don't reverse; |
| 95 | +# resolving before the operator has edited would lock in stale values. |
210 | 96 |
|
211 | 97 | # ── Configurator step: Epiphany on /configurator.html ──────────────────────── |
212 | 98 | # Per the self-replication-vision memo: |
@@ -281,6 +167,123 @@ else |
281 | 167 | _log "WARN: /usr/libexec/mios/mios-configurator-launch not present -- skipping configurator step" |
282 | 168 | fi |
283 | 169 |
|
| 170 | +# ── Resolve all settings from the layered mios.toml chain ─────────────────── |
| 171 | +# Runs AFTER the configurator step so the operator's edits in Epiphany |
| 172 | +# (now landed at /etc/mios/mios.toml) are sourced into env. |
| 173 | +# |
| 174 | +# CANONICAL: identity, AI choices, locale, image, network, desktop, |
| 175 | +# packages-to-layer (every [packages.*] enable flag), hosting/dev/build/ |
| 176 | +# run profile flags -- EVERY operator-visible setting comes from the |
| 177 | +# layered mios.toml, edited via /configurator.html. The driver does NOT |
| 178 | +# prompt for any of these values; they're set in the toml. |
| 179 | +# |
| 180 | +# Layer chain (highest wins): |
| 181 | +# /usr/share/mios/mios.toml vendor defaults baked into the image |
| 182 | +# /etc/mios/mios.toml host overlay (configurator promotes |
| 183 | +# ~/Downloads/mios.toml here when the |
| 184 | +# operator clicks Save in Epiphany) |
| 185 | +# ~/.config/mios/mios.toml per-user XDG overlay |
| 186 | +# |
| 187 | +# tools/lib/userenv.sh resolves the chain, exports MIOS_* env vars, and |
| 188 | +# is the single canonical reader. Source it so the entire build pipeline |
| 189 | +# downstream (automation/build.sh, sysusers, tmpfiles, profile.d, |
| 190 | +# Containerfile build args) inherits the resolved values without |
| 191 | +# re-parsing toml in 12 different ways. |
| 192 | +USERENV_SH="" |
| 193 | +for cand in \ |
| 194 | + /tools/lib/userenv.sh \ |
| 195 | + "${MIOS_REPO:-}/tools/lib/userenv.sh" \ |
| 196 | + /usr/share/mios/lib/userenv.sh \ |
| 197 | +; do |
| 198 | + if [[ -n "$cand" && -r "$cand" ]]; then USERENV_SH="$cand"; break; fi |
| 199 | +done |
| 200 | +if [[ -n "$USERENV_SH" ]]; then |
| 201 | + _log "Sourcing layered config: $USERENV_SH" |
| 202 | + # shellcheck disable=SC1090 |
| 203 | + source "$USERENV_SH" |
| 204 | +else |
| 205 | + _log "WARN: tools/lib/userenv.sh not found -- using vendor-default env" |
| 206 | +fi |
| 207 | + |
| 208 | +# Vendor-shipped fallbacks for any MIOS_* the toml didn't define. Never |
| 209 | +# prompt -- the principle is "configurator is SSOT". Missing means the |
| 210 | +# operator hasn't edited that field and the vendor default applies. |
| 211 | +: "${MIOS_USER:=mios}" |
| 212 | +: "${MIOS_HOSTNAME:=mios}" |
| 213 | +: "${MIOS_AI_MODEL:=qwen2.5-coder:7b}" |
| 214 | +: "${MIOS_AI_EMBED_MODEL:=nomic-embed-text}" |
| 215 | +: "${MIOS_OLLAMA_BAKE_MODELS:=${MIOS_AI_MODEL},${MIOS_AI_EMBED_MODEL}}" |
| 216 | +: "${MIOS_PASSWORD_POLICY:=interactive}" |
| 217 | +export MIOS_USER MIOS_HOSTNAME MIOS_AI_MODEL MIOS_AI_EMBED_MODEL \ |
| 218 | + MIOS_OLLAMA_BAKE_MODELS MIOS_PASSWORD_POLICY |
| 219 | + |
| 220 | +# Password handling. The toml schema says password_hash is NEVER |
| 221 | +# tracked in a checked-in copy. Three policies: |
| 222 | +# * interactive: prompt once if no /etc/mios/secrets.env exists -- |
| 223 | +# the ONLY operator prompt; everything else came from the toml |
| 224 | +# * hashed : use literal MIOS_USER_PASSWORD_HASH already in env |
| 225 | +# (operator pasted a sha512crypt hash into the |
| 226 | +# configurator's password field) |
| 227 | +# * none : skip password setup; useful for kiosk / CI |
| 228 | +case "${MIOS_PASSWORD_POLICY}" in |
| 229 | + none) |
| 230 | + _log "MIOS_PASSWORD_POLICY=none -- no password set" |
| 231 | + MIOS_USER_PASSWORD_HASH="" |
| 232 | + ;; |
| 233 | + hashed) |
| 234 | + if [[ -z "${MIOS_USER_PASSWORD_HASH:-}" ]]; then |
| 235 | + _log "WARN: password_policy=hashed but MIOS_USER_PASSWORD_HASH is empty -- falling back to 'mios'" |
| 236 | + MIOS_USER_PASSWORD_HASH=$(python3 -c 'import crypt;print(crypt.crypt("mios", crypt.METHOD_SHA512))') |
| 237 | + fi |
| 238 | + ;; |
| 239 | + interactive|*) |
| 240 | + if [[ -r /etc/mios/secrets.env ]] && grep -q '^MIOS_USER_PASSWORD_HASH=' /etc/mios/secrets.env; then |
| 241 | + _log "password hash loaded from /etc/mios/secrets.env" |
| 242 | + # shellcheck disable=SC1091 |
| 243 | + source /etc/mios/secrets.env |
| 244 | + elif [[ "${MIOS_NONINTERACTIVE:-0}" == "1" ]]; then |
| 245 | + _log "MIOS_NONINTERACTIVE=1 -- defaulting password to 'mios'" |
| 246 | + MIOS_USER_PASSWORD_HASH=$(python3 -c 'import crypt;print(crypt.crypt("mios", crypt.METHOD_SHA512))') |
| 247 | + else |
| 248 | + echo |
| 249 | + echo " password_policy=interactive and no /etc/mios/secrets.env yet." |
| 250 | + echo " This is the ONLY operator prompt -- everything else came from" |
| 251 | + echo " /mios.toml (edited via the Epiphany configurator above)." |
| 252 | + read -rsp " Set password for ${MIOS_USER} (default: 'mios'): " _pw_plain |
| 253 | + echo |
| 254 | + : "${_pw_plain:=mios}" |
| 255 | + if command -v mkpasswd >/dev/null 2>&1; then |
| 256 | + MIOS_USER_PASSWORD_HASH=$(mkpasswd -m sha-512 "$_pw_plain") |
| 257 | + else |
| 258 | + MIOS_USER_PASSWORD_HASH=$(python3 -c 'import crypt,sys;print(crypt.crypt(sys.argv[1], crypt.METHOD_SHA512))' "$_pw_plain") |
| 259 | + fi |
| 260 | + unset _pw_plain |
| 261 | + sudo install -d -m 0755 /etc/mios |
| 262 | + printf 'MIOS_USER_PASSWORD_HASH="%s"\n' "$MIOS_USER_PASSWORD_HASH" | |
| 263 | + sudo tee /etc/mios/secrets.env >/dev/null |
| 264 | + sudo chmod 0600 /etc/mios/secrets.env |
| 265 | + fi |
| 266 | + ;; |
| 267 | +esac |
| 268 | +export MIOS_USER_PASSWORD_HASH |
| 269 | + |
| 270 | +# Persist a flat install.env for downstream consumers that don't grok |
| 271 | +# layered toml directly: /etc/profile.d/mios-env.sh, automation/build.sh |
| 272 | +# during `podman build`, sysusers/tmpfiles generators. install.env |
| 273 | +# mirrors a subset of the resolved env. |
| 274 | +sudo install -d -m 0755 /etc/mios |
| 275 | +{ |
| 276 | + printf 'MIOS_USER="%s"\n' "$MIOS_USER" |
| 277 | + printf 'MIOS_HOSTNAME="%s"\n' "$MIOS_HOSTNAME" |
| 278 | + printf 'MIOS_USER_PASSWORD_HASH="%s"\n' "$MIOS_USER_PASSWORD_HASH" |
| 279 | + printf 'MIOS_AI_MODEL="%s"\n' "$MIOS_AI_MODEL" |
| 280 | + printf 'MIOS_AI_EMBED_MODEL="%s"\n' "$MIOS_AI_EMBED_MODEL" |
| 281 | + printf 'MIOS_OLLAMA_BAKE_MODELS="%s"\n' "$MIOS_OLLAMA_BAKE_MODELS" |
| 282 | +} | sudo tee /etc/mios/install.env >/dev/null |
| 283 | +sudo chmod 0640 /etc/mios/install.env |
| 284 | + |
| 285 | +_log "Resolved settings: user=$MIOS_USER host=$MIOS_HOSTNAME ai=$MIOS_AI_MODEL embed=$MIOS_AI_EMBED_MODEL pwpolicy=$MIOS_PASSWORD_POLICY" |
| 286 | + |
284 | 287 | # ── Build invocation ───────────────────────────────────────────────────────── |
285 | 288 | # automation/build.sh expects to run inside a `podman build` against the |
286 | 289 | # Containerfile. Outside that context it's a no-op driver entry. The |
|
0 commit comments