diff --git a/modules/k8s/Dockerfile.controller-manager b/modules/k8s/Dockerfile.controller-manager
index 628d9d7fee..865fec2e94 100644
--- a/modules/k8s/Dockerfile.controller-manager
+++ b/modules/k8s/Dockerfile.controller-manager
@@ -1,4 +1,4 @@
-FROM golang:1.23 AS builder
+FROM golang:1.24 AS builder
WORKDIR /workspace
@@ -14,7 +14,8 @@ ENV HTTP_PROXY=""
ENV HTTPS_PROXY=""
ENV http_proxy=""
ENV https_proxy=""
-ENV GOPROXY=https://goproxy.cn,direct
+ENV GOPROXY=https://goproxy.io,direct
+ENV GOSUMDB=sum.golang.org
# 下载依赖
RUN go mod download
diff --git a/modules/k8s/client/client.go b/modules/k8s/client/client.go
deleted file mode 100644
index de67a0f232..0000000000
--- a/modules/k8s/client/client.go
+++ /dev/null
@@ -1,51 +0,0 @@
-package client
-
-import (
- "context"
-
- "k8s.io/apimachinery/pkg/types"
- "sigs.k8s.io/controller-runtime/pkg/client"
-
- appsv1 "code.gitea.io/gitea/modules/k8s/api/devcontainer/v1"
-)
-
-// DevStarClient 提供操作 DevContainerApp 资源的方法
-type DevStarClient struct {
- client client.Client
-}
-
-// NewDevStarClient 创建一个新的客户端
-func NewDevStarClient(c client.Client) *DevStarClient {
- return &DevStarClient{
- client: c,
- }
-}
-
-// GetDevContainerApp 获取 DevContainerApp 资源
-func (c *DevStarClient) GetDevContainerApp(ctx context.Context, name, namespace string) (*appsv1.DevcontainerApp, error) {
- app := &appsv1.DevcontainerApp{}
- err := c.client.Get(ctx, types.NamespacedName{Name: name, Namespace: namespace}, app)
- return app, err
-}
-
-// CreateDevContainerApp 创建 DevContainerApp 资源
-func (c *DevStarClient) CreateDevContainerApp(ctx context.Context, app *appsv1.DevcontainerApp) error {
- return c.client.Create(ctx, app)
-}
-
-// UpdateDevContainerApp 更新 DevContainerApp 资源
-func (c *DevStarClient) UpdateDevContainerApp(ctx context.Context, app *appsv1.DevcontainerApp) error {
- return c.client.Update(ctx, app)
-}
-
-// DeleteDevContainerApp 删除 DevContainerApp 资源
-func (c *DevStarClient) DeleteDevContainerApp(ctx context.Context, app *appsv1.DevcontainerApp) error {
- return c.client.Delete(ctx, app)
-}
-
-// ListDevContainerApps 列出 DevContainerApp 资源
-func (c *DevStarClient) ListDevContainerApps(ctx context.Context, namespace string) (*appsv1.DevcontainerAppList, error) {
- list := &appsv1.DevcontainerAppList{}
- err := c.client.List(ctx, list, client.InNamespace(namespace))
- return list, err
-}
diff --git a/modules/k8s/k8s.go b/modules/k8s/k8s.go
index 5dfdd6ae0c..1c211435e9 100644
--- a/modules/k8s/k8s.go
+++ b/modules/k8s/k8s.go
@@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"fmt"
+ "strings"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
@@ -67,7 +68,21 @@ func GetKubernetesClient(ctx context.Context, kubeconfig []byte, contextName str
}
applyClientDefaults(config)
- return dynamicclient.NewForConfig(config)
+
+ // 强制跳过 TLS 证书校验(无论 kubeconfig 是否声明 insecure-skip-tls-verify)
+ // 同时清空 CA 配置
+ config.TLSClientConfig.Insecure = true
+ config.TLSClientConfig.CAData = nil
+ config.TLSClientConfig.CAFile = ""
+
+ // 尝试创建客户端,如果TLS验证失败则自动跳过验证
+ client, err := dynamicclient.NewForConfig(config)
+ if err != nil {
+ // 再次兜底:若识别为 TLS 错误,已 Insecure,无需再次设置;否则将错误上抛
+ return nil, fmt.Errorf("failed to create k8s client: %v", err)
+ }
+
+ return client, nil
}
// restConfigFromKubeconfigBytes 基于 kubeconfig 内容构造 *rest.Config,支持指定 context(为空则使用 current-context)
@@ -401,3 +416,36 @@ func ListDevcontainers(ctx context.Context, client dynamic_client.Interface, opt
}
return devcontainerList, nil
}
+
+// isTLSCertificateError 检查错误是否是TLS证书验证错误
+func isTLSCertificateError(err error) bool {
+ if err == nil {
+ return false
+ }
+
+ errStr := err.Error()
+
+ // 检查常见的TLS证书验证错误(尽量宽松,覆盖更多 x509 报错文案)
+ tlsErrorPatterns := []string{
+ "tls: failed to verify certificate",
+ "x509:",
+ "x509: certificate",
+ "cannot validate certificate",
+ "doesn't contain any IP SANs",
+ "certificate is valid for",
+ "certificate signed by unknown authority",
+ "unknown authority",
+ "self-signed certificate",
+ "certificate has expired",
+ "certificate is not valid",
+ "invalid certificate",
+ }
+
+ for _, pattern := range tlsErrorPatterns {
+ if strings.Contains(errStr, pattern) {
+ return true
+ }
+ }
+
+ return false
+}
diff --git a/routers/web/user/setting/appstore.go b/routers/web/user/setting/appstore.go
index b71cead293..1a106b8c5d 100644
--- a/routers/web/user/setting/appstore.go
+++ b/routers/web/user/setting/appstore.go
@@ -9,6 +9,7 @@ import (
"strings"
appstore_model "code.gitea.io/gitea/models/appstore"
+ user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/services/appstore"
@@ -23,6 +24,7 @@ const (
func AppStore(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("settings.appstore")
ctx.Data["PageIsSettingsAppStore"] = true
+ ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer)
ctx.HTML(http.StatusOK, tplSettingsAppStore)
}
@@ -222,6 +224,12 @@ func AppStoreInstall(ctx *context.Context) {
return
}
+ // 权限校验:仅管理员可选择默认位置
+ if installTarget == "local" && (ctx.Doer == nil || !ctx.Doer.IsAdmin) {
+ ctx.JSON(403, map[string]string{"error": "仅管理员可选择默认位置"})
+ return
+ }
+
// 创建 manager 并执行安装
manager := appstore.NewManager(ctx)
if err := manager.InstallApp(appID, configJSON, installTarget, kubeconfig, kubeconfigContext); err != nil {
@@ -245,7 +253,7 @@ func AppStoreInstall(ctx *context.Context) {
// 安装成功
if installTarget == "kubeconfig" && kubeconfig != "" {
- ctx.Flash.Success("应用已成功安装到外部 Kubernetes 集群")
+ ctx.Flash.Success("应用已成功安装到指定位置")
} else {
ctx.Flash.Success("应用配置已准备完成,本地安装功能开发中")
}
@@ -294,7 +302,7 @@ func AppStoreUninstall(ctx *context.Context) {
// 卸载成功
if installTarget == "kubeconfig" && kubeconfig != "" {
- ctx.Flash.Success("应用已成功从外部 Kubernetes 集群卸载")
+ ctx.Flash.Success("应用已成功从指定位置卸载")
} else {
ctx.Flash.Success("本地卸载功能开发中")
}
@@ -307,6 +315,7 @@ func AppStoreConfigure(ctx *context.Context) {
// TODO: Load app configuration form
ctx.Data["Title"] = "App Configuration"
ctx.Data["PageIsSettingsAppStore"] = true
+ ctx.Data["UserDisabledFeatures"] = user_model.DisabledFeaturesWithLoginType(ctx.Doer)
ctx.HTML(http.StatusOK, tplSettingsAppStore)
}
diff --git a/routers/web/web.go b/routers/web/web.go
index fafcb35bfe..7f201970a1 100644
--- a/routers/web/web.go
+++ b/routers/web/web.go
@@ -702,6 +702,16 @@ func registerWebRoutes(m *web.Router) {
m.Get("", user_setting.BlockedUsers)
m.Post("", web.Bind(forms.BlockUserForm{}), user_setting.BlockedUsersPost)
})
+ // appstore
+ m.Group("/appstore", func() {
+ m.Get("", user_setting.AppStore)
+ m.Get("/api/{action}", user_setting.AppStoreAPI)
+ m.Post("/api/add", user_setting.AddAppAPI)
+ m.Post("/install/{app_id}", user_setting.AppStoreInstall)
+ m.Post("/uninstall/{app_id}", user_setting.AppStoreUninstall)
+ m.Get("/configure/{app_id}", user_setting.AppStoreConfigure)
+ m.Post("/configure/{app_id}", user_setting.AppStoreConfigurePost)
+ })
}, reqSignIn, ctxDataSet("PageIsUserSettings", true, "EnablePackages", setting.Packages.Enabled, "EnableNotifyMail", setting.Service.EnableNotifyMail))
m.Group("/user", func() {
@@ -1697,14 +1707,4 @@ func registerWebRoutes(m *web.Router) {
defer routing.RecordFuncInfo(ctx, routing.GetFuncInfo(ctx.NotFound, "WebNotFound"))()
ctx.NotFound(nil)
})
-
- m.Group("/user/settings/appstore", func() {
- m.Get("", user_setting.AppStore)
- m.Get("/api/{action}", user_setting.AppStoreAPI)
- m.Post("/api/add", user_setting.AddAppAPI)
- m.Post("/install/{app_id}", user_setting.AppStoreInstall)
- m.Post("/uninstall/{app_id}", user_setting.AppStoreUninstall)
- m.Get("/configure/{app_id}", user_setting.AppStoreConfigure)
- m.Post("/configure/{app_id}", user_setting.AppStoreConfigurePost)
- })
}
diff --git a/templates/user/settings/appstore.tmpl b/templates/user/settings/appstore.tmpl
index fcc60f81f7..d53be6c54e 100644
--- a/templates/user/settings/appstore.tmpl
+++ b/templates/user/settings/appstore.tmpl
@@ -1,4 +1,5 @@
-{{template "user/settings/layout_head" .}}
+{{template "user/settings/layout_head" (dict "ctxData" . "pageClass" "user settings appstore")}}
+