diff --git a/README_ZH.md b/README_ZH.md index 669c4275fd..4265490076 100644 --- a/README_ZH.md +++ b/README_ZH.md @@ -3,49 +3,61 @@ DevStar Studio 是 Gitea 发行版 ## 1. 快速开始 -编译、打包成为镜像:代码目录执行 `make docker` 命令 +### 1.1 单机版 -### 1.1 单机版部署 +#### 1.1.1 部署 -使用docker-compose部署DevStar Studio需要参考官方网站: -https://docs.gitea.com/zh-cn/next/installation/install-with-docker-rootless +**Linux部署** + +- 数据库准备(可选,可使用sqlite)参考网址:https://docs.gitea.com/zh-cn/installation/database-prep +- 环境准备:docker、git +- 下载Linux可执行文件压缩包(-linux) **tar -xzvf** 解压缩得到可执行文件,Linux部署命令参考网址:https://docs.gitea.com/zh-cn/installation/install-from-binary + +**Docker镜像部署** + +- 数据库准备(可选,可使用sqlite)参考网址:https://docs.gitea.com/zh-cn/installation/database-prep +- 环境准备:docker、git +- 下载镜像文件压缩包(-docker) **tar -xzvf** 解压缩得到镜像文件 -下列以SQLite db本地最小安装为例,首先初次启动,复制配置信息: ```bash -docker run \ - --restart=always \ - --name devstar-studio \ - -d \ - -p 3000:3000 \ - devstar-studio:latest +# 加载镜像 +docker load -i <镜像文件名> + +# Windows: docker desktop需打开Expose daemon on tcp://localhost:2375 without TLS +docker run --restart=always --name <容器名> -d -p 3000:3000 devstar-studio:latest + + +# linux: docker运行容器,后面修改linux主机 /var/run/docker.sock 权限为 666 +docker run --restart=always --name <容器名> -d -p 3000:3000 -v /var/run/docker.sock:/var/run/docker.sock devstar-studio:latest +sudo chmod 666 /var/run/docker.sock ``` -浏览器打开 http://localhost:3000,执行安装操作,记录下列路径名记录下来,准备后续映射文件夹 -需要复制的配置信息如下: -- 数据库文件路径名字 -- 仓库根路径名字 -- LFS根目录名字 -- 日志路径名字 +#### 1.1.2 安装和配置 + + **!先运行并通过首页安装,完成后再进行配置!** + + -将容器内 `/etc/gitea/app.ini` 复制出来,参考[官网文档](https://docs.gitea.com/administration/config-cheat-sheet) 进行自定义 ```bash +# linux docker 需要把配置文件复制出来修改,Windows: docker desktop在容器界面-file找到/etc/gitea/app.ini修改 docker cp devstar-studio:/etc/gitea/app.ini ./app.ini +docker cp ./app.ini devstar-studio:/etc/gitea/app.ini ``` -然后 添加/修改 下列小节键: -```ini -[wechat] -WECHAT_OFFICIAL_ACCOUNT_TEMP_QR_EXPIRE_SECONDS=60 -WECHAT_OFFICIAL_ACCOUNT_APP_ID=<微信公众号APPID> -WECHAT_OFFICIAL_ACCOUNT_APP_SECRET=<微信公众号SECRET> -WECHAT_OFFICIAL_ACCOUNT_MESSAGE_TOKEN = <微信公众号自定义Token> -WECHAT_OFFICIAL_ACCOUNT_MESSAGE_AES_KEY = <微信公众号AES加密密钥> +添加/修改 下列小节键: +```ini [database] CHARSET_COLLATION = utf8mb4_bin +[devstar.ssh_key_pair] +KEY_SIZE = <写入希望生成的SSH密钥长度,比如 4096 ,默认值 2048> + [cors] -CONTENT_SECURITY_POLICY = default-src 'self' data: 'unsafe-inline' https://mp.weixin.qq.com; img-src * data: +ENABLED = true +ALLOW_DOMAIN = * +METHODS = GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS +MAX_AGE = 10m [ui.admin] ;; Dev Container 分页参数(每页展示 DevContainer 个数),若未指定,默认值 50 @@ -55,28 +67,21 @@ DEV_CONTAINERS_PAGING_NUM = 50 ENABLED = true AGENT = docker TIMEOUT_SECONDS = 120 -HOST = 127.0.0.1 - -[devstar.ssh_key_pair] -KEY_SIZE = <写入希望生成的SSH密钥长度,比如 4096 ,默认值 2048> +;; 修改为部署host地址 +HOST = 192.168.88.88 +;; docker desktop必须添加,linux docker可删除 +DOCKER_HOST = tcp://host.docker.internal:2375 ``` -正式部署单机版 -```bash -docker stop devstar-studio && docker rm devstar-studio +#### 1.1.3 单机版使用 + +1. 添加publickey:用户-设置-SSH / GPG 密钥-管理 SSH 密钥,windows一般在C:/用户/用户名/.ssh目录下 +2. 创建仓库,创建.devcontainer/devcontainer.json,并且 **添加 image 字段**,例如:"image":"mcr.microsoft.com/devcontainers/base:dev-ubuntu-20.04" +3. 仓库内-开发容器-创建开发容器 + +```bash -docker run \ - --restart=always \ - --name devstar-studio \ - -d \ - -p 3000:3000 \ - -p 2222:2222 \ - -v 本地数据库文件路径:容器内数据库文件路径 \ - -v 本地仓库根路径:容器内仓库根路径 \ - -v 本地LFS根目录:容器内LFS路径 \ - -v 本地日志路径:容器内日志路径 \ - devstar-studio:latest ``` ### 1.2 Kubernetes 集群部署 diff --git a/go.mod b/go.mod index 1486219718..6b6813c5a1 100644 --- a/go.mod +++ b/go.mod @@ -30,6 +30,8 @@ require ( github.com/dimiro1/reply v0.0.0-20200315094148-d0136a4c9e21 github.com/djherbis/buffer v1.2.0 github.com/djherbis/nio/v3 v3.0.1 + github.com/docker/docker v24.0.7+incompatible + github.com/docker/go-connections v0.4.0 github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 github.com/dustin/go-humanize v1.0.1 github.com/editorconfig/editorconfig-core-go/v2 v2.6.2 @@ -131,6 +133,14 @@ require ( xorm.io/xorm v1.3.8 ) +require ( + github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect + github.com/distribution/reference v0.5.0 // indirect + github.com/docker/distribution v2.8.3+incompatible // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/morikuni/aec v1.0.0 // indirect +) + require ( github.com/ArtisanCloud/PowerSocialite/v3 v3.0.7 // indirect github.com/clbanning/mxj/v2 v2.7.0 // indirect diff --git a/go.sum b/go.sum index b28787e1ac..43d9dd1434 100644 --- a/go.sum +++ b/go.sum @@ -94,6 +94,8 @@ github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0 h1:D3occ github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0/go.mod h1:bTSOgj05NGRuHHhQwAdPnYr9TOdNmKlZTgGLL6nyAdI= github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.3.2 h1:YUUxeiOWgdAQE3pXt2H7QXzZs0q8UBjgRbl56qo8GYM= github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.3.2/go.mod h1:dmXQgZuiSubAecswZE+Sm8jkvEa7kQgTPVRvwL/nd0E= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= @@ -289,6 +291,8 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dimiro1/reply v0.0.0-20200315094148-d0136a4c9e21 h1:PdsjTl0Cg+ZJgOx/CFV5NNgO1ThTreqdgKYiDCMHJwA= github.com/dimiro1/reply v0.0.0-20200315094148-d0136a4c9e21/go.mod h1:xJvkyD6Y2rZapGvPJLYo9dyx1s5dxBEDPa8T3YTuOk0= +github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= +github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/djherbis/buffer v1.1.0/go.mod h1:VwN8VdFkMY0DCALdY8o00d3IZ6Amz/UNVMWcSaJT44o= github.com/djherbis/buffer v1.2.0 h1:PH5Dd2ss0C7CRRhQCZ2u7MssF+No9ide8Ye71nPHcrQ= github.com/djherbis/buffer v1.2.0/go.mod h1:fjnebbZjCUpPinBRD+TDwXSOeNQ7fPQWLfGQqiAiUyE= @@ -299,6 +303,14 @@ github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55k github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= +github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM= +github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 h1:iFaUwBSo5Svw6L7HYpRu/0lE3e0BaElwnNO1qkNQxBY= github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s= @@ -738,12 +750,16 @@ github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= +github.com/moby/term v0.0.0-20200312100748-672ec06f55cd h1:aY7OQNf2XqY/JQ6qREWamhI/81os/agb2BAGpcx5yWI= +github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450 h1:j2kD3MT1z4PXCiUllUJF9mWUESr9TWKS7iEKsQ/IipM= github.com/mrjones/oauth v0.0.0-20190623134757-126b35219450/go.mod h1:skjdDftzkFALcuGzYSklqYd8gvat6F1gZJ4YPVbkZpM= github.com/mschoch/smat v0.0.0-20160514031455-90eadee771ae/go.mod h1:qAyveg+e4CE+eKJXWVjKXM4ck2QobLqTDytGJbLLhJg= @@ -1202,6 +1218,7 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -1455,6 +1472,8 @@ gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= +gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/modules/setting/devstar_devcontainer.go b/modules/setting/devstar_devcontainer.go index dd46408e7f..5eb9f03802 100644 --- a/modules/setting/devstar_devcontainer.go +++ b/modules/setting/devstar_devcontainer.go @@ -59,6 +59,7 @@ type DevcontainerType struct { DefaultGitBranchName string DefaultDevcontainerImageName string + DockerHost string } type SSHKeyPairType struct { diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index be67ec1695..e37d8c051a 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -727,6 +727,15 @@ func apiAuth(authMethod auth.Method) func(*context.APIContext) { // verifyAuthWithOptions checks authentication according to options func verifyAuthWithOptions(options *common.VerifyOptions) func(ctx *context.APIContext) { return func(ctx *context.APIContext) { + // 检查是否为 OPTIONS 请求 + if ctx.Req.Method == "OPTIONS" { + // 设置 CORS 响应头 + ctx.Resp.Header().Set("Access-Control-Allow-Origin", "*") + ctx.Resp.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE") + ctx.Resp.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization") + ctx.Resp.WriteHeader(http.StatusOK) + return + } // Check prohibit login users. if ctx.IsSigned { if !ctx.Doer.IsActive && setting.Service.RegisterEmailConfirm { diff --git a/routers/web/goget.go b/routers/web/goget.go index 8d5612ebfe..ef3692114c 100644 --- a/routers/web/goget.go +++ b/routers/web/goget.go @@ -18,6 +18,15 @@ import ( ) func goGet(ctx *context.Context) { + // 检查是否为 OPTIONS 请求 + if ctx.Req.Method == "OPTIONS" { + // 设置 CORS 响应头 + ctx.Resp.Header().Set("Access-Control-Allow-Origin", "*") + ctx.Resp.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE") + ctx.Resp.Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization") + ctx.Resp.WriteHeader(http.StatusOK) + return + } if ctx.Req.Method != "GET" || len(ctx.Req.URL.RawQuery) < 8 || ctx.FormString("go-get") != "1" { return } diff --git a/services/devstar_devcontainer/OpenDevcontainerAbstractAgentService.go b/services/devstar_devcontainer/OpenDevcontainerAbstractAgentService.go index 2425e987ee..e6e4aba436 100644 --- a/services/devstar_devcontainer/OpenDevcontainerAbstractAgentService.go +++ b/services/devstar_devcontainer/OpenDevcontainerAbstractAgentService.go @@ -40,7 +40,8 @@ func OpenDevcontainerService(ctx *gitea_web_context.Context, opts *devcontainer_ } } openDevcontainerAbstractAgentVO.NodePortAssigned = devcontainerApp.Status.NodePortAssigned - + case setting.DEVCONTAINER_AGENT_NAME_DOCKER: + openDevcontainerAbstractAgentVO.NodePortAssigned = opts.Port default: return nil, devcontainer_service_errors.ErrOperateDevcontainer{ Action: "Open DevContainer", diff --git a/services/devstar_devcontainer/RepoDevcontainerAbstractAgentService.go b/services/devstar_devcontainer/RepoDevcontainerAbstractAgentService.go index d89953854a..10ecd451fc 100644 --- a/services/devstar_devcontainer/RepoDevcontainerAbstractAgentService.go +++ b/services/devstar_devcontainer/RepoDevcontainerAbstractAgentService.go @@ -1,6 +1,7 @@ package devstar_devcontainer import ( + "code.gitea.io/gitea/services/devstar_devcontainer/docker_agent" "context" "encoding/json" "fmt" @@ -276,6 +277,8 @@ func purgeDevcontainersResource(ctx *context.Context, devcontainersList *[]devst switch setting.Devstar.Devcontainer.Agent { case setting.DEVCONTAINER_AGENT_NAME_K8S: return devcontainer_k8s_agent_service.AssignDevcontainerDeletion2K8sOperator(ctx, devcontainersList) + case setting.DEVCONTAINER_AGENT_NAME_DOCKER: + return docker_agent.AssignDevcontainerDeletionDockerOperator(ctx, devcontainersList) default: // 未知 Agent,直接报错 return devstar_devcontainer_models.ErrFailedToOperateDevstarDevcontainerDB{ @@ -300,6 +303,8 @@ func claimDevcontainerResource(ctx *context.Context, newDevContainer *devcontain case setting.DEVCONTAINER_AGENT_NAME_K8S: // k8s Operator return devcontainer_k8s_agent_service.AssignDevcontainerCreation2K8sOperator(ctx, newDevContainer) + case setting.DEVCONTAINER_AGENT_NAME_DOCKER: + return docker_agent.AssignDevcontainerCreationDockerOperator(ctx, newDevContainer) default: // 未知 Agent,直接报错 return devstar_devcontainer_models.ErrFailedToOperateDevstarDevcontainerDB{ diff --git a/services/devstar_devcontainer/api_services/OpenDevcontainerAPIService.go b/services/devstar_devcontainer/api_services/OpenDevcontainerAPIService.go index 98ac8472d2..f37dc6a9da 100644 --- a/services/devstar_devcontainer/api_services/OpenDevcontainerAPIService.go +++ b/services/devstar_devcontainer/api_services/OpenDevcontainerAPIService.go @@ -55,6 +55,7 @@ func OpenDevcontainerAPIService(ctx *gitea_web_context.Context, opts *devcontain // 2. 调用抽象层获取 DevContainer 最新状态(需要根据用户传入的 wait 参数决定是否要阻塞等待 DevContainer 就绪) optsOpenDevcontainer := &devcontainer_service_options.OpenDevcontainerAppDispatcherOptions{ Name: devcontainerDetails.DevContainerName, + Port: devcontainerDetails.DevContainerPort, Wait: opts.Wait, } openDevcontainerAbstractAgentVO, err := DevcontainersService.OpenDevcontainerService(ctx, optsOpenDevcontainer) diff --git a/services/devstar_devcontainer/docker_agent/AssignDevcontainerCreationDockerOperator.go b/services/devstar_devcontainer/docker_agent/AssignDevcontainerCreationDockerOperator.go new file mode 100644 index 0000000000..792c305dd3 --- /dev/null +++ b/services/devstar_devcontainer/docker_agent/AssignDevcontainerCreationDockerOperator.go @@ -0,0 +1,198 @@ +package docker_agent + +import ( + "context" + "fmt" + "io" + "io/ioutil" + "net/url" + "os" + "strconv" + "strings" + + devcontainer_dto "code.gitea.io/gitea/modules/devstar_devcontainer/k8s_agent/dto" + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" + devcontainer_service_dto "code.gitea.io/gitea/services/devstar_devcontainer/dto" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/client" + "github.com/docker/go-connections/nat" + apimachinery_meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +var commonSocketPaths = []string{ + "/var/run/docker.sock", + "/run/podman/podman.sock", + "$HOME/.colima/docker.sock", + "$XDG_RUNTIME_DIR/docker.sock", + "$XDG_RUNTIME_DIR/podman/podman.sock", + `\\.\pipe\docker_engine`, + "$HOME/.docker/run/docker.sock", +} + +func AssignDevcontainerCreationDockerOperator(ctx *context.Context, newDevContainer *devcontainer_service_dto.CreateDevcontainerDTO) error { + log.Info("Docker create container.....") + // 1. 创建docker client + cli, err := CreateDockerClient(ctx) + defer cli.Close() + if err != nil { + return err + } + + // 2. 创建容器 + opts := &devcontainer_dto.CreateDevcontainerOptions{ + Name: newDevContainer.Name, + CreateOptions: apimachinery_meta_v1.CreateOptions{}, + Image: newDevContainer.Image, + CommandList: []string{ + "sh", + "-c", + "rm -f /etc/ssh/ssh_host_*; ssh-keygen -A ; service ssh start ; while true; do sleep 60; done", + }, + SSHPublicKeyList: newDevContainer.SSHPublicKeyList, + GitRepositoryURL: newDevContainer.GitRepositoryURL, + } + + // 2. 创建成功,取回集群中的 DevContainer + port, err := CreateDevcontainer(ctx, cli, opts) + if err != nil { + return err + } + // 3. 将分配的 NodePort Service 写回 newDevcontainer,供写入数据库进行下一步操作 + uint16Value, _ := strconv.ParseUint(port, 10, 16) + newDevContainer.DevcontainerPort = uint16(uint16Value) + return nil +} + +func CreateDevcontainer(ctx *context.Context, cli *client.Client, opts *devcontainer_dto.CreateDevcontainerOptions) (string, error) { + if ctx == nil || opts == nil { + return "", fmt.Errorf("拉取镜像失败:环境为空") + } + // 拉取镜像 + err := pullImage(cli, opts.Image) + if err != nil { + return "", fmt.Errorf("拉取镜像失败:%v", err) + } + // 创建并启动容器 + port, err := createAndStartContainer(cli, opts) + if err != nil { + return "", fmt.Errorf("创建或启动容器失败:%v", err) + } + return port, nil +} + +// pullImage 用于拉取指定的 Docker 镜像 +func pullImage(cli *client.Client, image string) error { + ctx := context.Background() + resp, err := cli.ImagePull(ctx, image, types.ImagePullOptions{}) + if err != nil { + return err + } + defer resp.Close() + + _, err = io.Copy(os.Stdout, resp) + return err +} + +func createAndStartContainer(cli *client.Client, opts *devcontainer_dto.CreateDevcontainerOptions) (string, error) { + ctx := context.Background() + + // 创建容器配置 + config := &container.Config{ + Image: opts.Image, + Cmd: opts.CommandList, + AttachStdout: true, + AttachStderr: true, + Tty: true, + OpenStdin: true, + ExposedPorts: nat.PortSet{ + "22/tcp": struct{}{}, + }, + } + + // 设置容器主机配置 + hostConfig := &container.HostConfig{ + // PortBindings: nat.PortMap{ + // "22/tcp": []nat.PortBinding{{HostIP: "0.0.0.0", HostPort: strconv.Itoa(int(opts.ServicePort))}}, // 将容器的22端口映射到宿主机的8080端口 + // }, + ExtraHosts: []string{"host.docker.internal:" + setting.Devstar.Devcontainer.Host}, + PublishAllPorts: true, + } + + // 创建容器 + resp, err := cli.ContainerCreate(ctx, config, hostConfig, nil, nil, opts.Name) + if err != nil { + return "", fmt.Errorf("创建或启动容器失败:%v", err) + } + + // 启动容器 + err = cli.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{}) + if err != nil { + return "", fmt.Errorf("创建或启动容器失败:%v", err) + } + // 将公钥数组转换为字符串 + keysString := strings.Join(opts.SSHPublicKeyList, "\n") + log.Info(fmt.Sprintf("keysString %s", keysString)) + // 解析原始 URL + parsedURL, err := url.Parse(opts.GitRepositoryURL) + if err != nil { + fmt.Printf("Error parsing URL: %v\n", err) + return "", fmt.Errorf("error pull code from git: %v", err) + } + // 获取主机和端口 + hostParts := strings.Split(parsedURL.Host, ":") + + port := "" + if len(hostParts) > 1 { + port = hostParts[1] + } + // 修改主机部分 + newHost := "host.docker.internal" + if port != "" { + newHost += ":" + port + } + + // 设置新的主机 + parsedURL.Host = newHost + + // 生成新的 URL + newURL := parsedURL.String() + cmd := []string{ + "sh", "-c", + "if [ ! -d '/data/workspace' ]; then git clone " + newURL + " /data/workspace && echo \"Git Repository cloned.\"; else echo \"Folder already exists.\"; fi; mkdir -p ~/test; mkdir -p ~/.ssh ; chmod 700 ~/.ssh; echo \"" + keysString + "\" > ~/.ssh/authorized_keys ; chmod 600 ~/.ssh/authorized_keys; ", + } + // 创建 exec 实例 + ex, err := cli.ContainerExecCreate(context.Background(), resp.ID, types.ExecConfig{ + Cmd: cmd, + AttachStdout: true, + AttachStderr: true, + }) + if err != nil { + return "", fmt.Errorf("启动执行器失败:%v", err) + } + execResp, err := cli.ContainerExecAttach(context.Background(), ex.ID, types.ExecStartCheck{}) + if err != nil { + return "", fmt.Errorf("执行命令失败:%v", err) + } + output, err := ioutil.ReadAll(execResp.Reader) + log.Info("Command output:\n%s\n", output) + // 获取容器详细信息 + containerJSON, err := cli.ContainerInspect(context.Background(), resp.ID) + if err != nil { + return "", fmt.Errorf("获取容器信息失败:%v", err) + } + + // 获取端口映射信息 + portBindings := containerJSON.NetworkSettings.Ports + for containerPort, bindings := range portBindings { + for _, binding := range bindings { + log.Info("Container Port %s is mapped to Host Port %s on IP %s\n", containerPort, binding.HostPort, binding.HostIP) + if containerPort.Port() == "22" { + return binding.HostPort, nil + } + + } + } + return "", fmt.Errorf("未找到映射的端口") +} diff --git a/services/devstar_devcontainer/docker_agent/AssignDevcontainerDeletionDockerOperator.go b/services/devstar_devcontainer/docker_agent/AssignDevcontainerDeletionDockerOperator.go new file mode 100644 index 0000000000..346a9a57c7 --- /dev/null +++ b/services/devstar_devcontainer/docker_agent/AssignDevcontainerDeletionDockerOperator.go @@ -0,0 +1,50 @@ +package docker_agent + +import ( + devstar_devcontainer_models "code.gitea.io/gitea/models/devstar_devcontainer" + "code.gitea.io/gitea/modules/log" + "context" + "github.com/docker/docker/api/types" +) + +func AssignDevcontainerDeletionDockerOperator(ctx *context.Context, devcontainersList *[]devstar_devcontainer_models.DevstarDevcontainer) error { + log.Info("Docker delete container") + // 1. 创建docker client + cli, err := CreateDockerClient(ctx) + defer cli.Close() + if err != nil { + return err + } + // 获取容器 ID + containers, err := cli.ContainerList(context.Background(), types.ContainerListOptions{All: true}) + if err != nil { + log.Info("获取容器列表失败: %v", err) + } + + var containerIDList []string + for _, c := range containers { + log.Info("containersIDList: %v", c.ID) + for _, name := range c.Names { + log.Info("containerNameList: %v", name) + for _, devcontainer := range *devcontainersList { + log.Info("devcontainerName: %v", devcontainer.Name) + if name == "/"+devcontainer.Name { + containerIDList = append(containerIDList, c.ID) + } + } + } + } + // 删除容器 + options := types.ContainerRemoveOptions{ + Force: true, // 强制删除正在运行的容器 + RemoveVolumes: true, // 删除数据卷 + RemoveLinks: false, // 删除链接(已弃用) + } + for _, id := range containerIDList { + if err := cli.ContainerRemove(context.Background(), id, options); err != nil { + log.Info("删除%s容器失败: %v", id, err) + } + log.Info("容器 %s 已成功删除\n", id) + } + return nil +} diff --git a/services/devstar_devcontainer/docker_agent/DockerUtils.go b/services/devstar_devcontainer/docker_agent/DockerUtils.go new file mode 100644 index 0000000000..8fd742c4af --- /dev/null +++ b/services/devstar_devcontainer/docker_agent/DockerUtils.go @@ -0,0 +1,68 @@ +package docker_agent + +import ( + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" + "context" + "fmt" + "github.com/docker/docker/client" + "os" + "path/filepath" + "strings" +) + +func CreateDockerClient(ctx *context.Context) (*client.Client, error) { + log.Info("检查 Docker 环境") + // 1. 检查 Docker 环境 + dockerSocketPath, err := GetDockerSocketPath() + if err != nil { + return nil, err + } + log.Info("dockerSocketPath: %s", dockerSocketPath) + cli, err := CheckIfDockerRunning(*ctx, dockerSocketPath) + if err != nil { + return nil, err + } + return cli, nil +} + +/* +Docker环境路径优先级: 配置文件、环境变量 +*/ +func GetDockerSocketPath() (string, error) { + if setting.Devstar.Devcontainer.DockerHost != "" { + return setting.Devstar.Devcontainer.DockerHost, nil + } + socket, found := os.LookupEnv("DOCKER_HOST") + if found { + return socket, nil + } + for _, p := range commonSocketPaths { + if _, err := os.Lstat(os.ExpandEnv(p)); err == nil { + if strings.HasPrefix(p, `\\.\`) { + return "npipe://" + filepath.ToSlash(os.ExpandEnv(p)), nil + } + return "unix://" + filepath.ToSlash(os.ExpandEnv(p)), nil + } + } + return "", fmt.Errorf("daemon Docker Engine socket not found and docker_host config was invalid") +} +func CheckIfDockerRunning(ctx context.Context, configDockerHost string) (*client.Client, error) { + opts := []client.Opt{ + client.FromEnv, + } + + if configDockerHost != "" { + opts = append(opts, client.WithHost(configDockerHost)) + } + + cli, err := client.NewClientWithOpts(opts...) + if err != nil { + return nil, err + } + _, err = cli.Ping(ctx) + if err != nil { + return nil, fmt.Errorf("cannot ping the docker daemon, is it running? %w", err) + } + return cli, nil +} diff --git a/services/devstar_devcontainer/options/OpenDevcontainerAppDispatcherOptions.go b/services/devstar_devcontainer/options/OpenDevcontainerAppDispatcherOptions.go index dc984cf485..053678946c 100644 --- a/services/devstar_devcontainer/options/OpenDevcontainerAppDispatcherOptions.go +++ b/services/devstar_devcontainer/options/OpenDevcontainerAppDispatcherOptions.go @@ -3,4 +3,5 @@ package options type OpenDevcontainerAppDispatcherOptions struct { Name string `json:"name"` Wait bool `json:"wait"` + Port uint16 }