Compare commits
43 Commits
devcontain
...
feature/ap
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
64f019590f | ||
|
|
8111e63771 | ||
|
|
084ad3a542 | ||
|
|
1d6ba90c2f | ||
|
|
d7798195cb | ||
|
|
3673a3b519 | ||
|
|
c4bffdeaf7 | ||
|
|
770f295088 | ||
|
|
f6772611e6 | ||
|
|
f08a0d02c3 | ||
|
|
840c600073 | ||
|
|
b8303e9000 | ||
|
|
7927e572d2 | ||
|
|
a38f403ee9 | ||
|
|
a37025f3bf | ||
|
|
c623fdbc73 | ||
|
|
e32b039494 | ||
|
|
56a4039034 | ||
|
|
5891f4f23a | ||
|
|
1ac1caa4a4 | ||
|
|
54b7c8c9fb | ||
|
|
020d24af84 | ||
|
|
1bba6fe4d9 | ||
|
|
dbb295fc7c | ||
|
|
fbb30e8daf | ||
|
|
7be67bf669 | ||
|
|
597f156cee | ||
|
|
96e707b80f | ||
|
|
ba0b6c1b7a | ||
|
|
45d30fd01d | ||
|
|
c6781f60c8 | ||
|
|
ad923eea61 | ||
|
|
08d011488b | ||
|
|
b9ab9a41dd | ||
|
|
1f54171b37 | ||
|
|
28adf2541d | ||
|
|
c0fbbfb618 | ||
|
|
c0e4044dab | ||
|
|
d86772249c | ||
|
|
c1c6ae5351 | ||
|
|
c672ed12cf | ||
|
|
85591ff46d | ||
|
|
9327789bdc |
52
Makefile
@@ -733,7 +733,6 @@ backend-debug: go-check generate-backend $(EXECUTABLE)-debug
|
||||
$(EXECUTABLE)-debug: $(GO_SOURCES) $(TAGS_PREREQ)
|
||||
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o $@
|
||||
|
||||
.PHONY: frontend
|
||||
|
||||
.PHONY: frontend
|
||||
frontend: $(WEBPACK_DEST) ## build frontend files
|
||||
@@ -927,25 +926,50 @@ generate-manpage: ## generate manpage
|
||||
@gzip -9 man/man1/gitea.1 && echo man/man1/gitea.1.gz created
|
||||
@#TODO A small script that formats config-cheat-sheet.en-us.md nicely for use as a config man page
|
||||
|
||||
.PHONY: devstar
|
||||
devstar:
|
||||
docker build -t devstar-studio:latest -f docker/Dockerfile.devstar .
|
||||
|
||||
.PHONY: docker
|
||||
docker:
|
||||
docker build -t devstar.cn/devstar/webterminal:latest -f docker/Dockerfile.webTerminal .
|
||||
|
||||
docker build --disable-content-trust=false -t $(DOCKER_REF) .
|
||||
# support also build args docker build --build-arg GITEA_VERSION=v1.2.3 --build-arg TAGS="bindata sqlite sqlite_unlock_notify" .
|
||||
|
||||
# This endif closes the if at the top of the file
|
||||
|
||||
# 添加一个新目标,用于构建 controller-manager
|
||||
.PHONY: controller-manager
|
||||
controller-manager: go-check
|
||||
@echo "Building controller-manager..."
|
||||
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags '$(TAGS)' -ldflags '-s -w $(LDFLAGS)' -o controller-manager modules/k8s/cmd/controller-manager/controller-manager.go
|
||||
|
||||
# 添加调试版本的编译目标
|
||||
.PHONY: controller-manager-debug
|
||||
controller-manager-debug: go-check
|
||||
@echo "Building controller-manager with debug info..."
|
||||
CGO_CFLAGS="$(CGO_CFLAGS)" $(GO) build $(GOFLAGS) $(EXTRA_GOFLAGS) -tags '$(TAGS)' -ldflags '$(LDFLAGS)' -o controller-manager-debug modules/k8s/cmd/controller-manager/controller-manager.go
|
||||
|
||||
|
||||
.PHONY: devstar
|
||||
devstar:
|
||||
@if docker pull devstar.cn/devstar/devstar-dev-container:v1.0; then \
|
||||
docker tag devstar.cn/devstar/devstar-dev-container:v1.0 devstar.cn/devstar/devstar-dev-container:latest && \
|
||||
echo "Successfully pulled devstar.cn/devstar/devstar-dev-container:v1.0 taged to latest"; \
|
||||
else \
|
||||
docker build -t devstar.cn/devstar/devstar-dev-container:latest -f docker/Dockerfile.devContainer . && \
|
||||
echo "Successfully build devstar.cn/devstar/devstar-dev-container:latest"; \
|
||||
fi
|
||||
@if docker pull devstar.cn/devstar/devstar-runtime-container:v1.0; then \
|
||||
docker tag devstar.cn/devstar/devstar-runtime-container:v1.0 devstar.cn/devstar/devstar-runtime-container:latest && \
|
||||
echo "Successfully pulled devstar.cn/devstar/devstar-runtime-container:v1.0 taged to latest"; \
|
||||
else \
|
||||
docker build -t devstar.cn/devstar/devstar-runtime-container:latest -f docker/Dockerfile.runtimeContainer . && \
|
||||
echo "Successfully build devstar.cn/devstar/devstar-runtime-container:latest"; \
|
||||
fi
|
||||
@if docker pull devstar.cn/devstar/webterminal:v1.0; then \
|
||||
docker tag devstar.cn/devstar/webterminal:v1.0 devstar.cn/devstar/webterminal:latest && \
|
||||
echo "Successfully pulled devstar.cn/devstar/webterminal:v1.0 taged to latest"; \
|
||||
else \
|
||||
docker build --no-cache -t devstar.cn/devstar/webterminal:latest -f docker/Dockerfile.webTerminal . && \
|
||||
echo "Successfully build devstar.cn/devstar/devstar-runtime-container:latest"; \
|
||||
fi
|
||||
docker build -t devstar-studio:latest -f docker/Dockerfile.devstar .
|
||||
|
||||
.PHONY: docker
|
||||
docker:
|
||||
docker build --disable-content-trust=false -t $(DOCKER_REF) .
|
||||
# support also build args docker build --build-arg GITEA_VERSION=v1.2.3 --build-arg TAGS="bindata sqlite sqlite_unlock_notify" .
|
||||
|
||||
# This endif closes the if at the top of the file
|
||||
endif
|
||||
|
||||
# Disable parallel execution because it would break some targets that don't
|
||||
|
||||
@@ -12,6 +12,12 @@ RUN apk --no-cache add \
|
||||
&& rm -rf /var/cache/apk/*
|
||||
|
||||
# To acquire Gitea dev container:
|
||||
# $ docker build -t devstar.cn/devstar/devstar-dev-container:latest -f docker/Dockerfile.devContainer .
|
||||
# $ docker build -t devstar.cn/devstar/devstar-dev-container:v1.0 -f docker/Dockerfile.devContainer .
|
||||
# $ docker login devstar.cn
|
||||
# $ docker push devstar.cn/devstar/devstar-dev-container:v1.0
|
||||
# $ docker tag devstar.cn/devstar/devstar-dev-container:v1.0 devstar.cn/devstar/devstar-dev-container:latest
|
||||
# $ docker push devstar.cn/devstar/devstar-dev-container:latest
|
||||
|
||||
|
||||
# Release Notes:
|
||||
# v1.0 - Initial release
|
||||
|
||||
@@ -19,6 +19,12 @@ RUN apk --no-cache add \
|
||||
&& rm -rf /var/cache/apk/*
|
||||
|
||||
# To acquire Gitea base runtime container:
|
||||
# $ docker build -t devstar.cn/devstar/devstar-runtime-container:latest -f docker/Dockerfile.runtimeContainer .
|
||||
# $ docker build -t devstar.cn/devstar/devstar-runtime-container:v1.0 -f docker/Dockerfile.runtimeContainer .
|
||||
# $ docker login devstar.cn
|
||||
# $ docker push devstar.cn/devstar/devstar-runtime-container:v1.0
|
||||
# $ docker tag devstar.cn/devstar/devstar-runtime-container:v1.0 devstar.cn/devstar/devstar-runtime-container:latest
|
||||
# $ docker push devstar.cn/devstar/devstar-runtime-container:latest
|
||||
|
||||
|
||||
# Release Notes:
|
||||
# v1.0 - Initial release
|
||||
|
||||
@@ -37,4 +37,14 @@ RUN apt-get update && \
|
||||
apt remove --purge curl -y && apt autoremove -y && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
ENTRYPOINT ["/usr/bin/tini", "--"]
|
||||
CMD ["/home/webTerminal/build/ttyd", "-W", "bash"]
|
||||
CMD ["/home/webTerminal/build/ttyd", "-W", "bash"]
|
||||
|
||||
# To acquire devstar.cn/devstar/webterminal:latest:
|
||||
# $ docker build --no-cache -t devstar.cn/devstar/webterminal:v1.0 -f docker/Dockerfile.webTerminal .
|
||||
# $ docker login devstar.cn
|
||||
# $ docker push devstar.cn/devstar/webterminal:v1.0
|
||||
# $ docker tag devstar.cn/devstar/webterminal:v1.0 devstar.cn/devstar/webterminal:latest
|
||||
# $ docker push devstar.cn/devstar/webterminal:latest
|
||||
|
||||
# Release Notes:
|
||||
# v1.0 - Initial release https://devstar.cn/devstar/webTerminal/commit/2bf050cff984d6e64c4f9753d64e1124fc152ad7
|
||||
@@ -32,7 +32,7 @@ if [ ! -f ${GITEA_CUSTOM}/conf/app.ini ]; then
|
||||
fi
|
||||
|
||||
# Substitute the environment variables in the template
|
||||
APP_NAME=${APP_NAME:-"Gitea: Git with a cup of tea"} \
|
||||
APP_NAME=${APP_NAME:-"DevStar: The Last Mile of Al for R&D"} \
|
||||
RUN_MODE=${RUN_MODE:-"prod"} \
|
||||
DOMAIN=${DOMAIN:-"localhost"} \
|
||||
SSH_DOMAIN=${SSH_DOMAIN:-"localhost"} \
|
||||
|
||||
@@ -26,7 +26,7 @@ if [ ! -f ${GITEA_APP_INI} ]; then
|
||||
fi
|
||||
|
||||
# Substitute the environment variables in the template
|
||||
APP_NAME=${APP_NAME:-"Gitea: Git with a cup of tea"} \
|
||||
APP_NAME=${APP_NAME:-"DevStar: The Last Mile of Al for R&D"} \
|
||||
RUN_MODE=${RUN_MODE:-"prod"} \
|
||||
RUN_USER=${USER:-"git"} \
|
||||
SSH_DOMAIN=${SSH_DOMAIN:-"localhost"} \
|
||||
|
||||
124
docs/devstar-deploy/README.md
Normal file
@@ -0,0 +1,124 @@
|
||||
# Devstar 部署文档
|
||||
|
||||
## 1. 安装 Helm
|
||||
|
||||
在开始部署前,请先安装 Helm。建议使用官方提供的安装脚本,具体步骤可参考:安装 Helm | Helm。
|
||||
|
||||
推荐使用 `get_helm.sh`脚本进行安装,执行如下命令:
|
||||
|
||||
```
|
||||
curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3
|
||||
chmod 700 get_helm.sh
|
||||
./get_helm.sh
|
||||
```
|
||||
|
||||
## 2. 获取并准备部署文件
|
||||
|
||||
在待部署的机器上新建一个目录,然后将 DevStar 的 Helm Chart 仓库克隆到该目录中。请注意,该仓库为私有仓库,需先获取访问权限。
|
||||
|
||||
如部署目标为 `devstar.cn`,请确保切换到对应的分支(如图所示):
|
||||
|
||||

|
||||
|
||||
将代码仓库克隆到本地后,目录中应包含以下四个脚本文件:
|
||||
|
||||

|
||||
|
||||
## 3. 首次安装
|
||||
|
||||
执行 `step1-install-helm.sh`脚本进行首次安装。安装时间取决于网络状况和镜像拉取速度,请耐心等待。
|
||||
|
||||
安装完成后,使用以下命令检查 Pod 状态:
|
||||
|
||||
```
|
||||
kubectl get pods -n devstar-studio-ns
|
||||
```
|
||||
|
||||
如发现 Pod 状态异常,可使用如下命令排查:
|
||||
|
||||
- 查看 Pod 日志:
|
||||
|
||||
```
|
||||
kubectl logs -n devstar-studio-ns <pod名称>
|
||||
```
|
||||
|
||||
- 查看 Pod 详细信息:
|
||||
|
||||
```
|
||||
kubectl describe pod -n devstar-studio-ns <pod名称>
|
||||
```
|
||||
|
||||
首次安装时,Pod 可能处于 `Pending`状态,通常是由于 PVC(PersistentVolumeClaim)未绑定到对应的 PV(PersistentVolume)所致。请检查 PV 与 PVC 的状态:
|
||||
|
||||
```
|
||||
kubectl get pv -A
|
||||
kubectl get pvc -A
|
||||
```
|
||||
|
||||

|
||||
|
||||
如发现有 PVC 处于 `Pending`状态,请手动创建并绑定对应的 PV。以下为 PV 的示例 YAML 配置,请根据实际情况修改相应字段:
|
||||
|
||||
```
|
||||
apiVersion: v1
|
||||
kind: PersistentVolume
|
||||
metadata:
|
||||
name: node2-local-pv-gitea # 请根据实际情况修改名称
|
||||
spec:
|
||||
capacity:
|
||||
storage: 10Gi
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
storageClassName: local
|
||||
persistentVolumeReclaimPolicy: Retain
|
||||
local:
|
||||
path: /mnt/datadisk/devstar/gitea-storage # 修改为实际存储路径
|
||||
nodeAffinity:
|
||||
required:
|
||||
nodeSelectorTerms:
|
||||
- matchExpressions:
|
||||
- key: kubernetes.io/hostname
|
||||
operator: In
|
||||
values:
|
||||
- node2 # 修改为实际节点名称
|
||||
volumeMode: Filesystem
|
||||
claimRef:
|
||||
name: gitea-shared-storage-claim
|
||||
namespace: devstar-studio-ns
|
||||
```
|
||||
|
||||
使用以下命令应用 PV 配置:
|
||||
|
||||
```
|
||||
kubectl apply -f <yaml文件名>
|
||||
```
|
||||
|
||||
## 4. 域名解析与证书配置
|
||||
|
||||
如部署环境为公网可访问(如 `devstar.cn`),请在腾讯云(或其他域名服务商)控制台中配置域名解析,并申请及配置 HTTPS 证书。具体操作请参考相关证书配置文档。
|
||||
|
||||
## 5. 更新部署
|
||||
|
||||
若修改了 `values.yaml`文件,请执行 `step2-upgrade-helm.sh`脚本进行更新。更新完成后,会看到类似如下提示:
|
||||
|
||||

|
||||
|
||||
更新后请再次检查 Pod 状态:
|
||||
|
||||
```
|
||||
kubectl get pods -n devstar-studio-ns
|
||||
```
|
||||
|
||||
## 6. 验证部署版本
|
||||
|
||||
您可以通过以下两种方式确认 DevStar 的版本是否更新成功:
|
||||
|
||||
1. **在 DevStar 主界面查看**登录系统后,可在主界面右下角查看当前版本号。
|
||||
|
||||

|
||||
|
||||
2. **在流水线页面查看**进入“工作流”→“流水线”,在如图所示位置也可查看版本信息:
|
||||
|
||||

|
||||
|
||||
通过比对版本号,即可确认系统是否已成功更新至目标版本。
|
||||
47
docs/devstar-deploy/SSL 证书配置与续期指南.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# SSL 证书配置与续期指南
|
||||
|
||||
目前需要配置证书的有devstar.cn和dev.devstar.cn
|
||||
|
||||
## 一、证书申请与续期
|
||||
|
||||
腾讯云提供的免费 HTTPS 证书有效期为 **90 天**,系统会在到期前通过短信提醒您及时续期。
|
||||
|
||||
### 操作步骤:
|
||||
|
||||
登录腾讯云控制台,搜索进入 **SSL 证书**管理页面。
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
在证书列表中查看已申请证书的有效期及到期时间。
|
||||
|
||||

|
||||
|
||||
点击右侧的 **快速续期**进入续期界面。
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
选择 **自动 DNS 验证**并勾选 **自动删除旧验证记录**,提交后等待证书签发。
|
||||
|
||||
证书签发后,在下载界面选择 **Nginx**格式,下载包含 `.crt`和 `.key`文件的证书包。
|
||||
|
||||

|
||||
|
||||
在master节点登录集群,进入当前要更新的域名对应的文件夹
|
||||
|
||||

|
||||
|
||||
## 二、证书部署
|
||||
|
||||
### 部署流程:
|
||||
|
||||
登录 Kubernetes 集群的 Master 节点,进入目标域名对应的证书目录。
|
||||
|
||||
将下载的 `.crt`和 `.key`文件上传至服务器,覆盖原有证书文件。
|
||||
|
||||
执行相应的脚本完成证书更新:**首次部署**:使用 `make-k8s-tls-secret.sh`**证书续期**:使用 `update-xxx-tls-secret.sh`
|
||||
|
||||

|
||||
repo.diff.bin
docs/devstar-deploy/assets/image-20251104201036920.png
Normal file
|
repo.diff.file_after repo.diff.file_image_width: | repo.diff.file_image_height: | repo.diff.file_byte_size: 15 KiB |
repo.diff.bin
docs/devstar-deploy/assets/image-20251104201132614.png
Normal file
|
repo.diff.file_after repo.diff.file_image_width: | repo.diff.file_image_height: | repo.diff.file_byte_size: 14 KiB |
repo.diff.bin
docs/devstar-deploy/assets/image-20251104203152647.png
Normal file
|
repo.diff.file_after repo.diff.file_image_width: | repo.diff.file_image_height: | repo.diff.file_byte_size: 56 KiB |
repo.diff.bin
docs/devstar-deploy/assets/image-20251104203353406.png
Normal file
|
repo.diff.file_after repo.diff.file_image_width: | repo.diff.file_image_height: | repo.diff.file_byte_size: 56 KiB |
repo.diff.bin
docs/devstar-deploy/assets/image-20251104203454915.png
Normal file
|
repo.diff.file_after repo.diff.file_image_width: | repo.diff.file_image_height: | repo.diff.file_byte_size: 55 KiB |
repo.diff.bin
docs/devstar-deploy/assets/image-20251104203757243.png
Normal file
|
repo.diff.file_after repo.diff.file_image_width: | repo.diff.file_image_height: | repo.diff.file_byte_size: 51 KiB |
repo.diff.bin
docs/devstar-deploy/assets/image-20251104204421088.png
Normal file
|
repo.diff.file_after repo.diff.file_image_width: | repo.diff.file_image_height: | repo.diff.file_byte_size: 7.9 KiB |
repo.diff.bin
docs/devstar-deploy/assets/image-20251104204424116.png
Normal file
|
repo.diff.file_after repo.diff.file_image_width: | repo.diff.file_image_height: | repo.diff.file_byte_size: 7.9 KiB |
repo.diff.bin
docs/devstar-deploy/assets/image-20251104204453766.png
Normal file
|
repo.diff.file_after repo.diff.file_image_width: | repo.diff.file_image_height: | repo.diff.file_byte_size: 128 KiB |
repo.diff.bin
docs/devstar-deploy/assets/image-20251104221729545.png
Normal file
|
repo.diff.file_after repo.diff.file_image_width: | repo.diff.file_image_height: | repo.diff.file_byte_size: 15 KiB |
repo.diff.bin
docs/devstar-deploy/assets/image-20251104221749140.png
Normal file
|
repo.diff.file_after repo.diff.file_image_width: | repo.diff.file_image_height: | repo.diff.file_byte_size: 41 KiB |
repo.diff.bin
docs/devstar-deploy/assets/image-20251104221811555.png
Normal file
|
repo.diff.file_after repo.diff.file_image_width: | repo.diff.file_image_height: | repo.diff.file_byte_size: 107 KiB |
repo.diff.bin
docs/devstar-deploy/assets/image-20251104221928154.png
Normal file
|
repo.diff.file_after repo.diff.file_image_width: | repo.diff.file_image_height: | repo.diff.file_byte_size: 29 KiB |
repo.diff.bin
docs/devstar-deploy/assets/image-20251104221950856.png
Normal file
|
repo.diff.file_after repo.diff.file_image_width: | repo.diff.file_image_height: | repo.diff.file_byte_size: 7.5 KiB |
repo.diff.bin
docs/devstar-deploy/assets/image-20251104221953043.png
Normal file
|
repo.diff.file_after repo.diff.file_image_width: | repo.diff.file_image_height: | repo.diff.file_byte_size: 7.5 KiB |
repo.diff.bin
docs/devstar-deploy/assets/image-20251104222023293.png
Normal file
|
repo.diff.file_after repo.diff.file_image_width: | repo.diff.file_image_height: | repo.diff.file_byte_size: 21 KiB |
repo.diff.bin
docs/devstar-deploy/assets/image-20251104224918197.png
Normal file
|
repo.diff.file_after repo.diff.file_image_width: | repo.diff.file_image_height: | repo.diff.file_byte_size: 199 KiB |
repo.diff.bin
docs/devstar-deploy/assets/image-20251104224951191.png
Normal file
|
repo.diff.file_after repo.diff.file_image_width: | repo.diff.file_image_height: | repo.diff.file_byte_size: 187 KiB |
repo.diff.bin
docs/devstar-deploy/assets/image-20251104225007427.png
Normal file
|
repo.diff.file_after repo.diff.file_image_width: | repo.diff.file_image_height: | repo.diff.file_byte_size: 69 KiB |
repo.diff.bin
docs/devstar-deploy/assets/image-20251104225042423.png
Normal file
|
repo.diff.file_after repo.diff.file_image_width: | repo.diff.file_image_height: | repo.diff.file_byte_size: 192 KiB |
repo.diff.bin
docs/devstar-deploy/assets/image-20251104225305942.png
Normal file
|
repo.diff.file_after repo.diff.file_image_width: | repo.diff.file_image_height: | repo.diff.file_byte_size: 96 KiB |
repo.diff.bin
docs/devstar-deploy/assets/image-20251104225334394.png
Normal file
|
repo.diff.file_after repo.diff.file_image_width: | repo.diff.file_image_height: | repo.diff.file_byte_size: 49 KiB |
repo.diff.bin
docs/devstar-deploy/assets/image-20251104225929257.png
Normal file
|
repo.diff.file_after repo.diff.file_image_width: | repo.diff.file_image_height: | repo.diff.file_byte_size: 32 KiB |
26
go.mod
@@ -21,6 +21,8 @@ require (
|
||||
gitea.com/lunny/levelqueue v0.4.2-0.20230414023320-3c0159fe0fe4
|
||||
github.com/42wim/httpsig v1.2.2
|
||||
github.com/42wim/sshsig v0.0.0-20240818000253-e3a6333df815
|
||||
github.com/ArtisanCloud/PowerLibs/v3 v3.3.2
|
||||
github.com/ArtisanCloud/PowerWeChat/v3 v3.4.21
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.0
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358
|
||||
@@ -135,12 +137,15 @@ require (
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
istio.io/api v1.27.2
|
||||
istio.io/client-go v1.27.2
|
||||
k8s.io/api v0.34.1
|
||||
k8s.io/apimachinery v0.34.1
|
||||
k8s.io/client-go v0.34.1
|
||||
k8s.io/component-base v0.34.1
|
||||
k8s.io/klog/v2 v2.130.1
|
||||
k8s.io/kubectl v0.34.1
|
||||
mvdan.cc/xurls/v2 v2.6.0
|
||||
sigs.k8s.io/controller-runtime v0.22.3
|
||||
sigs.k8s.io/yaml v1.6.0
|
||||
strk.kbt.io/projects/go/libravatar v0.0.0-20191008002943-06d1c002b251
|
||||
xorm.io/builder v0.3.13
|
||||
xorm.io/xorm v1.3.9
|
||||
@@ -176,14 +181,6 @@ require (
|
||||
github.com/moby/term v0.5.0 // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
|
||||
github.com/stoewer/go-strcase v1.3.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.34.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.5.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.2 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
|
||||
@@ -200,7 +197,6 @@ require (
|
||||
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
|
||||
sigs.k8s.io/randfill v1.0.0 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
|
||||
sigs.k8s.io/yaml v1.6.0 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -208,9 +204,7 @@ require (
|
||||
dario.cat/mergo v1.0.1 // indirect
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 // indirect
|
||||
github.com/ArtisanCloud/PowerLibs/v3 v3.3.2
|
||||
github.com/ArtisanCloud/PowerSocialite/v3 v3.0.8 // indirect
|
||||
github.com/ArtisanCloud/PowerWeChat/v3 v3.4.21
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect
|
||||
github.com/DataDog/zstd v1.5.7 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
@@ -326,6 +320,7 @@ require (
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/skeema/knownhosts v1.3.1 // indirect
|
||||
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
|
||||
github.com/stoewer/go-strcase v1.3.0 // indirect
|
||||
github.com/unknwon/com v1.0.1 // indirect
|
||||
github.com/valyala/fastjson v1.6.4 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
@@ -336,8 +331,15 @@ require (
|
||||
github.com/zeebo/assert v1.3.0 // indirect
|
||||
github.com/zeebo/blake3 v0.2.4 // indirect
|
||||
go.etcd.io/bbolt v1.4.2 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect
|
||||
go.opentelemetry.io/otel v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.34.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.35.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.5.0 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
@@ -348,8 +350,6 @@ require (
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250422160041-2d3770c4ea7f // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
k8s.io/api v0.34.1
|
||||
k8s.io/client-go v0.34.1
|
||||
)
|
||||
|
||||
replace github.com/hashicorp/go-version => github.com/6543/go-version v1.3.1
|
||||
|
||||
207
models/appstore/appstore.go
Normal file
@@ -0,0 +1,207 @@
|
||||
/*
|
||||
* Copyright (c) Mengning Software. 2025. All rights reserved.
|
||||
* Authors: DevStar Team, panshuxiao
|
||||
* Create: 2025-11-19
|
||||
* Description: Defines the AppStore model and datastore helpers.
|
||||
*/
|
||||
|
||||
package appstore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
)
|
||||
|
||||
// AppStore represents an application in the app store
|
||||
type AppStore struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
AppID string `xorm:"UNIQUE NOT NULL"` // 应用唯一标识,如 "nginx"
|
||||
Name string `xorm:"NOT NULL"` // 应用名称,如 "Nginx Web Server"
|
||||
Description string `xorm:"TEXT"` // 应用描述
|
||||
Category string `xorm:"INDEX"` // 应用分类,如 "web-server"
|
||||
Tags string `xorm:"TEXT"` // 标签,JSON格式存储
|
||||
Icon string `xorm:"VARCHAR(2048)"` // 应用图标URL
|
||||
Author string `xorm:"VARCHAR(128)"` // 应用作者
|
||||
Website string `xorm:"VARCHAR(2048)"` // 应用官网
|
||||
Repository string `xorm:"VARCHAR(2048)"` // 应用仓库地址
|
||||
License string `xorm:"VARCHAR(64)"` // 许可证类型
|
||||
Version string `xorm:"NOT NULL"` // 应用版本
|
||||
DeploymentType string `xorm:"VARCHAR(20) NOT NULL DEFAULT 'docker' INDEX"` // 部署类型:docker, kubernetes, both
|
||||
JSONData string `xorm:"LONGTEXT NOT NULL"` // 完整的应用JSON描述
|
||||
|
||||
// 统计信息
|
||||
InstallCount int64 `xorm:"NOT NULL DEFAULT 0"` // 安装次数
|
||||
|
||||
// 状态
|
||||
IsActive bool `xorm:"INDEX NOT NULL DEFAULT true"` // 是否激活
|
||||
IsOfficial bool `xorm:"NOT NULL DEFAULT false"` // 是否官方应用
|
||||
IsVerified bool `xorm:"NOT NULL DEFAULT false"` // 是否已验证
|
||||
|
||||
// 时间戳
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
|
||||
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
|
||||
}
|
||||
|
||||
// TableName returns the table name for AppStore
|
||||
func (a *AppStore) TableName() string {
|
||||
return "app_store"
|
||||
}
|
||||
|
||||
// BeforeUpdate is called before updating the record
|
||||
func (a *AppStore) BeforeUpdate() {
|
||||
a.UpdatedUnix = timeutil.TimeStampNow()
|
||||
}
|
||||
|
||||
// BeforeInsert is called before inserting the record
|
||||
func (a *AppStore) BeforeInsert() {
|
||||
a.CreatedUnix = timeutil.TimeStampNow()
|
||||
a.UpdatedUnix = timeutil.TimeStampNow()
|
||||
}
|
||||
|
||||
// GetTagsList returns the tags as a string slice
|
||||
func (a *AppStore) GetTagsList() []string {
|
||||
if a.Tags == "" {
|
||||
return []string{}
|
||||
}
|
||||
return strings.Split(a.Tags, ",")
|
||||
}
|
||||
|
||||
// SetTagsList sets the tags from a string slice
|
||||
func (a *AppStore) SetTagsList(tags []string) {
|
||||
a.Tags = strings.Join(tags, ",")
|
||||
}
|
||||
|
||||
// 数据库操作函数
|
||||
|
||||
// GetAppStoreByID 根据ID获取应用商店记录
|
||||
func GetAppStoreByID(ctx context.Context, id int64) (*AppStore, error) {
|
||||
app := &AppStore{}
|
||||
has, err := db.GetEngine(ctx).ID(id).Get(app)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !has {
|
||||
return nil, fmt.Errorf("app store not found: %d", id)
|
||||
}
|
||||
return app, nil
|
||||
}
|
||||
|
||||
// GetAppStoreByAppID 根据AppID获取应用商店记录
|
||||
func GetAppStoreByAppID(ctx context.Context, appID string) (*AppStore, error) {
|
||||
app := &AppStore{}
|
||||
has, err := db.GetEngine(ctx).Where("app_id = ?", appID).Get(app)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !has {
|
||||
return nil, fmt.Errorf("app store not found: %s", appID)
|
||||
}
|
||||
return app, nil
|
||||
}
|
||||
|
||||
// GetAppStoreByName 根据应用名称获取应用商店记录
|
||||
func GetAppStoreByName(ctx context.Context, name string) (*AppStore, error) {
|
||||
app := &AppStore{}
|
||||
has, err := db.GetEngine(ctx).Where("name = ?", name).Get(app)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !has {
|
||||
return nil, fmt.Errorf("app store not found: %s", name)
|
||||
}
|
||||
return app, nil
|
||||
}
|
||||
|
||||
// ListAppStores 列出应用商店记录
|
||||
func ListAppStores(ctx context.Context, opts *db.ListOptions) ([]*AppStore, error) {
|
||||
var apps []*AppStore
|
||||
sess := db.GetEngine(ctx).Where("is_active = ?", true)
|
||||
|
||||
if opts != nil {
|
||||
sess = db.SetSessionPagination(sess, opts)
|
||||
}
|
||||
|
||||
err := sess.OrderBy("created_unix DESC").Find(&apps)
|
||||
return apps, err
|
||||
}
|
||||
|
||||
// CreateAppStore 创建应用商店记录
|
||||
func CreateAppStore(ctx context.Context, app *AppStore) error {
|
||||
// 检查AppID是否已存在
|
||||
has, err := db.GetEngine(ctx).Where("app_id = ?", app.AppID).Exist(&AppStore{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if has {
|
||||
return fmt.Errorf("app store already exists: %s", app.AppID)
|
||||
}
|
||||
|
||||
_, err = db.GetEngine(ctx).Insert(app)
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdateAppStore 更新应用商店记录
|
||||
func UpdateAppStore(ctx context.Context, app *AppStore) error {
|
||||
_, err := db.GetEngine(ctx).ID(app.ID).Update(app)
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteAppStore 删除应用商店记录
|
||||
func DeleteAppStore(ctx context.Context, id int64) error {
|
||||
_, err := db.GetEngine(ctx).ID(id).Delete(&AppStore{})
|
||||
return err
|
||||
}
|
||||
|
||||
// GetCategories 获取所有应用分类
|
||||
func GetCategories(ctx context.Context) ([]string, error) {
|
||||
var categories []string
|
||||
err := db.GetEngine(ctx).Table("app_store").
|
||||
Where("is_active = ?", true).
|
||||
Distinct("category").
|
||||
Cols("category").
|
||||
Find(&categories)
|
||||
return categories, err
|
||||
}
|
||||
|
||||
// GetTags 获取所有应用标签
|
||||
func GetTags(ctx context.Context) ([]string, error) {
|
||||
var tags []string
|
||||
if err := db.GetEngine(ctx).Table("app_store").
|
||||
Where("is_active = ? AND tags != ''", true).
|
||||
Cols("tags").
|
||||
Find(&tags); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 解析标签字符串并去重
|
||||
tagSet := make(map[string]bool)
|
||||
for _, tagStr := range tags {
|
||||
tagList := strings.Split(tagStr, ",")
|
||||
for _, tag := range tagList {
|
||||
if tag != "" {
|
||||
tagSet[tag] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result := make([]string, 0, len(tagSet))
|
||||
for tag := range tagSet {
|
||||
result = append(result, tag)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// IncrementInstallCount 增加安装次数
|
||||
func IncrementInstallCount(ctx context.Context, appID string) error {
|
||||
_, err := db.GetEngine(ctx).Exec("UPDATE app_store SET install_count = install_count + 1 WHERE app_id = ?", appID)
|
||||
return err
|
||||
}
|
||||
|
||||
func init() {
|
||||
db.RegisterModel(new(AppStore))
|
||||
}
|
||||
139
models/appstore/user_app_instance.go
Normal file
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* Copyright (c) Mengning Software. 2025. All rights reserved.
|
||||
* Authors: DevStar Team, panshuxiao
|
||||
* Create: 2025-11-19
|
||||
* Description: Represents user app instances and DB helpers.
|
||||
*/
|
||||
|
||||
package appstore
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
// UserAppInstance 用户应用实例
|
||||
type UserAppInstance struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
UserID int64 `xorm:"INDEX NOT NULL"` // 用户ID
|
||||
AppID string `xorm:"INDEX NOT NULL"` // 应用模板ID
|
||||
InstanceName string `xorm:"NOT NULL"` // 实例名称(用户自定义)
|
||||
|
||||
// 用户配置
|
||||
UserConfig string `xorm:"TEXT"` // 用户配置JSON
|
||||
MergedApp string `xorm:"TEXT"` // 合并后的完整应用配置JSON
|
||||
|
||||
// 部署信息
|
||||
DeployType string `xorm:"NOT NULL"` // 实际部署类型 (kubernetes/docker)
|
||||
|
||||
// Kubernetes 凭据(URL + Token)
|
||||
K8sURL string `xorm:"TEXT"`
|
||||
K8sToken string `xorm:"TEXT"`
|
||||
|
||||
// 元数据
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
|
||||
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
|
||||
}
|
||||
|
||||
// TableName 设置表名
|
||||
func (uai *UserAppInstance) TableName() string {
|
||||
return "user_app_instance"
|
||||
}
|
||||
|
||||
// BeforeInsert 插入前的钩子
|
||||
func (uai *UserAppInstance) BeforeInsert() {
|
||||
uai.CreatedUnix = timeutil.TimeStampNow()
|
||||
uai.UpdatedUnix = timeutil.TimeStampNow()
|
||||
}
|
||||
|
||||
// BeforeUpdate 更新前的钩子
|
||||
func (uai *UserAppInstance) BeforeUpdate() {
|
||||
uai.UpdatedUnix = timeutil.TimeStampNow()
|
||||
}
|
||||
|
||||
// GetUserAppInstances 按用户查询应用实例
|
||||
func GetUserAppInstances(ctx context.Context, userID int64) ([]*UserAppInstance, error) {
|
||||
return db.Find[UserAppInstance](ctx, FindUserAppInstanceOptions{
|
||||
UserID: userID,
|
||||
})
|
||||
}
|
||||
|
||||
// GetUserAppInstance 查询特定应用实例
|
||||
func GetUserAppInstance(ctx context.Context, userID, instanceID int64) (*UserAppInstance, error) {
|
||||
instance := new(UserAppInstance)
|
||||
has, err := db.GetEngine(ctx).Where("id = ? AND user_id = ?", instanceID, userID).Get(instance)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !has {
|
||||
return nil, nil
|
||||
}
|
||||
return instance, nil
|
||||
}
|
||||
|
||||
// GetUserAppInstanceByAppID 按应用ID查询用户的实例
|
||||
func GetUserAppInstanceByAppID(ctx context.Context, userID int64, appID string) (*UserAppInstance, error) {
|
||||
instance := new(UserAppInstance)
|
||||
has, err := db.GetEngine(ctx).Where("user_id = ? AND app_id = ?", userID, appID).Get(instance)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !has {
|
||||
return nil, nil
|
||||
}
|
||||
return instance, nil
|
||||
}
|
||||
|
||||
// CreateUserAppInstance 创建用户应用实例
|
||||
func CreateUserAppInstance(ctx context.Context, instance *UserAppInstance) error {
|
||||
return db.Insert(ctx, instance)
|
||||
}
|
||||
|
||||
// UpdateUserAppInstance 更新用户应用实例
|
||||
func UpdateUserAppInstance(ctx context.Context, instance *UserAppInstance) error {
|
||||
_, err := db.GetEngine(ctx).ID(instance.ID).Update(instance)
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteUserAppInstance 删除用户应用实例
|
||||
func DeleteUserAppInstance(ctx context.Context, userID, instanceID int64) error {
|
||||
_, err := db.GetEngine(ctx).Where("id = ? AND user_id = ?", instanceID, userID).Delete(&UserAppInstance{})
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteUserAppInstanceByAppID 按应用ID删除用户实例
|
||||
func DeleteUserAppInstanceByAppID(ctx context.Context, userID int64, appID string) error {
|
||||
_, err := db.GetEngine(ctx).Where("user_id = ? AND app_id = ?", userID, appID).Delete(&UserAppInstance{})
|
||||
return err
|
||||
}
|
||||
|
||||
// FindUserAppInstanceOptions 查询选项
|
||||
type FindUserAppInstanceOptions struct {
|
||||
db.ListOptions
|
||||
UserID int64 // 用户ID
|
||||
AppID string // 应用ID(可选)
|
||||
}
|
||||
|
||||
// ToConds 转换为查询条件
|
||||
func (opts FindUserAppInstanceOptions) ToConds() builder.Cond {
|
||||
conds := builder.NewCond()
|
||||
if opts.UserID != 0 {
|
||||
conds = conds.And(builder.Eq{"user_id": opts.UserID})
|
||||
}
|
||||
if opts.AppID != "" {
|
||||
conds = conds.And(builder.Eq{"app_id": opts.AppID})
|
||||
}
|
||||
return conds
|
||||
}
|
||||
|
||||
// ToOrders 转换为排序条件
|
||||
func (opts FindUserAppInstanceOptions) ToOrders() string {
|
||||
return "id DESC"
|
||||
}
|
||||
|
||||
func init() {
|
||||
db.RegisterModel(new(UserAppInstance))
|
||||
}
|
||||
51
models/migrations/devstar_v1_0/dv2.go
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (c) Mengning Software. 2025. All rights reserved.
|
||||
* Authors: DevStar Team, panshuxiao
|
||||
* Create: 2025-11-19
|
||||
* Description: Migration dv2 introducing app store tables.
|
||||
*/
|
||||
|
||||
package devstar_v1_0
|
||||
|
||||
// 构建 DevStar Studio v1.0 所需数据库类型
|
||||
// 从 dv2 到 dv3
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
appstore_model "code.gitea.io/gitea/models/appstore"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
// ErrMigrateDevstarDatabase represents an error during DevStar database migration
|
||||
type ErrMigrateDevstarDatabase struct {
|
||||
Step string
|
||||
Message string
|
||||
}
|
||||
|
||||
func (e ErrMigrateDevstarDatabase) Error() string {
|
||||
return fmt.Sprintf("DevStar database migration failed at step '%s': %s", e.Step, e.Message)
|
||||
}
|
||||
|
||||
// InitializeAppStoreDbTables 初始化应用商店相关数据库表
|
||||
func InitializeAppStoreDbTables(x *xorm.Engine) error {
|
||||
// 初始化应用商店表
|
||||
if err := addDBAppStore(x); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// addDBAppStore 初始化应用商店表
|
||||
func addDBAppStore(x *xorm.Engine) error {
|
||||
err := x.Sync(new(appstore_model.AppStore))
|
||||
if err != nil {
|
||||
return ErrMigrateDevstarDatabase{
|
||||
Step: "create table 'app_store'",
|
||||
Message: err.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
26
models/migrations/devstar_v1_0/dv3.go
Normal file
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (c) Mengning Software. 2025. All rights reserved.
|
||||
* Authors: DevStar Team, panshuxiao
|
||||
* Create: 2025-11-19
|
||||
* Description: Migration dv3 for user app instance schema.
|
||||
*/
|
||||
|
||||
package devstar_v1_0
|
||||
|
||||
import (
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
// AddDeploymentTypeToAppStore adds deployment_type column to app_store and creates index
|
||||
func AddDeploymentTypeToAppStore(x *xorm.Engine) error {
|
||||
// add column if not exists (most DBs will ignore existing)
|
||||
_, err := x.Exec("ALTER TABLE app_store ADD COLUMN deployment_type VARCHAR(20) NOT NULL DEFAULT 'docker'")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = x.Exec("CREATE INDEX idx_app_store_deployment_type ON app_store(deployment_type)")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
30
models/migrations/devstar_v1_0/dv4.go
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (c) Mengning Software. 2025. All rights reserved.
|
||||
* Authors: DevStar Team, panshuxiao
|
||||
* Create: 2025-11-19
|
||||
* Description: Migration dv4 extending app store relations.
|
||||
*/
|
||||
|
||||
package devstar_v1_0
|
||||
|
||||
// 构建 DevStar Studio v1.0 所需数据库类型
|
||||
// 从 dv3 到 dv4 - 添加用户应用实例表
|
||||
|
||||
import (
|
||||
appstore_model "code.gitea.io/gitea/models/appstore"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
// AddUserAppInstanceTable 创建用户应用实例表
|
||||
func AddUserAppInstanceTable(x *xorm.Engine) error {
|
||||
// 创建用户应用实例表
|
||||
err := x.Sync(new(appstore_model.UserAppInstance))
|
||||
if err != nil {
|
||||
return ErrMigrateDevstarDatabase{
|
||||
Step: "create table 'user_app_instance'",
|
||||
Message: err.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
29
models/migrations/devstar_v1_0/dv5.go
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (c) Mengning Software. 2025. All rights reserved.
|
||||
* Authors: DevStar Team, panshuxiao
|
||||
* Create: 2025-11-24
|
||||
* Description: Migration dv5 adding Kubernetes credential columns.
|
||||
*/
|
||||
|
||||
package devstar_v1_0
|
||||
|
||||
import (
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
// AddK8sCredentialColumns adds k8s_url and k8s_token columns to user_app_instance.
|
||||
func AddK8sCredentialColumns(x *xorm.Engine) error {
|
||||
if _, err := x.Exec("ALTER TABLE user_app_instance ADD COLUMN k8s_url TEXT DEFAULT ''"); err != nil {
|
||||
return ErrMigrateDevstarDatabase{
|
||||
Step: "add column 'k8s_url' to user_app_instance",
|
||||
Message: err.Error(),
|
||||
}
|
||||
}
|
||||
if _, err := x.Exec("ALTER TABLE user_app_instance ADD COLUMN k8s_token TEXT DEFAULT ''"); err != nil {
|
||||
return ErrMigrateDevstarDatabase{
|
||||
Step: "add column 'k8s_token' to user_app_instance",
|
||||
Message: err.Error(),
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -125,6 +125,7 @@ type User struct {
|
||||
AllowImportLocal bool // Allow migrate repository by local path
|
||||
AllowCreateOrganization bool `xorm:"DEFAULT true"`
|
||||
AllowCreateDevcontainer bool `xorm:"DEFAULT false"`
|
||||
AllowCreateActRunner bool `xorm:"DEFAULT false"`
|
||||
|
||||
// true: the user is not allowed to log in Web UI. Git/SSH access could still be allowed (please refer to Git/SSH access related code/documents)
|
||||
ProhibitLogin bool `xorm:"NOT NULL DEFAULT false"`
|
||||
@@ -274,6 +275,11 @@ func (u *User) CanCreateDevcontainer() bool {
|
||||
return u.AllowCreateDevcontainer
|
||||
}
|
||||
|
||||
// CanCreateActrunner returns true if user can create organisation.
|
||||
func (u *User) CanCreateActrunner() bool {
|
||||
return u.AllowCreateActRunner
|
||||
}
|
||||
|
||||
// CanEditGitHook returns true if user can edit Git hooks.
|
||||
func (u *User) CanEditGitHook() bool {
|
||||
return !setting.DisableGitHooks && (u.IsAdmin || u.AllowGitHook)
|
||||
@@ -640,6 +646,7 @@ type CreateUserOverwriteOptions struct {
|
||||
Visibility *structs.VisibleType
|
||||
AllowCreateOrganization optional.Option[bool]
|
||||
AllowCreateDevcontainer optional.Option[bool]
|
||||
AllowCreateActRunner optional.Option[bool]
|
||||
EmailNotificationsPreference *string
|
||||
MaxRepoCreation *int
|
||||
Theme *string
|
||||
@@ -667,6 +674,8 @@ func createUser(ctx context.Context, u *User, meta *Meta, createdByAdmin bool, o
|
||||
u.KeepEmailPrivate = setting.Service.DefaultKeepEmailPrivate
|
||||
u.Visibility = setting.Service.DefaultUserVisibilityMode
|
||||
u.AllowCreateOrganization = setting.Service.DefaultAllowCreateOrganization && !setting.Admin.DisableRegularOrgCreation
|
||||
u.AllowCreateDevcontainer = setting.Service.DefaultAllowCreateDevcontainer
|
||||
u.AllowCreateActRunner = setting.Service.DefaultAllowCreateActRunner
|
||||
u.EmailNotificationsPreference = setting.Admin.DefaultEmailNotification
|
||||
u.MaxRepoCreation = -1
|
||||
u.Theme = setting.UI.DefaultTheme
|
||||
|
||||
@@ -59,6 +59,7 @@ func NewActionsUser() *User {
|
||||
Type: UserTypeBot,
|
||||
AllowCreateOrganization: true,
|
||||
AllowCreateDevcontainer: false,
|
||||
AllowCreateActRunner: false,
|
||||
Visibility: structs.VisibleTypePublic,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,24 +89,22 @@ func GetContainerStatus(cli *client.Client, containerID string) (string, error)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
state := containerInfo.State
|
||||
return state.Status, nil
|
||||
}
|
||||
func PushImage(dockerHost string, username string, password string, registryUrl string, imageRef string) error {
|
||||
script := "docker " + "-H " + dockerHost + " login -u " + username + " -p " + password + " " + registryUrl + " "
|
||||
cmd := exec.Command("sh", "-c", script)
|
||||
_, err := cmd.CombinedOutput()
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("%s \n 镜像登录失败: %s", string(output), err.Error())
|
||||
}
|
||||
// 推送到仓库
|
||||
script = "docker " + "-H " + dockerHost + " push " + imageRef
|
||||
cmd = exec.Command("sh", "-c", script)
|
||||
_, err = cmd.CombinedOutput()
|
||||
|
||||
output, err = cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("%s \n 镜像推送失败: %s", string(output), err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -157,6 +157,18 @@ type GatewayTLS struct {
|
||||
// +kubebuilder:default="TLSv1_2"
|
||||
// +kubebuilder:validation:Enum=TLSv1_0;TLSv1_1;TLSv1_2;TLSv1_3
|
||||
MinProtocolVersion string `json:"minProtocolVersion,omitempty"`
|
||||
|
||||
// 目标 Secret 所在命名空间,默认 istio-system;若不指定,控制器将自动探测 IngressGateway 所在命名空间
|
||||
// +optional
|
||||
SecretNamespace string `json:"secretNamespace,omitempty"`
|
||||
|
||||
// 用户直接提供的证书(PEM,等价于 tls.crt)。若提供,需同时提供 privateKey,并必须指定 secretName
|
||||
// +optional
|
||||
Certificate string `json:"certificate,omitempty"`
|
||||
|
||||
// 用户直接提供的私钥(PEM,等价于 tls.key)。若提供,需同时提供 certificate,并必须指定 secretName
|
||||
// +optional
|
||||
PrivateKey string `json:"privateKey,omitempty"`
|
||||
}
|
||||
|
||||
// MeshConfig 定义服务网格相关配置
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
/*
|
||||
* Copyright (c) Mengning Software. 2025. All rights reserved.
|
||||
* Authors: DevStar Team, panshuxiao
|
||||
* Create: 2025-11-19
|
||||
* Description: GroupVersion metadata for Application API v1.
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright 2025.
|
||||
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
//go:build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
* Copyright (c) Mengning Software. 2025. All rights reserved.
|
||||
* Authors: DevStar Team, panshuxiao
|
||||
* Create: 2025-11-19
|
||||
* Description: Auto-generated deepcopy funcs for Application API.
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright 2025.
|
||||
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
/*
|
||||
* Copyright (c) Mengning Software. 2025. All rights reserved.
|
||||
* Authors: DevStar Team, panshuxiao
|
||||
* Create: 2025-11-19
|
||||
* Description: DevContainerApp CRD Go type definitions.
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright 2024.
|
||||
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
/*
|
||||
* Copyright (c) Mengning Software. 2025. All rights reserved.
|
||||
* Authors: DevStar Team, panshuxiao
|
||||
* Create: 2025-11-19
|
||||
* Description: GroupVersion metadata for DevContainer API v1.
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright 2024.
|
||||
|
||||
|
||||
@@ -1,5 +1,12 @@
|
||||
//go:build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
* Copyright (c) Mengning Software. 2025. All rights reserved.
|
||||
* Authors: DevStar Team, panshuxiao
|
||||
* Create: 2025-11-19
|
||||
* Description: Auto-generated deepcopy funcs for DevContainer API.
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright 2024.
|
||||
|
||||
|
||||
361
modules/k8s/application/application.go
Normal file
@@ -0,0 +1,361 @@
|
||||
/*
|
||||
* Copyright (c) Mengning Software. 2025. All rights reserved.
|
||||
* Authors: DevStar Team, panshuxiao
|
||||
* Create: 2025-11-19
|
||||
* Description: Application install runner and job lifecycle.
|
||||
*/
|
||||
|
||||
package application
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
applicationv1 "code.gitea.io/gitea/modules/k8s/api/application/v1"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
|
||||
application_errors "code.gitea.io/gitea/modules/k8s/application/errors"
|
||||
apimachinery_apis_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
apimachinery_apis_v1_unstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
apimachinery_runtime_utils "k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
apimachinery_watch "k8s.io/apimachinery/pkg/watch"
|
||||
dynamic_client "k8s.io/client-go/dynamic"
|
||||
)
|
||||
|
||||
// Application 资源定义
|
||||
var applicationGroupVersionResource = schema.GroupVersionResource{
|
||||
Group: "application.devstar.cn",
|
||||
Version: "v1",
|
||||
Resource: "applications",
|
||||
}
|
||||
|
||||
// IsApplicationStatusReady 判断 Application 是否就绪
|
||||
func IsApplicationStatusReady(applicationStatus *applicationv1.ApplicationStatus) bool {
|
||||
if applicationStatus == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// 如果不是 Running 状态,则未就绪
|
||||
if applicationStatus.Phase != "Running" {
|
||||
return false
|
||||
}
|
||||
|
||||
// 零副本应用被认为是就绪的(暂停状态)
|
||||
if applicationStatus.Replicas == 0 && applicationStatus.ReadyReplicas == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
// 有副本的应用需要至少有一个就绪副本
|
||||
return applicationStatus.ReadyReplicas > 0
|
||||
}
|
||||
|
||||
// GetApplication 获取 Application
|
||||
func GetApplication(ctx context.Context, client dynamic_client.Interface, opts *GetApplicationOptions) (*applicationv1.Application, error) {
|
||||
// 参数检查
|
||||
if ctx == nil || opts == nil || len(opts.Namespace) == 0 || len(opts.Name) == 0 {
|
||||
return nil, application_errors.ErrIllegalApplicationParameters{
|
||||
FieldList: []string{"ctx", "opts", "opts.Name", "opts.Namespace"},
|
||||
Message: "cannot be nil",
|
||||
}
|
||||
}
|
||||
|
||||
// 获取 Application 资源
|
||||
applicationUnstructured, err := client.Resource(applicationGroupVersionResource).
|
||||
Namespace(opts.Namespace).Get(ctx, opts.Name, opts.GetOptions)
|
||||
if err != nil {
|
||||
return nil, application_errors.ErrOperateApplication{
|
||||
Action: "Get Application through k8s API Server",
|
||||
Message: err.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
// 转换为 Application 对象
|
||||
application := &applicationv1.Application{}
|
||||
err = apimachinery_runtime_utils.DefaultUnstructuredConverter.
|
||||
FromUnstructured(applicationUnstructured.Object, application)
|
||||
if err != nil {
|
||||
return nil, application_errors.ErrOperateApplication{
|
||||
Action: "Convert k8s API Server unstructured response into Application",
|
||||
Message: err.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否需要等待就绪
|
||||
if !IsApplicationStatusReady(&application.Status) && opts.Wait {
|
||||
applicationStatusVO, err := waitUntilApplicationReadyWithTimeout(ctx, client, opts)
|
||||
if err != nil {
|
||||
return nil, application_errors.ErrOperateApplication{
|
||||
Action: "wait for k8s Application to be ready",
|
||||
Message: err.Error(),
|
||||
}
|
||||
}
|
||||
application.Status = *applicationStatusVO
|
||||
}
|
||||
|
||||
return application, nil
|
||||
}
|
||||
|
||||
// CreateApplication 创建 Application
|
||||
func CreateApplication(ctx context.Context, client dynamic_client.Interface, opts *CreateApplicationOptions) (*applicationv1.Application, error) {
|
||||
log.Info("Creating Application with options: name=%s, namespace=%s, image=%s",
|
||||
opts.Name, opts.Namespace, opts.Template.Image)
|
||||
|
||||
// 处理默认的 Resources
|
||||
var resources applicationv1.ResourceRequirements
|
||||
if opts.Resources != nil {
|
||||
resources = *opts.Resources
|
||||
}
|
||||
// 如果 Resources 为 nil,使用空的 ResourceRequirements(所有字段都是零值)
|
||||
|
||||
// 创建 Application 资源定义
|
||||
application := &applicationv1.Application{
|
||||
TypeMeta: apimachinery_apis_v1.TypeMeta{
|
||||
Kind: "Application",
|
||||
APIVersion: "application.devstar.cn/v1",
|
||||
},
|
||||
ObjectMeta: apimachinery_apis_v1.ObjectMeta{
|
||||
Name: opts.Name,
|
||||
Namespace: opts.Namespace,
|
||||
Labels: map[string]string{
|
||||
"app.kubernetes.io/name": opts.Name,
|
||||
"app.kubernetes.io/component": opts.Component,
|
||||
"app.kubernetes.io/managed-by": "devstar",
|
||||
},
|
||||
},
|
||||
Spec: applicationv1.ApplicationSpec{
|
||||
Template: opts.Template,
|
||||
Replicas: opts.Replicas,
|
||||
Environment: opts.Environment,
|
||||
Resources: resources,
|
||||
Expose: opts.Expose,
|
||||
Service: opts.Service,
|
||||
NetworkPolicy: opts.NetworkPolicy,
|
||||
TrafficPolicy: opts.TrafficPolicy,
|
||||
},
|
||||
}
|
||||
|
||||
// 转换为 JSON
|
||||
jsonData, err := json.Marshal(application)
|
||||
if err != nil {
|
||||
log.Error("Failed to marshal Application to JSON: %v", err)
|
||||
return nil, application_errors.ErrOperateApplication{
|
||||
Action: "Marshal JSON",
|
||||
Message: err.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
log.Debug("Generated JSON for Application:\n%s", string(jsonData))
|
||||
|
||||
// 转换为 Unstructured 对象
|
||||
unstructuredObj := &apimachinery_apis_v1_unstructured.Unstructured{}
|
||||
err = unstructuredObj.UnmarshalJSON(jsonData)
|
||||
if err != nil {
|
||||
log.Error("Failed to unmarshal JSON to Unstructured: %v", err)
|
||||
return nil, application_errors.ErrOperateApplication{
|
||||
Action: "Unmarshal JSON to Unstructured",
|
||||
Message: err.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
// 创建资源
|
||||
log.Info("Creating Application resource in namespace %s", opts.Namespace)
|
||||
result, err := client.Resource(applicationGroupVersionResource).
|
||||
Namespace(opts.Namespace).Create(ctx, unstructuredObj, opts.CreateOptions)
|
||||
if err != nil {
|
||||
log.Error("Failed to create Application: %v", err)
|
||||
return nil, application_errors.ErrOperateApplication{
|
||||
Action: "create Application via Dynamic Client",
|
||||
Message: err.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
log.Info("Application resource created successfully")
|
||||
|
||||
// 转换结果
|
||||
resultJSON, err := result.MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, application_errors.ErrOperateApplication{
|
||||
Action: "Marshal result JSON",
|
||||
Message: err.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
createdApplication := &applicationv1.Application{}
|
||||
if err := json.Unmarshal(resultJSON, createdApplication); err != nil {
|
||||
return nil, application_errors.ErrOperateApplication{
|
||||
Action: "Unmarshal result JSON",
|
||||
Message: err.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
return createdApplication, nil
|
||||
}
|
||||
|
||||
// UpdateApplication 更新 Application
|
||||
func UpdateApplication(ctx context.Context, client dynamic_client.Interface, opts *UpdateApplicationOptions) (*applicationv1.Application, error) {
|
||||
log.Info("Updating Application: name=%s, namespace=%s", opts.Name, opts.Namespace)
|
||||
|
||||
// 转换为 JSON
|
||||
jsonData, err := json.Marshal(opts.Application)
|
||||
if err != nil {
|
||||
return nil, application_errors.ErrOperateApplication{
|
||||
Action: "Marshal Application to JSON",
|
||||
Message: err.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
// 转换为 Unstructured
|
||||
unstructuredObj := &apimachinery_apis_v1_unstructured.Unstructured{}
|
||||
err = unstructuredObj.UnmarshalJSON(jsonData)
|
||||
if err != nil {
|
||||
return nil, application_errors.ErrOperateApplication{
|
||||
Action: "Unmarshal JSON to Unstructured",
|
||||
Message: err.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
// 更新资源
|
||||
result, err := client.Resource(applicationGroupVersionResource).
|
||||
Namespace(opts.Namespace).Update(ctx, unstructuredObj, opts.UpdateOptions)
|
||||
log.Info("Updated Application: %v", result)
|
||||
log.Info("Error: %v", err)
|
||||
if err != nil {
|
||||
return nil, application_errors.ErrOperateApplication{
|
||||
Action: "update Application via Dynamic Client",
|
||||
Message: err.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
// 转换结果
|
||||
resultJSON, err := result.MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, application_errors.ErrOperateApplication{
|
||||
Action: "Marshal result JSON",
|
||||
Message: err.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
updatedApplication := &applicationv1.Application{}
|
||||
if err := json.Unmarshal(resultJSON, updatedApplication); err != nil {
|
||||
return nil, application_errors.ErrOperateApplication{
|
||||
Action: "Unmarshal result JSON",
|
||||
Message: err.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
return updatedApplication, nil
|
||||
}
|
||||
|
||||
// DeleteApplication 删除 Application
|
||||
func DeleteApplication(ctx context.Context, client dynamic_client.Interface, opts *DeleteApplicationOptions) error {
|
||||
if ctx == nil || opts == nil || len(opts.Namespace) == 0 || len(opts.Name) == 0 {
|
||||
return application_errors.ErrIllegalApplicationParameters{
|
||||
FieldList: []string{"ctx", "opts", "opts.Name", "opts.Namespace"},
|
||||
Message: "cannot be nil",
|
||||
}
|
||||
}
|
||||
|
||||
err := client.Resource(applicationGroupVersionResource).
|
||||
Namespace(opts.Namespace).Delete(ctx, opts.Name, opts.DeleteOptions)
|
||||
if err != nil {
|
||||
log.Warn("Failed to delete Application '%s' in namespace '%s': %s",
|
||||
opts.Name, opts.Namespace, err.Error())
|
||||
return application_errors.ErrOperateApplication{
|
||||
Action: fmt.Sprintf("delete application '%s' in namespace '%s'", opts.Name, opts.Namespace),
|
||||
Message: err.Error(),
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListApplications 列出 Applications
|
||||
func ListApplications(ctx context.Context, client dynamic_client.Interface, opts *ListApplicationsOptions) (*applicationv1.ApplicationList, error) {
|
||||
if ctx == nil || opts == nil || len(opts.Namespace) == 0 {
|
||||
return nil, application_errors.ErrIllegalApplicationParameters{
|
||||
FieldList: []string{"ctx", "namespace"},
|
||||
Message: "cannot be empty",
|
||||
}
|
||||
}
|
||||
|
||||
list, err := client.Resource(applicationGroupVersionResource).
|
||||
Namespace(opts.Namespace).List(ctx, opts.ListOptions)
|
||||
if err != nil {
|
||||
return nil, application_errors.ErrOperateApplication{
|
||||
Action: fmt.Sprintf("List Application in namespace '%s'", opts.Namespace),
|
||||
Message: err.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
// 转换为 ApplicationList
|
||||
jsonData, err := list.MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, application_errors.ErrOperateApplication{
|
||||
Action: "verify JSON data of Application List",
|
||||
Message: err.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
applicationList := &applicationv1.ApplicationList{}
|
||||
if err := json.Unmarshal(jsonData, applicationList); err != nil {
|
||||
return nil, application_errors.ErrOperateApplication{
|
||||
Action: "deserialize Application List data",
|
||||
Message: err.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
return applicationList, nil
|
||||
}
|
||||
|
||||
// 等待 Application 就绪的辅助函数
|
||||
func waitUntilApplicationReadyWithTimeout(ctx context.Context, client dynamic_client.Interface, opts *GetApplicationOptions) (*applicationv1.ApplicationStatus, error) {
|
||||
// 监听 Application 状态变化,等待就绪
|
||||
watcherTimeoutSeconds := int64(300) // 5分钟超时
|
||||
watcher, err := client.Resource(applicationGroupVersionResource).
|
||||
Namespace(opts.Namespace).Watch(ctx, apimachinery_apis_v1.ListOptions{
|
||||
FieldSelector: fmt.Sprintf("metadata.name=%s", opts.Name),
|
||||
Watch: true,
|
||||
TimeoutSeconds: &watcherTimeoutSeconds,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, application_errors.ErrOperateApplication{
|
||||
Action: "register watcher of Application Readiness",
|
||||
Message: err.Error(),
|
||||
}
|
||||
}
|
||||
defer watcher.Stop()
|
||||
|
||||
for event := range watcher.ResultChan() {
|
||||
switch event.Type {
|
||||
case apimachinery_watch.Added, apimachinery_watch.Modified:
|
||||
if applicationUnstructured, ok := event.Object.(*apimachinery_apis_v1_unstructured.Unstructured); ok {
|
||||
application := &applicationv1.Application{}
|
||||
err = apimachinery_runtime_utils.DefaultUnstructuredConverter.
|
||||
FromUnstructured(applicationUnstructured.Object, application)
|
||||
if err == nil && IsApplicationStatusReady(&application.Status) {
|
||||
return &application.Status, nil
|
||||
}
|
||||
}
|
||||
case apimachinery_watch.Error:
|
||||
apimachineryApiMetav1Status, ok := event.Object.(*apimachinery_apis_v1.Status)
|
||||
if ok {
|
||||
return nil, application_errors.ErrOperateApplication{
|
||||
Action: fmt.Sprintf("wait for Application '%s' in namespace '%s' to be ready",
|
||||
opts.Name, opts.Namespace),
|
||||
Message: fmt.Sprintf("Watcher error: %v", apimachineryApiMetav1Status.Message),
|
||||
}
|
||||
}
|
||||
case apimachinery_watch.Deleted:
|
||||
return nil, application_errors.ErrOperateApplication{
|
||||
Action: fmt.Sprintf("wait for Application '%s' in namespace '%s'", opts.Name, opts.Namespace),
|
||||
Message: fmt.Sprintf("Application '%s' has been deleted", opts.Name),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, application_errors.ErrOperateApplication{
|
||||
Action: "wait for Application to be ready",
|
||||
Message: "timeout waiting for Application to be ready",
|
||||
}
|
||||
}
|
||||
42
modules/k8s/application/errors/errors.go
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (c) Mengning Software. 2025. All rights reserved.
|
||||
* Authors: DevStar Team, panshuxiao
|
||||
* Create: 2025-11-19
|
||||
* Description: Error helpers for the application module.
|
||||
*/
|
||||
|
||||
package application_errors
|
||||
|
||||
import "fmt"
|
||||
|
||||
// ErrIllegalApplicationParameters 非法参数错误
|
||||
type ErrIllegalApplicationParameters struct {
|
||||
FieldList []string
|
||||
Message string
|
||||
}
|
||||
|
||||
func (err ErrIllegalApplicationParameters) Error() string {
|
||||
return fmt.Sprintf("Illegal Application parameters: fields %v %s", err.FieldList, err.Message)
|
||||
}
|
||||
|
||||
// ErrOperateApplication 操作 Application 错误
|
||||
type ErrOperateApplication struct {
|
||||
Action string
|
||||
Message string
|
||||
}
|
||||
|
||||
func (err ErrOperateApplication) Error() string {
|
||||
return fmt.Sprintf("Failed to %s: %s", err.Action, err.Message)
|
||||
}
|
||||
|
||||
// ErrK8sApplicationNotReady Application 未就绪错误
|
||||
type ErrK8sApplicationNotReady struct {
|
||||
Name string
|
||||
Namespace string
|
||||
Wait bool
|
||||
}
|
||||
|
||||
func (err ErrK8sApplicationNotReady) Error() string {
|
||||
return fmt.Sprintf("Application '%s' in namespace '%s' is not ready (wait=%v)",
|
||||
err.Name, err.Namespace, err.Wait)
|
||||
}
|
||||
67
modules/k8s/application/options.go
Normal file
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright (c) Mengning Software. 2025. All rights reserved.
|
||||
* Authors: DevStar Team, panshuxiao
|
||||
* Create: 2025-11-19
|
||||
* Description: Configuration options for application installs.
|
||||
*/
|
||||
|
||||
package application
|
||||
|
||||
import (
|
||||
applicationv1 "code.gitea.io/gitea/modules/k8s/api/application/v1"
|
||||
apimachinery_apis_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// GetApplicationOptions 获取 Application 的选项
|
||||
type GetApplicationOptions struct {
|
||||
Name string
|
||||
Namespace string
|
||||
Wait bool
|
||||
GetOptions apimachinery_apis_v1.GetOptions
|
||||
}
|
||||
|
||||
// CreateApplicationOptions 创建 Application 的选项
|
||||
type CreateApplicationOptions struct {
|
||||
Name string
|
||||
Namespace string
|
||||
Component string
|
||||
Template applicationv1.ApplicationTemplate
|
||||
Replicas *int32
|
||||
Environment map[string]string
|
||||
Resources *applicationv1.ResourceRequirements
|
||||
Expose bool
|
||||
Service *applicationv1.ServiceConfig
|
||||
NetworkPolicy *applicationv1.NetworkPolicy
|
||||
TrafficPolicy *applicationv1.TrafficPolicy
|
||||
CreateOptions apimachinery_apis_v1.CreateOptions
|
||||
}
|
||||
|
||||
// UpdateApplicationOptions 更新 Application 的选项
|
||||
type UpdateApplicationOptions struct {
|
||||
Name string
|
||||
Namespace string
|
||||
Application *applicationv1.Application
|
||||
UpdateOptions apimachinery_apis_v1.UpdateOptions
|
||||
}
|
||||
|
||||
// DeleteApplicationOptions 删除 Application 的选项
|
||||
type DeleteApplicationOptions struct {
|
||||
Name string
|
||||
Namespace string
|
||||
DeleteOptions apimachinery_apis_v1.DeleteOptions
|
||||
}
|
||||
|
||||
// ListApplicationsOptions 列出 Applications 的选项
|
||||
type ListApplicationsOptions struct {
|
||||
Namespace string
|
||||
ListOptions apimachinery_apis_v1.ListOptions
|
||||
}
|
||||
|
||||
// ApplicationStatusVO Application 状态 VO
|
||||
type ApplicationStatusVO struct {
|
||||
Phase string
|
||||
Message string
|
||||
Replicas int32
|
||||
ReadyReplicas int32
|
||||
LastUpdated apimachinery_apis_v1.Time
|
||||
}
|
||||
@@ -1,3 +1,10 @@
|
||||
/*
|
||||
* Copyright (c) Mengning Software. 2025. All rights reserved.
|
||||
* Authors: DevStar Team, panshuxiao
|
||||
* Create: 2025-11-19
|
||||
* Description: Controller manager CLI option wiring.
|
||||
*/
|
||||
|
||||
package options
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
/*
|
||||
* Copyright (c) Mengning Software. 2025. All rights reserved.
|
||||
* Authors: DevStar Team, panshuxiao
|
||||
* Create: 2025-11-19
|
||||
* Description: Controller manager metrics/server bootstrap.
|
||||
*/
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
/*
|
||||
* Copyright (c) Mengning Software. 2025. All rights reserved.
|
||||
* Authors: DevStar Team, panshuxiao
|
||||
* Create: 2025-11-19
|
||||
* Description: Controller manager main entry point.
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
/*
|
||||
* Copyright (c) Mengning Software. 2025. All rights reserved.
|
||||
* Authors: DevStar Team, panshuxiao
|
||||
* Create: 2025-11-19
|
||||
* Description: Reconciliation logic for Application CRDs.
|
||||
*/
|
||||
|
||||
package application
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
@@ -38,6 +46,8 @@ type ApplicationReconciler struct {
|
||||
// +kubebuilder:rbac:groups=apps,resources=statefulsets,verbs=create;delete;get;list;watch;update;patch
|
||||
// +kubebuilder:rbac:groups="",resources=services,verbs=create;delete;get;list;watch;update;patch
|
||||
// +kubebuilder:rbac:groups=networking.k8s.io,resources=ingresses,verbs=create;delete;get;list;watch;update;patch
|
||||
// +kubebuilder:rbac:groups="",resources=secrets,verbs=get;list;watch;create;update;patch
|
||||
// +kubebuilder:rbac:groups="",resources=namespaces,verbs=get;list;watch;create
|
||||
|
||||
// Reconcile is part of the main kubernetes reconciliation loop
|
||||
func (r *ApplicationReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
|
||||
@@ -57,6 +67,12 @@ func (r *ApplicationReconciler) Reconcile(ctx context.Context, req ctrl.Request)
|
||||
|
||||
logger.Info("Processing Application", "name", app.Name, "namespace", app.Namespace, "type", app.Spec.Template.Type)
|
||||
|
||||
// 确保命名空间存在
|
||||
if err := r.ensureNamespace(ctx, app.Namespace); err != nil {
|
||||
logger.Error(err, "Failed to ensure namespace exists", "namespace", app.Namespace)
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
// 添加 finalizer 处理逻辑
|
||||
finalizerName := "application.devstar.cn/finalizer"
|
||||
|
||||
@@ -67,6 +83,18 @@ func (r *ApplicationReconciler) Reconcile(ctx context.Context, req ctrl.Request)
|
||||
// 执行清理操作
|
||||
logger.Info("Cleaning up resources before deletion", "name", app.Name)
|
||||
|
||||
// 清理 Gateway 资源(包括 Secret)
|
||||
if err := r.cleanupGateway(ctx, app); err != nil {
|
||||
logger.Error(err, "Failed to cleanup gateway resources")
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
// 清理 Mesh 资源
|
||||
if err := r.cleanupMesh(ctx, app); err != nil {
|
||||
logger.Error(err, "Failed to cleanup mesh resources")
|
||||
return ctrl.Result{}, err
|
||||
}
|
||||
|
||||
// 清理完成后移除 finalizer
|
||||
k8s_sigs_controller_runtime_utils.RemoveFinalizer(app, finalizerName)
|
||||
if err := r.Update(ctx, app); err != nil {
|
||||
@@ -739,21 +767,29 @@ func (r *ApplicationReconciler) cleanupGateway(ctx context.Context, app *applica
|
||||
gatewayName := app.Name + "-gateway"
|
||||
vsName := app.Name + "-gateway-vs"
|
||||
|
||||
// 清理Gateway
|
||||
gateway := &istionetworkingv1.Gateway{}
|
||||
err := r.Get(ctx, types.NamespacedName{Name: gatewayName, Namespace: app.Namespace}, gateway)
|
||||
if err == nil {
|
||||
logger.Info("Cleaning up Gateway that is no longer needed", "name", gatewayName)
|
||||
if err := r.Delete(ctx, gateway); err != nil && !errors.IsNotFound(err) {
|
||||
return fmt.Errorf("failed to delete Gateway: %w", err)
|
||||
targetNamespaces := map[string]struct{}{app.Namespace: {}}
|
||||
if ns, err := r.determineGatewayNamespace(ctx, app); err == nil {
|
||||
targetNamespaces[ns] = struct{}{}
|
||||
} else {
|
||||
logger.Error(err, "Failed to determine gateway namespace during cleanup, fallback to application namespace")
|
||||
}
|
||||
|
||||
for ns := range targetNamespaces {
|
||||
gateway := &istionetworkingv1.Gateway{}
|
||||
err := r.Get(ctx, types.NamespacedName{Name: gatewayName, Namespace: ns}, gateway)
|
||||
if err == nil {
|
||||
logger.Info("Cleaning up Gateway that is no longer needed", "name", gatewayName, "namespace", ns)
|
||||
if err := r.Delete(ctx, gateway); err != nil && !errors.IsNotFound(err) {
|
||||
return fmt.Errorf("failed to delete Gateway %s/%s: %w", ns, gatewayName, err)
|
||||
}
|
||||
} else if !errors.IsNotFound(err) {
|
||||
return fmt.Errorf("failed to get Gateway %s/%s: %w", ns, gatewayName, err)
|
||||
}
|
||||
} else if !errors.IsNotFound(err) {
|
||||
return fmt.Errorf("failed to get Gateway: %w", err)
|
||||
}
|
||||
|
||||
// 清理VirtualService
|
||||
vs := &istionetworkingv1.VirtualService{}
|
||||
err = r.Get(ctx, types.NamespacedName{Name: vsName, Namespace: app.Namespace}, vs)
|
||||
err := r.Get(ctx, types.NamespacedName{Name: vsName, Namespace: app.Namespace}, vs)
|
||||
if err == nil {
|
||||
logger.Info("Cleaning up Gateway VirtualService that is no longer needed", "name", vsName)
|
||||
if err := r.Delete(ctx, vs); err != nil && !errors.IsNotFound(err) {
|
||||
@@ -763,6 +799,43 @@ func (r *ApplicationReconciler) cleanupGateway(ctx context.Context, app *applica
|
||||
return fmt.Errorf("failed to get Gateway VirtualService: %w", err)
|
||||
}
|
||||
|
||||
// 清理 TLS Secret(如果是由控制器创建的)
|
||||
if app.Spec.NetworkPolicy != nil && app.Spec.NetworkPolicy.Gateway != nil {
|
||||
for _, tls := range app.Spec.NetworkPolicy.Gateway.TLS {
|
||||
if strings.EqualFold(tls.Mode, "PASSTHROUGH") {
|
||||
continue
|
||||
}
|
||||
if tls.SecretName == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
// 确定 Secret 的命名空间
|
||||
secretNS, err := r.detectIngressNamespace(ctx, tls.SecretNamespace)
|
||||
if err != nil {
|
||||
logger.Error(err, "Failed to determine secret namespace for cleanup, skipping", "secretName", tls.SecretName)
|
||||
continue
|
||||
}
|
||||
|
||||
// 检查 Secret 是否存在,并且有我们的标签(说明是我们创建的)
|
||||
secret := &core_v1.Secret{}
|
||||
err = r.Get(ctx, types.NamespacedName{Name: tls.SecretName, Namespace: secretNS}, secret)
|
||||
if err == nil {
|
||||
// 检查标签,确认是我们创建的
|
||||
if secret.Labels != nil &&
|
||||
secret.Labels["app.k8s.devstar/name"] == app.Name &&
|
||||
secret.Labels["app.k8s.devstar/type"] == "gateway-tls" {
|
||||
logger.Info("Cleaning up Gateway TLS Secret", "name", tls.SecretName, "namespace", secretNS)
|
||||
if err := r.Delete(ctx, secret); err != nil && !errors.IsNotFound(err) {
|
||||
logger.Error(err, "Failed to delete Gateway TLS Secret", "name", tls.SecretName, "namespace", secretNS)
|
||||
// 不返回错误,继续清理其他资源
|
||||
}
|
||||
}
|
||||
} else if !errors.IsNotFound(err) {
|
||||
logger.Error(err, "Failed to get Gateway TLS Secret for cleanup", "name", tls.SecretName, "namespace", secretNS)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -799,6 +872,44 @@ func (r *ApplicationReconciler) cleanupMesh(ctx context.Context, app *applicatio
|
||||
return nil
|
||||
}
|
||||
|
||||
// ensureNamespace 确保命名空间存在,如果不存在则创建
|
||||
func (r *ApplicationReconciler) ensureNamespace(ctx context.Context, namespace string) error {
|
||||
logger := log.FromContext(ctx)
|
||||
|
||||
// 跳过默认命名空间(这些命名空间通常由系统管理)
|
||||
if namespace == "default" || namespace == "kube-system" || namespace == "kube-public" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 检查命名空间是否存在
|
||||
ns := &core_v1.Namespace{}
|
||||
err := r.Get(ctx, types.NamespacedName{Name: namespace}, ns)
|
||||
if err == nil {
|
||||
// 命名空间已存在
|
||||
return nil
|
||||
}
|
||||
|
||||
if !errors.IsNotFound(err) {
|
||||
// 获取命名空间时发生其他错误
|
||||
return fmt.Errorf("failed to get namespace %s: %w", namespace, err)
|
||||
}
|
||||
|
||||
// 命名空间不存在,创建它
|
||||
logger.Info("Creating namespace", "namespace", namespace)
|
||||
newNS := &core_v1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: namespace,
|
||||
},
|
||||
}
|
||||
|
||||
if err := r.Create(ctx, newNS); err != nil {
|
||||
return fmt.Errorf("failed to create namespace %s: %w", namespace, err)
|
||||
}
|
||||
|
||||
logger.Info("Successfully created namespace", "namespace", namespace)
|
||||
return nil
|
||||
}
|
||||
|
||||
// 为了在SetupWithManager中注册Istio资源监控
|
||||
func (r *ApplicationReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
return ctrl.NewControllerManagedBy(mgr).
|
||||
@@ -807,9 +918,9 @@ func (r *ApplicationReconciler) SetupWithManager(mgr ctrl.Manager) error {
|
||||
Owns(&apps_v1.StatefulSet{}).
|
||||
Owns(&core_v1.Service{}).
|
||||
// 添加对Istio资源的监控
|
||||
// Owns(&istionetworkingv1.VirtualService{}).
|
||||
// Owns(&istionetworkingv1.Gateway{}).
|
||||
// Owns(&istionetworkingv1.DestinationRule{}).
|
||||
Owns(&istionetworkingv1.VirtualService{}).
|
||||
Owns(&istionetworkingv1.Gateway{}).
|
||||
Owns(&istionetworkingv1.DestinationRule{}).
|
||||
Complete(r)
|
||||
}
|
||||
|
||||
@@ -1028,36 +1139,68 @@ func (r *ApplicationReconciler) reconcileGateway(ctx context.Context, app *appli
|
||||
logger := log.FromContext(ctx)
|
||||
gatewayName := app.Name + "-gateway"
|
||||
|
||||
// 在创建/更新 Gateway 前,确保 TLS Secret
|
||||
if err := r.reconcileGatewayTLSSecret(ctx, app); err != nil {
|
||||
return fmt.Errorf("failed to reconcile gateway TLS secret: %w", err)
|
||||
}
|
||||
|
||||
gatewayNamespace, err := r.determineGatewayNamespace(ctx, app)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to determine gateway namespace: %w", err)
|
||||
}
|
||||
|
||||
// 删除遗留在应用命名空间的 Gateway(兼容旧版本)
|
||||
if gatewayNamespace != app.Namespace {
|
||||
legacyGateway := &istionetworkingv1.Gateway{}
|
||||
legacyKey := types.NamespacedName{Name: gatewayName, Namespace: app.Namespace}
|
||||
if legacyErr := r.Get(ctx, legacyKey, legacyGateway); legacyErr == nil {
|
||||
logger.Info("Deleting legacy Gateway from application namespace", "name", gatewayName)
|
||||
if err := r.Delete(ctx, legacyGateway); err != nil && !errors.IsNotFound(err) {
|
||||
return fmt.Errorf("failed to delete legacy gateway: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get + Create/Update 以规避 CreateOrUpdate 对 protobuf 类型的反射补丁
|
||||
existingGateway := &istionetworkingv1.Gateway{}
|
||||
err := r.Get(ctx, types.NamespacedName{Name: gatewayName, Namespace: app.Namespace}, existingGateway)
|
||||
gatewayKey := types.NamespacedName{Name: gatewayName, Namespace: gatewayNamespace}
|
||||
err = r.Get(ctx, gatewayKey, existingGateway)
|
||||
if errors.IsNotFound(err) {
|
||||
logger.Info("Creating new Gateway", "name", gatewayName)
|
||||
newGateway := &istionetworkingv1.Gateway{ObjectMeta: metav1.ObjectMeta{Name: gatewayName, Namespace: app.Namespace}}
|
||||
if err := r.configureGateway(newGateway, app); err != nil {
|
||||
newGateway := &istionetworkingv1.Gateway{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: gatewayName,
|
||||
Namespace: gatewayNamespace,
|
||||
},
|
||||
}
|
||||
ensureGatewayLabels(newGateway, app)
|
||||
if err := r.configureGateway(ctx, newGateway, app); err != nil {
|
||||
return fmt.Errorf("failed to configure Gateway: %w", err)
|
||||
}
|
||||
if err := k8s_sigs_controller_runtime_utils.SetControllerReference(app, newGateway, r.Scheme); err != nil {
|
||||
return fmt.Errorf("failed to set controller reference: %w", err)
|
||||
if gatewayNamespace == app.Namespace {
|
||||
if err := k8s_sigs_controller_runtime_utils.SetControllerReference(app, newGateway, r.Scheme); err != nil {
|
||||
return fmt.Errorf("failed to set controller reference: %w", err)
|
||||
}
|
||||
}
|
||||
if err := r.Create(ctx, newGateway); err != nil {
|
||||
return fmt.Errorf("failed to create Gateway: %w", err)
|
||||
}
|
||||
logger.Info("Gateway created", "name", gatewayName)
|
||||
logger.Info("Gateway created", "name", gatewayName, "namespace", gatewayNamespace)
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("failed to get Gateway: %w", err)
|
||||
} else {
|
||||
if err := r.configureGateway(existingGateway, app); err != nil {
|
||||
ensureGatewayLabels(existingGateway, app)
|
||||
if err := r.configureGateway(ctx, existingGateway, app); err != nil {
|
||||
return fmt.Errorf("failed to configure Gateway: %w", err)
|
||||
}
|
||||
if err := r.Update(ctx, existingGateway); err != nil {
|
||||
return fmt.Errorf("failed to update Gateway: %w", err)
|
||||
}
|
||||
logger.Info("Gateway updated", "name", gatewayName)
|
||||
logger.Info("Gateway updated", "name", gatewayName, "namespace", gatewayNamespace)
|
||||
}
|
||||
|
||||
// 协调与Gateway关联的VirtualService
|
||||
if err := r.reconcileGatewayVirtualService(ctx, app); err != nil {
|
||||
if err := r.reconcileGatewayVirtualService(ctx, app, gatewayNamespace); err != nil {
|
||||
return fmt.Errorf("failed to reconcile gateway VirtualService: %w", err)
|
||||
}
|
||||
|
||||
@@ -1065,7 +1208,7 @@ func (r *ApplicationReconciler) reconcileGateway(ctx context.Context, app *appli
|
||||
}
|
||||
|
||||
// configureGateway 配置Gateway资源
|
||||
func (r *ApplicationReconciler) configureGateway(gateway *istionetworkingv1.Gateway, app *applicationv1.Application) error {
|
||||
func (r *ApplicationReconciler) configureGateway(ctx context.Context, gateway *istionetworkingv1.Gateway, app *applicationv1.Application) error {
|
||||
// 设置Gateway选择器
|
||||
gateway.Spec.Selector = map[string]string{
|
||||
"istio": "ingressgateway",
|
||||
@@ -1126,9 +1269,22 @@ func (r *ApplicationReconciler) configureGateway(gateway *istionetworkingv1.Gate
|
||||
|
||||
// 配置TLS设置
|
||||
server := gateway.Spec.Servers[serverIndex]
|
||||
|
||||
// 确定 Secret 的命名空间
|
||||
secretNS, err := r.detectIngressNamespace(ctx, tls.SecretNamespace)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to determine secret namespace for TLS: %w", err)
|
||||
}
|
||||
|
||||
// 如果 Secret 不在 Gateway 所在命名空间,使用 namespace/secretName 格式
|
||||
credentialName := tls.SecretName
|
||||
if secretNS != gateway.Namespace {
|
||||
credentialName = secretNS + "/" + tls.SecretName
|
||||
}
|
||||
|
||||
server.Tls = &istioapinetworkingv1.ServerTLSSettings{
|
||||
Mode: getIstioTLSMode(tls.Mode),
|
||||
CredentialName: tls.SecretName,
|
||||
CredentialName: credentialName,
|
||||
}
|
||||
|
||||
// 设置最小TLS版本
|
||||
@@ -1183,10 +1339,391 @@ func getIstioTLSVersion(version string) istioapinetworkingv1.ServerTLSSettings_T
|
||||
}
|
||||
}
|
||||
|
||||
// detectIngressNamespace 自动探测 IngressGateway 所在命名空间
|
||||
// 优先级:CRD 指定 -> 根据 selector {istio=ingressgateway} 查找 Service
|
||||
func (r *ApplicationReconciler) detectIngressNamespace(ctx context.Context, explicit string) (string, error) {
|
||||
if explicit != "" {
|
||||
return explicit, nil
|
||||
}
|
||||
|
||||
// 优先找名为 istio-ingressgateway 的 Service
|
||||
svcList := &core_v1.ServiceList{}
|
||||
if err := r.List(ctx, svcList, client.MatchingLabels{"istio": "ingressgateway"}); err != nil {
|
||||
return "", fmt.Errorf("list ingressgateway services failed: %w", err)
|
||||
}
|
||||
var fallback string
|
||||
for _, svc := range svcList.Items {
|
||||
if svc.Name == "istio-ingressgateway" {
|
||||
return svc.Namespace, nil
|
||||
}
|
||||
if fallback == "" {
|
||||
fallback = svc.Namespace
|
||||
}
|
||||
}
|
||||
if fallback != "" {
|
||||
return fallback, nil
|
||||
}
|
||||
return "", fmt.Errorf("cannot detect ingress gateway namespace; set tls.secretNamespace or ISTIO_INGRESS_NAMESPACE")
|
||||
}
|
||||
|
||||
// determineGatewayNamespace 决定 Gateway 应创建到的命名空间
|
||||
func (r *ApplicationReconciler) determineGatewayNamespace(ctx context.Context, app *applicationv1.Application) (string, error) {
|
||||
var explicit string
|
||||
if app.Spec.NetworkPolicy != nil && app.Spec.NetworkPolicy.Gateway != nil {
|
||||
for _, tls := range app.Spec.NetworkPolicy.Gateway.TLS {
|
||||
if tls.SecretNamespace != "" {
|
||||
explicit = tls.SecretNamespace
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return r.detectIngressNamespace(ctx, explicit)
|
||||
}
|
||||
|
||||
func ensureGatewayLabels(gateway *istionetworkingv1.Gateway, app *applicationv1.Application) {
|
||||
if gateway.Labels == nil {
|
||||
gateway.Labels = make(map[string]string)
|
||||
}
|
||||
gateway.Labels["app.k8s.devstar/name"] = app.Name
|
||||
gateway.Labels["app.k8s.devstar/namespace"] = app.Namespace
|
||||
}
|
||||
|
||||
// reconcileGatewayTLSSecret 确保 Gateway TLS 所需的 Secret 存在/最新
|
||||
func (r *ApplicationReconciler) reconcileGatewayTLSSecret(ctx context.Context, app *applicationv1.Application) error {
|
||||
if app.Spec.NetworkPolicy == nil || app.Spec.NetworkPolicy.Gateway == nil {
|
||||
return nil
|
||||
}
|
||||
tlsList := app.Spec.NetworkPolicy.Gateway.TLS
|
||||
if len(tlsList) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, tls := range tlsList {
|
||||
if strings.EqualFold(tls.Mode, "PASSTHROUGH") {
|
||||
continue
|
||||
}
|
||||
|
||||
secretName := tls.SecretName
|
||||
if secretName == "" {
|
||||
return fmt.Errorf("gateway.tls.secretName is required when TLS mode is %s", tls.Mode)
|
||||
}
|
||||
|
||||
// 自动确定目标命名空间
|
||||
targetNS, err := r.detectIngressNamespace(ctx, tls.SecretNamespace)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hasInline := tls.Certificate != "" && tls.PrivateKey != ""
|
||||
|
||||
existing := &core_v1.Secret{}
|
||||
getErr := r.Get(ctx, types.NamespacedName{Namespace: targetNS, Name: secretName}, existing)
|
||||
if errors.IsNotFound(getErr) {
|
||||
if !hasInline {
|
||||
return fmt.Errorf("secret %s/%s not found; provide certificate/privateKey or create it manually", targetNS, secretName)
|
||||
}
|
||||
|
||||
// 规范化证书和私钥格式(确保是有效的 PEM 格式)
|
||||
certPEM := normalizePEM(tls.Certificate, "CERTIFICATE")
|
||||
keyPEM := normalizePEM(tls.PrivateKey, "PRIVATE KEY")
|
||||
|
||||
newSec := &core_v1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: secretName,
|
||||
Namespace: targetNS,
|
||||
Labels: map[string]string{
|
||||
"app.k8s.devstar/name": app.Name,
|
||||
"app.k8s.devstar/type": "gateway-tls",
|
||||
},
|
||||
},
|
||||
Type: core_v1.SecretTypeTLS,
|
||||
Data: map[string][]byte{
|
||||
core_v1.TLSCertKey: []byte(certPEM),
|
||||
core_v1.TLSPrivateKeyKey: []byte(keyPEM),
|
||||
},
|
||||
}
|
||||
if err := r.Create(ctx, newSec); err != nil {
|
||||
return fmt.Errorf("create tls secret %s/%s failed: %w", targetNS, secretName, err)
|
||||
}
|
||||
continue
|
||||
} else if getErr != nil {
|
||||
return fmt.Errorf("get secret %s/%s failed: %w", targetNS, secretName, getErr)
|
||||
}
|
||||
|
||||
if hasInline {
|
||||
// 规范化证书和私钥格式
|
||||
certPEM := normalizePEM(tls.Certificate, "CERTIFICATE")
|
||||
keyPEM := normalizePEM(tls.PrivateKey, "PRIVATE KEY")
|
||||
|
||||
if existing.Type != core_v1.SecretTypeTLS ||
|
||||
!bytes.Equal(existing.Data[core_v1.TLSCertKey], []byte(certPEM)) ||
|
||||
!bytes.Equal(existing.Data[core_v1.TLSPrivateKeyKey], []byte(keyPEM)) {
|
||||
if existing.Data == nil {
|
||||
existing.Data = map[string][]byte{}
|
||||
}
|
||||
existing.Type = core_v1.SecretTypeTLS
|
||||
existing.Data[core_v1.TLSCertKey] = []byte(certPEM)
|
||||
existing.Data[core_v1.TLSPrivateKeyKey] = []byte(keyPEM)
|
||||
if existing.Labels == nil {
|
||||
existing.Labels = map[string]string{}
|
||||
}
|
||||
existing.Labels["app.k8s.devstar/name"] = app.Name
|
||||
existing.Labels["app.k8s.devstar/type"] = "gateway-tls"
|
||||
if err := r.Update(ctx, existing); err != nil {
|
||||
return fmt.Errorf("update tls secret %s/%s failed: %w", targetNS, secretName, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// detectPrivateKeyType 检测私钥的原始格式类型
|
||||
// 返回 "RSA PRIVATE KEY" 或 "PRIVATE KEY"
|
||||
func detectPrivateKeyType(content string) string {
|
||||
contentUpper := strings.ToUpper(content)
|
||||
if strings.Contains(contentUpper, "-----BEGIN RSA PRIVATE KEY-----") {
|
||||
return "RSA PRIVATE KEY"
|
||||
}
|
||||
if strings.Contains(contentUpper, "-----BEGIN PRIVATE KEY-----") {
|
||||
return "PRIVATE KEY"
|
||||
}
|
||||
if strings.Contains(contentUpper, "-----BEGIN EC PRIVATE KEY-----") {
|
||||
return "EC PRIVATE KEY"
|
||||
}
|
||||
// 默认返回 PRIVATE KEY
|
||||
return "PRIVATE KEY"
|
||||
}
|
||||
|
||||
// normalizePEM 规范化 PEM 格式的证书或私钥
|
||||
// 支持两种输入格式:
|
||||
// 1. 只包含 base64 内容(没有 BEGIN/END):自动添加标记并格式化
|
||||
// 2. 完整 PEM 格式(包含 BEGIN/END):提取内容并重新格式化为标准格式
|
||||
// 对于私钥,如果 pemType 是 "PRIVATE KEY",会自动检测原始格式(RSA PRIVATE KEY 或 PRIVATE KEY)并保持
|
||||
func normalizePEM(content, pemType string) string {
|
||||
content = strings.TrimSpace(content)
|
||||
if content == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
// 对于私钥,如果指定为 "PRIVATE KEY",自动检测原始格式
|
||||
if pemType == "PRIVATE KEY" && strings.Contains(strings.ToUpper(content), "-----BEGIN") {
|
||||
detectedType := detectPrivateKeyType(content)
|
||||
if detectedType != "PRIVATE KEY" {
|
||||
// 使用检测到的原始格式
|
||||
pemType = detectedType
|
||||
}
|
||||
}
|
||||
|
||||
// 如果已经包含 BEGIN 标记,需要提取 BEGIN 和 END 之间的内容
|
||||
if strings.Contains(content, "-----BEGIN") {
|
||||
// 特别处理证书链:保留所有 CERTIFICATE 分段
|
||||
if pemType == "CERTIFICATE" {
|
||||
var blocks []string
|
||||
search := content
|
||||
for {
|
||||
beginIdx := strings.Index(search, "-----BEGIN CERTIFICATE-----")
|
||||
if beginIdx == -1 {
|
||||
break
|
||||
}
|
||||
searchFrom := search[beginIdx+len("-----BEGIN CERTIFICATE-----"):]
|
||||
endMarker := "-----END CERTIFICATE-----"
|
||||
endIdx := strings.Index(searchFrom, endMarker)
|
||||
if endIdx == -1 {
|
||||
// 没有匹配的 END,使用 BEGIN 之后的所有内容作为最后一段
|
||||
body := searchFrom
|
||||
blocks = append(blocks, body)
|
||||
break
|
||||
}
|
||||
body := searchFrom[:endIdx]
|
||||
blocks = append(blocks, body)
|
||||
// 继续在 END 之后搜索下一段
|
||||
nextStart := endIdx + len(endMarker)
|
||||
if nextStart >= len(searchFrom) {
|
||||
break
|
||||
}
|
||||
search = searchFrom[nextStart:]
|
||||
}
|
||||
|
||||
if len(blocks) > 0 {
|
||||
return buildPEMChainFromContent(blocks, "CERTIFICATE")
|
||||
}
|
||||
// 如果没有成功解析到分段,退化为单段处理
|
||||
}
|
||||
|
||||
// 非证书链,按单段处理
|
||||
// 对于私钥,需要匹配对应的 BEGIN/END 标记
|
||||
if pemType == "RSA PRIVATE KEY" || pemType == "EC PRIVATE KEY" || pemType == "PRIVATE KEY" {
|
||||
// 查找对应的 BEGIN 标记
|
||||
beginMarker := fmt.Sprintf("-----BEGIN %s-----", pemType)
|
||||
beginIdx := strings.Index(strings.ToUpper(content), strings.ToUpper(beginMarker))
|
||||
if beginIdx == -1 {
|
||||
// 如果找不到精确匹配,尝试查找任何 BEGIN 标记
|
||||
beginIdx = strings.Index(strings.ToUpper(content), "-----BEGIN")
|
||||
}
|
||||
if beginIdx == -1 {
|
||||
return buildPEMSingleFromContent(content, pemType)
|
||||
}
|
||||
|
||||
// 计算 BEGIN 标记行的结束位置
|
||||
// 先尝试查找换行符
|
||||
beginLineEnd := strings.Index(content[beginIdx:], "\n")
|
||||
if beginLineEnd == -1 {
|
||||
beginLineEnd = strings.Index(content[beginIdx:], "\r")
|
||||
}
|
||||
if beginLineEnd == -1 {
|
||||
// 如果没有换行符,直接使用 BEGIN 标记的长度
|
||||
// 这样可以避免在单行格式中错误地匹配到 END 标记
|
||||
beginLineEnd = len(beginMarker)
|
||||
} else {
|
||||
// 找到了换行符,beginLineEnd 是相对于 beginIdx 的偏移
|
||||
beginLineEnd += 1 // 包含换行符
|
||||
}
|
||||
|
||||
// 查找对应的 END 标记
|
||||
endMarker := fmt.Sprintf("-----END %s-----", pemType)
|
||||
searchStart := beginIdx + beginLineEnd
|
||||
if searchStart > len(content) {
|
||||
searchStart = len(content)
|
||||
}
|
||||
endIdx := strings.Index(strings.ToUpper(content[searchStart:]), strings.ToUpper(endMarker))
|
||||
if endIdx == -1 {
|
||||
// 如果找不到精确匹配,尝试查找任何 END 标记
|
||||
endIdx = strings.Index(strings.ToUpper(content[searchStart:]), "-----END")
|
||||
}
|
||||
if endIdx == -1 {
|
||||
// 如果找不到 END,提取 BEGIN 之后的所有内容
|
||||
bodyContent := content[beginIdx+beginLineEnd:]
|
||||
return buildPEMSingleFromContent(bodyContent, pemType)
|
||||
}
|
||||
|
||||
// 提取 BEGIN 行结束和 END 标记开始之间的内容
|
||||
bodyContent := content[beginIdx+beginLineEnd : searchStart+endIdx]
|
||||
// 移除可能包含的 END 标记前缀(防止单行格式时误包含)
|
||||
bodyContent = strings.TrimSpace(bodyContent)
|
||||
if strings.HasPrefix(strings.ToUpper(bodyContent), "-----END") {
|
||||
// 如果 bodyContent 以 END 标记开头,说明提取错误,需要重新提取
|
||||
endMarkerStart := strings.Index(strings.ToUpper(bodyContent), strings.ToUpper(endMarker))
|
||||
if endMarkerStart != -1 {
|
||||
bodyContent = bodyContent[:endMarkerStart]
|
||||
} else {
|
||||
// 如果找不到完整的 END 标记,尝试查找 "-----END" 的位置
|
||||
endDashIdx := strings.Index(strings.ToUpper(bodyContent), "-----END")
|
||||
if endDashIdx != -1 {
|
||||
bodyContent = bodyContent[:endDashIdx]
|
||||
}
|
||||
}
|
||||
}
|
||||
return buildPEMSingleFromContent(bodyContent, pemType)
|
||||
}
|
||||
|
||||
// 其他类型(证书等)的原有逻辑
|
||||
// 查找 BEGIN 标记的结束位置(包含完整标记行)
|
||||
beginIdx := strings.Index(content, "-----BEGIN")
|
||||
if beginIdx == -1 {
|
||||
return buildPEMSingleFromContent(content, pemType)
|
||||
}
|
||||
|
||||
// 找到 BEGIN 行结束(下一个换行符,或字符串结束)
|
||||
beginLineEnd := strings.Index(content[beginIdx:], "\n")
|
||||
if beginLineEnd == -1 {
|
||||
beginLineEnd = strings.Index(content[beginIdx:], "\r")
|
||||
}
|
||||
if beginLineEnd == -1 {
|
||||
// 如果没有换行,查找下一个 "-----" 作为结束
|
||||
nextDash := strings.Index(content[beginIdx+10:], "-----")
|
||||
if nextDash != -1 {
|
||||
beginLineEnd = beginIdx + 10 + nextDash + 5
|
||||
} else {
|
||||
beginLineEnd = len(content)
|
||||
}
|
||||
} else {
|
||||
beginLineEnd += beginIdx + 1
|
||||
}
|
||||
|
||||
// 查找 END 标记
|
||||
endPattern := "-----END"
|
||||
searchStart := beginLineEnd
|
||||
if searchStart > len(content) {
|
||||
searchStart = len(content)
|
||||
}
|
||||
endIdx := strings.Index(content[searchStart:], endPattern)
|
||||
if endIdx == -1 {
|
||||
// 如果找不到 END,提取 BEGIN 之后的所有内容
|
||||
bodyContent := content[beginLineEnd:]
|
||||
return buildPEMSingleFromContent(bodyContent, pemType)
|
||||
}
|
||||
|
||||
// 提取 BEGIN 行结束和 END 标记开始之间的内容
|
||||
bodyContent := content[beginLineEnd : searchStart+endIdx]
|
||||
|
||||
// 清理并重新格式化
|
||||
return buildPEMSingleFromContent(bodyContent, pemType)
|
||||
}
|
||||
|
||||
// 如果没有 BEGIN 标记,直接格式化
|
||||
return buildPEMSingleFromContent(content, pemType)
|
||||
}
|
||||
|
||||
// buildPEMSingleFromContent 从清理后的内容构建单段标准 PEM 格式
|
||||
// 只移除空白字符,保留所有实际的 base64 内容
|
||||
func buildPEMSingleFromContent(content, pemType string) string {
|
||||
// 移除所有空格、换行符、制表符等空白字符
|
||||
cleaned := strings.ReplaceAll(content, " ", "")
|
||||
cleaned = strings.ReplaceAll(cleaned, "\n", "")
|
||||
cleaned = strings.ReplaceAll(cleaned, "\r", "")
|
||||
cleaned = strings.ReplaceAll(cleaned, "\t", "")
|
||||
cleaned = strings.TrimSpace(cleaned)
|
||||
|
||||
if cleaned == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
// 构建标准 PEM 格式
|
||||
var pem strings.Builder
|
||||
pem.WriteString("-----BEGIN ")
|
||||
pem.WriteString(pemType)
|
||||
pem.WriteString("-----\n")
|
||||
|
||||
// 每 64 字符一行
|
||||
for i := 0; i < len(cleaned); i += 64 {
|
||||
end := i + 64
|
||||
if end > len(cleaned) {
|
||||
end = len(cleaned)
|
||||
}
|
||||
pem.WriteString(cleaned[i:end])
|
||||
pem.WriteString("\n")
|
||||
}
|
||||
|
||||
pem.WriteString("-----END ")
|
||||
pem.WriteString(pemType)
|
||||
pem.WriteString("-----\n")
|
||||
|
||||
return pem.String()
|
||||
}
|
||||
|
||||
// buildPEMChainFromContent 根据多段内容构建包含多段的 PEM 证书链
|
||||
func buildPEMChainFromContent(blockBodies []string, pemType string) string {
|
||||
var b strings.Builder
|
||||
for _, body := range blockBodies {
|
||||
seg := buildPEMSingleFromContent(body, pemType)
|
||||
if seg == "" {
|
||||
continue
|
||||
}
|
||||
b.WriteString(seg)
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// reconcileGatewayVirtualService 处理与Gateway关联的VirtualService
|
||||
func (r *ApplicationReconciler) reconcileGatewayVirtualService(ctx context.Context, app *applicationv1.Application) error {
|
||||
func (r *ApplicationReconciler) reconcileGatewayVirtualService(ctx context.Context, app *applicationv1.Application, gatewayNamespace string) error {
|
||||
logger := log.FromContext(ctx)
|
||||
vsName := app.Name + "-gateway-vs"
|
||||
gatewayName := app.Name + "-gateway"
|
||||
gatewayRef := gatewayName
|
||||
if gatewayNamespace != "" && gatewayNamespace != app.Namespace {
|
||||
gatewayRef = fmt.Sprintf("%s/%s", gatewayNamespace, gatewayName)
|
||||
}
|
||||
|
||||
// 检查服务是否存在
|
||||
serviceName := app.Name + "-svc"
|
||||
@@ -1205,7 +1742,7 @@ func (r *ApplicationReconciler) reconcileGatewayVirtualService(ctx context.Conte
|
||||
if errors.IsNotFound(err) {
|
||||
logger.Info("Creating new Gateway VirtualService", "name", vsName)
|
||||
newVS := &istionetworkingv1.VirtualService{ObjectMeta: metav1.ObjectMeta{Name: vsName, Namespace: app.Namespace}}
|
||||
if err := r.configureGatewayVirtualService(newVS, app, service); err != nil {
|
||||
if err := r.configureGatewayVirtualService(newVS, app, service, gatewayRef); err != nil {
|
||||
return fmt.Errorf("failed to configure VirtualService: %w", err)
|
||||
}
|
||||
if err := k8s_sigs_controller_runtime_utils.SetControllerReference(app, newVS, r.Scheme); err != nil {
|
||||
@@ -1218,7 +1755,7 @@ func (r *ApplicationReconciler) reconcileGatewayVirtualService(ctx context.Conte
|
||||
} else if err != nil {
|
||||
return fmt.Errorf("failed to get VirtualService: %w", err)
|
||||
} else {
|
||||
if err := r.configureGatewayVirtualService(existingVS, app, service); err != nil {
|
||||
if err := r.configureGatewayVirtualService(existingVS, app, service, gatewayRef); err != nil {
|
||||
return fmt.Errorf("failed to configure VirtualService: %w", err)
|
||||
}
|
||||
if err := r.Update(ctx, existingVS); err != nil {
|
||||
@@ -1231,10 +1768,10 @@ func (r *ApplicationReconciler) reconcileGatewayVirtualService(ctx context.Conte
|
||||
}
|
||||
|
||||
// configureGatewayVirtualService 配置与Gateway关联的VirtualService
|
||||
func (r *ApplicationReconciler) configureGatewayVirtualService(vs *istionetworkingv1.VirtualService, app *applicationv1.Application, service *core_v1.Service) error {
|
||||
func (r *ApplicationReconciler) configureGatewayVirtualService(vs *istionetworkingv1.VirtualService, app *applicationv1.Application, service *core_v1.Service, gatewayRef string) error {
|
||||
// 设置基本字段
|
||||
vs.Spec.Hosts = getHosts(app)
|
||||
vs.Spec.Gateways = []string{app.Name + "-gateway"}
|
||||
vs.Spec.Gateways = []string{gatewayRef}
|
||||
|
||||
// 创建HTTP路由
|
||||
httpRoute := &istioapinetworkingv1.HTTPRoute{
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
/*
|
||||
* Copyright (c) Mengning Software. 2025. All rights reserved.
|
||||
* Authors: DevStar Team, panshuxiao
|
||||
* Create: 2025-11-19
|
||||
* Description: Registers the Application controller with manager.
|
||||
*/
|
||||
|
||||
package application
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
/*
|
||||
* Copyright (c) Mengning Software. 2025. All rights reserved.
|
||||
* Authors: DevStar Team, panshuxiao
|
||||
* Create: 2025-11-19
|
||||
* Description: Helper utilities for rendering app templates.
|
||||
*/
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
@@ -55,6 +62,26 @@ func NewDeployment(app *applicationv1.Application) (*apps_v1.Deployment, error)
|
||||
deployment.Name = app.Name
|
||||
deployment.Namespace = app.Namespace
|
||||
|
||||
// 默认启用 Istio sidecar 注入(除非明确禁用)
|
||||
shouldInjectSidecar := true
|
||||
if app.Spec.NetworkPolicy != nil && app.Spec.NetworkPolicy.Mesh != nil && app.Spec.NetworkPolicy.Mesh.Sidecar != nil {
|
||||
// 如果明确配置了 sidecar,使用配置的值
|
||||
shouldInjectSidecar = app.Spec.NetworkPolicy.Mesh.Sidecar.Inject
|
||||
}
|
||||
if shouldInjectSidecar {
|
||||
// 添加注解触发 sidecar 注入
|
||||
if deployment.Spec.Template.Annotations == nil {
|
||||
deployment.Spec.Template.Annotations = make(map[string]string)
|
||||
}
|
||||
deployment.Spec.Template.Annotations["sidecar.istio.io/inject"] = "true"
|
||||
|
||||
// 添加标签确保 webhook 能够匹配(Istio 1.27+ 需要)
|
||||
if deployment.Spec.Template.Labels == nil {
|
||||
deployment.Spec.Template.Labels = make(map[string]string)
|
||||
}
|
||||
deployment.Spec.Template.Labels["istio.io/rev"] = "default"
|
||||
}
|
||||
|
||||
return deployment, nil
|
||||
}
|
||||
|
||||
@@ -105,6 +132,26 @@ func NewStatefulSet(app *applicationv1.Application) (*apps_v1.StatefulSet, error
|
||||
statefulSet.Name = app.Name
|
||||
statefulSet.Namespace = app.Namespace
|
||||
|
||||
// 默认启用 Istio sidecar 注入(除非明确禁用)
|
||||
shouldInjectSidecar := true
|
||||
if app.Spec.NetworkPolicy != nil && app.Spec.NetworkPolicy.Mesh != nil && app.Spec.NetworkPolicy.Mesh.Sidecar != nil {
|
||||
// 如果明确配置了 sidecar,使用配置的值
|
||||
shouldInjectSidecar = app.Spec.NetworkPolicy.Mesh.Sidecar.Inject
|
||||
}
|
||||
if shouldInjectSidecar {
|
||||
// 添加注解触发 sidecar 注入
|
||||
if statefulSet.Spec.Template.Annotations == nil {
|
||||
statefulSet.Spec.Template.Annotations = make(map[string]string)
|
||||
}
|
||||
statefulSet.Spec.Template.Annotations["sidecar.istio.io/inject"] = "true"
|
||||
|
||||
// 添加标签确保 webhook 能够匹配(Istio 1.27+ 需要)
|
||||
if statefulSet.Spec.Template.Labels == nil {
|
||||
statefulSet.Spec.Template.Labels = make(map[string]string)
|
||||
}
|
||||
statefulSet.Spec.Template.Labels["istio.io/rev"] = "default"
|
||||
}
|
||||
|
||||
return statefulSet, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
/*
|
||||
* Copyright (c) Mengning Software. 2025. All rights reserved.
|
||||
* Authors: DevStar Team, panshuxiao
|
||||
* Create: 2025-11-19
|
||||
* Description: Registers the DevContainer controller.
|
||||
*/
|
||||
|
||||
package devcontainer
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
/*
|
||||
* Copyright (c) Mengning Software. 2025. All rights reserved.
|
||||
* Authors: DevStar Team, panshuxiao
|
||||
* Create: 2025-11-19
|
||||
* Description: Reconciliation logic for DevContainer CRDs.
|
||||
*/
|
||||
|
||||
/*
|
||||
Copyright 2024.
|
||||
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
/*
|
||||
Copyright 2024.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package devcontainer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/rest"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest"
|
||||
logf "sigs.k8s.io/controller-runtime/pkg/log"
|
||||
"sigs.k8s.io/controller-runtime/pkg/log/zap"
|
||||
|
||||
devcontainerv1 "code.gitea.io/gitea/modules/k8s/api/devcontainer/v1"
|
||||
// +kubebuilder:scaffold:imports
|
||||
)
|
||||
|
||||
// These tests use Ginkgo (BDD-style Go testing framework). Refer to
|
||||
// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.
|
||||
|
||||
var cfg *rest.Config
|
||||
var k8sClient client.Client
|
||||
var testEnv *envtest.Environment
|
||||
var ctx context.Context
|
||||
var cancel context.CancelFunc
|
||||
|
||||
func TestControllers(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
|
||||
RunSpecs(t, "Controller Suite")
|
||||
}
|
||||
|
||||
var _ = BeforeSuite(func() {
|
||||
logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))
|
||||
|
||||
ctx, cancel = context.WithCancel(context.TODO())
|
||||
|
||||
By("bootstrapping test environment")
|
||||
testEnv = &envtest.Environment{
|
||||
CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")},
|
||||
ErrorIfCRDPathMissing: true,
|
||||
|
||||
// The BinaryAssetsDirectory is only required if you want to run the tests directly
|
||||
// without call the makefile target test. If not informed it will look for the
|
||||
// default path defined in controller-runtime which is /usr/local/kubebuilder/.
|
||||
// Note that you must have the required binaries setup under the bin directory to perform
|
||||
// the tests directly. When we run make test it will be setup and used automatically.
|
||||
BinaryAssetsDirectory: filepath.Join("..", "..", "bin", "k8s",
|
||||
fmt.Sprintf("1.31.0-%s-%s", runtime.GOOS, runtime.GOARCH)),
|
||||
}
|
||||
|
||||
var err error
|
||||
// cfg is defined in this file globally.
|
||||
cfg, err = testEnv.Start()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(cfg).NotTo(BeNil())
|
||||
|
||||
err = devcontainerv1.AddToScheme(scheme.Scheme)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// +kubebuilder:scaffold:scheme
|
||||
|
||||
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(k8sClient).NotTo(BeNil())
|
||||
|
||||
})
|
||||
|
||||
var _ = AfterSuite(func() {
|
||||
By("tearing down the test environment")
|
||||
cancel()
|
||||
err := testEnv.Stop()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
@@ -1,3 +1,10 @@
|
||||
/*
|
||||
* Copyright (c) Mengning Software. 2025. All rights reserved.
|
||||
* Authors: DevStar Team, panshuxiao
|
||||
* Create: 2025-11-19
|
||||
* Description: Template helpers for devcontainer manifests.
|
||||
*/
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
/*
|
||||
* Copyright (c) Mengning Software. 2025. All rights reserved.
|
||||
* Authors: DevStar Team, panshuxiao
|
||||
* Create: 2025-11-19
|
||||
* Description: Shared controller manager setup utilities.
|
||||
*/
|
||||
|
||||
package controller
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
/*
|
||||
* Copyright (c) Mengning Software. 2025. All rights reserved.
|
||||
* Authors: DevStar Team, panshuxiao
|
||||
* Create: 2025-11-19
|
||||
* Description: Common error types for Kubernetes helpers.
|
||||
*/
|
||||
|
||||
package errors
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
/*
|
||||
* Copyright (c) Mengning Software. 2025. All rights reserved.
|
||||
* Authors: DevStar Team, panshuxiao
|
||||
* Create: 2025-11-19
|
||||
* Description: Kubernetes client helper functions for DevContainerApp CRD.
|
||||
*/
|
||||
|
||||
package k8s_agent
|
||||
|
||||
import (
|
||||
@@ -83,6 +90,52 @@ func GetKubernetesClient(ctx context.Context, kubeconfig []byte, contextName str
|
||||
return client, nil
|
||||
}
|
||||
|
||||
// GetKubernetesClientWithToken 通过用户提供的 k8sURL 和 token 获取动态客户端
|
||||
// 如果 k8sURL 和 token 为空,则优先使用配置文件中的 K8sConfig.Url 和 K8sConfig.Token
|
||||
// 如果配置文件也未配置,则返回错误(不回退到 kubeconfig)
|
||||
func GetKubernetesClientWithToken(ctx context.Context, k8sURL, token string) (dynamicclient.Interface, error) {
|
||||
// 如果未提供 URL 和 token,优先使用配置文件中的设置
|
||||
if k8sURL == "" || token == "" {
|
||||
// 优先使用配置文件中的 K8s 配置
|
||||
if setting.K8sConfig.Enable && setting.K8sConfig.Url != "" && setting.K8sConfig.Token != "" {
|
||||
k8sURL = setting.K8sConfig.Url
|
||||
token = setting.K8sConfig.Token
|
||||
log.Info("使用配置文件中的 K8s 配置: URL=%s", k8sURL)
|
||||
}
|
||||
}
|
||||
|
||||
// 如果仍然没有 URL 和 token,直接返回错误
|
||||
if k8sURL == "" || token == "" {
|
||||
return nil, fmt.Errorf("k8sURL and token are required, neither provided nor found in config")
|
||||
}
|
||||
|
||||
// 使用 token 认证创建配置
|
||||
config := &clientgorest.Config{
|
||||
Host: k8sURL,
|
||||
BearerToken: token,
|
||||
TLSClientConfig: clientgorest.TLSClientConfig{
|
||||
Insecure: true,
|
||||
},
|
||||
}
|
||||
|
||||
applyClientDefaults(config)
|
||||
|
||||
// 强制跳过 TLS 证书校验(与 GetKubernetesClient 保持一致)
|
||||
// 同时清空 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)
|
||||
func restConfigFromKubeconfigBytes(kubeconfig []byte, contextName string) (*clientgorest.Config, error) {
|
||||
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
/*
|
||||
* Copyright (c) Mengning Software. 2025. All rights reserved.
|
||||
* Authors: DevStar Team, panshuxiao
|
||||
* Create: 2025-11-19
|
||||
* Description: Shared data types of Kubernetes helpers for DevContainerApp CRD.
|
||||
*/
|
||||
|
||||
package k8s_agent
|
||||
|
||||
import (
|
||||
|
||||
@@ -71,6 +71,8 @@ var Service = struct {
|
||||
McaptchaURL string
|
||||
DefaultKeepEmailPrivate bool
|
||||
DefaultAllowCreateOrganization bool
|
||||
DefaultAllowCreateDevcontainer bool
|
||||
DefaultAllowCreateActRunner bool
|
||||
DefaultUserIsRestricted bool
|
||||
EnableTimetracking bool
|
||||
DefaultEnableTimetracking bool
|
||||
@@ -205,6 +207,8 @@ func loadServiceFrom(rootCfg ConfigProvider) {
|
||||
Service.McaptchaSitekey = sec.Key("MCAPTCHA_SITEKEY").MustString("")
|
||||
Service.DefaultKeepEmailPrivate = sec.Key("DEFAULT_KEEP_EMAIL_PRIVATE").MustBool()
|
||||
Service.DefaultAllowCreateOrganization = sec.Key("DEFAULT_ALLOW_CREATE_ORGANIZATION").MustBool(true)
|
||||
Service.DefaultAllowCreateDevcontainer = sec.Key("DEFAULT_ALLOW_CREATE_DEVCONTAINER").MustBool(true)
|
||||
Service.DefaultAllowCreateActRunner = sec.Key("DEFAULT_ALLOW_CREATE_ACTRUNNER").MustBool(false)
|
||||
Service.DefaultUserIsRestricted = sec.Key("DEFAULT_USER_IS_RESTRICTED").MustBool(false)
|
||||
Service.EnableTimetracking = sec.Key("ENABLE_TIMETRACKING").MustBool(true)
|
||||
if Service.EnableTimetracking {
|
||||
|
||||
@@ -55,6 +55,7 @@ type EditUserOption struct {
|
||||
ProhibitLogin *bool `json:"prohibit_login"`
|
||||
AllowCreateOrganization *bool `json:"allow_create_organization"`
|
||||
AllowCreateDevcontainer *bool `json:"allow_create_devcontainer"`
|
||||
AllowCreateActRunner *bool `json:"allow_create_actrunner"`
|
||||
Restricted *bool `json:"restricted"`
|
||||
Visibility string `json:"visibility" binding:"In(,public,limited,private)"`
|
||||
}
|
||||
|
||||
@@ -245,8 +245,8 @@ license_desc = Go get <a target="_blank" rel="noopener noreferrer" href="%[1]s">
|
||||
install=Instalace
|
||||
installing_desc=Probíhá instalace, čekejte prosím...
|
||||
title=Výchozí konfigurace
|
||||
docker_helper=Pokud spouštíte Gitea v Dockeru, přečtěte si <a target="_blank" rel="noopener noreferrer" href="%s">dokumentaci</a>, než budete měnit jakákoliv nastavení.
|
||||
require_db_desc=Gitea requires MySQL, PostgreSQL, MSSQL, SQLite3 or TiDB (MySQL protocol).
|
||||
k8s_helper=Pokud spouštíte DevStar v Kubernetesu, přečtěte si <a target="_blank" rel="noopener noreferrer" href="%s">dokumentaci</a>, než budete měnit jakákoliv nastavení.
|
||||
require_db_desc=DevStar requires MySQL, PostgreSQL, MSSQL, SQLite3 or TiDB (MySQL protocol).
|
||||
db_title=Nastavení databáze
|
||||
db_type=Typ databáze
|
||||
host=Hostitel
|
||||
@@ -351,7 +351,7 @@ password_algorithm=Hash algoritmus hesla
|
||||
invalid_password_algorithm=Neplatný algoritmus hash hesla
|
||||
password_algorithm_helper=Nastavte algoritmus hashování hesla. Algoritmy mají odlišné požadavky a sílu. Algoritmus argon2 je poměrně bezpečný, ale používá spoustu paměti a může být nevhodný pro malé systémy.
|
||||
enable_update_checker=Povolit kontrolu aktualizací
|
||||
enable_update_checker_helper=Kontroluje vydání nových verzí pravidelně připojením ke gitea.io.
|
||||
enable_update_checker_helper=Kontroluje vydání nových verzí pravidelně připojením ke devstar.cn.
|
||||
env_config_keys=Konfigurace prostředí
|
||||
env_config_keys_prompt=Následující proměnné prostředí budou také použity pro váš konfigurační soubor:
|
||||
config_write_file_prompt=Tyto možnosti konfigurace budou zapsány do: %s
|
||||
|
||||
@@ -251,8 +251,8 @@ license_desc = Go get <a target="_blank" rel="noopener noreferrer" href="%[1]s">
|
||||
install=Installation
|
||||
installing_desc=Wird jetzt installiert, bitte warten...
|
||||
title=Erstkonfiguration
|
||||
docker_helper=Wenn du Gitea in einem Docker-Container nutzt, lies bitte die <a target="_blank" rel="noopener noreferrer" href="%s">Dokumentation</a>, bevor du irgendwelche Einstellungen veränderst.
|
||||
require_db_desc=Gitea benötigt MySQL, PostgreSQL, MSSQL, SQLite3 oder TiDB (MySQL-Protokoll).
|
||||
k8s_helper=Wenn du DevStar in einem Kubernetes-Container nutzt, lies bitte die <a target="_blank" rel="noopener noreferrer" href="%s">Dokumentation</a>, bevor du irgendwelche Einstellungen veränderst.
|
||||
require_db_desc=DevStar benötigt MySQL, PostgreSQL, MSSQL, SQLite3 oder TiDB (MySQL-Protokoll).
|
||||
db_title=Datenbankeinstellungen
|
||||
db_type=Datenbanktyp
|
||||
host=Host
|
||||
@@ -357,7 +357,7 @@ password_algorithm=Passwort Hashing Algorithmus
|
||||
invalid_password_algorithm=Ungültiger Passwort-Hash-Algorithmus
|
||||
password_algorithm_helper=Lege einen Passwort-Hashing-Algorithmus fest. Algorithmen haben unterschiedliche Anforderungen und Stärken. Der argon2-Algorithmus ist ziemlich sicher, aber er verbraucht viel Speicher und kann für kleine Systeme ungeeignet sein.
|
||||
enable_update_checker=Aktualisierungsprüfung aktivieren
|
||||
enable_update_checker_helper=Stellt regelmäßig eine Verbindung zu gitea.io her, um nach neuen Versionen zu prüfen.
|
||||
enable_update_checker_helper=Stellt regelmäßig eine Verbindung zu devstar.cn her, um nach neuen Versionen zu prüfen.
|
||||
env_config_keys=Umgebungskonfiguration
|
||||
env_config_keys_prompt=Die folgenden Umgebungsvariablen werden auch auf Ihre Konfigurationsdatei angewendet:
|
||||
config_write_file_prompt=Diese Konfigurationsoptionen werden in %s geschrieben
|
||||
|
||||
@@ -203,8 +203,8 @@ license_desc = Go get <a target="_blank" rel="noopener noreferrer" href="%[1]s">
|
||||
[install]
|
||||
install=Εγκατάσταση
|
||||
title=Αρχικές Ρυθμίσεις
|
||||
docker_helper=Αν εκτελέσετε το Gitea μέσα στο Docker, παρακαλώ διαβάστε την <a target="_blank" rel="noopener noreferrer" href="%s">τεκμηρίωση</a> πριν αλλάξετε τις ρυθμίσεις.
|
||||
require_db_desc=Το Gitea απαιτεί MySQL, PostgreSQL, MSSQL, SQLite3 ή TiDB (με πρωτόκολλο MySQL).
|
||||
k8s_helper=Αν εκτελέσετε το DevStar μέσα στο Kubernetes, παρακαλώ διαβάστε την <a target="_blank" rel="noopener noreferrer" href="%s">τεκμηρίωση</a> πριν αλλάξετε τις ρυθμίσεις.
|
||||
require_db_desc=Το DevStar απαιτεί MySQL, PostgreSQL, MSSQL, SQLite3 ή TiDB (με πρωτόκολλο MySQL).
|
||||
db_title=Ρυθμίσεις Βάσης Δεδομένων
|
||||
db_type=Τύπος της Βάσης Δεδομένων
|
||||
host=Διακομιστής
|
||||
@@ -308,7 +308,7 @@ password_algorithm=Αλγόριθμος Hash Κωδικού Πρόσβασης
|
||||
invalid_password_algorithm=Μη έγκυρος αλγόριθμος κωδικού πρόσβασης
|
||||
password_algorithm_helper=Ορίστε τον αλγόριθμο κατακερματισμού για το κωδικό πρόσβασης. Οι αλγόριθμοι διαφέρουν σε απαιτήσεις και αντοχή. Ο αλγόριθμος argon2 είναι αρκετά ασφαλής, αλλά χρησιμοποιεί πολλή μνήμη και μπορεί να είναι ακατάλληλος για μικρά συστήματα.
|
||||
enable_update_checker=Ενεργοποίηση Ελεγκτή Ενημερώσεων
|
||||
enable_update_checker_helper=Ελέγχει περιοδικά για νέες εκδόσεις κάνοντας σύνδεση στο gitea.io.
|
||||
enable_update_checker_helper=Ελέγχει περιοδικά για νέες εκδόσεις κάνοντας σύνδεση στο devstar.cn.
|
||||
env_config_keys=Ρυθμίσεις Περιβάλλοντος
|
||||
env_config_keys_prompt=Οι ακόλουθες μεταβλητές περιβάλλοντος θα εφαρμοστούν επίσης στο αρχείο ρυθμίσεων σας:
|
||||
|
||||
|
||||
@@ -256,8 +256,8 @@ license_desc = Go get <a target="_blank" rel="noopener noreferrer" href="%[1]s">
|
||||
install = Installation
|
||||
installing_desc = Installing now, please wait...
|
||||
title = Initial Configuration
|
||||
docker_helper = If you run Gitea inside Docker, please read the <a target="_blank" rel="noopener noreferrer" href="%s">documentation</a> before changing any settings.
|
||||
require_db_desc = Gitea requires MySQL, PostgreSQL, MSSQL, SQLite3 or TiDB (MySQL protocol).
|
||||
k8s_helper = If you run DevStar inside Kubernetes, please read the <a target="_blank" rel="noopener noreferrer" href="%s">documentation</a> before changing any settings.
|
||||
require_db_desc = DevStar requires MySQL, PostgreSQL, MSSQL, SQLite3 or TiDB (MySQL protocol).
|
||||
db_title = Database Settings
|
||||
db_type = Database Type
|
||||
host = Host
|
||||
@@ -362,7 +362,11 @@ invalid_log_root_path = The log path is invalid: %v
|
||||
default_keep_email_private = Hide Email Addresses by Default
|
||||
default_keep_email_private_popup = Hide email addresses of new user accounts by default.
|
||||
default_allow_create_organization = Allow Creation of Organizations by Default
|
||||
default_allow_create_devcontainer = Allow Creation of DevContainers by Default
|
||||
default_allow_create_actrunner = Allow Creation of ActionRunners by Default
|
||||
default_allow_create_organization_popup = Allow new user accounts to create organizations by default.
|
||||
default_allow_create_devcontainer_popup = Allow new user accounts to create devcontainers by default.
|
||||
default_allow_create_actrunner_popup = Allow new user accounts to create ActionRunner by default.
|
||||
default_enable_timetracking = Enable Time Tracking by Default
|
||||
default_enable_timetracking_popup = Enable time tracking for new repositories by default.
|
||||
no_reply_address = Hidden Email Domain
|
||||
@@ -371,7 +375,7 @@ password_algorithm = Password Hash Algorithm
|
||||
invalid_password_algorithm = Invalid password hash algorithm
|
||||
password_algorithm_helper = Set the password hashing algorithm. Algorithms have differing requirements and strength. The argon2 algorithm is rather secure but uses a lot of memory and may be inappropriate for small systems.
|
||||
enable_update_checker = Enable Update Checker
|
||||
enable_update_checker_helper = Checks for new version releases periodically by connecting to gitea.io.
|
||||
enable_update_checker_helper = Checks for new version releases periodically by connecting to devstar.cn
|
||||
env_config_keys = Environment Configuration
|
||||
env_config_keys_prompt = The following environment variables will also be applied to your configuration file:
|
||||
config_write_file_prompt = These configuration options will be written into: %s
|
||||
@@ -738,6 +742,7 @@ avatar = Avatar
|
||||
ssh_gpg_keys = SSH / GPG Keys
|
||||
social = Social Accounts
|
||||
applications = Applications
|
||||
appstore = App Store
|
||||
orgs = Manage Organizations
|
||||
repos = Repositories
|
||||
dev_containers_list = Dev Containers
|
||||
@@ -3160,6 +3165,7 @@ users.allow_git_hook_tooltip = Git Hooks are executed as the OS user running Git
|
||||
users.allow_import_local = May Import Local Repositories
|
||||
users.allow_create_organization = May Create Organizations
|
||||
users.allow_create_devcontainer= May Create Devcontainers
|
||||
users.allow_create_actrunner= May Create ActRunners
|
||||
users.update_profile = Update User Account
|
||||
users.delete_account = Delete User Account
|
||||
users.cannot_delete_self = "You cannot delete yourself"
|
||||
@@ -3420,6 +3426,7 @@ config.active_code_lives = Active Code Lives
|
||||
config.reset_password_code_lives = Recover Account Code Expiry Time
|
||||
config.default_keep_email_private = Hide Email Addresses by Default
|
||||
config.default_allow_create_organization = Allow Creation of Organizations by Default
|
||||
config.default_allow_create_devcontainer = Allow Creation of Dev Containers by Default
|
||||
config.enable_timetracking = Enable Time Tracking
|
||||
config.default_enable_timetracking = Enable Time Tracking by Default
|
||||
config.default_allow_only_contributors_to_track_time = Let Only Contributors Track Time
|
||||
@@ -3877,7 +3884,7 @@ status.blocked = "Blocked"
|
||||
runners = Runners
|
||||
runners.runner_manage_panel = Runners Management
|
||||
runners.new = Create new Runner
|
||||
runners.new_notice = How to start a runner
|
||||
runners.new_notice = How to start a runner manually
|
||||
runners.status = Status
|
||||
runners.id = ID
|
||||
runners.name = Name
|
||||
@@ -3990,3 +3997,42 @@ normal_file = Normal file
|
||||
executable_file = Executable file
|
||||
symbolic_link = Symbolic link
|
||||
submodule = Submodule
|
||||
|
||||
[appstore]
|
||||
title = App Store
|
||||
description = Browse and install various applications
|
||||
install = Install
|
||||
uninstall = Uninstall
|
||||
configure = Configure
|
||||
install_success = App "%s" installed successfully
|
||||
uninstall_success = App "%s" uninstalled successfully
|
||||
configure_success = App "%s" configuration updated
|
||||
no_apps = No applications available
|
||||
search_placeholder = Search applications...
|
||||
category_all = All Categories
|
||||
category_web_server = Web Server
|
||||
category_database = Database
|
||||
category_development = Development Tools
|
||||
category_monitoring = Monitoring Tools
|
||||
category_other = Other
|
||||
deployment_type = Deployment Type
|
||||
deployment_docker = Docker
|
||||
deployment_kubernetes = Kubernetes
|
||||
requirements = System Requirements
|
||||
version = Version
|
||||
author = Author
|
||||
license = License
|
||||
website = Website
|
||||
repository = Repository
|
||||
description = Description
|
||||
tags = Tags
|
||||
install_config = Installation Configuration
|
||||
config_schema = Configuration Schema
|
||||
default_values = Default Values
|
||||
user_values = User Values
|
||||
merged_config = Merged Configuration
|
||||
validation_error = Configuration validation error
|
||||
required_field = Required field
|
||||
invalid_value = Invalid value
|
||||
out_of_range = Out of range
|
||||
invalid_format = Invalid format
|
||||
@@ -201,8 +201,8 @@ license_desc = Go get <a target="_blank" rel="noopener noreferrer" href="%[1]s">
|
||||
[install]
|
||||
install=Instalación
|
||||
title=Configuración inicial
|
||||
docker_helper=Si está ejecutando Gitea dentro de un contenedor Docker, por favor lea la <a target="_blank" rel="noopener noreferrer" href="%s">documentación</a> antes de realizar cambios en la configuración.
|
||||
require_db_desc=Gitea requiere una base de datos MySQL, PostgreSQL, MSSQL, SQLite3 o TiDB (usar el protocolo MySQL).
|
||||
k8s_helper=Si está ejecutando DevStar dentro de un contenedor Kubernetes, por favor lea la <a target="_blank" rel="noopener noreferrer" href="%s">documentación</a> antes de realizar cambios en la configuración.
|
||||
require_db_desc=DevStar requiere una base de datos MySQL, PostgreSQL, MSSQL, SQLite3 o TiDB (usar el protocolo MySQL).
|
||||
db_title=Configuración de base de datos
|
||||
db_type=Tipo de base de datos
|
||||
host=Servidor
|
||||
@@ -306,7 +306,7 @@ password_algorithm=Algoritmo Hash de Contraseña
|
||||
invalid_password_algorithm=Algoritmo hash de contraseña no válido
|
||||
password_algorithm_helper=Establece el algoritmo de hashing de contraseña. Los algoritmos tienen diferentes requisitos y fuerza. El algoritmo argon2 es bastante seguro, pero usa mucha memoria y puede ser inapropiado para sistemas pequeños.
|
||||
enable_update_checker=Activar comprobador de actualizaciones
|
||||
enable_update_checker_helper=Comprueba el lanzamiento de nuevas versiones periódicamente en gitea.io.
|
||||
enable_update_checker_helper=Comprueba el lanzamiento de nuevas versiones periódicamente en devstar.cn.
|
||||
env_config_keys=Configuración del entorno
|
||||
env_config_keys_prompt=Las siguientes variables de entorno también se aplicarán a su archivo de configuración:
|
||||
|
||||
|
||||
@@ -134,7 +134,7 @@ license_desc = Go get <a target="_blank" rel="noopener noreferrer" href="%[1]s">
|
||||
[install]
|
||||
install=نصب و راه اندازی
|
||||
title=تنظیمات اولیه
|
||||
docker_helper=اگر گیتی را با داکر اجرا کردهاید، لطفا قبل از هر تغییری <a target="_blank" rel="noopener noreferrer" href="%s">مستندات</a> را مطالعه نمایید.
|
||||
k8s_helper=اگر گیتی را با داکر اجرا کردهاید، لطفا قبل از هر تغییری <a target="_blank" rel="noopener noreferrer" href="%s">مستندات</a> را مطالعه نمایید.
|
||||
db_title=تنظیمات پایگاه داده
|
||||
db_type=نوع پایگاه داده
|
||||
host=میزبان
|
||||
|
||||
@@ -152,8 +152,8 @@ license_desc = Go get <a target="_blank" rel="noopener noreferrer" href="%[1]s">
|
||||
[install]
|
||||
install=Asennus
|
||||
title=Alkuperäiset asetukset
|
||||
docker_helper=Jos ajat Giteaa Dockerin sisällä, lue <a target="_blank" rel="noopener noreferrer" href="%s">ohjeet</a> ennen minkään asetuksen muuttamista.
|
||||
require_db_desc=Gitea tarvitsee toimiakseen MySQL, PostgreSQL, MSSQL, SQLite3 tai TiDB (MySQL protokolla) tietokannan.
|
||||
k8s_helper=Jos ajat DevStara Kubernetesin sisällä, lue <a target="_blank" rel="noopener noreferrer" href="%s">ohjeet</a> ennen minkään asetuksen muuttamista.
|
||||
require_db_desc=DevStar tarvitsee toimiakseen MySQL, PostgreSQL, MSSQL, SQLite3 tai TiDB (MySQL protokolla) tietokannan.
|
||||
db_title=Tietokanta asetukset
|
||||
db_type=Tietokanta tyyppi
|
||||
host=Isäntä
|
||||
|
||||
@@ -254,8 +254,8 @@ license_desc = Go get <a target="_blank" rel="noopener noreferrer" href="%[1]s">
|
||||
install=Installation
|
||||
installing_desc=Installation en cours, veuillez patienter…
|
||||
title=Configuration initiale
|
||||
docker_helper=Si vous exécutez Gitea dans Docker, veuillez lire la <a target="_blank" rel="noopener noreferrer" href="%s">documentation</a> avant de modifier les paramètres.
|
||||
require_db_desc=Gitea nécessite MySQL, PostgreSQL, MSSQL, SQLite3 ou TiDB (avec le protocole MySQL).
|
||||
k8s_helper=Si vous exécutez DevStar dans Kubernetes, veuillez lire la <a target="_blank" rel="noopener noreferrer" href="%s">documentation</a> avant de modifier les paramètres.
|
||||
require_db_desc=DevStar nécessite MySQL, PostgreSQL, MSSQL, SQLite3 ou TiDB (avec le protocole MySQL).
|
||||
db_title=Paramètres de la base de données
|
||||
db_type=Type de base de données
|
||||
host=Hôte
|
||||
@@ -360,7 +360,7 @@ password_algorithm=Algorithme de hachage du mot de passe
|
||||
invalid_password_algorithm=Algorithme de hachage du mot de passe invalide
|
||||
password_algorithm_helper=Définissez l’algorithme de hachage du mot de passe. Les algorithmes ont des exigences matérielles et une résistance différentes. L’algorithme argon2 est bien sécurisé mais utilise beaucoup de mémoire et peut être inapproprié pour les systèmes limités en ressources.
|
||||
enable_update_checker=Activer la vérification des mises-à-jour
|
||||
enable_update_checker_helper=Vérifie les mises à jour régulièrement en se connectant à gitea.io.
|
||||
enable_update_checker_helper=Vérifie les mises à jour régulièrement en se connectant à devstar.cn.
|
||||
env_config_keys=Configuration de l'environnement
|
||||
env_config_keys_prompt=Les variables d'environnement suivantes seront également ajoutées à votre fichier de configuration :
|
||||
config_write_file_prompt=Ces options de configuration seront écrites dans : %s
|
||||
|
||||
@@ -254,7 +254,7 @@ license_desc = Go get <a target="_blank" rel="noopener noreferrer" href="%[1]s">
|
||||
install=Suiteáil
|
||||
installing_desc=Suiteáil anois, fan go fóill...
|
||||
title=Cumraíocht Tosaigh
|
||||
docker_helper=Má ritheann tú Gitea taobh istigh de Docker, léigh an <a target="_blank" rel="noopener noreferrer" href="%s">doiciméadúchán</a> roimh aon socruithe a athrú.
|
||||
k8s_helper=Má ritheann tú DevStar taobh istigh de Kubernetes, léigh an <a target="_blank" rel="noopener noreferrer" href="%s">doiciméadúchán</a> roimh aon socruithe a athrú.
|
||||
require_db_desc=Éilíonn Gitea MySQL, PostgreSQL, MSSQL, SQLite3 nó TiDB (prótacal MySQL).
|
||||
db_title=Socruithe Bunachar Sonraí
|
||||
db_type=Cineál Bunachar Sonraí
|
||||
@@ -360,7 +360,7 @@ password_algorithm=Algartam Hais Pasfhocal
|
||||
invalid_password_algorithm=Algartam hais pasfhocail neamhbhailí
|
||||
password_algorithm_helper=Socraigh an algartam hashing pasfhocal. Tá riachtanais agus neart éagsúla ag halgartaim. Tá an algartam argon2 sách slán ach úsáideann sé go leor cuimhne agus d'fhéadfadh sé a bheith míchuí do chórais bheaga.
|
||||
enable_update_checker=Cumasaigh Seiceoir Nuashonraithe
|
||||
enable_update_checker_helper=Seiceálacha ar eisiúintí leagan nua go tréimhsiúil trí nascadh le gitea.io.
|
||||
enable_update_checker_helper=Seiceálacha ar eisiúintí leagan nua go tréimhsiúil trí nascadh le devstar.cn.
|
||||
env_config_keys=Cumraíocht Comhshaoil
|
||||
env_config_keys_prompt=Cuirfear na hathróga comhshaoil seo a leanas i bhfeidhm ar do chomhad cumraíochta freisin:
|
||||
config_write_file_prompt=Scríobhfar na roghanna cumraíochta seo isteach: %s
|
||||
|
||||
@@ -123,7 +123,7 @@ license_desc = Go get <a target="_blank" rel="noopener noreferrer" href="%[1]s">
|
||||
[install]
|
||||
install=Telepítés
|
||||
title=Kezdeti konfiguráció
|
||||
docker_helper=Ha ön a Gitea-t Docker-ből futtatja, kérem olvassa el a <a target="_blank" rel="noopener noreferrer" href="%s">dokumentációt</a> a beállítások megváltoztatása előtt.
|
||||
k8s_helper=Ha ön a DevStar-t Kubernetes-ből futtatja, kérem olvassa el a <a target="_blank" rel="noopener noreferrer" href="%s">dokumentációt</a> a beállítások megváltoztatása előtt.
|
||||
db_title=Adatbázis beállítások
|
||||
db_type=Adatbázis típusa
|
||||
host=Kiszolgáló
|
||||
|
||||
@@ -148,8 +148,8 @@ license_desc = Go get <a target="_blank" rel="noopener noreferrer" href="%[1]s">
|
||||
[install]
|
||||
install=Uppsetning
|
||||
title=Upphafleg Uppsetning
|
||||
docker_helper=Ef þú keyrir Gitea inni í Docker þá viltu vinsamlegast lesa <a target="_blank" rel="noopener noreferrer" href="%s">leiðbeiningaritið</a> áður en þú breytir stillingum.
|
||||
require_db_desc=Gitea krefst MySQL, PostgreSQL, MSSQL, SQLite3 eða TiDB (MySQL samskiptareglur).
|
||||
k8s_helper=Ef þú keyrir DevStar inni í Kubernetes þá viltu vinsamlegast lesa <a target="_blank" rel="noopener noreferrer" href="%s">leiðbeiningaritið</a> áður en þú breytir stillingum.
|
||||
require_db_desc=DevStar krefst MySQL, PostgreSQL, MSSQL, SQLite3 eða TiDB (MySQL samskiptareglur).
|
||||
db_title=Gagnagrunnsstillingar
|
||||
db_type=Tegund Gagnagrunns
|
||||
host=Hýsill
|
||||
|
||||
@@ -154,8 +154,8 @@ license_desc = Go get <a target="_blank" rel="noopener noreferrer" href="%[1]s">
|
||||
[install]
|
||||
install=Installazione
|
||||
title=Configurazione Iniziale
|
||||
docker_helper=Se stai usando Gitea con Docker, leggi <a target="_blank" rel="noopener noreferrer" href="%s">la documentazione</a> prima di cambiare qualsiasi impostazione.
|
||||
require_db_desc=Gitea requires MySQL, PostgreSQL, MSSQL, SQLite3 or TiDB (MySQL protocol).
|
||||
k8s_helper=Se stai usando DevStar con Kubernetes, leggi <a target="_blank" rel="noopener noreferrer" href="%s">la documentazione</a> prima di cambiare qualsiasi impostazione.
|
||||
require_db_desc=DevStar requires MySQL, PostgreSQL, MSSQL, SQLite3 or TiDB (MySQL protocol).
|
||||
db_title=Impostazioni Database
|
||||
db_type=Tipo di database
|
||||
host=Host
|
||||
|
||||
@@ -251,8 +251,8 @@ license_desc = Go get <a target="_blank" rel="noopener noreferrer" href="%[1]s">
|
||||
install=インストール
|
||||
installing_desc=インストール中です、お待ちください...
|
||||
title=初期設定
|
||||
docker_helper=GiteaをDocker内で実行する場合は、設定を変更する前に<a target="_blank" rel="noopener noreferrer" href="%s">ドキュメント</a>を読んでください。
|
||||
require_db_desc=Giteaには、MySQL、PostgreSQL、MSSQL、SQLite3、またはTiDB(MySQL プロトコル) が必要です。
|
||||
k8s_helper=DevStarをKubernetes内で実行する場合は、設定を変更する前に<a target="_blank" rel="noopener noreferrer" href="%s">ドキュメント</a>を読んでください。
|
||||
require_db_desc=DevStarには、MySQL、PostgreSQL、MSSQL、SQLite3、またはTiDB(MySQL プロトコル) が必要です。
|
||||
db_title=データベース設定
|
||||
db_type=データベースのタイプ
|
||||
host=ホスト
|
||||
@@ -357,7 +357,7 @@ password_algorithm=パスワードハッシュアルゴリズム
|
||||
invalid_password_algorithm=無効なパスワードハッシュアルゴリズム
|
||||
password_algorithm_helper=パスワードハッシュアルゴリズムを設定します。 アルゴリズムにより動作要件と強度が異なります。 argon2アルゴリズムはかなり安全ですが、多くのメモリを使用するため小さなシステムには適さない場合があります。
|
||||
enable_update_checker=アップデートチェッカーを有効にする
|
||||
enable_update_checker_helper=gitea.ioに接続して定期的に新しいバージョンのリリースを確認します。
|
||||
enable_update_checker_helper=devstar.cnに接続して定期的に新しいバージョンのリリースを確認します。
|
||||
env_config_keys=環境設定
|
||||
env_config_keys_prompt=以下の環境変数も設定ファイルに適用されます:
|
||||
config_write_file_prompt=これらの設定オプションは %s に書き込まれます
|
||||
|
||||
@@ -115,7 +115,7 @@ license_desc = Go get <a target="_blank" rel="noopener noreferrer" href="%[1]s">
|
||||
[install]
|
||||
install=설치
|
||||
title=초기 설정
|
||||
docker_helper=Gitea를 Docker에서 실행하려면 설정 전에 이 <a target="_blank" rel="noopener noreferrer" href="%s">문서</a>를 읽어보세요.
|
||||
k8s_helper=DevStar를 Kubernetes에서 실행하려면 설정 전에 이 <a target="_blank" rel="noopener noreferrer" href="%s">문서</a>를 읽어보세요.
|
||||
db_title=데이터베이스 설정
|
||||
db_type=데이터베이스 유형
|
||||
host=호스트
|
||||
|
||||
@@ -206,8 +206,8 @@ license_desc = Go get <a target="_blank" rel="noopener noreferrer" href="%[1]s">
|
||||
[install]
|
||||
install=Instalācija
|
||||
title=Sākotnējā konfigurācija
|
||||
docker_helper=Ja Gitea ir uzstādīts Docker konteinerī, izlasiet <a target="_blank" rel="noopener noreferrer" href="%s">vadlīninas</a> pirms maināt iestatījumus.
|
||||
require_db_desc=Gitea nepieciešams MySQL, PostgreSQL, MSSQL, SQLite3 vai TiDB (izmantojot MySQL protokolu).
|
||||
k8s_helper=Ja DevStar ir uzstādīts Kubernetes konteinerī, izlasiet <a target="_blank" rel="noopener noreferrer" href="%s">vadlīninas</a> pirms maināt iestatījumus.
|
||||
require_db_desc=DevStar nepieciešams MySQL, PostgreSQL, MSSQL, SQLite3 vai TiDB (izmantojot MySQL protokolu).
|
||||
db_title=Datu bāzes iestatījumi
|
||||
db_type=Datu bāzes veids
|
||||
host=Resursdators
|
||||
@@ -311,7 +311,7 @@ password_algorithm=Paroles jaucējsummas algoritms
|
||||
invalid_password_algorithm=Kļūdaina paroles jaucējfunkcija
|
||||
password_algorithm_helper=Norādiet paroles jaucējalgoritmu. Algoritmi atšķirās pēc prasībām pret resursiem un stipruma. Argon2 algoritms ir drošs, bet tam nepieciešams daudz operatīvās atmiņas, līdz ar ko tas var nebūt piemērots sistēmām ar maz pieejamajiem resursiem.
|
||||
enable_update_checker=Iespējot jaunu versiju paziņojumus
|
||||
enable_update_checker_helper=Periodiski pārbaudīt jaunu version pieejamību, izgūstot datus no gitea.io.
|
||||
enable_update_checker_helper=Periodiski pārbaudīt jaunu version pieejamību, izgūstot datus no devstar.cn.
|
||||
env_config_keys=Vides konfigurācija
|
||||
env_config_keys_prompt=Šie vides mainīgie tiks pielietoti arī konfigurācijas failā:
|
||||
|
||||
|
||||
@@ -153,8 +153,8 @@ license_desc = Go get <a target="_blank" rel="noopener noreferrer" href="%[1]s">
|
||||
[install]
|
||||
install=Installatie
|
||||
title=Initiële configuratie
|
||||
docker_helper=Als je gitea draait in Docker, Lees eerst de <a target="_blank" rel="noopener noreferrer" href="%s">documentatie</a> voordat je een instelling aanpast.
|
||||
require_db_desc=Gitea vereist MySQL, PostgreSQL, MSSQL, SQLite3 of TiDB (MySQL protocol).
|
||||
k8s_helper=Als je gitea draait in Kubernetes, Lees eerst de <a target="_blank" rel="noopener noreferrer" href="%s">documentatie</a> voordat je een instelling aanpast.
|
||||
require_db_desc=DevStar vereist MySQL, PostgreSQL, MSSQL, SQLite3 of TiDB (MySQL protocol).
|
||||
db_title=Database-instellingen
|
||||
db_type=Database-type
|
||||
host=Server
|
||||
|
||||
@@ -149,8 +149,8 @@ license_desc = Go get <a target="_blank" rel="noopener noreferrer" href="%[1]s">
|
||||
[install]
|
||||
install=Instalacja
|
||||
title=Wstępna konfiguracja
|
||||
docker_helper=Jeśli używasz Gitea za pomocą Docker'a, przeczytaj <a target="_blank" rel="noopener noreferrer" href="%s">dokumentację</a> przed wprowadzeniem jakichkolwiek zmian.
|
||||
require_db_desc=Gitea wymaga MySQL, PostgreSQL, MSSQL, SQLite3 lub TiDB (protokół MySQL).
|
||||
k8s_helper=Jeśli używasz DevStar za pomocą Kubernetes'a, przeczytaj <a target="_blank" rel="noopener noreferrer" href="%s">dokumentację</a> przed wprowadzeniem jakichkolwiek zmian.
|
||||
require_db_desc=DevStar wymaga MySQL, PostgreSQL, MSSQL, SQLite3 lub TiDB (protokół MySQL).
|
||||
db_title=Ustawienia bazy danych
|
||||
db_type=Typ bazy danych
|
||||
host=Serwer
|
||||
|
||||
@@ -202,8 +202,8 @@ license_desc = Go get <a target="_blank" rel="noopener noreferrer" href="%[1]s">
|
||||
[install]
|
||||
install=Instalação
|
||||
title=Configuração inicial
|
||||
docker_helper=Se você está rodando o Gitea dentro do Docker, por favor leia a <a target="_blank" rel="noopener noreferrer" href="%s">documentação</a> cuidadosamente antes de alterar qualquer coisa nesta página.
|
||||
require_db_desc=Gitea requer MySQL, PostgreSQL, MSSQL, SQLite3 ou TiDB (protocolo MySQL).
|
||||
k8s_helper=Se você está rodando o DevStar dentro do Kubernetes, por favor leia a <a target="_blank" rel="noopener noreferrer" href="%s">documentação</a> cuidadosamente antes de alterar qualquer coisa nesta página.
|
||||
require_db_desc=DevStar requer MySQL, PostgreSQL, MSSQL, SQLite3 ou TiDB (protocolo MySQL).
|
||||
db_title=Configurações de banco de dados
|
||||
db_type=Tipo de banco de dados
|
||||
host=Servidor
|
||||
@@ -307,7 +307,7 @@ password_algorithm=Algoritmo Hash de Senha
|
||||
invalid_password_algorithm=Algoritmo de hash de senha inválido
|
||||
password_algorithm_helper=Escolha o algoritmo de hash para as senhas. Diferentes algoritmos têm requerimentos e forças diversos. O algoritmo argon2 é bastante seguro, mas usa muita memória e pode ser inapropriado para sistemas com menos recursos.
|
||||
enable_update_checker=Habilitar Verificador de Atualizações
|
||||
enable_update_checker_helper=Procura por novas versões periodicamente conectando-se ao gitea.io.
|
||||
enable_update_checker_helper=Procura por novas versões periodicamente conectando-se ao devstar.cn.
|
||||
env_config_keys=Configuração do ambiente
|
||||
env_config_keys_prompt=As seguintes variáveis de ambiente também serão aplicadas ao seu arquivo de configuração:
|
||||
|
||||
|
||||
@@ -254,8 +254,8 @@ license_desc = Go get <a target="_blank" rel="noopener noreferrer" href="%[1]s">
|
||||
install=Instalação
|
||||
installing_desc=Instalando agora, por favor aguarde...
|
||||
title=Configuração inicial
|
||||
docker_helper=Se correr o Gitea dentro do Docker, leia a <a target="_blank" rel="noopener noreferrer" href="%s">documentação</a> antes de alterar quaisquer configurações.
|
||||
require_db_desc=Gitea requer MySQL, PostgreSQL, MSSQL, SQLite3 ou TiDB (protocolo MySQL).
|
||||
k8s_helper=Se correr o DevStar dentro do Kubernetes, leia a <a target="_blank" rel="noopener noreferrer" href="%s">documentação</a> antes de alterar quaisquer configurações.
|
||||
require_db_desc=DevStar requer MySQL, PostgreSQL, MSSQL, SQLite3 ou TiDB (protocolo MySQL).
|
||||
db_title=Configurações da base de dados
|
||||
db_type=Tipo de base de dados
|
||||
host=Servidor
|
||||
@@ -360,7 +360,7 @@ password_algorithm=Algoritmo de Hash da Senha
|
||||
invalid_password_algorithm=Algoritmo de hash da senha inválido
|
||||
password_algorithm_helper=Definir o algoritmo de hash da senha. Os algoritmos têm requisitos e resistência distintos. `argon2` é bastante seguro, mas usa muita memória e pode ser inapropriado para sistemas pequenos.
|
||||
enable_update_checker=Habilitar verificador de novidades
|
||||
enable_update_checker_helper=Verifica, periodicamente, se foi lançada alguma versão nova, fazendo uma ligação ao gitea.io.
|
||||
enable_update_checker_helper=Verifica, periodicamente, se foi lançada alguma versão nova, fazendo uma ligação ao devstar.cn.
|
||||
env_config_keys=Configuração do ambiente
|
||||
env_config_keys_prompt=As seguintes variáveis de ambiente também serão aplicadas ao seu ficheiro de configuração:
|
||||
config_write_file_prompt=Estas opções de configuração serão escritas em: %s
|
||||
|
||||
@@ -201,8 +201,8 @@ license_desc = Go get <a target="_blank" rel="noopener noreferrer" href="%[1]s">
|
||||
[install]
|
||||
install=Установка
|
||||
title=Начальная конфигурация
|
||||
docker_helper=Если вы запускаете Gitea внутри Docker, пожалуйста внимательно прочтите <a target="_blank" rel="noopener noreferrer" href="%s">документацию</a> перед тем, как изменить любые настройки.
|
||||
require_db_desc=Gitea требует MySQL, PostgreSQL, MSSQL, SQLite3 или TiDB (через протокол MySQL).
|
||||
k8s_helper=Если вы запускаете DevStar внутри Kubernetes, пожалуйста внимательно прочтите <a target="_blank" rel="noopener noreferrer" href="%s">документацию</a> перед тем, как изменить любые настройки.
|
||||
require_db_desc=DevStar требует MySQL, PostgreSQL, MSSQL, SQLite3 или TiDB (через протокол MySQL).
|
||||
db_title=Настройки базы данных
|
||||
db_type=Тип базы данных
|
||||
host=Хост
|
||||
@@ -306,7 +306,7 @@ password_algorithm=Алгоритм хеширования пароля
|
||||
invalid_password_algorithm=Некорректный алгоритм хеширования пароля
|
||||
password_algorithm_helper=Задайте алгоритм хеширования паролей. Алгоритмы имеют различные требования и стойкость. Алгоритм argon2 довольно безопасен, но он использует много памяти и может не подходить для слабых систем.
|
||||
enable_update_checker=Включить проверку обновлений
|
||||
enable_update_checker_helper=Периодически проверяет наличие новых версий, подключаясь к gitea.io.
|
||||
enable_update_checker_helper=Периодически проверяет наличие новых версий, подключаясь к devstar.cn.
|
||||
env_config_keys=Настройка окружения
|
||||
env_config_keys_prompt=Следующие переменные окружения также будут применены к вашему конфигурационному файлу:
|
||||
|
||||
|
||||
@@ -134,7 +134,7 @@ license_desc = Go get <a target="_blank" rel="noopener noreferrer" href="%[1]s">
|
||||
[install]
|
||||
install=ස්ථාපනය
|
||||
title=මූලික වින්යාසය
|
||||
docker_helper=ඔබ Docker තුළ Gitea ධාවනය කරන්නේ නම්, කරුණාකර ඕනෑම සැකසුම් වෙනස් කිරීමට පෙර <a target="_blank" rel="noopener noreferrer" href="%s">ලියකියවිලි</a> කියවන්න.
|
||||
k8s_helper=ඔබ Kubernetes තුළ DevStar ධාවනය කරන්නේ නම්, කරුණාකර ඕනෑම සැකසුම් වෙනස් කිරීමට පෙර <a target="_blank" rel="noopener noreferrer" href="%s">ලියකියවිලි</a> කියවන්න.
|
||||
db_title=දත්ත සමුදායේ සැකසුම්
|
||||
db_type=දත්ත සමුදායේ වර්ගය
|
||||
host=සත්කාරක
|
||||
|
||||
@@ -200,8 +200,8 @@ license_desc = Go get <a target="_blank" rel="noopener noreferrer" href="%[1]s">
|
||||
[install]
|
||||
install=Inštalácia
|
||||
title=Východzia konfigurácia
|
||||
docker_helper=Ak spúšťate Gitea v Docker kontajneri, prečítajte si <a target="_blank" rel="noopener noreferrer" href="%s">dokumentáciu</a> pred zmenou akýchkoľvek nastavení.
|
||||
require_db_desc=Gitea vyžaduje MySQL, PostgreSQL, MSSQL, SQLite3 alebo TiDB (MySQL protokol).
|
||||
k8s_helper=Ak spúšťate DevStar v Kubernetes kontajneri, prečítajte si <a target="_blank" rel="noopener noreferrer" href="%s">dokumentáciu</a> pred zmenou akýchkoľvek nastavení.
|
||||
require_db_desc=DevStar vyžaduje MySQL, PostgreSQL, MSSQL, SQLite3 alebo TiDB (MySQL protokol).
|
||||
db_title=Nastavenie databázy
|
||||
db_type=Typ databázy
|
||||
host=Host
|
||||
@@ -303,7 +303,7 @@ password_algorithm=Hašovací algoritmus hesla
|
||||
invalid_password_algorithm=Neplatný hash algoritmus hesla
|
||||
password_algorithm_helper=Nastavte algoritmus hashovania hesla. Algoritmy majú rôzne požiadavky a silu. Algoritmus argon2 je pomerne bezpečný, ale využíva veľa pamäte a môže byť nevhodný pre malé systémy.
|
||||
enable_update_checker=Povoliť kontrolu aktualizácií
|
||||
enable_update_checker_helper=Pravidelne kontroluje nové verzie pripojením k gitea.io.
|
||||
enable_update_checker_helper=Pravidelne kontroluje nové verzie pripojením k devstar.cn.
|
||||
|
||||
[home]
|
||||
uname_holder=Používateľské meno alebo emailová adresa
|
||||
|
||||
@@ -124,7 +124,7 @@ license_desc = Go get <a target="_blank" rel="noopener noreferrer" href="%[1]s">
|
||||
[install]
|
||||
install=Installation
|
||||
title=Ursprunglig konfiguration
|
||||
docker_helper=Om du kör Gitea i Docker, vänligen läs igenom <a target="_blank" rel="noopener noreferrer" href="%s">dokumentationen</a> innan några inställningar ändras.
|
||||
k8s_helper=Om du kör DevStar i Kubernetes, vänligen läs igenom <a target="_blank" rel="noopener noreferrer" href="%s">dokumentationen</a> innan några inställningar ändras.
|
||||
db_title=Databasinställningar
|
||||
db_type=Databastyp
|
||||
host=Server
|
||||
|
||||
@@ -251,8 +251,8 @@ license_desc = Go get <a target="_blank" rel="noopener noreferrer" href="%[1]s">
|
||||
install=Kurulum
|
||||
installing_desc=Şimdi kuruluyor, lütfen bekleyin...
|
||||
title=Başlangıç Yapılandırması
|
||||
docker_helper=Eğer Gitea'yı Docker içerisinde çalıştırıyorsanız, lütfen herhangi bir değişiklik yapmadan önce <a target="_blank" rel="noopener noreferrer" href="%s">belgeleri</a> okuyun.
|
||||
require_db_desc=Gitea MySQL, PostgreSQL, MSSQL, SQLite3 veya TiDB (MySQL protokolü) gerektirir.
|
||||
k8s_helper=Eğer DevStar'yı Kubernetes içerisinde çalıştırıyorsanız, lütfen herhangi bir değişiklik yapmadan önce <a target="_blank" rel="noopener noreferrer" href="%s">belgeleri</a> okuyun.
|
||||
require_db_desc=DevStar MySQL, PostgreSQL, MSSQL, SQLite3 veya TiDB (MySQL protokolü) gerektirir.
|
||||
db_title=Veritabanı Ayarları
|
||||
db_type=Veritabanı Türü
|
||||
host=Sunucu
|
||||
@@ -357,7 +357,7 @@ password_algorithm=Parola Hash Algoritması
|
||||
invalid_password_algorithm=Hatalı parola hash algoritması
|
||||
password_algorithm_helper=Parola hash algoritmasını ayarlayın. Algoritmalar değişen gereksinimlere ve güce sahiptirler. argon2 algoritması iyi özelliklere sahip olmasına rağmen fazla miktarda bellek kullanır ve küçük sistemler için uygun olmayabilir.
|
||||
enable_update_checker=Güncelleme Denetleyicisini Etkinleştir
|
||||
enable_update_checker_helper=Düzenli olarak gitea.io'ya bağlanarak yeni yayınlanan sürümleri denetler.
|
||||
enable_update_checker_helper=Düzenli olarak devstar.cn'ya bağlanarak yeni yayınlanan sürümleri denetler.
|
||||
env_config_keys=Ortam Yapılandırma
|
||||
env_config_keys_prompt=Aşağıdaki ortam değişkenleri de yapılandırma dosyanıza eklenecektir:
|
||||
config_write_file_prompt=Bu yapılandırma seçenekleri şuraya yazılacak: %s
|
||||
|
||||
@@ -246,8 +246,8 @@ license_desc = Go get <a target="_blank" rel="noopener noreferrer" href="%[1]s">
|
||||
install=Встановлення
|
||||
installing_desc=Встановлення, будь ласка, зачекайте...
|
||||
title=Початкова конфігурація
|
||||
docker_helper=Якщо ви запускаєте Gitea у Docker, будь ласка, прочитайте <a target="_blank" rel="noopener noreferrer" href="%s">документацію</a> перед тим, як змінювати будь-які налаштування.
|
||||
require_db_desc=Gitea потребує MySQL, PostgreSQL, MSSQL, SQLite3 або TiDB (протокол MySQL).
|
||||
k8s_helper=Якщо ви запускаєте DevStar у Kubernetes, будь ласка, прочитайте <a target="_blank" rel="noopener noreferrer" href="%s">документацію</a> перед тим, як змінювати будь-які налаштування.
|
||||
require_db_desc=DevStar потребує MySQL, PostgreSQL, MSSQL, SQLite3 або TiDB (протокол MySQL).
|
||||
db_title=Налаштування бази даних
|
||||
db_type=Тип бази даних
|
||||
host=Хост
|
||||
@@ -352,7 +352,7 @@ password_algorithm=Алгоритм хешування пароля
|
||||
invalid_password_algorithm=Недійсний хеш-алгоритм пароля
|
||||
password_algorithm_helper=Встановіть алгоритм хешування пароля. Алгоритми мають різні вимоги та стійкість. Алгоритм argon2 є досить безпечним, але використовує багато пам'яті і може бути недоречним для малих систем.
|
||||
enable_update_checker=Увімкнути перевірку оновлень
|
||||
enable_update_checker_helper=Періодично перевіряти наявність нових версій, підключаючись до gitea.io.
|
||||
enable_update_checker_helper=Періодично перевіряти наявність нових версій, підключаючись до devstar.cn.
|
||||
env_config_keys=Конфігурація середовища
|
||||
env_config_keys_prompt=Наступні змінні середовища також будуть застосовані до вашого файлу конфігурації:
|
||||
config_write_file_prompt=Ці параметри будуть записані в: %s
|
||||
|
||||
@@ -251,8 +251,8 @@ license_desc=所有的代码都开源在 <a target="_blank" rel="noopener norefe
|
||||
install=安装页面
|
||||
installing_desc=正在安装,请稍候...
|
||||
title=初始配置
|
||||
docker_helper=如果您正在使用 Docker 容器运行 Gitea,请务必先仔细阅读 <a target="_blank" rel="noopener noreferrer" href="%s">官方文档</a> 后再对本页面进行填写。
|
||||
require_db_desc=Gitea 需要使用 MySQL、PostgreSQL、MSSQL、SQLite3 或 TiDB (MySQL协议) 等数据库
|
||||
k8s_helper=如果您正在使用 Kubernetes 运行 DevStar,请务必先仔细阅读 <a target="_blank" rel="noopener noreferrer" href="%s">官方文档</a> 后再对本页面进行填写。
|
||||
require_db_desc=DevStar 需要使用 MySQL、PostgreSQL、MSSQL、SQLite3 或 TiDB (MySQL协议) 等数据库
|
||||
db_title=数据库设置
|
||||
db_type=数据库类型
|
||||
host=数据库主机
|
||||
@@ -357,7 +357,11 @@ invalid_log_root_path=日志路径无效: %v
|
||||
default_keep_email_private=默认情况下隐藏邮箱地址
|
||||
default_keep_email_private_popup=默认情况下,隐藏新用户帐户的邮箱地址。
|
||||
default_allow_create_organization=默认情况下允许创建组织
|
||||
default_allow_create_devcontainer=默认情况下允许创建容器
|
||||
default_allow_create_actrunner=默认情况下允许创建工作流运行器
|
||||
default_allow_create_organization_popup=默认情况下, 允许新用户帐户创建组织。
|
||||
default_allow_create_devcontainer_popup=默认情况下, 允许新用户帐户创建容器。
|
||||
default_allow_create_actrunner_popup=默认情况下, 允许新用户帐户创建工作流运行器。
|
||||
default_enable_timetracking=默认情况下启用时间跟踪
|
||||
default_enable_timetracking_popup=默认情况下启用新仓库的时间跟踪。
|
||||
no_reply_address=隐藏邮件域
|
||||
@@ -366,7 +370,7 @@ password_algorithm=密码哈希算法
|
||||
invalid_password_algorithm=无效的密码哈希算法
|
||||
password_algorithm_helper=设置密码散列算法。算法有不同的要求和强度。 argon2 算法相当安全,但使用大量内存,因此可能不适合小型系统。
|
||||
enable_update_checker=启用更新检查
|
||||
enable_update_checker_helper=通过连接到 gitea.io 定期检查新版本发布。
|
||||
enable_update_checker_helper=通过连接到 devstar.cn 定期检查新版本发布。
|
||||
env_config_keys=环境配置
|
||||
env_config_keys_prompt=以下环境变量也将应用于您的配置文件:
|
||||
config_write_file_prompt=这些配置选项将写入以下位置: %s
|
||||
@@ -732,6 +736,7 @@ avatar=头像设置
|
||||
ssh_gpg_keys=SSH / GPG 密钥
|
||||
social=社交帐号绑定
|
||||
applications=应用
|
||||
appstore=应用商店
|
||||
orgs=管理组织
|
||||
repos=仓库列表
|
||||
dev_containers_list = 开发容器列表
|
||||
@@ -3150,6 +3155,7 @@ users.allow_git_hook_tooltip=Git 钩子将会以操作系统用户运行,拥
|
||||
users.allow_import_local=允许导入本地仓库
|
||||
users.allow_create_organization=允许创建组织
|
||||
users.allow_create_devcontainer=允许创建开发容器
|
||||
users.allow_create_actrunner=允许创建工作流运行器
|
||||
users.update_profile=更新帐户
|
||||
users.delete_account=删除帐户
|
||||
users.cannot_delete_self=您不能删除自己
|
||||
@@ -3408,6 +3414,8 @@ config.active_code_lives=激活用户链接有效期
|
||||
config.reset_password_code_lives=恢复账户验证码过期时间
|
||||
config.default_keep_email_private=默认隐藏邮箱地址
|
||||
config.default_allow_create_organization=默认情况下允许创建组织
|
||||
config.default_allow_create_devcontainer=默认情况下允许创建 DevContainer
|
||||
config.default_allow_create_actrunner=默认情况下允许创建 ActRunner
|
||||
config.enable_timetracking=启用时间跟踪
|
||||
config.default_enable_timetracking=默认情况下启用时间跟踪
|
||||
config.default_allow_only_contributors_to_track_time=仅允许成员跟踪时间
|
||||
@@ -3865,7 +3873,7 @@ status.blocked=阻塞中
|
||||
runners=运行器
|
||||
runners.runner_manage_panel=运行器管理
|
||||
runners.new=创建新运行器
|
||||
runners.new_notice=如何启动一个运行器
|
||||
runners.new_notice=如何手动启动一个运行器
|
||||
runners.status=状态
|
||||
runners.id=ID
|
||||
runners.name=名称
|
||||
@@ -3979,3 +3987,41 @@ executable_file=可执行文件
|
||||
symbolic_link=符号链接
|
||||
submodule=子模块
|
||||
|
||||
[appstore]
|
||||
title=应用商店
|
||||
description=浏览和安装各种应用程序
|
||||
install=安装
|
||||
uninstall=卸载
|
||||
configure=配置
|
||||
install_success=应用「%s」安装成功
|
||||
uninstall_success=应用「%s」卸载成功
|
||||
configure_success=应用「%s」配置已更新
|
||||
no_apps=暂无可用应用
|
||||
search_placeholder=搜索应用...
|
||||
category_all=所有分类
|
||||
category_web_server=Web服务器
|
||||
category_database=数据库
|
||||
category_development=开发工具
|
||||
category_monitoring=监控工具
|
||||
category_other=其他
|
||||
deployment_type=部署类型
|
||||
deployment_docker=Docker
|
||||
deployment_kubernetes=Kubernetes
|
||||
requirements=系统要求
|
||||
version=版本
|
||||
author=作者
|
||||
license=许可证
|
||||
website=网站
|
||||
repository=仓库
|
||||
description=描述
|
||||
tags=标签
|
||||
install_config=安装配置
|
||||
config_schema=配置模式
|
||||
default_values=默认值
|
||||
user_values=用户配置
|
||||
merged_config=合并配置
|
||||
validation_error=配置验证错误
|
||||
required_field=必填字段
|
||||
invalid_value=无效值
|
||||
out_of_range=超出范围
|
||||
invalid_format=格式无效
|
||||
@@ -243,8 +243,8 @@ license_desc=所有的代码都开源在 <a target="_blank" rel="noopener norefe
|
||||
[install]
|
||||
install=安裝頁面
|
||||
title=初始組態
|
||||
docker_helper=如果您在 Docker 中執行 Gitea,請先閱讀<a target="_blank" rel="noopener noreferrer" href="%s">安裝指南</a>再來調整設定。
|
||||
require_db_desc=Gitea 需要 MySQL、PostgreSQL、SQLite3、MSSQL、TiDB (MySQL 協定) 等其中一項。
|
||||
k8s_helper=如果您在 Kubernetes 中執行 DevStar,請先閱讀<a target="_blank" rel="noopener noreferrer" href="%s">安裝指南</a>再來調整設定。
|
||||
require_db_desc=DevStar 需要 MySQL、PostgreSQL、SQLite3、MSSQL、TiDB (MySQL 協定) 等其中一項。
|
||||
db_title=資料庫設定
|
||||
db_type=資料庫類型
|
||||
host=主機
|
||||
@@ -349,7 +349,7 @@ password_algorithm=密碼雜湊演算法
|
||||
invalid_password_algorithm=無效的密碼雜湊演算法
|
||||
password_algorithm_helper=設定密碼雜湊演算法。演算法有不同的需求與強度。argon2 演算法雖然較安全但會使用大量記憶體,可能不適用於小型系統。
|
||||
enable_update_checker=啟用更新檢查器
|
||||
enable_update_checker_helper=定期連線到 gitea.io 檢查更新。
|
||||
enable_update_checker_helper=定期連線到 devstar.cn 檢查更新。
|
||||
env_config_keys=環境組態設定
|
||||
env_config_keys_prompt=下列環境變數也會套用到您的組態檔:
|
||||
config_write_file_prompt=這些配置選項將被寫入到: %s
|
||||
|
||||
@@ -86,12 +86,19 @@ function install {
|
||||
sudo docker pull devstar.cn/devstar/$IMAGE_NAME:$VERSION
|
||||
IMAGE_REGISTRY_USER=devstar.cn/devstar
|
||||
fi
|
||||
if sudo docker pull devstar.cn/devstar/webterminal:latest; then
|
||||
success "Successfully pulled devstar.cn/devstar/webterminal:latest"
|
||||
else
|
||||
sudo docker pull mengning997/webterminal:latest
|
||||
success "Successfully pulled mengning997/webterminal:latest renamed to devstar.cn/devstar/webterminal:latest"
|
||||
if sudo docker pull mengning997/webterminal:latest; then
|
||||
sudo docker tag mengning997/webterminal:latest devstar.cn/devstar/webterminal:latest
|
||||
success "Successfully pulled mengning997/webterminal:latest renamed to devstar.cn/devstar/webterminal:latest"
|
||||
else
|
||||
sudo docker pull devstar.cn/devstar/webterminal:latest
|
||||
success "Successfully pulled devstar.cn/devstar/webterminal:latest"
|
||||
fi
|
||||
if sudo docker pull mengning997/act_runner:latest; then
|
||||
sudo docker tag mengning997/act_runner:latest devstar.cn/devstar/act_runner:latest
|
||||
success "Successfully pulled mengning997/act_runner:latest renamed to devstar.cn/devstar/act_runner:latest"
|
||||
else
|
||||
sudo docker pull devstar.cn/devstar/act_runner:latest
|
||||
success "Successfully pulled devstar.cn/devstar/act_runner:latest"
|
||||
fi
|
||||
}
|
||||
|
||||
@@ -137,7 +144,13 @@ function stop {
|
||||
fi
|
||||
if [ $(docker ps -a --filter "name=^/devstar-studio$" -q | wc -l) -gt 0 ]; then
|
||||
sudo docker stop devstar-studio && sudo docker rm -f devstar-studio
|
||||
fi
|
||||
fi
|
||||
if [ $(docker ps -a --filter "name=^/webterminal-" -q | wc -l) -gt 0 ]; then
|
||||
sudo docker stop $(docker ps -a --filter "name=^/webterminal-" -q) && sudo docker rm -f $(docker ps -a --filter "name=^/webterminal-" -q)
|
||||
fi
|
||||
if [ $(docker ps -a --filter "name=^/runner-" -q | wc -l) -gt 0 ]; then
|
||||
sudo docker stop $(docker ps -a --filter "name=^/runner-" -q) && sudo docker rm -f $(docker ps -a --filter "name=^/runner-" -q)
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to logs
|
||||
|
||||
1
public/assets/version.json
Normal file
@@ -0,0 +1 @@
|
||||
{"latest":{"version":"1.25.1"}}
|
||||
@@ -246,6 +246,7 @@ func EditUser(ctx *context.APIContext) {
|
||||
MaxRepoCreation: optional.FromPtr(form.MaxRepoCreation),
|
||||
AllowCreateOrganization: optional.FromPtr(form.AllowCreateOrganization),
|
||||
AllowCreateDevcontainer: optional.FromPtr(form.AllowCreateDevcontainer),
|
||||
AllowCreateActRunner: optional.FromPtr(form.AllowCreateActRunner),
|
||||
IsRestricted: optional.FromPtr(form.Restricted),
|
||||
}
|
||||
|
||||
|
||||
@@ -974,6 +974,7 @@ func Routes() *web.Router {
|
||||
m.Get("/version", misc.Version)
|
||||
m.Get("/signing-key.gpg", misc.SigningKeyGPG)
|
||||
m.Get("/signing-key.pub", misc.SigningKeySSH)
|
||||
m.Get("/appstore/apps", AppStorePublicApps)
|
||||
m.Post("/markup", reqToken(), bind(api.MarkupOption{}), misc.Markup)
|
||||
m.Post("/markdown", reqToken(), bind(api.MarkdownOption{}), misc.Markdown)
|
||||
m.Post("/markdown/raw", reqToken(), misc.MarkdownRaw)
|
||||
|
||||
29
routers/api/v1/appstore_app.go
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (c) Mengning Software. 2025. All rights reserved.
|
||||
* Authors: DevStar Team, panshuxiao
|
||||
* Create: 2025-11-19
|
||||
* Description: REST API handlers for AppStore resources.
|
||||
*/
|
||||
|
||||
package v1
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/services/appstore"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
)
|
||||
|
||||
// AppStorePublicApps 返回公开的应用列表(供外部同步/拉取)
|
||||
func AppStorePublicApps(ctx *context.APIContext) {
|
||||
// 将 APIContext 转换为 Context
|
||||
webCtx := &context.Context{Base: ctx.Base}
|
||||
// 对于公开API,使用0作为用户ID
|
||||
manager := appstore.NewManager(webCtx, 0)
|
||||
apps, err := manager.ListApps()
|
||||
if err != nil {
|
||||
ctx.JSON(http.StatusInternalServerError, map[string]interface{}{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
ctx.JSON(http.StatusOK, map[string]interface{}{"apps": apps})
|
||||
}
|
||||
@@ -155,6 +155,8 @@ func Install(ctx *context.Context) {
|
||||
form.RequireSignInView = setting.Service.RequireSignInViewStrict
|
||||
form.DefaultKeepEmailPrivate = setting.Service.DefaultKeepEmailPrivate
|
||||
form.DefaultAllowCreateOrganization = setting.Service.DefaultAllowCreateOrganization
|
||||
form.DefaultAllowCreateDevcontainer = setting.Service.DefaultAllowCreateDevcontainer
|
||||
form.DefaultAllowCreateActRunner = setting.Service.DefaultAllowCreateActRunner
|
||||
form.DefaultEnableTimetracking = setting.Service.DefaultEnableTimetracking
|
||||
form.NoReplyAddress = setting.Service.NoReplyAddress
|
||||
form.PasswordAlgorithm = hash.ConfigHashAlgorithm(setting.PasswordHashAlgo)
|
||||
@@ -490,6 +492,8 @@ func SubmitInstall(ctx *context.Context) {
|
||||
cfg.Section("service").Key("REQUIRE_SIGNIN_VIEW").SetValue(strconv.FormatBool(form.RequireSignInView))
|
||||
cfg.Section("service").Key("DEFAULT_KEEP_EMAIL_PRIVATE").SetValue(strconv.FormatBool(form.DefaultKeepEmailPrivate))
|
||||
cfg.Section("service").Key("DEFAULT_ALLOW_CREATE_ORGANIZATION").SetValue(strconv.FormatBool(form.DefaultAllowCreateOrganization))
|
||||
cfg.Section("service").Key("DEFAULT_ALLOW_CREATE_DEVCONTAINER").SetValue(strconv.FormatBool(form.DefaultAllowCreateDevcontainer))
|
||||
cfg.Section("service").Key("DEFAULT_ALLOW_CREATE_ACTRUNNER").SetValue(strconv.FormatBool(form.DefaultAllowCreateActRunner))
|
||||
cfg.Section("service").Key("DEFAULT_ENABLE_TIMETRACKING").SetValue(strconv.FormatBool(form.DefaultEnableTimetracking))
|
||||
cfg.Section("service").Key("NO_REPLY_ADDRESS").SetValue(form.NoReplyAddress)
|
||||
cfg.Section("cron.update_checker").Key("ENABLED").SetValue(strconv.FormatBool(form.EnableUpdateChecker))
|
||||
@@ -635,11 +639,17 @@ func SubmitInstall(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
}
|
||||
// 注册Global Runners
|
||||
if setting.Runner.AutoStart {
|
||||
for i := 0; i < setting.Runner.Count; i++ {
|
||||
runners_service.RegistGlobalRunner(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
setting.ClearEnvConfigKeys()
|
||||
log.Info("First-time run install finished!")
|
||||
InstallDone(ctx)
|
||||
|
||||
|
||||
go func() {
|
||||
// Sleep for a while to make sure the user's browser has loaded the post-install page and its assets (images, css, js)
|
||||
// What if this duration is not long enough? That's impossible -- if the user can't load the simple page in time, how could they install or use Gitea in the future ....
|
||||
@@ -653,8 +663,6 @@ func SubmitInstall(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
}
|
||||
runners_service.RegistGlobalRunner(otherCtx)
|
||||
|
||||
// Now get the http.Server from this request and shut it down
|
||||
// NB: This is not our hammerable graceful shutdown this is http.Server.Shutdown
|
||||
srv := ctx.Value(http.ServerContextKey).(*http.Server)
|
||||
|
||||
24
routers/web/admin/appstore.go
Normal file
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (c) Mengning Software. 2025. All rights reserved.
|
||||
* Authors: DevStar Team, panshuxiao
|
||||
* Create: 2025-11-19
|
||||
* Description: Admin portal handlers for AppStore management.
|
||||
*/
|
||||
|
||||
package admin
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"code.gitea.io/gitea/modules/templates"
|
||||
"code.gitea.io/gitea/services/context"
|
||||
)
|
||||
|
||||
const tplAdminAppStore templates.TplName = "admin/appstore"
|
||||
|
||||
// AdminAppStore renders the App Store page in admin console
|
||||
func AdminAppStore(ctx *context.Context) {
|
||||
ctx.Data["Title"] = ctx.Tr("admin.appstore")
|
||||
ctx.Data["PageIsAdminAppStore"] = true
|
||||
ctx.HTML(http.StatusOK, tplAdminAppStore)
|
||||
}
|
||||