Commit Graph

210 Commits

Author SHA1 Message Date
Tower deploy
57e15b25dd F-undo: tower 0.65.0 + tower-ui 0.65.0 (Instances primary + tenant pinning + spawn-env from instance card) 2026-05-02 07:11:21 +02:00
3c3f921854 ship 0.64.5 — resolve addon Odoo major from Gitea branch (fixes odoo:1.0 build error) 2026-05-02 04:48:38 +00:00
c625d9c29e ship 0.64.4 — derive Odoo major from filename when manifest version is short 2026-05-01 14:18:18 +00:00
820832a634 ship 0.64.3 — multi-addon zip + upload progress bar 2026-05-01 14:13:22 +00:00
8e0057b967 ship 0.64.2 — hide platform server from /servers (tower itself, not a deploy target) 2026-05-01 13:11:17 +00:00
dcfad63247 ship 0.64.1 — fix /instances → /projects links across views 2026-05-01 07:55:18 +00:00
89b80bca2d ship 0.64.0 — Phase F2 (Add Env buttons + sidebar reorder + Deploy Odoo CTA) 2026-05-01 07:48:37 +00:00
e8990f7f65 ship 0.63.2 — fix promotable null + bump frontend 2026-05-01 07:38:09 +00:00
183df42f1d ship 0.63.1 — Phase F1 fix: sweep reads actual env kind 2026-05-01 07:33:07 +00:00
baf04829a1 ship 0.63.0 — Phase F1 (Project as identity unit + spawn-env + sweep) 2026-05-01 07:28:52 +00:00
4d5aa41a91 ship 0.62.1 backend — pin k3s v1.34.6+k3s1 in bootstrap.sh (1.35.4 cloud-controller race) 2026-04-30 19:21:35 +00:00
6aabb11fff ship 0.62.0 — Phase E (Projects: dev/stage/prod + Promote ↑ + Refresh ↓ + Drift) 2026-04-30 18:38:47 +00:00
Claude
5f569ae2e0 tower-ui 0.61.25 — bell + activity follow the tenant filter
Backend SSE handler already accepts ?tenantId= as an additive filter
on top of canSeeOp (Phase G stays load-bearing); frontend now passes
the global tenant filter chip's value through both NotificationBell
and ActivityTab. Watcher clears + restarts the stream when the
super-admin switches tenant context. Dismissed Set is user-level
and survives the switch.
2026-04-30 18:38:59 +03:00
Claude
b5a3a3029b tower-ui 0.61.24 — bell dismiss + completion toasts
#2 Dismiss / Clear failed:
  - Per-row [×] (text 'Dismiss', shown on hover) on terminal ops
  - 'Clear' button in dropdown header when any dismissable rows exist
  - Dismissed IDs persisted to localStorage (tower_dismissed_ops)
  - Pruned during the 30s sweep when the underlying op falls out
    of the recent window — keeps storage from growing forever
  - badgeCount + failedCount filter dismissed entries so the red
    pill clears the moment the operator acks the failure

