Skip to content

feat: --no-proxy mode for app deploy/init/logs/... (#102)#103

Merged
crowdy merged 18 commits into
mainfrom
feat/no-proxy-mode
Apr 21, 2026
Merged

feat: --no-proxy mode for app deploy/init/logs/... (#102)#103
crowdy merged 18 commits into
mainfrom
feat/no-proxy-mode

Conversation

@crowdy
Copy link
Copy Markdown
Owner

@crowdy crowdy commented Apr 21, 2026

Summary

Adds a coexisting --no-proxy mode to the conoha app * command tree. Closes #102. Absorbs #93 (slot-aware logs/stop/restart/status in proxy mode).

Spec: `docs/superpowers/specs/2026-04-21-no-proxy-mode-design.md`
Plan: `docs/superpowers/plans/2026-04-21-no-proxy-mode.md`

Design

  • Hybrid mode selection: server-side marker /opt/conoha/<name>/.conoha-mode (written by app init on either path) with --proxy/--no-proxy mutually-exclusive flag override.
  • Proxy and no-proxy apps can share a single VPS — different subdirectories under /opt/conoha/.
  • Explicit mode-conflict errors instead of silent auto-migration. app init now rejects implicit mode switches.
  • Spec §3.2 v0.1.x-style env merge: no-proxy deploy merges /opt/conoha/<app>.env.server (written by app env set) into work-dir .env before docker compose up.
  • app logs/stop/restart/status: target active slot, not legacy /opt/conoha/<app> #93 absorbed: logs/stop/restart/status now target the active slot in proxy mode via CURRENT_SLOT.

Command matrix

Command proxy mode no-proxy mode
app init Docker + conoha.yml + proxy upsert + marker Docker + marker (no conoha.yml)
app deploy blue/green slot swap flat /opt/conoha/<app>/ + compose up
app rollback proxy /rollback exit + git-based recovery hint
app destroy all slots down + proxy DELETE + rm compose down + rm (no proxy call)
app logs/stop/restart/status slot-scoped compose project flat compose project
app env writes .env.server + warns (see #94) writes .env.server, merged at deploy

Breaking changes

None. Proxy-mode users who ran app init before this PR will be prompted to re-run init once (their marker file doesn't exist yet).

Test plan

  • `go test ./...` — all packages pass
  • `go build ./...` — clean
  • `go vet ./...` — clean
  • `gofmt -l cmd/ internal/` — clean
  • `conoha app --help` matrix verified: --proxy/--no-proxy on init/deploy/rollback/destroy/logs/stop/restart/status; absent from env subcommands (env uses marker-read warning)
  • End-to-end against a real VPS (post-merge):
    • `app init --no-proxy --app-name myapp ` → `.conoha-mode=no-proxy` on disk
    • `app deploy --no-proxy --app-name myapp ` → `docker ps` shows project `myapp`
    • Proxy-init'd app rejects `app init --no-proxy` with mode-conflict error
    • Proxy-init'd app rejects `app deploy --no-proxy` with mode-conflict error
    • `app rollback --no-proxy` returns git-based recovery hint
    • `app env set FOO=bar` then `app deploy --no-proxy` → `FOO` visible in container interpolation
    • `app logs/stop/restart/status` target the active slot in proxy mode
    • `app destroy` cleans up both layouts on the same VPS

Follow-ups (non-blocking)

  • Exit codes — spec §5 aspires to distinct exit codes (5 = mode-conflict, 6 = not-initialized) but all errors currently return cobra's default exit 1. Deferred; can be plumbed through `cmd/cmdutil`.
  • destroy on legacy + local conoha.yml — spec §3.4 permits best-effort proxy DELETE when marker is absent but `conoha.yml` is present. Currently no-op. Minor deviation; either update spec or extend the gate in a follow-up.
  • `runInitProxy` marker-write failure severity — currently fatal; spec §12 notes "警告のみ、proxy 側は残す" as an accepted trade-off. Consider downgrading to warning in a follow-up.
  • Re-implement conoha app reset for blue/green model #92 `app reset` reintroduction — needs both modes, blocked on this PR.
  • app env: redesign for blue/green + accessory separation #94 `app env` redesign for proxy mode — this PR only adds the warning shim.
  • app list: enumerate via proxy /v1/services, drop /opt/conoha/*.git scan #95 `app list` for no-proxy — separate PR.

🤖 Generated with Claude Code

t-kim-planitai and others added 17 commits April 21, 2026 11:43
Specifies a coexisting no-proxy path for app {init,deploy,logs,stop,
restart,status,destroy,env,rollback}, a server-side .conoha-mode
marker for hybrid detection with --proxy/--no-proxy overrides, and
the per-command semantics + exit codes. Absorbs #93 (slot-aware
logs/stop/restart/status in proxy mode).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
12 bite-sized tasks covering cmd/app/mode.go foundation, per-command
mode dispatch (init, deploy, rollback, destroy, logs/stop/restart/
status, env warning shim), documentation, and verification. Each task
follows TDD (failing test → implementation → passing test → commit).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Introduce Mode enum (proxy | no-proxy), ErrNoMarker / ErrModeConflict,
shell-command builders for the .conoha-mode marker file, ReadMarker /
WriteMarker / ResolveMode / ReadCurrentSlot helpers, and the
--proxy/--no-proxy mutually-exclusive flag pair. Foundation for #102.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
No-proxy init installs only the mkdir + marker write (no conoha.yml
required). Proxy init continues through the existing upsert path and
now writes the marker at the end. --app-name is required with
--no-proxy.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The .conoha-mode marker is the single source of truth for mode
dispatch in subsequent app commands. A silently-skipped marker leaves
deploy/logs/stop/... unable to detect mode and surfaces a misleading
'not initialized' error for an app that was successfully registered
with the proxy. Match no-proxy's fatal-on-marker-failure policy.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
runDeployDispatch reads the --no-proxy flag, resolves the server
marker, and either calls runProxyDeploy (existing blue/green flow)
or runNoProxyDeploy (tar upload to /opt/conoha/<name>/ + compose up
against the project name <name>). Proxy/no-proxy marker mismatches
produce the standard mode-conflict error.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Spec §3.2 requires rejecting 'conoha app deploy <server>' (no flag) or
'--proxy' against an app with a no-proxy marker. Previously only the
--no-proxy branch enforced marker consistency; the proxy branch went
straight to admin.Get and returned a misleading 'service not found'.
Now the proxy branch reads the marker first and issues the standard
mode-conflict error. Also unify the 'not initialized' phrasing across
both branches. Additionally, single-quote composeFile in
buildNoProxyDeployCmd so the builder stays safe if a future caller
passes a non-whitelisted compose file path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
--no-proxy flag (or a no-proxy marker detected after SSH connect)
now returns an explicit error pointing at 'git checkout <rev> &&
conoha app deploy --no-proxy'. Proxy-mode behavior is unchanged but
now runs through ResolveMode first so marker mismatches surface as
the standard mode-conflict error.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
destroy now reads the .conoha-mode marker before the destroy script
runs (the script removes the marker via rm -rf /opt/conoha/<app>)
and only deregisters from conoha-proxy when the marker is 'proxy'.
No-proxy and unmarked (legacy v0.1.x) servers continue to run the
shared compose-down + rm -rf cleanup without touching the proxy.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Absorbs #93 for app logs: proxy mode reads CURRENT_SLOT and runs
'docker compose -p <app>-<slot> logs' against the active slot project.
No-proxy mode keeps the flat 'cd /opt/conoha/<app>' path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Proxy-mode stop runs 'docker compose -p <app>-<slot> stop' against
the active slot (CURRENT_SLOT); no-proxy keeps the legacy flat path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Proxy-mode restart runs 'docker compose -p <app>-<slot> restart' against
CURRENT_SLOT. No-proxy keeps the legacy flat path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Proxy status scans all slot compose projects and appends the proxy
service phase block. No-proxy status runs a simple 'docker compose ps'
in the flat work dir and skips the proxy enrichment.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ReadCurrentSlot returned raw trimmed file content, which was then
interpolated into 'docker compose -p <app>-<slot>' in logs/stop/
restart. A compromised or manually-edited CURRENT_SLOT could smuggle
shell metacharacters through that path. Re-apply ValidateSlotID
(same rule that gates writes at deploy time) so the read side is
defense-in-depth rather than trust-the-file.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
app env still writes to /opt/conoha/<app>.env.server (the v0.1.x path,
now canonical for no-proxy mode). When the .conoha-mode marker says
proxy, print a single-line warning pointing at #94 rather than breaking
existing CI scripts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a 'Two deploy modes' section in README.md (ja), README-en.md,
and README-ko.md; a full no-proxy single-server recipe at
recipes/single-server-app-noproxy.md; and a one-line cross-reference
from the 2026-04-20 proxy-deploy spec to the new 2026-04-21 no-proxy
design spec.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Fix 1 — app init now rejects implicit mode switches:
  Before writing the marker, init reads the existing marker and
  returns the standard mode-conflict error if it disagrees with the
  target mode. Previously, 'conoha app init --no-proxy' on a
  proxy-registered app (or vice versa) would silently overwrite the
  marker while leaving the proxy service or flat layout behind,
  defeating spec §2.3's coexistence guarantee.

Fix 2 — no-proxy deploy now merges /opt/conoha/<app>.env.server:
  Spec §3.2 requires replaying the v0.1.x behavior where values set
  by 'conoha app env set' are merged into the work-dir .env at deploy
  time. buildNoProxyDeployCmd now prepends a shell block that cats
  the env.server file into .env (server-side values first so user's
  repo-level .env wins on duplicates per last-occurrence semantics)
  before 'docker compose up'. Added test assertions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@crowdy
Copy link
Copy Markdown
Owner Author

crowdy commented Apr 21, 2026

Adversarial review (second pass) — 5 defects found, blocks merge

Second review after the earlier round. Spec + plan + tests all checked. go test ./... -race passes. Diff is 3,931 lines across 26 files. Below are defects the earlier review missed.

Critical (blocks merge)

C1 — `app env unset` silently fails to take effect on redeploy.
`cmd/app/deploy.go:122-132` merges `.env.server` with the existing `.env` on each deploy. On first deploy the merged `.env` persists on the server. On subsequent redeploys the tar extract does NOT replace `.env`, so the merge re-appends `.env.server` to a file that still contains the OLD merge. Keys removed via `conoha app env unset` continue to resolve to the stale value (Compose: last-occurrence wins). Repro:

  1. `conoha app env set FOO=1`; deploy → `.env` = `FOO=1`.
  2. `conoha app env unset FOO`; redeploy → merge becomes `(empty).env.server + (stale FOO=1).env` → FOO still `1`.

Fix: rebuild `.env` from scratch per deploy (don't cat the existing `.env`).

C2 — `.env` merge precedence is wrong for secret injection.
`deploy.go:128` produces `{ cat .env.server; printf '\n'; cat .env; } > .env.merged`. Compose uses last-occurrence, so repo `.env` overrides `conoha app env set`. A user who runs `conoha app env set DATABASE_URL=prod` with `DATABASE_URL=localhost` in their repo `.env` will deploy with `localhost` — silently. Spec §3.6 is quiet on precedence; v0.1.x `ENV_EXISTS` behavior was the opposite. Repo-wins defeats the entire point of `app env set`.

Fix: swap order so `.env.server` wins; document in README.

Important

I1 — Zero test coverage on every dispatch path introduced by this PR.
Grepped for tests against `ResolveMode` branches, `runInitProxy`/`runInitNoProxy` conflict rejection (commit `4ff6b2a`), `runDeployDispatch` conflict, `runProxyDeploy` no-proxy-marker rejection (`b6fa465`/`0281f2f`), `destroy` mode-resolution swallow. None. Every test is a `strings.Contains` shape check on shell builders. Spec §4.3 required `mode_test.go` to cover "ResolveMode 全分岐"; that never landed.

I2 — `destroy` skips proxy DELETE for legacy proxy apps missing the marker.
`destroy.go:48-53` swallows `ErrNoMarker` and falls through with `mode == ""`. `if mode == ModeProxy` → false → `admin.Delete` skipped. Pre-PR proxy deployments now leak orphan proxy service registrations on destroy. Regression vs. pre-PR.

Fix: default to ModeProxy when marker absent + `conoha.yml` validates, or print a warning so users run manual cleanup.

I3 — `destroy`/`stop` prompt before mode check.
`destroy.go:37-46` prompts at line 39, resolves mode at line 50. User passes `--no-proxy` against a proxy app, confirms "Destroy app X?", then gets a mode-conflict error. Leaves the user wondering "did I just destroy it or not?". Move mode resolution before the prompt. Same for `stop.go:30-45`.

I5 — `maybeWarnProxyEnvMode` adds an SSH round-trip to every `app env get`/`list`.
Doubles SSH work for read-only subcommands. Cache the mode in `appContext`, or skip the warning for `get`/`list`.

I6 — `status.go:47-49` masks real errors as warnings.
`if err != nil { fmt.Fprintf(..., "warning: compose ps: %v\n", err) }` — SSH/auth failures are swallowed, return 0. Spec §5's exit-6 "not-initialized" is unreachable.

Minor

M1 — Four different error phrasings for "not initialized"/"not deployed" across init/deploy/rollback/logs/stop/restart/status. Pull `notInitializedError`/`notDeployedError` helpers into `mode.go`.

M2 — `formatModeConflictError` emits literal `` token. Thread the real `serverID`.

M4 — `ReadCurrentSlot` error embeds the full (possibly adversarial) slot content via `%q`. Truncate to 16 chars.

M5 — No-proxy deploy (`mkdir -p; tar xzf -`) never removes files deleted from the repo. Document.

M6 — TOCTOU between marker read and action. Concurrent destroy + deploy can leave a running compose project without a marker. Single-operator assumption is fine; worth a sentence in spec §12.

M7 — `runProxyDeploy` open-codes marker read instead of calling `ResolveMode`. Two divergent paths for the same logic.

Observations

O1 — Squash the 5 fix commits (`db7b1b9`, `0281f2f`, `c80d0e6`, `4ff6b2a`) before merge. `git bisect` will land on broken intermediates otherwise.

O2 — Spec §12 trade-off drift: §12 said "marker write failure in init → warn only", but `db7b1b9` made it fatal. Update §12.

O3 — Spec §2.3 ("no auto-conversion") holds. No silent flip path found.

O4 — Spec §11 acceptance "unit tests cover all branches" NOT satisfied — see I1.

O5 — logs/stop/restart/status omit `-p ` and rely on cwd-derived project name. Works for lowercase; `ValidateAppName` permits uppercase which Compose normalizes differently. Follow-up.

Overall

Do not merge as-is. C1/C2 are silent wrong-env deployments. I1 leaves the dispatch logic (the value of this PR) unverified. I2 regresses legacy proxy destroys. I3 creates confusing post-prompt UX.

Fix C1, C2, I1, I2, I3 before merge. Squash per O1.

…tests

C1 — app env unset now takes effect on redeploy.
  buildNoProxyUploadCmd now removes the previous deploy's merged .env
  before tar extraction so the repo's .env (or its absence) is
  authoritative each cycle. Subsequent .env.server overlay rebuilds
  from scratch instead of accumulating stale entries.

C2 — .env merge precedence reversed so app env wins over repo .env.
  .env.server is now appended AFTER the repo .env, making its values
  last-occurrence and therefore the ones docker compose picks up.
  This matches v0.1.x ENV_EXISTS semantics: runtime secrets set via
  conoha app env set override anything committed to the repo.

I1 — Extracted resolveModeLogic as a pure function and added an
  11-case table-driven test covering every combination of
  (flag × marker × SSH error). Previously ResolveMode had zero
  coverage despite being the dispatch spine of this feature.

I2 — destroy now honors legacy proxy deployments.
  When the marker is absent but conoha.yml validates locally, we
  treat the server as a pre-PR proxy deployment and still issue
  proxy DELETE to avoid leaking orphan service registrations.

I3 — destroy and stop now resolve mode BEFORE prompting.
  A flag/marker conflict or a "not deployed" error now aborts
  before asking the user to confirm, removing the "did it or
  didn't it?" UX after a rejected operation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@crowdy
Copy link
Copy Markdown
Owner Author

crowdy commented Apr 21, 2026

Review fixes — C1/C2/I1/I2/I3 (`b38b4b8`)

C1 — `buildNoProxyUploadCmd` now does `rm -f .env` before tar extract, so each deploy rebuilds `.env` from scratch. `app env unset` takes effect on the next deploy.

C2 — `.env.server` is now appended after the repo `.env`, so `conoha app env set` values win (last-occurrence in compose parser).

I1 — Extracted `resolveModeLogic` as a pure function and added an 11-case table test covering every combination of (`want` × `got` × `readErr`). Previously ResolveMode had zero coverage.

I2 — `destroy` now treats marker-absent + locally-valid `conoha.yml` as a legacy proxy deployment and still calls `admin.Delete`. Emits a visible "treating as legacy proxy deployment" notice.

I3 — `destroy` and `stop` now resolve mode BEFORE prompting. A flag/marker conflict or "not deployed" error aborts before the user confirms — no more ambiguous post-prompt rejection.

Tests green with `-race`. Remaining items from the review (I5, I6, M1–M7, O1 squash, O2 spec update, O5 ValidateAppName lowercase) are follow-ups to land separately.

@crowdy crowdy merged commit 354f35f into main Apr 21, 2026
3 checks passed
@crowdy crowdy deleted the feat/no-proxy-mode branch April 21, 2026 04:40
crowdy added a commit that referenced this pull request Apr 21, 2026
* docs(readme): refresh deploy-mode coverage — document proxy + no-proxy in parallel

Expands the two-mode summary table with conoha.yml / proxy boot / DNS
columns so users can pick a mode at a glance. Adds a parallel "no-proxy
mode" subsection that previously only existed as a one-line footnote,
plus explicit mode-marker semantics (set by init, auto-detected, --proxy
/ --no-proxy as override-with-error-on-mismatch).

Also documents the full conoha.yml schema (compose_file, accessories,
health, deploy.drain_ms) — previously only name/hosts/web were shown.

Adds a flags reference table covering --slot, --drain-ms, --follow,
--service, --tail, --data-dir that were reachable only via --help.

No functional change; just documents features already shipped in
#98 (proxy blue/green) and #102/#103 (--no-proxy mode).

* docs(readme-en): mirror proxy + no-proxy refresh from README.md

* docs(readme-ko): mirror proxy + no-proxy refresh from README.md

* docs(readme): fix C1/I1/I2/I3 from doc review

- C1: correct .env.server semantics — the local-file auto-copy claim
  was wrong. `.env.server` is the server-side file at
  /opt/conoha/<app>.env.server, appended to the deploy's .env after
  the repo-committed .env (see cmd/app/deploy.go:132-140).
- I1: flag-table row for --proxy/--no-proxy wrongly excluded `init`,
  which also takes the flag (selects the mode to write into the
  marker). See cmd/app/init.go:25.
- I2: rollback on no-proxy emits a dedicated "rollback is not
  supported" error, not a mode-mismatch error. See
  cmd/app/rollback.go:22-26.
- I3: drop redundant --app-name from proxy-mode init/deploy/rollback
  examples (proxy mode reads name from conoha.yml's `name` field;
  --app-name is silently ignored there). Tighten the flag-table row
  to distinguish where --app-name is actually required vs where it's
  overridden by conoha.yml.

* docs(plan): fix .env.server claim in phase A+B replacement blocks

The plan's replacement Markdown for Tasks 2/3/4 (JA/EN/KO READMEs) and
the Phase B Task 7 recipe block embedded the same wrong "local
.env.server auto-copies to .env on deploy" claim. Ground truth from
cmd/app/deploy.go:107-140 and cmd/app/env.go:94,144,170,211: the
server-side /opt/conoha/<app>.env.server file (written by
`conoha app env set`) is appended onto the repo-committed .env at
deploy time; no local .env.server is ever picked up specially. Plan
updated so future re-runs don't regress the README.

* docs(readme): fix C1/C2/I1/I2 from review — no-proxy init does not install Docker, env is no-proxy only, drain_ms default 30000, flag scope excludes list

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(plan): propagate C1/C2/I1/I2 corrections into plan replacement blocks

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: t-kim <t-kim@planitai.co.jp>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
crowdy added a commit that referenced this pull request Apr 22, 2026
…#123)

- Replace the marker-write rollback bullet: PR #103 flipped the policy
  to fatal in cmd/app/init.go; §12 kept the pre-flip text. Now points
  to review item I1 and commit db7b1b9 (closes #112).
- Add a TOCTOU bullet covering concurrent destroy/deploy against the
  same app. No code change — single-operator assumption retained
  (closes #113).

Co-authored-by: t-kim <t-kim@planitai.co.jp>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
crowdy added a commit that referenced this pull request Apr 23, 2026
#101) (#135)

* feat(ssh): verify host keys via known_hosts + TOFU fallback (closes #101)

Every SSH connection the CLI makes used ssh.InsecureIgnoreHostKey(),
which was justified in v0.1.x as "personal VPS use" but becomes
unsafe in the post-#98/#103 world: the same SSH channel now carries
state-mutating Admin API calls (upsert, deploy, delete), compose
archive uploads, and a growing number of ops commands. A MITM on the
path can silently reroute those to an attacker-controlled responder
and misreport phase/active_target back to the operator.

New default:
- Resolve ~/.ssh/known_hosts (override: SSH_KNOWN_HOSTS env var).
- On first connect to an unknown host, prompt operator to accept and
  pin the key (TOFU).
- When CONOHA_NO_INPUT is set or stdin isn't a TTY, refuse the
  connection with a message pointing at --insecure and ssh-keyscan.
- On host-key mismatch (pinned key differs from what the server
  presented), return HostKeyMismatchError with a recovery hint
  ('ssh-keygen -R <host>' to remove the stale pin after a legit rebuild).

Opt-out:
- --insecure global flag (persistent, applies to every subcommand).
- CONOHA_SSH_INSECURE env var for CI / wrappers.
- Preserves the old behavior bit-for-bit when set; documented as the
  explicit lab / throwaway-VPS knob.

Connect() reads insecure state from either ConnectConfig.Insecure or
the env, so no caller changes were needed across the 7 existing
Connect sites.

Tests:
- Insecure callback accepts any key.
- Pre-seeded known_hosts + mismatched server key → HostKeyMismatchError
  with the 'ssh-keygen -R' hint.
- Unknown host + noInput → helpful refusal message.
- Missing known_hosts is auto-created.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(lint): satisfy errcheck on sshClient.Close + collapse S1020 in known_hosts

- cmd/gpu/setup.go: explicit `_ =` discard on Close() (errcheck does not
  exclude *ssh.Client; the io.Closer exclusion only matches the interface
  type itself).
- internal/ssh/knownhosts.go: drop redundant `remote != nil` outer guard;
  type assertion on a nil interface returns ok=false. (staticcheck S1020.)

* refactor(ssh): drop dead Insecure field, alias config import, fail-closed on non-TTY TOFU

Three follow-ups from PR #135 review:

1. Remove ConnectConfig.Insecure — no caller sets it. The --insecure
   flag / CONOHA_SSH_INSECURE env var routes through configpkg.IsSSHInsecure
   inside Connect, which is the single source of truth. The unused field
   was a misleading second knob.

2. Alias the config import as `configpkg` to remove the package-vs-local
   shadowing trap in Connect (the previous `config -> clientCfg` rename
   only fixed the immediate collision; an import alias prevents recurrence).

3. Guard TOFU prompt with term.IsTerminal so a non-TTY stdin (CI, build
   script piping a heredoc) fails closed instead of letting an attacker-
   controlled `yes\n` silently trust an unknown host. The function-level
   doc already promised this behavior.

Adds TestHostKeyCallback_UnknownHost_NonTTYFailsClosed; skips when stdin
happens to be a real TTY (interactive `go test` runs).

---------

Co-authored-by: t-kim <t-kim@planitai.co.jp>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: --no-proxy mode for app deploy/logs/status/stop/restart/destroy/init (TLS-less single-slot)

2 participants