Compare commits

..
Author SHA1 Message Date
1d10a30496 chore(deps): update dependency _1password to v2 (#42)
AI Code Review / ai-review (pull_request) Has been skipped
/ test (pull_request) Successful in 34s
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [_1password](https://developer.1password.com/docs/cli/) | major | `1.12.2` → `2.30.0` |

---

📅 **Schedule**: (in timezone Europe/Oslo)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate CLI](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xMTIuMyIsInVwZGF0ZWRJblZlciI6IjQ0LjExMi4zIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJyZW5vdmF0ZSJdfQ==-->

---------

Co-authored-by: Renovate Bot <renovate@forteapps.net>
Reviewed-on: #42
Reviewed-by: Danijel Simeunovic <danijel.simeunovic@fortedigital.com>
Co-authored-by: gitea_admin <admin@forteapps.net>
Co-committed-by: gitea_admin <admin@forteapps.net>
2026-09-29 00:04:44 +02:00
Renovate Bot 6a17243115 chore(deps): update all non-major dependencies
renovate/stability-days Updates have met minimum release age requirement
/ test (pull_request) Successful in 1m22s
2026-09-28 00:07:57 +00:00
52068dc533 chore(deps): update dependency grype to v0.118.0 (#34)
/ test (push) Failing after 14m26s
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [grype](https://github.com/anchore/grype) | minor | `0.92.2` → `0.118.0` |

---

### Release Notes

<details>
<summary>anchore/grype (grype)</summary>

### [`v0.118.0`](https://github.com/anchore/grype/releases/tag/v0.118.0)

##### Added Features

- apk matcher does alias aware aggregation \[PR [#&#8203;3634](https://github.com/anchore/grype/pull/3634) [@&#8203;crosleyzack](https://github.com/crosleyzack)]

##### Bug Fixes

- prevent panic on portage versions without digits \[PR [#&#8203;3655](https://github.com/anchore/grype/pull/3655) [@&#8203;ashvinctrl](https://github.com/ashvinctrl)]
- grype vex does not match oci purl with repository\_url \[Issue [#&#8203;3657](https://github.com/anchore/grype/issues/3657)] \[PR [#&#8203;3659](https://github.com/anchore/grype/pull/3659) [@&#8203;spiffcs](https://github.com/spiffcs)]
- Old JVM version comparisons sometimes incorrect \[Issue [#&#8203;2701](https://github.com/anchore/grype/issues/2701)] \[PR [#&#8203;3583](https://github.com/anchore/grype/pull/3583) [@&#8203;Eljees](https://github.com/Eljees)]
- CVSSv4 calculation can produce incorrect vulnerability severity \[Issue [#&#8203;3656](https://github.com/anchore/grype/issues/3656)]
- Grype 0.90.0 DB update failed \[Issue [#&#8203;3629](https://github.com/anchore/grype/issues/3629)]

##### Dependencies

72 dependency changes (70 updated, 1 added, 1 removed). 3 vulnerabilities remediated.

**🟢 Remediated (3)**

- [GO-2026-5158](https://github.com/open-telemetry/opentelemetry-go/security/advisories/GHSA-5wrp-cwcj-q835) (Medium) — go.opentelemetry.io/otel
- [GO-2026-6179](https://go.dev/issue/80744) (High) — golang.org/x/mod
- [GO-2026-6180](https://go.dev/issue/80745) (High) — golang.org/x/mod

<details>
<summary>Updated (70 packages)</summary>

- cel.dev/expr `v0.25.1` → `v0.25.2`
- cloud.google.com/go/auth `v0.18.2` → `v0.22.0`
- cloud.google.com/go/iam `v1.5.3` → `v1.11.0`
- cloud.google.com/go/logging `v1.13.1` → `v1.18.0`
- cloud.google.com/go/longrunning `v0.8.0` → `v1.2.0`
- cloud.google.com/go/monitoring `v1.24.3` → `v1.29.0`
- cloud.google.com/go/storage `v1.61.3` → `v1.64.0`
- cloud.google.com/go/trace `v1.11.7` → `v1.16.0`
- github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp `v1.32.0` → `v1.33.0`
- github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric `v0.55.0` → `v0.57.0`
- github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock `v0.55.0` → `v0.57.0`
- github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping `v0.55.0` → `v0.57.0`
- github.com/anchore/stereoscope `v0.3.0` → `v0.3.1`
- github.com/anchore/syft `v1.51.0` → `v1.51.1`
- github.com/aws/aws-sdk-go-v2 `v1.41.5` → `v1.43.4`
- github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream `v1.7.8` → `v1.7.16`
- github.com/aws/aws-sdk-go-v2/config `v1.32.12` → `v1.32.35`
- github.com/aws/aws-sdk-go-v2/credentials `v1.19.12` → `v1.19.34`
- github.com/aws/aws-sdk-go-v2/feature/ec2/imds `v1.18.20` → `v1.18.35`
- github.com/aws/aws-sdk-go-v2/internal/configsources `v1.4.21` → `v1.4.35`
- github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 `v2.7.21` → `v2.7.35`
- github.com/aws/aws-sdk-go-v2/internal/v4a `v1.4.22` → `v1.4.36`
- github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding `v1.13.7` → `v1.13.15`
- github.com/aws/aws-sdk-go-v2/service/internal/checksum `v1.9.13` → `v1.9.28`
- github.com/aws/aws-sdk-go-v2/service/internal/presigned-url `v1.13.21` → `v1.13.35`
- github.com/aws/aws-sdk-go-v2/service/internal/s3shared `v1.19.21` → `v1.19.36`
- github.com/aws/aws-sdk-go-v2/service/s3 `v1.97.3` → `v1.106.5`
- github.com/aws/aws-sdk-go-v2/service/signin `v1.0.8` → `v1.5.4`
- github.com/aws/aws-sdk-go-v2/service/sso `v1.30.13` → `v1.33.4`
- github.com/aws/aws-sdk-go-v2/service/ssooidc `v1.35.17` → `v1.38.4`
- github.com/aws/aws-sdk-go-v2/service/sts `v1.41.9` → `v1.45.4`
- github.com/aws/smithy-go `v1.24.2` → `v1.27.6`
- github.com/containerd/containerd/v2 `v2.3.3` → `v2.3.4`
- github.com/containerd/platforms `v1.0.0-rc.4` → `v1.0.0-rc.5`
- github.com/docker/cli `v29.6.1+incompatible` → `v29.7.2+incompatible`
- github.com/docker/go-connections `v0.7.0` → `v0.8.1`
- github.com/fatih/color `v1.18.0` → `v1.19.0`
- github.com/google/go-containerregistry `v0.21.7` → `v0.21.9`
- github.com/google/pprof `v0.0.0-6e76a2b` → `v0.0.0-ef3492d`
- github.com/googleapis/enterprise-certificate-proxy `v0.3.14` → `v0.3.19`
- github.com/googleapis/gax-go/v2 `v2.17.0` → `v2.23.0`
- github.com/hashicorp/aws-sdk-go-base/v2 `v2.0.0-beta.72` → `v2.0.0-beta.74`
- github.com/hashicorp/go-getter `v1.8.6` → `v1.8.8`
- github.com/hashicorp/go-version `v1.8.0` → `v1.9.0`
- github.com/klauspost/compress `v1.19.1` → `v1.19.2`
- github.com/mattn/go-isatty `v0.0.20` → `v0.0.24`
- github.com/moby/moby/client `v0.5.0` → `v0.5.1`
- github.com/spiffe/go-spiffe/v2 `v2.6.0` → `v2.7.0`
- github.com/stretchr/objx `v0.5.2` → `v0.5.3`
- github.com/stretchr/testify `v1.11.1` → `v1.12.1`
- go.opentelemetry.io/contrib/detectors/gcp `v1.43.0` → `v1.44.0`
- go.opentelemetry.io/otel `v1.43.0` → `v1.44.0` **(🟢 remediated [GO-2026-5158](https://github.com/open-telemetry/opentelemetry-go/security/advisories/GHSA-5wrp-cwcj-q835))**
- go.opentelemetry.io/otel/exporters/stdout/stdoutmetric `v1.40.0` → `v1.44.0`
- go.opentelemetry.io/otel/metric `v1.43.0` → `v1.44.0`
- go.opentelemetry.io/otel/sdk `v1.43.0` → `v1.44.0`
- go.opentelemetry.io/otel/sdk/metric `v1.43.0` → `v1.44.0`
- go.opentelemetry.io/otel/trace `v1.43.0` → `v1.44.0`
- golang.org/x/crypto `v0.54.0` → `v0.55.0`
- golang.org/x/mod `v0.38.0` → `v0.40.0` **(🟢 remediated [GO-2026-6179](https://go.dev/issue/80744), [GO-2026-6180](https://go.dev/issue/80745))**
- golang.org/x/net `v0.57.0` → `v0.58.0`
- golang.org/x/text `v0.40.0` → `v0.41.0`
- golang.org/x/tools `v0.48.0` → `v0.49.0`
- google.golang.org/api `v0.271.0` → `v0.292.0`
- google.golang.org/genproto `v0.0.0-8636f87` → `v0.0.0-aa98bba`
- google.golang.org/genproto/googleapis/api `v0.0.0-afd174a` → `v0.0.0-925bb5d`
- google.golang.org/genproto/googleapis/rpc `v0.0.0-afd174a` → `v0.0.0-6ac0973`
- google.golang.org/grpc `v1.82.1` → `v1.83.0`
- modernc.org/cc/v4 `v4.29.0` → `v4.29.1`
- modernc.org/libc `v1.74.1` → `v1.74.4`
- modernc.org/sqlite `v1.55.0` → `v1.56.0`

</details>

<details>
<summary>Added (1 package)</summary>

- go.opentelemetry.io/otel/metric/x `v0.66.0`

</details>

<details>
<summary>Removed (1 package)</summary>

- github.com/aws/aws-sdk-go-v2/internal/ini `v1.8.6`

</details>

**[(Full Changelog)](https://github.com/anchore/grype/compare/v0.117.0...v0.118.0)**

### [`v0.117.0`](https://github.com/anchore/grype/releases/tag/v0.117.0)

##### Added Features

- Include vulnerable ranges in CycloneDX output format \[Issue [#&#8203;3512](https://github.com/anchore/grype/issues/3512)] \[PR [#&#8203;3519](https://github.com/anchore/grype/pull/3519) [@&#8203;somaz94](https://github.com/somaz94)]

##### Bug Fixes

- honor match.rust.using-cpes configuration \[PR [#&#8203;3611](https://github.com/anchore/grype/pull/3611) [@&#8203;Dashtid](https://github.com/Dashtid)]

##### Dependencies

11 dependency changes (11 updated). 2 vulnerabilities remediated.

**🟢 Remediated (2)**

- [GHSA-hc8v-wwc9-vgxm](https://github.com/advisories/GHSA-hc8v-wwc9-vgxm) (High) — github.com/go-git/go-git/v5
- [GHSA-qgq7-7hm3-q39j](https://github.com/advisories/GHSA-qgq7-7hm3-q39j) (Medium) — github.com/go-git/go-git/v5

<details>
<summary>Updated (11 packages)</summary>

- github.com/anchore/syft `v1.50.0` → `v1.51.0`
- github.com/diskfs/go-diskfs `v1.9.3` → `v1.9.4`
- github.com/gabriel-vasile/mimetype `v1.4.13` → `v1.4.15`
- github.com/go-git/go-billy/v5 `v5.9.0` → `v5.9.1`
- github.com/go-git/go-git/v5 `v5.19.1` → `v5.19.2` **(🟢 remediated [GHSA-hc8v-wwc9-vgxm](https://github.com/advisories/GHSA-hc8v-wwc9-vgxm), [GHSA-qgq7-7hm3-q39j](https://github.com/advisories/GHSA-qgq7-7hm3-q39j))**
- github.com/klauspost/compress `v1.19.0` → `v1.19.1`
- github.com/magiconair/properties `v1.8.10` → `v1.18.11`
- github.com/santhosh-tekuri/jsonschema/v6 `v6.0.2` → `v6.0.3`
- github.com/ulikunitz/xz `v0.5.15` → `v0.5.16`
- go.yaml.in/yaml/v3 `v3.0.4` → `v3.0.5`
- modernc.org/sqlite `v1.54.0` → `v1.55.0`

</details>

**[(Full Changelog)](https://github.com/anchore/grype/compare/v0.116.1...v0.117.0)**

### [`v0.116.1`](https://github.com/anchore/grype/releases/tag/v0.116.1)

##### Bug Fixes

- Ensure channel parsing is consistent \[PR [#&#8203;3603](https://github.com/anchore/grype/pull/3603) [@&#8203;wagoodman](https://github.com/wagoodman)]
- Scope Go GHSA twins by shared CVE \[PR [#&#8203;3592](https://github.com/anchore/grype/pull/3592) [@&#8203;wagoodman](https://github.com/wagoodman)]
- do not cache a comparator that failed to build \[PR [#&#8203;3567](https://github.com/anchore/grype/pull/3567) [@&#8203;arpitjain099](https://github.com/arpitjain099)]
- Add fix date to rhel minor records created from rhsa \[PR [#&#8203;3585](https://github.com/anchore/grype/pull/3585) [@&#8203;wagoodman](https://github.com/wagoodman)]
- grype reporting CVE-64091 as critical - redhat says it is not affected \[Issue [#&#8203;3591](https://github.com/anchore/grype/issues/3591)]
- panic: index out of range in distro.parseVersion for VERSION\_ID=v \[Issue [#&#8203;3588](https://github.com/anchore/grype/issues/3588)] \[PR [#&#8203;3589](https://github.com/anchore/grype/pull/3589) [@&#8203;matiasinsaurralde](https://github.com/matiasinsaurralde)]
- False Positive: GO-2026-5932 \[Issue [#&#8203;3573](https://github.com/anchore/grype/issues/3573)]

##### Dependencies

30 dependency changes (30 updated). 1 vulnerability remediated.

**🟢 Remediated (1)**

- [GHSA-hrxh-6v49-42gf](https://github.com/advisories/GHSA-hrxh-6v49-42gf) (High) — google.golang.org/grpc

<details>
<summary>Updated (30 packages)</summary>

- github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp `v1.31.0` → `v1.32.0`
- github.com/anchore/stereoscope `v0.2.2` → `v0.3.0`
- github.com/anchore/syft `v1.48.0` → `v1.50.0`
- github.com/cncf/xds/go `v0.0.0-ee656c7` → `v0.0.0-dba9d58`
- github.com/containerd/containerd/v2 `v2.3.2` → `v2.3.3`
- github.com/docker/cli `v29.5.3+incompatible` → `v29.6.1+incompatible`
- github.com/envoyproxy/go-control-plane/envoy `v1.36.0` → `v1.37.0`
- github.com/envoyproxy/protoc-gen-validate `v1.3.0` → `v1.3.3`
- github.com/gkampitakis/go-snaps `v0.5.22` → `v0.5.23`
- github.com/gpustack/gguf-parser-go `v0.24.1` → `v0.25.0`
- github.com/moby/moby/api `v1.54.2` → `v1.55.0`
- github.com/moby/moby/client `v0.4.1` → `v0.5.0`
- github.com/pelletier/go-toml/v2 `v2.3.1` → `v2.4.3`
- go.opentelemetry.io/contrib/detectors/gcp `v1.39.0` → `v1.43.0`
- golang.org/x/crypto `v0.53.0` → `v0.54.0`
- golang.org/x/mod `v0.37.0` → `v0.38.0`
- golang.org/x/net `v0.56.0` → `v0.57.0`
- golang.org/x/sync `v0.21.0` → `v0.22.0`
- golang.org/x/sys `v0.46.0` → `v0.47.0`
- golang.org/x/term `v0.44.0` → `v0.45.0`
- golang.org/x/text `v0.39.0` → `v0.40.0`
- golang.org/x/tools `v0.47.0` → `v0.48.0`
- google.golang.org/genproto/googleapis/api `v0.0.0-9d38bb4` → `v0.0.0-afd174a`
- google.golang.org/genproto/googleapis/rpc `v0.0.0-6f92a3b` → `v0.0.0-afd174a`
- google.golang.org/grpc `v1.80.0` → `v1.82.1` **(🟢 remediated [GHSA-hrxh-6v49-42gf](https://github.com/advisories/GHSA-hrxh-6v49-42gf))**
- modernc.org/cc/v4 `v4.28.4` → `v4.29.0`
- modernc.org/ccgo/v4 `v4.34.4` → `v4.34.6`
- modernc.org/gc/v3 `v3.1.3` → `v3.1.4`
- modernc.org/libc `v1.73.4` → `v1.74.1`
- modernc.org/sqlite `v1.53.0` → `v1.54.0`

</details>

**[(Full Changelog)](https://github.com/anchore/grype/compare/v0.116.0...v0.116.1)**

### [`v0.115.0`](https://github.com/anchore/grype/releases/tag/v0.115.0)

##### Added Features

- emit golang.org/x/net vulns from govlundb \[PR [#&#8203;3534](https://github.com/anchore/grype/pull/3534) [@&#8203;willmurphyscode](https://github.com/willmurphyscode)]
- Merge Go vuln matches with GHSA matches \[Issue [#&#8203;3515](https://github.com/anchore/grype/issues/3515)]

##### Bug Fixes

- only emit records for stdlib \[PR [#&#8203;3527](https://github.com/anchore/grype/pull/3527) [@&#8203;willmurphyscode](https://github.com/willmurphyscode)]
- mark hummingbird distro as rolling \[PR [#&#8203;3521](https://github.com/anchore/grype/pull/3521) [@&#8203;willmurphyscode](https://github.com/willmurphyscode)]
- disable go stdlib CPE matching by default \[PR [#&#8203;3517](https://github.com/anchore/grype/pull/3517) [@&#8203;willmurphyscode](https://github.com/willmurphyscode)]
- merge in custom ranges when applicable \[PR [#&#8203;3514](https://github.com/anchore/grype/pull/3514) [@&#8203;willmurphyscode](https://github.com/willmurphyscode)]
- exclude linux-kbuild deb indirect matches by default \[PR [#&#8203;3506](https://github.com/anchore/grype/pull/3506) [@&#8203;westonsteimel](https://github.com/westonsteimel)]
- avoid panic on invalid RHEL version IDs \[PR [#&#8203;3490](https://github.com/anchore/grype/pull/3490) [@&#8203;jspilman](https://github.com/jspilman)]
- Support reading CycloneDX 1.7 SBOMs \[Issue [#&#8203;3373](https://github.com/anchore/grype/issues/3373)]
- Grype cannot read mariadb version correctly \[Issue [#&#8203;3452](https://github.com/anchore/grype/issues/3452)]
- grype hangs when downloading certain images using registry client \[Issue [#&#8203;3492](https://github.com/anchore/grype/issues/3492)]
- Can we get a fix for these Critical findings reported for grype \[Issue [#&#8203;3484](https://github.com/anchore/grype/issues/3484)]

##### Additional Changes

- Security: bump golang.org/x/crypto to v0.52.0 to resolve multiple CVEs \[Issue [#&#8203;3493](https://github.com/anchore/grype/issues/3493)]
- Security: bump golang.org/x/net to v0.55.0 to resolve CVEs \[Issue [#&#8203;3494](https://github.com/anchore/grype/issues/3494)]

##### Dependencies

35 dependency changes (31 updated, 3 added, 1 removed). 5 vulnerabilities remediated.

**🟢 Remediated (5)**

- [GHSA-33vj-92qq-66hc](https://github.com/advisories/GHSA-33vj-92qq-66hc) (High) — github.com/containerd/containerd/v2
- [GHSA-cvxm-645q-p574](https://github.com/advisories/GHSA-cvxm-645q-p574) (Medium) — github.com/containerd/containerd/v2
- [GHSA-jpcc-p29g-p8mq](https://github.com/advisories/GHSA-jpcc-p29g-p8mq) (Medium) — github.com/containerd/containerd/v2
- [GHSA-rgh6-rfwx-v388](https://github.com/advisories/GHSA-rgh6-rfwx-v388) (High) — github.com/containerd/containerd/v2
- [GHSA-xhf5-7wjv-pqxp](https://github.com/advisories/GHSA-xhf5-7wjv-pqxp) (High) — github.com/containerd/containerd/v2

<details>
<summary>Updated (31 packages)</summary>

- github.com/ProtonMail/go-crypto `v1.4.0` → `v1.4.1`
- github.com/anchore/bubbly `v0.2.0` → `v0.2.1`
- github.com/anchore/clio `v0.1.0` → `v0.1.1`
- github.com/anchore/fangs `v0.1.0` → `v0.1.1`
- github.com/anchore/go-collections `v0.1.0` → `v0.1.1`
- github.com/anchore/go-homedir `v0.1.0` → `v0.1.1`
- github.com/anchore/go-logger `v0.1.0` → `v0.1.1`
- github.com/anchore/go-lzo `v0.1.0` → `v0.1.1`
- github.com/anchore/go-macholibre `v0.1.0` → `v0.1.1`
- github.com/anchore/go-make `v0.5.0` → `v0.8.0`
- github.com/anchore/go-struct-converter `v0.1.0` → `v0.2.0-rc2`
- github.com/anchore/go-sync `v0.1.0` → `v0.1.1`
- github.com/anchore/stereoscope `v0.2.1` → `v0.2.2`
- github.com/anchore/syft `v1.45.1` → `v1.46.0`
- github.com/charmbracelet/colorprofile `v0.4.1` → `v0.4.3`
- github.com/clipperhouse/displaywidth `v0.10.0` → `v0.11.0`
- github.com/clipperhouse/uax29/v2 `v2.6.0` → `v2.7.0`
- github.com/containerd/containerd/v2 `v2.3.1` → `v2.3.2` **(🟢 remediated [GHSA-33vj-92qq-66hc](https://github.com/advisories/GHSA-33vj-92qq-66hc), [GHSA-cvxm-645q-p574](https://github.com/advisories/GHSA-cvxm-645q-p574), [GHSA-jpcc-p29g-p8mq](https://github.com/advisories/GHSA-jpcc-p29g-p8mq), [GHSA-rgh6-rfwx-v388](https://github.com/advisories/GHSA-rgh6-rfwx-v388), [GHSA-xhf5-7wjv-pqxp](https://github.com/advisories/GHSA-xhf5-7wjv-pqxp))**
- github.com/docker/cli `v29.4.3+incompatible` → `v29.5.3+incompatible`
- github.com/google/go-containerregistry `v0.21.6` → `v0.21.7`
- github.com/mattn/go-runewidth `v0.0.19` → `v0.0.21`
- github.com/spdx/tools-golang `v0.5.7` → `v0.6.0-rc4`
- github.com/sylabs/sif/v2 `v2.24.0` → `v2.24.1`
- golang.org/x/crypto `v0.52.0` → `v0.53.0`
- golang.org/x/mod `v0.36.0` → `v0.37.0`
- golang.org/x/net `v0.55.0` → `v0.56.0`
- golang.org/x/sync `v0.20.0` → `v0.21.0`
- golang.org/x/sys `v0.45.0` → `v0.46.0`
- golang.org/x/term `v0.43.0` → `v0.44.0`
- golang.org/x/text `v0.37.0` → `v0.38.0`
- golang.org/x/tools `v0.45.0` → `v0.46.0`

</details>

<details>
<summary>Added (3 packages)</summary>

- github.com/piprate/json-gold `v0.7.0`
- github.com/pquerna/cachecontrol `v0.0.0-1555304`
- github.com/tailscale/hujson `v0.0.0-ecc657c`

</details>

<details>
<summary>Removed (1 package)</summary>

- github.com/google/osv-scanner `v1.9.2`

</details>

**[(Full Changelog)](https://github.com/anchore/grype/compare/v0.114.0...v0.115.0)**

### [`v0.114.0`](https://github.com/anchore/grype/releases/tag/v0.114.0)

##### Added Features

- Add ability to scan zarf packages \[[#&#8203;3329](https://github.com/anchore/grype/issues/3329) [#&#8203;3366](https://github.com/anchore/grype/pull/3366) [@&#8203;brandtkeller](https://github.com/brandtkeller)]

##### Additional Changes

- respect withdrawn status of Go Vuln DB OSV records \[[#&#8203;3495](https://github.com/anchore/grype/pull/3495) [@&#8203;willmurphyscode](https://github.com/willmurphyscode)]
- Govulndb OSV transformer \[[#&#8203;3485](https://github.com/anchore/grype/pull/3485) [@&#8203;willmurphyscode](https://github.com/willmurphyscode)]

**[(Full Changelog)](https://github.com/anchore/grype/compare/v0.113.0...v0.114.0)**

### [`v0.113.0`](https://github.com/anchore/grype/releases/tag/v0.113.0)

##### Added Features

- Include Ubuntu 26.04 "resolute" in distro codenames \[[#&#8203;3397](https://github.com/anchore/grype/pull/3397) [@&#8203;anchore-oss-update-bot](https://github.com/anchore-oss-update-bot)]
- source RPM filtering on Hummingbird \[[#&#8203;3410](https://github.com/anchore/grype/pull/3410) [@&#8203;willmurphyscode](https://github.com/willmurphyscode)]

##### Bug Fixes

- use relatedVulnerabilities description as fallback in SARIF output \[[#&#8203;3271](https://github.com/anchore/grype/pull/3271) [@&#8203;axidex](https://github.com/axidex)]
- improve platform CPE determination logic \[[#&#8203;3470](https://github.com/anchore/grype/pull/3470) [@&#8203;westonsteimel](https://github.com/westonsteimel)]
- normalize uppercase V in semantic version comparison \[[#&#8203;3461](https://github.com/anchore/grype/pull/3461) [@&#8203;immanuwell](https://github.com/immanuwell)]
- purl handling in cgr maven libs \[[#&#8203;3420](https://github.com/anchore/grype/pull/3420) [@&#8203;willmurphyscode](https://github.com/willmurphyscode)]
- Treat uppercase V prefixes the same as lowercase v prefixes in fuzzy version comparison \[[#&#8203;3037](https://github.com/anchore/grype/issues/3037) [#&#8203;3089](https://github.com/anchore/grype/pull/3089) [@&#8203;wasup-yash](https://github.com/wasup-yash)]
- Add Runtime Warnings When TLS Verification Is Disabled or HTTP Is Enabled \[[#&#8203;3101](https://github.com/anchore/grype/issues/3101) [#&#8203;3396](https://github.com/anchore/grype/pull/3396) [@&#8203;Dashtid](https://github.com/Dashtid)]
- Add support for the aarch64 architecture when parsing the version of Ruby gems in lockfiles \[[#&#8203;3442](https://github.com/anchore/grype/issues/3442) [#&#8203;3475](https://github.com/anchore/grype/pull/3475) [@&#8203;msnandhis](https://github.com/msnandhis)]
- zsh completion fails \[[#&#8203;2933](https://github.com/anchore/grype/issues/2933) [#&#8203;3433](https://github.com/anchore/grype/pull/3433) [@&#8203;brandtkeller](https://github.com/brandtkeller)]

**[(Full Changelog)](https://github.com/anchore/grype/compare/v0.112.0...v0.113.0)**

### [`v0.112.0`](https://github.com/anchore/grype/releases/tag/v0.112.0)

##### Added Features

- Expand ignore rules to owned sub packages of distro packages \[[#&#8203;3368](https://github.com/anchore/grype/issues/3368) [#&#8203;3326](https://github.com/anchore/grype/pull/3326) [@&#8203;kzantow](https://github.com/kzantow)]

##### Additional Changes

- update anchore dependencies \[[#&#8203;3391](https://github.com/anchore/grype/pull/3391) [@&#8203;anchore-oss-update-bot](https://github.com/anchore-oss-update-bot)]

**[(Full Changelog)](https://github.com/anchore/grype/compare/v0.111.1...v0.112.0)**

### [`v0.111.1`](https://github.com/anchore/grype/releases/tag/v0.111.1)

##### Bug Fixes

- apply overlap by ownership removal to dynamically created relationships \[[#&#8203;3363](https://github.com/anchore/grype/pull/3363) [@&#8203;kzantow](https://github.com/kzantow)]
- compare mismatched package / db versions \[[#&#8203;3372](https://github.com/anchore/grype/pull/3372) [@&#8203;kzantow](https://github.com/kzantow)]
- Grype doesn't recognize debian component when `"group" : "debian"` is specified \[[#&#8203;2967](https://github.com/anchore/grype/issues/2967)]
- HelpURI missing information in SARIF output \[[#&#8203;2874](https://github.com/anchore/grype/issues/2874) [#&#8203;3351](https://github.com/anchore/grype/pull/3351) [@&#8203;will-bates11](https://github.com/will-bates11)]

**[(Full Changelog)](https://github.com/anchore/grype/compare/v0.111.0...v0.111.1)**

### [`v0.108.0`](https://github.com/anchore/grype/releases/tag/v0.108.0)

##### Added Features

- enable disabling EOL warnings \[[#&#8203;3204](https://github.com/anchore/grype/pull/3204) [@&#8203;willmurphyscode](https://github.com/willmurphyscode)]

##### Bug Fixes

- fix fallback on major only distro \[[#&#8203;3213](https://github.com/anchore/grype/pull/3213) [@&#8203;willmurphyscode](https://github.com/willmurphyscode)]
- VEX Documents still not working with syft sbom \[[#&#8203;3167](https://github.com/anchore/grype/issues/3167)]
- VEX: minimal OpenVEX Example not working \[[#&#8203;3212](https://github.com/anchore/grype/issues/3212)]

##### Additional Changes

- support more accurate scanning for postmarketos \[[#&#8203;3182](https://github.com/anchore/grype/pull/3182) [@&#8203;westonsteimel](https://github.com/westonsteimel)]
- charmbracelet/bubbletea erases grype ui status line \[[#&#8203;3214](https://github.com/anchore/grype/pull/3214) [@&#8203;spiffcs](https://github.com/spiffcs)]
- bump labels and add several test images \[[#&#8203;3215](https://github.com/anchore/grype/pull/3215) [@&#8203;westonsteimel](https://github.com/westonsteimel)]
- improve VEX product and subcomponent matching \[[#&#8203;3168](https://github.com/anchore/grype/pull/3168) [@&#8203;dariozachow](https://github.com/dariozachow)]

**[(Full Changelog)](https://github.com/anchore/grype/compare/v0.107.1...v0.108.0)**

### [`v0.105.0`](https://github.com/anchore/grype/releases/tag/v0.105.0)

##### Added Features

- Add archlinux matcher to grype \[[#&#8203;3154](https://github.com/anchore/grype/pull/3154) [@&#8203;willmurphyscode](https://github.com/willmurphyscode)]

**[(Full Changelog)](https://github.com/anchore/grype/compare/v0.104.4...v0.105.0)**

### [`v0.104.4`](https://github.com/anchore/grype/releases/tag/v0.104.4)

##### Bug Fixes

- preserve local version segment in constraints for PEP 440 comparison \[[#&#8203;3146](https://github.com/anchore/grype/pull/3146) [@&#8203;willmurphyscode](https://github.com/willmurphyscode)]

##### Additional Changes

- correct help text for return code for fail-on severity option \[[#&#8203;3138](https://github.com/anchore/grype/pull/3138) [@&#8203;u-ways](https://github.com/u-ways)]

**[(Full Changelog)](https://github.com/anchore/grype/compare/v0.104.3...v0.104.4)**

### [`v0.104.3`](https://github.com/anchore/grype/releases/tag/v0.104.3)

##### Bug Fixes

- Use specifier matching rules when comparing python versions \[[#&#8203;3121](https://github.com/anchore/grype/pull/3121) [@&#8203;wagoodman](https://github.com/wagoodman)]

**[(Full Changelog)](https://github.com/anchore/grype/compare/v0.104.2...v0.104.3)**

### [`v0.104.2`](https://github.com/anchore/grype/releases/tag/v0.104.2)

##### Bug Fixes

- Since version 0.104.0 shaded jars are not reported \[[#&#8203;3098](https://github.com/anchore/grype/issues/3098)]
- db search fails with misleading message (out of memory) when no db is present \[[#&#8203;3049](https://github.com/anchore/grype/issues/3049) [#&#8203;3077](https://github.com/anchore/grype/pull/3077) [@&#8203;JvD-Ericsson](https://github.com/JvD-Ericsson)]

##### Additional Changes

- replace os.Chdir with t.Chdir in test code \[[#&#8203;3067](https://github.com/anchore/grype/pull/3067) [@&#8203;joonas](https://github.com/joonas)]

**[(Full Changelog)](https://github.com/anchore/grype/compare/v0.104.1...v0.104.2)**

### [`v0.104.1`](https://github.com/anchore/grype/releases/tag/v0.104.1)

##### Bug Fixes

- Redact during file output \[[#&#8203;3068](https://github.com/anchore/grype/pull/3068) [@&#8203;kzantow](https://github.com/kzantow)]
- Unaffected match table does not filter results if CPE matching is enabled \[[#&#8203;3056](https://github.com/anchore/grype/issues/3056) [#&#8203;3066](https://github.com/anchore/grype/pull/3066) [@&#8203;kzantow](https://github.com/kzantow)]

##### Additional Changes

- Migrate grype to use `mholt/archives` instead of anchore fork \[[#&#8203;3036](https://github.com/anchore/grype/pull/3036) [@&#8203;joonas](https://github.com/joonas)]

**[(Full Changelog)](https://github.com/anchore/grype/compare/v0.104.0...v0.104.1)**

### [`v0.104.0`](https://github.com/anchore/grype/releases/tag/v0.104.0)

##### Added Features

- Add `--from` flag \[[#&#8203;3035](https://github.com/anchore/grype/pull/3035) [@&#8203;wagoodman](https://github.com/wagoodman)]
- Let a suppression expire to prevent that one will forget to resolve a vulnerability \[[#&#8203;3031](https://github.com/anchore/grype/issues/3031)]

##### Bug Fixes

- Unnormalized fix version triggers false-positive in mssql-jdbc \[[#&#8203;3042](https://github.com/anchore/grype/issues/3042) [#&#8203;3034](https://github.com/anchore/grype/pull/3034) [@&#8203;jamestexas](https://github.com/jamestexas)]

##### Additional Changes

- junit template use CDATA block to prevent XML parse errors \[[#&#8203;3019](https://github.com/anchore/grype/pull/3019) [@&#8203;nvtkaszpir](https://github.com/nvtkaszpir)]
- Keep nested loggers labeled \[[#&#8203;3040](https://github.com/anchore/grype/pull/3040) [@&#8203;wagoodman](https://github.com/wagoodman)]

**[(Full Changelog)](https://github.com/anchore/grype/compare/v0.103.0...v0.104.0)**

### [`v0.103.0`](https://github.com/anchore/grype/releases/tag/v0.103.0)

##### Added Features

- Allow hyphen in version string \[[#&#8203;3021](https://github.com/anchore/grype/pull/3021) [@&#8203;willmurphyscode](https://github.com/willmurphyscode)]
- Respect rpmmod PURL qualifier \[[#&#8203;3020](https://github.com/anchore/grype/pull/3020) [@&#8203;willmurphyscode](https://github.com/willmurphyscode)]

**[(Full Changelog)](https://github.com/anchore/grype/compare/v0.102.0...v0.103.0)**

### [`v0.102.0`](https://github.com/anchore/grype/releases/tag/v0.102.0)

##### Added Features

- Use Alma Linux specific advisories for Alma Linux scans \[[#&#8203;2745](https://github.com/anchore/grype/issues/2745) [#&#8203;2939](https://github.com/anchore/grype/pull/2939) [@&#8203;willmurphyscode](https://github.com/willmurphyscode)]

##### Bug Fixes

- Bitnami packages with CPEs are not matched against CPE-based vulnerabilities \[[#&#8203;2997](https://github.com/anchore/grype/issues/2997)]

##### Additional Changes

- add markdown template \[[#&#8203;2987](https://github.com/anchore/grype/pull/2987) [@&#8203;sebdanielsson](https://github.com/sebdanielsson)]

**[(Full Changelog)](https://github.com/anchore/grype/compare/v0.101.1...v0.102.0)**

### [`v0.101.1`](https://github.com/anchore/grype/releases/tag/v0.101.1)

##### Bug Fixes

- Panic error scanning images with v0.101.0 on some java dependencies \[[#&#8203;3002](https://github.com/anchore/grype/issues/3002)]

**[(Full Changelog)](https://github.com/anchore/grype/compare/v0.101.0...v0.101.1)**

### [`v0.101.0`](https://github.com/anchore/grype/releases/tag/v0.101.0)

##### Added Features

- Add cyclonedx to RpmMetadata \[[#&#8203;2935](https://github.com/anchore/grype/pull/2935) [@&#8203;sfc-gh-rmaj](https://github.com/sfc-gh-rmaj)]
- `grype db search` can filter by fixed state \[[#&#8203;2968](https://github.com/anchore/grype/pull/2968) [@&#8203;willmurphyscode](https://github.com/willmurphyscode)]
- Support using VEX documents with directory scans and SBOMs \[[#&#8203;2471](https://github.com/anchore/grype/issues/2471) [#&#8203;2811](https://github.com/anchore/grype/pull/2811) [@&#8203;alegrey91](https://github.com/alegrey91)]

##### Bug Fixes

- Issue installing Grype using documented curl command \[[#&#8203;2985](https://github.com/anchore/grype/issues/2985)]
- Advisory ID blank in JSON output \[[#&#8203;2965](https://github.com/anchore/grype/issues/2965)]

##### Additional Changes

- update flags with v3 to not use default config \[[#&#8203;3000](https://github.com/anchore/grype/pull/3000) [@&#8203;spiffcs](https://github.com/spiffcs)]
- fix Cosign documentation URL in installer \[[#&#8203;2995](https://github.com/anchore/grype/pull/2995) [@&#8203;lime](https://github.com/lime)]
- set advisory id again \[[#&#8203;2979](https://github.com/anchore/grype/pull/2979) [@&#8203;willmurphyscode](https://github.com/willmurphyscode)]
- add db schema validation \[[#&#8203;2962](https://github.com/anchore/grype/pull/2962) [@&#8203;willmurphyscode](https://github.com/willmurphyscode)]

**[(Full Changelog)](https://github.com/anchore/grype/compare/v0.100.0...v0.101.0)**

### [`v0.100.0`](https://github.com/anchore/grype/releases/tag/v0.100.0)

##### Added Features

- Add unaffected package and CPE stores \[[#&#8203;2888](https://github.com/anchore/grype/pull/2888) [@&#8203;wagoodman](https://github.com/wagoodman)]
- use unaffected match table to remove appropriate vulns \[[#&#8203;2886](https://github.com/anchore/grype/pull/2886) [@&#8203;CrosleyZack](https://github.com/CrosleyZack)]

**[(Full Changelog)](https://github.com/anchore/grype/compare/v0.99.1...v0.100.0)**

</details>

---

### Configuration

📅 **Schedule**: (in timezone Europe/Oslo)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate CLI](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xMTIuMyIsInVwZGF0ZWRJblZlciI6IjQ0LjExMi4zIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJyZW5vdmF0ZSJdfQ==-->

---------

Co-authored-by: Danijel Simeunovic <danijel.simeunovic@fortedigital.com>
Co-authored-by: Renovate Bot <renovate@forteapps.net>
Reviewed-on: https://git.forteapps.net/Forte/launchpad/pulls/34
Reviewed-by: Danijel Simeunovic <danijel.simeunovic@fortedigital.com>
Co-authored-by: gitea_admin <admin@forteapps.net>
Co-committed-by: gitea_admin <admin@forteapps.net>
2026-09-27 22:15:50 +00:00
365661853a chore(deps): update dependency k9s to v0.51.0 (#35)
/ test (push) Failing after 10m18s
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [k9s](https://github.com/derailed/k9s) | minor | `0.50.7` → `0.51.0` |

---

### Release Notes

<details>
<summary>derailed/k9s (k9s)</summary>

### [`v0.51.0`](https://github.com/derailed/k9s/releases/tag/v0.51.0)

<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/k9s.png" align="center" width="800" height="auto"/>

### Release v0.51.0
#### Notes

Thank you to all that contributed with flushing out issues and enhancements for K9s!
I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev
and see if we're happier with some of the fixes!
If you've filed an issue please help me verify and close.

Your support, kindness and awesome suggestions to make K9s better are, as ever, very much noted and appreciated!
Also big thanks to all that have allocated their own time to help others on both slack and on this repo!!

As you may know, K9s is not pimped out by big corporations with deep pockets, thus if you feel K9s is helping in your Kubernetes journey, please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@&#8203;kitesurfer](https://twitter.com/kitesurfer)

On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/zt-3360a389v-ElLHrb0Dp1kAXqYUItSAFA)

***

#### ♫ Sounds Behind The Release ♭

- [Aprieta - Vincen Garcia](https://www.youtube.com/watch?v=ldQ6hpg9BD0\&list=RDldQ6hpg9BD0\&start_radio=1)
- [Graham Chapman - John Cleese](https://www.youtube.com/watch?v=Bm2XPkqENaw)
- [Kill the pain - SYZGYX](https://www.youtube.com/watch?v=5XuvMhHZorw\&list=RD5XuvMhHZorw\&start_radio=1)

***

#### Maintenance Release!

Please help me welcome [Ümüt Özalp](https://github.com/uozalp) as a core contributor to K9s!
Ümüt has been instrumental in helping this project grow.
I trust you will help Ümüt triage issues and prs reviews and show him
the kindness and patience all k9sers are famous for!

Sponsorships are dropping at an alarming rate which puts this project in the red.
This is becoming a concern and sad not to mention unsustainable ;(
If you dig `k9s` and want to help the project, please consider `paying it forward!` and
don't become just another `satisfied, non paying customer!`.
K9s does take a lot of my `free` time to maintain, enhance and keep the light on.
Many cool ideas are making it straight to the `freezer` as I just can't budget them in.
I know many of you work for big corporations, so please put in the word/work and have
them help us out via sponsorships or other means.

Thank you!

***

#### Contributed PRs

Please be sure to give `Big Thanks!` and `ATTA Girls/Boys!` to all the fine contributors for making K9s better for all of us!!

- [#&#8203;4026](https://github.com/derailed/k9s/pull/4026) fix(xray): disable edit/delete actions in XRay view when readonly mode is enabled
- [#&#8203;4024](https://github.com/derailed/k9s/pull/4024) Fix 'J'umping to owner of cluster scoped resources
- [#&#8203;4005](https://github.com/derailed/k9s/pull/4005) Fix pod status for sidecar init containers
- [#&#8203;4001](https://github.com/derailed/k9s/pull/4001) Adjust namespace handling for RBAC checks in CanForResource and CanForInstance
- [#&#8203;3997](https://github.com/derailed/k9s/pull/3997) chore: fix wrong function name in comment
- [#&#8203;3993](https://github.com/derailed/k9s/pull/3993) fix(browser): show syncing status instead of spurious no-resources warning
- [#&#8203;3989](https://github.com/derailed/k9s/pull/3989) perf: skip reconcile cycle when informer data is unchanged
- [#&#8203;3988](https://github.com/derailed/k9s/pull/3988) perf: raise default client QPS from 5 to 50
- [#&#8203;3987](https://github.com/derailed/k9s/pull/3987) fix: paginate metrics API calls to prevent timeout on large clusters
- [#&#8203;3986](https://github.com/derailed/k9s/pull/3986) perf: batch Hydrate workers to eliminate per-item goroutine overhead
- [#&#8203;3917](https://github.com/derailed/k9s/pull/3917) Respect wide columns in default view
- [#&#8203;3911](https://github.com/derailed/k9s/pull/3911) fix: reset styles before loading skin on context switch
- [#&#8203;3908](https://github.com/derailed/k9s/pull/3908) fix: populate pod count in Node.Get() for single-node view
- [#&#8203;3902](https://github.com/derailed/k9s/pull/3902) Add OSC52 clipboard backend with native fallback
- [#&#8203;3888](https://github.com/derailed/k9s/pull/3888) feat: add One Light skin
- [#&#8203;3879](https://github.com/derailed/k9s/pull/3879) feat: allow users to cycle pulse grid selection forwards and backwards
- [#&#8203;3873](https://github.com/derailed/k9s/pull/3873) feat: enhance pvc-shell configuration with dynamic inputs and RWO support
- [#&#8203;3872](https://github.com/derailed/k9s/pull/3872) Add default confirm:true for plugins with inputs
- [#&#8203;3871](https://github.com/derailed/k9s/pull/3871) internal/render: prevent index out of range panic in initContainerStats
- [#&#8203;3865](https://github.com/derailed/k9s/pull/3865) Handle blank PVC capacities for the purpose of sorting
- [#&#8203;3854](https://github.com/derailed/k9s/pull/3854) feat: enhance debug container configuration with input fields
- [#&#8203;3851](https://github.com/derailed/k9s/pull/3851) Use \*grey instead of grey in black-and-wtf.yaml
- [#&#8203;3839](https://github.com/derailed/k9s/pull/3839) fix: optimize context switching to reduce redundant API calls
- [#&#8203;3823](https://github.com/derailed/k9s/pull/3823) feat: add resize PVC plugin for dynamic storage resizing
- [#&#8203;3821](https://github.com/derailed/k9s/pull/3821) feat: add support for plugin input fields
- [#&#8203;3817](https://github.com/derailed/k9s/pull/3817) Fix Readme: Ubuntu installation command not working
- [#&#8203;3798](https://github.com/derailed/k9s/pull/3798) Fix boom on Jumping Owner in rare cases
- [#&#8203;3797](https://github.com/derailed/k9s/pull/3797) feat: add extra hints for column navigation in table view
- [#&#8203;3792](https://github.com/derailed/k9s/pull/3792) fix: adjust resource access checks for namespace resources
- [#&#8203;3783](https://github.com/derailed/k9s/pull/3783) fix: avoid logging errors when no context is configured
- [#&#8203;3780](https://github.com/derailed/k9s/pull/3780) feat: add selected color to table header
- [#&#8203;3736](https://github.com/derailed/k9s/pull/3736) feat: add custom resource jump support
- [#&#8203;3634](https://github.com/derailed/k9s/pull/3634) Add shell detection for Windows NanoServer containers

***

<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/imhotep_logo.png" width="32" height="auto"/> © 2026 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)#

### [`v0.50.18`](https://github.com/derailed/k9s/releases/tag/v0.50.18)

<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/k9s.png" align="center" width="800" height="auto"/>

### Release v0.50.18
#### Notes

🥳🎉 Happy new year fellow k9ers!🎊🍾 Hoping 2026 will bring good health and great success to you and yours...

Thank you to all that contributed with flushing out issues and enhancements for K9s!
I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev
and see if we're happier with some of the fixes!
If you've filed an issue please help me verify and close.

Your support, kindness and awesome suggestions to make K9s better are, as ever, very much noted and appreciated!
Also big thanks to all that have allocated their own time to help others on both slack and on this repo!!

As you may know, K9s is not pimped out by big corporations with deep pockets, thus if you feel K9s is helping in your Kubernetes journey, please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@&#8203;kitesurfer](https://twitter.com/kitesurfer)

On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/zt-3360a389v-ElLHrb0Dp1kAXqYUItSAFA)

***

#### ♫ Sounds Behind The Release ♭

- [A cool new way - Joe Satriani](https://www.youtube.com/watch?v=4apA948yOF0)
- [Song for you - Ray Charles](https://www.youtube.com/watch?v=CzAkTrDiXxg)
- [Kill the pain - SYZGYX](https://www.youtube.com/watch?v=5XuvMhHZorw\&list=RD5XuvMhHZorw\&start_radio=1)

***

#### Maintenance Release!

Oops! I've missed a PR in the v0.50.17 excitement ;( Dropping v0.50.18 with feelings...

Sponsorships are dropping at an alarming rate which puts this project in the red. This is becoming a concern and sad not to mention unsustainable ;( If you dig `k9s` and want to help the project, please consider `paying it forward!` and don't become just another `satisfied, non paying customer!`. K9s does take a lot of my `free` time to maintain, enhance and keep the light on. Many cool ideas are making it straight to the `freezer` as I just can't budget them in.
I know many of you work for big corporations, so please put in the word/work and have them help us out via sponsorships or other means.

Thank you!

***

#### A Word From Our Sponsors...

To all the good folks and orgs below that opted to `pay it forward` and join our sponsorship program, I salute you!!

- [Philomena Yeboah](https://github.com/PhilomenaYeboah1989)
- [Kilian](https://github.com/kaerbr)
- [TVRiddle](https://github.com/TVRiddle)
- [Tom Morelly](https://github.com/FalcoSuessgott)
- [Nikhil Narayen](https://github.com/nnarayen)
- [Andrew Aadland](https://github.com/DaemonDude23)
- [Radek](https://github.com/radvym)
- [Timothée Gerber](https://github.com/TimotheeGerber)
- [Matthias](https://github.com/maetthu)
- [DKB](https://github.com/dkb-bank) ❤️
- [Kraken Tech](https://github.com/kraken-tech)
- [Daniel](https://github.com/sherlock7402)
- [Fred Loucks](https://github.com/fullmetal-fred)
- [Patricia Mascaros](https://github.com/ccong2586)
- [Qube Research & Technologies](https://github.com/qube-rt)
- [Michel Jung](https://github.com/micheljung)
- [Ümüt Özalp](https://github.com/uozalp)
- [Nathan Papapietro](https://github.com/npapapietro)
- [Oleksandr Podze](https://github.com/dasdy)
- [Lee Jones](https://github.com/leejones)
- [tsahlif](https://github.com/tshalif)
- [Jean-Christophe Amiel](https://github.com/jcamiel)
- [Lightspark](https://github.com/lightsparkdev)
- [egs-hub](https://github.com/egs-hub) ❤️
- [Sergey](https://github.com/malsatin)
- [Wynter Inc](https://github.com/copytesting)
- [Jen Norris](https://github.com/tnorris)
- [Joakim-Byg](https://github.com/Joakim-Byg)
- [Oleksandr Podze](https://github.com/dasdy)
- [Lee Jones](https://github.com/leejones)

> Sponsorship cancellations since the last release: **17!** 🥹

#### Resolved Issues

- [#&#8203;3765](https://github.com/derailed/k9s/issues/3765) quay.io docker images not up to date but referenced in README.md
- [#&#8203;3762](https://github.com/derailed/k9s/issues/3762) Copy multiple selected items
- [#&#8203;3751](https://github.com/derailed/k9s/issues/3751) Improve visual distinction for cordoned nodes in Node view
- [#&#8203;3735](https://github.com/derailed/k9s/issues/3735) Cannot decode secret if there is no get permissions for all secrets
- [#&#8203;3708](https://github.com/derailed/k9s/issues/3708) Editing a single Namespace opens the editor with a list of all Namespaces
- [#&#8203;3731](https://github.com/derailed/k9s/issues/3731) feat: add neat plugin
- [#&#8203;3735](https://github.com/derailed/k9s/issues/3735) Cannot decode secret if there is no get permissions for all secrets
- [#&#8203;3708](https://github.com/derailed/k9s/issues/3708) Editing a single Namespace opens the editor with a list of all Namespaces
- [#&#8203;3649](https://github.com/derailed/k9s/issues/3649) Improved Column Sorting

***

#### Contributed PRs

Please be sure to give `Big Thanks!` and `ATTA Girls/Boys!` to all the fine contributors for making K9s better for all of us!!

- [#&#8203;3763](https://github.com/derailed/k9s/pull/3763) feat: enable copying multiple resource, namespace names to clipboard
- [#&#8203;3760](https://github.com/derailed/k9s/pull/3760) fix: Editing a single Namespace opens the editor with a list of all Namespaces
- [#&#8203;3756](https://github.com/derailed/k9s/pull/3756) feat: Add reconcile plugin for Flux instances
- [#&#8203;3755](https://github.com/derailed/k9s/pull/3755) fix: panic on 'jump to owner' of reflect.Value.Elem on zero Value
- [#&#8203;3753](https://github.com/derailed/k9s/pull/3553) feat: add plugins for argo workflows
- [#&#8203;3750](https://github.com/derailed/k9s/pull/3750) fix: Flux trace plugin shortcut conflict by changing to Shift-Q
- [#&#8203;3749](https://github.com/derailed/k9s/pull/3749) feat: add dark/light theme inversion using Oklch
- [#&#8203;3739](https://github.com/derailed/k9s/pull/3739) chore: refine LabelsSelector comment to match function behavior
- [#&#8203;3738](https://github.com/derailed/k9s/pull/3738) feat: add symlink handle for plugin directory
- [#&#8203;3720](https://github.com/derailed/k9s/pull/3720) fix(internal/render): ensure object is deep copied before realization in Render method
- [#&#8203;3704](https://github.com/derailed/k9s/pull/3704) Allow k9s to start without a valid Kubernetes context
- [#&#8203;3699](https://github.com/derailed/k9s/pull/3699) feat(pulse): map hjkl to navigate as help shows
- [#&#8203;3697](https://github.com/derailed/k9s/pull/3697) Issue 3667 Fix
- [#&#8203;3696](https://github.com/derailed/k9s/pull/3696) fix for scale option appearing on non-scalable resources
- [#&#8203;3690](https://github.com/derailed/k9s/pull/3690) feat: add support for scaling HPA targets
- [#&#8203;3671](https://github.com/derailed/k9s/pull/3671) fix fails to modify or delete namespaces using RBAC
- [#&#8203;3669](https://github.com/derailed/k9s/pull/3669) feat: logs column lock
- [#&#8203;3663](https://github.com/derailed/k9s/pull/3663) Map Q to "Back"
- [#&#8203;3661](https://github.com/derailed/k9s/pull/3661) refactor: remove unused sorting key bindings from various views
- [#&#8203;3859](https://github.com/derailed/k9s/pull/3859) fix: update busybox image version to 1.37.0 in configuration files
- [#&#8203;3650](https://github.com/derailed/k9s/pull/3650) Sort all columns
- [#&#8203;3458](https://github.com/derailed/k9s/pull/3458) Document how to install on Fedora

***

<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/imhotep_logo.png" width="32" height="auto"/> © 2026 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)#

### [`v0.50.16`](https://github.com/derailed/k9s/releases/tag/v0.50.16)

<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/k9s.png" align="center" width="800" height="auto"/>

### Release v0.50.16
#### Notes

Thank you to all that contributed with flushing out issues and enhancements for K9s!
I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev
and see if we're happier with some of the fixes!
If you've filed an issue please help me verify and close.

Your support, kindness and awesome suggestions to make K9s better are, as ever, very much noted and appreciated!
Also big thanks to all that have allocated their own time to help others on both slack and on this repo!!

As you may know, K9s is not pimped out by big corporations with deep pockets, thus if you feel K9s is helping in your Kubernetes journey, please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@&#8203;kitesurfer](https://twitter.com/kitesurfer)

On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/zt-3360a389v-ElLHrb0Dp1kAXqYUItSAFA)

#### Maintenance Release!

Sponsorships are dropping at an alarming rate which puts this project in the red. This is becoming a concern and sad not to mention unsustainable ;( If you dig `k9s` and want to help the project, please consider `paying it forward!` and don't become just another `satisfied, non paying customer!`. K9s does take a lot of my `free` time to maintain, enhance and keep the light on. Many cool ideas are making it straight to the `freezer` as I just can't budget them in.
I know many of you work for big corporations, so please put in the word/work and have them help us out via sponsorships or other means.

Thank you!

##### Warp Speed Scotty!

As of this drop, we are introducing `namespace warp` via shortcut `w`.
This affords to view all resources of that type based on the currently selected resource namespace.
This command is only available on namespaced resources.
For example, if you are in pod view and select pod-xxx in namespace `bozo`, hitting `w` will `warp`
you to view all pods in namespace `bozo`.

#### Resolved Issues

- [#&#8203;3629](https://github.com/derailed/k9s/issues/3629) vulnerability in k9s project
- [#&#8203;3621](https://github.com/derailed/k9s/issues/3621) Switching to ":Deploy" sends you to deployments from namespace "deploy"
- [#&#8203;3620](https://github.com/derailed/k9s/issues/3620) Trying to show pod yaml using custom views.yaml crashes k9s
- [#&#8203;3608](https://github.com/derailed/k9s/issues/3608) k9s crashes when :namespaces used
- [#&#8203;3601](https://github.com/derailed/k9s/issues/3601) Can't delete namespace
- [#&#8203;3595](https://github.com/derailed/k9s/issues/3595) Toggle Namespace Filter in Pods View with 'n' Key
- [#&#8203;3576](https://github.com/derailed/k9s/issues/3576) Custom alias/view not working anymore since v0.50.10

***

#### Contributed PRs

Please be sure to give `Big Thanks!` and `ATTA Girls/Boys!` to all the fine contributors for making K9s better for all of us!!

- [#&#8203;3625](https://github.com/derailed/k9s/pull/3625) fix: debug-container plugin when KUBECONFIG has multiple files
- [#&#8203;3623](https://github.com/derailed/k9s/pull/3623) bugfix: fix panic in BenchmarkPodRender by using NewPod() constructor
- [#&#8203;3619](https://github.com/derailed/k9s/pull/3619) feat: plugin to list all resources by namespace
- [#&#8203;3605](https://github.com/derailed/k9s/pull/3605) browser: do not prevent redraw when connection unavailable
- [#&#8203;3600](https://github.com/derailed/k9s/pull/3600) fix(shell): set linux when OS detection fails
- [#&#8203;3588](https://github.com/derailed/k9s/pull/3588) fix: do not error out of shellIn if OS detection fails

***

<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/imhotep_logo.png" width="32" height="auto"/> © 2025 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)#

### [`v0.50.15`](https://github.com/derailed/k9s/releases/tag/v0.50.15)

<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/k9s.png" align="center" width="800" height="auto"/>

### Release v0.50.15
#### Notes

Thank you to all that contributed with flushing out issues and enhancements for K9s!
I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev
and see if we're happier with some of the fixes!
If you've filed an issue please help me verify and close.

Your support, kindness and awesome suggestions to make K9s better are, as ever, very much noted and appreciated!
Also big thanks to all that have allocated their own time to help others on both slack and on this repo!!

As you may know, K9s is not pimped out by big corporations with deep pockets, thus if you feel K9s is helping in your Kubernetes journey, please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@&#8203;kitesurfer](https://twitter.com/kitesurfer)

On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/zt-3360a389v-ElLHrb0Dp1kAXqYUItSAFA)

#### Maintenance Release!

Sponsorships are dropping at an alarming rate which puts this project in the red. This is becoming a concern and sad not to mention unsustainable ;( If you dig `k9s` and want to help the project, please consider `paying it forward!` and don't become just another `satisfied, non paying customer!`. K9s does take a lot of my `free` time to maintain, enhance and keep the light on. Many cool ideas are making it straight to the `freezer` as I just can't budget them in.
I know many of you work for big corporations, so please put in the word/work and have them help us out via sponsorships or other means.

Thank you!

#### Resolved Issues

- [#&#8203;3591](https://github.com/derailed/k9s/issues/3591) REVERTED! Accept suggestion with enter (without having to "tab")

***

<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/imhotep_logo.png" width="32" height="auto"/> © 2025 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)#

### [`v0.50.13`](https://github.com/derailed/k9s/releases/tag/v0.50.13)

<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/k9s.png" align="center" width="800" height="auto"/>

### Release v0.50.13
#### Notes

Thank you to all that contributed with flushing out issues and enhancements for K9s!
I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev
and see if we're happier with some of the fixes!
If you've filed an issue please help me verify and close.

Your support, kindness and awesome suggestions to make K9s better are, as ever, very much noted and appreciated!
Also big thanks to all that have allocated their own time to help others on both slack and on this repo!!

As you may know, K9s is not pimped out by corps with deep pockets, thus if you feel K9s is helping your Kubernetes journey,
please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@&#8203;kitesurfer](https://twitter.com/kitesurfer)

On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/zt-3360a389v-ElLHrb0Dp1kAXqYUItSAFA)

#### Maintenance Release!

#### Resolved Issues

- [#&#8203;3587](https://github.com/derailed/k9s/issues/3587) UI doesn't show any updates when restarting a Deployment
- [#&#8203;3585](https://github.com/derailed/k9s/issues/3585) abbreviation sec for secret not working
- [#&#8203;3584](https://github.com/derailed/k9s/issues/3584) Show managed fields doesn't show them
- [#&#8203;3583](https://github.com/derailed/k9s/issues/3583) Cannot open shell to pods without node read access as of 0.50.12
- [#&#8203;3577](https://github.com/derailed/k9s/issues/3577) Log view is broken as of v0.50.10
- [#&#8203;3574](https://github.com/derailed/k9s/issues/3574) Aliases for pods with label filters not working

***

<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/imhotep_logo.png" width="32" height="auto"/> © 2025 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)#

### [`v0.50.12`](https://github.com/derailed/k9s/releases/tag/v0.50.12)

<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/k9s.png" align="center" width="800" height="auto"/>

### Release v0.50.12
#### Notes

Thank you to all that contributed with flushing out issues and enhancements for K9s!
I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev
and see if we're happier with some of the fixes!
If you've filed an issue please help me verify and close.

Your support, kindness and awesome suggestions to make K9s better are, as ever, very much noted and appreciated!
Also big thanks to all that have allocated their own time to help others on both slack and on this repo!!

As you may know, K9s is not pimped out by corps with deep pockets, thus if you feel K9s is helping your Kubernetes journey,
please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@&#8203;kitesurfer](https://twitter.com/kitesurfer)

On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/zt-3360a389v-ElLHrb0Dp1kAXqYUItSAFA)

#### Maintenance Release!

#### Resolved Issues

- [#&#8203;3570](https://github.com/derailed/k9s/issues/3570) 0.50.11 could not display any resources
- [#&#8203;3562](https://github.com/derailed/k9s/issues/3562) Can't delete namespace
- [#&#8203;3547](https://github.com/derailed/k9s/issues/3547) Error message from admission controller

***

<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/imhotep_logo.png" width="32" height="auto"/> © 2025 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)#

### [`v0.50.9`](https://github.com/derailed/k9s/releases/tag/v0.50.9)

<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/k9s.png" align="center" width="800" height="auto"/>

### Release v0.50.9
#### Notes

Thank you to all that contributed with flushing out issues and enhancements for K9s!
I'll try to mark some of these issues as fixed. But if you don't mind grab the latest rev
and see if we're happier with some of the fixes!
If you've filed an issue please help me verify and close.

Your support, kindness and awesome suggestions to make K9s better are, as ever, very much noted and appreciated!
Also big thanks to all that have allocated their own time to help others on both slack and on this repo!!

As you may know, K9s is not pimped out by corps with deep pockets, thus if you feel K9s is helping your Kubernetes journey,
please consider joining our [sponsorship program](https://github.com/sponsors/derailed) and/or make some noise on social! [@&#8203;kitesurfer](https://twitter.com/kitesurfer)

On Slack? Please join us [K9slackers](https://join.slack.com/t/k9sers/shared_invite/zt-3360a389v-ElLHrb0Dp1kAXqYUItSAFA)

#### Maintenance Release!

***

#### Resolved Issues

- [#&#8203;3459](https://github.com/derailed/k9s/issues/3459) Update the tablewriter dependency + implementation
- [#&#8203;3458](https://github.com/derailed/k9s/issues/3458) Unable to switch namespaces with 0.50.8

***

#### Contributed PRs

Please be sure to give `Big Thanks!` and `ATTA Girls/Boys!` to all the fine contributors for making K9s better for all of us!!

- [#&#8203;3460](https://github.com/derailed/k9s/pull/3460) update to tablewriter v1 apis

***

<img src="https://raw.githubusercontent.com/derailed/k9s/master/assets/imhotep_logo.png" width="32" height="auto"/> © 2025 Imhotep Software LLC. All materials licensed under [Apache v2.0](http://www.apache.org/licenses/LICENSE-2.0)#

</details>

---

### Configuration

📅 **Schedule**: (in timezone Europe/Oslo)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate CLI](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xMTIuMyIsInVwZGF0ZWRJblZlciI6IjQ0LjExMi4zIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJyZW5vdmF0ZSJdfQ==-->

---------

Co-authored-by: Danijel Simeunovic <danijel.simeunovic@fortedigital.com>
Co-authored-by: Renovate Bot <renovate@forteapps.net>
Reviewed-on: https://git.forteapps.net/Forte/launchpad/pulls/35
Reviewed-by: Danijel Simeunovic <danijel.simeunovic@fortedigital.com>
Co-authored-by: gitea_admin <admin@forteapps.net>
Co-committed-by: gitea_admin <admin@forteapps.net>
2026-09-27 22:14:56 +00:00
2e54387eed chore(deps): update dependency kubectl-tree to v0.6.0 (#39)
/ test (push) Failing after 12m43s
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [kubectl-tree](https://github.com/ahmetb/kubectl-tree) | minor | `0.4.3` → `0.6.0` |

---

### Release Notes

<details>
<summary>ahmetb/kubectl-tree (kubectl-tree)</summary>

### [`v0.6.0`](https://github.com/ahmetb/kubectl-tree/releases/tag/v0.6.0)

#### Changelog

- [`07c5f12`](https://github.com/ahmetb/kubectl-tree/commit/07c5f1241c0af99171cd3d3ff5ec0f109f37058f) Release v0.6.0
- [`52dda04`](https://github.com/ahmetb/kubectl-tree/commit/52dda045e341f7b24e4258c0286c3fec10410ed2) feat: filter resources ([#&#8203;115](https://github.com/ahmetb/kubectl-tree/issues/115))
- [`55f6b48`](https://github.com/ahmetb/kubectl-tree/commit/55f6b482b7a828554850d30a603fc7bfcd05ba43) Merge pull request [#&#8203;117](https://github.com/ahmetb/kubectl-tree/issues/117) from ahmetb/dependabot/go\_modules/github.com/fatih/color-1.19.0
- [`f8a3daf`](https://github.com/ahmetb/kubectl-tree/commit/f8a3daf738056f538876d39047c24c7859650884) Merge pull request [#&#8203;116](https://github.com/ahmetb/kubectl-tree/issues/116) from ahmetb/dependabot/go\_modules/kubernetes-cc55a87f86
- [`f524512`](https://github.com/ahmetb/kubectl-tree/commit/f5245124c6dd6885075bc80d1a88a46e515c87ca) chore(deps): bump github.com/fatih/color from 1.15.0 to 1.19.0
- [`9d3c3c9`](https://github.com/ahmetb/kubectl-tree/commit/9d3c3c9aaa641f2c2a2a5a55077acd5d9477144b) chore(deps): bump the kubernetes group with 3 updates

### [`v0.5.0`](https://github.com/ahmetb/kubectl-tree/releases/tag/v0.5.0)

#### Changelog

- [`44b7841`](https://github.com/ahmetb/kubectl-tree/commit/44b784104c46a1ebd3c9a2f990106c2790de7708) chore(deps): bump goreleaser/goreleaser-action from 6 to 7 ([#&#8203;110](https://github.com/ahmetb/kubectl-tree/issues/110))

### [`v0.4.6`](https://github.com/ahmetb/kubectl-tree/releases/tag/v0.4.6)

#### Changelog

- [`ee7f2ca`](https://github.com/ahmetb/kubectl-tree/commit/ee7f2ca11e271c8ece1a8426bce4119d1ff38bac) Fix namespace and CRD handling logic (vibe slop)

</details>

---

### Configuration

📅 **Schedule**: (in timezone Europe/Oslo)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate CLI](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xMTIuMyIsInVwZGF0ZWRJblZlciI6IjQ0LjExMi4zIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJyZW5vdmF0ZSJdfQ==-->

---------

Co-authored-by: Danijel Simeunovic <danijel.simeunovic@fortedigital.com>
Co-authored-by: Renovate Bot <renovate@forteapps.net>
Reviewed-on: https://git.forteapps.net/Forte/launchpad/pulls/39
Reviewed-by: Danijel Simeunovic <danijel.simeunovic@fortedigital.com>
Co-authored-by: gitea_admin <admin@forteapps.net>
Co-committed-by: gitea_admin <admin@forteapps.net>
2026-09-27 22:12:29 +00:00
danijel.simeunovic f35af13ef3 group:allNonMajor
/ test (push) Failing after 13m29s
2026-09-27 22:11:47 +00:00
7abee90f4e chore(deps): update dependency claude-code to v0.2.122 (#27)
/ test (push) Successful in 29s
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [claude-code](https://github.com/anthropics/claude-code) | patch | `latest` → `0.2.122` |

---

### Release Notes

<details>
<summary>anthropics/claude-code (claude-code)</summary>

### [`v0.2.107`](https://github.com/anthropics/claude-code/blob/HEAD/CHANGELOG.md#02107)

- CLAUDE.md files can now import other files. Add @&#8203;path/to/file.md to ./CLAUDE.md to load additional files on launch

### [`v0.2.74`](https://github.com/anthropics/claude-code/blob/HEAD/CHANGELOG.md#0274)

- Added support for refreshing dynamically generated API keys (via apiKeyHelper), with a 5 minute TTL
- Task tool can now perform writes and run bash commands

### [`v0.2.69`](https://github.com/anthropics/claude-code/blob/HEAD/CHANGELOG.md#0269)

- Fixed UI glitches with improved Select component behavior
- Enhanced terminal output display with better text truncation logic

### [`v0.2.67`](https://github.com/anthropics/claude-code/blob/HEAD/CHANGELOG.md#0267)

- Shared project permission rules can be saved in .claude/settings.json

### [`v0.2.59`](https://github.com/anthropics/claude-code/blob/HEAD/CHANGELOG.md#0259)

- Copy+paste images directly into your prompt
- Improved progress indicators for bash and fetch tools
- Bugfixes for non-interactive mode (-p)

### [`v0.2.54`](https://github.com/anthropics/claude-code/blob/HEAD/CHANGELOG.md#0254)

- Quickly add to Memory by starting your message with '#'
- Press ctrl+r to see full output for long tool results
- Added support for MCP SSE transport

### [`v0.2.53`](https://github.com/anthropics/claude-code/blob/HEAD/CHANGELOG.md#0253)

- New web fetch tool lets Claude view URLs that you paste in
- Fixed a bug with JPEG detection

### [`v0.2.41`](https://github.com/anthropics/claude-code/blob/HEAD/CHANGELOG.md#0241)

- MCP server startup timeout can now be configured via MCP\_TIMEOUT environment variable
- MCP server startup no longer blocks the app from starting up

### [`v0.2.32`](https://github.com/anthropics/claude-code/blob/HEAD/CHANGELOG.md#0232)

- Interactive MCP setup wizard: Run "claude mcp add" to add MCP servers with a step-by-step interface
- Fix for some PersistentShell issues

### [`v0.2.30`](https://github.com/anthropics/claude-code/blob/HEAD/CHANGELOG.md#0230)

- Added ANSI color theme for better terminal compatibility
- Fixed issue where slash command arguments weren't being sent properly
- (Mac-only) API keys are now stored in macOS Keychain

</details>

---

### Configuration

📅 **Schedule**: (in timezone Europe/Oslo)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate CLI](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xMTIuMyIsInVwZGF0ZWRJblZlciI6IjQ0LjExMi4zIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJyZW5vdmF0ZSJdfQ==-->

---------

Co-authored-by: Danijel Simeunovic <danijel.simeunovic@fortedigital.com>
Co-authored-by: Renovate Bot <renovate@forteapps.net>
Reviewed-on: #27
Reviewed-by: Danijel Simeunovic <danijel.simeunovic@fortedigital.com>
Co-authored-by: gitea_admin <admin@forteapps.net>
Co-committed-by: gitea_admin <admin@forteapps.net>
2026-09-25 05:47:12 +00:00
bb37c6051f chore(deps): update dependency _1password to v1.12.2 (#30)
/ test (push) Failing after 14m48s
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [_1password](https://developer.1password.com/docs/cli/) | minor | `latest` → `1.12.2` |

---

### Configuration

📅 **Schedule**: (in timezone Europe/Oslo)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate CLI](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xMTIuMyIsInVwZGF0ZWRJblZlciI6IjQ0LjExMi4zIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJyZW5vdmF0ZSJdfQ==-->

---------

Co-authored-by: Danijel Simeunovic <danijel.simeunovic@fortedigital.com>
Co-authored-by: Renovate Bot <renovate@forteapps.net>
Reviewed-on: #30
Reviewed-by: Danijel Simeunovic <danijel.simeunovic@fortedigital.com>
Co-authored-by: gitea_admin <admin@forteapps.net>
Co-committed-by: gitea_admin <admin@forteapps.net>
2026-09-25 05:46:32 +00:00
91c7a90d1b chore(deps): update alpine docker tag to v3.24 (#29)
/ test (push) Failing after 10m45s
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [alpine](https://hub.docker.com/_/alpine) ([source](https://github.com/alpinelinux/docker-alpine)) | minor | `3.20` → `3.24` |

---

### Configuration

📅 **Schedule**: (in timezone Europe/Oslo)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate CLI](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xMTIuMyIsInVwZGF0ZWRJblZlciI6IjQ0LjExMi4zIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJyZW5vdmF0ZSJdfQ==-->

---------

Co-authored-by: Renovate Bot <renovate@forteapps.net>
Reviewed-on: #29
Reviewed-by: Danijel Simeunovic <danijel.simeunovic@fortedigital.com>
Co-authored-by: gitea_admin <admin@forteapps.net>
Co-committed-by: gitea_admin <admin@forteapps.net>
2026-09-25 05:40:40 +00:00
d42b5a89f3 chore(deps): update dependency dotnet-sdk to v2.1.810 (#28)
/ test (push) Failing after 11m30s
This PR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [dotnet-sdk](https://dotnet.github.io/) | patch | `latest` → `2.1.810` |

---

### Configuration

📅 **Schedule**: (in timezone Europe/Oslo)

- Branch creation
  - At any time (no schedule defined)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate CLI](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xMTIuMyIsInVwZGF0ZWRJblZlciI6IjQ0LjExMi4zIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJyZW5vdmF0ZSJdfQ==-->

---------

Co-authored-by: Renovate Bot <renovate@forteapps.net>
Reviewed-on: #28
Reviewed-by: Danijel Simeunovic <danijel.simeunovic@fortedigital.com>
Co-authored-by: gitea_admin <admin@forteapps.net>
Co-committed-by: gitea_admin <admin@forteapps.net>
2026-09-25 05:39:52 +00:00
danijel.simeunovic c1f4298bfe renovate
/ test (push) Successful in 23s
2026-09-25 00:02:00 +02:00
danijel.simeunovic 14cabaf3f5 version bumps
/ test (push) Successful in 36s
2026-09-24 23:56:20 +02:00
danijel.simeunovic 809e90d12d upgrade loki
/ test (push) Successful in 31s
2026-09-23 20:46:25 +00:00
danijel.simeunovic eeb7321819 loki values
/ test (push) Failing after 11m2s
2026-09-23 20:20:19 +00:00
danijel.simeunovic 133bdb8715 benken secret
/ test (push) Successful in 46s
2026-09-23 20:30:09 +02:00
danijel.simeunovic c47e89e02a Update infra/values/base/loki-values.yaml
/ test (push) Successful in 11s
2026-09-16 17:17:12 +00:00
danijel.simeunovic 3bf6f22a4a keycloak client cleanup: harden candidate selection
/ test (push) Successful in 8s
2026-09-04 12:05:23 +00:00
danijel.simeunovic 10f27fa3c4 harden client cleanup scheduling
/ test (push) Successful in 13s
timeZone: "Europe/Oslo"
    startingDeadlineSeconds: 3600
2026-09-04 11:38:33 +00:00
gitea_admin 90ba7ac905 Keycloak: Run client cleanup once a month
/ test (push) Successful in 13s
2026-09-03 09:52:49 +00:00
gitea_admin b616e59231 Increase min age for keycloak client cleanup
/ test (push) Successful in 13s
increase to 15 days min age
2026-09-03 07:26:30 +00:00
danijel.simeunovic 705c010806 Mute deployment notifications
/ test (push) Successful in 9s
2026-08-25 12:34:33 +00:00
29624e845d fix(forte-drop-pg-backup): set MC_CONFIG_DIR so backups can upload (#23)
/ test (push) Successful in 10s
The nightly Postgres backup CronJob has been **failing every run** — no backups exist in `s3://drops/_pgbackups/`.

**Cause:** the upload container runs as uid 65532 (`runAsNonRoot`). `mc` defaults its config to `$HOME/.mc` = `/.mc` and dies with `mkdir /.mc: permission denied` on the non-writable root fs — before any upload.

**Fix:** set `MC_CONFIG_DIR=/work/.mc` (the shared emptyDir, writable via `fsGroup: 65532`). The `pg_dump` initContainer already succeeds; this lets the upload step actually run.

Validated: `kubectl kustomize` renders clean; env present on the upload container.

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

---------

Co-authored-by: Danijel Simeunovic <danijel.simeunovic@fortedigital.com>
Co-authored-by: Sten <sten@Sten-sin-MacBook-Pro.local>
Reviewed-on: #23
Reviewed-by: Danijel Simeunovic <danijel.simeunovic@fortedigital.com>
Co-authored-by: Jørgen Stensrud <jorgen.stensrud@fortedigital.com>
Co-committed-by: Jørgen Stensrud <jorgen.stensrud@fortedigital.com>
2026-08-25 09:44:02 +00:00
danijel.simeunovic 4712eb4804 wayfinder instructions
/ test (push) Successful in 8s
2026-08-01 12:10:50 +02:00
danijel.simeunovic 2696044a02 docs
/ test (push) Successful in 8s
2026-08-01 12:07:26 +02:00
danijel.simeunovic b0c0074f7f Merge branch 'main' of https://git.forteapps.net/Forte/launchpad
/ test (push) Successful in 8s
2026-07-02 15:27:25 +02:00
danijel.simeunovic 7f4a0bccf1 notify mail 2026-07-02 15:27:16 +02:00
jorgen.stensrud 52c752caba feat(auth-sidecar): inject AUTH_OIDC_ALLOWED_RETURN_HOSTS (#25)
/ test (push) Successful in 9s
2026-07-02 13:17:57 +00:00
danijel.simeunovic af1e94d85d review
/ test (push) Successful in 9s
2026-07-02 12:25:11 +02:00
jorgen.stensrudanddanijel.simeunovic df35cd0630 feat(auth-sidecar): inject AUTH_OIDC_COOKIE_DOMAIN (#24)
/ test (push) Successful in 10s
Adds AUTH_OIDC_COOKIE_DOMAIN to the injected OIDC sidecar, from the `policies.forteapps.io/auth-oidc-cookie-domain` annotation. Empty when unset = host-only = unchanged for every app. Pairs with forte-helm + auth-sidecar#23. Safe to merge anytime (opt-in).

---------

Co-authored-by: Danijel Simeunovic <danijel.simeunovic@fortedigital.com>
Reviewed-on: #24
Reviewed-by: Danijel Simeunovic <danijel.simeunovic@fortedigital.com>
2026-06-30 06:59:37 +00:00
danijel.simeunovic 04b3a210fe shared-prompts
/ test (push) Successful in 8s
2026-06-29 17:02:50 +02:00
danijel.simeunovic 330c25f241 model
/ test (push) Successful in 8s
2026-06-29 16:47:51 +02:00
jorgen.stensrud 3a23451802 feat(forte-drop): issuer dnsZones for *.drop.forteapps.net (subdomain-per-drop) (#22)
/ test (push) Successful in 12s
2026-06-26 11:38:30 +00:00
danijel.simeunovic 9297398d56 gitea update
/ test (push) Successful in 8s
2026-06-11 13:03:59 +02:00
danijel.simeunovic b0804e1e6a scan
/ test (push) Successful in 11s
2026-06-11 10:34:11 +02:00
danijel.simeunovic 8216399155 trufflehog
/ test (push) Failing after 33s
2026-06-11 10:14:25 +02:00
danijel.simeunovic a70f078bbb drop drop 2026-06-05 19:38:30 +02:00
danijel.simeunovic a24e61d538 disable slack notifications for forte-drop
Signed-off-by: Danijel Simeunovic <danijel.simeunovic@fortedigital.com>
2026-06-05 13:41:42 +00:00
jorgen.stensrudandSten 275ec675da fix(apps): drop dangling namespace.yaml ref (enterprise-apps ComparisonError) (#19)
0a98674 deleted `namespace.yaml` but `apps/overlays/upc-dev/forte-drop/kustomization.yaml` still lists it → `kustomize build` fails → the **enterprise-apps** app-of-apps has a ComparisonError and the whole overlay stopped syncing. Visible symptom: `secret "forte-drop-secrets" not found` on all forte-drop pods (the SealedSecret no longer applies).

One-line fix: remove the dangling resource entry. The namespace itself is fine — the forte-drop Application has `CreateNamespace=true`.

@danijel.simeunovic — pairs with your cleanup; after this merges the secret re-applies and the pods only need the right image tag (helm-prod-values PR #4: `buildcache` → `v20260604-200105-1316f7a`, buildcache is the buildx cache manifest, not a runnable image).

Co-authored-by: Sten <sten@Sten-sin-MacBook-Pro.local>
Reviewed-on: #19
Reviewed-by: Danijel Simeunovic <danijel.simeunovic@fortedigital.com>
2026-06-05 08:44:56 +00:00
danijel.simeunovic 0a98674a27 not needed 2026-06-05 00:05:56 +02:00
b713ec853c feat(apps): forte-drop web + mcp argocd apps (prod) (#18)
## Summary

ArgoCD Applications + Keycloak clients + sealed secret for forte-drop **web + mcp** (PROD).

## What changed

- **forte-drop** + **forte-drop-mcp** ArgoCD Applications (two-source: forte-helm chart + helm-prod-values).
- **namespace.yaml** — explicit `forte-drop` Namespace at sync-wave -1, `Prune=false` (avoids first-sync race for namespaced resources; doesn't cascade-delete on base removal).
- **keycloak-client-forte-drop** + **keycloak-client-forte-drop-mcp** — labeled config Secrets; the registrar creates the OIDC clients in the `forte` realm within ~2 min.
- **forte-drop-secrets** SealedSecret — UpCloud S3 creds (existing drops bucket) + PG creds + PASSWORD_GATE_SECRET. Consumed by both deployments + the pg-backup CronJob.
- **forte-drop-web PDB** — minAvailable 1 (selector verified against the live forteapp chart's pod labels).
- Wired into `apps/overlays/upc-dev` (NOT base → stays out of upc-prod).

## Post-merge manual step (one-time)

`auth-oidc` SealedSecret for the web sidecar is still commented out — it needs the `client-secret` the Keycloak registrar writes to `forte-drop-oidc-credentials` after first sync:

```bash
CLIENT_SECRET=$(kubectl -n forte-drop get secret forte-drop-oidc-credentials -o jsonpath='{.data.client-secret}' | base64 -d)
kubectl create secret generic auth-oidc -n forte-drop \
  --from-literal=client-secret="$CLIENT_SECRET" \
  --from-literal=cookie-secret="$(openssl rand -hex 32)" \
  --dry-run=client -o yaml > private/auth-oidc.yaml
kubeseal --format=yaml --controller-name=sealed-secrets-controller --controller-namespace=kube-system \
  < private/auth-oidc.yaml > apps/base/forte-drop/auth-oidc-sealed.yaml
# uncomment in kustomization, commit, push
```

## Depends on

- launchpad PR #17 (postgres + namespace via CreateNamespace).
- helm-prod-values forte-drop PR (values).

## Review

- [x] codex: namespace first-sync race → fixed (explicit namespace, sync-wave -1).
- [x] Keycloak registrar unblocked (stale chibisafe/minio config secrets removed; registrar green).

🤖 Generated with Claude Code

Co-authored-by: Sten <sten@Sten-sin-MacBook-Pro.local>
Co-authored-by: Sten <sten@Mac.domain_not_set.invalid>
Co-authored-by: Danijel Simeunovic <danijel.simeunovic@fortedigital.com>
Reviewed-on: #18
Reviewed-by: Danijel Simeunovic <danijel.simeunovic@fortedigital.com>
2026-06-04 18:47:08 +00:00
danijel.simeunovic dffb9c43f0 dbunk delete 2026-06-03 20:16:37 +02:00
danijel.simeunovic 33f0463c1f upc dev spec 2026-06-03 20:14:21 +02:00
danijel.simeunovic a997a6b81e kc cleanup 2026-06-03 17:41:10 +02:00
danijel.simeunovic 071f57f1d3 kc cleanup 2026-06-03 17:39:02 +02:00
danijel.simeunovic ecf871f0e4 kc fix 2026-06-03 17:36:29 +02:00
danijel.simeunovic 376d81a5ac keycloak client cleanup 2026-06-03 17:28:08 +02:00
danijel.simeunovic 428de7af78 tofu config and docs 2026-05-31 20:48:25 +02:00
danijel.simeunovic 24c59256c9 tofu+tools 2026-05-31 19:53:26 +02:00
danijel.simeunovic e319295f62 bunker host 2026-05-29 22:06:08 +02:00
danijel.simeunovic a7106bc8f4 new tls wildcard 2026-05-29 21:58:34 +02:00
danijel.simeunovic 6d874111da tenantID 2026-05-29 21:51:27 +02:00
danijel.simeunovic a8cc103e4c dns01 2026-05-29 21:48:32 +02:00
Ghostdanijel.simeunovicGhost <>
a9dbaf5354 feature/tofu (#15)
@thomas.solbjor her er "import" av tofu fra ditt repo med justeringer for å tilpasse patterns her. Også minimalisert til å kun opprette cluster, ingen managed services som postgres etc. Ta en titt.

Co-authored-by: Danijel Simeunovic <danijel.simeunovic@fortedigital.com>
Reviewed-on: #15
Reviewed-by: Danijel Simeunovic <danijel.simeunovic@fortedigital.com>
Co-authored-by: Ghost <>
Co-committed-by: Ghost <>
2026-05-29 15:48:28 +00:00
danijel.simeunovic 6e175e9e8c docs 2026-05-29 15:20:51 +02:00
jorgen.stensrudandSten 396c771f59 feat(homepage): list forte_drop in Apps (#16)
Adds forte_drop as an external service entry in the upc-dev Homepage portal.

- Target host: https://drop.hackathon.forteapps.net (current Coolify deploy).
- One-line addition under `services > Apps` in `infra/values/upc-dev/homepage-values.yaml`.
- Will be retargeted to https://drop.forteapps.net once the K8s migration ships (spec in forte_drop repo: docs/superpowers/specs/2026-05-28-k8s-migration-design.md).

Zero risk — pure metadata, no cluster mutation beyond Homepage refresh.

Co-authored-by: Sten <sten@Mac.domain_not_set.invalid>
Reviewed-on: #16
Reviewed-by: Danijel Simeunovic <danijel.simeunovic@fortedigital.com>
2026-05-28 14:04:05 +00:00
danijel.simeunovic 0582cd9917 policy 2026-05-27 23:23:21 +02:00
danijel.simeunovic c49d03d7f7 onlySSO 2026-05-16 23:04:11 +02:00
danijel.simeunovic d47dba2ae5 signups 2026-05-16 22:12:04 +02:00
danijel.simeunovic cf9eb47ecf script fix 2026-05-16 22:08:56 +02:00
danijel.simeunovic 3eca723f05 diffs 2026-05-16 22:05:02 +02:00
danijel.simeunovic f36996da11 script fix 2026-05-16 21:57:44 +02:00
danijel.simeunovic 6bf7db21d0 registrar error 2026-05-16 21:55:44 +02:00
danijel.simeunovic 2641d55784 scopes 2026-05-16 21:53:36 +02:00
danijel.simeunovic 117297effc sso vw 2026-05-16 21:47:59 +02:00
danijel.simeunovic fda90f9e01 adminToken enc 2026-05-16 21:34:34 +02:00
danijel.simeunovic 1124377d97 adminToken 2026-05-16 21:29:14 +02:00
danijel.simeunovic c0710b89bb no signup 2026-05-16 21:15:38 +02:00
danijel.simeunovic d7bda18aea domain 2026-05-16 21:11:17 +02:00
danijel.simeunovic 2796e1b9d3 name 2026-05-16 21:09:04 +02:00
danijel.simeunovic d7a0c26117 icon 2026-05-16 21:08:36 +02:00
danijel.simeunovic 693f2f9168 homepage 2026-05-16 21:07:29 +02:00
danijel.simeunovic 2509ef062c domain restriction 2026-05-16 20:58:00 +02:00
danijel.simeunovic 957757e557 host 2026-05-16 20:51:44 +02:00
danijel.simeunovic 070799da05 bitw 2026-05-16 20:49:25 +02:00
danijel.simeunovic 1a2817e537 domain fix 2026-05-16 20:42:17 +02:00
danijel.simeunovic b47b0035f5 smtp auth 2026-05-16 20:38:21 +02:00
danijel.simeunovic d3fac4d43e smtp port 2026-05-16 20:34:22 +02:00
danijel.simeunovic c37bd3ef04 from 2026-05-16 20:30:32 +02:00
danijel.simeunovic ad661ba3dd allow signup 2026-05-16 20:27:36 +02:00
danijel.simeunovic a9625f96e6 db secrets 2026-05-16 20:23:58 +02:00
danijel.simeunovic cb64edc927 cleanup 2026-05-16 20:18:48 +02:00
danijel.simeunovic ac1c242fb9 kust 2026-05-16 20:17:14 +02:00
danijel.simeunovic 4b29c07fd6 secret 2026-05-16 20:15:37 +02:00
danijel.simeunovic 52732626e5 ignorediffs 2026-05-16 20:10:19 +02:00
danijel.simeunovic 8634436dd4 StatefulSet 2026-05-16 20:07:17 +02:00
danijel.simeunovic a8baa169e9 secrets vw 2026-05-16 20:00:22 +02:00
danijel.simeunovic 73ef3a6e12 pg fix 2026-05-16 19:49:38 +02:00
danijel.simeunovic 302705d374 icon 2026-05-16 19:45:19 +02:00
danijel.simeunovic f3286ef77e homepage vw 2026-05-16 19:44:17 +02:00
danijel.simeunovic 74f4f86770 vw apps 2026-05-16 19:34:42 +02:00
danijel.simeunovic f2c56156bf vw postgres 2026-05-16 18:10:14 +02:00
danijel.simeunovic 21fb50ba00 vw fixes 2026-05-16 15:55:18 +02:00
danijel.simeunovic b90b630b06 comment 2026-05-16 15:52:10 +02:00
danijel.simeunovic 66de9b8a0a replicas 2026-05-16 15:48:13 +02:00
danijel.simeunovic 716c552be9 ns 2026-05-16 15:44:04 +02:00
danijel.simeunovic f048b47a0f vaultwarden 2026-05-16 15:39:55 +02:00
danijel.simeunovic 66f40427ee mappings 2026-05-15 15:47:25 +02:00
danijel.simeunovic 332881cbd0 fix 2026-05-14 23:47:14 +02:00
danijel.simeunovic f363afa087 browser flow override 2026-05-14 23:43:40 +02:00
danijel.simeunovic bc42347cb6 gitea+ACCOUNT_LINKING 2026-05-14 21:30:53 +02:00
danijel.simeunovic 80d7bff4bc groups 2026-05-14 21:18:17 +02:00
danijel.simeunovic 3644a3ec87 mappers 2026-05-14 21:14:57 +02:00
danijel.simeunovic bd478478f1 fix attemt 2026-05-14 20:40:44 +02:00
danijel.simeunovic 67b1d95509 account linking 2026-05-14 19:39:38 +02:00
danijel.simeunovic fff95d98a5 remove protocol mappers 2026-05-13 23:15:28 +02:00
danijel.simeunovic 8b743efa43 KC fix 2026-05-13 23:13:09 +02:00
172 changed files with 4955 additions and 1443 deletions
+6 -3
View File
@@ -23,7 +23,7 @@ jobs:
REVIEW__INLINE_COMMENT_FALLBACK: "false"
# LLM configuration
LLM__PROVIDER: CLAUDE
LLM__META__MODEL: claude-sonnet-4-20250514
LLM__META__MODEL: claude-3-opus
LLM__META__MAX_TOKENS: "4096"
LLM__HTTP_CLIENT__API_URL: https://api.anthropic.com
LLM__HTTP_CLIENT__API_TOKEN: ${{ secrets.ANTHROPIC_API_KEY }}
@@ -36,12 +36,15 @@ jobs:
fetch-depth: 0
token: ${{ secrets.AI_REVIEW_TOKEN }}
- name: Update submodules to remote
run: git submodule update --remote --merge
- name: Run inline review
uses: docker://nikitafilonov/ai-review:v0.64.0
uses: docker://nikitafilonov/ai-review:v0.77.0
with:
args: ai-review run-inline
- name: Run summary review
uses: docker://nikitafilonov/ai-review:v0.64.0
uses: docker://nikitafilonov/ai-review:v0.77.0
with:
args: ai-review run-summary
+20
View File
@@ -0,0 +1,20 @@
on:
push:
branches:
- main
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install TruffleHog
run: |
curl -sSfL https://raw.githubusercontent.com/trufflesecurity/trufflehog/main/scripts/install.sh \
| sh -s -- -b /usr/local/bin
- name: Secret Scanning
run: trufflehog git file://. --fail --no-update --results=verified,unknown
+8
View File
@@ -16,3 +16,11 @@ devbox.d/
devbox.lock
.devbox/
bash.exe.stackdump
# OpenTofu
.tofu/configs/*.env
.tofu/scripts/*.config
.tofu/platforms/**/.terraform/
.tofu/platforms/**/terraform.tfstate*
.tofu/platforms/**/tfplan
.tofu/platforms/**/.terraform.lock.hcl
+2
View File
@@ -1,3 +1,5 @@
[submodule "shared-prompts"]
path = shared-prompts
url = https://git.forteapps.net/Forte/ai-review-prompts.git
branch = main
+9
View File
@@ -0,0 +1,9 @@
# Azure AKS credentials — copy to aks.env and fill in values
# NEVER commit aks.env to git!
# Required
AZURE_TENANT_ID=your-azure-tenant-id
AZURE_SUBSCRIPTION_ID=your-azure-subscription-id
# Optional — defaults to cluster name if not set
ARM_RESOURCE_GROUP=
+10
View File
@@ -0,0 +1,10 @@
# AWS EKS credentials — copy to eks.env and fill in values
# NEVER commit eks.env to git!
# Required — AWS CLI profile or access key
AWS_PROFILE=default
AWS_REGION=eu-west-1
# Optional — override with explicit keys instead of profile
# AWS_ACCESS_KEY_ID=
# AWS_SECRET_ACCESS_KEY=
+9
View File
@@ -0,0 +1,9 @@
# GCP GKE credentials — copy to gke.env and fill in values
# NEVER commit gke.env to git!
# Required
GCP_PROJECT_ID=your-gcp-project-id
GCP_REGION=europe-west4
# Optional — path to service account JSON key (if not using gcloud auth)
# GOOGLE_APPLICATION_CREDENTIALS=/path/to/sa-key.json
+8
View File
@@ -0,0 +1,8 @@
# UpCloud credentials — copy to upc.env and fill in values
# NEVER commit upc.env to git!
# Required
UPCLOUD_TOKEN=your-upcloud-api-token
# Optional — set after cluster creation for kubeconfig retrieval
UPCLOUD_CLUSTER_ID=
+18
View File
@@ -0,0 +1,18 @@
module "cluster" {
source = "../modules/cluster"
prefix = "clst-dev"
location = "norwayeast"
resource_group_name = "clst-dev-rg"
# AKS — small dev nodes
aks_node_vm_size = "Standard_B2s"
aks_node_count = 2
enable_delete_lock = false
tags = {
Environment = "dev"
ManagedBy = "tofu"
}
}
+26
View File
@@ -0,0 +1,26 @@
# ─── Cluster ─────────────────────────────────────────────────────────
output "cluster_name" {
value = module.cluster.cluster_name
}
output "resource_group_name" {
value = module.cluster.resource_group_name
}
output "kubernetes_version" {
value = module.cluster.kubernetes_version
}
output "location" {
value = module.cluster.location
}
output "oidc_issuer_url" {
value = module.cluster.oidc_issuer_url
}
output "kubeconfig" {
value = module.cluster.kubeconfig
sensitive = true
}
+17
View File
@@ -0,0 +1,17 @@
terraform {
required_version = ">= 1.0"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 4.0"
}
}
}
provider "azurerm" {
features {}
# Credentials via environment variables:
# ARM_SUBSCRIPTION_ID, ARM_TENANT_ID, ARM_CLIENT_ID, ARM_CLIENT_SECRET
# Or: az login (uses your Azure CLI session)
}
@@ -0,0 +1,72 @@
# Current Azure/Entra ID context — provides tenant_id used in outputs
data "azurerm_client_config" "current" {}
# ─── Resource Group ───────────────────────────────────────────────────
resource "azurerm_resource_group" "main" {
name = var.resource_group_name
location = var.location
tags = var.tags
}
resource "azurerm_management_lock" "main" {
count = var.enable_delete_lock ? 1 : 0
name = "${var.prefix}-delete-lock"
scope = azurerm_resource_group.main.id
lock_level = "CanNotDelete"
notes = "Prevents accidental deletion of production resources"
}
# ─── Networking ───────────────────────────────────────────────────────
resource "azurerm_virtual_network" "main" {
name = "${var.prefix}-vnet"
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
address_space = [var.vnet_address_space]
tags = var.tags
}
# AKS nodes subnet
resource "azurerm_subnet" "aks" {
name = "${var.prefix}-aks-subnet"
resource_group_name = azurerm_resource_group.main.name
virtual_network_name = azurerm_virtual_network.main.name
address_prefixes = [var.aks_subnet_cidr]
}
# ─── AKS Cluster ──────────────────────────────────────────────────────
resource "azurerm_kubernetes_cluster" "main" {
name = "${var.prefix}-aks"
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
dns_prefix = replace(var.prefix, "-", "")
kubernetes_version = var.aks_kubernetes_version
tags = var.tags
default_node_pool {
name = "system"
node_count = var.aks_node_count
vm_size = var.aks_node_vm_size
vnet_subnet_id = azurerm_subnet.aks.id
node_labels = {
prefix = var.prefix
role = "worker"
env = lookup(var.tags, "Environment", "dev")
}
}
identity {
type = "SystemAssigned"
}
network_profile {
network_plugin = "azure"
network_policy = "azure"
}
# Enable Workload Identity for keyless Azure service access (MSI)
oidc_issuer_enabled = true
workload_identity_enabled = true
}
@@ -0,0 +1,32 @@
# ─── Cluster ─────────────────────────────────────────────────────────
output "cluster_name" {
description = "AKS cluster name"
value = azurerm_kubernetes_cluster.main.name
}
output "resource_group_name" {
description = "Resource group name"
value = azurerm_resource_group.main.name
}
output "kubernetes_version" {
description = "Kubernetes version"
value = azurerm_kubernetes_cluster.main.kubernetes_version
}
output "location" {
description = "Azure region"
value = azurerm_resource_group.main.location
}
output "oidc_issuer_url" {
description = "AKS OIDC issuer URL (for workload identity federation)"
value = azurerm_kubernetes_cluster.main.oidc_issuer_url
}
output "kubeconfig" {
description = "Kubeconfig for the AKS cluster"
value = azurerm_kubernetes_cluster.main.kube_config_raw
sensitive = true
}
@@ -0,0 +1,18 @@
terraform {
required_version = ">= 1.0"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 4.0"
}
azuread = {
source = "hashicorp/azuread"
version = "~> 3.0"
}
random = {
source = "hashicorp/random"
version = "~> 3.0"
}
}
}
@@ -0,0 +1,56 @@
# ─── Cluster ─────────────────────────────────────────────────────────
variable "prefix" {
description = "Prefix for resource names"
type = string
}
variable "location" {
description = "Azure region (e.g., norwayeast, westeurope, northeurope)"
type = string
}
variable "resource_group_name" {
description = "Name of the Azure Resource Group to create"
type = string
}
variable "vnet_address_space" {
description = "Address space for the virtual network"
type = string
default = "10.100.0.0/16"
}
variable "aks_subnet_cidr" {
description = "CIDR block for the AKS node subnet"
type = string
default = "10.100.0.0/22"
}
variable "aks_node_vm_size" {
description = "VM size for AKS worker nodes (e.g., Standard_B2s, Standard_D4s_v3)"
type = string
}
variable "aks_node_count" {
description = "Number of AKS worker nodes"
type = number
}
variable "aks_kubernetes_version" {
description = "Kubernetes version for AKS (null = latest stable)"
type = string
default = null
}
variable "enable_delete_lock" {
description = "Protect the resource group from accidental deletion"
type = bool
default = false
}
variable "tags" {
description = "Tags applied to all resources"
type = map(string)
default = {}
}
+18
View File
@@ -0,0 +1,18 @@
module "cluster" {
source = "../modules/cluster"
prefix = "clst"
location = "westeurope"
resource_group_name = "clst-prod-rg"
# AKS — general-purpose nodes for production
aks_node_vm_size = "Standard_D4s_v3"
aks_node_count = 3
enable_delete_lock = true
tags = {
Environment = "prod"
ManagedBy = "tofu"
}
}
+26
View File
@@ -0,0 +1,26 @@
# ─── Cluster ─────────────────────────────────────────────────────────
output "cluster_name" {
value = module.cluster.cluster_name
}
output "resource_group_name" {
value = module.cluster.resource_group_name
}
output "kubernetes_version" {
value = module.cluster.kubernetes_version
}
output "location" {
value = module.cluster.location
}
output "oidc_issuer_url" {
value = module.cluster.oidc_issuer_url
}
output "kubeconfig" {
value = module.cluster.kubeconfig
sensitive = true
}
+17
View File
@@ -0,0 +1,17 @@
terraform {
required_version = ">= 1.0"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 4.0"
}
}
}
provider "azurerm" {
features {}
# Credentials via environment variables:
# ARM_SUBSCRIPTION_ID, ARM_TENANT_ID, ARM_CLIENT_ID, ARM_CLIENT_SECRET
# Or: az login (uses your Azure CLI session)
}
+173
View File
@@ -0,0 +1,173 @@
# =============================================================================
# Azure Workload Cluster
# =============================================================================
# A lean AKS cluster for running application workloads. No managed data
# services — those live on the platform cluster. ArgoCD (on the platform
# cluster) deploys apps to this cluster via the app-of-apps pattern.
#
# Platform components deployed by deploy-workload.sh:
# nginx-ingress, cert-manager, external-dns, external-secrets, alloy
#
# Usage:
# tofu init && tofu plan && tofu apply
# ./sync-tofu-outputs.sh --env azure-workload
# ./deploy-workload.sh --env azure-workload
# =============================================================================
variable "prefix" {
description = "Prefix for resource names (e.g., clst-workload)"
type = string
default = "clst-workload"
}
variable "location" {
description = "Azure region"
type = string
default = "norwayeast"
}
variable "resource_group_name" {
description = "Name of the Azure Resource Group to create"
type = string
default = "clst-workload-rg"
}
variable "vnet_address_space" {
description = "Address space for the virtual network"
type = string
default = "10.110.0.0/16"
}
variable "aks_subnet_cidr" {
description = "CIDR block for the AKS node subnet"
type = string
default = "10.110.0.0/22"
}
variable "aks_node_vm_size" {
description = "VM size for AKS worker nodes"
type = string
default = "Standard_B2s"
}
variable "aks_node_count" {
description = "Number of AKS worker nodes"
type = number
default = 2
}
variable "aks_kubernetes_version" {
description = "Kubernetes version for AKS (null = latest stable)"
type = string
default = null
}
variable "domain" {
description = "Public domain name — must have an existing Azure DNS zone"
type = string
}
variable "dns_zone_resource_group" {
description = "Resource group containing the Azure DNS zone (defaults to cluster RG)"
type = string
default = ""
}
variable "tags" {
description = "Tags applied to all resources"
type = map(string)
default = {
Environment = "workload"
ManagedBy = "tofu"
}
}
# ─── Resource Group ───────────────────────────────────────────────────
resource "azurerm_resource_group" "main" {
name = var.resource_group_name
location = var.location
tags = var.tags
}
# ─── Networking ───────────────────────────────────────────────────────
resource "azurerm_virtual_network" "main" {
name = "${var.prefix}-vnet"
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
address_space = [var.vnet_address_space]
tags = var.tags
}
resource "azurerm_subnet" "aks" {
name = "${var.prefix}-aks-subnet"
resource_group_name = azurerm_resource_group.main.name
virtual_network_name = azurerm_virtual_network.main.name
address_prefixes = [var.aks_subnet_cidr]
}
# ─── AKS Cluster ──────────────────────────────────────────────────────
resource "azurerm_kubernetes_cluster" "main" {
name = "${var.prefix}-aks"
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
dns_prefix = replace(var.prefix, "-", "")
kubernetes_version = var.aks_kubernetes_version
tags = var.tags
default_node_pool {
name = "system"
node_count = var.aks_node_count
vm_size = var.aks_node_vm_size
vnet_subnet_id = azurerm_subnet.aks.id
node_labels = {
prefix = var.prefix
role = "worker"
env = lookup(var.tags, "Environment", "workload")
}
}
identity {
type = "SystemAssigned"
}
network_profile {
network_plugin = "azure"
network_policy = "azure"
}
oidc_issuer_enabled = true
workload_identity_enabled = true
}
# ─── External-DNS Workload Identity ──────────────────────────────────
# Allows external-dns to manage Azure DNS records for app ingresses.
data "azurerm_dns_zone" "main" {
name = var.domain
resource_group_name = var.dns_zone_resource_group != "" ? var.dns_zone_resource_group : azurerm_resource_group.main.name
}
resource "azurerm_user_assigned_identity" "external_dns" {
name = "${var.prefix}-external-dns-identity"
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
tags = var.tags
}
resource "azurerm_role_assignment" "external_dns_dns_contributor" {
scope = data.azurerm_dns_zone.main.id
role_definition_name = "DNS Zone Contributor"
principal_id = azurerm_user_assigned_identity.external_dns.principal_id
}
resource "azurerm_federated_identity_credential" "external_dns" {
name = "${var.prefix}-external-dns-fedcred"
resource_group_name = azurerm_resource_group.main.name
parent_id = azurerm_user_assigned_identity.external_dns.id
audience = ["api://AzureADTokenExchange"]
issuer = azurerm_kubernetes_cluster.main.oidc_issuer_url
subject = "system:serviceaccount:external-dns:external-dns"
}
+4
View File
@@ -0,0 +1,4 @@
output "cluster_name" { value = azurerm_kubernetes_cluster.main.name }
output "resource_group_name" { value = azurerm_resource_group.main.name }
output "location" { value = azurerm_resource_group.main.location }
output "external_dns_identity_client_id" { value = azurerm_user_assigned_identity.external_dns.client_id }
+21
View File
@@ -0,0 +1,21 @@
terraform {
required_version = ">= 1.0"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 4.0"
}
random = {
source = "hashicorp/random"
version = "~> 3.0"
}
}
}
provider "azurerm" {
features {}
# Credentials via environment variables:
# ARM_SUBSCRIPTION_ID, ARM_TENANT_ID, ARM_CLIENT_ID, ARM_CLIENT_SECRET
# Or: az login (uses your Azure CLI session)
}
+21
View File
@@ -0,0 +1,21 @@
module "cluster" {
source = "../modules/cluster"
region = var.region
prefix = "clst-dev"
# VPC
availability_zones = ["${var.region}a", "${var.region}b"]
# EKS — small dev nodes
node_instance_type = "t3.medium"
node_count = 2
node_min_count = 1
node_max_count = 4
kubernetes_version = "1.30"
tags = {
Environment = "dev"
ManagedBy = "tofu"
}
}
+5
View File
@@ -0,0 +1,5 @@
output "cluster_name" { value = module.cluster.cluster_name }
output "aws_region" { value = module.cluster.aws_region }
output "oidc_issuer_url" { value = module.cluster.oidc_issuer_url }
output "oidc_provider_arn" { value = module.cluster.oidc_provider_arn }
output "vpc_id" { value = module.cluster.vpc_id }
+24
View File
@@ -0,0 +1,24 @@
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
tls = {
source = "hashicorp/tls"
version = "~> 4.0"
}
}
}
# Authentication: set AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN
# or configure an AWS profile: export AWS_PROFILE=clst
provider "aws" {
region = var.region
}
variable "region" {
description = "AWS region for dev environment"
type = string
default = "eu-west-1"
}
+207
View File
@@ -0,0 +1,207 @@
# ─── VPC ──────────────────────────────────────────────────────────────
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
enable_dns_support = true
tags = merge(var.tags, { Name = "${var.prefix}-vpc" })
}
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = merge(var.tags, { Name = "${var.prefix}-igw" })
}
# Public subnets (one per AZ) — for NAT gateways and load balancers
resource "aws_subnet" "public" {
count = length(var.availability_zones)
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 4, count.index)
availability_zone = var.availability_zones[count.index]
map_public_ip_on_launch = true
tags = merge(var.tags, {
Name = "${var.prefix}-public-${count.index + 1}"
"kubernetes.io/cluster/${var.prefix}-eks" = "shared"
"kubernetes.io/role/elb" = "1"
})
}
# Private subnets (one per AZ) — for EKS nodes
resource "aws_subnet" "private" {
count = length(var.availability_zones)
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 4, count.index + length(var.availability_zones))
availability_zone = var.availability_zones[count.index]
tags = merge(var.tags, {
Name = "${var.prefix}-private-${count.index + 1}"
"kubernetes.io/cluster/${var.prefix}-eks" = "shared"
"kubernetes.io/role/internal-elb" = "1"
})
}
# NAT Gateway (single, in first public subnet — use one per AZ for prod HA)
resource "aws_eip" "nat" {
domain = "vpc"
tags = merge(var.tags, { Name = "${var.prefix}-nat-eip" })
}
resource "aws_nat_gateway" "main" {
allocation_id = aws_eip.nat.id
subnet_id = aws_subnet.public[0].id
tags = merge(var.tags, { Name = "${var.prefix}-nat" })
depends_on = [aws_internet_gateway.main]
}
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.main.id
}
tags = merge(var.tags, { Name = "${var.prefix}-public-rt" })
}
resource "aws_route_table_association" "public" {
count = length(var.availability_zones)
subnet_id = aws_subnet.public[count.index].id
route_table_id = aws_route_table.public.id
}
resource "aws_route_table" "private" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.main.id
}
tags = merge(var.tags, { Name = "${var.prefix}-private-rt" })
}
resource "aws_route_table_association" "private" {
count = length(var.availability_zones)
subnet_id = aws_subnet.private[count.index].id
route_table_id = aws_route_table.private.id
}
# ─── EKS Cluster ──────────────────────────────────────────────────────
resource "aws_iam_role" "eks_cluster" {
name_prefix = "${var.prefix}-eks-cluster-"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = { Service = "eks.amazonaws.com" }
}]
})
tags = var.tags
}
resource "aws_iam_role_policy_attachment" "eks_cluster_policy" {
policy_arn = "arn:aws:iam::aws:policy/AmazonEKSClusterPolicy"
role = aws_iam_role.eks_cluster.name
}
resource "aws_eks_cluster" "main" {
name = "${var.prefix}-eks"
role_arn = aws_iam_role.eks_cluster.arn
version = var.kubernetes_version
vpc_config {
subnet_ids = concat(aws_subnet.private[*].id, aws_subnet.public[*].id)
endpoint_private_access = true
endpoint_public_access = true
}
# Enable OIDC issuer for IRSA (IAM Roles for Service Accounts)
access_config {
authentication_mode = "API_AND_CONFIG_MAP"
}
tags = var.tags
depends_on = [aws_iam_role_policy_attachment.eks_cluster_policy]
}
# OIDC provider — required for IRSA (IAM Roles for Service Accounts)
data "tls_certificate" "eks" {
url = aws_eks_cluster.main.identity[0].oidc[0].issuer
}
resource "aws_iam_openid_connect_provider" "eks" {
client_id_list = ["sts.amazonaws.com"]
thumbprint_list = [data.tls_certificate.eks.certificates[0].sha1_fingerprint]
url = aws_eks_cluster.main.identity[0].oidc[0].issuer
tags = var.tags
}
# EKS Node Group
resource "aws_iam_role" "eks_nodes" {
name_prefix = "${var.prefix}-eks-nodes-"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = { Service = "ec2.amazonaws.com" }
}]
})
tags = var.tags
}
resource "aws_iam_role_policy_attachment" "eks_worker_node_policy" {
policy_arn = "arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy"
role = aws_iam_role.eks_nodes.name
}
resource "aws_iam_role_policy_attachment" "eks_cni_policy" {
policy_arn = "arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy"
role = aws_iam_role.eks_nodes.name
}
resource "aws_iam_role_policy_attachment" "eks_ecr_readonly" {
policy_arn = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly"
role = aws_iam_role.eks_nodes.name
}
resource "aws_eks_node_group" "main" {
cluster_name = aws_eks_cluster.main.name
node_group_name = "${var.prefix}-nodes"
node_role_arn = aws_iam_role.eks_nodes.arn
subnet_ids = aws_subnet.private[*].id
instance_types = [var.node_instance_type]
scaling_config {
desired_size = var.node_count
max_size = var.node_max_count
min_size = var.node_min_count
}
update_config {
max_unavailable = 1
}
tags = var.tags
depends_on = [
aws_iam_role_policy_attachment.eks_worker_node_policy,
aws_iam_role_policy_attachment.eks_cni_policy,
aws_iam_role_policy_attachment.eks_ecr_readonly,
]
}
@@ -0,0 +1,26 @@
# ─── Cluster ─────────────────────────────────────────────────────────
output "cluster_name" {
description = "EKS cluster name"
value = aws_eks_cluster.main.name
}
output "aws_region" {
description = "AWS region"
value = var.region
}
output "oidc_issuer_url" {
description = "EKS OIDC issuer URL (for IRSA)"
value = aws_eks_cluster.main.identity[0].oidc[0].issuer
}
output "oidc_provider_arn" {
description = "IAM OIDC provider ARN (for IRSA trust policies)"
value = aws_iam_openid_connect_provider.eks.arn
}
output "vpc_id" {
description = "VPC ID"
value = aws_vpc.main.id
}
@@ -0,0 +1,12 @@
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
tls = {
source = "hashicorp/tls"
version = "~> 4.0"
}
}
}
@@ -0,0 +1,61 @@
# ─── Region ──────────────────────────────────────────────────────────
variable "region" {
description = "AWS region (e.g., eu-west-1, us-east-1)"
type = string
}
variable "prefix" {
description = "Prefix for resource names (e.g., clst-dev)"
type = string
}
# ─── Networking ───────────────────────────────────────────────────────
variable "vpc_cidr" {
description = "VPC CIDR block"
type = string
default = "10.100.0.0/16"
}
variable "availability_zones" {
description = "List of AZs for subnets (2–3 recommended)"
type = list(string)
}
# ─── EKS Cluster ─────────────────────────────────────────────────────
variable "node_instance_type" {
description = "EKS node instance type (e.g., t3.medium, m5.xlarge)"
type = string
}
variable "node_count" {
description = "Desired number of EKS worker nodes"
type = number
}
variable "node_min_count" {
description = "Minimum number of EKS worker nodes"
type = number
default = 1
}
variable "node_max_count" {
description = "Maximum number of EKS worker nodes"
type = number
}
variable "kubernetes_version" {
description = "Kubernetes version for EKS (e.g., \"1.30\")"
type = string
default = "1.30"
}
# ─── Tags ─────────────────────────────────────────────────────────────
variable "tags" {
description = "Tags applied to all resources"
type = map(string)
default = {}
}
+21
View File
@@ -0,0 +1,21 @@
module "cluster" {
source = "../modules/cluster"
region = var.region
prefix = "clst"
# VPC
availability_zones = ["${var.region}a", "${var.region}b", "${var.region}c"]
# EKS — general-purpose nodes for production
node_instance_type = "m5.xlarge"
node_count = 3
node_min_count = 3
node_max_count = 6
kubernetes_version = "1.30"
tags = {
Environment = "prod"
ManagedBy = "tofu"
}
}
+5
View File
@@ -0,0 +1,5 @@
output "cluster_name" { value = module.cluster.cluster_name }
output "aws_region" { value = module.cluster.aws_region }
output "oidc_issuer_url" { value = module.cluster.oidc_issuer_url }
output "oidc_provider_arn" { value = module.cluster.oidc_provider_arn }
output "vpc_id" { value = module.cluster.vpc_id }
+22
View File
@@ -0,0 +1,22 @@
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
tls = {
source = "hashicorp/tls"
version = "~> 4.0"
}
}
}
provider "aws" {
region = var.region
}
variable "region" {
description = "AWS region for prod environment"
type = string
default = "eu-west-1"
}
+339
View File
@@ -0,0 +1,339 @@
# =============================================================================
# AWS Workload Cluster
# =============================================================================
# A lean EKS cluster for running application workloads. No managed data
# services — those live on the platform cluster. ArgoCD (on the platform
# cluster) deploys apps to this cluster via the app-of-apps pattern.
#
# Platform components deployed by deploy-workload.sh:
# nginx-ingress, cert-manager, external-dns, external-secrets, alloy
#
# Usage:
# tofu init && tofu plan && tofu apply
# ./sync-tofu-outputs.sh --env aws-workload
# ./deploy-workload.sh --env aws-workload
# =============================================================================
variable "prefix" {
description = "Prefix for resource names (e.g., clst-workload)"
type = string
default = "clst-workload"
}
variable "availability_zones" {
description = "List of AZs for subnets"
type = list(string)
default = ["eu-west-1a", "eu-west-1b"]
}
variable "vpc_cidr" {
description = "VPC CIDR block"
type = string
default = "10.110.0.0/16"
}
variable "node_instance_type" {
description = "EKS node instance type"
type = string
default = "t3.medium"
}
variable "node_count" {
description = "Desired number of EKS worker nodes"
type = number
default = 2
}
variable "node_min_count" {
description = "Minimum number of EKS worker nodes"
type = number
default = 1
}
variable "node_max_count" {
description = "Maximum number of EKS worker nodes"
type = number
default = 4
}
variable "kubernetes_version" {
description = "Kubernetes version for EKS"
type = string
default = "1.30"
}
variable "domain" {
description = "Public domain name — must have an existing Route53 hosted zone"
type = string
}
variable "tags" {
description = "Tags applied to all resources"
type = map(string)
default = {
Environment = "workload"
ManagedBy = "tofu"
}
}
# ─── VPC ──────────────────────────────────────────────────────────────
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
enable_dns_hostnames = true
enable_dns_support = true
tags = merge(var.tags, { Name = "${var.prefix}-vpc" })
}
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = merge(var.tags, { Name = "${var.prefix}-igw" })
}
resource "aws_subnet" "public" {
count = length(var.availability_zones)
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 4, count.index)
availability_zone = var.availability_zones[count.index]
map_public_ip_on_launch = true
tags = merge(var.tags, {
Name = "${var.prefix}-public-${count.index + 1}"
"kubernetes.io/cluster/${var.prefix}-eks" = "shared"
"kubernetes.io/role/elb" = "1"
})
}
resource "aws_subnet" "private" {
count = length(var.availability_zones)
vpc_id = aws_vpc.main.id
cidr_block = cidrsubnet(var.vpc_cidr, 4, count.index + length(var.availability_zones))
availability_zone = var.availability_zones[count.index]
tags = merge(var.tags, {
Name = "${var.prefix}-private-${count.index + 1}"
"kubernetes.io/cluster/${var.prefix}-eks" = "shared"
"kubernetes.io/role/internal-elb" = "1"
})
}
resource "aws_eip" "nat" {
domain = "vpc"
tags = merge(var.tags, { Name = "${var.prefix}-nat-eip" })
}
resource "aws_nat_gateway" "main" {
allocation_id = aws_eip.nat.id
subnet_id = aws_subnet.public[0].id
tags = merge(var.tags, { Name = "${var.prefix}-nat" })
depends_on = [aws_internet_gateway.main]
}
resource "aws_route_table" "public" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.main.id
}
tags = merge(var.tags, { Name = "${var.prefix}-public-rt" })
}
resource "aws_route_table_association" "public" {
count = length(var.availability_zones)
subnet_id = aws_subnet.public[count.index].id
route_table_id = aws_route_table.public.id
}
resource "aws_route_table" "private" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.main.id
}
tags = merge(var.tags, { Name = "${var.prefix}-private-rt" })
}
resource "aws_route_table_association" "private" {
count = length(var.availability_zones)
subnet_id = aws_subnet.private[count.index].id
route_table_id = aws_route_table.private.id
}
# ─── EKS Cluster ──────────────────────────────────────────────────────
resource "aws_iam_role" "eks_cluster" {
name_prefix = "${var.prefix}-eks-cluster-"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = { Service = "eks.amazonaws.com" }
}]
})
tags = var.tags
}
resource "aws_iam_role_policy_attachment" "eks_cluster_policy" {
policy_arn = "arn:aws:iam::aws:policy/AmazonEKSClusterPolicy"
role = aws_iam_role.eks_cluster.name
}
resource "aws_eks_cluster" "main" {
name = "${var.prefix}-eks"
role_arn = aws_iam_role.eks_cluster.arn
version = var.kubernetes_version
vpc_config {
subnet_ids = concat(aws_subnet.private[*].id, aws_subnet.public[*].id)
endpoint_private_access = true
endpoint_public_access = true
}
access_config {
authentication_mode = "API_AND_CONFIG_MAP"
}
tags = var.tags
depends_on = [aws_iam_role_policy_attachment.eks_cluster_policy]
}
# OIDC provider — required for IRSA
data "tls_certificate" "eks" {
url = aws_eks_cluster.main.identity[0].oidc[0].issuer
}
resource "aws_iam_openid_connect_provider" "eks" {
client_id_list = ["sts.amazonaws.com"]
thumbprint_list = [data.tls_certificate.eks.certificates[0].sha1_fingerprint]
url = aws_eks_cluster.main.identity[0].oidc[0].issuer
tags = var.tags
}
resource "aws_iam_role" "eks_nodes" {
name_prefix = "${var.prefix}-eks-nodes-"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = { Service = "ec2.amazonaws.com" }
}]
})
tags = var.tags
}
resource "aws_iam_role_policy_attachment" "eks_worker_node_policy" {
policy_arn = "arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy"
role = aws_iam_role.eks_nodes.name
}
resource "aws_iam_role_policy_attachment" "eks_cni_policy" {
policy_arn = "arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy"
role = aws_iam_role.eks_nodes.name
}
resource "aws_iam_role_policy_attachment" "eks_ecr_readonly" {
policy_arn = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly"
role = aws_iam_role.eks_nodes.name
}
resource "aws_eks_node_group" "main" {
cluster_name = aws_eks_cluster.main.name
node_group_name = "${var.prefix}-nodes"
node_role_arn = aws_iam_role.eks_nodes.arn
subnet_ids = aws_subnet.private[*].id
instance_types = [var.node_instance_type]
scaling_config {
desired_size = var.node_count
max_size = var.node_max_count
min_size = var.node_min_count
}
update_config {
max_unavailable = 1
}
tags = var.tags
depends_on = [
aws_iam_role_policy_attachment.eks_worker_node_policy,
aws_iam_role_policy_attachment.eks_cni_policy,
aws_iam_role_policy_attachment.eks_ecr_readonly,
]
}
# ─── External-DNS IRSA ───────────────────────────────────────────────
# Allows external-dns to manage Route53 records for app ingresses.
data "aws_route53_zone" "main" {
name = var.domain
private_zone = false
}
data "aws_iam_policy_document" "external_dns_assume_role" {
statement {
effect = "Allow"
principals {
type = "Federated"
identifiers = [aws_iam_openid_connect_provider.eks.arn]
}
actions = ["sts:AssumeRoleWithWebIdentity"]
condition {
test = "StringEquals"
variable = "${replace(aws_iam_openid_connect_provider.eks.url, "https://", "")}:sub"
values = ["system:serviceaccount:external-dns:external-dns"]
}
condition {
test = "StringEquals"
variable = "${replace(aws_iam_openid_connect_provider.eks.url, "https://", "")}:aud"
values = ["sts.amazonaws.com"]
}
}
}
resource "aws_iam_role" "external_dns_irsa" {
name_prefix = "${var.prefix}-external-dns-irsa-"
assume_role_policy = data.aws_iam_policy_document.external_dns_assume_role.json
tags = var.tags
}
data "aws_iam_policy_document" "external_dns_route53" {
statement {
effect = "Allow"
actions = ["route53:ChangeResourceRecordSets"]
resources = ["arn:aws:route53:::hostedzone/${data.aws_route53_zone.main.zone_id}"]
}
statement {
effect = "Allow"
actions = ["route53:ListHostedZones", "route53:ListResourceRecordSets", "route53:ListTagsForResource"]
resources = ["*"]
}
}
resource "aws_iam_role_policy" "external_dns_route53" {
name_prefix = "${var.prefix}-external-dns-route53-"
role = aws_iam_role.external_dns_irsa.id
policy = data.aws_iam_policy_document.external_dns_route53.json
}
+3
View File
@@ -0,0 +1,3 @@
output "cluster_name" { value = aws_eks_cluster.main.name }
output "aws_region" { value = var.region }
output "external_dns_irsa_role_arn" { value = aws_iam_role.external_dns_irsa.arn }
+24
View File
@@ -0,0 +1,24 @@
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
tls = {
source = "hashicorp/tls"
version = "~> 4.0"
}
}
}
# Authentication: set AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_SESSION_TOKEN
# or configure an AWS profile: export AWS_PROFILE=clst
provider "aws" {
region = var.region
}
variable "region" {
description = "AWS region for the workload environment"
type = string
default = "eu-west-1"
}
+17
View File
@@ -0,0 +1,17 @@
module "cluster" {
source = "../modules/cluster"
project_id = var.project_id
region = var.region
prefix = "clst-dev"
# GKE — small dev nodes
node_machine_type = "e2-standard-2"
node_count = 2
deletion_protection = false
labels = {
environment = "dev"
managed-by = "tofu"
}
}
+3
View File
@@ -0,0 +1,3 @@
output "cluster_name" { value = module.cluster.cluster_name }
output "project_id" { value = module.cluster.project_id }
output "region" { value = module.cluster.region }
+26
View File
@@ -0,0 +1,26 @@
terraform {
required_providers {
google = {
source = "hashicorp/google"
version = "~> 6.0"
}
}
}
# Authentication: use Application Default Credentials (gcloud auth application-default login)
# or set GOOGLE_APPLICATION_CREDENTIALS to a service account key file.
provider "google" {
project = var.project_id
region = var.region
}
variable "project_id" {
description = "GCP project ID for the dev environment"
type = string
}
variable "region" {
description = "GCP region"
type = string
default = "europe-west4"
}
+115
View File
@@ -0,0 +1,115 @@
# ─── Required APIs ────────────────────────────────────────────────────
resource "google_project_service" "compute" {
project = var.project_id
service = "compute.googleapis.com"
disable_on_destroy = false
}
resource "google_project_service" "container" {
project = var.project_id
service = "container.googleapis.com"
disable_on_destroy = false
}
# ─── Networking ───────────────────────────────────────────────────────
resource "google_compute_network" "main" {
project = var.project_id
name = "${var.prefix}-vpc"
auto_create_subnetworks = false
depends_on = [google_project_service.compute]
}
resource "google_compute_subnetwork" "main" {
project = var.project_id
name = "${var.prefix}-subnet"
ip_cidr_range = "10.100.0.0/22"
region = var.region
network = google_compute_network.main.id
# Secondary ranges required for GKE VPC-native cluster
secondary_ip_range {
range_name = "pods"
ip_cidr_range = "10.200.0.0/14" # /14 = ~262k pod IPs
}
secondary_ip_range {
range_name = "services"
ip_cidr_range = "10.204.0.0/20" # /20 = ~4k service IPs
}
}
# ─── GKE Cluster ──────────────────────────────────────────────────────
#
# Regional cluster (3 control-plane replicas) for HA.
# Workload Identity enabled — allows K8s service accounts to impersonate
# Google Service Accounts for keyless access to GCP services.
resource "google_container_cluster" "main" {
project = var.project_id
name = "${var.prefix}-gke"
location = var.region # regional cluster
network = google_compute_network.main.id
subnetwork = google_compute_subnetwork.main.id
# VPC-native cluster with alias IP ranges
ip_allocation_policy {
cluster_secondary_range_name = "pods"
services_secondary_range_name = "services"
}
# Workload Identity pool — enables OIDC token projection for pods
workload_identity_config {
workload_pool = "${var.project_id}.svc.id.goog"
}
# Remove default node pool — we manage our own below
remove_default_node_pool = true
initial_node_count = 1
deletion_protection = var.deletion_protection
dynamic "release_channel" {
for_each = var.kubernetes_version == null ? [1] : []
content {
channel = "STABLE"
}
}
resource_labels = var.labels
depends_on = [google_project_service.container]
}
resource "google_container_node_pool" "main" {
project = var.project_id
name = "${var.prefix}-nodes"
location = var.region
cluster = google_container_cluster.main.name
node_count = var.node_count
node_config {
machine_type = var.node_machine_type
# GKE_METADATA mode is required for Workload Identity
workload_metadata_config {
mode = "GKE_METADATA"
}
oauth_scopes = [
"https://www.googleapis.com/auth/cloud-platform",
]
labels = merge(var.labels, {
role = "worker"
})
}
management {
auto_repair = true
auto_upgrade = true
}
}
@@ -0,0 +1,16 @@
# ─── Cluster ─────────────────────────────────────────────────────────
output "cluster_name" {
description = "GKE cluster name"
value = google_container_cluster.main.name
}
output "project_id" {
description = "GCP project ID"
value = var.project_id
}
output "region" {
description = "GCP region"
value = var.region
}
@@ -0,0 +1,8 @@
terraform {
required_providers {
google = {
source = "hashicorp/google"
version = "~> 6.0"
}
}
}
@@ -0,0 +1,48 @@
# ─── Project / Region ────────────────────────────────────────────────
variable "project_id" {
description = "GCP project ID"
type = string
}
variable "region" {
description = "GCP region (e.g., europe-west4, europe-west1)"
type = string
}
variable "prefix" {
description = "Prefix for resource names (e.g., clst-dev)"
type = string
}
# ─── GKE Cluster ─────────────────────────────────────────────────────
variable "node_machine_type" {
description = "GKE node machine type (e.g., e2-standard-2, e2-standard-4)"
type = string
}
variable "node_count" {
description = "Number of nodes per zone (regional cluster spawns nodes in each zone)"
type = number
}
variable "kubernetes_version" {
description = "GKE Kubernetes version channel (null = STABLE release channel)"
type = string
default = null
}
variable "deletion_protection" {
description = "Prevent cluster deletion (set true for production)"
type = bool
default = false
}
# ─── Labels ──────────────────────────────────────────────────────────
variable "labels" {
description = "Labels applied to all resources"
type = map(string)
default = {}
}
+17
View File
@@ -0,0 +1,17 @@
module "cluster" {
source = "../modules/cluster"
project_id = var.project_id
region = var.region
prefix = "clst"
# GKE — general-purpose nodes for production
node_machine_type = "e2-standard-4"
node_count = 3
deletion_protection = true
labels = {
environment = "prod"
managed-by = "tofu"
}
}
+3
View File
@@ -0,0 +1,3 @@
output "cluster_name" { value = module.cluster.cluster_name }
output "project_id" { value = module.cluster.project_id }
output "region" { value = module.cluster.region }
+24
View File
@@ -0,0 +1,24 @@
terraform {
required_providers {
google = {
source = "hashicorp/google"
version = "~> 6.0"
}
}
}
provider "google" {
project = var.project_id
region = var.region
}
variable "project_id" {
description = "GCP project ID for the prod environment"
type = string
}
variable "region" {
description = "GCP region"
type = string
default = "europe-west1"
}
+194
View File
@@ -0,0 +1,194 @@
# =============================================================================
# GCP Workload Cluster
# =============================================================================
# A lean GKE cluster for running application workloads. No managed data
# services — those live on the platform cluster. ArgoCD (on the platform
# cluster) deploys apps to this cluster via the app-of-apps pattern.
#
# Platform components deployed by deploy-workload.sh:
# nginx-ingress, cert-manager, external-dns, external-secrets, alloy
#
# Usage:
# tofu init && tofu plan && tofu apply
# ./sync-tofu-outputs.sh --env gcp-workload
# ./deploy-workload.sh --env gcp-workload
# =============================================================================
variable "prefix" {
description = "Prefix for resource names (e.g., clst-workload)"
type = string
default = "clst-workload"
}
variable "node_machine_type" {
description = "GKE node machine type"
type = string
default = "e2-standard-2"
}
variable "node_count" {
description = "Number of nodes per zone"
type = number
default = 1
}
variable "kubernetes_version" {
description = "GKE Kubernetes version (null = STABLE release channel)"
type = string
default = null
}
variable "deletion_protection" {
description = "Prevent cluster deletion"
type = bool
default = false
}
variable "labels" {
description = "Labels applied to all resources"
type = map(string)
default = {
environment = "workload"
managed-by = "tofu"
}
}
# ─── Required APIs ────────────────────────────────────────────────────
resource "google_project_service" "compute" {
project = var.project_id
service = "compute.googleapis.com"
disable_on_destroy = false
}
resource "google_project_service" "container" {
project = var.project_id
service = "container.googleapis.com"
disable_on_destroy = false
}
resource "google_project_service" "iam" {
project = var.project_id
service = "iam.googleapis.com"
disable_on_destroy = false
}
resource "google_project_service" "dns" {
project = var.project_id
service = "dns.googleapis.com"
disable_on_destroy = false
}
# ─── Networking ───────────────────────────────────────────────────────
resource "google_compute_network" "main" {
project = var.project_id
name = "${var.prefix}-vpc"
auto_create_subnetworks = false
depends_on = [google_project_service.compute]
}
resource "google_compute_subnetwork" "main" {
project = var.project_id
name = "${var.prefix}-subnet"
ip_cidr_range = "10.110.0.0/22"
region = var.region
network = google_compute_network.main.id
secondary_ip_range {
range_name = "pods"
ip_cidr_range = "10.210.0.0/14"
}
secondary_ip_range {
range_name = "services"
ip_cidr_range = "10.214.0.0/20"
}
}
# ─── GKE Cluster ──────────────────────────────────────────────────────
resource "google_container_cluster" "main" {
project = var.project_id
name = "${var.prefix}-gke"
location = var.region
network = google_compute_network.main.id
subnetwork = google_compute_subnetwork.main.id
ip_allocation_policy {
cluster_secondary_range_name = "pods"
services_secondary_range_name = "services"
}
workload_identity_config {
workload_pool = "${var.project_id}.svc.id.goog"
}
remove_default_node_pool = true
initial_node_count = 1
deletion_protection = var.deletion_protection
dynamic "release_channel" {
for_each = var.kubernetes_version == null ? [1] : []
content {
channel = "STABLE"
}
}
resource_labels = var.labels
depends_on = [google_project_service.container]
}
resource "google_container_node_pool" "main" {
project = var.project_id
name = "${var.prefix}-nodes"
location = var.region
cluster = google_container_cluster.main.name
node_count = var.node_count
node_config {
machine_type = var.node_machine_type
workload_metadata_config {
mode = "GKE_METADATA"
}
oauth_scopes = [
"https://www.googleapis.com/auth/cloud-platform",
]
labels = merge(var.labels, { role = "worker" })
}
management {
auto_repair = true
auto_upgrade = true
}
}
# ─── External-DNS Workload Identity ──────────────────────────────────
# Allows external-dns to manage Cloud DNS records for app ingresses.
resource "google_service_account" "external_dns" {
project = var.project_id
account_id = "${var.prefix}-external-dns"
display_name = "External-DNS Service Account (Workload Identity)"
depends_on = [google_project_service.iam]
}
resource "google_project_iam_member" "external_dns_dns_admin" {
project = var.project_id
role = "roles/dns.admin"
member = "serviceAccount:${google_service_account.external_dns.email}"
}
resource "google_service_account_iam_member" "external_dns_workload_identity" {
service_account_id = google_service_account.external_dns.name
role = "roles/iam.workloadIdentityUser"
member = "serviceAccount:${var.project_id}.svc.id.goog[external-dns/external-dns]"
}
+4
View File
@@ -0,0 +1,4 @@
output "cluster_name" { value = google_container_cluster.main.name }
output "project_id" { value = var.project_id }
output "region" { value = var.region }
output "external_dns_gsa_email" { value = google_service_account.external_dns.email }
+26
View File
@@ -0,0 +1,26 @@
terraform {
required_providers {
google = {
source = "hashicorp/google"
version = "~> 6.0"
}
}
}
# Authentication: use Application Default Credentials (gcloud auth application-default login)
# or set GOOGLE_APPLICATION_CREDENTIALS to a service account key file.
provider "google" {
project = var.project_id
region = var.region
}
variable "project_id" {
description = "GCP project ID for the workload environment"
type = string
}
variable "region" {
description = "GCP region"
type = string
default = "europe-west4"
}
+14
View File
@@ -0,0 +1,14 @@
module "cluster" {
source = "../modules/cluster"
prefix = "clst-dev"
zone = "no-svg1"
node_plan = "DEV-1xCPU-2GB"
node_count = 2
network_cidr = "10.100.0.0/24"
tags = {
Environment = "dev"
ManagedBy = "tofu"
}
}
+13
View File
@@ -0,0 +1,13 @@
# ─── Cluster ─────────────────────────────────────────────────────────
output "cluster_id" {
value = module.cluster.cluster_id
}
output "cluster_name" {
value = module.cluster.cluster_name
}
output "zone" {
value = module.cluster.zone
}
+14
View File
@@ -0,0 +1,14 @@
terraform {
required_version = ">= 1.0"
required_providers {
upcloud = {
source = "UpCloudLtd/upcloud"
version = "~> 5.0"
}
}
}
provider "upcloud" {
# Set via environment variables: UPCLOUD_USERNAME, UPCLOUD_PASSWORD
}
@@ -0,0 +1,64 @@
# Router for the private network
resource "upcloud_router" "kubernetes" {
name = "${var.prefix}-${var.cluster_name}-router"
}
# Gateway for internet connectivity
resource "upcloud_gateway" "kubernetes" {
name = "${var.prefix}-${var.cluster_name}-gateway"
zone = var.zone
features = ["nat"]
router {
id = upcloud_router.kubernetes.id
}
}
# Private network for the Kubernetes cluster
resource "upcloud_network" "kubernetes" {
name = "${var.prefix}-${var.cluster_name}-network"
zone = var.zone
router = upcloud_router.kubernetes.id
ip_network {
address = var.network_cidr
dhcp = true
dhcp_default_route = true
family = "IPv4"
gateway = cidrhost(var.network_cidr, 1)
}
depends_on = [upcloud_gateway.kubernetes]
}
# Kubernetes cluster
resource "upcloud_kubernetes_cluster" "main" {
name = "${var.prefix}-${var.cluster_name}"
zone = var.zone
network = upcloud_network.kubernetes.id
control_plane_ip_filter = var.control_plane_ip_filter
private_node_groups = true
}
# Node group for worker nodes
resource "upcloud_kubernetes_node_group" "workers" {
cluster = upcloud_kubernetes_cluster.main.id
name = "${var.prefix}-${var.cluster_name}-workers"
node_count = var.node_count
plan = var.node_plan
anti_affinity = var.node_count > 1
dynamic "cloud_native_plan" {
for_each = var.storage_size != null ? [1] : []
content {
storage_size = var.storage_size
}
}
labels = {
prefix = var.prefix
cluster = var.cluster_name
role = "worker"
env = lookup(var.tags, "Environment", "dev")
}
}
@@ -0,0 +1,31 @@
# ─── Cluster ─────────────────────────────────────────────────────────
output "cluster_id" {
description = "The ID of the Kubernetes cluster"
value = upcloud_kubernetes_cluster.main.id
}
output "cluster_name" {
description = "The name of the Kubernetes cluster"
value = upcloud_kubernetes_cluster.main.name
}
output "network_id" {
description = "The ID of the private network"
value = upcloud_network.kubernetes.id
}
output "network_cidr" {
description = "The CIDR block of the private network"
value = var.network_cidr
}
output "kubernetes_version" {
description = "The Kubernetes version of the cluster"
value = upcloud_kubernetes_cluster.main.version
}
output "zone" {
description = "The zone where the cluster is deployed"
value = var.zone
}
@@ -0,0 +1,8 @@
terraform {
required_providers {
upcloud = {
source = "UpCloudLtd/upcloud"
version = "~> 5.0"
}
}
}
@@ -0,0 +1,50 @@
# ─── Cluster ─────────────────────────────────────────────────────────
variable "prefix" {
description = "Prefix for resource names"
type = string
}
variable "cluster_name" {
description = "Name of the Kubernetes cluster"
type = string
default = "main"
}
variable "zone" {
description = "UpCloud zone"
type = string
}
variable "node_plan" {
description = "UpCloud server plan for worker nodes"
type = string
}
variable "node_count" {
description = "Number of worker nodes"
type = number
}
variable "network_cidr" {
description = "CIDR block for the private network"
type = string
default = "10.100.0.0/24"
}
variable "control_plane_ip_filter" {
description = "CIDRs allowed to access the K8s API"
type = list(string)
default = ["0.0.0.0/0"]
}
variable "storage_size" {
description = "Storage size in GB for worker nodes (overrides plan default via cloud_native_plan block)"
type = number
default = null
}
variable "tags" {
description = "Labels to apply to resources"
type = map(string)
}
+120
View File
@@ -0,0 +1,120 @@
# =============================================================================
# UpCloud Workload Cluster
# =============================================================================
# A lean UCS cluster for running application workloads. No managed data
# services — those live on the platform cluster. ArgoCD (on the platform
# cluster) deploys apps to this cluster via the app-of-apps pattern.
#
# Platform components deployed by deploy-workload.sh:
# nginx-ingress, cert-manager, external-dns, external-secrets, alloy
#
# Usage:
# tofu init && tofu plan && tofu apply
# ./sync-tofu-outputs.sh --env upcloud-workload
# ./deploy-workload.sh --env upcloud-workload
# =============================================================================
variable "prefix" {
description = "Prefix for resource names"
type = string
default = "clst-workload"
}
variable "zone" {
description = "UpCloud zone"
type = string
default = "no-svg1"
}
variable "node_plan" {
description = "UpCloud server plan for worker nodes"
type = string
default = "2xCPU-4GB"
}
variable "node_count" {
description = "Number of worker nodes"
type = number
default = 2
}
variable "network_cidr" {
description = "CIDR block for the private network"
type = string
default = "10.110.0.0/24"
}
variable "control_plane_ip_filter" {
description = "CIDRs allowed to access the K8s API"
type = list(string)
default = ["0.0.0.0/0"]
}
variable "tags" {
description = "Labels to apply to resources"
type = map(string)
default = {
Environment = "workload"
ManagedBy = "tofu"
}
}
module "cluster" {
source = "../modules/cluster"
prefix = "clst-prod"
zone = "no-svg1"
node_plan = "CLOUDNATIVE-4xCPU-8GB"
node_count = 4
storage_size = 30
network_cidr = "10.100.0.0/24"
control_plane_ip_filter = ["0.0.0.0/0"] # TODO: restrict to known CIDRs
tags = {
Environment = "prod"
ManagedBy = "tofu"
}
}
# ─── Networking ───────────────────────────────────────────────────────
resource "upcloud_router" "kubernetes" {
name = "${var.prefix}-workload-router"
}
resource "upcloud_gateway" "kubernetes" {
name = "${var.prefix}-workload-gateway"
zone = var.zone
features = ["nat"]
router {
id = upcloud_router.kubernetes.id
}
}
resource "upcloud_network" "kubernetes" {
name = "${var.prefix}-workload-network"
zone = var.zone
router = upcloud_router.kubernetes.id
ip_network {
address = var.network_cidr
dhcp = true
dhcp_default_route = true
family = "IPv4"
gateway = cidrhost(var.network_cidr, 1)
}
depends_on = [upcloud_gateway.kubernetes]
}
# ─── Kubernetes Cluster ───────────────────────────────────────────────
resource "upcloud_kubernetes_cluster" "main-prod" {
name = "${var.prefix}-workload"
zone = var.zone
network = upcloud_network.kubernetes.id
control_plane_ip_filter = var.control_plane_ip_filter
private_node_groups = true
}
+13
View File
@@ -0,0 +1,13 @@
# ─── Cluster ─────────────────────────────────────────────────────────
output "cluster_id" {
value = module.cluster.cluster_id
}
output "cluster_name" {
value = module.cluster.cluster_name
}
output "zone" {
value = module.cluster.zone
}
+14
View File
@@ -0,0 +1,14 @@
terraform {
required_version = ">= 1.0"
required_providers {
upcloud = {
source = "UpCloudLtd/upcloud"
version = "~> 5.0"
}
}
}
provider "upcloud" {
# Set via environment variables: UPCLOUD_USERNAME, UPCLOUD_PASSWORD
}
+116
View File
@@ -0,0 +1,116 @@
# =============================================================================
# UpCloud Workload Cluster
# =============================================================================
# A lean UCS cluster for running application workloads. No managed data
# services — those live on the platform cluster. ArgoCD (on the platform
# cluster) deploys apps to this cluster via the app-of-apps pattern.
#
# Platform components deployed by deploy-workload.sh:
# nginx-ingress, cert-manager, external-dns, external-secrets, alloy
#
# Usage:
# tofu init && tofu plan && tofu apply
# ./sync-tofu-outputs.sh --env upcloud-workload
# ./deploy-workload.sh --env upcloud-workload
# =============================================================================
variable "prefix" {
description = "Prefix for resource names"
type = string
default = "clst-workload"
}
variable "zone" {
description = "UpCloud zone"
type = string
default = "no-svg1"
}
variable "node_plan" {
description = "UpCloud server plan for worker nodes"
type = string
default = "2xCPU-4GB"
}
variable "node_count" {
description = "Number of worker nodes"
type = number
default = 2
}
variable "network_cidr" {
description = "CIDR block for the private network"
type = string
default = "10.110.0.0/24"
}
variable "control_plane_ip_filter" {
description = "CIDRs allowed to access the K8s API"
type = list(string)
default = ["0.0.0.0/0"]
}
variable "tags" {
description = "Labels to apply to resources"
type = map(string)
default = {
Environment = "workload"
ManagedBy = "tofu"
}
}
# ─── Networking ───────────────────────────────────────────────────────
resource "upcloud_router" "kubernetes" {
name = "${var.prefix}-workload-router"
}
resource "upcloud_gateway" "kubernetes" {
name = "${var.prefix}-workload-gateway"
zone = var.zone
features = ["nat"]
router {
id = upcloud_router.kubernetes.id
}
}
resource "upcloud_network" "kubernetes" {
name = "${var.prefix}-workload-network"
zone = var.zone
router = upcloud_router.kubernetes.id
ip_network {
address = var.network_cidr
dhcp = true
dhcp_default_route = true
family = "IPv4"
gateway = cidrhost(var.network_cidr, 1)
}
depends_on = [upcloud_gateway.kubernetes]
}
# ─── Kubernetes Cluster ───────────────────────────────────────────────
resource "upcloud_kubernetes_cluster" "main" {
name = "${var.prefix}-workload"
zone = var.zone
network = upcloud_network.kubernetes.id
control_plane_ip_filter = var.control_plane_ip_filter
private_node_groups = true
}
resource "upcloud_kubernetes_node_group" "workers" {
cluster = upcloud_kubernetes_cluster.main.id
name = "${var.prefix}-workload-workers"
node_count = var.node_count
plan = var.node_plan
anti_affinity = var.node_count > 1
labels = {
prefix = var.prefix
cluster = "workload"
role = "worker"
env = lookup(var.tags, "Environment", "workload")
}
}
+3
View File
@@ -0,0 +1,3 @@
output "cluster_name" { value = upcloud_kubernetes_cluster.main.name }
output "cluster_id" { value = upcloud_kubernetes_cluster.main.id }
output "zone" { value = var.zone }
+14
View File
@@ -0,0 +1,14 @@
terraform {
required_version = ">= 1.0"
required_providers {
upcloud = {
source = "UpCloudLtd/upcloud"
version = "~> 5.0"
}
}
}
provider "upcloud" {
# Set via environment variables: UPCLOUD_USERNAME, UPCLOUD_PASSWORD
}
+66
View File
@@ -0,0 +1,66 @@
#!/bin/bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
TOFU_ROOT="$(dirname "$SCRIPT_DIR")"
PROJECT_ROOT="$(dirname "$TOFU_ROOT")"
CLUSTER="${1:?Usage: $0 <cluster> (e.g., aks-dev, eks-prod)}"
PLATFORM="${CLUSTER%%-*}"
ENV="${CLUSTER#*-}"
KUBECONFIG_FILE="$PROJECT_ROOT/private/$CLUSTER/kubeconfig"
if [[ -f "$KUBECONFIG_FILE" ]]; then
echo "Kubeconfig already exists: $KUBECONFIG_FILE"
echo ""
echo " export KUBECONFIG=$KUBECONFIG_FILE"
else
echo "No cached kubeconfig. Fetching from platform..."
# Load platform credentials
ENV_FILE="$TOFU_ROOT/configs/$PLATFORM.env"
if [[ -f "$ENV_FILE" ]]; then
set -a; source "$ENV_FILE"; set +a
fi
TOFU_DIR="$TOFU_ROOT/platforms/$PLATFORM/$ENV"
mkdir -p "$(dirname "$KUBECONFIG_FILE")"
case "$PLATFORM" in
aks)
cd "$TOFU_DIR"
RG=$(tofu output -raw resource_group_name 2>/dev/null || echo "$CLUSTER-rg")
NAME=$(tofu output -raw cluster_name 2>/dev/null || echo "$CLUSTER")
az aks get-credentials --resource-group "$RG" --name "$NAME" --file "$KUBECONFIG_FILE" --overwrite-existing
;;
eks)
cd "$TOFU_DIR"
NAME=$(tofu output -raw cluster_name 2>/dev/null || echo "$CLUSTER")
REGION=$(tofu output -raw aws_region 2>/dev/null || echo "${AWS_REGION:-eu-west-1}")
aws eks update-kubeconfig --name "$NAME" --region "$REGION" --kubeconfig "$KUBECONFIG_FILE"
;;
gke)
cd "$TOFU_DIR"
NAME=$(tofu output -raw cluster_name 2>/dev/null || echo "$CLUSTER")
REGION=$(tofu output -raw region 2>/dev/null || echo "${GCP_REGION:-europe-west4}")
PROJECT=$(tofu output -raw project_id 2>/dev/null || echo "${GCP_PROJECT_ID:-}")
gcloud container clusters get-credentials "$NAME" --region "$REGION" --project "$PROJECT"
cp ~/.kube/config "$KUBECONFIG_FILE"
;;
upc)
cd "$TOFU_DIR"
CLUSTER_ID=$(tofu output -raw cluster_id 2>/dev/null || echo "${UPCLOUD_CLUSTER_ID:-}")
upctl kubernetes config "$CLUSTER_ID" > "$KUBECONFIG_FILE"
;;
*)
echo "Error: unknown platform '$PLATFORM'"
exit 1
;;
esac
chmod 600 "$KUBECONFIG_FILE"
echo "Kubeconfig saved: $KUBECONFIG_FILE"
echo ""
echo " export KUBECONFIG=$KUBECONFIG_FILE"
fi
+246
View File
@@ -0,0 +1,246 @@
#!/bin/bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
TOFU_ROOT="$(dirname "$SCRIPT_DIR")"
PROJECT_ROOT="$(dirname "$TOFU_ROOT")"
# ─── Usage ────────────────────────────────────────────────────────────
usage() {
cat <<EOF
Usage: $0 <cluster> [options]
Provision a Kubernetes cluster using OpenTofu.
Mirrors bootstrap.sh convention: cluster = <platform>-<env>
Clusters: aks-dev | aks-prod | eks-dev | eks-prod
gke-dev | gke-prod | upc-dev | upc-prod
<platform>-workload (for workload clusters)
Options:
--plan Plan only, don't apply
--destroy Destroy the cluster (use teardown-cluster.sh instead)
--auto Skip confirmation prompts
-h, --help Show this help
Examples:
$0 aks-dev
$0 eks-prod --plan
$0 upc-dev --auto
Prerequisites:
- tofu, kubectl, helm installed
- Platform credentials in .tofu/configs/<platform>.env
- Cluster config in clusters/<cluster>.yaml
After provisioning, run:
./bootstrap.sh <cluster>
EOF
exit "${1:-0}"
}
# ─── Parse arguments ──────────────────────────────────────────────────
CLUSTER=""
PLAN_ONLY=false
DESTROY=false
AUTO_APPROVE=false
while [[ $# -gt 0 ]]; do
case "$1" in
--plan) PLAN_ONLY=true; shift ;;
--destroy) DESTROY=true; shift ;;
--auto) AUTO_APPROVE=true; shift ;;
-h|--help) usage 0 ;;
-*) echo "Unknown option: $1"; usage 1 ;;
*)
if [[ -z "$CLUSTER" ]]; then
CLUSTER="$1"
else
echo "Error: unexpected argument '$1'"
usage 1
fi
shift
;;
esac
done
[[ -z "$CLUSTER" ]] && { echo "Error: <cluster> argument required"; usage 1; }
# ─── Map cluster → platform + env ────────────────────────────────────
PLATFORM="${CLUSTER%%-*}" # aks-dev → aks
ENV="${CLUSTER#*-}" # aks-dev → dev
case "$PLATFORM" in
aks|eks|gke|upc) ;;
*) echo "Error: unknown platform '$PLATFORM'. Expected: aks, eks, gke, upc"; exit 1 ;;
esac
TOFU_DIR="$TOFU_ROOT/platforms/$PLATFORM/$ENV"
if [[ ! -d "$TOFU_DIR" ]]; then
echo "Error: tofu directory not found: $TOFU_DIR"
echo "Available environments for $PLATFORM:"
ls -1 "$TOFU_ROOT/platforms/$PLATFORM/" 2>/dev/null | grep -v modules || echo " (none)"
exit 1
fi
echo "========================================="
echo " Kubernetes Cluster Setup"
echo "========================================="
echo ""
echo " Cluster: $CLUSTER"
echo " Platform: $PLATFORM"
echo " Env: $ENV"
echo " Tofu dir: $TOFU_DIR"
echo ""
# ─── Prerequisites ────────────────────────────────────────────────────
echo "=== Checking Prerequisites ==="
command -v tofu >/dev/null 2>&1 || { echo "Error: tofu is not installed."; exit 1; }
command -v kubectl >/dev/null 2>&1 || { echo "Error: kubectl is not installed."; exit 1; }
command -v helm >/dev/null 2>&1 || { echo "Error: helm is not installed."; exit 1; }
echo " tofu, kubectl, helm: OK"
# ─── Load platform credentials ────────────────────────────────────────
ENV_FILE="$TOFU_ROOT/configs/$PLATFORM.env"
if [[ -f "$ENV_FILE" ]]; then
echo " Loading credentials from configs/$PLATFORM.env"
set -a
# shellcheck disable=SC1090
source "$ENV_FILE"
set +a
else
echo " Warning: $ENV_FILE not found — using existing environment/CLI auth"
echo " Copy configs/$PLATFORM.env.example → configs/$PLATFORM.env to configure"
fi
# ─── Load cluster config (if exists) ──────────────────────────────────
CLUSTER_CONFIG="$PROJECT_ROOT/clusters/$CLUSTER.yaml"
if [[ -f "$CLUSTER_CONFIG" ]]; then
echo " Loading cluster config from clusters/$CLUSTER.yaml"
if command -v yq >/dev/null 2>&1; then
eval "$(yq -r 'to_entries[] | "export CLUSTER_\(.key)=\"\(.value)\""' "$CLUSTER_CONFIG")"
echo " Cluster name: ${CLUSTER_clusterName:-$CLUSTER}"
else
echo " Warning: yq not installed — cluster config not loaded"
fi
else
echo " Warning: $CLUSTER_CONFIG not found — using defaults"
fi
echo ""
# ─── Run OpenTofu ─────────────────────────────────────────────────────
cd "$TOFU_DIR"
echo "=== Initializing OpenTofu ==="
tofu init
echo ""
if $DESTROY; then
echo "=== Planning Destruction ==="
tofu plan -destroy -out=tfplan
if ! $AUTO_APPROVE; then
echo ""
read -rp "DESTROY cluster $CLUSTER? This is irreversible. (yes/no) " REPLY
[[ "$REPLY" == "yes" ]] || { echo "Cancelled."; exit 1; }
fi
echo "Destroying infrastructure..."
tofu apply tfplan
echo ""
echo "=== Cluster $CLUSTER Destroyed ==="
elif $PLAN_ONLY; then
echo "=== Planning Infrastructure ==="
tofu plan
echo ""
echo "=== Plan complete (--plan mode, no changes applied) ==="
else
echo "=== Planning Infrastructure ==="
tofu plan -out=tfplan
if ! $AUTO_APPROVE; then
echo ""
read -rp "Apply this plan for $CLUSTER? (y/n) " -n 1 REPLY
echo
[[ "$REPLY" =~ ^[Yy]$ ]] || { echo "Cancelled."; exit 1; }
fi
echo "Applying infrastructure..."
tofu apply tfplan
# ─── Save kubeconfig ──────────────────────────────────────────────
KUBECONFIG_DIR="$PROJECT_ROOT/private/$CLUSTER"
mkdir -p "$KUBECONFIG_DIR"
KUBECONFIG_FILE="$KUBECONFIG_DIR/kubeconfig"
echo ""
echo "=== Saving Kubeconfig ==="
case "$PLATFORM" in
aks)
if tofu output -raw kubeconfig > "$KUBECONFIG_FILE" 2>/dev/null; then
echo " Saved from tofu output"
else
echo " Fetching from Azure CLI..."
RG=$(tofu output -raw resource_group_name 2>/dev/null || echo "${CLUSTER_clusterName:-$CLUSTER}-rg")
NAME=$(tofu output -raw cluster_name 2>/dev/null || echo "${CLUSTER_clusterName:-$CLUSTER}")
az aks get-credentials --resource-group "$RG" --name "$NAME" --file "$KUBECONFIG_FILE" --overwrite-existing
fi
;;
eks)
NAME=$(tofu output -raw cluster_name 2>/dev/null || echo "${CLUSTER_clusterName:-$CLUSTER}")
REGION=$(tofu output -raw aws_region 2>/dev/null || echo "${AWS_REGION:-eu-west-1}")
aws eks update-kubeconfig --name "$NAME" --region "$REGION" --kubeconfig "$KUBECONFIG_FILE"
;;
gke)
NAME=$(tofu output -raw cluster_name 2>/dev/null || echo "${CLUSTER_clusterName:-$CLUSTER}")
REGION=$(tofu output -raw region 2>/dev/null || echo "${GCP_REGION:-europe-west4}")
PROJECT=$(tofu output -raw project_id 2>/dev/null || echo "${GCP_PROJECT_ID:-}")
gcloud container clusters get-credentials "$NAME" --region "$REGION" --project "$PROJECT" 2>/dev/null \
&& cp ~/.kube/config "$KUBECONFIG_FILE" \
|| echo " Warning: could not fetch kubeconfig via gcloud"
;;
upc)
if tofu output -raw kubeconfig > "$KUBECONFIG_FILE" 2>/dev/null; then
echo " Saved from tofu output"
else
CLUSTER_ID=$(tofu output -raw cluster_id 2>/dev/null || echo "${UPCLOUD_CLUSTER_ID:-}")
if [[ -n "$CLUSTER_ID" ]]; then
upctl kubernetes config "$CLUSTER_ID" > "$KUBECONFIG_FILE"
else
echo " Warning: could not determine cluster ID for kubeconfig"
fi
fi
;;
esac
if [[ -f "$KUBECONFIG_FILE" ]]; then
chmod 600 "$KUBECONFIG_FILE"
echo " Kubeconfig: $KUBECONFIG_FILE"
fi
# ─── Wait for nodes ──────────────────────────────────────────────
echo ""
echo "=== Waiting for Cluster Nodes ==="
export KUBECONFIG="$KUBECONFIG_FILE"
if kubectl wait --for=condition=Ready nodes --all --timeout=300s 2>/dev/null; then
echo " All nodes ready"
else
echo " Warning: nodes not ready within timeout — check cluster status"
fi
# ─── Summary ─────────────────────────────────────────────────────
echo ""
echo "========================================="
echo " Cluster $CLUSTER Provisioned"
echo "========================================="
echo ""
echo " Kubeconfig: $KUBECONFIG_FILE"
echo ""
echo " Next steps:"
echo " export KUBECONFIG=$KUBECONFIG_FILE"
echo " ./bootstrap.sh $CLUSTER"
echo ""
fi
+7
View File
@@ -0,0 +1,7 @@
#!/bin/bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Delegate to setup-cluster.sh with --destroy flag
exec "$SCRIPT_DIR/setup-cluster.sh" "$@" --destroy
+36 -19
View File
@@ -57,11 +57,11 @@ This repository contains the complete GitOps configuration for our Kubernetes cl
### What's Inside
- **Infrastructure Applications**: Traefik, Cert-Manager, Kyverno, Prometheus, Grafana, Loki, Tempo, Vault, Vault Secrets Operator, Homepage (platform dashboard)
- **Infrastructure Applications**: Traefik, Cert-Manager, Kyverno, Prometheus, Grafana, Loki, Tempo, Sealed Secrets, Homepage (platform dashboard)
- **Business Applications**: MCP10X, MusicMan, Dot-AI Stack, ArgoCD MCP
- **Policies**: Kyverno security policies for secret management, namespace controls, pod verification
- **Monitoring**: Full observability stack with metrics, logs, traces, and alerting
- **Secrets**: Vault Secrets Operator (VSO) syncs secrets from HashiCorp Vault to K8s
- **Secrets**: Sealed Secrets for secure Git storage
### Key Features
@@ -80,8 +80,23 @@ This repository contains the complete GitOps configuration for our Kubernetes cl
```
.
├── bootstrap.sh # Cluster initialization script
├── _app-of-apps.yaml # Root ArgoCD Application (App-of-Apps pattern)
├── bootstrap.sh # Cluster initialization (ArgoCD + GitOps)
├── _app-of-apps-{cluster}.yaml # Root ArgoCD Application (per cluster)
│
├── .tofu/ # Infrastructure provisioning (OpenTofu)
│ ├── platforms/ # Per-platform IaC (one dir per cloud)
│ │ ├── aks/ # Azure AKS (modules/ + dev/ + prod/ + workload/)
│ │ ├── eks/ # AWS EKS
│ │ ├── gke/ # GCP GKE
│ │ └── upc/ # UpCloud
│ ├── configs/ # Platform credentials (git-ignored)
│ │ └── *.env.example # Template for each platform
│ └── scripts/ # Cluster lifecycle scripts
│ ├── setup-cluster.sh # Create cluster: ./setup-cluster.sh aks-dev
│ ├── teardown-cluster.sh
│ └── get-kubeconfig.sh
│
├── clusters/ # Cluster metadata (domain, trustedIPs, etc.)
│
├── infra/ # Infrastructure ArgoCD Applications (Kustomize multi-cluster)
│ ├── base/ # Base ArgoCD Application manifests (one dir per component)
@@ -187,7 +202,7 @@ Developer commits code → CI/CD builds image → Updates helm-prod-values → A
**Quick version**:
1. Create `apps/myapp.yaml` (ArgoCD Application manifest)
2. Create `helm-prod-values/myapp/values.yaml` (configuration)
3. Write secrets to Vault and create VaultStaticSecret CRD if needed
3. Create sealed secrets if needed
4. Commit and push - ArgoCD auto-syncs!
### Update an Existing Application
@@ -200,18 +215,22 @@ Developer commits code → CI/CD builds image → Updates helm-prod-values → A
### Manage Secrets
**See detailed guide**: [Vault Secrets Operator Reference](docs/vault-secrets-operator.md)
**See detailed guide**: [Developer Guide - Working with Secrets](docs/DEVELOPER-GUIDE.md#working-with-secrets)
```bash
# 1. Write secret to Vault
vault kv put kv/myapp/myapp-creds KEY=value
# Create plain secret
kubectl create secret generic myapp-creds \
--from-literal=KEY=value \
--dry-run=client -o yaml > private/myapp-creds.yaml
# 2. Create VaultStaticSecret CRD (one-time, commit to git)
# See docs/vault-secrets-operator.md for CRD template
# Seal it
kubeseal --format=yaml --cert=pub-cert.pem \
< private/myapp-creds.yaml > secrets/myapp-creds-sealed.yaml
# 3. Rotate secrets — no git commit needed!
vault kv put kv/myapp/myapp-creds KEY=new-value
# VSO picks up changes within 30 seconds
# Commit sealed version
git add secrets/myapp-creds-sealed.yaml
git commit -m "Add myapp credentials"
git push
```
### Enable Authentication
@@ -324,7 +343,7 @@ kubectl patch application myapp -n argocd \
## 🔐 Security
### Secret Management
- ✅ Vault Secrets Operator (VSO) for secret management
- ✅ Sealed Secrets for Git storage
- ✅ Kyverno auto-clones secrets to namespaces
- ❌ Never commit plain secrets
@@ -351,8 +370,7 @@ kubectl patch application myapp -n argocd \
| **Traefik** | Ingress controller | `traefik` | 2 |
| **Cert-Manager** | TLS certificates | `cert-manager` | 1 |
| **Kyverno** | Policy engine | `kyverno` | 1 |
| **Vault** | Secret storage | `vault` | 1 |
| **Vault Secrets Operator** | Secret sync (Vault → K8s) | `vault-secrets-operator-system` | 1 |
| **Sealed Secrets** | Secret encryption | `kube-system` | 1 |
| **Prometheus** | Metrics | `monitoring` | 1 |
| **Grafana** | Dashboards | `monitoring` | 1 |
| **Loki** | Logs | `monitoring` | 1 |
@@ -452,7 +470,7 @@ Applications deploy in order using `argocd.argoproj.io/sync-wave`:
1. Read [Developer Guide - Deploying Your First Application](docs/DEVELOPER-GUIDE.md#deploying-your-first-application)
2. Create ArgoCD Application manifest in `apps/`
3. Create Helm values in `helm-prod-values/`
4. Write secrets to Vault and create VaultStaticSecret CRD if needed
4. Create sealed secrets if needed
5. Commit and push - ArgoCD handles the rest!
### Modifying Infrastructure
@@ -496,8 +514,7 @@ Documentation lives in `docs/`. To update:
- [Traefik Documentation](https://doc.traefik.io/traefik/)
- [Cert-Manager Documentation](https://cert-manager.io/docs/)
- [Grafana Tempo Documentation](https://grafana.com/docs/tempo/)
- [Vault Secrets Operator](https://developer.hashicorp.com/vault/docs/platform/k8s/vso)
- [HashiCorp Vault](https://developer.hashicorp.com/vault/docs)
- [Sealed Secrets](https://github.com/bitnami-labs/sealed-secrets)
### Related Repositories
- [forte-helm](https://git.forteapps.net/Forte/forte-helm) - Helm chart templates
@@ -1,14 +0,0 @@
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: argocd-mcp-credentials
namespace: argocd-mcp
spec:
type: kv-v2
mount: kv
path: argocd-mcp/argocd-mcp-credentials
destination:
name: argocd-mcp-credentials
create: true
refreshAfter: 30s
vaultAuthRef: vault-auth
-14
View File
@@ -1,14 +0,0 @@
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: auth-oidc
namespace: argocd-mcp
spec:
type: kv-v2
mount: kv
path: argocd-mcp/auth-oidc
destination:
name: auth-oidc
create: true
refreshAfter: 30s
vaultAuthRef: vault-auth
+2 -4
View File
@@ -2,7 +2,5 @@ apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- argo-mcp.yaml
- vault-auth.yaml
- auth-oidc-vault.yaml
- argocd-mcp-credentials-vault.yaml
# Removed: argocdmcp-auth-oidc-sealed.yaml, argocd-mcp-credentials.yaml (migrated to VSO)
- argocdmcp-auth-oidc-sealed.yaml
- argocd-mcp-credentials.yaml
-20
View File
@@ -1,20 +0,0 @@
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: vault-auth-argocd-mcp
namespace: argocd-mcp
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultAuth
metadata:
name: vault-auth
namespace: argocd-mcp
spec:
method: kubernetes
mount: kubernetes
kubernetes:
role: ns-argocd-mcp
serviceAccount: vault-auth-argocd-mcp
audiences:
- vault
@@ -1,14 +0,0 @@
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: dot-ai-secrets
namespace: dot-ai
spec:
type: kv-v2
mount: kv
path: dot-ai/dot-ai-secrets
destination:
name: dot-ai-secrets
create: true
refreshAfter: 30s
vaultAuthRef: vault-auth
-3
View File
@@ -15,9 +15,6 @@ metadata:
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "1"
notifications.argoproj.io/subscribe.on-sync-succeeded.slack: ""
notifications.argoproj.io/subscribe.on-sync-failed.slack: ""
notifications.argoproj.io/subscribe.on-degraded.slack: ""
labels:
app.kubernetes.io/name: dot-ai-stack
app.kubernetes.io/part-of: apps
+1 -3
View File
@@ -2,6 +2,4 @@ apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- dot-ai-stack.yaml
- vault-auth.yaml
- dot-ai-secrets-vault.yaml
# Removed: dot-ai-secrets.yaml (migrated to VSO)
- dot-ai-secrets.yaml
-20
View File
@@ -1,20 +0,0 @@
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: vault-auth-dot-ai
namespace: dot-ai
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultAuth
metadata:
name: vault-auth
namespace: dot-ai
spec:
method: kubernetes
mount: kubernetes
kubernetes:
role: ns-dot-ai
serviceAccount: vault-auth-dot-ai
audiences:
- vault
@@ -1,15 +0,0 @@
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: app-credentials
namespace: mcp10x
spec:
type: kv-v2
mount: kv
path: mcp10x/app-credentials
destination:
name: app-credentials
create: true
type: Opaque
refreshAfter: 30s
vaultAuthRef: vault-auth
@@ -7,7 +7,8 @@ metadata:
namespace: mcp10x
spec:
encryptedData:
FLOWCASE_TOKEN: AgAjQ0Jm779LiB2/EBKtIl18Iky/tgl7VvEvkJfr82mbe7JIp99+I8WPIMgmlXsxWBDfwJ/DZoDEAKG5o8mvwc7DtaE7LkpPSXKRIviLrm+7amNdEpwb1zKjCmZ3vNOPXLnCTD3W1od8u106MEvrZpt1N1Pzav/QUElAA4lqyf4AZPuio0H/7jF8QciVHIN4BdofodOmWWCTxP1aVMylat3txlkzuqwkXc1fKBPmOKC1qWepAG7dlQ3xJ2C5Cinz5IqOYnQPO6sm/Gp+c8yQSS+0NpKO3dp93yuwRsOLOMNpCeeYOyzn2ypFIdiBuLA5k85l5WyI9BCvRjP+kzRX+u69XL7p2eMEg+vRVfoOWNetysYCdK+96bWNVVleQEuPAFxsbJWK5eV8jTZk+QeFES8kkXQwUoTMqh+JULgONakxrzc1LmA+FtKNwHWCHalh8pLQ9KeI2i9E5CtotjHfEGLE6T0ibwwOg1ZrL3irQVII8VFm4Rk+dSicbeEMAtIaII023NWuZlonrwIIy+GSVzskhhcjV7t51hylqFA3yfb+RQCzdGYA5fySlJBWhli8IotvqnDmWduPBHGb/+yrdXHsaiDqWuUu6JBUg4JqM+AMa8FkaFh8SbctbGBoycpzLVFn9N2qWviojhC9s1NJ4MiCegvE6IsCLaHidY4UXeeGjUK6Mx6h4x3y/q7ZIrBIpUOJ+8I4Mgvi9sTCogeR+vWtpP9ZZ/31MXELuu/ho3Wn9Q==
BENKEN_CLIENT_SECRET: AgBjps8BCn20C8upAaAdb1fG4Qh3eKBbTeFuuWhQb8WoIvHmk+v03mw8i39evkQwlUWvLBWV1mtGiKwpCNhmAAdicRFG6u0m30+21K6ZtYqtKEniPKo/kjPd9nDbtxvIwiaqKs+kvFpqbxKaeqF5THYNxHZBg8gGeSbAkSx+BNIY2iLm8NRBzgC5FME5jbX8pet+DN7tbmsQTtG5f1q+h24aKXjvN5SigPft1BetfleHGlus0hFXpJ7zS4rV7rpSKSiLVV/KtS5YqEDj9uunZKyGT8j93z5xQbj0bCPVF3TU5+RCL6eE/GSgJxM4ePZ2gWZRG4eHDlGK2qE+JaNIng8wWycqLVrbs1kWoP2cdRGq6b8GHwoplAM4h4ud2fac6+5V4alhy8uhRTAX7dUG3UkwOQV/hTRb97pctPwQfltkBpDBhhW3Lfy9whSdqBuD9OqhegTqxCuWXr/P4KVOORUr0V0Iex45oLMqivI3FB2rTq9Lkwpf5YKE14d7RbKWF1G28FCNAcV8vnOIslkztLuu3GgQbVIoIiZGnnYEPofjUxbDZ/Sd1LrGeHEPR+/JJnNhNxv2297UuIIEC8kfiKuwjtLOMVQg51UbESx/+d1XmgLPqkz+jAqEPxvu8u5wO0Dov6vliTbtewJf8+fet3xnFNvyuY5ymmiTla6Gm9XPJscLj5iCzT3VhA/QuhhWXinmE/UnpYP0U4YPl5B/dUA9qT95BA6GBpDXPrDJRWNfpAH1wfG70lEBuBQXgdixYlwvx2JO6OI5+ztBKO0qFkgK
FLOWCASE_TOKEN: AgBLN1BWnFs3R2K+Wvb37ABucQWvx8mVVDEhZiIOYYLE8SDdbshxluP4J4IISmHwLX7Q0gYWeypfTCfvMa9c5b9AEjTcpodv4CMhjp5Mq7zUCtTI6pH6QZgLhjGheKCY+RciIK1DBsNk7CZa9MuVUdd1w0/m2C7fwikPPvNDHcNLR7zBBPWVlprgRyMrsdykcm7cmWcyqTwa/55DVPPJsG9x1K2YnjzgWHlX7BoLB/Nx7qX4XVadKEXnV0jVO5isalARxvke32pwebaiFSg8EZMhe4lntOqMSP26y/9RbveeEZS8qxzoR2OmsVrHUpMeppzpVkvQ9sKi2B4Vx8XoiRY6hZR+TmZKTvFa6JKx9gwAOVs53LEUaF7+uOQOrIcFyOXm7Ibj/azf7opqfvGajygRzSUaffTIixj+M5eJnm/iua/BWarW2w4c+ZIKvQ9DhcYpnF9Gj0U7+EWfNZyzlMoLRHsOIyKNUhOeEaSLoVQGyqRY72ZGqs83EPfQYlEu74PGyXPjd11KMhAayluGLadeAH2QDyigpf7REdXAGULNDhkJrqeYMmmGCZCyrvNOoXjYC4jQQEYA4Sd4QJA05q/EW8aFE/++nY8RqhbHqDz6wshAW/WFsTsR4JC6fc5qR+YVSmjKOa7lnk7I9vD7uPEZzCc6ImvA/NxTCfDKCREvLA5G0lYsmiTuxYmBesUgCSVVMxflEl1GXvGpFniZKPyvxIdna9j7XEQjUZsGTiyN1w==
template:
metadata:
creationTimestamp: null
+1 -3
View File
@@ -2,6 +2,4 @@ apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- mcp10x.yaml
- vault-auth.yaml
- app-credentials-vault.yaml
# Removed: forte10x-app-credentials-sealed.yaml (migrated to VSO)
- forte10x-app-credentials-sealed.yaml
-20
View File
@@ -1,20 +0,0 @@
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: vault-auth-mcp10x
namespace: mcp10x
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultAuth
metadata:
name: vault-auth
namespace: mcp10x
spec:
method: kubernetes
mount: kubernetes
kubernetes:
role: ns-mcp10x
serviceAccount: vault-auth-mcp10x
audiences:
- vault
+1 -3
View File
@@ -2,6 +2,4 @@ apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- musicman.yaml
- vault-auth.yaml
- musicman-credentials-vault.yaml
# Removed: musicman-credentials.yaml (migrated to VSO)
- musicman-credentials.yaml
@@ -1,15 +0,0 @@
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: musicman-credentials
namespace: music-man
spec:
type: kv-v2
mount: kv
path: music-man/musicman-credentials
destination:
name: musicman-credentials
create: true
type: Opaque
refreshAfter: 30s
vaultAuthRef: vault-auth
-20
View File
@@ -1,20 +0,0 @@
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: vault-auth-music-man
namespace: music-man
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultAuth
metadata:
name: vault-auth
namespace: music-man
spec:
method: kubernetes
mount: kubernetes
kubernetes:
role: ns-music-man
serviceAccount: vault-auth-music-man
audiences:
- vault
+1 -3
View File
@@ -2,6 +2,4 @@ apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ts-mcp.yaml
- vault-auth.yaml
- ts-mcp-secrets-vault.yaml
# Removed: ts-mcp-secrets-sealed.yaml (migrated to VSO)
- ts-mcp-secrets-sealed.yaml
@@ -1,14 +0,0 @@
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: ts-mcp-secrets
namespace: ts-mcp
spec:
type: kv-v2
mount: kv
path: ts-mcp/ts-mcp-secrets
destination:
name: ts-mcp-secrets
create: true
refreshAfter: 30s
vaultAuthRef: vault-auth
-20
View File
@@ -1,20 +0,0 @@
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: vault-auth-ts-mcp
namespace: ts-mcp
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultAuth
metadata:
name: vault-auth
namespace: ts-mcp
spec:
method: kubernetes
mount: kubernetes
kubernetes:
role: ns-ts-mcp
serviceAccount: vault-auth-ts-mcp
audiences:
- vault
@@ -1,53 +1,37 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: feedback
name: forte-drop-mcp
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "12"
argocd.argoproj.io/sync-wave: "1"
notifications.argoproj.io/subscribe.on-sync-succeeded.slack: ""
notifications.argoproj.io/subscribe.on-sync-failed.slack: ""
notifications.argoproj.io/subscribe.on-degraded.slack: ""
labels:
app.kubernetes.io/name: feedback
app.kubernetes.io/name: forte-drop-mcp
app.kubernetes.io/part-of: apps
app.kubernetes.io/managed-by: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
sources:
- repoURL: ssh://git@git.forteapps.net:2222/Forte/forte-helm.git
path: forteapp
targetRevision: HEAD
helm:
valueFiles:
- $values/feedback/values.yaml
- $values/forte-drop-mcp/values.yaml
- repoURL: ssh://git@git.forteapps.net:2222/Forte/helm-prod-values.git
targetRevision: HEAD
ref: values
destination:
server: https://kubernetes.default.svc
namespace: feedback
namespace: forte-drop
syncPolicy:
automated:
prune: true
selfHeal: true
allowEmpty: false
syncOptions:
- CreateNamespace=true
- Validate=true
- ServerSideApply=true
retry:
limit: 5
backoff:
duration: 5s
factor: 2
maxDuration: 3m
ignoreDifferences:
- group: apps
kind: StatefulSet
jsonPointers:
- /spec/volumeClaimTemplates
@@ -0,0 +1,8 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- forte-drop-mcp.yaml
# No keycloak-client config + no auth-oidc Secret for mcp mode. The chart's
# auth.type: mcp auto-registers the MCP client; the sidecar is an RFC 9728
# resource server that validates tokens (no client-secret of its own).
# forte-drop-secrets (shared with web) covers PG + S3 creds.
@@ -0,0 +1,143 @@
# forte-drop Postgres — backup & restore runbook
## What gets backed up
A CronJob (`forte-drop-pg-backup`, namespace `forte-drop`) runs nightly at **02:00 UTC**:
1. `pg_dump` of the `drops` database → gzip.
2. Upload to **UpCloud Managed Object Storage**: `s3://drops/_pgbackups/forte-drop-<TS>.sql.gz`
(the `_pgbackups/` prefix is collision-proof: app slugs match `/^[a-z0-9][a-z0-9-]{0,62}$/`
and can never start with `_`).
3. Retention: dumps older than **30 days** are pruned.
S3 creds come from the `forte-drop-secrets` Secret (`S3_ENDPOINT` / `S3_KEY` / `S3_SECRET`).
Postgres creds from `forte-drop-pg-creds` (`pgusername` / `pgpassword`).
> **Object storage is the durable tier.** App data + DB backups both live in UpCloud
> Managed Object Storage (replicated by UpCloud). The in-cluster Postgres PVC is the
> live working copy; the nightly dump is the recovery point. The PVC carries
> `Prune=false,Delete=false` so ArgoCD never deletes it.
## Prerequisites
```bash
export KUBECONFIG=~/Downloads/dev-fd-no-svg1_kubeconfig.yaml
# Confirm the namespace + DB pod are up:
kubectl -n forte-drop get pods -l app.kubernetes.io/name=postgresql
```
## List available backups
```bash
# Run an ephemeral mc pod with the app's S3 creds:
kubectl -n forte-drop run mc-list --rm -it --restart=Never \
--image=quay.io/minio/mc:RELEASE.2024-11-21T17-21-54Z \
--overrides='{"spec":{"containers":[{"name":"mc","image":"quay.io/minio/mc:RELEASE.2024-11-21T17-21-54Z","command":["sh","-c","mc alias set obj \"$S3_ENDPOINT\" \"$S3_KEY\" \"$S3_SECRET\" >/dev/null && mc ls obj/drops/_pgbackups/"],"envFrom":[{"secretRef":{"name":"forte-drop-secrets"}}]}]}}'
```
## Manually trigger a backup (before risky changes)
```bash
kubectl -n forte-drop create job --from=cronjob/forte-drop-pg-backup pg-backup-manual-$(date +%s)
# Watch:
kubectl -n forte-drop get jobs -l app.kubernetes.io/component=backup
kubectl -n forte-drop logs -l app.kubernetes.io/component=backup --tail=40
```
## Restore a dump
> **Destructive.** This overwrites the live `drops` database. Take a fresh manual
> backup first (above) and confirm with whoever owns the data before proceeding.
### 1. Pick the dump to restore
List backups (above), choose `forte-drop-<TS>.sql.gz`.
### 2. Run a restore pod that pulls the dump and pipes it into Postgres
```bash
DUMP="forte-drop-20260530T020000Z.sql.gz" # <-- set to the chosen file
kubectl -n forte-drop run pg-restore --rm -it --restart=Never \
--image=postgres:16-alpine \
--overrides='{
"spec": {
"containers": [{
"name": "restore",
"image": "postgres:16-alpine",
"command": ["sh","-c","set -euo pipefail; \
apk add --no-cache curl >/dev/null; \
# download via mc is simpler — use a 2-step instead (see note). \
echo placeholder"],
"envFrom": [
{"secretRef":{"name":"forte-drop-pg-creds"}},
{"secretRef":{"name":"forte-drop-secrets"}}
]
}]
}
}'
```
**Simpler 2-pod approach (recommended — avoids cramming mc + psql in one image):**
```bash
DUMP="forte-drop-20260530T020000Z.sql.gz"
# (a) Download the dump from object storage to a local file:
kubectl -n forte-drop run mc-get --rm -it --restart=Never \
--image=quay.io/minio/mc:RELEASE.2024-11-21T17-21-54Z \
--overrides='{"spec":{"containers":[{"name":"mc","image":"quay.io/minio/mc:RELEASE.2024-11-21T17-21-54Z","command":["sh","-c","mc alias set obj \"$S3_ENDPOINT\" \"$S3_KEY\" \"$S3_SECRET\" >/dev/null && mc cat obj/drops/_pgbackups/'"$DUMP"'"],"envFrom":[{"secretRef":{"name":"forte-drop-secrets"}}]}]}}' \
> /tmp/$DUMP
# (b) Pipe it into the live Postgres via the service:
gunzip -c /tmp/$DUMP | kubectl -n forte-drop run pg-restore --rm -i --restart=Never \
--image=postgres:16-alpine \
--overrides='{"spec":{"containers":[{"name":"psql","image":"postgres:16-alpine","stdin":true,"command":["sh","-c","PGPASSWORD=\"$pgpassword\" psql -h forte-drop-postgresql.forte-drop.svc -U \"$pgusername\" -d drops"],"env":[{"name":"pgusername","valueFrom":{"secretKeyRef":{"name":"forte-drop-pg-creds","key":"pgusername"}}},{"name":"pgpassword","valueFrom":{"secretKeyRef":{"name":"forte-drop-pg-creds","key":"pgpassword"}}}]}]}}'
```
> The app's schema is created idempotently on boot (`CREATE TABLE IF NOT EXISTS` +
> `ALTER TABLE ... ADD COLUMN IF NOT EXISTS` in `src/repo/pg.ts`), and `pg_dump`
> output includes the data. For a clean restore into a fresh DB this just works.
> To restore over an existing DB with conflicting rows, drop/recreate the `drops`
> database first (coordinate downtime — scale the web Deployment to 0 during the
> restore so the app isn't writing).
### 3. Verify
```bash
kubectl -n forte-drop run pg-check --rm -it --restart=Never \
--image=postgres:16-alpine \
--env="PGPASSWORD=$(kubectl -n forte-drop get secret forte-drop-pg-creds -o jsonpath='{.data.pgpassword}' | base64 -d)" \
--command -- psql -h forte-drop-postgresql.forte-drop.svc -U drops -d drops \
-c "SELECT count(*) AS drops FROM drops;" -c "SELECT count(*) AS view_hits FROM view_hits;"
```
### 4. Bring the app back
```bash
# If you scaled web to 0 for the restore:
kubectl -n forte-drop scale deploy/forte-drop --replicas=2
```
## Object data (uploaded drop files)
Drop files live in `s3://drops/<slug>/...` in the same managed bucket. They are
**not** part of the pg backup (the dump only holds metadata). Object storage is
UpCloud-managed/replicated, so no separate file backup is configured. If a
file-level backup is later required, mirror the bucket to a second bucket/region:
```bash
mc mirror --overwrite obj/drops/ backup-target/drops-mirror/
```
(Exclude `_pgbackups/` from the app-data mirror if you split them.)
## Disaster scenarios
| Scenario | Recovery |
|---|---|
| Postgres pod crash / reschedule | StatefulSet reattaches the PVC; ~1–2 min downtime; no data loss. |
| PVC lost / corrupted | Recreate StatefulSet, restore latest nightly dump (above). Data since last dump is lost. |
| Accidental `drops` table data loss | Restore latest dump; or `pg_restore` a single table from a dump. |
| Namespace deleted | PVC has `Prune=false,Delete=false`; recreate Applications, PVC re-binds, app recovers. Backups in object storage are independent. |
| Object storage bucket lost | UpCloud-managed (replicated). If the IAM key is rotated, update `forte-drop-secrets` (re-seal). |
@@ -0,0 +1,40 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: forte-drop-postgresql
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "0"
labels:
app.kubernetes.io/name: forte-drop-postgresql
app.kubernetes.io/part-of: apps
app.kubernetes.io/managed-by: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: ssh://git@git.forteapps.net:2222/Forte/launchpad.git
targetRevision: HEAD
path: apps/overlays/upc-dev/forte-drop-postgresql/resources
destination:
server: https://kubernetes.default.svc
namespace: forte-drop
syncPolicy:
automated:
prune: true
selfHeal: true
allowEmpty: false
syncOptions:
- CreateNamespace=true
- Validate=true
- ServerSideApply=true
ignoreDifferences:
- group: apps
kind: StatefulSet
jsonPointers:
- /spec/volumeClaimTemplates
@@ -1,4 +1,4 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- feedback.yaml
- forte-drop-postgresql.yaml
@@ -0,0 +1,14 @@
---
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
name: forte-drop-pg-creds
namespace: forte-drop
spec:
encryptedData:
pgpassword: AgBYokuQRCTmPGC8soB7n8W39nmSAZDTV97i77NmdMh5ndaZNtCtXxhMcpM2z8kaGv2tCWIh47dr38a5tGPZ0TsmeEQOF9Rbbtq1fyR7pJwT8S+N2Z2zu354wNWQlltEAVvTvthe2wTer/BpyRofeSZprxihmfNpuHUP8rLsnIXln5tOWDzJ8hnRWoYZFITaihC2qrJj/kFE0Rfdcmzt1tSq3jCB/rWijVaJF9XSh4rzQoqZDiUNjDPUjyERILw59JWU4zf9OKcqNHDnmpXBR4LSjLhd9waN6ElEzO4gGcVaHISKrTwewX1ONwPHDnw6lqkQObyBPx8aUsGzxLkUhNDtvIYDkB4BKWP5Qu4bNcSztIbrxi6l7Lr/DWC9qTbKm1/p83rc6r8VqRMURUyQg8/vBlCHOIUbZ8DM1OfNlMd8gvcSkaxEVIdCDUjguCvE3cGyG4cqv2unllZQ+9417WwLNJecT6x1EL3nQyAlK5c9vUIbcVyaFlbSUcGB7xmPgZrZ6/3RDyOH6Tmew1ssV9gLvdaehscUE0fjnnFnJpczkwdyxIOSNLIkjlWetCKEbhowJbzk05h3M2p6XQQOuNTnsYjAADMGD72GUgAQlY8KXmDELtv09KELcXbeYS4gABPpMrVmvZymq8lqQ13Py8o+cIqbrU5V86WxASTfQ5gMo/ymYabuhTBIapcnaKR1dFCCfu8deh5f2HJ6/1NjdWR+XvEshg+EF5OkTUInukX4vA==
pgusername: AgCs6vyQ8CIv5OneP/jMltIPGdZQbpq/BFmQM1mkBD61Ve+anzve5K0Gkg+zsNfbZf0pOPAXtu4C4aL1Lwv7gqpoe4Hp/UEb/X9uLfJ1b8ZitmM1XsPmmSiCskHjrc2BLkAvfrVIXkHc3LOY2uZ/E5stc6Ss2WFE8/uzzVXW0B8fdEK0criludQ8iwR1gypulEcDNomXgkK/1gmmCWosUcVv4jDMDhqBD+b9WYnBB6J73gUclWVMvYDFdNas2PuoRzu5Twc9TAZrTxN5lvLOXAonOo0YiUbUhEC83sfMWYDT5/9OxqcJhAxtgFe9j83MpCwLSwfeLZm7UsUapWDb60MxPJLGvoGD/ZOhkeYt/YCZYROa57TMslVIL5YU1KCiNWvtRjIqnvdiBxI7MRvPUfAoawS4ktT5PDhTTfrixFbaF95jul2kKBXV+OYB1UNsFhcCgZx9rzYRt4lNmBv4m4HeXIp3EYY8VlGLQ45BVVqjJ4QkISvb7ifQWH1aPMQllj+J3GwW0KJN0dEgsh1LT+C7W5I5mq461NOTF1eih/XRBeuPoLlgApxiGXvFCTx8lji2/JIdOaqcg29hdabSprxa0YMStChi2pbtHhRzAuFCp8mInGt8Q406vu67Y4/51yuwI40YeDVu0lf010TB+/v2Zy3OrNyjlqrD5JNynsLuRl3UhuAKC14Xhg/MiDLvTzfsYE8aog==
template:
metadata:
name: forte-drop-pg-creds
namespace: forte-drop
@@ -0,0 +1,6 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- postgresql.yaml
- forte-drop-pg-creds-sealed.yaml
- pg-backup-cronjob.yaml
@@ -0,0 +1,99 @@
# Nightly logical backup of the forte-drop Postgres → UpCloud Managed Object Storage.
# Dumps to s3://drops/_pgbackups/ (the `_` prefix is collision-proof: app slugs match
# /^[a-z0-9][a-z0-9-]{0,62}$/ and can never start with `_`). Retains 30 days.
#
# Pod shape: initContainer pg_dump → shared emptyDir → mc upload + retention prune.
# Both images pinned. S3 creds reuse forte-drop-secrets (the app's UpCloud user has
# s3:* on the drops bucket). PG creds from forte-drop-pg-creds.
apiVersion: batch/v1
kind: CronJob
metadata:
name: forte-drop-pg-backup
namespace: forte-drop
labels:
app.kubernetes.io/name: postgresql
app.kubernetes.io/instance: forte-drop
app.kubernetes.io/component: backup
spec:
schedule: "0 2 * * *" # 02:00 UTC daily
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 3
failedJobsHistoryLimit: 3
jobTemplate:
spec:
backoffLimit: 2
template:
metadata:
labels:
app.kubernetes.io/name: postgresql
app.kubernetes.io/instance: forte-drop
app.kubernetes.io/component: backup
spec:
restartPolicy: Never
securityContext:
runAsNonRoot: true
runAsUser: 65532
fsGroup: 65532
volumes:
- name: work
emptyDir: {}
initContainers:
- name: dump
image: postgres:16-alpine
command:
- sh
- -c
- |
set -euo pipefail
TS=$(date -u +%Y%m%dT%H%M%SZ)
echo "dumping to /work/forte-drop-${TS}.sql.gz"
PGPASSWORD="$PGPASSWORD" pg_dump \
-h forte-drop-postgresql.forte-drop.svc \
-p 5432 -U "$PGUSER" -d drops \
--no-owner --no-privileges \
| gzip -9 > "/work/forte-drop-${TS}.sql.gz"
echo "dump complete: $(ls -lh /work/)"
env:
- name: PGUSER
valueFrom:
secretKeyRef: { name: forte-drop-pg-creds, key: pgusername }
- name: PGPASSWORD
valueFrom:
secretKeyRef: { name: forte-drop-pg-creds, key: pgpassword }
volumeMounts:
- name: work
mountPath: /work
containers:
- name: upload
image: quay.io/minio/mc:RELEASE.2024-11-21T17-21-54Z
command:
- sh
- -c
- |
set -euo pipefail
mc alias set obj "$S3_ENDPOINT" "$S3_KEY" "$S3_SECRET"
mc cp /work/*.sql.gz "obj/${S3_BUCKET}/_pgbackups/"
echo "uploaded. pruning backups older than 30d:"
mc rm --recursive --force --older-than 30d "obj/${S3_BUCKET}/_pgbackups/" || true
echo "backup retention pass complete"
env:
# mc writes its config under $MC_CONFIG_DIR; point it at the shared
# emptyDir (writable by uid 65532 via fsGroup). Without this it tries
# to mkdir /.mc on the read-only-to-nonroot root fs -> "mkdir /.mc:
# permission denied" and every run fails before uploading.
- name: MC_CONFIG_DIR
value: "/work/.mc"
- name: S3_ENDPOINT
valueFrom:
secretKeyRef: { name: forte-drop-secrets, key: S3_ENDPOINT }
- name: S3_BUCKET
value: "drops"
- name: S3_KEY
valueFrom:
secretKeyRef: { name: forte-drop-secrets, key: S3_KEY }
- name: S3_SECRET
valueFrom:
secretKeyRef: { name: forte-drop-secrets, key: S3_SECRET }
volumeMounts:
- name: work
mountPath: /work
@@ -0,0 +1,105 @@
apiVersion: v1
kind: Service
metadata:
name: forte-drop-postgresql
namespace: forte-drop
labels:
app.kubernetes.io/name: postgresql
app.kubernetes.io/instance: forte-drop
app.kubernetes.io/component: database
spec:
type: ClusterIP
ports:
- name: tcp-postgresql
port: 5432
targetPort: tcp-postgresql
selector:
app.kubernetes.io/name: postgresql
app.kubernetes.io/instance: forte-drop
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: forte-drop-postgresql
namespace: forte-drop
labels:
app.kubernetes.io/name: postgresql
app.kubernetes.io/instance: forte-drop
app.kubernetes.io/component: database
spec:
serviceName: forte-drop-postgresql
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: postgresql
app.kubernetes.io/instance: forte-drop
template:
metadata:
labels:
app.kubernetes.io/name: postgresql
app.kubernetes.io/instance: forte-drop
app.kubernetes.io/component: database
spec:
containers:
- name: postgresql
image: postgres:16-alpine
# NOTE: no securityContext. The official postgres image's entrypoint must
# start as root to chown a fresh /var/lib/postgresql/data, then drops to
# the postgres user (uid 70 in alpine) via gosu. Forcing runAsNonRoot here
# breaks initdb on a fresh PVC. Matches the vaultwarden-postgresql pattern.
ports:
- name: tcp-postgresql
containerPort: 5432
env:
- name: POSTGRES_USER
valueFrom:
secretKeyRef:
name: forte-drop-pg-creds
key: pgusername
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: forte-drop-pg-creds
key: pgpassword
- name: POSTGRES_DB
value: drops
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
volumeMounts:
- name: data
mountPath: /var/lib/postgresql/data
livenessProbe:
exec:
command:
- sh
- -c
- pg_isready -U "$POSTGRES_USER" -d drops
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
exec:
command:
- sh
- -c
- pg_isready -U "$POSTGRES_USER" -d drops
initialDelaySeconds: 5
periodSeconds: 5
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 512Mi
volumeClaimTemplates:
- metadata:
name: data
annotations:
argocd.argoproj.io/sync-options: Prune=false,Delete=false
spec:
accessModes:
- ReadWriteOnce
storageClassName: upcloud-block-storage-maxiops
resources:
requests:
storage: 5Gi
@@ -0,0 +1,24 @@
# Keep at least 1 web pod up during voluntary disruptions (node drain, upgrade).
# Pairs with replicaCount: 2 so a drain can evict one pod while the other serves.
#
# Selector verified against live forteapp-chart deployments (mcp10x, argocd-mcp):
# the chart's pod selector is {app.kubernetes.io/instance, app.kubernetes.io/name,
# component: app} where instance/name == the ArgoCD Application (Helm release) name.
# Using all three labels also disambiguates the web pods from the forte-drop-mcp
# deployment that shares the forte-drop namespace (its instance/name == forte-drop-mcp).
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: forte-drop-web
namespace: forte-drop
labels:
app.kubernetes.io/name: forte-drop
app.kubernetes.io/part-of: apps
app.kubernetes.io/managed-by: argocd
spec:
minAvailable: 1
selector:
matchLabels:
app.kubernetes.io/instance: forte-drop
app.kubernetes.io/name: forte-drop
component: app
@@ -0,0 +1,24 @@
---
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
name: forte-drop-secrets
namespace: forte-drop
spec:
encryptedData:
BASE_DOMAIN: AgAFybdBryVb2AQuGQC8REXzW0YZlyycJp/KeXnROkW71UjDe4qMAWkWszrJWxZMvAPO/tXmibp7jEol6aB5GKG0k3tswWoprTFXLd9CMR2U9SWR3ZCol4npPXo7uOxhBcNSVt+cDXyejSiFTi6goY2oOtbKAJSF9Nv7Z5ePaqhhFni3ntcmM0S1Ad1l3QR7VvyazHFBXfO0b8Z9NgYsUNbGrXWDwoSAZIv3ly3wx90AXn+dXX5FNPtl9CtyAVhHsl3liwQdhEwS2krZZjj7NiQTCfNXp7BSB9ZETpo9KkoV4AZNy1zupd3HpeXHsyhHjq/JqXIAF3iFU0tZTWjhcwnehYdEU5oduwfLCWym5PYgpiQAGiazpkm1Ss3/PYpZYnR2nWv60b1Pa5i79ZiPNi4GL67AiWoJDw6QxV0Kbzi0AvUkZI1E2PeIJvv1w9NKdMRo49xK8LUx2qSTpWeqRP+1kzklHqclTuNVxiWtR2wUgdoLzvU7p5ETu7kPEmaoE8rYw4dKgQvHlMok2Ky2JsELGBkCiYjUN75T+yNlGs5dzbiwtWOja/r0dJ3ZGBQjcK4/BbTLiMYsrxmJTPPF/2zhCOlFY6cfcRMmc7Mwr68mK9m2rTOJQNjBMDoASiqVMmeSqfRSln7JNb1pAeq4xcz9YJMBJhPy2XNiBvRJK3pGIjVcNST0jSpic1X01NJTy7aFbcniZzYnsKJV61AQb+daGEsB1Ib3GnJ+Rv8+9NfvWg==
PASSWORD_GATE_SECRET: AgDIiAPb7bCpTeORoZXIn7Is/yKT7Qm+qSexgmXuXpe+WaLCJDWM5uOlvxp5tbtK2KD3SNtUDrOJQYfWYBOAPrBMBnSpDr8Ie7/wlJBxXCfROk1TldOU3s5O+5OnFfTDS9rAdcWdZdJ+Bt0aktnpuHJpjdurFca5o8ne5SRwYtGk6mNinCYRcnwMApZ9Y7IxvC9xzOi1QIhoOepMrotRkVJtbrXiclN5cI+P/nU8LaGwM4AEJtO3L8zY5QK30SZ/Tsz20Qo8HHGdIBLhHbhTALrFe8+n+Egda66Vr1DkKgDERW59PeWBd6xHCRGNuAk2ZzUmuiN5DJmWgacAGy9QblMMjhOIGKgiqsd4jx/8wC8FoyxBSstGFajahL2C6oFzCpbp5NiS185znN0ohJXqQzkp5RbcBliSYUZgHC8D1jDGxzcTksD2Hgh2NIAlJOStsfB3fUb6hWIFzxim0lyP9kFM5y92Sf5B1/h6PeHKOi1CMC5RFAEKhoyM4W1W+NxASJeT/NptTehcrjBPxLVKBPh/qgMcuNYNk34EB0asELalYBJ5cxzt31LLiLE/uDxCydXiSk7ACho7LxNffcUUakEwwsJDmjFiCscu83TfZq2vw5/2bgOzUng4XGDBgYwwr/KEHueGX4Keg255R3KqxyiMSKDi4CUEtYShjSsSYwU62rdogOQu20N3HGVn+/CksqKky7GoD958d4PwT5MGJwQhp8EjemBomDxTKNsO+C4moMNZpAEBtPlP65Lc1cfcaLlaFrwP4VtYHLXXBIvMzCC+
PGDATABASE: AgCCkY7AUaoy2V+fRXrEeLODlHOTXWXmWCW6MBWEY1J32QrN0bWUmwDdTxv5KHchbhH8lra9bABNOGfRcz59GIg8fH3fwTWOE8luh9cD50QkphjZPe9eGXc6YzheG0CG5hDnUoiJjnP9/l5cZc6sRxAo7for/bbLa0zja3VMzI+NMhVVRZT01G5R/Aoyf+B/TGm4mYbMyWAIgEEfjl50yIkc4WscfQrRkbxAtBF2qFuS3OL95TYSRULBAt0f3Z2WpCdS2b/pUyHJuuoi8aTo1a9QLFMrUdxVi6ydIKaMcx8xR8DlXQLOOdtIQu4TrgzGX2MgHwrbWuf/SFO5IWB/6/JI3yubo+i88M9LwPKsGeslcquoBG3Ibqlnmw8pcPrXUwBq2BowjAqpGsxdiR5XIi2j8QnpA8dTmFARX9CWKHoXFH5+uxx7SSTPG+izlGtVspsu5xx3F8MZ6eStInCyBimTVrn74+IvMPEBrj7wThO6Bl9EA3POkTRf6AmjuPItTgZK8lfXY/t0qDN5zApeB051+jSGZ0/o4PaY414vCm/+OteuCAI4ooCpDOwG3QD52VtpmoAvGjtuWExrvCHMW+hTIxsNbYJ+2SE1oQy8n8J5kKK+AU/SnYf/VEpbawHrpU48g6sDUHQzzPhoLN41j67qopmfRZAo1r5Tb4n7MnEOF3Yi7aT3lE6JvB3gZUSRqnDYSWObTg==
PGHOST: AgCWDPCQ4P+vPKQXmzNspODUJYVidwQDzFRxY8JDj5JYW1u+2xK8LqE2uWWYCW3YjF+H6yJoH6gu35KqOtjm0E23zMlknwZwkK+L93J/nrnNZHCznoub9jEzaAsTRhytvuIUBPQnFulP/t9Q04RjmR9c14puS7VS/tM57/cLq5zq/BiSYL3Wx2lblE7Xn6k44+8robPP3YkmlOuqziM1li/qevB/jxRV9YFjIpCz6D7WG6Kkx9CA6fzgalMJ5ErUVTJOuLDDty3qKd+9mg4WCLahjyzhPq/rIPUFE77EBDCulnnlMyvOSPSP7pMzkaNR1Ce3cOxOYAZ5mO2u5Y5pXt34V8iPe87N85KHQNZTu1Mu5ELMLdhEYOlO5ZX2x/RsdEuriWz1NW8MC57pEUGE/XrFhDe6x80eHi++qDRat2d04rTBOtlceZMmPVxs8tTa3VaD4PepekSogddLVooscLL7lYIpyR0Q6qOA1NdF2uOpx7hf6QLY1HeD4V8RSVVxV9uK5lUTrnBjtU/aUwnGs0xKxVNdBEKwG5Kh7qdsgyBiEa9V4Y2ElteGHave4J7xzimdASrbgMnlgIfcMuo94eX/0M0BAmpD4bEByMCWbcFgAfNnIOpXLZvoEfZ3DxVysmCzD+0nq+CEatdOjKp11EKbtf3H4PgnKqWDe2p8FNPXhev60CMhFvXS/QYg9fe2YJt/wqcN5MstCej4IuiOJkuFJ0UhZ/F5760xhuPYCUWP1suIVZM=
PGPASSWORD: AgBH32G+EUtc3jzGCA9bf27TCbzgK9xz+r4dqd0QQJL9xHbqgOARGVVaQ88AOkWV5VgYjqc/GFp51jLzVOHxgLdkqO/oCBuX9ajQEoGfq24AxFnaB7fh+Vlc3/N9yhT8lWoxHmHjyMVeX75g/9KvhNaRKBgiWQNHlt1C2FNh1h3U/aMfWVJIENmKKH2A5sxWe5haB7nynZc9r1QXBQKa7XVpuxAFXDHz3j3cFyR5Qflp+ac2APEM1/xbiaZDgkBtBd6dsDoCP56Dr1m91kaRGgbeX6WmRJ/Y89WAp4yt3QVfa8uGL1+DrBBMcfB1nAQKA45eZjPE6zTOEHxgTETCcmXJQiOzttDmBHRkIClOLLipgGDJwMqtgQoEMoJKjXMC0rsRy0NVRmibZa310R3PQjuHrQXxRD9ZAXkYg3opwLKeKi07b/7mvLHr7hU81fkBGnqNm/6heOSAqDZfRdregbBbcI/go72aypn2vQ5R+ozCdfwcp1tGla8FGpkI+zAdBKihp5Yo21VZ83FlIMq2JHF2+tv58C+LFeyqL1nr6BUmGKUQ+lEOnRzGYo1sbO5wBChc6yP3ZbzZYfxfvXAdfDY7vZUsareOC4uyR1wDnIiJgQ4kqmAKf7HulJJatKNgsvbmukj7c6lHLsfFRg5pwLO6iese9TZgtima2wkdcRpHSdt4ycnyHbwrEZ4kepfFlN1pGUl573/3l2cOdzO+WLCqV96P5myL6OOmCTxOaLdSyA==
PGPORT: AgCXyGjhlTcq3ffD+ZZZxe5gvqRLQl2MYGl7SfJDjw4LMdr3iojAeQNdYZIeZN4Bec9thhz1DI/Bc5Iiw5TiB3MAAvl/XtQM/T23JrSlUsfqECuHKQGw5kkgeVCds4j4d/IHFt9yBv4W4+ogcL5TZIGGgMiKa/99T77xgE4OqjeqqC+bpFuLObiKt5sYcqcHl2DDshTy4xeukfdA2yI3N9Tq7zfbLpZUKHWWe1gE+FbA0s+QA1ZCYv9uEtu/E5BWOYxE0u4GKhMDfMpguks0CNnIoRpovY3vmOxzFtqa7RpPoQrQtEN2Xtizu/G8K8p+mZLQ0cf3KX0rwp8tAsVKWrp8FYOy6+2OLYlhmDl1hK+M15czaL60DVBqiRIlQFdj9K+s5KQ0qfsG9m4v2WPSeCLEKwcsyLyq9vzXbDKwcMN2DOMOan8VDo04UZzkfdZa3lztfw7MWIdpvB0+cpgK3mdbbPCqSIQ8UTjeSVBgEz4EPUds81W8gqAgmeVKpUQplgFuLNwCTnCkuMSNiA93D3dgGccXzXiDrIBIcLzxHAWr0XEjISyD/pmNEowbDvXB5yRlNi1ZB5AHsRPurUYs4XtZN5ar+sxyex78tG18OkcBbABatBp3ol28Rs1LEN+HJupzNl4XDUx35/j3t/FPcw9PkcF+hzaQL7aWj4EDEWfjlzz5FEBlU5N6sd078tB79TpasuIk
PGUSER: AgDH9oSzu61NVOUlK7Evr278VuOx7ZxZgdsk+kdJmwpBGX7r5gmPrdomh/mqLYTuLokkanFiTfchRWHc76FvjbA/KxqwCq1qsZbW+dXrRtx/z1wQApKxNUJ7JolwMwP7tHE6QlGzO2mWj6RUROnhKpNybJXVvC3E5sSyz2QWC9hjamQP997RGA9yiiT/OShC7I6drFYR5cRDtpjW7Sy46qhMwlCRppiKh3wOV7qIAa0aPQE3Rfcg2WpK2ugRL1N+SiVnM+wPQwYVLiDaVF40vP40Kari99hIgmhcbjPeGG3kGX5VLww9KGm7iryrW3Yx45L/CCh1arUUpjkK2FGLVKtb3+YmDadnOA/I8Rr5kebhoMc93E3U3+mDfQA3cO/23xgpOJEGRCQwBlN9mazqkdq4zQkb4+nuxsdyQcxYtncgxhfCcZ0mXnbX2aW2kYcxKqa/jNjBcEpGMvos7dq6QzNq2nHrITo15S74M0292CAje2NFvKURA/KZnT26dDw3e5xa74E1nI/tBJEHWrUwRXpPu7naCZ2sZMxQV6ixQMuDakx3YamXZmMwgFO2FZ6ZL9BDDsbV4+JAsNwEaHGIIaTbE28R/xPIcUqcxrQV4ZWmHnJXFGyJ0XXxJ57GGjs7QwvvzAm+9WGYtlSC6H/8rX8uZIQLr3llVbJMuLpIv45i3p0Nkx8jyxGSG3rNQ4l3K0rjly2qZg==
S3_BUCKET: AgBcx2UvWafkVQ4fc+Xuc+gCLm5O5DceYSYBslL1bd8p4hKI/2hJF9z9UoLyAedyyrx8pHizcugkcIccrV1iyfQod2lDrivVJCy3qHz0mrQ6ZCOAk9ApNJTYUn+x/AYZL9EKVj/vfEzSwOTqlri6H53aUrZr8lv01TqLcrZDeG+jv64psydYKmSESkPl0iQ9DOx2sGkowNYbiTjux+ED9yXDMDdzojhMepbuUHhjNr7V7Z2NpX8+pNVz2o8o/oIA0zAJs3+C02018N3cyJ9P9BP2/qR6N31aykyI4P6GU5WlL0CDGKaheYy9ukM5x8SoU87yZBHLUxN0MhrMVKoFswU6CwTG/A8Gjkp84ce2cvlnuXP6JEMlqgSb+O+SeCH8+kwL9A87xmb42OOXSGokS188/2/13b6S733WL81B0RX1X1pOfWybpJzDhgiOC3Pjn18ItVHjKw3FlY6kwk1+P1vbZoRl343avRdyQyJam28A9UvZeTG+ac7drRTDndKYKGP8IWg/A32MEFSiKHsvnRQoYE7KORyzJxp610L1+w46KOUF5VF2afch02Jt4uLcmCOyUb6LdcWln3Jiz+wydJI1HK1RPio5oUT5d5se3K3DCa+GffbPMorHe/SdzqaBH2+uBcQn+wZO7JC1ei7hz4U1xdV41gkG1uq7Xz49ymW6P7G6qUr65MtvD73iMRlnJt/mdkw5UQ==
S3_ENDPOINT: AgDA5n96LeW1spJfmm7f9lcus5Ndv/91HAlhOfv0YwP+9R8rQhKF1sEL9Oi7Fpd4VJCQi9vDbeWYjGOMtsMqsLJivcZK6367rdSqL/YpKax9SkDgU+UG5Bpr5SDv4p5C3/J4+GPkZV+BduJQlNVPycTrdM80VBK8aALCNs7gGxy68v6mMgI90MpTp84ZeWgtJ7ZpTySivpwgycwcezldJTYDDjOvVfbK79trsRzDtjbAZzbDn0M8At4LYAfY7JTqcdCRHSGnzyg+81CX2jjyMOH0K55SsIqPEBdXqvh+VmC+jPyFMzfAN5hA8+otWTXfI7VucxAMZtRUiLU3I4/kIO60OkVjEf/SWrJv3nKb85aVfvRZ9k/A7kJ/ysKTXz2/iSAHU1DHTJlMW7SfhXciotx1p3BWOREF0QAQvrNCURFP+eZogBCIuiP38fD0gHkhG2BZkAY/OVZTr17a3F02kwemAuJ85hgUf0iwFV+/LD6X2OiqM85lnac/3B5Qp7UQf+Enn5RTqd90Pk/7QtAf/zTDmfxiWTkwpKXeQ62xcnHL9dQnUmDDs16eJZd04AJGgT8yrC//2lVOht7u6KuyG/+JXRxLBXAkGkr68lHBBuGmeVZ/t7ChL2Asyfu/uUTl9E1BOUfNvZXxwyLgqueL0Uw/MGLgswBsP6pYNGraMF+K5v7dse8cUxpyqk3s1C1zqoAhkehsCBtTjI+hF90G9Nh6mVSocoH12znt7fgUnwNbpg==
S3_KEY: AgAiciTM2ZNVlU1M1CNNXLkhCEYYbO7q5+Mp/DoC4OHBgIDKVfHurH39Dniuwxe6DcvE3vG2glRyTxQEg/ASLcwa7HBwBAwXe3wl1tRGM5Bp40/Vq935BXpkhcdp2fSoup8lPEbKS8q+L5LOqUlx7jmnkHXbI1tasz63KE8O9RUFDdQ8Gxy3nn/u4xkvibYxwmo60ApLKYgOu/ODPEETrWBcITHAVFUxbA8Kr9X9mPm3VpfrnFcUlxsCFr/zZwE/Y01eWdi8GGafb+apDPKMd7mAsLHFcPIQlpkHVT1M21qwwntZg9yV0RBACNu5BVPUgbmtUOQeWYMXn3FE+NJ7ajfdKAUCcEUV/f4s00b0S7jJTJwOUixDquMKSfu00AwDRCs8UcouikZe110uWnfEF3tVE0xQGF/3ItLni9VugBz7wQv7ACvmwnHmX5ZcjE0hxYcIS7ABWgHOZxgWoRWPao8eNAATipafcVIG1szl5ZMNTmAqHFyp2dlNU3zaiW6fz4q4CU7SrlhsrtqYM788qHvpJvDpFdF/i6oitH9CgpwmdCpH6YbBXxatnkWq9bqjEFcSZGDfDyT+iZaoPwhiOfaEoCyKlZ9RLLaK3E8zFcCDRXHnvnkqPtP/+VG30xz9pIat2EVB1N4b/kVIrr+fIM28mwk0vkC/tU8T55GF5BZr7VaYedHM9DVcQ4OJl7Ctrc9Ki8PXrne8gywyomA0F+YY9lxdDw==
S3_REGION: AgAAHTbNQ3gGnvg67ck2N5zSKKhMwR1j6pi/tZzYMEPK8jSnDTBkLYAt6ZVRtdsO+dG9kjnMsc/xTUMxJspbvQkgLSd9mG5FzJ37rBn+azCCSTDYBKq2ddGK1Yf/9w7MxOgN8aCyD4QCFOzR0EI49GbVYQynxDD5BwYuf7y4t2xCYt5wRGsjyNAmH3202Z90XKUkts8Na1hyD+xrrtrAtNfugyZqKo2WUSsHu82TD+cu5xZI51oQ9w9Mh9LaH3nfn/X5S+t17TjYvI7/c6hOwCVv10OdoaZa+SzqOvy7XxnqAShAYkPqJKfWhjecE1b9c/Cun32X4MRI0GBWyA02z4nR+WBbaVmasicx6hchVn8/wZeKMIl68F5LE7184MKKPNNwqsYslwFhuWq8dEw3TaVvnWgx3+cSMiX15SBwcLtE2UzJp+jjN/dpQ2MM0+6uV1GK4mNso5JAwGpUUUi2i+V1Ng7uXipI+5J9w6K0IMg1puGqFyahby42saSuH6vuPnjSx+2dXQTlgbl2SvCBoCgyOOJs4q5IafEvupAmRCNzx2HHv8/z6CFbSQZITQ3plmyNGLFXjynVw/6Q1PD4z3Pows4uVcYOEPbC0UoaXVgwgdBWa2N44BUhpdbqJUCyKuapLigpjKujG43jmdLzuk6gaPeH7SJTZKr624vs9hrGhzQYKdl6FZOQhmCFRDKXVCUiH2Z2pfwd3oo=
S3_SECRET: AgCcVQ7YtBAGpKBm+rE/hQBHrFlX5O0JO94xkZeoAppA9Tf8YR/PguZRGBWgLdNEJRI8C08lRRCUX3PY68jTySyjamb32iQkslOXGjAfnULeNGoGg05nLY2ZDYCEom6ieL8cc2xfbrV3yHoPQ7yVz9vcLjh1vATxyfdkqMapl8FpvQf0k0Zecmw3rLWE9y6vAn6Gb+/CWTnuhcW/8uDykmjIBTDQQddWshaZi+HosHyDbNxlnGj4U8mie68wytpS+Unp1gIWWE0hvelqO/3OUEEBB1OYMLV2DW8v86HXAE1Ix9jiCpSbyB+UzjOlrE/p4fJpeG4FtUC+/5ibRSxxgQRQYklKFJmdRDYWUnOngjgcT/Ewe41mTrpCUvb+jtir68pYLmVrLoha7S60w1YQHNkDAN2GftOyBjkkt6MtUDNzvNkfnKqKGUWyDSC27yfJdE/9k/4lDxQs0Sp20kIuz66/culBpg/s/oPSNs4SolCqG3GVLlKL775uqwLLuDN3txlPLb+Ex5vZAUapke+rn2zXzJVc1qlPfI/96vSEy6cx58LXdBadmBXn6c4Uy2MDa66EwsxOMXxzGLTd7AGkd5oeQVYfVPdTfGV5zx1AdzQhP3u/DD5FhKeWGDOr21iYB2jNm/P/hw0nFP2pf83W4/jLzPvuth1LF/WLF8cjclnGbcep2Kxrh/Xq0LmufofuVJyEI9/fl6onl5KIa6ZnVBJ8TsQesXJtNEKt9cPHiCvBKfLj5C+a4FlY
template:
metadata:
name: forte-drop-secrets
namespace: forte-drop
@@ -0,0 +1,37 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: forte-drop
namespace: argocd
annotations:
argocd.argoproj.io/sync-wave: "1"
# notifications.argoproj.io/subscribe.on-sync-succeeded.slack: ""
# notifications.argoproj.io/subscribe.on-sync-failed.slack: ""
# notifications.argoproj.io/subscribe.on-degraded.slack: ""
labels:
app.kubernetes.io/name: forte-drop
app.kubernetes.io/part-of: apps
app.kubernetes.io/managed-by: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
sources:
- repoURL: ssh://git@git.forteapps.net:2222/Forte/forte-helm.git
path: forteapp
targetRevision: HEAD
helm:
valueFiles:
- $values/forte-drop/values.yaml
- repoURL: ssh://git@git.forteapps.net:2222/Forte/helm-prod-values.git
targetRevision: HEAD
ref: values
destination:
server: https://kubernetes.default.svc
namespace: forte-drop
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
@@ -0,0 +1,33 @@
apiVersion: v1
kind: Secret
metadata:
name: keycloak-client-forte-drop
namespace: forte-drop
labels:
keycloak.forteapps.net/client-config: "true"
annotations:
keycloak.forteapps.net/source-namespace: "forte-drop"
stringData:
client.json: |
{
"clientId": "forte-drop",
"name": "Forte Drop (web)",
"enabled": true,
"protocol": "openid-connect",
"clientAuthenticatorType": "client-secret",
"standardFlowEnabled": true,
"directAccessGrantsEnabled": false,
"serviceAccountsEnabled": false,
"publicClient": false,
"redirectUris": ["https://drop.forteapps.net/auth/callback"],
"webOrigins": ["https://drop.forteapps.net"],
"defaultClientScopes": ["openid","email","profile"],
"secret": {
"namespace": "forte-drop",
"name": "forte-drop-oidc-credentials",
"keys": {
"clientId": "client-id",
"clientSecret": "client-secret"
}
}
}
@@ -0,0 +1,7 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- forte-drop.yaml
- keycloak-client-forte-drop.yaml
- forte-drop-pdb.yaml
- forte-drop-secrets-sealed.yaml
+8 -4
View File
@@ -2,8 +2,12 @@ apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
- dbunk-demo
- feedback
- forte-drop-postgresql
- forte-drop
- forte-drop-mcp
# No patches needed — base already has "upc-dev" paths
# upc-dev is the default/base cluster
# No patches needed — base apps already default to "upc-dev" value paths
# (upc-dev is the default/base cluster).
# forte-drop (postgres + web + mcp) and dbunk-demo are upc-dev-only apps — their
# values hardcode upc-dev hosts (drop.forteapps.net etc.) and must not sync to
# upc-prod, so they live here in the overlay rather than in apps/base/.
@@ -1,15 +0,0 @@
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: argocd-notifications-secret
namespace: argocd
spec:
type: kv-v2
mount: kv
path: argocd/argocd-notifications-secret
destination:
name: argocd-notifications-secret
create: true
type: Opaque
refreshAfter: 30s
vaultAuthRef: vault-auth
@@ -0,0 +1,15 @@
---
apiVersion: bitnami.com/v1alpha1
kind: SealedSecret
metadata:
creationTimestamp: null
name: azuredns-config
namespace: cert-manager
spec:
encryptedData:
client-secret: AgBPCix6yTt8gXV2pMfRx6weWtLWUeMa/cBwuaDBZkqZE4CTSDxjQWWK8ul5OtndlEX7+gXV2HpuAmHRhGC8P1z39yWRxIDbKf+AH4JOGSIfu57tnrGvzAwjanKtqeFCEM67Y42Kz1rahCEtdwJj2YIL9oVdCtRPkpAJrAJclqmrCvJZtqQYcDeEn5ONK5Gchxc6x2/J0U0LTjLz/MP74CEKw3CiZ+9VKA4ppvPqjqoE4yyAXSglSnhYTqtMZpFg5sr+at6rAB1ufmtiS70Vfks46OL0/ZJjdS6h5wjIZhl/1DgIFXqc1yoAjuEoMRzMeW9ji1PnjRqas4lU19tuEOf9/Eq65BYOqSIqJ3saG4I/+z013coCGCalo4ghuufmu5pcsi6ywcszz+g20N/PZcVcLbzZbMnKcXsaE007MDLxGY1QD89aribAvsypDUNEuNh7w2n/OAyQdw+nRDGIi8Oz3FoJUmC0HBxin2hstuBnrAXtxdqRX1NU25HfR2s3qt/yQ33TFx4xHA3NAll8Riyg2rvgqAKo9x9rmlcnzML011vlNu5oLKAuCqJlH8W/Nnnh6LNcZJy8Cj+fqXPeWHS4Qk7nEAbYJN9/sNAUg/VzsP0yYejPfjwzoDDaPLvTHROwv9nZ+Lr/U5epHr231jc5+i3x8dLuBtg+aa5PHoS/Ml1a4811w3Bxj3u36q8UPJGnszQXLKCpucynVVstAj4ufhXhhNXJdK/U31Zrc6j3Skw4zgF8Ddv0
template:
metadata:
creationTimestamp: null
name: azuredns-config
namespace: cert-manager
@@ -1,16 +0,0 @@
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
name: forte-helm-repo
namespace: argocd
spec:
type: kv-v2
mount: kv
path: argocd/forte-helm-repo
destination:
name: forte-helm-repo
create: true
labels:
argocd.argoproj.io/secret-type: repository
refreshAfter: 30s
vaultAuthRef: vault-auth

Some files were not shown because too many files have changed in this diff Show More