Skip to content

Commit 52068c8

Browse files
author
Loki
committed
fix: address Codex review — 6 fixes for v2 installer
Based on full review of rc11..rc19 changes: 1. ENVIRONMENT NAME PERSISTENCE: Read from terraform state first, then .loki-env file, then generate new suffix. Prevents destroying old resources on reinstall from same checkout. 2. PACK/PROFILE SELECTION: PacksLoaded and ProfilesLoaded no longer auto-chain to the next action. They set the cursor on the default and render the screen, so user can see and change the selection before pressing Enter. 3. UTF-8 SAFE TRUNCATION: Deploy screen log truncation now uses chars().take() instead of byte slicing to avoid panics on multibyte characters. 4. ARTIFACT LOG GARBLING: ArtifactRecorded values now have newlines collapsed to spaces before display. Strip \r from terraform output. 5. BEDROCK FORM: Reverted enable_satellite_services to true (security services should run on first install). Instead, gated bedrock form invoke on request_quota_increases == true (defaults to false). 6. STACK NAME IN POST-INSTALL: Record environment_name as stack_name artifact so post-install screen shows the actual deployed name.
1 parent b88cf16 commit 52068c8

File tree

10 files changed

+208
-27
lines changed

10 files changed

+208
-27
lines changed

REVIEW.md

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
# Review: `v0.2.0-rc11..v0.2.0-rc19`
2+
3+
## CRITICAL
4+
5+
No critical findings in this range.
6+
7+
## HIGH
8+
9+
### 1. `enable_satellite_services` is now effectively hard-disabled for all Terraform installs
10+
11+
Files:
12+
- `deploy/terraform/variables.tf:206`
13+
- `tools/loki-installer/src/adapters/terraform.rs:444`
14+
- `methods/terraform.yaml:12`
15+
- `packs/openclaw/manifest.yaml:19`
16+
17+
What changed:
18+
- `enable_satellite_services` now defaults to `false` in Terraform.
19+
- The adapter can only pass `-var=enable_satellite_services=...` if `adapter_options` contains that key.
20+
- Nothing in the Terraform method manifest or pack manifests exposes `enable_satellite_services` as a user-selectable or defaulted option.
21+
22+
Impact:
23+
- Fresh Terraform installs will now skip the entire satellite-services path by default, including the Bedrock form Lambda, security enablement Lambda, and builder admin-user setup.
24+
- The description in `variables.tf` says this should be disabled only for reinstalls or existing-VPC cases, but the current wiring makes `false` the only reachable value.
25+
26+
Why this is a bug:
27+
- This is not just a safer default. It is a behavioral regression that silently removes resources from every Terraform deployment.
28+
29+
Recommended fix:
30+
- Keep the Terraform variable default aligned with the intended default behavior for first installs.
31+
- If you want reinstall-specific behavior, derive it in the installer and pass it explicitly, instead of changing the Terraform default to an unreachable value.
32+
33+
### 2. The environment-name persistence fix is repo-global and does not actually solve reinstall/state continuity safely
34+
35+
Files:
36+
- `tools/loki-installer/src/adapters/terraform.rs:196`
37+
38+
What changed:
39+
- When `resolved_stack_name` is absent, Terraform now reads `deploy/terraform/.loki-env` and reuses `ENVIRONMENT_NAME`.
40+
- If the file does not exist, it generates a random suffix and immediately writes it back.
41+
42+
Impact:
43+
- All future Terraform installs from the same checkout share one repo-local environment name, regardless of pack, profile, region, AWS account, or workspace.
44+
- A second install from the same repo can unintentionally target the first install’s naming namespace.
45+
- A reinstall from a fresh checkout still generates a new name, so the original “destroy old resources on reinstall” problem is not actually solved across machines or clones.
46+
47+
Why this is a bug:
48+
- The fix stores identity in an unscoped local sidecar file rather than in deployment state.
49+
- That avoids churn only in the narrow “same checkout, same repo dir” case, and creates cross-install collisions in the broader case.
50+
51+
Recommended fix:
52+
- Prefer existing Terraform state first, not a random local file.
53+
- Practical order:
54+
1. If the user supplied `stack_name`, use it.
55+
2. If Terraform state already exists for the selected workspace, read the prior `environment_name` from state before planning.
56+
3. Only if no state exists, generate a name once and persist it.
57+
- If you keep a local file fallback, key it by at least workspace + pack + profile + region + AWS account, not a single repo-wide `.loki-env`.
58+
59+
### 3. Deploy log truncation is not safe and likely does not fix the reported garbling reliably
60+
61+
Files:
62+
- `tools/loki-installer/src/tui/screens/deploy.rs:15`
63+
- `tools/loki-installer/src/tui/runtime.rs:334`
64+
65+
What changed:
66+
- The deploy screen now truncates log lines using `content_with_width`.
67+
- Truncation is done with `line.len()` and `&line[..max_line]`.
68+
69+
Impact:
70+
- `len()` is byte length, not terminal display width.
71+
- `&line[..max_line]` can panic if `max_line` lands inside a multibyte UTF-8 character.
72+
- This code uses the outer widget width (`body[1].width`) rather than the inner content width after borders, so even ASCII truncation is off by the block chrome.
73+
74+
Why this is a bug:
75+
- The current implementation is not width-aware and is not UTF-8 safe.
76+
- That means it can still wrap badly, and with the wrong input it can crash instead of merely truncating.
77+
78+
Recommended fix:
79+
- Measure display width with `unicode-width`.
80+
- Truncate by `chars()` or grapheme boundaries, not raw byte slicing.
81+
- Use the inner paragraph width, not the block width, when computing available columns.
82+
83+
## MEDIUM
84+
85+
### 4. Simple mode still does not let the user see or change the preselected pack/profile before the plan is built
86+
87+
Files:
88+
- `tools/loki-installer/src/tui/update.rs:280`
89+
- `tools/loki-installer/src/tui/update.rs:307`
90+
- `tools/loki-installer/src/tui/runtime.rs:93`
91+
92+
What happens:
93+
- In simple mode, `DoctorPreflight -> LoadPacks`.
94+
- `PacksLoaded` auto-selects `openclaw` and returns `LoadProfiles`.
95+
- `ProfilesLoaded` auto-selects `builder` and returns `LoadMethods`.
96+
- In `ProfileSelection`, simple mode immediately returns `BuildPlan`.
97+
- `run_actions()` drains this queue synchronously in one pass.
98+
99+
Impact:
100+
- The user does not get an interactive stop on pack or profile selection.
101+
- They jump straight from doctor to review.
102+
- The only way to change pack/profile is later, from review, by pressing `A` to switch modes and restart selection.
103+
104+
Answer to the question:
105+
- No, not in the current simple-mode runtime flow.
106+
107+
Recommended fix:
108+
- Decide which behavior you want:
109+
- If simple mode should still be editable, stop the queue on the pack/profile screens and wait for input.
110+
- If simple mode should be non-editable, make that explicit in the UX and present the chosen defaults only on review.
111+
112+
### 5. The new post-install summary will often show `Stack: <environment_name>` for Terraform installs
113+
114+
Files:
115+
- `tools/loki-installer/src/tui/screens/post_install.rs:25`
116+
- `methods/terraform.yaml:5`
117+
- `tools/loki-installer/src/adapters/terraform.rs:380`
118+
119+
What changed:
120+
- The post-install screen now tries `resolved_stack_name`, then `artifacts["stack_name"]`, then `request.stack_name`.
121+
- For Terraform, `requires_stack_name` is `false`, so `resolved_stack_name` is normally `None`.
122+
- The Terraform adapter records outputs like `instance_id`/`public_ip`, but it does not record the chosen `environment_name` as an artifact.
123+
124+
Impact:
125+
- Successful Terraform installs can land on the new completion screen with a placeholder instead of the actual deployed environment name.
126+
127+
Recommended fix:
128+
- Record the final `environment_name` into session artifacts during Terraform apply/output capture, and have post-install read that artifact explicitly.
129+
130+
## LOW
131+
132+
### 6. The `admin_setup_basic` count mismatch was fixed correctly
133+
134+
Files:
135+
- `deploy/terraform/main.tf:638`
136+
137+
Assessment:
138+
- The new `count = var.enable_satellite_services && var.profile_name == "builder" ? 1 : 0` now matches the producer resource `aws_iam_role.admin_setup_lambda[0]`.
139+
- I did not find a remaining count/index mismatch in this specific block after the change.