#4 Toast popups:
  - useToast composable + Toaster.vue mounted at App.vue
  - Triggered from NotificationBell.upsert on terminal transition
    when the op was running ≥30s AND the bell isn't open
  - Success: 5s auto-dismiss; Failure: sticky until clicked away
  - Click-through links to the per-instance Activity timeline
    (#op_<id>) for any op carrying instanceCode
  - Stack of 3, oldest drops on overflow
  - No external dep — hand-rolled to match v3's component style
2026-04-30 18:25:45 +03:00
Claude
6b7743ecaf tower 0.61.23 / tower-ui 0.61.23 — SSE auth via ?token=
NotificationBell + ActivityTab opened EventSource without auth
(native EventSource API can't set Authorization headers). Phase G's
canSeeOp guard correctly dropped every event for the resulting
anonymous viewer, leaving the bell silent except for the one-shot
backfill on mount.

Backend: claimsFromRequest now falls back to ?token= query param
when the Authorization header is absent. HTTPS-only ingress means
the token stays inside the TLS tunnel; the 15-min access-token TTL
bounds any leakage if it ever surfaces in browser history or proxy
logs.

Frontend: streamOperation + streamAllOperations append the access
token via streamURL(). Plus token-expiry-aware reconnect: on
EventSource error, debounce 5s, close, run authFetch('/me') to let
the 0.61.18 refresh path renew the access token, then re-open with
a fresh streamURL. Without this, the native auto-reconnect would
loop forever with the now-stale token after 15 min.
2026-04-30 18:15:11 +03:00
Claude
b96204312f tower 0.61.22 — import filestore-rename: head -1 + debug echo
The grep -oE for instanceCode matches BOTH provenance.instanceCode
AND the v2 root mirror, returning two lines. sed processes each line
but the resulting SOURCE_CODE shell variable was multi-line, which
made the directory check fail (-d "/var/lib/odoo/filestore/odoo16
[newline]odoo16") → rename branch silently skipped → Odoo with
db_name=odoo16v2 looks at /var/lib/odoo/filestore/odoo16v2/, finds
nothing, returns 500 on every asset.

Added head -1 to the pipe so SOURCE_CODE is single-line, plus an
echo so the rename branch's path is visible in Job logs even when
it short-circuits.
2026-04-30 17:43:37 +03:00
Claude
8024015b9e tower 0.61.21 — migrate + template-deploy use tenant CF resolver
Phase C made instance-create tenant-aware for Cloudflare DNS, but
migrate.go and templates_deploy.go kept using the legacy global
*cloudflareClient (zone=odoosky.org). Result: a tenant migrate to
4th.online silently created the A record under odoosky.org as a
literal subdomain ('odoo16v2.tenants.4th.online.odoosky.org' →
right IP) — Tower logged 'DNS A record set' successfully because
the API accepted the call, but the actual hostname the user
browses to was never published to the right zone.

Both flows now use cfResolver.clientFor(tenantID, fqdn) to find
the tenant's CF token + correct zone. If no token covers the
domain, the op fails with a clear 'configure tenant CF token'
message instead of silently writing to the wrong zone.
2026-04-30 17:31:44 +03:00
Claude
9ad0ce9f1f tower-ui 0.61.20 — Migrate picker hides platform server
MigrateDrawer hardcoded '<option value="">Platform server (default)</option>' as
the first picker entry, regardless of tenant scope. A tenant operator
saw it as a selectable default — and selecting it (or just leaving
the default empty) sent the migrate to the platform cluster, which
the operator has no business deploying to.

Now: removed the hardcoded option. Auto-pick the first deployable
non-platform server on load (matches DeployInstanceDrawer pattern).
Picker shows 'Pick a server…' as a disabled placeholder when nothing
is selected.
2026-04-30 17:17:35 +03:00
Claude
52f7030c8f tower 0.61.19 / tower-ui 0.61.19 — tenant-scoped S3 in export+import Jobs
The export Job was using a stale platform Secret (s3-backup-creds)
and hardcoded bucket/endpoint, so bundles landed in odoosky-v3-backups
while Tower's verify (tenant-scoped after Phase F) read from the
tenant's bucket. Result: 'bundle missing from S3 after job
succeeded' even though the upload itself worked.

Same bug existed in import. Both fixed: keys+region+endpoint+bucket
now come from Tower's resolver view of the tenant, passed directly
into the Job env.

Plus: BackupsView crashed on r.backups.runs.length when runs is
null. Added the missing null guard.
2026-04-30 16:42:33 +03:00
Claude
66b9c60c68 tower-ui 0.61.18 — refresh-and-retry on 401
Frontend authFetch was bouncing every 401 straight to /login,
ignoring the 30-day refresh-token cookie the backend already issues.
Result: access-token TTL is 15 min, so the operator was kicked to
login every 15 min of idle.

Now: on 401, authFetch silently calls /api/auth/refresh, retries
the original request once with the new access token, and only
bounces to /login if refresh ALSO fails (refresh cookie expired or
revoked). Concurrent 401s coalesce onto a single in-flight refresh
to avoid rotating the refresh-token jti N times in a burst.
2026-04-30 16:30:44 +03:00
Claude
fab8d62521 tower 0.61.17 / tower-ui 0.61.17 — async addon save (kill Saving freeze)
handleUpdateAddons was fully synchronous including BuildKit Jobs per
addon image. cetmix_tower auto-pulls 8 deps × ~30-90s each on a fresh
cluster = 5-15 min. Reverse proxy timeout (60-120s) cuts the request
mid-build, browser shows 'Saving' forever, drawer eventually closes
on the timeout error, AND the cancelled context kills the goroutine
mid-flight so values.yaml never gets committed.

Now: handler validates inline (immediate feedback on bad input),
spawns an addon-stage op, returns 202 + opId in <1s. The goroutine
runs phases (resolve → build → commit → refresh) with a fresh
context that survives client disconnect. Operator watches it in the
bell + Activity tab, can keep working in another tab. Same pattern
Deploy/Migrate/Apply already use.
2026-04-30 16:19:47 +03:00
Claude
971fd12fae tower 0.61.16 / tower-ui 0.61.16 — review fixes
Two bugs the calm re-review surfaced:

1. substrateGateRejection used ListApplications (instance-only
   selector) — same bug we fixed in handleListServers but missed in
   the gate function. Result: the BACKEND gate was a pass-through
   for every preparing server. Frontend disable held the line in
   normal use, but the non-bypassable layer wasn't actually
   non-bypassable. Now uses ListClusterPlatformApps.

2. PlatformAppBadge had its own Argo→state mapping that classified
   Health=Degraded as 'Failed' immediately. Backend's
   deriveSubstrateStatus is forgiving within the budget (Degraded
   inside 10-min window reads as preparing). The two parallel
   logics meant the badge could show red 'Failed' while the gate
   considered it 'preparing'. Refactored badge to consume the
   backend's substrateStatus enum directly — single source of
   truth, no more disagreement.
2026-04-30 15:06:50 +03:00
Claude
d2f86d7e1a tower 0.61.15 — substrate budget 5→10m + forgiving Degraded
DNS-01 against Cloudflare reliably takes 6-8 min for first-issue
(Cloudflare TXT write + global propagation + LE polling). The 5-min
budget red-flagged normal installs. Bumped to 10 min.

Also: Argo flickers to Degraded during retries while waiting on the
Certificate hook. Within the budget window we now classify Degraded
as 'preparing' instead of 'degraded' — only declare degraded after
the budget fully expires. Stops the badge from showing 'Failed'
during a healthy install.
2026-04-30 14:25:25 +03:00
Claude
253a74aec0 tower 0.61.14 — list cluster-platform apps for substrate gate
ListApplications uses selector odoosky.io/component=instance which
hides cluster-platform apps. Separate ListClusterPlatformApps method
selects on app.kubernetes.io/managed-by=tower then filters by
-platform suffix — no backfill needed for existing servers.

Without this, substrateStatus + the Phase B4 PlatformAppBadge both
silently fell to 'unknown' / invisible because the lookup map was
empty.
2026-04-30 14:18:26 +03:00
Claude
aefcac742a tower 0.61.13 / tower-ui 0.61.13 — substrate readiness gate
Servers connected via the URL-token flow take ~5 min for the
cluster-platform-v3 chart to install. Without a gate, an over-eager
operator could click Deploy on a fresh server and land an instance
on a half-built cluster — Argo sync errors on missing CRDs, Pod
ImagePullBackOff on the in-cluster registry that doesn't exist yet.

Two layers, both shipped here:

Backend (defensive, non-bypassable):
  - argoCluster gains substrateStatus enum (ready | preparing |
    degraded | unknown) and substrateETA (RFC3339, when preparing)
    derived from the per-cluster <name>-platform Argo App's
    health+sync + the App's createdAt + 5min budget.
  - handleCreateInstance + handleApplyMigration refuse with 409 +
    "server still preparing — please wait" when the gate fails.
    Same posture as the in-flight-delete check we shipped in 0.61.11.

Frontend (visible UX, prevents the user reaching the gate):
  - DeployInstanceDrawer + MigrateDrawer server pickers disable
    rows where substrateStatus !== ready/unknown. Disabled rows
    show "Preparing · ~Xm" with a live countdown.
  - Default-server selection prefers a deployable row.
  - PlatformAppBadge shows the same countdown on the server card +
    detail vitals panel — operator can watch it tick down without
    leaving the Servers list.
  - Drawers auto-poll listServers() while a preparing server is
    visible — rows enable themselves the moment the chart lands.
2026-04-30 14:15:29 +03:00
Claude
db2dfaae87 tower 0.61.12 — silent connect + no kubeconfig leak
Customer running the connect URL was getting the entire k3s install
transcript scrolled to their terminal — including the base64-encoded
kubeconfig (cluster-admin certs visible in scrollback). Two problems:

1. UX: violates "Tower silent in the background" platform principle.
2. Security: cluster-admin material visible to anyone shoulder-surfing
   or screen-sharing.

wrapQuiet() in connect_token.go now wraps bootstrap + trailer:
  - all output → /var/log/odoosky-connect.log (operator-readable)
  - ONE friendly line to terminal at start ("Connecting…")
  - ONE outcome line at end (✓ success / ⚠ failure)
  - on non-zero exit: dump last 30 log lines so customer isn't
    staring at a silent terminal

Kubeconfig is already tee'd to /tmp/odoosky-kubeconfig.yaml by the
bootstrap, so the trailer reads it from disk — never needs stdout.
2026-04-30 13:56:54 +03:00
Claude
44e6945aea tower 0.61.11 — fix delete race + force-delete patchType
End-to-end smoke surfaced two bugs that combine to nuke a fresh
instance:

1. ForceDeleteApplication was unreachable because Argo's HTTP PATCH
   is gRPC-gateway-backed and requires a structured body
   `{name, patch, patchType}` where patch is the merge-patch encoded
   as a string. We were sending a raw merge-patch with `?type=merge`,
   which Argo rejects with HTTP 500: `proto: required field
   "patchType" not set`. Result: every instance-delete in history is
   marked "failed" because force-delete fallback never worked.

2. handleCreateInstance only checked `applicationExists` for the
   conflict guard. ArgoCD's cascade can take 5+ minutes to actually
   tear down (PVC protection, dead customer cluster) — long enough
   that applicationExists returns false but the cascade is STILL
   running. If a fresh create lands in that window, the new App
   takes the same name and the stale cascade clobbers it when it
   finally finishes.

Fix #2: also reject create when an instance-delete op for the same
code is in pending/running state. Operator gets a clear "delete
still in progress — please wait" message instead of the silent
ten-minutes-later "instance vanished" failure.

Confirmed in production by op log:
  10:28:46 instance-delete odoo16 (running 6 min, finally errored
           out at 10:34:48 with the patchType marshal error)
  10:29:59 instance-create odoo16 (succeeded at 10:30:02 — landed
           inside the still-cascading window)
  10:36:08 addon-apply odoo16 (failed: HTTP 403 — Argo App gone)
2026-04-30 13:47:55 +03:00
Claude
bf0c67539e tower 0.61.10 — Phase I review hardening
Pre-test review of 0.61.9 surfaced two issues in the manifest reader:

1. v3 stores instanceCode under provenance.* but readManifestString
   only looked at recipe → root. Today the v2 root mirror covers it,
   but a future v4 dropping that mirror would silently lose the
   filestore-rename hint.

2. Adding a blanket provenance lookup re-opened the leak: a poison
   bundle could embed provenance.tenantId and have it reachable to
   any future caller.

Fix: provenance lookup is now allowlisted to {instanceCode}. Any
new provenance field requires an explicit constant addition,
which is a code-review gate against re-introducing the leak.

Round-trip simulation (tools/phase_i_simulate.go) passes for v3,
v3-pure (no v2 mirrors), v3-poison, and v2.
2026-04-30 13:24:56 +03:00
Claude
6007de6e41 tower 0.61.9 — Phase I tenant-neutral export bundle
Bundles now emit odoosky.export.v3 schema with a recipe sub-object
and explicit provenance section. The `source` field is dropped from
addon entries (the Phase I tenant leak we identified). Readers handle
both v2 and v3 schemas; v2 bundles in flight stay restorable but their
addon `source` field is silently ignored at restore time.

Phase I closes the export-bundle side of the cross-tenant data leak.
2026-04-30 13:09:26 +03:00
Tower Bot
b2244d0c02 tower 0.61.8 — Phase H substrate completeness
bootstrap.sh now writes /etc/rancher/k3s/registries.yaml BEFORE k3s
starts, mapping the cluster-platform-v3 registry's in-cluster DNS
hostname to the localhost NodePort the host's containerd can reach.
Without this, every Odoo Pod ImagePullBackOffs on its addon
initContainers — caught 2026-04-30 mid-migrate.

ApplyConnectSecrets now also applies docker-mirror-pull (a docker-
config-json Secret in odoosky-system) when the platform-side env
provides DOCKER_MIRROR_{REGISTRY,USERNAME,PASSWORD}. Until today
the customer cluster's BuildKit Jobs sat in Init:0/1 for ~14 minutes
waiting on a non-existent docker-mirror-pull, blocking every
addon-build the migrate flow needs.

Both gaps were silent — neither produced a visible error in Tower's
op log; the cluster sat there waiting on a kubelet that couldn't
resolve and a Job that couldn't mount. Connect now fully provisions
both at substrate setup time, no manual post-step.

Threads:
  - new EnvProvider methods: DockerMirror{Registry,Username,Password}
  - new ConnectSecrets fields + applier method
  - chart values pull from existingSecret keys DOCKER_MIRROR_*
  - bootstrap.sh idempotent registries.yaml + systemctl restart on
    re-Connect to pick up updated routing rules
2026-04-30 12:50:25 +03:00
Tower Bot
e3756ac1d1 tower 0.61.7 — Phase G + delete fall-through to force-delete
Phase G: every Operation now carries (TenantID, ActorUserID, ActorEmail)
stamped at opStore.Create from the request scope. The bell SSE stream
filters per event against the caller's scope before emitting (closes
the cross-tenant leak — non-super-admin users no longer see other
tenants' ops). Get / Cancel / Stream-one return 404 (not 403) when
the caller can't see the op so existence isn't probable across
tenants. List endpoint uses op.TenantID directly (covers in-flight
ops with no Argo App yet); legacy ops with empty tenant fall back
to the Argo lookup so the upgrade is seamless.

Delete leak: cascade-delete failure used to fail the whole flow,
stranding the Gitea overlay repo + DNS A record. Now: cascade
fails → escalate to ForceDeleteApplication (strip finalizers) →
continue to repo + DNS cleanup. Both fail only when ArgoCD itself
is unreachable. Caught when odoo16v2 left tenant-havari/instance-
odoo16v2 orphaned across the smoke test.

Tests + build green.
2026-04-30 12:42:12 +03:00
Tower Bot
eece448b6b tower-ui 0.61.10 — MigrateDrawer domain watcher tracks last suggestion
The MigrateDrawer's auto-suggest watcher compared form.value.domain
against staged.sourceCode (the *initial* suggestion from the bundle)
to decide whether the domain field had been hand-edited. Result: the
moment the operator typed past the initial suggestion (e.g. typed
'odoo16v' then 'odoo16v2'), every subsequent code edit was treated
as a hand-edit and the domain stuck at the previous suggestion.

Concrete failure caught live: typing 'odoo16v2' as the new code left
domain='odoo16v.tenants.4th.online' (missing the '2'). Argo App
annotation, instance row, and Cloudflare A record all carried the
truncated domain.

Match DeployInstanceDrawer's pattern: track the last value we
auto-suggested in a previousDerivedDomain ref, compare against that
on each watcher fire.
2026-04-30 12:25:02 +03:00
Tower Bot
816328b49d tower 0.61.6 — hide platform cluster from tenant-scoped server lists
handleListServers's existing tenant filter would still admit a
platform cluster (type='platform') if it had been mis-labeled with
a tenant or if a tenant happened to host an instance there. Belt-
and-braces: explicit reject of any cluster with type='platform'
when the request is tenant-scoped (non-super-admin). The platform
control-plane runs Tower itself + platform-tenant template builds
— it is not a deployment target for customer tenants and surfacing
it in their Server picker breaks the bring-your-own-cluster model.

Caught while smoke-testing MigrateDrawer: a fresh tenant's Server
dropdown defaulted to 'Platform server', risking a customer
deploying their tenant data onto the operator's shared infra by
accident.
2026-04-30 11:54:32 +03:00
Tower Bot
53d8a21a19 tower 0.61.5 / tower-ui 0.61.9 — domain on Argo App, no more hardcode
The instance row's '<code>.tenants.odoosky.org' was being computed
client-side from the code alone, so a tenant whose domain is
'4th.online' still saw 'odoo16.tenants.odoosky.org' in the list +
the Open button — wrong zone, no cert, scary Firefox warning.

Backend: Argo App now carries an 'odoosky.io/domain' annotation
written at create time from req.Domain (the values.yaml domain),
read back in argoApplicationSummary.Domain. Delete handler reads
the same annotation so DNS cleanup hits the right Cloudflare zone
instead of the platform default.

Frontend: Instance.domain field, used by InstancesView, Vitals,
ActionBar, with a fallback to the legacy pattern for any pre-Phase-F
Argo App that hasn't been backfilled yet.

Backfill for live odoo16: kubectl annotate done out-of-band.
2026-04-30 11:47:07 +03:00
Tower Bot
dcef586b21 tower-ui 0.61.8 — Deploy/Migrate drawers read tenant wildcard
DeployInstanceDrawer + MigrateDrawer were hardcoding
'<code>.tenants.odoosky.org' as the auto-suggested instance domain,
even when the operator's tenant has its own domain set in Settings.
A tenant whose wildcardHost is '*.tenants.4th.online' would still see
the wizard pre-fill 'odoo16.tenants.odoosky.org' in the Domain field —
the suggestion landed in the wrong zone, instance create would fail
DNS write.

Both drawers now fetch /api/tenants/<id>/settings on mount and use
'tenants.<domain>' (with leading '*.' stripped from wildcardHost) as
the suffix, falling back to odoosky.org only if the call errors or
the field is empty.
2026-04-30 11:32:00 +03:00
Tower Bot
31e05e96c1 tower 0.61.4 — stamp owner tenant on cluster Secret at connect time
UpsertCluster was not setting the odoosky.io/tenant-id label on the
ArgoCD cluster Secret. Result: handleListServers tenant filter
attributed every freshly-connected cluster to no-tenant, making the
just-connected box invisible to the operator who registered it.

Threads ownerTenantID from the connect token through to the body of
the POST /api/v1/clusters call. Backfill of any pre-fix cluster
Secret is a one-line kubectl label.
2026-04-30 11:25:20 +03:00
Tower Bot
61669ff704 tower 0.61.3 — fix CF token Vault key mismatch
cloudflare_resolver.go reads data['token'] but the writer (and the
test endpoint) stores under data['api_token']. Result: every fresh
tenant's CF resolver returned no token even when one was saved,
killing DNS records view AND any instance lifecycle DNS write.

Caught while smoke-testing the multi-tenant signup flow.
2026-04-30 11:12:28 +03:00
Tower Bot
28f15c92f3 tower 0.61.2 / tower-ui 0.61.7 — fixes from end-to-end smoke
3 bugs caught while smoke-testing the multi-tenant signup → first
deploy flow:

1. /api/me returned capabilities: null when scope.role was nil
   (a tenant member viewing the platform tenant context). Frontend's
   useAuth.can() does for-of on the array → TypeError. Fix: backend
   returns []string{} not nil; frontend defends against null.

2. WelcomeView slug input pattern '[a-z][a-z0-9-]{2,39}' is rejected
   by Firefox's HTML5 form validation under the v-flag (treats trailing
   '-' as a class-range start). Move hyphen to start of the class.

3. Domain & DNS Save button is genuinely separate from the Cloudflare
   token Save (by design — token rotates independently). Documenting
   here so the next person doesn't think they saved when they didn't.
2026-04-30 11:03:12 +03:00
Tower Bot
9d9138231a tower 0.61.1 — Phase F (B): tenant-scoped S3 resolver
Refactor s3Resolver from a single-global-creds reader into a
tenant-scoped factory. Each tenant brings their own S3 endpoint,
region, three named buckets (backups + templates + audit), and
access keys (in Vault at v3/tenants/<id>/s3-credentials).

Touches:
  s3.go         — s3Resolver becomes factory; tenantS3 wraps
                  one minio.Client + bucket per tenant
  audit.go      — events grouped by tenantID per flush, written
                  to the tenant's audit bucket
  backups.go    — fleet view fans out one S3 LIST per tenant;
                  per-instance handlers resolve via Argo App
  export/import/migrate — tenant resolved from Argo App label
                  or scope.TenantID
  templates_*   — per-template tenant lookup via templateTenantID
                  (platform tenant for OwnerPlatform manifests)
  vitals.go     — last-backup probe pulls tenantID before list

Adds AllTenants() to PlatformStore so the templates orphan sweep
can iterate every tenant configured with a templates bucket.

Build: tower:0.61.1 — pushed to registry.odoosky.cloud
2026-04-30 10:37:24 +03:00
ops
3370097dcc tower 0.61.0 — Stage 2: per-tenant Gitea org for instance overlays (instance-<code> in tenant-<slug>) 2026-04-30 00:16:34 +03:00
ops
d81c313543 tower 0.60.12 — Stage 1: per-tenant Gitea org provisioned on tenant create/delete 2026-04-29 23:59:42 +03:00
ops
efce4b2e1d tower 0.60.11 / tower-ui 0.61.6 — wildcard cert badge on Server Detail 2026-04-29 23:39:42 +03:00
ops
77331e5e70 tower 0.60.10 — pass cluster.name helm param (per-cluster differentiator SAN) 2026-04-29 23:27:39 +03:00
ops
a912284226 tower 0.60.9 — apply cloudflare-api-token Secret to odoosky-system 2026-04-29 23:06:56 +03:00
ops
76d1ab036d tower 0.60.8 — Argo App: SkipDryRunOnMissingResource + retry limit 10 (CRD-ordering) 2026-04-29 22:32:04 +03:00
ops
7b2fadd132 tower-ui 0.61.5 — Connect drawer: no auto-close on success, manual Done button 2026-04-29 22:25:37 +03:00
ops
78bc427201 tower 0.60.7 — set source.path=. on per-cluster platform Application 2026-04-29 22:20:46 +03:00
ops
36c4d32d51 tower 0.60.6 — rewrite kubeconfig server URL before applying secrets (URL-token Connect) 2026-04-29 22:15:32 +03:00
ops
333850d994 tower 0.60.5 — bootstrap.sh: poll for node before kubectl wait (fixes kubectl --all race on fresh k3s) 2026-04-29 22:09:47 +03:00
ops
9aa2466d8d tower 0.60.4 / tower-ui 0.61.4 — disconnect resilient to dead customer cluster 2026-04-29 21:53:00 +03:00