Repository: glasskube/glasskube
Pull request #1292 (2024-09-30 06:19:19)

fix(ui): clear previous modal content
<!-- Thanks for creating this pull request 🤗 Please make sure you followed the conventional commit -->

<!-- If this pull request closes an issue, please mention the issue number below -->

## 📑 Description
When opening a modal, the previous content is still there until it is swapped out. Sometimes on slow requests, that can be confusing. When the new modal request errors, there might be a toast shown but also the previous content is still there. This would be a hack to avoid that confusion. 

Use cases where this might occur: installing a namespaced package in gitops mode, using a namespace that does not exist yet. The dry-run installation will throw an error because of that, but the modal will still be shown with the previous content. 

## ✅ Checks
- [ ] My code requires changes to the documentation
- [ ] I have updated the documentation as required

## ℹ Additional Information
<!-- Any additional information like breaking changes, dependencies added, screenshots, comparisons between new and old behavior, etc. -->


Patch:
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index cb9d254..7f3f5c8 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
 {
-  ".": "0.22.0"
+  ".": "0.23.0"
 }
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1f87694..56fb4d5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,34 @@
 # Changelog
 
+## [0.23.0](https://github.com/glasskube/glasskube/compare/v0.22.0...v0.23.0) (2024-10-01)
+
+
+### Features
+
+* make auto update changeable on UI and show hint ([#1291](https://github.com/glasskube/glasskube/issues/1291)) ([69fe287](https://github.com/glasskube/glasskube/commit/69fe28722b80ec656efcc01f2d08d8a239ed0b4a))
+* show conflicts at package update ([#1282](https://github.com/glasskube/glasskube/issues/1282)) ([13e35c7](https://github.com/glasskube/glasskube/commit/13e35c71d38fe1db12f68d2c064b25b61ca6c6f9))
+* show package components ([#1283](https://github.com/glasskube/glasskube/issues/1283)) ([4525ce8](https://github.com/glasskube/glasskube/commit/4525ce897b0674193408c41a804d22f43ded6232))
+
+
+### Bug Fixes
+
+* avoid dependency errors during uninstallation ([#1284](https://github.com/glasskube/glasskube/issues/1284)) ([e0a09a8](https://github.com/glasskube/glasskube/commit/e0a09a8c0b0594616f16625c45f227d67a813bf6))
+* **deps:** update module github.com/fluxcd/helm-controller/api to v1.1.0 ([#1287](https://github.com/glasskube/glasskube/issues/1287)) ([3ed128c](https://github.com/glasskube/glasskube/commit/3ed128c83951fd1ca8ac3aefad41988830077893))
+* **deps:** update module github.com/fluxcd/source-controller/api to v1.4.1 ([#1281](https://github.com/glasskube/glasskube/issues/1281)) ([9621cce](https://github.com/glasskube/glasskube/commit/9621cce0c9899b015a5f4227a8b68c28e202a2b2))
+* **package-operator:** add auth support for relative manifest urls in package.yaml ([#1295](https://github.com/glasskube/glasskube/issues/1295)) ([bbd685a](https://github.com/glasskube/glasskube/commit/bbd685ad7ed586dc57a9c8fb2ac079c021f65805))
+
+
+### Other
+
+* **deps:** update dependency eslint-plugin-react to v7.37.0 ([#1288](https://github.com/glasskube/glasskube/issues/1288)) ([200a8fa](https://github.com/glasskube/glasskube/commit/200a8fa16267fa350806121da2eba27d224d6cd3))
+* **deps:** update dependency typescript-eslint to v8.8.0 ([#1293](https://github.com/glasskube/glasskube/issues/1293)) ([ded7312](https://github.com/glasskube/glasskube/commit/ded7312fe4e9af4412dcd0d32a8713cdc2893875))
+* **docs:** fix architecture diagram in readme ([#1289](https://github.com/glasskube/glasskube/issues/1289)) ([2dd9890](https://github.com/glasskube/glasskube/commit/2dd9890b8fc1e8989d1e3ccb797a6fb1440d3c52))
+
+
+### Docs
+
+* **website:** fix typo in website ([#1290](https://github.com/glasskube/glasskube/issues/1290)) ([5484e98](https://github.com/glasskube/glasskube/commit/5484e98322be7162b1d55fef18bdcf58bae5b549))
+
 ## [0.22.0](https://github.com/glasskube/glasskube/compare/v0.21.0...v0.22.0) (2024-09-25)
 
 
diff --git a/cmd/glasskube/cmd/bootstrap_git.go b/cmd/glasskube/cmd/bootstrap_git.go
index 6110a25..6448846 100644
--- a/cmd/glasskube/cmd/bootstrap_git.go
+++ b/cmd/glasskube/cmd/bootstrap_git.go
@@ -119,7 +119,7 @@ var bootstrapGitCmd = &cobra.Command{
 		argocdPath := baseURL.JoinPath("packages", "argo-cd", "clusterpackage.yaml").String()
 		argocdVersion := "v2.11.7+1"
 		argocdRepo := "glasskube"
-		if objs, err := clientutils.FetchResources(argocdPath); err != nil {
+		if objs, err := clientutils.FetchResourcesFromUrl(argocdPath); err != nil {
 			fmt.Fprintf(os.Stderr, "\nAn error occurred fetching argo-cd clusterpackage "+
 				"(will use %v from repo %v instead): %v\n", argocdVersion, argocdRepo, err)
 		} else if len(objs) != 1 {
@@ -184,7 +184,7 @@ var bootstrapGitCmd = &cobra.Command{
 		// apply bootstrap/glasskube-application.yaml into the cluster
 		// appPath := fmt.Sprintf("%v/bootstrap/glasskube-application.yaml", baseURL.String())
 		appPath := baseURL.JoinPath("bootstrap", "glasskube-application.yaml").String()
-		if objs, err := clientutils.FetchResources(appPath); err != nil {
+		if objs, err := clientutils.FetchResourcesFromUrl(appPath); err != nil {
 			fmt.Fprintf(os.Stderr, "\nAn error occurred fetching the bootstrap application:\n%v\n", err)
 			cliutils.ExitWithError()
 		} else {
diff --git a/cmd/glasskube/cmd/install.go b/cmd/glasskube/cmd/install.go
index 02a0dbd..098e85c 100644
--- a/cmd/glasskube/cmd/install.go
+++ b/cmd/glasskube/cmd/install.go
@@ -193,6 +193,15 @@ var installCmd = &cobra.Command{
 				installCmdOptions.EnableAutoUpdates = true
 			}
 		}
+		if installCmdOptions.EnableAutoUpdates {
+			ok, err := clientutils.IsAutoUpdaterInstalled(ctx)
+			if err != nil {
+				fmt.Fprintf(os.Stderr, "! Error: Could not check whether glasskube-autoupdater is installed: %v\n", err)
+			}
+			if !ok {
+				fmt.Fprintf(os.Stderr, "Please install glasskube-autoupdater for automatic updates to be applied.\n")
+			}
+		}
 
 		pkgBuilder.WithAutoUpdates(installCmdOptions.EnableAutoUpdates)
 
diff --git a/cmd/glasskube/cmd/update.go b/cmd/glasskube/cmd/update.go
index b239d8c..cc1d4e0 100644
--- a/cmd/glasskube/cmd/update.go
+++ b/cmd/glasskube/cmd/update.go
@@ -109,6 +109,16 @@ var updateCmd = &cobra.Command{
 				fmt.Fprintf(os.Stderr, "❌ update preparation failed: %v\n", err)
 				cliutils.ExitWithError()
 			}
+			if len(tx.ConflictItems) > 0 {
+				for _, conflictItem := range tx.ConflictItems {
+					for _, conflict := range conflictItem.Conflicts {
+						fmt.Fprintf(os.Stderr, "❌ Cannot Update %s due to dependency conflicts: %s\n"+
+							" (required: %s, actual: %s)\n",
+							conflictItem.Package.GetName(), conflict.Actual.Name, conflict.Required.Version, conflict.Actual.Version)
+					}
+				}
+				cliutils.ExitWithError()
+			}
 		}
 
 		if tx != nil && !tx.IsEmpty() {
diff --git a/config/autoupdater/kustomization.yaml b/config/autoupdater/kustomization.yaml
index 0f3e7d6..d7a099b 100644
--- a/config/autoupdater/kustomization.yaml
+++ b/config/autoupdater/kustomization.yaml
@@ -7,4 +7,4 @@ commonLabels:
   app.kubernetes.io/name: glasskube-autoupdate
 images:
   - name: ghcr.io/glasskube/glasskube
-    newTag: v0.22.0 # x-release-please-version
+    newTag: v0.23.0 # x-release-please-version
diff --git a/config/default/kustomization.yaml b/config/default/kustomization.yaml
index 1aae8b9..50b9399 100644
--- a/config/default/kustomization.yaml
+++ b/config/default/kustomization.yaml
@@ -34,7 +34,7 @@ resources:
 images:
   - name: controller
     newName: ghcr.io/glasskube/package-operator
-    newTag: v0.22.0 # x-release-please-version
+    newTag: v0.23.0 # x-release-please-version
 
 # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in
 # crd/kustomization.yaml
diff --git a/go.mod b/go.mod
index 989a384..d9dc81c 100644
--- a/go.mod
+++ b/go.mod
@@ -7,8 +7,8 @@ require (
 	github.com/denisbrodbeck/machineid v1.0.1
 	github.com/evanphx/json-patch/v5 v5.9.0
 	github.com/fatih/color v1.17.0
-	github.com/fluxcd/helm-controller/api v1.0.1
-	github.com/fluxcd/source-controller/api v1.3.0
+	github.com/fluxcd/helm-controller/api v1.1.0
+	github.com/fluxcd/source-controller/api v1.4.1
 	github.com/fsnotify/fsnotify v1.7.0
 	github.com/go-logr/logr v1.4.2
 	github.com/google/go-containerregistry v0.20.2
@@ -43,8 +43,8 @@ require (
 	github.com/emicklei/go-restful/v3 v3.11.2 // indirect
 	github.com/evanphx/json-patch v4.12.0+incompatible // indirect
 	github.com/fluxcd/pkg/apis/acl v0.3.0 // indirect
-	github.com/fluxcd/pkg/apis/kustomize v1.5.0 // indirect
-	github.com/fluxcd/pkg/apis/meta v1.5.0 // indirect
+	github.com/fluxcd/pkg/apis/kustomize v1.6.1 // indirect
+	github.com/fluxcd/pkg/apis/meta v1.6.1 // indirect
 	github.com/fxamacker/cbor/v2 v2.7.0 // indirect
 	github.com/go-errors/errors v1.4.2 // indirect
 	github.com/go-logr/zapr v1.3.0 // indirect
@@ -90,10 +90,10 @@ require (
 	go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 // indirect
 	go.uber.org/zap v1.26.0 // indirect
 	golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
-	golang.org/x/net v0.28.0 // indirect
+	golang.org/x/net v0.29.0 // indirect
 	golang.org/x/oauth2 v0.21.0 // indirect
 	golang.org/x/sys v0.25.0 // indirect
-	golang.org/x/text v0.17.0 // indirect
+	golang.org/x/text v0.18.0 // indirect
 	golang.org/x/time v0.5.0 // indirect
 	golang.org/x/tools v0.24.0 // indirect
 	gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
diff --git a/go.sum b/go.sum
index e11ad66..03caf91 100644
--- a/go.sum
+++ b/go.sum
@@ -32,16 +32,16 @@ github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0
 github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ=
 github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
 github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
-github.com/fluxcd/helm-controller/api v1.0.1 h1:Gn9qEVuif6D5+gHmVwTEZkR4+nmLOcOhKx4Sw2gL2EA=
-github.com/fluxcd/helm-controller/api v1.0.1/go.mod h1:/6AD5a2qjo/ttxVM8GR33syLZwqigta60DCLdy8GrME=
+github.com/fluxcd/helm-controller/api v1.1.0 h1:NS5Wm3U6Kv4w7Cw2sDOV++vf2ecGfFV00x1+2Y3QcOY=
+github.com/fluxcd/helm-controller/api v1.1.0/go.mod h1:BgHMgMY6CWynzl4KIbHpd6Wpn3FN9BqgkwmvoKCp6iE=
 github.com/fluxcd/pkg/apis/acl v0.3.0 h1:UOrKkBTOJK+OlZX7n8rWt2rdBmDCoTK+f5TY2LcZi8A=
 github.com/fluxcd/pkg/apis/acl v0.3.0/go.mod h1:WVF9XjSMVBZuU+HTTiSebGAWMgM7IYexFLyVWbK9bNY=
-github.com/fluxcd/pkg/apis/kustomize v1.5.0 h1:ah4sfqccnio+/5Edz/tVz6LetFhiBoDzXAElj6fFCzU=
-github.com/fluxcd/pkg/apis/kustomize v1.5.0/go.mod h1:nEzhnhHafhWOUUV8VMFLojUOH+HHDEsL75y54mt/c30=
-github.com/fluxcd/pkg/apis/meta v1.5.0 h1:/G82d2Az5D9op3F+wJUpD8jw/eTV0suM6P7+cSURoUM=
-github.com/fluxcd/pkg/apis/meta v1.5.0/go.mod h1:Y3u7JomuuKtr5fvP1Iji2/50FdRe5GcBug2jawNVkdM=
-github.com/fluxcd/source-controller/api v1.3.0 h1:Z5Lq0aJY87yg0cQDEuwGLKS60GhdErCHtsi546HUt10=
-github.com/fluxcd/source-controller/api v1.3.0/go.mod h1:+tfd0vltjcVs/bbnq9AlYR9AAHSVfM/Z4v4TpQmdJf4=
+github.com/fluxcd/pkg/apis/kustomize v1.6.1 h1:22FJc69Mq4i8aCxnKPlddHhSMyI4UPkQkqiAdWFcqe0=
+github.com/fluxcd/pkg/apis/kustomize v1.6.1/go.mod h1:5dvQ4IZwz0hMGmuj8tTWGtarsuxW0rWsxJOwC6i+0V8=
+github.com/fluxcd/pkg/apis/meta v1.6.1 h1:maLhcRJ3P/70ArLCY/LF/YovkxXbX+6sTWZwZQBeNq0=
+github.com/fluxcd/pkg/apis/meta v1.6.1/go.mod h1:YndB/gxgGZmKfqpAfFxyCDNFJFP0ikpeJzs66jwq280=
+github.com/fluxcd/source-controller/api v1.4.1 h1:zV01D7xzHOXWbYXr36lXHWWYS7POARsjLt61Nbh3kVY=
+github.com/fluxcd/source-controller/api v1.4.1/go.mod h1:gSjg57T+IG66SsBR0aquv+DFrm4YyBNpKIJVDnu3Ya8=
 github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
 github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
 github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
@@ -196,8 +196,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
-golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
+golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
+golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
 golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
 golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -215,8 +215,8 @@ golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM=
 golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
-golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
+golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
+golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
 golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
 golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
diff --git a/internal/clientutils/autoupdate.go b/internal/clientutils/autoupdate.go
index 3589c02..7ff964f 100644
--- a/internal/clientutils/autoupdate.go
+++ b/internal/clientutils/autoupdate.go
@@ -1,7 +1,12 @@
 package clientutils
 
 import (
+	"context"
+
+	"github.com/glasskube/glasskube/api/v1alpha1"
+	"github.com/glasskube/glasskube/internal/clicontext"
 	"github.com/glasskube/glasskube/internal/controller/ctrlpkg"
+	"k8s.io/apimachinery/pkg/api/errors"
 )
 
 func AutoUpdateString(pkg ctrlpkg.Package, disabledStr string) string {
@@ -13,3 +18,17 @@ func AutoUpdateString(pkg ctrlpkg.Package, disabledStr string) string {
 	}
 	return ""
 }
+
+func IsAutoUpdaterInstalled(ctx context.Context) (bool, error) {
+	client := clicontext.PackageClientFromContext(ctx)
+	var pkg v1alpha1.ClusterPackage
+	if err := client.ClusterPackages().Get(ctx, "glasskube-autoupdater", &pkg); err != nil {
+		if errors.IsNotFound(err) {
+			return false, nil
+		} else {
+			return false, err
+		}
+	} else {
+		return true, nil
+	}
+}
diff --git a/internal/clientutils/resources.go b/internal/clientutils/resources.go
index d14eda0..309f5ed 100644
--- a/internal/clientutils/resources.go
+++ b/internal/clientutils/resources.go
@@ -11,13 +11,26 @@ import (
 	"k8s.io/apimachinery/pkg/util/yaml"
 )
 
-func FetchResources(url string) ([]unstructured.Unstructured, error) {
+func FetchResourcesFromUrl(url string) ([]unstructured.Unstructured, error) {
+	if request, err := NewResourcesRequest(url); err != nil {
+		return nil, err
+	} else {
+		return FetchResources(request)
+	}
+}
+
+func NewResourcesRequest(url string) (*http.Request, error) {
 	request, err := http.NewRequest(http.MethodGet, url, nil)
 	if err != nil {
 		return nil, err
 	}
 	request.Header.Add("Accept", contenttype.MediaTypeJSON)
 	request.Header.Add("Accept", contenttype.MediaTypeYAML)
+	return request, nil
+}
+
+func FetchResources(request *http.Request) ([]unstructured.Unstructured, error) {
+	url := request.URL.Redacted()
 	response, err := httperror.CheckResponse(http.DefaultClient.Do(request))
 	if err != nil {
 		switch {
diff --git a/internal/controller/common.go b/internal/controller/common.go
index d8a8ac6..ab132b3 100644
--- a/internal/controller/common.go
+++ b/internal/controller/common.go
@@ -92,7 +92,7 @@ func (r *PackageReconcilerCommon) baseSetup(
 func (r *PackageReconcilerCommon) InitAdapters(builder *builder.Builder) error {
 	for _, adapter := range []manifest.ManifestAdapter{r.HelmAdapter, r.KustomizeAdapter, r.ManifestAdapter} {
 		if adapter != nil {
-			if err := adapter.ControllerInit(builder, r.Client, r.Scheme); err != nil {
+			if err := adapter.ControllerInit(builder, r.Client, r.RepoClientset, r.Scheme); err != nil {
 				return err
 			}
 		}
diff --git a/internal/manifest/adapter.go b/internal/manifest/adapter.go
index aa99256..ceede2f 100644
--- a/internal/manifest/adapter.go
+++ b/internal/manifest/adapter.go
@@ -6,6 +6,7 @@ import (
 	"github.com/glasskube/glasskube/api/v1alpha1"
 	"github.com/glasskube/glasskube/internal/controller/ctrlpkg"
 	"github.com/glasskube/glasskube/internal/manifest/result"
+	repoclient "github.com/glasskube/glasskube/internal/repo/client"
 	"github.com/glasskube/glasskube/internal/resourcepatch"
 	"k8s.io/apimachinery/pkg/runtime"
 	"sigs.k8s.io/controller-runtime/pkg/builder"
@@ -13,8 +14,14 @@ import (
 )
 
 type ManifestAdapter interface {
-	ControllerInit(builder *builder.Builder, client client.Client, scheme *runtime.Scheme) error
-	Reconcile(ctx context.Context,
+	ControllerInit(
+		builder *builder.Builder,
+		client client.Client,
+		repo repoclient.RepoClientset,
+		scheme *runtime.Scheme,
+	) error
+	Reconcile(
+		ctx context.Context,
 		pkg ctrlpkg.Package,
 		pi *v1alpha1.PackageInfo,
 		patches resourcepatch.TargetPatches,
diff --git a/internal/manifest/helm/flux/adapter.go b/internal/manifest/helm/flux/adapter.go
index 871357a..f7ae542 100644
--- a/internal/manifest/helm/flux/adapter.go
+++ b/internal/manifest/helm/flux/adapter.go
@@ -16,6 +16,7 @@ import (
 	"github.com/glasskube/glasskube/internal/manifest"
 	"github.com/glasskube/glasskube/internal/manifest/result"
 	"github.com/glasskube/glasskube/internal/names"
+	repoclient "github.com/glasskube/glasskube/internal/repo/client"
 	"github.com/glasskube/glasskube/internal/resourcepatch"
 	corev1 "k8s.io/api/core/v1"
 	extv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
@@ -38,7 +39,12 @@ func NewAdapter() manifest.ManifestAdapter {
 	return &FluxHelmAdapter{}
 }
 
-func (a *FluxHelmAdapter) ControllerInit(buildr *builder.Builder, client client.Client, scheme *runtime.Scheme) error {
+func (a *FluxHelmAdapter) ControllerInit(
+	buildr *builder.Builder,
+	client client.Client,
+	repo repoclient.RepoClientset,
+	scheme *runtime.Scheme,
+) error {
 	if err := sourcev1.AddToScheme(scheme); err != nil {
 		return err
 	}
diff --git a/internal/manifest/plain/adapter.go b/internal/manifest/plain/adapter.go
index cf8ab0c..6527888 100644
--- a/internal/manifest/plain/adapter.go
+++ b/internal/manifest/plain/adapter.go
@@ -13,6 +13,7 @@ import (
 	ownerutils "github.com/glasskube/glasskube/internal/controller/owners/utils"
 	"github.com/glasskube/glasskube/internal/manifest"
 	"github.com/glasskube/glasskube/internal/manifest/result"
+	repoclient "github.com/glasskube/glasskube/internal/repo/client"
 	"github.com/glasskube/glasskube/internal/resourcepatch"
 	appsv1 "k8s.io/api/apps/v1"
 	corev1 "k8s.io/api/core/v1"
@@ -29,6 +30,7 @@ var fieldOwner = client.FieldOwner("packages.glasskube.dev/package-controller")
 
 type Adapter struct {
 	client.Client
+	repo repoclient.RepoClientset
 	*owners.OwnerManager
 	namespaceGVK schema.GroupVersionKind
 }
@@ -38,11 +40,17 @@ func NewAdapter() manifest.ManifestAdapter {
 }
 
 // ControllerInit implements manifest.ManifestAdapter.
-func (a *Adapter) ControllerInit(builder *builder.Builder, client client.Client, scheme *runtime.Scheme) error {
+func (a *Adapter) ControllerInit(
+	builder *builder.Builder,
+	client client.Client,
+	repo repoclient.RepoClientset,
+	scheme *runtime.Scheme,
+) error {
 	if a.OwnerManager == nil {
 		a.OwnerManager = owners.NewOwnerManager(scheme)
 	}
 	a.Client = client
+	a.repo = repo
 	if nsGVK, err := client.GroupVersionKindFor(&corev1.Namespace{}); err != nil {
 		return err
 	} else {
@@ -115,9 +123,9 @@ func (r *Adapter) reconcilePlainManifest(
 ) ([]packagesv1alpha1.OwnedResourceRef, error) {
 	log := ctrl.LoggerFrom(ctx)
 	var objectsToApply []client.Object
-	if url, err := getActualManifestUrl(pi, manifest.Url); err != nil {
+	if request, err := r.newManifestRequest(pi, manifest.Url); err != nil {
 		return nil, err
-	} else if unstructured, err := clientutils.FetchResources(url); err != nil {
+	} else if unstructured, err := clientutils.FetchResources(request); err != nil {
 		return nil, err
 	} else {
 		// Unstructured implements client.Object but we need it as a reference so the interface is fulfilled.
@@ -125,7 +133,9 @@ func (r *Adapter) reconcilePlainManifest(
 		for i := range unstructured {
 			objectsToApply[i] = &unstructured[i]
 		}
-		log.V(1).Info("fetched "+url, "objectCount", len(objectsToApply))
+		log.V(1).Info("fetched manifest resources",
+			"url", request.URL.Redacted(),
+			"objectCount", len(objectsToApply))
 	}
 
 	if pkg.IsNamespaceScoped() {
diff --git a/internal/manifest/plain/url.go b/internal/manifest/plain/url.go
index 31cec75..9686bad 100644
--- a/internal/manifest/plain/url.go
+++ b/internal/manifest/plain/url.go
@@ -1,23 +1,28 @@
 package plain
 
 import (
+	"net/http"
 	"net/url"
 
 	packagesv1alpha1 "github.com/glasskube/glasskube/api/v1alpha1"
+	"github.com/glasskube/glasskube/internal/clientutils"
 )
 
-func getActualManifestUrl(pi *packagesv1alpha1.PackageInfo, urlOrPath string) (string, error) {
+func (r *Adapter) newManifestRequest(pi *packagesv1alpha1.PackageInfo, urlOrPath string) (*http.Request, error) {
 	if parsedUrl, err := url.Parse(urlOrPath); err != nil {
-		return "", err
+		return nil, err
 	} else if parsedUrl.Scheme == "" && parsedUrl.Host == "" {
 		if parsedBase, err := url.Parse(pi.Status.ResolvedUrl); err != nil {
-			return "", err
+			return nil, err
 		} else if ref, err := parsedBase.Parse(urlOrPath); err != nil {
-			return "", err
+			return nil, err
+		} else if request, err := clientutils.NewResourcesRequest(ref.String()); err != nil {
+			return nil, err
 		} else {
-			return ref.String(), nil
+			r.repo.ForRepoWithName(pi.Spec.RepositoryName).Authenticate(request)
+			return request, nil
 		}
 	} else {
-		return urlOrPath, nil
+		return clientutils.NewResourcesRequest(urlOrPath)
 	}
 }
diff --git a/internal/repo/client/auth/basic.go b/internal/repo/client/auth/basic.go
new file mode 100644
index 0000000..1fe3f2b
--- /dev/null
+++ b/internal/repo/client/auth/basic.go
@@ -0,0 +1,14 @@
+package auth
+
+import (
+	"net/http"
+)
+
+type basicAuthenticator struct {
+	username, password string
+}
+
+// Authenticate implements client.Authenticator.
+func (b *basicAuthenticator) Authenticate(request *http.Request) {
+	request.SetBasicAuth(b.username, b.password)
+}
diff --git a/internal/repo/client/auth/bearer.go b/internal/repo/client/auth/bearer.go
new file mode 100644
index 0000000..8d79a79
--- /dev/null
+++ b/internal/repo/client/auth/bearer.go
@@ -0,0 +1,14 @@
+package auth
+
+import (
+	"net/http"
+)
+
+type bearerAuthenticator struct {
+	token string
+}
+
+// Authenticate implements client.Authenticator.
+func (b *bearerAuthenticator) Authenticate(request *http.Request) {
+	request.Header.Set("Authorization", "Bearer "+b.token)
+}
diff --git a/internal/repo/client/auth/interface.go b/internal/repo/client/auth/interface.go
new file mode 100644
index 0000000..37e12ea
--- /dev/null
+++ b/internal/repo/client/auth/interface.go
@@ -0,0 +1,19 @@
+package auth
+
+import "net/http"
+
+type Authenticator interface {
+	Authenticate(request *http.Request)
+}
+
+func Basic(username, password string) Authenticator {
+	return &basicAuthenticator{username: username, password: password}
+}
+
+func Bearer(token string) Authenticator {
+	return &bearerAuthenticator{token: token}
+}
+
+func Noop() Authenticator {
+	return NoopAuthenticator{}
+}
diff --git a/internal/repo/client/auth/noop.go b/internal/repo/client/auth/noop.go
new file mode 100644
index 0000000..a1ebd69
--- /dev/null
+++ b/internal/repo/client/auth/noop.go
@@ -0,0 +1,11 @@
+package auth
+
+import (
+	"net/http"
+)
+
+type NoopAuthenticator struct{}
+
+// Authenticate implements client.Authenticator.
+func (n NoopAuthenticator) Authenticate(request *http.Request) {
+}
diff --git a/internal/repo/client/clientset.go b/internal/repo/client/clientset.go
index b1b0305..9a09dce 100644
--- a/internal/repo/client/clientset.go
+++ b/internal/repo/client/clientset.go
@@ -5,7 +5,6 @@ import (
 	"encoding/base64"
 	"errors"
 	"fmt"
-	"net/http"
 	"reflect"
 	"sync"
 	"time"
@@ -13,6 +12,7 @@ import (
 	"github.com/glasskube/glasskube/api/v1alpha1"
 	"github.com/glasskube/glasskube/internal/adapter"
 	"github.com/glasskube/glasskube/internal/controller/ctrlpkg"
+	"github.com/glasskube/glasskube/internal/repo/client/auth"
 	corev1 "k8s.io/api/core/v1"
 )
 
@@ -75,7 +75,7 @@ func (d *defaultClientset) ForRepoWithName(name string) RepoClient {
 	}
 	if len(name) > 0 {
 		if repo, err := d.client.GetPackageRepository(context.TODO(), name); err != nil {
-			return &errorclient{err}
+			return &errorclient{err: err}
 		} else {
 			return d.ForRepo(*repo)
 		}
@@ -87,14 +87,14 @@ func (d *defaultClientset) ForRepoWithName(name string) RepoClient {
 // Default implements RepoClientset.
 func (d *defaultClientset) Default() RepoClient {
 	if repos, err := d.client.ListPackageRepositories(context.TODO()); err != nil {
-		return &errorclient{err}
+		return &errorclient{err: err}
 	} else {
 		for _, repo := range repos.Items {
 			if repo.IsDefaultRepository() {
 				return d.ForRepo(repo)
 			}
 		}
-		return &errorclient{errors.New("default repository not found")}
+		return &errorclient{err: errors.New("default repository not found")}
 	}
 }
 
@@ -106,10 +106,10 @@ func (d *defaultClientset) ForRepo(repo v1alpha1.PackageRepository) RepoClient {
 		clientState.lastCheckedRepoSpec = time.Now()
 		return clientState.client
 	} else {
-		if headers, err := d.getAuthHeaders(repo); err != nil {
-			return &errorclient{fmt.Errorf("invalid auth config: %w", err)}
+		if auth, err := d.newAuthenticator(repo); err != nil {
+			return &errorclient{err: fmt.Errorf("invalid auth config: %w", err)}
 		} else {
-			client := New(repo.Spec.Url, headers, d.maxCacheAge)
+			client := New(repo.Spec.Url, auth, d.maxCacheAge)
 			d.clients[repo.Name] = repoClientWithState{
 				client:              client,
 				lastCheckedRepoSpec: time.Now(),
@@ -120,8 +120,7 @@ func (d *defaultClientset) ForRepo(repo v1alpha1.PackageRepository) RepoClient {
 	}
 }
 
-func (d *defaultClientset) getAuthHeaders(repo v1alpha1.PackageRepository) (http.Header, error) {
-	headers := http.Header{}
+func (d *defaultClientset) newAuthenticator(repo v1alpha1.PackageRepository) (auth.Authenticator, error) {
 	if repo.Spec.Auth != nil {
 		if repo.Spec.Auth.Basic != nil {
 			var user, pass string
@@ -159,9 +158,7 @@ func (d *defaultClientset) getAuthHeaders(repo v1alpha1.PackageRepository) (http
 					}
 				}
 			}
-			userpass := fmt.Sprintf("%v:%v", user, pass)
-			userpassEncoded := base64.StdEncoding.EncodeToString([]byte(userpass))
-			headers.Set("Authorization", fmt.Sprintf("Basic %v", userpassEncoded))
+			return auth.Basic(user, pass), nil
 		} else if repo.Spec.Auth.Bearer != nil {
 			var token string
 			if repo.Spec.Auth.Bearer.Token != nil {
@@ -176,10 +173,10 @@ func (d *defaultClientset) getAuthHeaders(repo v1alpha1.PackageRepository) (http
 					token = t
 				}
 			}
-			headers.Set("Authorization", fmt.Sprintf("Bearer %v", token))
+			return auth.Bearer(token), nil
 		}
 	}
-	return headers, nil
+	return auth.Noop(), nil
 }
 
 // Meta implements RepoClientset.
diff --git a/internal/repo/client/defaultclient.go b/internal/repo/client/defaultclient.go
index db82231..571b8b7 100644
--- a/internal/repo/client/defaultclient.go
+++ b/internal/repo/client/defaultclient.go
@@ -13,13 +13,14 @@ import (
 	"github.com/glasskube/glasskube/api/v1alpha1"
 	"github.com/glasskube/glasskube/internal/contenttype"
 	"github.com/glasskube/glasskube/internal/httperror"
+	"github.com/glasskube/glasskube/internal/repo/client/auth"
 	"github.com/glasskube/glasskube/internal/repo/types"
 	"k8s.io/apimachinery/pkg/util/yaml"
 )
 
 type defaultClient struct {
+	auth.Authenticator
 	url         string
-	headers     http.Header
 	maxCacheAge time.Duration
 	cache       sync.Map
 	debug       bool
@@ -31,12 +32,12 @@ type cacheItem struct {
 	mutex   sync.Mutex
 }
 
-func New(url string, headers http.Header, maxCacheAge time.Duration) *defaultClient {
-	return &defaultClient{url: url, headers: headers, maxCacheAge: maxCacheAge}
+func New(url string, authenticator auth.Authenticator, maxCacheAge time.Duration) *defaultClient {
+	return &defaultClient{url: url, Authenticator: authenticator, maxCacheAge: maxCacheAge}
 }
 
-func NewDebug(url string, headers http.Header, maxCacheAge time.Duration) *defaultClient {
-	c := New(url, headers, maxCacheAge)
+func NewDebug(url string, authenticator auth.Authenticator, maxCacheAge time.Duration) *defaultClient {
+	c := New(url, authenticator, maxCacheAge)
 	c.debug = true
 	return c
 }
@@ -125,16 +126,12 @@ func (c *defaultClient) fetchYAMLOrJSON(url string, target any) error {
 	}
 
 	request, err := http.NewRequest(http.MethodGet, url, nil)
-	request.Header.Add("Accept", contenttype.MediaTypeJSON)
-	request.Header.Add("Accept", contenttype.MediaTypeYAML)
 	if err != nil {
 		return err
 	}
-	for k, v := range c.headers {
-		for _, s := range v {
-			request.Header.Add(k, s)
-		}
-	}
+	c.Authenticate(request)
+	request.Header.Add("Accept", contenttype.MediaTypeJSON)
+	request.Header.Add("Accept", contenttype.MediaTypeYAML)
 	resp, err := httperror.CheckResponse(http.DefaultClient.Do(request))
 	if err != nil {
 		return fmt.Errorf("failed to fetch %v: %w", url, err)
diff --git a/internal/repo/client/errorclient.go b/internal/repo/client/errorclient.go
index 59b4374..e60a31a 100644
--- a/internal/repo/client/errorclient.go
+++ b/internal/repo/client/errorclient.go
@@ -2,12 +2,14 @@ package client
 
 import (
 	"github.com/glasskube/glasskube/api/v1alpha1"
+	"github.com/glasskube/glasskube/internal/repo/client/auth"
 	"github.com/glasskube/glasskube/internal/repo/types"
 )
 
 // errorclient is a no-op implementation of RepoClient that defers returning an error to the method call.
 // This is done to improve ergonomics of the RepoClientset, such that the ForRepo function does not return an error.
 type errorclient struct {
+	auth.NoopAuthenticator
 	err error
 }
 
diff --git a/internal/repo/client/fake/fakeclient.go b/internal/repo/client/fake/fakeclient.go
index 10723a4..ecd2c5e 100644
--- a/internal/repo/client/fake/fakeclient.go
+++ b/internal/repo/client/fake/fakeclient.go
@@ -6,57 +6,81 @@ import (
 	"github.com/glasskube/glasskube/api/v1alpha1"
 	"github.com/glasskube/glasskube/internal/controller/ctrlpkg"
 	"github.com/glasskube/glasskube/internal/repo/client"
+	"github.com/glasskube/glasskube/internal/repo/client/auth"
 	"github.com/glasskube/glasskube/internal/repo/types"
 )
 
-type FakeClientset struct {
-	Client *FakeClient
+func ClientsetWithClient(client *fakeClient) *fakeClientset {
+	return &fakeClientset{Client: client}
+}
+
+func EmptyClientset() *fakeClientset {
+	return ClientsetWithClient(EmptyClient())
+}
+
+type fakeClientset struct {
+	Client *fakeClient
 }
 
 // Default implements client.RepoClientset.
-func (f *FakeClientset) Default() client.RepoClient {
+func (f *fakeClientset) Default() client.RepoClient {
 	return f.Client
 }
 
 // Meta implements client.RepoClientset.
-func (f *FakeClientset) Meta() client.RepoMetaclient {
+func (f *fakeClientset) Meta() client.RepoMetaclient {
 	return f.Client
 }
 
 // ForRepo implements client.RepoClientset.
-func (f *FakeClientset) ForRepo(repo v1alpha1.PackageRepository) client.RepoClient {
+func (f *fakeClientset) ForRepo(repo v1alpha1.PackageRepository) client.RepoClient {
 	return f.Client
 }
 
 // ForRepoWithName implements client.RepoClientset.
-func (f *FakeClientset) ForRepoWithName(name string) client.RepoClient {
+func (f *fakeClientset) ForRepoWithName(name string) client.RepoClient {
 	return f.Client
 }
 
 // ForPackage implements client.RepoClientset.
-func (f *FakeClientset) ForPackage(pkg ctrlpkg.Package) client.RepoClient {
+func (f *fakeClientset) ForPackage(pkg ctrlpkg.Package) client.RepoClient {
 	return f.Client
 }
 
-var _ client.RepoClientset = &FakeClientset{}
+var _ client.RepoClientset = &fakeClientset{}
 
-// FakeClient is a mock implementation of RepoClient for use in tests
-type FakeClient struct {
+// fakeClient is a mock implementation of RepoClient for use in tests
+type fakeClient struct {
+	auth.Authenticator
 	Packages            map[string]map[string]*v1alpha1.PackageManifest
 	PackageRepositories []v1alpha1.PackageRepository
 }
 
+func EmptyClient() *fakeClient {
+	return &fakeClient{
+		Authenticator:       auth.Noop(),
+		PackageRepositories: []v1alpha1.PackageRepository{{}},
+	}
+}
+
+func EmptyClientWithAuth(auth auth.Authenticator) *fakeClient {
+	return &fakeClient{
+		Authenticator:       auth,
+		PackageRepositories: []v1alpha1.PackageRepository{{}},
+	}
+}
+
 // FetchMetaIndex implements client.RepoMetaclient.
-func (f *FakeClient) FetchMetaIndex(target *types.MetaIndex) error {
+func (f *fakeClient) FetchMetaIndex(target *types.MetaIndex) error {
 	panic("unimplemented")
 }
 
 // GetReposForPackage implements client.RepoAggregator.
-func (f *FakeClient) GetReposForPackage(name string) ([]v1alpha1.PackageRepository, error) {
+func (f *fakeClient) GetReposForPackage(name string) ([]v1alpha1.PackageRepository, error) {
 	return f.PackageRepositories, nil
 }
 
-func (f *FakeClient) AddPackage(name, version string, manifest *v1alpha1.PackageManifest) {
+func (f *fakeClient) AddPackage(name, version string, manifest *v1alpha1.PackageManifest) {
 	if f.Packages == nil {
 		f.Clear()
 	}
@@ -66,14 +90,14 @@ func (f *FakeClient) AddPackage(name, version string, manifest *v1alpha1.Package
 	f.Packages[name][version] = manifest
 }
 
-func (f *FakeClient) Clear() {
+func (f *fakeClient) Clear() {
 	f.Packages = map[string]map[string]*v1alpha1.PackageManifest{}
 }
 
-var _ client.RepoClient = &FakeClient{}
+var _ client.RepoClient = &fakeClient{}
 
 // FetchLatestPackageManifest implements client.RepoClient.
-func (f *FakeClient) FetchLatestPackageManifest(name string, target *v1alpha1.PackageManifest) (
+func (f *fakeClient) FetchLatestPackageManifest(name string, target *v1alpha1.PackageManifest) (
 	version string, err error,
 ) {
 	if versions, ok := f.Packages[name]; ok {
@@ -86,7 +110,7 @@ func (f *FakeClient) FetchLatestPackageManifest(name string, target *v1alpha1.Pa
 }
 
 // FetchPackageIndex implements client.RepoClient.
-func (f *FakeClient) FetchPackageIndex(name string, target *types.PackageIndex) error {
+func (f *fakeClient) FetchPackageIndex(name string, target *types.PackageIndex) error {
 	if versions, ok := f.Packages[name]; ok {
 		var result types.PackageIndex
 		for v := range versions {
@@ -100,7 +124,7 @@ func (f *FakeClient) FetchPackageIndex(name string, target *types.PackageIndex)
 }
 
 // FetchPackageManifest implements client.RepoClient.
-func (f *FakeClient) FetchPackageManifest(name string, version string, target *v1alpha1.PackageManifest) error {
+func (f *fakeClient) FetchPackageManifest(name string, version string, target *v1alpha1.PackageManifest) error {
 	if versions, ok := f.Packages[name]; ok {
 		if manifest, ok := versions[version]; ok {
 			*target = *manifest
@@ -111,7 +135,7 @@ func (f *FakeClient) FetchPackageManifest(name string, version string, target *v
 }
 
 // FetchPackageRepoIndex implements client.RepoClient.
-func (f *FakeClient) FetchPackageRepoIndex(target *types.PackageRepoIndex) error {
+func (f *fakeClient) FetchPackageRepoIndex(target *types.PackageRepoIndex) error {
 	var result types.PackageRepoIndex
 	for pkg, versions := range f.Packages {
 		item := types.PackageRepoIndexItem{Name: pkg}
@@ -125,7 +149,7 @@ func (f *FakeClient) FetchPackageRepoIndex(target *types.PackageRepoIndex) error
 }
 
 // GetLatestVersion implements client.RepoClient.
-func (f *FakeClient) GetLatestVersion(pkgName string) (string, error) {
+func (f *fakeClient) GetLatestVersion(pkgName string) (string, error) {
 	if versions, ok := f.Packages[pkgName]; ok {
 		for v := range versions {
 			return v, nil
@@ -135,6 +159,6 @@ func (f *FakeClient) GetLatestVersion(pkgName string) (string, error) {
 }
 
 // GetPackageManifestURL implements client.RepoClient.
-func (f *FakeClient) GetPackageManifestURL(name string, version string) (string, error) {
+func (f *fakeClient) GetPackageManifestURL(name string, version string) (string, error) {
 	return "fake url", nil
 }
diff --git a/internal/repo/client/repoclient.go b/internal/repo/client/repoclient.go
index cec7e64..2614347 100644
--- a/internal/repo/client/repoclient.go
+++ b/internal/repo/client/repoclient.go
@@ -3,6 +3,7 @@ package client
 import (
 	packagesv1alpha1 "github.com/glasskube/glasskube/api/v1alpha1"
 	"github.com/glasskube/glasskube/internal/controller/ctrlpkg"
+	"github.com/glasskube/glasskube/internal/repo/client/auth"
 	"github.com/glasskube/glasskube/internal/repo/types"
 )
 
@@ -11,6 +12,7 @@ type LatestVersionGetter interface {
 }
 
 type RepoClient interface {
+	auth.Authenticator
 	LatestVersionGetter
 	FetchPackageRepoIndex(target *types.PackageRepoIndex) error
 	FetchLatestPackageManifest(name string, target *packagesv1alpha1.PackageManifest) (version string, err error)
diff --git a/internal/web/discussions.go b/internal/web/discussions.go
index 352758e..bd95a1e 100644
--- a/internal/web/discussions.go
+++ b/internal/web/discussions.go
@@ -5,9 +5,10 @@ import (
 	"net/http"
 	"os"
 
+	"github.com/glasskube/glasskube/internal/clientutils"
+
 	"github.com/glasskube/glasskube/internal/web/components/toast"
 
-	"github.com/glasskube/glasskube/internal/clientutils"
 	"github.com/glasskube/glasskube/internal/web/util"
 
 	"github.com/glasskube/glasskube/internal/giscus"
@@ -100,17 +101,21 @@ func (s *server) handlePackageDiscussionPage(w http.ResponseWriter, r *http.Requ
 
 	pkgHref := util.GetPackageHrefWithFallback(d.pkg, d.manifest)
 
-	err := s.templates.pkgDiscussionPageTmpl.Execute(w, s.enrichPage(r, map[string]any{
-		"Giscus":             giscus.Client().Config,
-		"Package":            d.pkg,
-		"Status":             client.GetStatusOrPending(d.pkg),
-		"Manifest":           d.manifest,
-		"LatestVersion":      idx.LatestVersion,
-		"UpdateAvailable":    s.isUpdateAvailableForPkg(r.Context(), d.pkg),
-		"ShowDiscussionLink": true,
-		"PackageHref":        pkgHref,
-		"DiscussionHref":     fmt.Sprintf("%s/discussion", pkgHref),
-		"AutoUpdate":         clientutils.AutoUpdateString(d.pkg, "Disabled"),
+	autoUpdaterInstalled, err := clientutils.IsAutoUpdaterInstalled(r.Context())
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "failed to check whether auto updater is installed: %v\n", err)
+	}
+	err = s.templates.pkgDiscussionPageTmpl.Execute(w, s.enrichPage(r, map[string]any{
+		"Giscus":               giscus.Client().Config,
+		"Package":              d.pkg,
+		"Status":               client.GetStatusOrPending(d.pkg),
+		"Manifest":             d.manifest,
+		"LatestVersion":        idx.LatestVersion,
+		"UpdateAvailable":      s.isUpdateAvailableForPkg(r.Context(), d.pkg),
+		"ShowDiscussionLink":   true,
+		"PackageHref":          pkgHref,
+		"DiscussionHref":       fmt.Sprintf("%s/discussion", pkgHref),
+		"AutoUpdaterInstalled": autoUpdaterInstalled,
 	}, nil))
 	util.CheckTmplError(err, fmt.Sprintf("package-discussion (%s)", d.request.manifestName))
 }
diff --git a/internal/web/packagedetail.go b/internal/web/packagedetail.go
index 70c3900..8fa26a9 100644
--- a/internal/web/packagedetail.go
+++ b/internal/web/packagedetail.go
@@ -224,13 +224,17 @@ func (s *server) renderPackageDetailPage(ctx context.Context, r *http.Request, w
 	if err != nil {
 		fmt.Fprintf(os.Stderr, "failed to get advanced options from cookie: %v\n", err)
 	}
+
+	autoUpdaterInstalled, err := clientutils.IsAutoUpdaterInstalled(r.Context())
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "failed to check whether auto updater is installed: %v\n", err)
+	}
 	templateData := map[string]any{
 		"Package":              p.pkg,
 		"Status":               client.GetStatusOrPending(p.pkg),
 		"Manifest":             p.manifest,
 		"LatestVersion":        latestVersion,
 		"UpdateAvailable":      s.isUpdateAvailableForPkg(r.Context(), p.pkg),
-		"AutoUpdate":           clientutils.AutoUpdateString(p.pkg, "Disabled"),
 		"ValidationResult":     validationResult,
 		"ShowConflicts":        validationResult.Status == dependency.ValidationResultStatusConflict,
 		"SelectedVersion":      p.request.version,
@@ -244,6 +248,7 @@ func (s *server) renderPackageDetailPage(ctx context.Context, r *http.Request, w
 		"PackageHref":          webutil.GetPackageHrefWithFallback(p.pkg, p.manifest),
 		"AdvancedOptions":      advancedOptions,
 		"LostValueDefinitions": lostValueDefinitions,
+		"AutoUpdaterInstalled": autoUpdaterInstalled,
 	}
 
 	if p.request.component == "header" {
@@ -307,7 +312,7 @@ func (s *server) installOrConfigurePackage(w http.ResponseWriter, r *http.Reques
 	ctx := r.Context()
 	namespace := r.FormValue("namespace")
 	name := r.FormValue("name")
-	enableAutoUpdate := r.FormValue("enableAutoUpdate")
+	autoUpdate := strings.ToLower(r.FormValue("autoUpdate")) == "on"
 	dryRun, _ := strconv.ParseBool(r.FormValue("dryRun"))
 
 	var err error
@@ -361,7 +366,7 @@ func (s *server) installOrConfigurePackage(w http.ResponseWriter, r *http.Reques
 		pkg = client.PackageBuilder(p.manifestName).
 			WithVersion(p.version).
 			WithRepositoryName(p.repositoryName).
-			WithAutoUpdates(strings.ToLower(enableAutoUpdate) == "on").
+			WithAutoUpdates(autoUpdate).
 			WithValues(values).
 			WithNamespace(namespace).
 			WithName(name).
@@ -383,6 +388,7 @@ func (s *server) installOrConfigurePackage(w http.ResponseWriter, r *http.Reques
 		pkg.Spec.PackageInfo.Version = p.version
 		pkg.Spec.PackageInfo.RepositoryName = p.repositoryName
 		pkg.Spec.Values = values
+		pkg.SetAutoUpdatesEnabled(autoUpdate)
 		opts := v1.UpdateOptions{}
 		if dryRun {
 			opts.DryRun = []string{v1.DryRunAll}
@@ -415,7 +421,7 @@ func (s *server) installOrConfigurePackage(w http.ResponseWriter, r *http.Reques
 // being set in the packages spec.
 func (s *server) installOrConfigureClusterPackage(w http.ResponseWriter, r *http.Request, p *packageContextRequest) {
 	ctx := r.Context()
-	enableAutoUpdate := r.FormValue("enableAutoUpdate")
+	autoUpdate := strings.ToLower(r.FormValue("autoUpdate")) == "on"
 	dryRun, _ := strconv.ParseBool(r.FormValue("dryRun"))
 
 	var err error
@@ -451,7 +457,7 @@ func (s *server) installOrConfigureClusterPackage(w http.ResponseWriter, r *http
 		pkg = client.PackageBuilder(p.manifestName).
 			WithVersion(p.version).
 			WithRepositoryName(p.repositoryName).
-			WithAutoUpdates(strings.ToLower(enableAutoUpdate) == "on").
+			WithAutoUpdates(autoUpdate).
 			WithValues(values).
 			BuildClusterPackage()
 		opts := v1.CreateOptions{}
@@ -473,6 +479,7 @@ func (s *server) installOrConfigureClusterPackage(w http.ResponseWriter, r *http
 		pkg.Spec.PackageInfo.Version = p.version
 		pkg.Spec.PackageInfo.RepositoryName = p.repositoryName
 		pkg.Spec.Values = values
+		pkg.SetAutoUpdatesEnabled(autoUpdate)
 		opts := v1.UpdateOptions{}
 		if dryRun {
 			opts.DryRun = []string{v1.DryRunAll}
diff --git a/internal/web/server.go b/internal/web/server.go
index 50ed03f..6bc29ea 100644
--- a/internal/web/server.go
+++ b/internal/web/server.go
@@ -393,6 +393,7 @@ func (s *server) update(w http.ResponseWriter, r *http.Request) {
 			"UpdateTransactionId": utId,
 			"Updates":             updates,
 			"PackageHref":         packageHref,
+			"ConflictItems":       updateTx.ConflictItems,
 		})
 		util.CheckTmplError(err, "pkgUpdateModalTmpl")
 	}
@@ -1273,6 +1274,8 @@ func (s *server) isUpdateAvailable(ctx context.Context, pkgs []ctrlpkg.Package)
 	if tx, err := update.NewUpdater(ctx).Prepare(ctx, update.GetExact(pkgs)); err != nil {
 		fmt.Fprintf(os.Stderr, "Error checking for updates: %v\n", err)
 		return false
+	} else if len(tx.ConflictItems) > 0 {
+		return true
 	} else {
 		return !tx.IsEmpty()
 	}
diff --git a/internal/web/sse/broadcaster.go b/internal/web/sse/broadcaster.go
index 5916218..9c94109 100644
--- a/internal/web/sse/broadcaster.go
+++ b/internal/web/sse/broadcaster.go
@@ -59,10 +59,10 @@ func (b *Broadcaster) UpdatesAvailableForPackage(oldPkg ctrlpkg.Package, newPkg
 	if oldPkg != nil && !oldPkg.IsNil() && newPkg != nil && !newPkg.IsNil() {
 		if !reflect.DeepEqual(oldPkg.GetSpec(), newPkg.GetSpec()) {
 			b.UpdatesAvailable(refresh.RefreshTriggerAll, newPkg)
-		} else if !reflect.DeepEqual(oldPkg.GetStatus(), newPkg.GetStatus()) {
-			b.UpdatesAvailable(refresh.RefreshTriggerHeader, newPkg)
 		} else if !reflect.DeepEqual(oldPkg.GetAnnotations(), newPkg.GetAnnotations()) ||
 			!reflect.DeepEqual(oldPkg.GetLabels(), newPkg.GetLabels()) {
+			b.UpdatesAvailable(refresh.RefreshTriggerAll, newPkg)
+		} else if !reflect.DeepEqual(oldPkg.GetStatus(), newPkg.GetStatus()) {
 			b.UpdatesAvailable(refresh.RefreshTriggerHeader, newPkg)
 		} else if !newPkg.GetDeletionTimestamp().IsZero() {
 			b.UpdatesAvailable(refresh.RefreshTriggerAll, newPkg)
diff --git a/internal/web/templates.go b/internal/web/templates.go
index 7361920..5d2f0af 100644
--- a/internal/web/templates.go
+++ b/internal/web/templates.go
@@ -145,6 +145,12 @@ func (t *templates) parseTemplates() {
 		"PackageOverviewRefreshId":        webutil.PackageOverviewRefreshId,
 		"ClusterPackageOverviewRefreshId": webutil.ClusterPackageOverviewRefreshId,
 		"ComponentName":                   depUtil.ComponentName,
+		"AutoUpdateEnabled": func(pkg ctrlpkg.Package) bool {
+			if pkg != nil && !pkg.IsNil() {
+				return pkg.AutoUpdatesEnabled()
+			}
+			return false
+		},
 	}
 
 	t.baseTemplate = template.Must(template.New("base.html").
diff --git a/internal/web/templates/components/pkg-detail-header.html b/internal/web/templates/components/pkg-detail-header.html
index f245326..a229966 100644
--- a/internal/web/templates/components/pkg-detail-header.html
+++ b/internal/web/templates/components/pkg-detail-header.html
@@ -112,7 +112,7 @@
           </span>
           <span class="badge bg-body-secondary text-primary-emphasis border-primary border border-1 p-1 fw-normal">
             Auto-Update:
-            <strong>{{ .AutoUpdate }}</strong>
+            <strong>{{ if AutoUpdateEnabled .Package }}Enabled{{ else }}Disabled{{ end }}</strong>
           </span>
           {{ if eq .Status.Status "Failed" }}
             {{ template "failed-badge" . }}
@@ -125,6 +125,22 @@
             <div>{{ .Status.Message }}</div>
           </div>
         {{ end }}
+        {{ if and (AutoUpdateEnabled .Package) (not .AutoUpdaterInstalled) }}
+          <div class="mt-2 alert alert-warning">
+            <div>
+              Please install
+              <a
+                href="/clusterpackages/glasskube-autoupdater"
+                hx-boost="true"
+                hx-target="main"
+                hx-select="main"
+                class="text-reset"
+                ><code>glasskube-autoupdater</code></a
+              >
+              for automatic updates to be applied.
+            </div>
+          </div>
+        {{ end }}
       {{ end }}
     </div>
   </div>
diff --git a/internal/web/templates/components/pkg-update-modal.html b/internal/web/templates/components/pkg-update-modal.html
index 137b31e..aa70d4a 100644
--- a/internal/web/templates/components/pkg-update-modal.html
+++ b/internal/web/templates/components/pkg-update-modal.html
@@ -8,32 +8,45 @@
           <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
         </div>
         <div class="modal-body" id="pkg-update-modal-body">
-          <table class="table table-striped">
-            <thead>
-              <tr>
-                <td></td>
-                <td><b>Current Version</b></td>
-                <td><b>Latest Version</b></td>
-              </tr>
-            </thead>
-            <tbody>
-              {{ range .Updates }}
+          {{ if .ConflictItems }}
+            <div class="alert alert-danger m-0 mb-2" role="alert">
+              <span>Cannot Update due to dependency conflicts:</span>
+              <ul class="mb-0 mt-1">
+                {{ range  .ConflictItems }}
+                  {{ range .Conflicts }}
+                    <li>{{ .Actual.Name }} (required: {{ .Required.Version }}, actual: {{ .Actual.Version }})</li>
+                  {{ end }}
+                {{ end }}
+              </ul>
+            </div>
+          {{ else }}
+            <table class="table table-striped">
+              <thead>
                 <tr>
-                  <td>
-                    <b
-                      >{{ if not .Package.IsNamespaceScoped }}
-                        {{ .Package.Name }}
-                      {{ else }}
-                        {{ .Package.Namespace }}/{{ .Package.Name }}
-                      {{ end }}</b
-                    >
-                  </td>
-                  <td>{{ .CurrentVersion }}</td>
-                  <td>{{ .LatestVersion }}</td>
+                  <td></td>
+                  <td><b>Current Version</b></td>
+                  <td><b>Latest Version</b></td>
                 </tr>
-              {{ end }}
-            </tbody>
-          </table>
+              </thead>
+              <tbody>
+                {{ range .Updates }}
+                  <tr>
+                    <td>
+                      <b
+                        >{{ if not .Package.IsNamespaceScoped }}
+                          {{ .Package.Name }}
+                        {{ else }}
+                          {{ .Package.Namespace }}/{{ .Package.Name }}
+                        {{ end }}</b
+                      >
+                    </td>
+                    <td>{{ .CurrentVersion }}</td>
+                    <td>{{ .LatestVersion }}</td>
+                  </tr>
+                {{ end }}
+              </tbody>
+            </table>
+          {{ end }}
         </div>
         <div class="modal-footer">
           <button type="button" class="btn btn-outline-primary btn-sm" data-bs-dismiss="modal">Cancel</button>
@@ -42,7 +55,8 @@
             name="dryRun"
             value="{{ .GitopsMode }}"
             {{ if not .GitopsMode }}data-bs-dismiss="modal"{{ end }}
-            class="btn btn-primary btn-sm">
+            class="btn btn-primary btn-sm"
+            {{ if.ConflictItems }}disabled{{ end }}>
             {{ if .GitopsMode }}
               Show YAML
             {{ else }}
diff --git a/internal/web/templates/pages/package.html b/internal/web/templates/pages/package.html
index 3028980..6f8747f 100644
--- a/internal/web/templates/pages/package.html
+++ b/internal/web/templates/pages/package.html
@@ -179,8 +179,13 @@
 
               <div class="mt-1 mb-2">
                 <div class="form-check ">
-                  <input class="form-check-input" type="checkbox" name="enableAutoUpdate" id="enable-auto-update" />
-                  <label class="form-check-label ms-1" for="enable-auto-update">Automatically keep up-to-date</label>
+                  <input
+                    class="form-check-input"
+                    type="checkbox"
+                    name="autoUpdate"
+                    id="pkg-auto-update"
+                    {{ if AutoUpdateEnabled .Package }}checked{{ end }} />
+                  <label class="form-check-label ms-1" for="pkg-auto-update">Automatically keep up-to-date</label>
                 </div>
               </div>
 
diff --git a/pkg/bootstrap/bootstrap.go b/pkg/bootstrap/bootstrap.go
index ad8f31e..1781feb 100644
--- a/pkg/bootstrap/bootstrap.go
+++ b/pkg/bootstrap/bootstrap.go
@@ -132,7 +132,7 @@ func (c *BootstrapClient) Bootstrap(
 	}
 
 	statusMessage("Fetching Glasskube manifest from "+parsedUrl.Redacted(), true, options.NoProgress)
-	manifests, err := clientutils.FetchResources(options.Url)
+	manifests, err := clientutils.FetchResourcesFromUrl(options.Url)
 	if err != nil {
 		statusMessage("Couldn't fetch Glasskube manifests", false, false)
 		telemetry.BootstrapFailure(time.Since(start))
diff --git a/pkg/purge/purge.go b/pkg/purge/purge.go
index 8f37453..b9d80c9 100644
--- a/pkg/purge/purge.go
+++ b/pkg/purge/purge.go
@@ -70,7 +70,7 @@ func (c *Purger) Purge(ctx context.Context) error {
 		operatorVersion, "slim")
 
 	c.status.SetStatus("Fetching Glasskube manifest from " + manifestUrl)
-	manifests, err := clientutils.FetchResources(manifestUrl)
+	manifests, err := clientutils.FetchResourcesFromUrl(manifestUrl)
 	if err != nil {
 		return fmt.Errorf("Couldn't fetch Glasskube manifests: %w", err)
 	}
diff --git a/web/custom.js b/web/custom.js
index 1027b5a..ee2f1d2 100644
--- a/web/custom.js
+++ b/web/custom.js
@@ -23,6 +23,15 @@
     });
 })();
 
+(() => {
+  const modal = document.getElementById('modal-container');
+  modal.addEventListener('show.bs.modal', (evt) => {
+    // https://getbootstrap.com/docs/5.3/components/modal/#events
+    // "hidden.bs.modal" is too early to clear innerHTML – the form submission from inside the modal would be cancelled
+    modal.innerHTML = '';
+  });
+})();
+
 function setSSEDisconnected() {
   const elem = document.getElementById('disconnected-toast');
   if (elem && !elem.classList.contains('show')) {
diff --git a/website/docusaurus.config.ts b/website/docusaurus.config.ts
index 4e8d350..94f9750 100644
--- a/website/docusaurus.config.ts
+++ b/website/docusaurus.config.ts
@@ -129,7 +129,7 @@ const config: Config = {
     announcementBar: {
       id: 'announcementBar-1', // Increment on change
       /* x-release-please-start-version */
-      content: `🎉 We just released v0.22.0 of our Open Source Kubernetes Package Manager on <a href="https://github.com/glasskube/glasskube/" target="_blank">⭐ GitHub ⭐</a>.`,
+      content: `🎉 We just released v0.23.0 of our Open Source Kubernetes Package Manager on <a href="https://github.com/glasskube/glasskube/" target="_blank">⭐ GitHub ⭐</a>.`,
       /* x-release-please-end */
       isCloseable: false,
     },
diff --git a/website/package-lock.json b/website/package-lock.json
index 9581fd7..9a1408e 100644
--- a/website/package-lock.json
+++ b/website/package-lock.json
@@ -4595,17 +4595,17 @@
       "license": "MIT"
     },
     "node_modules/@typescript-eslint/eslint-plugin": {
-      "version": "8.7.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.7.0.tgz",
-      "integrity": "sha512-RIHOoznhA3CCfSTFiB6kBGLQtB/sox+pJ6jeFu6FxJvqL8qRxq/FfGO/UhsGgQM9oGdXkV4xUgli+dt26biB6A==",
+      "version": "8.8.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.8.0.tgz",
+      "integrity": "sha512-wORFWjU30B2WJ/aXBfOm1LX9v9nyt9D3jsSOxC3cCaTQGCW5k4jNpmjFv3U7p/7s4yvdjHzwtv2Sd2dOyhjS0A==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
         "@eslint-community/regexpp": "^4.10.0",
-        "@typescript-eslint/scope-manager": "8.7.0",
-        "@typescript-eslint/type-utils": "8.7.0",
-        "@typescript-eslint/utils": "8.7.0",
-        "@typescript-eslint/visitor-keys": "8.7.0",
+        "@typescript-eslint/scope-manager": "8.8.0",
+        "@typescript-eslint/type-utils": "8.8.0",
+        "@typescript-eslint/utils": "8.8.0",
+        "@typescript-eslint/visitor-keys": "8.8.0",
         "graphemer": "^1.4.0",
         "ignore": "^5.3.1",
         "natural-compare": "^1.4.0",
@@ -4629,14 +4629,14 @@
       }
     },
     "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": {
-      "version": "8.7.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.7.0.tgz",
-      "integrity": "sha512-87rC0k3ZlDOuz82zzXRtQ7Akv3GKhHs0ti4YcbAJtaomllXoSO8hi7Ix3ccEvCd824dy9aIX+j3d2UMAfCtVpg==",
+      "version": "8.8.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.8.0.tgz",
+      "integrity": "sha512-EL8eaGC6gx3jDd8GwEFEV091210U97J0jeEHrAYvIYosmEGet4wJ+g0SYmLu+oRiAwbSA5AVrt6DxLHfdd+bUg==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
-        "@typescript-eslint/types": "8.7.0",
-        "@typescript-eslint/visitor-keys": "8.7.0"
+        "@typescript-eslint/types": "8.8.0",
+        "@typescript-eslint/visitor-keys": "8.8.0"
       },
       "engines": {
         "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -4647,9 +4647,9 @@
       }
     },
     "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": {
-      "version": "8.7.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.7.0.tgz",
-      "integrity": "sha512-LLt4BLHFwSfASHSF2K29SZ+ZCsbQOM+LuarPjRUuHm+Qd09hSe3GCeaQbcCr+Mik+0QFRmep/FyZBO6fJ64U3w==",
+      "version": "8.8.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.8.0.tgz",
+      "integrity": "sha512-QJwc50hRCgBd/k12sTykOJbESe1RrzmX6COk8Y525C9l7oweZ+1lw9JiU56im7Amm8swlz00DRIlxMYLizr2Vw==",
       "dev": true,
       "license": "MIT",
       "engines": {
@@ -4661,14 +4661,14 @@
       }
     },
     "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/typescript-estree": {
-      "version": "8.7.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.7.0.tgz",
-      "integrity": "sha512-MC8nmcGHsmfAKxwnluTQpNqceniT8SteVwd2voYlmiSWGOtjvGXdPl17dYu2797GVscK30Z04WRM28CrKS9WOg==",
+      "version": "8.8.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.8.0.tgz",
+      "integrity": "sha512-ZaMJwc/0ckLz5DaAZ+pNLmHv8AMVGtfWxZe/x2JVEkD5LnmhWiQMMcYT7IY7gkdJuzJ9P14fRy28lUrlDSWYdw==",
       "dev": true,
       "license": "BSD-2-Clause",
       "dependencies": {
-        "@typescript-eslint/types": "8.7.0",
-        "@typescript-eslint/visitor-keys": "8.7.0",
+        "@typescript-eslint/types": "8.8.0",
+        "@typescript-eslint/visitor-keys": "8.8.0",
         "debug": "^4.3.4",
         "fast-glob": "^3.3.2",
         "is-glob": "^4.0.3",
@@ -4690,16 +4690,16 @@
       }
     },
     "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils": {
-      "version": "8.7.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.7.0.tgz",
-      "integrity": "sha512-ZbdUdwsl2X/s3CiyAu3gOlfQzpbuG3nTWKPoIvAu1pu5r8viiJvv2NPN2AqArL35NCYtw/lrPPfM4gxrMLNLPw==",
+      "version": "8.8.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.8.0.tgz",
+      "integrity": "sha512-QE2MgfOTem00qrlPgyByaCHay9yb1+9BjnMFnSFkUKQfu7adBXDTnCAivURnuPPAG/qiB+kzKkZKmKfaMT0zVg==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
         "@eslint-community/eslint-utils": "^4.4.0",
-        "@typescript-eslint/scope-manager": "8.7.0",
-        "@typescript-eslint/types": "8.7.0",
-        "@typescript-eslint/typescript-estree": "8.7.0"
+        "@typescript-eslint/scope-manager": "8.8.0",
+        "@typescript-eslint/types": "8.8.0",
+        "@typescript-eslint/typescript-estree": "8.8.0"
       },
       "engines": {
         "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -4713,13 +4713,13 @@
       }
     },
     "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": {
-      "version": "8.7.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.7.0.tgz",
-      "integrity": "sha512-b1tx0orFCCh/THWPQa2ZwWzvOeyzzp36vkJYOpVg0u8UVOIsfVrnuC9FqAw9gRKn+rG2VmWQ/zDJZzkxUnj/XQ==",
+      "version": "8.8.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.8.0.tgz",
+      "integrity": "sha512-8mq51Lx6Hpmd7HnA2fcHQo3YgfX1qbccxQOgZcb4tvasu//zXRaA1j5ZRFeCw/VRAdFi4mRM9DnZw0Nu0Q2d1g==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
-        "@typescript-eslint/types": "8.7.0",
+        "@typescript-eslint/types": "8.8.0",
         "eslint-visitor-keys": "^3.4.3"
       },
       "engines": {
@@ -4757,16 +4757,16 @@
       }
     },
     "node_modules/@typescript-eslint/parser": {
-      "version": "8.7.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.7.0.tgz",
-      "integrity": "sha512-lN0btVpj2unxHlNYLI//BQ7nzbMJYBVQX5+pbNXvGYazdlgYonMn4AhhHifQ+J4fGRYA/m1DjaQjx+fDetqBOQ==",
+      "version": "8.8.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.8.0.tgz",
+      "integrity": "sha512-uEFUsgR+tl8GmzmLjRqz+VrDv4eoaMqMXW7ruXfgThaAShO9JTciKpEsB+TvnfFfbg5IpujgMXVV36gOJRLtZg==",
       "dev": true,
       "license": "BSD-2-Clause",
       "dependencies": {
-        "@typescript-eslint/scope-manager": "8.7.0",
-        "@typescript-eslint/types": "8.7.0",
-        "@typescript-eslint/typescript-estree": "8.7.0",
-        "@typescript-eslint/visitor-keys": "8.7.0",
+        "@typescript-eslint/scope-manager": "8.8.0",
+        "@typescript-eslint/types": "8.8.0",
+        "@typescript-eslint/typescript-estree": "8.8.0",
+        "@typescript-eslint/visitor-keys": "8.8.0",
         "debug": "^4.3.4"
       },
       "engines": {
@@ -4786,14 +4786,14 @@
       }
     },
     "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": {
-      "version": "8.7.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.7.0.tgz",
-      "integrity": "sha512-87rC0k3ZlDOuz82zzXRtQ7Akv3GKhHs0ti4YcbAJtaomllXoSO8hi7Ix3ccEvCd824dy9aIX+j3d2UMAfCtVpg==",
+      "version": "8.8.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.8.0.tgz",
+      "integrity": "sha512-EL8eaGC6gx3jDd8GwEFEV091210U97J0jeEHrAYvIYosmEGet4wJ+g0SYmLu+oRiAwbSA5AVrt6DxLHfdd+bUg==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
-        "@typescript-eslint/types": "8.7.0",
-        "@typescript-eslint/visitor-keys": "8.7.0"
+        "@typescript-eslint/types": "8.8.0",
+        "@typescript-eslint/visitor-keys": "8.8.0"
       },
       "engines": {
         "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -4804,9 +4804,9 @@
       }
     },
     "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": {
-      "version": "8.7.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.7.0.tgz",
-      "integrity": "sha512-LLt4BLHFwSfASHSF2K29SZ+ZCsbQOM+LuarPjRUuHm+Qd09hSe3GCeaQbcCr+Mik+0QFRmep/FyZBO6fJ64U3w==",
+      "version": "8.8.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.8.0.tgz",
+      "integrity": "sha512-QJwc50hRCgBd/k12sTykOJbESe1RrzmX6COk8Y525C9l7oweZ+1lw9JiU56im7Amm8swlz00DRIlxMYLizr2Vw==",
       "dev": true,
       "license": "MIT",
       "engines": {
@@ -4818,14 +4818,14 @@
       }
     },
     "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": {
-      "version": "8.7.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.7.0.tgz",
-      "integrity": "sha512-MC8nmcGHsmfAKxwnluTQpNqceniT8SteVwd2voYlmiSWGOtjvGXdPl17dYu2797GVscK30Z04WRM28CrKS9WOg==",
+      "version": "8.8.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.8.0.tgz",
+      "integrity": "sha512-ZaMJwc/0ckLz5DaAZ+pNLmHv8AMVGtfWxZe/x2JVEkD5LnmhWiQMMcYT7IY7gkdJuzJ9P14fRy28lUrlDSWYdw==",
       "dev": true,
       "license": "BSD-2-Clause",
       "dependencies": {
-        "@typescript-eslint/types": "8.7.0",
-        "@typescript-eslint/visitor-keys": "8.7.0",
+        "@typescript-eslint/types": "8.8.0",
+        "@typescript-eslint/visitor-keys": "8.8.0",
         "debug": "^4.3.4",
         "fast-glob": "^3.3.2",
         "is-glob": "^4.0.3",
@@ -4847,13 +4847,13 @@
       }
     },
     "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": {
-      "version": "8.7.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.7.0.tgz",
-      "integrity": "sha512-b1tx0orFCCh/THWPQa2ZwWzvOeyzzp36vkJYOpVg0u8UVOIsfVrnuC9FqAw9gRKn+rG2VmWQ/zDJZzkxUnj/XQ==",
+      "version": "8.8.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.8.0.tgz",
+      "integrity": "sha512-8mq51Lx6Hpmd7HnA2fcHQo3YgfX1qbccxQOgZcb4tvasu//zXRaA1j5ZRFeCw/VRAdFi4mRM9DnZw0Nu0Q2d1g==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
-        "@typescript-eslint/types": "8.7.0",
+        "@typescript-eslint/types": "8.8.0",
         "eslint-visitor-keys": "^3.4.3"
       },
       "engines": {
@@ -4909,14 +4909,14 @@
       }
     },
     "node_modules/@typescript-eslint/type-utils": {
-      "version": "8.7.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.7.0.tgz",
-      "integrity": "sha512-tl0N0Mj3hMSkEYhLkjREp54OSb/FI6qyCzfiiclvJvOqre6hsZTGSnHtmFLDU8TIM62G7ygEa1bI08lcuRwEnQ==",
+      "version": "8.8.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.8.0.tgz",
+      "integrity": "sha512-IKwJSS7bCqyCeG4NVGxnOP6lLT9Okc3Zj8hLO96bpMkJab+10HIfJbMouLrlpyOr3yrQ1cA413YPFiGd1mW9/Q==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
-        "@typescript-eslint/typescript-estree": "8.7.0",
-        "@typescript-eslint/utils": "8.7.0",
+        "@typescript-eslint/typescript-estree": "8.8.0",
+        "@typescript-eslint/utils": "8.8.0",
         "debug": "^4.3.4",
         "ts-api-utils": "^1.3.0"
       },
@@ -4934,14 +4934,14 @@
       }
     },
     "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/scope-manager": {
-      "version": "8.7.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.7.0.tgz",
-      "integrity": "sha512-87rC0k3ZlDOuz82zzXRtQ7Akv3GKhHs0ti4YcbAJtaomllXoSO8hi7Ix3ccEvCd824dy9aIX+j3d2UMAfCtVpg==",
+      "version": "8.8.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.8.0.tgz",
+      "integrity": "sha512-EL8eaGC6gx3jDd8GwEFEV091210U97J0jeEHrAYvIYosmEGet4wJ+g0SYmLu+oRiAwbSA5AVrt6DxLHfdd+bUg==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
-        "@typescript-eslint/types": "8.7.0",
-        "@typescript-eslint/visitor-keys": "8.7.0"
+        "@typescript-eslint/types": "8.8.0",
+        "@typescript-eslint/visitor-keys": "8.8.0"
       },
       "engines": {
         "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -4952,9 +4952,9 @@
       }
     },
     "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": {
-      "version": "8.7.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.7.0.tgz",
-      "integrity": "sha512-LLt4BLHFwSfASHSF2K29SZ+ZCsbQOM+LuarPjRUuHm+Qd09hSe3GCeaQbcCr+Mik+0QFRmep/FyZBO6fJ64U3w==",
+      "version": "8.8.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.8.0.tgz",
+      "integrity": "sha512-QJwc50hRCgBd/k12sTykOJbESe1RrzmX6COk8Y525C9l7oweZ+1lw9JiU56im7Amm8swlz00DRIlxMYLizr2Vw==",
       "dev": true,
       "license": "MIT",
       "engines": {
@@ -4966,14 +4966,14 @@
       }
     },
     "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": {
-      "version": "8.7.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.7.0.tgz",
-      "integrity": "sha512-MC8nmcGHsmfAKxwnluTQpNqceniT8SteVwd2voYlmiSWGOtjvGXdPl17dYu2797GVscK30Z04WRM28CrKS9WOg==",
+      "version": "8.8.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.8.0.tgz",
+      "integrity": "sha512-ZaMJwc/0ckLz5DaAZ+pNLmHv8AMVGtfWxZe/x2JVEkD5LnmhWiQMMcYT7IY7gkdJuzJ9P14fRy28lUrlDSWYdw==",
       "dev": true,
       "license": "BSD-2-Clause",
       "dependencies": {
-        "@typescript-eslint/types": "8.7.0",
-        "@typescript-eslint/visitor-keys": "8.7.0",
+        "@typescript-eslint/types": "8.8.0",
+        "@typescript-eslint/visitor-keys": "8.8.0",
         "debug": "^4.3.4",
         "fast-glob": "^3.3.2",
         "is-glob": "^4.0.3",
@@ -4995,16 +4995,16 @@
       }
     },
     "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/utils": {
-      "version": "8.7.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.7.0.tgz",
-      "integrity": "sha512-ZbdUdwsl2X/s3CiyAu3gOlfQzpbuG3nTWKPoIvAu1pu5r8viiJvv2NPN2AqArL35NCYtw/lrPPfM4gxrMLNLPw==",
+      "version": "8.8.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.8.0.tgz",
+      "integrity": "sha512-QE2MgfOTem00qrlPgyByaCHay9yb1+9BjnMFnSFkUKQfu7adBXDTnCAivURnuPPAG/qiB+kzKkZKmKfaMT0zVg==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
         "@eslint-community/eslint-utils": "^4.4.0",
-        "@typescript-eslint/scope-manager": "8.7.0",
-        "@typescript-eslint/types": "8.7.0",
-        "@typescript-eslint/typescript-estree": "8.7.0"
+        "@typescript-eslint/scope-manager": "8.8.0",
+        "@typescript-eslint/types": "8.8.0",
+        "@typescript-eslint/typescript-estree": "8.8.0"
       },
       "engines": {
         "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -5018,13 +5018,13 @@
       }
     },
     "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": {
-      "version": "8.7.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.7.0.tgz",
-      "integrity": "sha512-b1tx0orFCCh/THWPQa2ZwWzvOeyzzp36vkJYOpVg0u8UVOIsfVrnuC9FqAw9gRKn+rG2VmWQ/zDJZzkxUnj/XQ==",
+      "version": "8.8.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.8.0.tgz",
+      "integrity": "sha512-8mq51Lx6Hpmd7HnA2fcHQo3YgfX1qbccxQOgZcb4tvasu//zXRaA1j5ZRFeCw/VRAdFi4mRM9DnZw0Nu0Q2d1g==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
-        "@typescript-eslint/types": "8.7.0",
+        "@typescript-eslint/types": "8.8.0",
         "eslint-visitor-keys": "^3.4.3"
       },
       "engines": {
@@ -10226,9 +10226,9 @@
       }
     },
     "node_modules/globals": {
-      "version": "15.9.0",
-      "resolved": "https://registry.npmjs.org/globals/-/globals-15.9.0.tgz",
-      "integrity": "sha512-SmSKyLLKFbSr6rptvP8izbyxJL4ILwqO9Jg23UA0sDlGlu58V59D1//I3vlc0KJphVdUR7vMjHIplYnzBxorQA==",
+      "version": "15.10.0",
+      "resolved": "https://registry.npmjs.org/globals/-/globals-15.10.0.tgz",
+      "integrity": "sha512-tqFIbz83w4Y5TCbtgjZjApohbuh7K9BxGYFm7ifwDR240tvdb7P9x+/9VvUKlmkPoiknoJtanI8UOrqxS3a7lQ==",
       "dev": true,
       "license": "MIT",
       "engines": {
@@ -19661,15 +19661,15 @@
       }
     },
     "node_modules/typescript-eslint": {
-      "version": "8.7.0",
-      "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.7.0.tgz",
-      "integrity": "sha512-nEHbEYJyHwsuf7c3V3RS7Saq+1+la3i0ieR3qP0yjqWSzVmh8Drp47uOl9LjbPANac4S7EFSqvcYIKXUUwIfIQ==",
+      "version": "8.8.0",
+      "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.8.0.tgz",
+      "integrity": "sha512-BjIT/VwJ8+0rVO01ZQ2ZVnjE1svFBiRczcpr1t1Yxt7sT25VSbPfrJtDsQ8uQTy2pilX5nI9gwxhUyLULNentw==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
-        "@typescript-eslint/eslint-plugin": "8.7.0",
-        "@typescript-eslint/parser": "8.7.0",
-        "@typescript-eslint/utils": "8.7.0"
+        "@typescript-eslint/eslint-plugin": "8.8.0",
+        "@typescript-eslint/parser": "8.8.0",
+        "@typescript-eslint/utils": "8.8.0"
       },
       "engines": {
         "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -19685,14 +19685,14 @@
       }
     },
     "node_modules/typescript-eslint/node_modules/@typescript-eslint/scope-manager": {
-      "version": "8.7.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.7.0.tgz",
-      "integrity": "sha512-87rC0k3ZlDOuz82zzXRtQ7Akv3GKhHs0ti4YcbAJtaomllXoSO8hi7Ix3ccEvCd824dy9aIX+j3d2UMAfCtVpg==",
+      "version": "8.8.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.8.0.tgz",
+      "integrity": "sha512-EL8eaGC6gx3jDd8GwEFEV091210U97J0jeEHrAYvIYosmEGet4wJ+g0SYmLu+oRiAwbSA5AVrt6DxLHfdd+bUg==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
-        "@typescript-eslint/types": "8.7.0",
-        "@typescript-eslint/visitor-keys": "8.7.0"
+        "@typescript-eslint/types": "8.8.0",
+        "@typescript-eslint/visitor-keys": "8.8.0"
       },
       "engines": {
         "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -19703,9 +19703,9 @@
       }
     },
     "node_modules/typescript-eslint/node_modules/@typescript-eslint/types": {
-      "version": "8.7.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.7.0.tgz",
-      "integrity": "sha512-LLt4BLHFwSfASHSF2K29SZ+ZCsbQOM+LuarPjRUuHm+Qd09hSe3GCeaQbcCr+Mik+0QFRmep/FyZBO6fJ64U3w==",
+      "version": "8.8.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.8.0.tgz",
+      "integrity": "sha512-QJwc50hRCgBd/k12sTykOJbESe1RrzmX6COk8Y525C9l7oweZ+1lw9JiU56im7Amm8swlz00DRIlxMYLizr2Vw==",
       "dev": true,
       "license": "MIT",
       "engines": {
@@ -19717,14 +19717,14 @@
       }
     },
     "node_modules/typescript-eslint/node_modules/@typescript-eslint/typescript-estree": {
-      "version": "8.7.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.7.0.tgz",
-      "integrity": "sha512-MC8nmcGHsmfAKxwnluTQpNqceniT8SteVwd2voYlmiSWGOtjvGXdPl17dYu2797GVscK30Z04WRM28CrKS9WOg==",
+      "version": "8.8.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.8.0.tgz",
+      "integrity": "sha512-ZaMJwc/0ckLz5DaAZ+pNLmHv8AMVGtfWxZe/x2JVEkD5LnmhWiQMMcYT7IY7gkdJuzJ9P14fRy28lUrlDSWYdw==",
       "dev": true,
       "license": "BSD-2-Clause",
       "dependencies": {
-        "@typescript-eslint/types": "8.7.0",
-        "@typescript-eslint/visitor-keys": "8.7.0",
+        "@typescript-eslint/types": "8.8.0",
+        "@typescript-eslint/visitor-keys": "8.8.0",
         "debug": "^4.3.4",
         "fast-glob": "^3.3.2",
         "is-glob": "^4.0.3",
@@ -19746,16 +19746,16 @@
       }
     },
     "node_modules/typescript-eslint/node_modules/@typescript-eslint/utils": {
-      "version": "8.7.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.7.0.tgz",
-      "integrity": "sha512-ZbdUdwsl2X/s3CiyAu3gOlfQzpbuG3nTWKPoIvAu1pu5r8viiJvv2NPN2AqArL35NCYtw/lrPPfM4gxrMLNLPw==",
+      "version": "8.8.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.8.0.tgz",
+      "integrity": "sha512-QE2MgfOTem00qrlPgyByaCHay9yb1+9BjnMFnSFkUKQfu7adBXDTnCAivURnuPPAG/qiB+kzKkZKmKfaMT0zVg==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
         "@eslint-community/eslint-utils": "^4.4.0",
-        "@typescript-eslint/scope-manager": "8.7.0",
-        "@typescript-eslint/types": "8.7.0",
-        "@typescript-eslint/typescript-estree": "8.7.0"
+        "@typescript-eslint/scope-manager": "8.8.0",
+        "@typescript-eslint/types": "8.8.0",
+        "@typescript-eslint/typescript-estree": "8.8.0"
       },
       "engines": {
         "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -19769,13 +19769,13 @@
       }
     },
     "node_modules/typescript-eslint/node_modules/@typescript-eslint/visitor-keys": {
-      "version": "8.7.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.7.0.tgz",
-      "integrity": "sha512-b1tx0orFCCh/THWPQa2ZwWzvOeyzzp36vkJYOpVg0u8UVOIsfVrnuC9FqAw9gRKn+rG2VmWQ/zDJZzkxUnj/XQ==",
+      "version": "8.8.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.8.0.tgz",
+      "integrity": "sha512-8mq51Lx6Hpmd7HnA2fcHQo3YgfX1qbccxQOgZcb4tvasu//zXRaA1j5ZRFeCw/VRAdFi4mRM9DnZw0Nu0Q2d1g==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
-        "@typescript-eslint/types": "8.7.0",
+        "@typescript-eslint/types": "8.8.0",
         "eslint-visitor-keys": "^3.4.3"
       },
       "engines": {
diff --git a/website/src/pages/pricing/_components/PricingComponent/index.tsx b/website/src/pages/pricing/_components/PricingComponent/index.tsx
index 7cdf438..5bf9ec7 100644
--- a/website/src/pages/pricing/_components/PricingComponent/index.tsx
+++ b/website/src/pages/pricing/_components/PricingComponent/index.tsx
@@ -23,7 +23,7 @@ function Pricing() {
                   deploy across clusters.
                 </p>
                 <ul className={styles.pricingItemList}>
-                  <li>Never maintain a Helm charts every again</li>
+                  <li>Never maintain Helm charts ever again</li>
                   <li>
                     Compatibility across different Kubernetes environments
                   </li>
diff --git a/website/src/partials/_install.mdx b/website/src/partials/_install.mdx
index 2ee112a..38d78f2 100644
--- a/website/src/partials/_install.mdx
+++ b/website/src/partials/_install.mdx
@@ -15,7 +15,7 @@ import TabItem from '@theme/TabItem';
 
       {/* x-release-please-start-version */}
       ```
-      dnf install https://releases.dl.glasskube.dev/glasskube_v0.22.0_amd64.rpm
+      dnf install https://releases.dl.glasskube.dev/glasskube_v0.23.0_amd64.rpm
       ```
       {/* x-release-please-end */}
     </details>
@@ -25,8 +25,8 @@ import TabItem from '@theme/TabItem';
 
       {/* x-release-please-start-version */}
       ```
-      curl -LO https://releases.dl.glasskube.dev/glasskube_v0.22.0_amd64.deb
-      sudo dpkg -i glasskube_v0.22.0_amd64.deb
+      curl -LO https://releases.dl.glasskube.dev/glasskube_v0.23.0_amd64.deb
+      sudo dpkg -i glasskube_v0.23.0_amd64.deb
       ```
       {/* x-release-please-end */}
     </details>
@@ -35,8 +35,8 @@ import TabItem from '@theme/TabItem';
       <summary>APK-based installation (Alpine)</summary>
       {/* x-release-please-start-version */}
       ```
-      curl -LO https://releases.dl.glasskube.dev/glasskube_v0.22.0_amd64.apk
-      apk add --allow-untrusted glasskube_v0.22.0_amd64.apk
+      curl -LO https://releases.dl.glasskube.dev/glasskube_v0.23.0_amd64.apk
+      apk add --allow-untrusted glasskube_v0.23.0_amd64.apk
       ```
       {/* x-release-please-end */}
     </details>
@@ -47,7 +47,7 @@ import TabItem from '@theme/TabItem';
 
   <TabItem value="win" label="Windows">
     {/* x-release-please-start-version */}
-    Download the [windows archive](https://releases.dl.glasskube.dev/glasskube_v0.22.0_windows_x86_64.zip) from our
+    Download the [windows archive](https://releases.dl.glasskube.dev/glasskube_v0.23.0_windows_x86_64.zip) from our
     latest [Release](https://github.com/glasskube/glasskube/releases/latest) and unpack it using Windows Explorer.
     {/* x-release-please-end */}
   </TabItem>
diff --git a/website/static/release.json b/website/static/release.json
index 3af564e..299c54e 100644
--- a/website/static/release.json
+++ b/website/static/release.json
@@ -1,1 +1,1 @@
-{"version":"0.22.0"}
+{"version":"0.23.0"}


Test patch:
diff --git a/internal/dependency/manager_test.go b/internal/dependency/manager_test.go
index ac84163..ba20cf2 100644
--- a/internal/dependency/manager_test.go
+++ b/internal/dependency/manager_test.go
@@ -14,11 +14,7 @@ import (
 	"k8s.io/apimachinery/pkg/runtime/schema"
 )
 
-var fakeRepo = &fake.FakeClient{
-	PackageRepositories: []v1alpha1.PackageRepository{
-		{},
-	},
-}
+var fakeRepo = fake.EmptyClient()
 
 type testClientAdapter struct {
 	clusterPackages []v1alpha1.ClusterPackage
@@ -66,7 +62,7 @@ func (a *testClientAdapter) ListPackageRepositories(ctx context.Context) (*v1alp
 
 func createDependencyManager() *DependendcyManager {
 	testClient = &testClientAdapter{}
-	return NewDependencyManager(testClient, &fake.FakeClientset{Client: fakeRepo})
+	return NewDependencyManager(testClient, fake.ClientsetWithClient(fakeRepo))
 }
 
 var testClient *testClientAdapter
diff --git a/internal/manifest/plain/url_test.go b/internal/manifest/plain/url_test.go
index 6ff3f2f..7946b1b 100644
--- a/internal/manifest/plain/url_test.go
+++ b/internal/manifest/plain/url_test.go
@@ -2,49 +2,89 @@ package plain
 
 import (
 	"github.com/glasskube/glasskube/api/v1alpha1"
+	"github.com/glasskube/glasskube/internal/repo/client/auth"
+	"github.com/glasskube/glasskube/internal/repo/client/fake"
 	. "github.com/onsi/ginkgo/v2"
 	. "github.com/onsi/gomega"
 )
 
+var adapter = &Adapter{}
+
 var _ = Describe("getActualManifestUrl", func() {
+	BeforeEach(func() {
+		adapter.repo = fake.EmptyClientset()
+	})
+
 	It("should handle relative url", func() {
-		result, err := getActualManifestUrl(
+		result, err := adapter.newManifestRequest(
 			&v1alpha1.PackageInfo{Status: v1alpha1.PackageInfoStatus{ResolvedUrl: "http://localhost/packages/foo/package.yaml"}},
 			"./manifest.yaml",
 		)
 		Expect(err).NotTo(HaveOccurred())
-		Expect(result).To(Equal("http://localhost/packages/foo/manifest.yaml"))
+		Expect(result.URL.String()).To(Equal("http://localhost/packages/foo/manifest.yaml"))
 	})
 	It("should handle plain file name", func() {
-		result, err := getActualManifestUrl(
+		result, err := adapter.newManifestRequest(
 			&v1alpha1.PackageInfo{Status: v1alpha1.PackageInfoStatus{ResolvedUrl: "http://localhost/packages/foo/package.yaml"}},
 			"manifest.yaml",
 		)
 		Expect(err).NotTo(HaveOccurred())
-		Expect(result).To(Equal("http://localhost/packages/foo/manifest.yaml"))
+		Expect(result.URL.String()).To(Equal("http://localhost/packages/foo/manifest.yaml"))
 	})
 	It("should handle relative url with \"..\"", func() {
-		result, err := getActualManifestUrl(
+		result, err := adapter.newManifestRequest(
 			&v1alpha1.PackageInfo{Status: v1alpha1.PackageInfoStatus{ResolvedUrl: "http://localhost/packages/foo/package.yaml"}},
 			"../manifest.yaml",
 		)
 		Expect(err).NotTo(HaveOccurred())
-		Expect(result).To(Equal("http://localhost/packages/manifest.yaml"))
+		Expect(result.URL.String()).To(Equal("http://localhost/packages/manifest.yaml"))
 	})
 	It("should handle absolute path", func() {
-		result, err := getActualManifestUrl(
+		result, err := adapter.newManifestRequest(
 			&v1alpha1.PackageInfo{Status: v1alpha1.PackageInfoStatus{ResolvedUrl: "http://localhost/packages/foo/package.yaml"}},
 			"/manifest.yaml",
 		)
 		Expect(err).NotTo(HaveOccurred())
-		Expect(result).To(Equal("http://localhost/manifest.yaml"))
+		Expect(result.URL.String()).To(Equal("http://localhost/manifest.yaml"))
 	})
 	It("should handle real url", func() {
-		result, err := getActualManifestUrl(
+		result, err := adapter.newManifestRequest(
 			&v1alpha1.PackageInfo{Status: v1alpha1.PackageInfoStatus{ResolvedUrl: "http://localhost/packages/foo/package.yaml"}},
 			"https://github.com/glasskube/glasskube/manifest.yaml",
 		)
 		Expect(err).NotTo(HaveOccurred())
-		Expect(result).To(Equal("https://github.com/glasskube/glasskube/manifest.yaml"))
+		Expect(result.URL.String()).To(Equal("https://github.com/glasskube/glasskube/manifest.yaml"))
+	})
+
+	Context("with basic auth", func() {
+		BeforeEach(func() {
+			adapter.repo = fake.ClientsetWithClient(fake.EmptyClientWithAuth(auth.Basic("test", "test")))
+		})
+
+		It("should add auth header for relative url", func() {
+			result, err := adapter.newManifestRequest(
+				&v1alpha1.PackageInfo{Status: v1alpha1.PackageInfoStatus{ResolvedUrl: "http://localhost/packages/foo/package.yaml"}},
+				"manifest.yaml",
+			)
+			Expect(err).NotTo(HaveOccurred())
+			Expect(result.URL.String()).To(Equal("http://localhost/packages/foo/manifest.yaml"))
+			user, pass, ok := result.BasicAuth()
+			Expect(ok).To(BeTrueBecause("basic auth should be added for relative urls"))
+			Expect(user).To(Equal("test"))
+			Expect(pass).To(Equal("test"))
+		})
+
+		It("should NOT add auth header for absolute url", func() {
+			result, err := adapter.newManifestRequest(
+				&v1alpha1.PackageInfo{Status: v1alpha1.PackageInfoStatus{ResolvedUrl: "http://localhost/packages/foo/package.yaml"}},
+				"https://github.com/glasskube/glasskube/manifest.yaml",
+			)
+			Expect(err).NotTo(HaveOccurred())
+			Expect(result.URL.String()).To(Equal("https://github.com/glasskube/glasskube/manifest.yaml"))
+			user, pass, ok := result.BasicAuth()
+			Expect(ok).To(BeFalseBecause("basic auth must NOT be added for absolute urls"))
+			Expect(user).To(Equal(""))
+			Expect(pass).To(Equal(""))
+		})
 	})
 })
diff --git a/internal/webhook/package_validator_test.go b/internal/webhook/package_validator_test.go
index c953ecb..db41499 100644
--- a/internal/webhook/package_validator_test.go
+++ b/internal/webhook/package_validator_test.go
@@ -20,10 +20,8 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
 )
 
-var fakeRepoClient = fakerepo.FakeClient{
-	PackageRepositories: []v1alpha1.PackageRepository{{}},
-}
-var fakeRepoClientset = fakerepo.FakeClientset{Client: &fakeRepoClient}
+var fakeRepoClient = fakerepo.EmptyClient()
+var fakeRepoClientset = fakerepo.ClientsetWithClient(fakeRepoClient)
 
 func newPackageValidatingWebhook(objects ...client.Object) *PackageValidatingWebhook {
 	fakeClient := fake.NewClientBuilder().
@@ -36,9 +34,9 @@ func newPackageValidatingWebhook(objects ...client.Object) *PackageValidatingWeb
 		OwnerManager: ownerManager,
 		DependendcyManager: dependency.NewDependencyManager(
 			ctrladapter.NewPackageClientAdapter(fakeClient),
-			&fakeRepoClientset,
+			fakeRepoClientset,
 		),
-		RepoClient: &fakeRepoClientset,
+		RepoClient: fakeRepoClientset,
 	}
 }