Compare commits

...
Sign in to create a new pull request.

67 commits

Author SHA1 Message Date
cozystack-ci[bot]
2eb484d8d4 Prepare release v1.1.7
Signed-off-by: cozystack-ci[bot] <274107086+cozystack-ci[bot]@users.noreply.github.com>
2026-04-30 01:38:26 +00:00
myasnikovdaniil
5de17e5191
[Backport release-1.1] fix(platform): migrate ACME HTTP-01 to ingressClassName API (#2439)
ClusterIssuer solver referenced IngressClass "nginx" which does not
exist on cozystack clusters — real classes are named after tenant
namespaces (e.g. tenant-root). Cert issuance only worked because every
requesting Ingress overrode the ClusterIssuer via the legacy
acme.cert-manager.io/http01-ingress-class annotation.

Switch both sides to the modern cert-manager API (available since
cert-manager 1.12; cozystack ships 1.19.3):

- ClusterIssuer: http01.ingress.ingressClassName, value parameterized
from _cluster.expose-ingress (default "tenant-root")
- Ingress annotation: http01-ingress-ingressclassname

These must migrate together — mixing ingressClassName (ClusterIssuer)
with the old http01-ingress-class annotation triggers cert-manager's
"fields ingressClassName and class cannot be set at the same time"
validation and breaks issuance.

Assisted-By: Claude <noreply@anthropic.com>

(cherry picked from commit 2b6e20cc3f)

<!-- Thank you for making a contribution! Here are some tips for you:
- Use Conventional Commits for the PR title: `type(scope): description`
- Types: feat, fix, docs, style, refactor, perf, test, build, ci, chore
- Scopes for system components: dashboard, platform, cilium, kube-ovn,
linstor, fluxcd, cluster-api
- Scopes for managed apps: postgres, mariadb, redis, kafka, clickhouse,
virtual-machine, kubernetes
- Scopes for development and maintenance: api, hack, tests, ci, docs,
maintenance
- Breaking changes: append `!` after type/scope (`feat(api)!: ...`) or
add a `BREAKING CHANGE:` footer
- If it's a work in progress, consider creating this PR as a draft.
- Don't hesistate to ask for opinion and review in the community chats,
even if it's still a draft.
- Add the label `backport` if it's a bugfix that needs to be backported
to a previous version.
-->

## What this PR does


### Screenshots

<!-- REQUIRED for UI changes: attach screenshots or screen recordings
demonstrating
the visual impact of your changes. PRs with UI changes without
screenshots will not be merged. -->

### Release note

<!--  Write a release note:
- Explain what has changed internally and for users.
- Start with the same `type(scope):` prefix as in the PR title
- Follow the guidelines at
https://github.com/kubernetes/community/blob/master/contributors/guide/release-notes.md.
-->

```release-note

```
2026-04-21 19:34:57 +05:00
Myasnikov Daniil
72efe285c9
fix(platform): migrate ACME HTTP-01 to ingressClassName API
ClusterIssuer solver referenced IngressClass "nginx" which does not
exist on cozystack clusters — real classes are named after tenant
namespaces (e.g. tenant-root). Cert issuance only worked because every
requesting Ingress overrode the ClusterIssuer via the legacy
acme.cert-manager.io/http01-ingress-class annotation.

Switch both sides to the modern cert-manager API (available since
cert-manager 1.12; cozystack ships 1.19.3):

- ClusterIssuer: http01.ingress.ingressClassName, value parameterized
  from _cluster.expose-ingress (default "tenant-root")
- Ingress annotation: http01-ingress-ingressclassname

These must migrate together — mixing ingressClassName (ClusterIssuer)
with the old http01-ingress-class annotation triggers cert-manager's
"fields ingressClassName and class cannot be set at the same time"
validation and breaks issuance.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
(cherry picked from commit 2b6e20cc3f)
2026-04-21 18:45:00 +05:00
Andrei Kvapil
3f3470a120
Release v1.1.6 (#2381)
This PR prepares the release `v1.1.6`.
2026-04-13 19:04:16 +02:00
cozystack-ci[bot]
ddd452dcea Prepare release v1.1.6
Signed-off-by: cozystack-ci[bot] <274107086+cozystack-ci[bot]@users.noreply.github.com>
2026-04-13 14:53:11 +00:00
Andrei Kvapil
208a9337ee
ci: use cozystack org noreply email for bot commits (#2392)
## Summary
- Replace `3297617+cozystack-ci[bot]@users.noreply.github.com` with
`cozystack@users.noreply.github.com` across all CI workflows
- DCO probot rejects the `[bot]` brackets in email as invalid, causing
release PRs to fail DCO checks

## Test plan
- [ ] Verify DCO passes on release PRs after backport to release
branches

Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2026-04-13 16:37:43 +02:00
Andrei Kvapil
50977618f5
[Backport release-1.1] fix(build): filter git describe to match only v* tags (#2388)
# Description
Backport of #2386 to `release-1.1`.
2026-04-13 15:27:04 +02:00
Andrei Kvapil
718eb81b4b fix(build): filter git describe to match only v* tags
The api/apps/v1alpha1/* subtags share the same commit as the release
tags. git describe --exact-match picks the first match alphabetically,
returning api/apps/v1alpha1/vX.Y.Z instead of vX.Y.Z, which produces
invalid Docker image tags.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
(cherry picked from commit fbbccdbb7b)
2026-04-13 13:26:43 +00:00
Andrei Kvapil
7d9e9107eb
ci(pull-requests): replace GH_PAT with cozystack-ci GitHub App token (#2383)
## Summary
- Replace `GH_PAT` (cozystack-bot PAT) with `cozystack-ci` GitHub App
token in `pull-requests.yaml`
- This was missed in #2351 and broke the `resolve_assets` / `e2e` jobs
for release PRs (draft releases not visible with invalid token)

## Test plan
- [ ] Re-run the release PR pipeline for `release-1.1.6` and verify
`resolve_assets` job passes

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Chores**
* Updated continuous integration authentication mechanism to use GitHub
App tokens instead of personal access tokens.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2026-04-13 10:08:50 +02:00
Andrei Kvapil
53a8ce6fd0
Replace cozystack-bot PAT with cozystack-ci GitHub App (#2351)
Replaces the `cozystack-bot` user account + personal access token
(`GH_PAT`) with the `cozystack-ci` GitHub App across all CI/CD release
workflows.

- **Security**: GitHub App tokens are short-lived (1h) and scoped, vs. a
long-lived PAT tied to a user account
- **Auditability**: App actions are clearly attributed to
`cozystack-ci[bot]`
- **No 2FA issue**: The bot account lacked 2FA; GitHub Apps don't
require it
- **Best practice**: GitHub recommends Apps over PATs for automation

| Workflow | What changed |
|---|---|
| `tags.yaml` | App token in all 3 jobs: prepare-release,
generate-changelog, update-website-docs |
| `auto-release.yaml` | App token for daily auto-patch tagging |
| `pull-requests-release.yaml` | App token for release PR finalization |

All `cozystack-bot` git identity replaced with `cozystack-ci[bot]`.

- `COZYSTACK_CI_APP_ID` — GitHub App ID
- `COZYSTACK_CI_PRIVATE_KEY` — GitHub App private key

- [ ] Delete `GH_PAT` repo-level secret
- [ ] Remove `cozystack-bot` from the cozystack org

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

* **Chores**
* Updated CI/CD automation authentication mechanisms across release and
tagging workflows for improved security practices.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
2026-04-12 12:07:59 +02:00
Andrei Kvapil
601b605f97
Release v1.1.5 (#2305)
This PR prepares the release `v1.1.5`.
2026-03-31 10:15:40 +02:00
cozystack-bot
8364c25787 Prepare release v1.1.5
Signed-off-by: cozystack-bot <217169706+cozystack-bot@users.noreply.github.com>
2026-03-31 01:39:31 +00:00
Andrei Kvapil
fe26737ede
[Backport release-1.1] [linstor] Preserve TCP ports during toggle-disk operations (#2300)
# Description
Backport of #2292 to `release-1.1`.
2026-03-30 20:08:34 +02:00
Andrei Kvapil
a81a3679d2 fix(linstor): preserve TCP ports during toggle-disk operations
Update fix-duplicate-tcp-ports patch to preserve existing TCP ports when
DrbdRscData is recreated during toggle-disk operations. Without this,
removeLayerData() frees ports and ensureStackDataExists() may allocate
different ones, causing port mismatches between controller and satellites
if the satellite misses the update.

Also add dh_strip_nondeterminism override in Dockerfile to fix build
failures on some JAR files.

Upstream: https://github.com/LINBIT/linstor-server/pull/476#issuecomment-4147527442

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
(cherry picked from commit 812d4138bb)
2026-03-30 18:06:57 +00:00
Andrei Kvapil
7880d55587
[Backport release-1.1] [platform] Prevent installed packages deletion (#2298)
# Description
Backport of #2273 to `release-1.1`.
2026-03-30 20:04:55 +02:00
Myasnikov Daniil
ae72c69c1c [platform] Added resource-policy to keep installed packages
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
(cherry picked from commit a243f3d72a)
2026-03-30 16:37:02 +00:00
Andrei Kvapil
576e9ac5d7
Release v1.1.4 (#2275)
This PR prepares the release `v1.1.4`.
2026-03-30 13:33:08 +02:00
cozystack-bot
82a833267e Prepare release v1.1.4
Signed-off-by: cozystack-bot <217169706+cozystack-bot@users.noreply.github.com>
2026-03-30 01:37:40 +00:00
Kirill Ilin
9910188c5a
[Backport release-1.1] [linstor] Fix swapped VMPodScrape job labels (#2289)
# Description
Backport of #2264 to `release-1.1`.
2026-03-27 17:51:42 +05:00
Kirill Ilin
7fc6207fbc
[Backport release-1.1] [piraeus-operator] Fix LINSTOR satellite alert labels and scrape flapping false positives (#2286)
# Description
Backport of #2265 to `release-1.1`.
2026-03-27 17:51:33 +05:00
Kirill Ilin
8c24435ee7
[Backport release-1.1] [dashboard] Fix EndpointSlice column definitions for Pod serving table (#2283)
# Description
Backport of #2266 to `release-1.1`.
2026-03-27 17:51:11 +05:00
Kirill Ilin
9fb1f79d7c
[Backport release-1.1] [system] Fix tenant RBAC for endpointslices read access (#2285)
# Description
Backport of #2257 to `release-1.1`.
2026-03-27 17:51:04 +05:00
sasha-sup
890bb00e4b [linstor] Fix swapped VMPodScrape job labels
Signed-off-by: sasha-sup <alexsup4ik@gmail.com>
(cherry picked from commit bad59103f4)
2026-03-27 12:51:01 +00:00
sasha-sup
f961438c9d [piraeus-operator] Split LINSTOR controller availability and metrics alerts
Signed-off-by: sasha-sup <alexsup4ik@gmail.com>
(cherry picked from commit a052a650b0)
2026-03-27 12:50:55 +00:00
sasha-sup
53d603f50c [piraeus-operator] Add hold time to LINSTOR controller offline alert
Signed-off-by: sasha-sup <alexsup4ik@gmail.com>
(cherry picked from commit 4a92d7753c)
2026-03-27 12:50:55 +00:00
sasha-sup
f7161c2af8 [piraeus-operator] Fix LINSTOR satellite alert labels and scrape flapping false positives
Signed-off-by: sasha-sup <alexsup4ik@gmail.com>
(cherry picked from commit a562d9cb80)
2026-03-27 12:50:55 +00:00
Kirill Ilin
a33c143c2d fix(rbac): add endpointslices read access for tenant roles
Dashboard requests EndpointSlices from discovery.k8s.io API group
to display "Pod serving" section on Service details page. Without
this permission, tenant users see a 403 error.

Assisted-By: Claude AI
Signed-off-by: Kirill Ilin <stitch14@yandex.ru>
(cherry picked from commit ab92b67c3f)
2026-03-27 12:49:02 +00:00
Kirill Ilin
002d3eb44c fix(dashboard): add EndpointSlice column definitions for Pod serving table
The service details page showed "Raw:" and "Invalid Date" in the Pod
serving table because the EnrichedTable referenced customizationId
"factory-kube-service-details-endpointslice" which had no corresponding
CustomColumnsOverride. Add column definitions for Pod, Addresses, Ready,
and Node fields.

Assisted-By: Claude AI
Signed-off-by: Kirill Ilin <stitch14@yandex.ru>
(cherry picked from commit 37f1b3b59c)
2026-03-27 12:48:56 +00:00
Kirill Ilin
e77ee47e15
[Backport release-1.1] [dashboard] Fix StorageClass "Error" in forms by granting RBAC read access (#2274)
# Description
Backport of #2267 to `release-1.1`.
2026-03-26 21:02:05 +05:00
Kirill Ilin
7780f50714 fix(dashboard): grant read access to storageclasses for authenticated users
The dashboard UI fetches StorageClasses via the Kubernetes API to populate
dropdowns (e.g. in the Postgres form), but the cozystack-dashboard-readonly
ClusterRole did not include storage.k8s.io/storageclasses. This caused
authenticated users to see "Error" instead of the StorageClass name.

Add get/list/watch permissions for storageclasses to the dashboard readonly
role, consistent with the existing backupclasses entry.

Assisted-By: Claude AI
Signed-off-by: Kirill Ilin <stitch14@yandex.ru>
(cherry picked from commit a7a80a7628)
2026-03-26 14:58:14 +00:00
Kirill Ilin
ec7cef8c69
[Backport release-1.1] [dashboard] Fix broken backup menu links missing cluster context (#2269)
# Description
Backport of #2232 to `release-1.1`.
2026-03-26 18:56:22 +05:00
Kirill Ilin
bcdf308226
[Backport release-1.1] [platform] Add missing apps to tenant admin RBAC (#2272)
# Description
Backport of #2268 to `release-1.1`.
2026-03-26 18:55:32 +05:00
Kirill Ilin
d5d4d86556 fix(platform): add missing apps to tenant admin RBAC
The cozy:tenant:admin:base ClusterRole was missing several application
resources from apps.cozystack.io, preventing tenant admins from creating
them (the "Add" button was inactive in the dashboard).

Add the following resources: foundationdbs, harbors, mongodbs, openbaos,
opensearches, qdrants, vpns.

Assisted-By: Claude AI
Signed-off-by: Kirill Ilin <stitch14@yandex.ru>
(cherry picked from commit 8e01eb5f7e)
2026-03-26 13:05:16 +00:00
Kirill Ilin
efc640103d [dashboard] Add missing baseFactoriesMapping for backup resources
Backup resources (plans, backupjobs, backups) are not
ApplicationDefinitions, so ensureNavigation() never adds their
baseFactoriesMapping entries. Without these mappings the OpenUI
frontend cannot resolve the {cluster} context for backup pages,
producing broken sidebar links with an empty cluster segment
(e.g. /openapi-ui//tenant-root/...).

Add the three missing entries to the static Navigation resource.

Assisted-By: Claude AI
Signed-off-by: Kirill Ilin <stitch14@yandex.ru>
(cherry picked from commit 398ca5844a)
2026-03-26 12:49:30 +00:00
Andrei Kvapil
d799731923
Release v1.1.3 (#2236)
This PR prepares the release `v1.1.3`.
2026-03-19 07:59:36 +01:00
cozystack-bot
7fed9d4a42 Prepare release v1.1.3
Signed-off-by: cozystack-bot <217169706+cozystack-bot@users.noreply.github.com>
2026-03-19 01:36:21 +00:00
Andrei Kvapil
01ef9f1223
[Backport release-1.1] [dashboard] Add secret-hash annotation to KeycloakClient for secret sync (#2241)
# Description
Backport of #2231 to `release-1.1`.
2026-03-18 12:53:33 +01:00
Kirill Ilin
e4fadb50dc [dashboard] Add secret-hash annotation to KeycloakClient for secret sync
Add secret-hash annotation to the dashboard KeycloakClient CRD resource
so that when the Kubernetes Secret value changes, the operator detects
the CRD update and reconciles the client secret in Keycloak.

Without this annotation, if the dashboard-client Secret is recreated
with a new value (e.g. after upgrade), the KeycloakClient spec remains
unchanged, the operator skips reconciliation, and Keycloak retains the
stale secret — causing authentication failures for the dashboard.

Assisted-By: Claude AI
Signed-off-by: Kirill Ilin <stitch14@yandex.ru>
(cherry picked from commit bb5ee3ea4a)
2026-03-18 10:23:43 +00:00
Andrei Kvapil
736790852e
[Backport release-1.1] [etcd] Add protective limits to defrag CronJob (#2234)
# Description
Backport of #2233 to `release-1.1`.
2026-03-17 17:41:04 +01:00
Kirill Ilin
6017dabaee fix(etcd): add protective limits to defrag CronJob
Without concurrencyPolicy and job limits, the defrag CronJob can
accumulate hundreds of running/failed pods during cluster upgrades
when etcd is temporarily unavailable. This was observed after
upgrading to v1.1.2 where defrag jobs piled up across tenants.

Assisted-By: Claude AI
Signed-off-by: Kirill Ilin <stitch14@yandex.ru>
(cherry picked from commit ed8ba3beec)
2026-03-17 16:36:28 +00:00
Andrei Kvapil
ea641f7ec7
[Backport release-1.1] [kubernetes] Fix CiliumNetworkPolicy endpointSelector for multi-node RWX volumes (#2229)
# Description
Backport of #2227 to `release-1.1`.
2026-03-17 09:31:24 +01:00
mattia-eleuteri
2c23f1fab6 [kubernetes] Fix CiliumNetworkPolicy endpointSelector for multi-node RWX volumes
When an NFS-backed RWX volume is published to multiple VMs, the
CiliumNetworkPolicy egress rule only allowed traffic from the first VM.
The endpointSelector.matchLabels was set once on creation and never
broadened, causing NFS mounts to hang on all nodes except the first.

Switch from matchLabels to matchExpressions (operator: In) so the
selector can list multiple VM names. Rebuild the selector whenever
ownerReferences are added or removed.

Signed-off-by: mattia-eleuteri <mattia@hidora.io>
(cherry picked from commit cc5ec0b7a3)
2026-03-16 16:22:05 +00:00
Andrei Kvapil
2de20be50b
Release v1.1.2 (#2218)
This PR prepares the release `v1.1.2`.
2026-03-16 09:38:35 +01:00
cozystack-bot
1c87fe2004 Prepare release v1.1.2
Signed-off-by: cozystack-bot <217169706+cozystack-bot@users.noreply.github.com>
2026-03-16 01:36:47 +00:00
Andrei Kvapil
a18047fc16
[Backport release-1.1] fix(api): skip OpenAPI post-processor for non-apps group versions (#2217)
# Description
Backport of #2212 to `release-1.1`.
2026-03-13 16:24:27 +01:00
Andrei Kvapil
803d6b15ba
[Backport release-1.1] [bucket] Fix s3manager endpoint mismatch with COSI credentials (#2215)
# Description
Backport of #2211 to `release-1.1`.
2026-03-13 16:23:58 +01:00
Andrei Kvapil
c94768c64b Revert "fix(operator): requeue packages when dependencies are not ready"
This reverts commit f906a0d8ad.

Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
(cherry picked from commit 2b60c010dd)
2026-03-13 15:23:34 +00:00
Andrei Kvapil
093329cdce fix(operator): requeue packages when dependencies are not ready
When dependencies are not ready the reconciler returned without
requeueing, relying solely on watch events to re-trigger. If a watch
event was missed (controller restart, race condition, dependency already
ready before watch setup), the package would stay stuck in
DependenciesNotReady forever.

Add RequeueAfter: 30s so dependencies are periodically rechecked.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
(cherry picked from commit f906a0d8ad)
2026-03-13 15:23:34 +00:00
Andrei Kvapil
0e3c7fabff fix(api): skip OpenAPI post-processor for non-apps group versions
The OpenAPI PostProcessSpec callback is invoked for every group-version
(apps, core, version, etc.), but the Application schema cloning logic
only applies to apps.cozystack.io. When called for other GVs the base
Application schemas are absent, causing a spurious error log on every
API server start.

Return early instead of erroring when the base schemas are not found.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
(cherry picked from commit ee83aaa82e)
2026-03-13 15:23:34 +00:00
IvanHunters
0ba129b4b7 [bucket] Fix s3manager endpoint to use actual S3 endpoint from BucketInfo
The deployment template was constructing the S3 endpoint from the tenant's
namespace host (e.g. s3.freedom.infra.example.com), while COSI credentials
are issued for the actual SeaweedFS endpoint (e.g. s3.infra.example.com).
This mismatch caused 'invalid credentials' errors when users tried to log
in with valid credentials from the bucket secret.

Now the endpoint is resolved from BucketInfo (same source as credentials),
with a fallback to the constructed namespace host for first-time deploys
before BucketAccess secrets are created.

Signed-off-by: IvanHunters <xorokhotnikov@gmail.com>
(cherry picked from commit f647cfd7b9)
2026-03-13 15:23:24 +00:00
Andrei Kvapil
095d3ab150
Release v1.1.1 (#2184)
This PR prepares the release `v1.1.1`.
2026-03-11 00:12:15 +01:00
cozystack-bot
b06e2cecd5 Prepare release v1.1.1
Signed-off-by: cozystack-bot <217169706+cozystack-bot@users.noreply.github.com>
2026-03-10 20:31:40 +00:00
Andrei Kvapil
b255214da0
[Backport release-1.1] fix(dashboard): exclude hidden MarketplacePanel resources from sidebar menu (#2203)
# Description
Backport of #2177 to `release-1.1`.
2026-03-10 17:50:15 +01:00
Andrei Kvapil
4c9c68b7f5
[Backport release-1.1] fix(dashboard): preserve disabled/hidden state on MarketplacePanel reconciliation (#2201)
# Description
Backport of #2176 to `release-1.1`.
2026-03-10 17:50:04 +01:00
IvanHunters
002bd20f19 fix(dashboard): exclude hidden MarketplacePanel resources from sidebar menu
The sidebar was generated independently from MarketplacePanels, always
showing all resources regardless of their hidden state. Fetch
MarketplacePanels during sidebar reconciliation and skip resources
where hidden=true, so hiding a resource from the marketplace also
removes it from the sidebar navigation.

Signed-off-by: IvanHunters <xorokhotnikov@gmail.com>
(cherry picked from commit 318079bf66)
2026-03-10 16:49:44 +00:00
IvanHunters
22c46d7271 fix(dashboard): preserve disabled/hidden state on MarketplacePanel reconciliation
The controller was hardcoding disabled=false and hidden=false on every
reconciliation, overwriting any user changes made through the dashboard
UI. Move spec building inside the CreateOrUpdate mutate function to read
and preserve current disabled/hidden values from the existing resource.

Signed-off-by: IvanHunters <xorokhotnikov@gmail.com>
(cherry picked from commit e69efd80c4)
2026-03-10 16:48:33 +00:00
Andrei Kvapil
dce757c884
[Backport release-1.1] fix(dashboard): fix External IPs factory EnrichedTable rendering (#2193)
# Description
Backport of #2175 to `release-1.1`.
2026-03-10 15:19:42 +01:00
Andrei Kvapil
109b4a333e
[Backport release-1.1] [platform] Fix VM MAC address not preserved during migration (#2190)
# Description
Backport of #2169 to `release-1.1`.
2026-03-10 15:19:15 +01:00
IvanHunters
4dada99a92 fix(dashboard): fix External IPs factory EnrichedTable rendering
The external-ips factory used incorrect EnrichedTable properties causing
empty rows in the dashboard. Replace `clusterNamePartOfUrl` with
`cluster` and change `pathToItems` from array to dot-path string format
to match the convention used by all other working EnrichedTable instances.

Signed-off-by: IvanHunters <xorokhotnikov@gmail.com>
(cherry picked from commit 49601b166d)
2026-03-10 14:19:13 +00:00
Kirill Ilin
fd18a699b9 fix(migration): preserve VM MAC address during virtual-machine to vm-instance migration
Kube-OVN reads MAC address exclusively from the pod annotation
ovn.kubernetes.io/mac_address, not from the IP resource spec.macAddress.
Without pod-level annotations, migrated VMs receive a new random MAC,
breaking OS-level network config that matches by MAC (e.g. netplan).

Add a Helm lookup for the Kube-OVN IP resource in the vm-instance chart
template. When the IP resource exists, its macAddress and ipAddress are
automatically injected as pod annotations. This removes the need for
fragile Flux postRenderers on the HelmRelease — the chart itself handles
MAC/IP preservation based on actual cluster state.

Remove the postRenderers approach from migration 29 since the chart now
handles this natively.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Kirill Ilin <stitch14@yandex.ru>
(cherry picked from commit 9a4f49238c)
2026-03-10 14:18:35 +00:00
Andrei Kvapil
532669ad61
[Backport release-1.1] fix(etcd-operator): replace deprecated kube-rbac-proxy image (#2182)
# Description
Backport of #2181 to `release-1.1`.
2026-03-10 12:39:16 +01:00
Andrei Kvapil
116e1baf63 fix(etcd-operator): replace deprecated kube-rbac-proxy image
The gcr.io/kubebuilder/kube-rbac-proxy image is no longer available
since GCR was deprecated. Replace it with quay.io/brancz/kube-rbac-proxy
from the original upstream author.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
(cherry picked from commit 4946383cf1)
2026-03-10 11:37:08 +00:00
Andrei Kvapil
52b4a0a7c6
[Backport release-1.1] fix(migrations): handle missing rabbitmq CRD in migration 34 (#2180)
# Description
Backport of #2168 to `release-1.1`.
2026-03-10 09:03:24 +01:00
IvanHunters
0b4f3c7d30 fix(migrations): handle missing rabbitmq CRD in migration 34
Migration 34 fails when rabbitmqs.apps.cozystack.io CRD does not exist,
which happens when RabbitMQ was never installed on the cluster. Add a
check for CRD presence before attempting to list resources.

Signed-off-by: IvanHunters <xorokhotnikov@gmail.com>
(cherry picked from commit 21f293ace5)
2026-03-10 07:19:01 +00:00
Andrei Kvapil
ccb37d3fac
[Backport release-1.1] fix(keycloak): use management port health endpoints for probes (#2179)
# Description
Backport of #2162 to `release-1.1`.
2026-03-10 08:17:32 +01:00
mattia-eleuteri
89d90cac2d fix(keycloak): add startupProbe, remove initialDelaySeconds
Use a startupProbe to defer liveness/readiness checks until Keycloak
has fully started, instead of relying on initialDelaySeconds. This is
more robust for applications with variable startup times.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: mattia-eleuteri <mattia@hidora.io>
(cherry picked from commit d18ed79382)
2026-03-10 07:15:33 +00:00
mattia-eleuteri
4f9a035c5b fix(keycloak): use management port health endpoints for probes
Keycloak 26.x exposes dedicated health endpoints on the management
port (9000) via /health/live and /health/ready. The previous probes
used GET / on port 8080 which redirects to the configured KC_HOSTNAME
(HTTPS), causing kubelet to fail the probe with "Probe terminated
redirects" and eventually kill the pod in a crashloop.

Changes:
- Add KC_HEALTH_ENABLED=true to activate health endpoints
- Expose management port 9000 in container ports
- Switch liveness probe to /health/live on port 9000
- Switch readiness probe to /health/ready on port 9000
- Increase failure thresholds for more tolerance during startup

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: mattia-eleuteri <mattia@hidora.io>
(cherry picked from commit 0873691913)
2026-03-10 07:15:33 +00:00
61 changed files with 572 additions and 199 deletions

View file

@ -20,6 +20,14 @@ jobs:
pull-requests: read
steps:
- name: Generate GitHub App token
id: app-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.COZYSTACK_CI_APP_ID }}
private-key: ${{ secrets.COZYSTACK_CI_PRIVATE_KEY }}
owner: cozystack
- name: Checkout code
uses: actions/checkout@v4
with:
@ -28,27 +36,27 @@ jobs:
- name: Configure git
env:
GH_PAT: ${{ secrets.GH_PAT }}
APP_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
git config user.name "cozystack-bot"
git config user.email "217169706+cozystack-bot@users.noreply.github.com"
git remote set-url origin https://cozystack-bot:${GH_PAT}@github.com/${GITHUB_REPOSITORY}
git config user.name "cozystack-ci[bot]"
git config user.email "274107086+cozystack-ci[bot]@users.noreply.github.com"
git remote set-url origin https://x-access-token:${APP_TOKEN}@github.com/${GITHUB_REPOSITORY}
git config --unset-all http.https://github.com/.extraheader || true
- name: Process release branches
uses: actions/github-script@v7
env:
GH_PAT: ${{ secrets.GH_PAT }}
APP_TOKEN: ${{ steps.app-token.outputs.token }}
with:
github-token: ${{ secrets.GH_PAT }}
github-token: ${{ steps.app-token.outputs.token }}
script: |
const { execSync } = require('child_process');
// Configure git to use PAT for authentication
execSync('git config user.name "cozystack-bot"', { encoding: 'utf8' });
execSync('git config user.email "217169706+cozystack-bot@users.noreply.github.com"', { encoding: 'utf8' });
execSync(`git remote set-url origin https://cozystack-bot:${process.env.GH_PAT}@github.com/${process.env.GITHUB_REPOSITORY}`, { encoding: 'utf8' });
// Remove GITHUB_TOKEN extraheader to ensure PAT is used (needed to trigger other workflows)
// Configure git to use GitHub App token for authentication
execSync('git config user.name "cozystack-ci[bot]"', { encoding: 'utf8' });
execSync('git config user.email "274107086+cozystack-ci[bot]@users.noreply.github.com"', { encoding: 'utf8' });
execSync(`git remote set-url origin https://x-access-token:${process.env.APP_TOKEN}@github.com/${process.env.GITHUB_REPOSITORY}`, { encoding: 'utf8' });
// Remove GITHUB_TOKEN extraheader to ensure App token is used (needed to trigger other workflows)
execSync('git config --unset-all http.https://github.com/.extraheader || true', { encoding: 'utf8' });
// Get all release-X.Y branches

View file

@ -23,6 +23,14 @@ jobs:
contains(github.event.pull_request.labels.*.name, 'release')
steps:
- name: Generate GitHub App token
id: app-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.COZYSTACK_CI_APP_ID }}
private-key: ${{ secrets.COZYSTACK_CI_PRIVATE_KEY }}
owner: cozystack
# Extract tag from branch name (branch = release-X.Y.Z*)
- name: Extract tag from branch name
id: get_tag
@ -47,11 +55,11 @@ jobs:
- name: Create tag on merge commit
env:
GH_PAT: ${{ secrets.GH_PAT }}
APP_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
git config user.name "cozystack-bot"
git config user.email "217169706+cozystack-bot@users.noreply.github.com"
git remote set-url origin https://cozystack-bot:${GH_PAT}@github.com/${GITHUB_REPOSITORY}
git config user.name "cozystack-ci[bot]"
git config user.email "274107086+cozystack-ci[bot]@users.noreply.github.com"
git remote set-url origin https://x-access-token:${APP_TOKEN}@github.com/${GITHUB_REPOSITORY}
git tag -f ${{ steps.get_tag.outputs.tag }} ${{ github.sha }}
git push -f origin ${{ steps.get_tag.outputs.tag }}
@ -59,7 +67,7 @@ jobs:
- name: Ensure maintenance branch release-X.Y
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GH_PAT }}
github-token: ${{ steps.app-token.outputs.token }}
script: |
const tag = '${{ steps.get_tag.outputs.tag }}'; // e.g. v0.1.3 or v0.1.3-rc3
const match = tag.match(/^v(\d+)\.(\d+)\.\d+(?:[-\w\.]+)?$/);

View file

@ -85,6 +85,14 @@ jobs:
disk_id: ${{ steps.fetch_assets.outputs.disk_id }}
steps:
- name: Generate GitHub App token
id: app-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.COZYSTACK_CI_APP_ID }}
private-key: ${{ secrets.COZYSTACK_CI_PRIVATE_KEY }}
owner: cozystack
- name: Checkout code
if: contains(github.event.pull_request.labels.*.name, 'release')
uses: actions/checkout@v4
@ -111,7 +119,7 @@ jobs:
id: fetch_assets
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GH_PAT }}
github-token: ${{ steps.app-token.outputs.token }}
script: |
const tag = '${{ steps.get_tag.outputs.tag }}';
const releases = await github.rest.repos.listReleases({
@ -144,6 +152,15 @@ jobs:
if: ${{ always() && (needs.build.result == 'success' || needs.resolve_assets.result == 'success') }}
steps:
- name: Generate GitHub App token
if: contains(github.event.pull_request.labels.*.name, 'release')
id: app-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.COZYSTACK_CI_APP_ID }}
private-key: ${{ secrets.COZYSTACK_CI_PRIVATE_KEY }}
owner: cozystack
# ▸ Checkout and prepare the codebase
- name: Checkout code
uses: actions/checkout@v4
@ -173,11 +190,11 @@ jobs:
if: contains(github.event.pull_request.labels.*.name, 'release')
run: |
mkdir -p _out/assets
curl -sSL -H "Authorization: token ${GH_PAT}" -H "Accept: application/octet-stream" \
curl -sSL -H "Authorization: token ${APP_TOKEN}" -H "Accept: application/octet-stream" \
-o _out/assets/nocloud-amd64.raw.xz \
"https://api.github.com/repos/${GITHUB_REPOSITORY}/releases/assets/${{ needs.resolve_assets.outputs.disk_id }}"
env:
GH_PAT: ${{ secrets.GH_PAT }}
APP_TOKEN: ${{ steps.app-token.outputs.token }}
- name: Set sandbox ID
run: echo "SANDBOX_NAME=cozy-e2e-sandbox-$(echo "${GITHUB_REPOSITORY}:${GITHUB_WORKFLOW}:${GITHUB_REF}" | sha256sum | cut -c1-10)" >> $GITHUB_ENV

View file

@ -23,6 +23,14 @@ jobs:
actions: write
steps:
- name: Generate GitHub App token
id: app-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.COZYSTACK_CI_APP_ID }}
private-key: ${{ secrets.COZYSTACK_CI_PRIVATE_KEY }}
owner: cozystack
# Check if a non-draft release with this tag already exists
- name: Check if release already exists
id: check_release
@ -113,16 +121,31 @@ jobs:
- name: Commit release artifacts
if: steps.check_release.outputs.skip == 'false'
env:
GH_PAT: ${{ secrets.GH_PAT }}
APP_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
git config user.name "cozystack-bot"
git config user.email "217169706+cozystack-bot@users.noreply.github.com"
git remote set-url origin https://cozystack-bot:${GH_PAT}@github.com/${GITHUB_REPOSITORY}
git config user.name "cozystack-ci[bot]"
git config user.email "274107086+cozystack-ci[bot]@users.noreply.github.com"
git remote set-url origin https://x-access-token:${APP_TOKEN}@github.com/${GITHUB_REPOSITORY}
git config --unset-all http.https://github.com/.extraheader || true
git add .
git commit -m "Prepare release ${GITHUB_REF#refs/tags/}" -s || echo "No changes to commit"
git push origin HEAD || true
# Tag the api/apps/v1alpha1 submodule for pkg.go.dev
- name: Tag API submodule
if: steps.check_release.outputs.skip == 'false'
env:
APP_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
VTAG="${{ steps.tag.outputs.tag }}"
SUBTAG="api/apps/v1alpha1/${VTAG}"
git config user.name "cozystack-ci[bot]"
git config user.email "274107086+cozystack-ci[bot]@users.noreply.github.com"
git remote set-url origin https://x-access-token:${APP_TOKEN}@github.com/${GITHUB_REPOSITORY}
TARGET="$(git rev-parse "${VTAG}^{}")"
git tag -f "${SUBTAG}" "$TARGET"
git push -f origin "refs/tags/${SUBTAG}"
# Create or reuse draft release
- name: Create / reuse draft release
if: steps.check_release.outputs.skip == 'false'
@ -167,11 +190,11 @@ jobs:
- name: Create release branch
if: steps.check_release.outputs.skip == 'false'
env:
GH_PAT: ${{ secrets.GH_PAT }}
APP_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
git config user.name "cozystack-bot"
git config user.email "217169706+cozystack-bot@users.noreply.github.com"
git remote set-url origin https://cozystack-bot:${GH_PAT}@github.com/${GITHUB_REPOSITORY}
git config user.name "cozystack-ci[bot]"
git config user.email "274107086+cozystack-ci[bot]@users.noreply.github.com"
git remote set-url origin https://x-access-token:${APP_TOKEN}@github.com/${GITHUB_REPOSITORY}
BRANCH="release-${GITHUB_REF#refs/tags/v}"
git branch -f "$BRANCH"
git push -f origin "$BRANCH"
@ -181,7 +204,7 @@ jobs:
if: steps.check_release.outputs.skip == 'false'
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GH_PAT }}
github-token: ${{ steps.app-token.outputs.token }}
script: |
const version = context.ref.replace('refs/tags/v', '');
const base = '${{ steps.get_base.outputs.branch }}';
@ -223,6 +246,14 @@ jobs:
pull-requests: write
if: needs.prepare-release.result == 'success'
steps:
- name: Generate GitHub App token
id: app-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.COZYSTACK_CI_APP_ID }}
private-key: ${{ secrets.COZYSTACK_CI_PRIVATE_KEY }}
owner: cozystack
- name: Parse tag
id: tag
uses: actions/github-script@v7
@ -245,7 +276,7 @@ jobs:
ref: main
fetch-depth: 0
fetch-tags: true
token: ${{ secrets.GH_PAT }}
token: ${{ steps.app-token.outputs.token }}
- name: Check if changelog already exists
id: check_changelog
@ -273,7 +304,7 @@ jobs:
if: steps.check_changelog.outputs.exists == 'false'
env:
COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
GH_TOKEN: ${{ secrets.GH_PAT }}
GH_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
copilot --prompt "prepare changelog file for tagged release v${{ steps.tag.outputs.version }}, use @docs/agents/changelog.md for it. Create the changelog file at docs/changelogs/v${{ steps.tag.outputs.version }}.md" \
--allow-all-tools --allow-all-paths < /dev/null
@ -281,11 +312,11 @@ jobs:
- name: Create changelog branch and commit
if: steps.check_changelog.outputs.exists == 'false'
env:
GH_PAT: ${{ secrets.GH_PAT }}
APP_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
git config user.name "cozystack-bot"
git config user.email "217169706+cozystack-bot@users.noreply.github.com"
git remote set-url origin https://cozystack-bot:${GH_PAT}@github.com/${GITHUB_REPOSITORY}
git config user.name "cozystack-ci[bot]"
git config user.email "274107086+cozystack-ci[bot]@users.noreply.github.com"
git remote set-url origin https://x-access-token:${APP_TOKEN}@github.com/${GITHUB_REPOSITORY}
CHANGELOG_FILE="docs/changelogs/v${{ steps.tag.outputs.version }}.md"
CHANGELOG_BRANCH="changelog-v${{ steps.tag.outputs.version }}"
@ -320,7 +351,7 @@ jobs:
if: steps.check_changelog.outputs.exists == 'false'
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GH_PAT }}
github-token: ${{ steps.app-token.outputs.token }}
script: |
const version = '${{ steps.tag.outputs.version }}';
const changelogBranch = `changelog-v${version}`;
@ -370,3 +401,82 @@ jobs:
console.log(`Created PR #${pr.data.number} for changelog`);
}
update-website-docs:
name: Update Website Docs
runs-on: [self-hosted]
needs: [generate-changelog, prepare-release]
if: needs.generate-changelog.result == 'success' && needs.prepare-release.outputs.skip != 'true'
permissions:
contents: read
steps:
- name: Generate GitHub App token
id: app-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.COZYSTACK_CI_APP_ID }}
private-key: ${{ secrets.COZYSTACK_CI_PRIVATE_KEY }}
owner: cozystack
- name: Parse tag
id: tag
uses: actions/github-script@v7
with:
script: |
const ref = context.ref.replace('refs/tags/', '');
const m = ref.match(/^v(\d+\.\d+\.\d+)(-(?:alpha|beta|rc)\.\d+)?$/);
if (!m) {
core.setFailed(`❌ tag '${ref}' must match 'vX.Y.Z' or 'vX.Y.Z-(alpha|beta|rc).N'`);
return;
}
const version = m[1] + (m[2] ?? '');
core.setOutput('tag', ref); // v0.22.0
core.setOutput('version', version); // 0.22.0
- name: Checkout website repo
uses: actions/checkout@v4
with:
repository: cozystack/website
token: ${{ steps.app-token.outputs.token }}
ref: main
- name: Update docs from release branch
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
run: make update-all BRANCH=release-${{ steps.tag.outputs.version }} RELEASE_TAG=${{ steps.tag.outputs.tag }}
- name: Commit and push
id: commit
run: |
git config user.name "cozystack-ci[bot]"
git config user.email "274107086+cozystack-ci[bot]@users.noreply.github.com"
git add content
if git diff --cached --quiet; then
echo "No changes to commit"
echo "changed=false" >> $GITHUB_OUTPUT
exit 0
fi
BRANCH="update-docs-v${{ steps.tag.outputs.version }}"
git branch -D "$BRANCH" 2>/dev/null || true
git checkout -b "$BRANCH"
git commit --signoff -m "[docs] Update managed apps reference for v${{ steps.tag.outputs.version }}"
git push --force --set-upstream origin "$BRANCH"
echo "changed=true" >> $GITHUB_OUTPUT
- name: Open pull request
if: steps.commit.outputs.changed == 'true'
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
run: |
BRANCH="update-docs-v${{ steps.tag.outputs.version }}"
pr_state=$(gh pr view "$BRANCH" --repo cozystack/website --json state --jq .state 2>/dev/null || echo "")
if [[ "$pr_state" == "OPEN" ]]; then
echo "PR already open, skipping creation."
else
gh pr create \
--repo cozystack/website \
--title "[docs] Update managed apps reference for v${{ steps.tag.outputs.version }}" \
--body "Automated docs update for release \`v${{ steps.tag.outputs.version }}\`." \
--head "update-docs-v${{ steps.tag.outputs.version }}" \
--base main
fi

View file

@ -6,13 +6,13 @@ else
endif
REGISTRY ?= ghcr.io/cozystack/cozystack
TAG = $(shell git describe --tags --exact-match 2>/dev/null || echo latest)
TAG = $(shell git describe --tags --exact-match --match 'v*' 2>/dev/null || echo latest)
PUSH := 1
LOAD := 0
BUILDER ?=
PLATFORM ?=
BUILDX_EXTRA_ARGS ?=
COZYSTACK_VERSION = $(patsubst v%,%,$(shell git describe --tags))
COZYSTACK_VERSION = $(patsubst v%,%,$(shell git describe --tags --match 'v*'))
BUILDX_ARGS := --provenance=false --push=$(PUSH) --load=$(LOAD) \
--label org.opencontainers.image.source=https://github.com/cozystack/cozystack \
@ -28,6 +28,6 @@ endef
ifeq ($(COZYSTACK_VERSION),)
$(shell git remote add upstream https://github.com/cozystack/cozystack.git || true)
$(shell git fetch upstream --tags)
COZYSTACK_VERSION = $(patsubst v%,%,$(shell git describe --tags))
COZYSTACK_VERSION = $(patsubst v%,%,$(shell git describe --tags --match 'v*'))
endif

View file

@ -68,31 +68,46 @@ func (m *Manager) ensureMarketplacePanel(ctx context.Context, crd *cozyv1alpha1.
tags[i] = t
}
specMap := map[string]any{
"description": d.Description,
"name": displayName,
"type": "nonCrd",
"apiGroup": "apps.cozystack.io",
"apiVersion": "v1alpha1",
"plural": app.Plural, // e.g., "buckets"
"disabled": false,
"hidden": false,
"tags": tags,
"icon": d.Icon,
}
specBytes, err := json.Marshal(specMap)
if err != nil {
return reconcile.Result{}, err
}
_, err = controllerutil.CreateOrUpdate(ctx, m.Client, mp, func() error {
_, err := controllerutil.CreateOrUpdate(ctx, m.Client, mp, func() error {
if err := controllerutil.SetOwnerReference(crd, mp, m.Scheme); err != nil {
return err
}
// Add dashboard labels to dynamic resources
m.addDashboardLabels(mp, crd, ResourceTypeDynamic)
// Preserve user-set disabled/hidden values from existing resource
disabled := false
hidden := false
if mp.Spec.Raw != nil {
var existing map[string]any
if err := json.Unmarshal(mp.Spec.Raw, &existing); err == nil {
if v, ok := existing["disabled"].(bool); ok {
disabled = v
}
if v, ok := existing["hidden"].(bool); ok {
hidden = v
}
}
}
specMap := map[string]any{
"description": d.Description,
"name": displayName,
"type": "nonCrd",
"apiGroup": "apps.cozystack.io",
"apiVersion": "v1alpha1",
"plural": app.Plural, // e.g., "buckets"
"disabled": disabled,
"hidden": hidden,
"tags": tags,
"icon": d.Icon,
}
specBytes, err := json.Marshal(specMap)
if err != nil {
return err
}
// Only update spec if it's different to avoid unnecessary updates
newSpec := dashv1alpha1.ArbitrarySpec{
JSON: apiextv1.JSON{Raw: specBytes},

View file

@ -38,6 +38,23 @@ func (m *Manager) ensureSidebar(ctx context.Context, crd *cozyv1alpha1.Applicati
}
all = crdList.Items
// 1b) Fetch all MarketplacePanels to determine which resources are hidden
hiddenResources := map[string]bool{}
var mpList dashv1alpha1.MarketplacePanelList
if err := m.List(ctx, &mpList, &client.ListOptions{}); err == nil {
for i := range mpList.Items {
mp := &mpList.Items[i]
if mp.Spec.Raw != nil {
var spec map[string]any
if err := json.Unmarshal(mp.Spec.Raw, &spec); err == nil {
if hidden, ok := spec["hidden"].(bool); ok && hidden {
hiddenResources[mp.Name] = true
}
}
}
}
}
// 2) Build category -> []item map (only for CRDs with spec.dashboard != nil)
type item struct {
Key string
@ -63,6 +80,11 @@ func (m *Manager) ensureSidebar(ctx context.Context, crd *cozyv1alpha1.Applicati
plural := pickPlural(kind, def)
lowerKind := strings.ToLower(kind)
// Skip resources hidden via MarketplacePanel
if hiddenResources[def.Name] {
continue
}
// Check if this resource is a module
if def.Spec.Dashboard.Module {
// Special case: info should have its own keysAndTags, not be in modules

View file

@ -154,6 +154,14 @@ func CreateAllCustomColumnsOverrides() []*dashboardv1alpha1.CustomColumnsOverrid
createStringColumn("Version", ".status.version"),
}),
// Factory service details endpointslice (Pod serving table)
createCustomColumnsOverride("factory-kube-service-details-endpointslice", []any{
createStringColumn("Pod", ".targetRef.name"),
createArrayColumn("Addresses", ".addresses"),
createBoolColumn("Ready", ".conditions.ready"),
createStringColumn("Node", ".nodeName"),
}),
// Factory service details port mapping
createCustomColumnsOverride("factory-kube-service-details-port-mapping", []any{
createStringColumn("Name", ".name"),
@ -1924,12 +1932,12 @@ func CreateAllFactories() []*dashboardv1alpha1.Factory {
map[string]any{
"type": "EnrichedTable",
"data": map[string]any{
"id": "external-ips-table",
"fetchUrl": "/api/clusters/{2}/k8s/api/v1/namespaces/{3}/services",
"clusterNamePartOfUrl": "{2}",
"baseprefix": "/openapi-ui",
"customizationId": "factory-details-v1.services",
"pathToItems": []any{"items"},
"id": "external-ips-table",
"fetchUrl": "/api/clusters/{2}/k8s/api/v1/namespaces/{3}/services",
"cluster": "{2}",
"baseprefix": "/openapi-ui",
"customizationId": "factory-details-v1.services",
"pathToItems": ".items",
"fieldSelector": map[string]any{
"spec.type": "LoadBalancer",
},
@ -1985,6 +1993,10 @@ func CreateAllNavigations() []*dashboardv1alpha1.Navigation {
// Namespaced API resources
"base-factory-namespaced-api-networking.k8s.io-v1-ingresses": "kube-ingress-details",
"base-factory-namespaced-api-cozystack.io-v1alpha1-workloadmonitors": "workloadmonitor-details",
// Backup resources (not ApplicationDefinitions, so ensureNavigation doesn't cover them)
"base-factory-namespaced-api-backups.cozystack.io-v1alpha1-plans": "plan-details",
"base-factory-namespaced-api-backups.cozystack.io-v1alpha1-backupjobs": "backupjob-details",
"base-factory-namespaced-api-backups.cozystack.io-v1alpha1-backups": "backup-details",
}
return []*dashboardv1alpha1.Navigation{

View file

@ -15,7 +15,7 @@ metadata:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/backend-protocol: "HTTP"
{{- if eq $solver "http01" }}
acme.cert-manager.io/http01-ingress-class: {{ $ingress }}
acme.cert-manager.io/http01-ingress-ingressclassname: {{ $ingress }}
{{- end }}
cert-manager.io/cluster-issuer: {{ $clusterIssuer }}
spec:

View file

@ -1 +1 @@
ghcr.io/cozystack/cozystack/nginx-cache:0.0.0@sha256:cb25e40cb665b8bbeee8cb1ec39da4c9a7452ef3f2f371912bbc0d1b1e2d40a8
ghcr.io/cozystack/cozystack/nginx-cache:0.0.0@sha256:d397781152ab9123b11b8191d92eba7a0d2faa376aa2c15ddeb67842a9b59bab

View file

@ -1 +1 @@
ghcr.io/cozystack/cozystack/cluster-autoscaler:0.0.0@sha256:3753b735b0315bee90de54cb25cfebc63bd2cc90ad11ca4fdc0e70439abd5096
ghcr.io/cozystack/cozystack/cluster-autoscaler:0.0.0@sha256:e9d0aa7e651b03a6713b380101a61832818a91b5f989da9754f58693898c4056

View file

@ -1 +1 @@
ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.0.0@sha256:faaa6bcdb68196edb4baafe643679bd7d2ef35f910c639b71e06a4ecc034f232
ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.0.0@sha256:c40b352c18e4a7d9b3a40d6c29c32bbf6dd9bd8fdca35185b194187e797aeeb6

View file

@ -317,11 +317,7 @@ func (w *WrappedControllerService) ControllerPublishVolume(ctx context.Context,
"ownerReferences": []interface{}{vmiOwnerRef},
},
"spec": map[string]interface{}{
"endpointSelector": map[string]interface{}{
"matchLabels": map[string]interface{}{
"kubevirt.io/vm": vmName,
},
},
"endpointSelector": buildEndpointSelector([]string{vmName}),
"egress": []interface{}{
map[string]interface{}{
"toEndpoints": []interface{}{
@ -441,6 +437,13 @@ func (w *WrappedControllerService) addCNPOwnerReference(ctx context.Context, nam
if err := unstructured.SetNestedSlice(existing.Object, ownerRefs, "metadata", "ownerReferences"); err != nil {
return status.Errorf(codes.Internal, "failed to set ownerReferences: %v", err)
}
// Rebuild endpointSelector to include all VMs
selector := buildEndpointSelector(vmNamesFromOwnerRefs(ownerRefs))
if err := unstructured.SetNestedField(existing.Object, selector, "spec", "endpointSelector"); err != nil {
return status.Errorf(codes.Internal, "failed to set endpointSelector: %v", err)
}
if _, err := w.dynamicClient.Resource(ciliumNetworkPolicyGVR).Namespace(namespace).Update(ctx, existing, metav1.UpdateOptions{}); err != nil {
return err
}
@ -486,6 +489,13 @@ func (w *WrappedControllerService) removeCNPOwnerReference(ctx context.Context,
if err := unstructured.SetNestedSlice(existing.Object, remaining, "metadata", "ownerReferences"); err != nil {
return status.Errorf(codes.Internal, "failed to set ownerReferences: %v", err)
}
// Rebuild endpointSelector from remaining VMs
selector := buildEndpointSelector(vmNamesFromOwnerRefs(remaining))
if err := unstructured.SetNestedField(existing.Object, selector, "spec", "endpointSelector"); err != nil {
return status.Errorf(codes.Internal, "failed to set endpointSelector: %v", err)
}
if _, err := w.dynamicClient.Resource(ciliumNetworkPolicyGVR).Namespace(namespace).Update(ctx, existing, metav1.UpdateOptions{}); err != nil {
return err
}
@ -494,6 +504,37 @@ func (w *WrappedControllerService) removeCNPOwnerReference(ctx context.Context,
})
}
// buildEndpointSelector returns an endpointSelector using matchExpressions
// so that multiple VMs can be listed in a single selector.
func buildEndpointSelector(vmNames []string) map[string]interface{} {
values := make([]interface{}, len(vmNames))
for i, name := range vmNames {
values[i] = name
}
return map[string]interface{}{
"matchExpressions": []interface{}{
map[string]interface{}{
"key": "kubevirt.io/vm",
"operator": "In",
"values": values,
},
},
}
}
// vmNamesFromOwnerRefs extracts VM names from ownerReferences.
func vmNamesFromOwnerRefs(ownerRefs []interface{}) []string {
var names []string
for _, ref := range ownerRefs {
if refMap, ok := ref.(map[string]interface{}); ok {
if name, ok := refMap["name"].(string); ok {
names = append(names, name)
}
}
}
return names
}
func hasRWXAccessMode(pvc *corev1.PersistentVolumeClaim) bool {
for _, mode := range pvc.Spec.AccessModes {
if mode == corev1.ReadWriteMany {

View file

@ -1 +1 @@
ghcr.io/cozystack/cozystack/ubuntu-container-disk:v1.35@sha256:39f626c802dd84f95720ffb54fcd80dfb8a58ac280498870d0a1aa30d4252f94
ghcr.io/cozystack/cozystack/ubuntu-container-disk:v1.35@sha256:364c6d454891f1eb1a598fddb69cf328a14dbc451a8ac65812b038a7756da60a

View file

@ -1 +1 @@
ghcr.io/cozystack/cozystack/mariadb-backup:0.0.0@sha256:0ddbbec0568dcb9fbc317cd9cc654e826dbe88ba3f184fa9b6b58aacb93b4570
ghcr.io/cozystack/cozystack/mariadb-backup:0.0.0@sha256:3841eb171416711977dea0cf8cd45d32344caac9727af760c37d5e1dd41ee4bb

View file

@ -34,6 +34,12 @@ spec:
metadata:
annotations:
kubevirt.io/allow-pod-bridge-network-live-migration: "true"
{{- $ovnIPName := printf "%s.%s" (include "virtual-machine.fullname" .) .Release.Namespace }}
{{- $ovnIP := lookup "kubeovn.io/v1" "IP" "" $ovnIPName }}
{{- if $ovnIP }}
ovn.kubernetes.io/mac_address: {{ $ovnIP.spec.macAddress | quote }}
ovn.kubernetes.io/ip_address: {{ $ovnIP.spec.ipAddress | quote }}
{{- end }}
labels:
{{- include "virtual-machine.labels" . | nindent 8 }}
spec:

View file

@ -1,9 +1,9 @@
cozystackOperator:
# Deployment variant: talos, generic, hosted
variant: talos
image: ghcr.io/cozystack/cozystack/cozystack-operator:v1.1.0@sha256:9367001a8d1d2dcf08ae74a42ac234eaa6af18f1af64ac28ce8a5946af9c5d3f
image: ghcr.io/cozystack/cozystack/cozystack-operator:v1.1.7@sha256:cac3ed524881459a70b1b2384feacc045674e2b6390b695b959a01022b469642
platformSourceUrl: 'oci://ghcr.io/cozystack/cozystack/cozystack-packages'
platformSourceRef: 'digest=sha256:7c6da38e7b99ec80d35ba2cef721ea1579f8a0824989454544fa85318bb7bf15'
platformSourceRef: 'digest=sha256:bda5caa46b06dab270ef2416fc2468c79e6df8c40532de0b8e6172e72a7e5f44'
# Generic variant configuration (only used when cozystackOperator.variant=generic)
cozystack:
# Kubernetes API server host (IP only, no protocol/port)

View file

@ -13,6 +13,15 @@
set -euo pipefail
DEFAULT_VERSION="v3.13"
# Skip if the CRD does not exist (rabbitmq was never installed)
if ! kubectl api-resources --api-group=apps.cozystack.io -o name 2>/dev/null | grep -q '^rabbitmqs\.'; then
echo "CRD rabbitmqs.apps.cozystack.io not found, skipping migration"
kubectl create configmap -n cozy-system cozystack-version \
--from-literal=version=35 --dry-run=client -o yaml | kubectl apply -f-
exit 0
fi
RABBITMQS=$(kubectl get rabbitmqs.apps.cozystack.io -A -o jsonpath='{range .items[*]}{.metadata.namespace}/{.metadata.name}{"\n"}{end}')
for resource in $RABBITMQS; do
NS="${resource%%/*}"

View file

@ -13,6 +13,8 @@ apiVersion: cozystack.io/v1alpha1
kind: Package
metadata:
name: {{ $name }}
annotations:
helm.sh/resource-policy: keep
spec:
variant: {{ $variant }}
{{- if $components }}
@ -40,6 +42,8 @@ apiVersion: cozystack.io/v1alpha1
kind: Package
metadata:
name: {{ $name }}
annotations:
helm.sh/resource-policy: keep
spec:
variant: {{ $variant }}
{{- end }}

View file

@ -5,7 +5,7 @@ sourceRef:
path: /
migrations:
enabled: false
image: ghcr.io/cozystack/cozystack/platform-migrations:v1.1.0@sha256:d7e8955c1ad8c8fbd4ce42b014c0f849d73d0c3faf0cedaac8e15d647fb2f663
image: ghcr.io/cozystack/cozystack/platform-migrations:v1.1.7@sha256:bcbe612879cecd2ae1cef91dfff6d34d009c2f7de6592145c04a2d6d21b28f4b
targetVersion: 35
# Bundle deployment configuration
bundles:

View file

@ -1,2 +1,2 @@
e2e:
image: ghcr.io/cozystack/cozystack/e2e-sandbox:v1.1.0@sha256:0eae9f519669667d60b160ebb93c127843c470ad9ca3447fceaa54604503a7ba
image: ghcr.io/cozystack/cozystack/e2e-sandbox:v1.1.7@sha256:0367a03b981df2a3ea13f411d4cb7869c2bf2c89c07d3d5c8971b9a28921ccef

View file

@ -1 +1 @@
ghcr.io/cozystack/cozystack/matchbox:v1.1.0@sha256:e4c872f6dadc2bbcb9200d04a1d9878f62502f74e979b4eae6c7203abc6d8fa6
ghcr.io/cozystack/cozystack/matchbox:v1.1.7@sha256:c184033a07b48c8c0519bcf1d31de3820730786b23b2f2d4deb9f56b1b62a49e

View file

@ -10,7 +10,7 @@ metadata:
app: bootbox
annotations:
{{- if eq $solver "http01" }}
acme.cert-manager.io/http01-ingress-class: {{ $ingress }}
acme.cert-manager.io/http01-ingress-ingressclassname: {{ $ingress }}
{{- end }}
cert-manager.io/cluster-issuer: {{ $clusterIssuer }}
{{- if .Values.whitelistHTTP }}

View file

@ -4,9 +4,14 @@ metadata:
name: {{ .Release.Name }}-defrag
spec:
schedule: "0 * * * *"
concurrencyPolicy: Forbid
startingDeadlineSeconds: 300
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 1
jobTemplate:
spec:
activeDeadlineSeconds: 1800
backoffLimit: 2
template:
spec:
containers:

View file

@ -1 +1 @@
ghcr.io/cozystack/cozystack/objectstorage-sidecar:v1.1.0@sha256:2a3595cd88b30af55b2000d3ca204899beecef0012b0e0402754c3914aad1f7f
ghcr.io/cozystack/cozystack/objectstorage-sidecar:v1.1.7@sha256:fd3571e746efbc65fab342ca557beb2d0ad46fc0183772605fe84c6d3dd446a4

View file

@ -243,7 +243,7 @@ spec:
nginx.ingress.kubernetes.io/proxy-body-size: "0"
nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
{{- if eq $solver "http01" }}
acme.cert-manager.io/http01-ingress-class: {{ $ingress }}
acme.cert-manager.io/http01-ingress-ingressclassname: {{ $ingress }}
{{- end }}
cert-manager.io/cluster-issuer: {{ $clusterIssuer }}
tls:

View file

@ -1,5 +1,5 @@
backupController:
image: "ghcr.io/cozystack/cozystack/backup-controller:v1.1.0@sha256:8e42e29f5d30ecbef1f05cb0601c32703c5f9572b89d2c9032c1dff186e9a526"
image: "ghcr.io/cozystack/cozystack/backup-controller:v1.1.7@sha256:ee54ad2007f7f103ba3d01ba95fa62e28419e96ba14cc8f96c7283bdac886f18"
replicas: 2
debug: false
metrics:

View file

@ -1,5 +1,5 @@
backupStrategyController:
image: "ghcr.io/cozystack/cozystack/backupstrategy-controller:v1.1.0@sha256:508e3bd5a83a316732cfb84fe598064e3092482d941cfc53738ca21237642e6f"
image: "ghcr.io/cozystack/cozystack/backupstrategy-controller:v1.1.7@sha256:3dcdbf368a33c85961fe1b692c0bfe5280c8c3c64bb8235c89d5efc1ea33c2d9"
replicas: 2
debug: false
metrics:

View file

@ -1 +1 @@
ghcr.io/cozystack/cozystack/s3manager:v0.5.0@sha256:5a7cae722ff6b424bdfbc4aba9d072c11b6930e2ee0f5fa97c3a565bd1c8dc88
ghcr.io/cozystack/cozystack/s3manager:v0.5.0@sha256:c785518051e005de3e2a8393949b7a0040bd9bca9c0aea556ee3f401cc93e631

View file

@ -1,3 +1,12 @@
{{- $endpoint := printf "s3.%s" .Values._namespace.host }}
{{- range $name, $user := .Values.users }}
{{- $secretName := printf "%s-%s" $.Values.bucketName $name }}
{{- $existingSecret := lookup "v1" "Secret" $.Release.Namespace $secretName }}
{{- if $existingSecret }}
{{- $bucketInfo := fromJson (b64dec (index $existingSecret.data "BucketInfo")) }}
{{- $endpoint = trimPrefix "https://" (index $bucketInfo.spec.secretS3 "endpoint") }}
{{- end }}
{{- end }}
apiVersion: apps/v1
kind: Deployment
metadata:
@ -17,6 +26,6 @@ spec:
image: "{{ $.Files.Get "images/s3manager.tag" | trim }}"
env:
- name: ENDPOINT
value: "s3.{{ .Values._namespace.host }}"
value: {{ $endpoint | quote }}
- name: SKIP_SSL_VERIFICATION
value: "true"

View file

@ -12,7 +12,7 @@ metadata:
nginx.ingress.kubernetes.io/proxy-read-timeout: "99999"
nginx.ingress.kubernetes.io/proxy-send-timeout: "99999"
{{- if eq $solver "http01" }}
acme.cert-manager.io/http01-ingress-class: {{ $ingress }}
acme.cert-manager.io/http01-ingress-ingressclassname: {{ $ingress }}
{{- end }}
cert-manager.io/cluster-issuer: {{ $clusterIssuer }}
spec:

View file

@ -1,6 +1,7 @@
{{- $solver := (index .Values._cluster "solver") | default "http01" }}
{{- $exposeIngress := (index .Values._cluster "expose-ingress") | default "tenant-root" }}
apiVersion: cert-manager.io/v1
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
@ -17,9 +18,9 @@ spec:
name: cloudflare-api-token-secret
key: api-token
{{- else }}
http01:
ingress:
class: nginx
http01:
ingress:
ingressClassName: {{ $exposeIngress }}
{{- end }}
---
@ -41,9 +42,9 @@ spec:
name: cloudflare-api-token-secret
key: api-token
{{- else }}
http01:
ingress:
class: nginx
http01:
ingress:
ingressClassName: {{ $exposeIngress }}
{{- end }}
---

View file

@ -1,3 +1,3 @@
cozystackAPI:
image: ghcr.io/cozystack/cozystack/cozystack-api:v1.1.0@sha256:3a8e559b1a71cffb445bab14178d9abeba1b90509f9fec31df5ff5a9a38333d1
image: ghcr.io/cozystack/cozystack/cozystack-api:v1.1.7@sha256:035d2dafb4a4cd5e806c5cdab19916fca7f9550ef898263cc8c891eaac745085
replicas: 2

View file

@ -21,6 +21,9 @@ rules:
- apiGroups: [""]
resources: ["pods", "services", "persistentvolumes", "endpoints", "events", "resourcequotas"]
verbs: ["get", "list", "watch"]
- apiGroups: ["discovery.k8s.io"]
resources: ["endpointslices"]
verbs: ["get", "list", "watch"]
- apiGroups: ["networking.k8s.io"]
resources: ["ingresses"]
verbs: ["get", "list", "watch"]
@ -94,6 +97,14 @@ rules:
- get
- list
- watch
- apiGroups:
- discovery.k8s.io
resources:
- endpointslices
verbs:
- get
- list
- watch
- apiGroups:
- networking.k8s.io
resources:
@ -194,12 +205,18 @@ rules:
- buckets
- clickhouses
- foos
- foundationdbs
- harbors
- httpcaches
- kafkas
- kuberneteses
- mariadbs
- mongodbs
- natses
- openbaos
- opensearches
- postgreses
- qdrants
- rabbitmqs
- redises
- seaweedfses
@ -207,6 +224,7 @@ rules:
- virtualmachines
- vmdisks
- vminstances
- vpns
- infos
- virtualprivateclouds
verbs:

View file

@ -1,4 +1,4 @@
cozystackController:
image: ghcr.io/cozystack/cozystack/cozystack-controller:v1.1.0@sha256:f04fa839924a761571e1035d83f380f39f62d1708ea8d22f7a323f17bb59ff96
image: ghcr.io/cozystack/cozystack/cozystack-controller:v1.1.7@sha256:84a4b7770622308242be8371d4c25af201f817b75c5fc0b1d54da27d2f68f102
debug: false
disableTelemetry: false

View file

@ -1,6 +1,6 @@
{{- $brandingConfig := .Values._cluster.branding | default dict }}
{{- $tenantText := "v1.1.0" }}
{{- $tenantText := "v1.1.7" }}
{{- $footerText := "Cozystack" }}
{{- $titleText := "Cozystack Dashboard" }}
{{- $logoText := "" }}

View file

@ -11,7 +11,7 @@ metadata:
annotations:
cert-manager.io/cluster-issuer: {{ $clusterIssuer }}
{{- if eq $solver "http01" }}
acme.cert-manager.io/http01-ingress-class: {{ $exposeIngress }}
acme.cert-manager.io/http01-ingress-ingressclassname: {{ $exposeIngress }}
{{- end }}
nginx.ingress.kubernetes.io/rewrite-target: /
nginx.ingress.kubernetes.io/client-max-body-size: 100m

View file

@ -49,6 +49,8 @@ apiVersion: v1.edp.epam.com/v1
kind: KeycloakClient
metadata:
name: dashboard-client
annotations:
secret-hash: {{ $dashboardClient | sha256sum }}
spec:
serviceAccount:
enabled: true

View file

@ -20,6 +20,14 @@ rules:
- get
- list
- watch
- apiGroups:
- storage.k8s.io
resources:
- storageclasses
verbs:
- get
- list
- watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding

View file

@ -1,6 +1,6 @@
openapiUI:
image: ghcr.io/cozystack/cozystack/openapi-ui:v1.1.0@sha256:bc530ae2e428727eed284d7f80b2eea4fdd98b7618d20cab262eef7199af5fa5
image: ghcr.io/cozystack/cozystack/openapi-ui:v1.1.7@sha256:e5971ae9138bb736bfbcccd39ca4c0d96d9df98190f7e33963b4de0223b31d72
openapiUIK8sBff:
image: ghcr.io/cozystack/cozystack/openapi-ui-k8s-bff:v1.1.0@sha256:c938fee904acd948800d4dc5e121c4c5cd64cb4a3160fb8d2f9dbff0e5168740
image: ghcr.io/cozystack/cozystack/openapi-ui-k8s-bff:v1.1.7@sha256:75d51a65e190e83f538ae3cafdaa756930e53c7112a1daa4080d92dc67a9532a
tokenProxy:
image: ghcr.io/cozystack/cozystack/token-proxy:v1.1.0@sha256:2e280991e07853ea48f97b0a42946afffa10d03d6a83d41099ed83e6ffc94fdc
image: ghcr.io/cozystack/cozystack/token-proxy:v1.1.7@sha256:2e280991e07853ea48f97b0a42946afffa10d03d6a83d41099ed83e6ffc94fdc

View file

@ -38,8 +38,8 @@
| kubeRbacProxy.args[2] | string | `"--logtostderr=true"` | |
| kubeRbacProxy.args[3] | string | `"--v=0"` | |
| kubeRbacProxy.image.pullPolicy | string | `"IfNotPresent"` | Image pull policy |
| kubeRbacProxy.image.repository | string | `"gcr.io/kubebuilder/kube-rbac-proxy"` | Image repository |
| kubeRbacProxy.image.tag | string | `"v0.16.0"` | Version of image |
| kubeRbacProxy.image.repository | string | `"quay.io/brancz/kube-rbac-proxy"` | Image repository |
| kubeRbacProxy.image.tag | string | `"v0.18.1"` | Version of image |
| kubeRbacProxy.livenessProbe | object | `{}` | https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/ |
| kubeRbacProxy.readinessProbe | object | `{}` | https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/ |
| kubeRbacProxy.resources | object | `{"limits":{"cpu":"250m","memory":"128Mi"},"requests":{"cpu":"100m","memory":"64Mi"}}` | ref: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ |

View file

@ -98,13 +98,13 @@ kubeRbacProxy:
image:
# -- Image repository
repository: gcr.io/kubebuilder/kube-rbac-proxy
repository: quay.io/brancz/kube-rbac-proxy
# -- Image pull policy
pullPolicy: IfNotPresent
# -- Version of image
tag: v0.16.0
tag: v0.18.1
args:
- --secure-listen-address=0.0.0.0:8443

View file

@ -1 +1 @@
ghcr.io/cozystack/cozystack/grafana-dashboards:v1.1.0@sha256:2c9aa0b48e2bf6167db198f4d15882bfe51700108edf2e9f6d0942940a2c1204
ghcr.io/cozystack/cozystack/grafana-dashboards:v1.1.7@sha256:2c9aa0b48e2bf6167db198f4d15882bfe51700108edf2e9f6d0942940a2c1204

View file

@ -3,7 +3,7 @@ kamaji:
deploy: false
image:
pullPolicy: IfNotPresent
tag: v1.1.0@sha256:914d04f7442f0faecf18f8282c192dee9fe244a711494a8c892e2f9e2ad415f7
tag: v1.1.7@sha256:e9456e26c2b6f681704ad7b3017444eb244b370c8b5b3aee8ae30e967022f08b
repository: ghcr.io/cozystack/cozystack/kamaji
resources:
limits:
@ -13,4 +13,4 @@ kamaji:
cpu: 100m
memory: 100Mi
extraArgs:
- --migrate-image=ghcr.io/cozystack/cozystack/kamaji:v1.1.0@sha256:914d04f7442f0faecf18f8282c192dee9fe244a711494a8c892e2f9e2ad415f7
- --migrate-image=ghcr.io/cozystack/cozystack/kamaji:v1.1.7@sha256:e9456e26c2b6f681704ad7b3017444eb244b370c8b5b3aee8ae30e967022f08b

View file

@ -11,7 +11,7 @@ metadata:
{{- with .Values.ingress.annotations }}
annotations:
{{- if eq $solver "http01" }}
acme.cert-manager.io/http01-ingress-class: {{ $exposeIngress }}
acme.cert-manager.io/http01-ingress-ingressclassname: {{ $exposeIngress }}
{{- end }}
cert-manager.io/cluster-issuer: {{ $clusterIssuer }}
{{- toYaml . | nindent 4 }}

View file

@ -76,6 +76,8 @@ spec:
{{- end }}
- name: KC_METRICS_ENABLED
value: "true"
- name: KC_HEALTH_ENABLED
value: "true"
- name: KC_LOG_LEVEL
value: "info"
- name: KC_CACHE
@ -130,16 +132,27 @@ spec:
- name: http
containerPort: 8080
protocol: TCP
- name: management
containerPort: 9000
protocol: TCP
startupProbe:
httpGet:
path: /health/ready
port: management
failureThreshold: 30
periodSeconds: 10
livenessProbe:
httpGet:
path: /
port: http
initialDelaySeconds: 120
path: /health/live
port: management
periodSeconds: 15
timeoutSeconds: 5
failureThreshold: 5
readinessProbe:
httpGet:
path: /realms/master
port: http
initialDelaySeconds: 60
timeoutSeconds: 1
path: /health/ready
port: management
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
terminationGracePeriodSeconds: 60

View file

@ -1,4 +1,4 @@
portSecurity: true
routes: ""
image: ghcr.io/cozystack/cozystack/kubeovn-plunger:v1.1.0@sha256:b91bf0964a3204e50f703092f190b7d96c078a6ccee430215042ae1275ed5127
image: ghcr.io/cozystack/cozystack/kubeovn-plunger:v1.1.7@sha256:3c134647f27ec8c0fdd6f3a0780dcc999300f35ad8d6f3a4d3cf221bb5bbd39d
ovnCentralName: ovn-central

View file

@ -1,3 +1,3 @@
portSecurity: true
routes: ""
image: ghcr.io/cozystack/cozystack/kubeovn-webhook:v1.1.0@sha256:e18f9fd679e38f65362a8d0042f25468272f6d081136ad47027168d8e7e07a4a
image: ghcr.io/cozystack/cozystack/kubeovn-webhook:v1.1.7@sha256:e18f9fd679e38f65362a8d0042f25468272f6d081136ad47027168d8e7e07a4a

View file

@ -1,3 +1,3 @@
storageClass: replicated
csiDriver:
image: ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.0.0@sha256:faaa6bcdb68196edb4baafe643679bd7d2ef35f910c639b71e06a4ecc034f232
image: ghcr.io/cozystack/cozystack/kubevirt-csi-driver:0.0.0@sha256:c40b352c18e4a7d9b3a40d6c29c32bbf6dd9bd8fdca35185b194187e797aeeb6

View file

@ -1,5 +1,5 @@
lineageControllerWebhook:
image: ghcr.io/cozystack/cozystack/lineage-controller-webhook:v1.1.0@sha256:4d6a2bb76cae84e24cd48c7377b03ed6bdfefe611221d2c0a7f77a5457db8849
image: ghcr.io/cozystack/cozystack/lineage-controller-webhook:v1.1.7@sha256:bc03f10192158e4e7df3e3b3dfcad646b422245c6d31c6f25637cb6bf2db6e27
debug: false
localK8sAPIEndpoint:
enabled: true

View file

@ -61,6 +61,8 @@ RUN test -d .gradlehome && echo ".gradlehome found in tarball" || (echo ".gradle
# Build DEB packages from tarball
# Override GRADLE_FLAGS to remove --offline flag, allowing Gradle to download missing dependencies
RUN sed -i 's/GRADLE_FLAGS = --offline/GRADLE_FLAGS =/' debian/rules || true
# Skip dh_strip_nondeterminism to avoid failures on some JAR files (logback-core)
RUN printf '\noverride_dh_strip_nondeterminism:\n\ttrue\n' >> debian/rules
RUN LD_LIBRARY_PATH='' dpkg-buildpackage -rfakeroot -b -uc
# Copy built .deb packages to a location accessible from final image

View file

@ -8,7 +8,7 @@ Custom patches for piraeus-server (linstor-server) v1.32.3.
- Upstream: [#475](https://github.com/LINBIT/linstor-server/pull/475)
- **force-metadata-check-on-disk-add.diff** — Create metadata during toggle-disk from diskless to diskful
- Upstream: [#474](https://github.com/LINBIT/linstor-server/pull/474)
- **fix-duplicate-tcp-ports.diff** — Prevent duplicate TCP ports after toggle-disk operations
- Upstream: [#476](https://github.com/LINBIT/linstor-server/pull/476)
- **fix-duplicate-tcp-ports.diff** — Preserve TCP ports during toggle-disk to prevent port mismatch between controller and satellites
- Upstream: [#476](https://github.com/LINBIT/linstor-server/pull/476) (superseded by this expanded fix)
- **skip-adjust-when-device-inaccessible.diff** — Fix resources stuck in StandAlone after reboot, Unknown state race condition, and encrypted resource deletion
- Upstream: [#477](https://github.com/LINBIT/linstor-server/pull/477)

View file

@ -1,80 +1,131 @@
From 1250abe99d64a0501795e37d3b6af62410002239 Mon Sep 17 00:00:00 2001
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Andrei Kvapil <kvapss@gmail.com>
Date: Mon, 12 Jan 2026 13:44:46 +0100
Subject: [PATCH] fix(drbd): prevent duplicate TCP ports after toggle-disk
operations
Date: Fri, 28 Mar 2026 13:00:00 +0100
Subject: [PATCH] fix(drbd): preserve TCP ports during toggle-disk operations
Remove redundant ensureStackDataExists() call with empty payload from
resetStoragePools() method that was causing TCP port conflicts after
toggle-disk operations.
Prevent TCP port mismatches after toggle-disk operations by preserving
existing TCP ports when rebuilding DrbdRscData.
Root Cause:
-----------
The resetStoragePools() method, introduced in 2019 (commit 95cc17d0b8),
calls ensureStackDataExists() with an empty LayerPayload. This worked
correctly when TCP ports were stored at RscDfn level.
During toggle-disk operations, removeLayerData() deletes DrbdRscData
(freeing its TCP ports from the number pool), then ensureStackDataExists()
creates new DrbdRscData. Since the payload has no explicit tcpPorts,
the controller allocates new ports from the pool -- which may differ from
the old ports if other resources claimed them in the meantime.
After the TCP port migration to per-node level (commit f754943463, May
2025), this empty payload results in DrbdRscData being created without
TCP ports assigned. The controller then sends a Pojo with an empty port
Set to satellites.
The controller correctly avoids collisions in its own number pool, but
the satellite may miss the update (e.g. during controller restart or
network issues). When this happens, the satellite keeps the old ports
while peers receive the new ones, causing DRBD connection failures
(StandAlone/Connecting state).
On satellites, when DrbdRscData is initialized with an empty port list,
initPorts() uses preferredNewPortsRef from peer resources. Since
SatelliteDynamicNumberPool.tryAllocate() always returns true (no-op),
any port from preferredNewPortsRef is accepted without conflict checking,
leading to duplicate TCP port assignments.
Impact:
-------
This regression affects toggle-disk operations, particularly:
- Snapshot creation/restore operations
- Manual toggle-disk operations
- Any operation calling resetStoragePools()
Symptoms include:
- DRBD resources failing to adjust with "port is also used" errors
- Resources stuck in StandAlone or Connecting states
- Multiple resources on the same node using identical TCP ports
Additionally, remove the redundant ensureStackDataExists() call from
resetStoragePools() -- the caller already invokes it with the correct
payload.
Solution:
---------
Remove the ensureStackDataExists() call from resetStoragePools() as it
is redundant. The calling code (e.g., CtrlRscToggleDiskApiCallHandler
line 1071) already invokes ensureStackDataExists() with the correct
payload immediately after resetStoragePools().
1. Add copyDrbdTcpPortsIfExists() to save existing TCP ports into the
LayerPayload before removeLayerData() deletes them.
2. Call it from copyDrbdNodeIdIfExists() (covers both toggle-disk paths)
and from the needsDeactivate path (shared storage pool case).
3. Remove the redundant ensureStackDataExists() from resetStoragePools().
This fix ensures:
1. resetStoragePools() only resets storage pool assignments
2. Layer data creation with proper TCP ports happens via the caller's
ensureStackDataExists() with correct payload
3. No DrbdRscData objects are created without TCP port assignments
Related Issues:
---------------
Fixes #454 - Duplicate TCP ports after backup/restore operations
Related to user reports of resources stuck in StandAlone after node
reboots when toggle-disk or backup operations were in progress.
Testing:
--------
Verified that:
- Toggle-disk operations no longer create resources without TCP ports
- Backup/restore operations complete without TCP port conflicts
- Resources maintain unique TCP ports across toggle-disk cycles
This ensures the same TCP ports are reused when DrbdRscData is recreated,
eliminating the window for port mismatch between controller and satellites.
Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
---
.../linbit/linstor/layer/resource/CtrlRscLayerDataFactory.java | 2 --
1 file changed, 2 deletions(-)
.../controller/CtrlRscToggleDiskApiCallHandler.java | 40 +++++++++++++++++++--
.../linstor/layer/resource/CtrlRscLayerDataFactory.java | 2 --
2 files changed, 38 insertions(+), 4 deletions(-)
diff --git a/controller/src/main/java/com/linbit/linstor/core/apicallhandler/controller/CtrlRscToggleDiskApiCallHandler.java b/controller/src/main/java/com/linbit/linstor/core/apicallhandler/controller/CtrlRscToggleDiskApiCallHandler.java
index ccdb0cee5..b0554c2ec 100644
--- a/controller/src/main/java/com/linbit/linstor/core/apicallhandler/controller/CtrlRscToggleDiskApiCallHandler.java
+++ b/controller/src/main/java/com/linbit/linstor/core/apicallhandler/controller/CtrlRscToggleDiskApiCallHandler.java
@@ -58,6 +58,7 @@ import com.linbit.linstor.stateflags.StateFlags;
import com.linbit.linstor.storage.StorageException;
import com.linbit.linstor.storage.data.adapter.drbd.DrbdRscData;
import com.linbit.linstor.storage.interfaces.categories.resource.AbsRscLayerObject;
+import com.linbit.linstor.core.types.TcpPortNumber;
import com.linbit.linstor.storage.interfaces.categories.resource.VlmProviderObject;
import com.linbit.linstor.storage.kinds.DeviceLayerKind;
import com.linbit.linstor.storage.kinds.DeviceProviderKind;
@@ -88,6 +89,7 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
+import java.util.TreeSet;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
@@ -587,8 +589,9 @@ public class CtrlRscToggleDiskApiCallHandler implements CtrlSatelliteConnectionL
/*
* We also have to remove the currently diskless DrbdRscData and free up the node-id as now we must
- * use the shared resource's node-id
+ * use the shared resource's node-id. We still need to preserve TCP ports though.
*/
+ copyDrbdTcpPortsIfExists(rsc, payload);
}
else
{
@@ -726,7 +729,7 @@ public class CtrlRscToggleDiskApiCallHandler implements CtrlSatelliteConnectionL
/**
* Although we need to rebuild the layerData as the layerList might have changed, if we do not
* deactivate (i.e. down) the current resource, we need to make sure that deleting DrbdRscData
- * and recreating a new DrbdRscData ends up with the same node-id as before.
+ * and recreating a new DrbdRscData ends up with the same node-id and TCP ports as before.
*/
private void copyDrbdNodeIdIfExists(Resource rsc, LayerPayload payload) throws ImplementationError
{
@@ -743,6 +746,37 @@ public class CtrlRscToggleDiskApiCallHandler implements CtrlSatelliteConnectionL
DrbdRscData<Resource> drbdRscData = (DrbdRscData<Resource>) drbdRscDataSet.iterator().next();
payload.drbdRsc.nodeId = drbdRscData.getNodeId().value;
}
+ copyDrbdTcpPortsIfExists(rsc, payload);
+ }
+
+ /**
+ * Preserves existing TCP ports during toggle-disk operations.
+ *
+ * When removeLayerData() deletes DrbdRscData, the TCP ports are freed from the number pool.
+ * If ensureStackDataExists() then allocates different ports, and the satellite misses the update
+ * (e.g. due to controller restart or connectivity issues), the satellite keeps the old ports
+ * while peers get the new ones, causing DRBD connections to fail with StandAlone state.
+ */
+ private void copyDrbdTcpPortsIfExists(Resource rsc, LayerPayload payload) throws ImplementationError
+ {
+ Set<AbsRscLayerObject<Resource>> drbdRscDataSet = LayerRscUtils.getRscDataByLayer(
+ getLayerData(apiCtx, rsc),
+ DeviceLayerKind.DRBD
+ );
+ if (!drbdRscDataSet.isEmpty())
+ {
+ DrbdRscData<Resource> drbdRscData = (DrbdRscData<Resource>) drbdRscDataSet.iterator().next();
+ Collection<TcpPortNumber> tcpPorts = drbdRscData.getTcpPortList();
+ if (tcpPorts != null && !tcpPorts.isEmpty())
+ {
+ Set<Integer> portInts = new TreeSet<>();
+ for (TcpPortNumber port : tcpPorts)
+ {
+ portInts.add(port.value);
+ }
+ payload.drbdRsc.tcpPorts = portInts;
+ }
+ }
}
private List<DeviceLayerKind> removeLayerData(Resource rscRef)
diff --git a/controller/src/main/java/com/linbit/linstor/layer/resource/CtrlRscLayerDataFactory.java b/controller/src/main/java/com/linbit/linstor/layer/resource/CtrlRscLayerDataFactory.java
index 3538b380c..4f589145e 100644
--- a/controller/src/main/java/com/linbit/linstor/layer/resource/CtrlRscLayerDataFactory.java
+++ b/controller/src/main/java/com/linbit/linstor/layer/resource/CtrlRscLayerDataFactory.java
@@ -276,8 +276,6 @@ public class CtrlRscLayerDataFactory
rscDataToProcess.addAll(rscData.getChildren());
}
-
@ -82,6 +133,6 @@ index 3538b380c..4f589145e 100644
}
catch (AccessDeniedException exc)
{
--
--
2.39.5 (Apple Git-154)

View file

@ -11,7 +11,7 @@ spec:
relabelConfigs:
- action: labeldrop
regex: (endpoint|namespace|pod|container)
- replacement: linstor-controller
- replacement: linstor-satellite
targetLabel: job
- sourceLabels: [__meta_kubernetes_pod_node_name]
targetLabel: node
@ -34,7 +34,7 @@ spec:
relabelConfigs:
- action: labeldrop
regex: (endpoint|namespace|pod|container)
- replacement: linstor-satellite
- replacement: linstor-controller
targetLabel: job
- sourceLabels: [__meta_kubernetes_pod_node_name]
targetLabel: controller_node

View file

@ -1,7 +1,7 @@
piraeusServer:
image:
repository: ghcr.io/cozystack/cozystack/piraeus-server
tag: 1.32.3@sha256:aa97f39d90c0726b587f0a376504f13d1f308adeb42db7d98cec9ac7de237361
tag: 1.32.3@sha256:6d2b12f4bb0641f997b96b93f9c9d29efeb790fb1f179e2856b85ab992235bf8
# Talos-specific workarounds (disable for generic Linux like Ubuntu/Debian)
talos:
enabled: true
@ -13,4 +13,4 @@ linstor:
linstorCSI:
image:
repository: ghcr.io/cozystack/cozystack/linstor-csi
tag: v1.10.5@sha256:50ab1ab0210d4e7ebfca311f445bb764516db5ddb63fc6d28536b28622eee753
tag: v1.10.5@sha256:651d67f0f2123c4ef5cb15c6be9f9c3c759549d3304467e492a01588cf8bd50e

View file

@ -173,7 +173,7 @@ metadata:
app: alerta
annotations:
{{- if eq $solver "http01" }}
acme.cert-manager.io/http01-ingress-class: {{ $ingress }}
acme.cert-manager.io/http01-ingress-ingressclassname: {{ $ingress }}
{{- end }}
cert-manager.io/cluster-issuer: {{ $clusterIssuer }}
spec:

View file

@ -74,7 +74,7 @@ spec:
metadata:
annotations:
{{- if eq $solver "http01" }}
acme.cert-manager.io/http01-ingress-class: "{{ $ingress }}"
acme.cert-manager.io/http01-ingress-ingressclassname: "{{ $ingress }}"
{{- end }}
cert-manager.io/cluster-issuer: {{ $clusterIssuer }}
spec:

View file

@ -1,3 +1,3 @@
objectstorage:
controller:
image: "ghcr.io/cozystack/cozystack/objectstorage-controller:v1.1.0@sha256:e40e94f3014cfd04cce4230597315a1acfcca2daa8051b987614d0c05da6d928"
image: "ghcr.io/cozystack/cozystack/objectstorage-controller:v1.1.7@sha256:080b89a5bbee971180f9e2ad16672795d990751f7953ebfd83042a32acb57a35"

View file

@ -7,19 +7,31 @@ spec:
groups:
- name: linstor.rules
rules:
- alert: linstorControllerOffline
- alert: linstorControllerUnavailable
annotations:
description: |
LINSTOR Controller is not reachable.
expr: up{job="linstor-controller"} == 0
LINSTOR Controller deployment has no available replicas.
expr: kube_deployment_status_replicas_available{namespace="cozy-linstor",deployment="linstor-controller"} < 1
for: 3m
labels:
severity: critical
- alert: linstorControllerMetricsScrapeFailing
annotations:
description: |
LINSTOR Controller metrics endpoint is not being scraped successfully.
expr: up{job="linstor-controller"} == 0
for: 10m
labels:
severity: warning
- alert: linstorSatelliteErrorRate
annotations:
description: |
LINSTOR Satellite "{{ $labels.name }}" reports {{ $value }} errors in the last 15 minutes.
Use "linstor error-reports list --nodes {{ $labels.name }} --since 15minutes" to see them.
expr: increase(linstor_error_reports_count{module="SATELLITE"}[15m]) > 0
LINSTOR Satellite "{{ $labels.hostname }}" reports {{ $value }} errors in the last 15 minutes.
Use "linstor error-reports list --nodes {{ $labels.hostname }}" to inspect the reports.
expr: |
increase(linstor_error_reports_count{module="SATELLITE"}[15m]) > 0
and on(instance, job)
min_over_time(up{job="linstor-controller"}[15m]) == 1
labels:
severity: warning
- alert: linstorControllerErrorRate
@ -33,7 +45,7 @@ spec:
- alert: linstorSatelliteNotOnline
annotations:
description: |
LINSTOR Satellite "{{ $labels.name }}" is not ONLINE.
LINSTOR Satellite "{{ $labels.hostname }}" is not ONLINE.
Check that the Satellite is running and reachable from the LINSTOR Controller.
expr: linstor_node_state{nodetype="SATELLITE"} != 2
labels:

View file

@ -111,7 +111,7 @@ seaweedfs:
nginx.ingress.kubernetes.io/client-body-timeout: "3600"
nginx.ingress.kubernetes.io/client-header-timeout: "120"
nginx.ingress.kubernetes.io/service-upstream: "true"
acme.cert-manager.io/http01-ingress-class: tenant-root
acme.cert-manager.io/http01-ingress-ingressclassname: tenant-root
cert-manager.io/cluster-issuer: letsencrypt-prod
tls:
- hosts:
@ -177,7 +177,7 @@ seaweedfs:
bucketClassName: "seaweedfs"
region: ""
sidecar:
image: "ghcr.io/cozystack/cozystack/objectstorage-sidecar:v1.1.0@sha256:2a3595cd88b30af55b2000d3ca204899beecef0012b0e0402754c3914aad1f7f"
image: "ghcr.io/cozystack/cozystack/objectstorage-sidecar:v1.1.7@sha256:fd3571e746efbc65fab342ca557beb2d0ad46fc0183772605fe84c6d3dd446a4"
certificates:
commonName: "SeaweedFS CA"
ipAddresses: []

View file

@ -224,8 +224,8 @@ func buildPostProcessV3(kindSchemas map[string]string) func(*spec3.OpenAPI) (*sp
base, ok1 := doc.Components.Schemas[baseRef]
list, ok2 := doc.Components.Schemas[baseListRef]
stat, ok3 := doc.Components.Schemas[baseStatusRef]
if !(ok1 && ok2 && ok3) && len(kindSchemas) > 0 {
return doc, fmt.Errorf("base Application* schemas not found")
if !(ok1 && ok2 && ok3) {
return doc, nil // not the apps GV — nothing to patch
}
// Clone base schemas for each kind
@ -339,8 +339,8 @@ func buildPostProcessV2(kindSchemas map[string]string) func(*spec.Swagger) (*spe
base, ok1 := defs[baseRef]
list, ok2 := defs[baseListRef]
stat, ok3 := defs[baseStatusRef]
if !(ok1 && ok2 && ok3) && len(kindSchemas) > 0 {
return sw, fmt.Errorf("base Application* schemas not found")
if !(ok1 && ok2 && ok3) {
return sw, nil // not the apps GV — nothing to patch
}
for kind, raw := range kindSchemas {