deploy/terraform/main.tf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,7 @@ resource "aws_lambda_function" "bedrock_form" {
347347
}
348348

349349
resource "null_resource" "bedrock_form_invoke" {
350-
count = var.enable_satellite_services ? 1 : 0
350+
count = var.enable_satellite_services && var.request_quota_increases == "true" ? 1 : 0
351351
depends_on = [aws_lambda_function.bedrock_form]
352352

353353
provisioner "local-exec" {

deploy/terraform/variables.tf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ variable "request_quota_increases" {
205205

206206
variable "enable_satellite_services" {
207207
type = bool
208-
default = false
208+
default = true
209209
description = "Enable satellite services (security monitoring, admin user, bedrock form lambda). Set false for re-installs or existing VPC deployments."
210210
}
211211

tools/loki-installer/Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tools/loki-installer/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "loki-installer"
3-
version = "0.2.0-rc19"
3+
version = "0.2.0-rc20"
44
edition = "2024"
55
description = "Rust implementation of the Loki Installer V2 CLI, planner, TUI, and session runtime"
66
license = "Apache-2.0"

tools/loki-installer/src/adapters/support.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,10 @@ pub(crate) async fn run_command_streaming(
166166
fn strip_ansi(input: &str) -> String {
167167
let re = Regex::new(r"\x1b\[[0-9;]*[a-zA-Z]").unwrap();
168168
let cleaned = re.replace_all(input, "");
169-
cleaned.replace(['│', '╵', '╷'], "").trim().to_string()
169+
cleaned
170+
.replace(['│', '╵', '╷'], "").replace('\r', "")
171+
.trim()
172+
.to_string()
170173
}
171174

172175
pub(crate) fn spawn_child(spec: &CommandSpec) -> Result<tokio::process::Child, AdapterError> {

tools/loki-installer/src/adapters/terraform.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,9 +197,29 @@ impl TerraformContext {
197197
.resolved_stack_name
198198
.clone()
199199
.unwrap_or_else(|| {
200+
// 1. Try reading from existing terraform state
201+
if let Some(name) = read_env_name_from_tf_state(&working_dir) {
202+
return name;
203+
}
204+
// 2. Try reading from .loki-env (persisted from previous install)
205+
let env_file = Path::new(&working_dir).join(".loki-env");
206+
if let Ok(contents) = fs::read_to_string(&env_file) {
207+
for line in contents.lines() {
208+
if let Some(name) = line.strip_prefix("ENVIRONMENT_NAME=") {
209+
let name = name.trim();
210+
if !name.is_empty() {
211+
return name.to_string();
212+
}
213+
}
214+
}
215+
}
216+
// 3. Generate a new unique name
200217
let suffix = &uuid::Uuid::new_v4().to_string()[..8];
201218
format!("loki-{}-{suffix}", plan.resolved_pack.id)
202219
});
220+
// Persist environment name for future re-installs
221+
let env_file = Path::new(&working_dir).join(".loki-env");
222+
let _ = fs::write(&env_file, format!("ENVIRONMENT_NAME={environment_name}\n"));
203223

204224
Ok(Self {
205225
terraform_bin,
@@ -240,6 +260,7 @@ async fn apply_terraform(
240260
}
241261

242262
let mut artifacts = BTreeMap::new();
263+
artifacts.insert("stack_name".into(), context.environment_name.clone());
243264

244265
for step in &plan.deploy_steps {
245266
if phase_is_past(session.phase, step.phase) {
@@ -608,6 +629,37 @@ fn terraform_install_path() -> Result<PathBuf, AdapterError> {
608629
Ok(dir.join("terraform"))
609630
}
610631

632+
fn read_env_name_from_tf_state(working_dir: &str) -> Option<String> {
633+
let state_path = Path::new(working_dir).join("terraform.tfstate");
634+
let contents = fs::read_to_string(&state_path).ok()?;
635+
// Look for environment_name in the state's root module outputs or resource attributes
636+
// The IAM role name pattern is "${environment_name}-role"
637+
for line in contents.lines() {
638+
let line = line.trim();
639+
if line.contains("\"name\"") && line.contains("-role\"") {
640+
// Extract name like "loki-openclaw-abc12345-role"
641+
if let (Some(start), Some(end)) = (line.find('"'), line.rfind("-role\"")) {
642+
let candidate = &line[start + 1..end];
643+
if candidate.starts_with("loki-") {
644+
return Some(candidate.to_string());
645+
}
646+
}
647+
}
648+
}
649+
// Fallback: try terraform output
650+
let output = Command::new("terraform")
651+
.args(["-chdir", working_dir, "output", "-raw", "environment_name"])
652+
.output()
653+
.ok()?;
654+
if output.status.success() {
655+
let name = String::from_utf8_lossy(&output.stdout).trim().to_string();
656+
if !name.is_empty() {
657+
return Some(name);
658+
}
659+
}
660+
None
661+
}
662+
611663
fn can_write_to(dir: &Path) -> bool {
612664
if fs::create_dir_all(dir).is_err() {
613665
return false;

tools/loki-installer/src/tui/runtime.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,7 @@ impl InstallEventSink for TuiEventSink {
243243
.await
244244
}
245245
InstallEvent::ArtifactRecorded { key, value } => {
246+
let value = value.lines().collect::<Vec<_>>().join(" ");
246247
let display_value = if value.len() > 80 {
247248
format!("{}…", &value[..80])
248249
} else {

tools/loki-installer/src/tui/screens/deploy.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,10 @@ pub fn content_with_width(state: &AppState, max_width: usize) -> Text<'static> {
7575
for line in &state.deployment.logs[start..end] {
7676
// Truncate long lines to avoid horizontal overflow
7777
let max_line = max_width.saturating_sub(6);
78-
let display = if line.len() > max_line {
79-
format!(" - {}…", &line[..max_line])
78+
let char_len: usize = line.chars().count();
79+
let display = if char_len > max_line {
80+
let truncated: String = line.chars().take(max_line).collect();
81+
format!(" - {truncated}…")
8082
} else {
8183
format!(" - {line}")
8284
};

tools/loki-installer/src/tui/update.rs

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -119,10 +119,6 @@ pub fn update(state: &mut AppState, event: InstallerEvent) -> Vec<AppAction> {
119119
state.request_draft.pack_id = Some(pack.id.clone());
120120
state.request_draft.profile_id = pack.default_profile.clone();
121121
state.request_draft.method_id = pack.default_method;
122-
state.auto_selected_pack = true;
123-
return vec![AppAction::LoadProfiles {
124-
pack_id: pack.id.clone(),
125-
}];
126122
}
127123
}
128124
}
@@ -142,12 +138,6 @@ pub fn update(state: &mut AppState, event: InstallerEvent) -> Vec<AppAction> {
142138
state.request_draft.profile_cursor = profile_idx;
143139
if let Some(profile) = state.profiles.get(profile_idx) {
144140
state.request_draft.profile_id = Some(profile.id.clone());
145-
state.auto_selected_profile = true;
146-
if let Some(pack_id) = &state.request_draft.pack_id {
147-
return vec![AppAction::LoadMethods {
148-
pack_id: pack_id.clone(),
149-
}];
150-
}
151141
}
152142
}
153143
}
@@ -630,11 +620,8 @@ mod tests {
630620

631621
assert_eq!(state.packs.len(), 1);
632622
assert_eq!(state.request_draft.pack_id.as_deref(), Some("openclaw"));
633-
assert!(state.auto_selected_pack);
634-
assert!(matches!(
635-
actions.as_slice(),
636-
[AppAction::LoadProfiles { pack_id }] if pack_id == "openclaw"
637-
));
623+
assert_eq!(state.screen, ScreenId::PackSelection);
624+
assert!(matches!(actions.as_slice(), [AppAction::Render]));
638625
}
639626

640627
#[test]
@@ -671,11 +658,8 @@ mod tests {
671658
);
672659

673660
assert_eq!(state.request_draft.profile_id.as_deref(), Some("builder"));
674-
assert!(state.auto_selected_profile);
675-
assert!(matches!(
676-
actions.as_slice(),
677-
[AppAction::LoadMethods { pack_id }] if pack_id == "openclaw"
678-
));
661+
assert_eq!(state.screen, ScreenId::ProfileSelection);
662+
assert!(matches!(actions.as_slice(), [AppAction::Render]));
679663
}
680664

681665
#[test]

0 commit comments

Comments
 (0)