This commit is contained in:
init
2025-10-29 22:43:10 +08:00
repo.diff.parent 65ca3716c1
repo.diff.commit e485930124
repo.diff.stats_desc%!(EXTRA int=7, int=219, int=92)

repo.diff.view_file

@@ -37,7 +37,6 @@ 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", "--"]
ENV WEB_TERMINAL_HELLO="Successfully connected to the devcontainer"
CMD ["/home/webTerminal/build/ttyd", "-W", "bash"]
# To acquire devstar.cn/devstar/webterminal:latest:

repo.diff.view_file

@@ -55,12 +55,13 @@ func GetDevContainerDetails(ctx *context.Context) {
ctx.Data["ValidateDevContainerConfiguration"] = false
}
ctx.Data["HasDevContainerDockerfile"], err = devcontainer_service.HasDevContainerDockerFile(ctx, ctx.Repo)
ctx.Data["HasDevContainerDockerfile"], ctx.Data["DockerfilePath"], err = devcontainer_service.HasDevContainerDockerFile(ctx, ctx.Repo)
if err != nil {
log.Info(err.Error())
ctx.Flash.Error(err.Error(), true)
}
if ctx.Data["HasDevContainer"] == true {
if ctx.Data["HasDevContainerConfiguration"] == true {
configurationString, _ := devcontainer_service.GetDevcontainerConfigurationString(ctx, ctx.Repo.Repository)
configurationModel, _ := devcontainer_service.UnmarshalDevcontainerConfigContent(configurationString)
imageName := configurationModel.Image
@@ -69,7 +70,7 @@ func GetDevContainerDetails(ctx *context.Context) {
ctx.Data["RepositoryAddress"] = registry
ctx.Data["RepositoryUsername"] = namespace
ctx.Data["ImageName"] = "dev-" + ctx.Repo.Repository.Name + ":latest"
}
if cfg.Section("k8s").Key("ENABLE").Value() == "true" {
// 获取WebSSH服务端口
webTerminalURL, err := devcontainer_service.GetWebTerminalURL(ctx, ctx.Doer.ID, ctx.Repo.Repository.ID)
@@ -111,7 +112,6 @@ func GetDevContainerDetails(ctx *context.Context) {
}
ctx.Redirect(path.Join(ctx.Repo.RepoLink, "/devcontainer"))
} else {
rootPort, err := devcontainer_service.GetPortFromURL(cfg.Section("server").Key("ROOT_URL").Value())
if err != nil {
ctx.Flash.Error(err.Error(), true)
@@ -136,7 +136,6 @@ func GetDevContainerDetails(ctx *context.Context) {
}
ctx.Data["WebSSHUrl"] = webTerminalURL + "?type=docker&" + terminalParams
}
}
terminalURL, err := devcontainer_service.Get_IDE_TerminalURL(ctx, ctx.Doer, ctx.Repo)
if err == nil {
@@ -299,7 +298,7 @@ func UpdateDevContainer(ctx *context.Context) {
ctx.JSON(http.StatusOK, map[string]string{"message": err.Error()})
return
}
err = devcontainer_service.UpdateDevContainer(ctx, ctx.Doer, ctx.Repo.Repository, &updateInfo)
err = devcontainer_service.UpdateDevContainer(ctx, ctx.Doer, ctx.Repo, &updateInfo)
if err != nil {
ctx.JSON(http.StatusOK, map[string]string{"message": err.Error()})
return

repo.diff.view_file

@@ -11,6 +11,7 @@ import (
"path"
"strings"
"code.gitea.io/gitea/models/db"
git_model "code.gitea.io/gitea/models/git"
"code.gitea.io/gitea/models/issues"
"code.gitea.io/gitea/models/unit"
@@ -25,6 +26,7 @@ import (
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/context"
"code.gitea.io/gitea/services/context/upload"
devcontainer_service "code.gitea.io/gitea/services/devcontainer"
"code.gitea.io/gitea/services/forms"
files_service "code.gitea.io/gitea/services/repository/files"
)
@@ -411,6 +413,23 @@ func DeleteFilePost(ctx *context.Context) {
editorHandleFileOperationError(ctx, parsed.NewBranchName, err)
return
}
log.Info("File deleted: %s", treePath)
if treePath == `.devcontainer/devcontainer.json` {
var userIds []int64
err = db.GetEngine(ctx).
Table("devcontainer").
Select("user_id").
Where("repo_id = ?", ctx.Repo.Repository.ID).
Find(&userIds)
if err != nil {
ctx.ServerError("GetEngine", err)
return
}
for _, userId := range userIds {
devcontainer_service.DeleteDevContainer(ctx, userId, ctx.Repo.Repository.ID)
}
}
ctx.Flash.Success(ctx.Tr("repo.editor.file_delete_success", treePath))
redirectTreePath := getClosestParentWithFiles(ctx.Repo.GitRepo, parsed.NewBranchName, treePath)

repo.diff.view_file

@@ -68,21 +68,21 @@ func HasDevContainerConfiguration(ctx context.Context, repo *gitea_context.Repos
return true, nil
}
}
func HasDevContainerDockerFile(ctx context.Context, repo *gitea_context.Repository) (bool, error) {
func HasDevContainerDockerFile(ctx context.Context, repo *gitea_context.Repository) (bool, string, error) {
_, err := FileExists(".devcontainer/devcontainer.json", repo)
if err != nil {
if git.IsErrNotExist(err) {
return false, nil
return false, "", nil
}
return false, err
return false, "", err
}
configurationString, err := GetDevcontainerConfigurationString(ctx, repo.Repository)
if err != nil {
return false, err
return false, "", err
}
configurationModel, err := UnmarshalDevcontainerConfigContent(configurationString)
if err != nil {
return false, err
return false, "", err
}
// 执行验证
if errs := configurationModel.Validate(); len(errs) > 0 {
@@ -90,20 +90,34 @@ func HasDevContainerDockerFile(ctx context.Context, repo *gitea_context.Reposito
for _, err := range errs {
fmt.Printf(" - %s\n", err.Error())
}
return false, fmt.Errorf("配置格式错误")
return false, "", fmt.Errorf("配置格式错误")
} else {
log.Info("%v", configurationModel)
if configurationModel.Build == nil || configurationModel.Build.Dockerfile == "" {
return false, nil
_, err := FileExists(".devcontainer/Dockerfile", repo)
if err != nil {
if git.IsErrNotExist(err) {
return false, "", nil
}
return false, "", err
}
return true, ".devcontainer/Dockerfile", nil
}
_, err := FileExists(".devcontainer/"+configurationModel.Build.Dockerfile, repo)
if err != nil {
if git.IsErrNotExist(err) {
return false, nil
_, err := FileExists(".devcontainer/Dockerfile", repo)
if err != nil {
if git.IsErrNotExist(err) {
return false, "", nil
}
return false, err
return false, "", err
}
return true, nil
return true, ".devcontainer/Dockerfile", nil
}
return false, "", err
}
return true, ".devcontainer/" + configurationModel.Build.Dockerfile, nil
}
}
func CreateDevcontainerConfiguration(repo *repo.Repository, doer *user.User) error {
@@ -433,7 +447,7 @@ func StopDevContainer(ctx context.Context, userID, repoID int64) error {
return nil
}
func UpdateDevContainer(ctx context.Context, doer *user.User, repo *repo.Repository, updateInfo *UpdateInfo) error {
func UpdateDevContainer(ctx context.Context, doer *user.User, repo *gitea_context.Repository, updateInfo *UpdateInfo) error {
dbEngine := db.GetEngine(ctx)
var devContainerInfo devcontainer_models.Devcontainer
cfg, err := setting.NewConfigProviderFromFile(setting.CustomConf)
@@ -443,13 +457,13 @@ func UpdateDevContainer(ctx context.Context, doer *user.User, repo *repo.Reposit
_, err = dbEngine.
Table("devcontainer").
Select("*").
Where("user_id = ? AND repo_id = ?", doer.ID, repo.ID).
Where("user_id = ? AND repo_id = ?", doer.ID, repo.Repository.ID).
Get(&devContainerInfo)
if err != nil {
return err
}
_, err = dbEngine.Table("devcontainer").
Where("user_id = ? AND repo_id = ? ", doer.ID, repo.ID).
Where("user_id = ? AND repo_id = ? ", doer.ID, repo.Repository.ID).
Update(&devcontainer_models.Devcontainer{DevcontainerStatus: 5})
if err != nil {
return err
@@ -460,7 +474,7 @@ func UpdateDevContainer(ctx context.Context, doer *user.User, repo *repo.Reposit
} else {
updateErr := UpdateDevContainerByDocker(otherCtx, &devContainerInfo, updateInfo, repo, doer)
_, err = dbEngine.Table("devcontainer").
Where("user_id = ? AND repo_id = ? ", doer.ID, repo.ID).
Where("user_id = ? AND repo_id = ? ", doer.ID, repo.Repository.ID).
Update(&devcontainer_models.Devcontainer{DevcontainerStatus: 4})
if err != nil {
return err
@@ -531,13 +545,26 @@ func GetTerminalCommand(ctx context.Context, userID string, repo *repo.Repositor
return "", "", err
}
}
}
break
case 2:
//正在创建容器,创建容器成功,则状态转移
if cfg.Section("k8s").Key("ENABLE").Value() == "true" {
//k8s的逻辑
} else {
exist, _, err := ContainerExists(ctx, devContainerInfo.Name)
if err != nil {
return "", "", err
}
if !exist {
_, err = dbEngine.Table("devcontainer_output").
Select("command").
Where("user_id = ? AND repo_id = ? AND list_id = ?", userID, repo.ID, realTimeStatus).
Get(&cmd)
if err != nil {
return "", "", err
}
} else {
status, err := GetDevContainerStatusFromDocker(ctx, devContainerInfo.Name)
if err != nil {
@@ -583,6 +610,8 @@ func GetTerminalCommand(ctx context.Context, userID string, repo *repo.Repositor
}
}
}
break
case 3:
//正在初始化容器,初始化容器成功,则状态转移
@@ -665,6 +694,16 @@ func GetDevContainerOutput(ctx context.Context, user_id string, repo *repo.Repos
if err != nil {
return "", err
}
if devContainerOutput != "" {
_, err = dbEngine.Table("devcontainer_output").
Where("user_id = ? AND repo_id = ? AND list_id = ?", user_id, repo.ID, 4).
Update(map[string]interface{}{
"output": "",
})
if err != nil {
return "", err
}
}
return devContainerOutput, nil
}
@@ -936,7 +975,6 @@ func GetCommandContent(ctx context.Context, userId int64, repo *repo.Repository)
script = append(script, v)
}
scriptCommand := strings.TrimSpace(strings.Join(script, "\n"))
userCommand := scriptCommand + "\n" + onCreateCommand + "\n" + updateCommand + "\n" + postCreateCommand + "\n" + postStartCommand + "\n"
assetFS := templates.AssetFS()
Content_tmpl, err := assetFS.ReadFile("repo/devcontainer/devcontainer_tmpl.sh")
@@ -988,6 +1026,7 @@ func AddPublicKeyToAllRunningDevContainer(ctx context.Context, userId int64, pub
if err != nil {
return err
}
if len(devcontainerList) > 0 {
// 将公钥写入这些打开的容器中
for _, repoDevContainer := range devcontainerList {

repo.diff.view_file

@@ -16,10 +16,13 @@ import (
"code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/user"
docker_module "code.gitea.io/gitea/modules/docker"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
gitea_context "code.gitea.io/gitea/services/context"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/client"
"github.com/docker/docker/errdefs"
"github.com/docker/go-connections/nat"
@@ -213,7 +216,8 @@ func CreateDevContainerByDockerCommand(ctx context.Context, newDevcontainer *dev
var envFlags string = ` -e RepoLink="` + strings.TrimSuffix(cfg.Section("server").Key("ROOT_URL").Value(), `/`) + repo.Link() + `" ` +
` -e DevstarHost="` + newDevcontainer.DevcontainerHost + `"` +
` -e WorkSpace="` + newDevcontainer.DevcontainerWorkDir + `/` + repo.Name + `" ` +
` -e DEVCONTAINER_STATUS="start" `
` -e DEVCONTAINER_STATUS="start" ` +
` -e WEB_TERMINAL_HELLO="Successfully connected to the devcontainer" `
// 遍历 ContainerEnv 映射中的每个环境变量
for name, value := range configurationModel.ContainerEnv {
// 将每个环境变量转换为 "-e name=value" 格式
@@ -391,7 +395,7 @@ func StopDevContainerByDocker(ctx context.Context, devContainerName string) erro
}
return nil
}
func UpdateDevContainerByDocker(ctx context.Context, devContainerInfo *devcontainer_models.Devcontainer, updateInfo *UpdateInfo, repo *repo.Repository, doer *user.User) error {
func UpdateDevContainerByDocker(ctx context.Context, devContainerInfo *devcontainer_models.Devcontainer, updateInfo *UpdateInfo, repo *gitea_context.Repository, doer *user.User) error {
// 创建docker client
cli, err := docker_module.CreateDockerClient(ctx)
if err != nil {
@@ -401,7 +405,7 @@ func UpdateDevContainerByDocker(ctx context.Context, devContainerInfo *devcontai
// update容器
imageRef := updateInfo.RepositoryAddress + "/" + updateInfo.RepositoryUsername + "/" + updateInfo.ImageName
configurationString, err := GetDevcontainerConfigurationString(ctx, repo)
configurationString, err := GetDevcontainerConfigurationString(ctx, repo.Repository)
if err != nil {
return err
}
@@ -411,16 +415,45 @@ func UpdateDevContainerByDocker(ctx context.Context, devContainerInfo *devcontai
}
if updateInfo.SaveMethod == "on" {
// 创建构建上下文包含Dockerfile的tar包
var buf bytes.Buffer
tw := tar.NewWriter(&buf)
defer tw.Close()
// 添加Dockerfile到tar包
var dockerfileContent string
dockerfile := "Dockerfile"
dockerfileContent, err := GetFileContentByPath(ctx, repo, ".devcontainer/"+configurationModel.Build.Dockerfile)
if configurationModel.Build == nil || configurationModel.Build.Dockerfile == "" {
_, err := FileExists(".devcontainer/Dockerfile", repo)
if err != nil {
return err
}
dockerfileContent, err = GetFileContentByPath(ctx, repo.Repository, ".devcontainer/Dockerfile")
if err != nil {
return err
}
} else {
_, err := FileExists(".devcontainer/"+configurationModel.Build.Dockerfile, repo)
if err != nil {
if git.IsErrNotExist(err) {
_, err := FileExists(".devcontainer/Dockerfile", repo)
if err != nil {
return err
}
dockerfileContent, err = GetFileContentByPath(ctx, repo.Repository, ".devcontainer/Dockerfile")
if err != nil {
return err
}
}
return err
} else {
dockerfileContent, err = GetFileContentByPath(ctx, repo.Repository, ".devcontainer/"+configurationModel.Build.Dockerfile)
if err != nil {
return err
}
}
}
content := []byte(dockerfileContent)
header := &tar.Header{
Name: dockerfile,
@@ -473,7 +506,7 @@ func UpdateDevContainerByDocker(ctx context.Context, devContainerInfo *devcontai
re := regexp.MustCompile(`"image"\s*:\s*"([^"]+)"`)
// 使用正则表达式查找并替换 image 字段的值
newConfiguration := re.ReplaceAllString(configurationString, `"image": "`+imageRef+`"`)
err = UpdateDevcontainerConfiguration(newConfiguration, repo, doer)
err = UpdateDevcontainerConfiguration(newConfiguration, repo.Repository, doer)
if err != nil {
return err
}
@@ -632,3 +665,36 @@ func RegistWebTerminal(ctx context.Context) error {
}
return nil
}
// ContainerExists 检查容器是否存在返回存在状态和容器ID如果存在
func ContainerExists(ctx context.Context, containerName string) (bool, string, error) {
cli, err := docker_module.CreateDockerClient(ctx)
if err != nil {
return false, "", err
}
// 设置过滤器,根据容器名称过滤
filter := filters.NewArgs()
filter.Add("name", containerName)
// 获取容器列表,使用过滤器
containers, err := cli.ContainerList(context.Background(), types.ContainerListOptions{
All: true, // 包括所有容器(运行的和停止的)
Filters: filter,
})
if err != nil {
return false, "", err
}
// 遍历容器,检查名称是否完全匹配
for _, container := range containers {
for _, name := range container.Names {
// 容器名称在Docker API中是以斜杠开头的例如 "/my-container"
// 所以我们需要检查去掉斜杠后的名称是否匹配
if strings.TrimPrefix(name, "/") == containerName {
return true, container.ID, nil
}
}
}
return false, "", nil
}

repo.diff.view_file

@@ -22,6 +22,7 @@
{{else}}
<div class="ui container">
<form class="ui edit form">
<div class="repo-editor-header">
<div class="ui breadcrumb field">
@@ -36,7 +37,9 @@
</div>
</form>
{{if and .ValidateDevContainerConfiguration .HasDevContainer}}
<iframe id="webTerminalContainer" src="{{.WebSSHUrl}}" width="100%" style="height: 100vh; display: none;" frameborder="0">您的浏览器不支持iframe</iframe>
{{end}}
</div>
{{end}}
</div>
@@ -47,7 +50,7 @@
<strong>{{ctx.Locale.Tr "repo.dev_container_control"}}</strong>
<div class="ui relaxed list">
{{if .HasDevContainer}}
{{if and .ValidateDevContainerConfiguration .HasDevContainer}}
<div style=" display: none;" id="deleteContainer" class="item"><a class="delete-button flex-text-inline" data-modal="#delete-repo-devcontainer-of-user-modal" href="#" data-url="{{.Repository.Link}}/devcontainer/delete">{{svg "octicon-trash" 14}}{{ctx.Locale.Tr "repo.dev_container_control.delete"}}</a></div>
{{if .isAdmin}}
<div style=" display: none;" id="updateContainer" class="item"><a class="delete-button flex-text-inline" style="color:black; " data-modal-id="updatemodal" href="#">{{svg "octicon-database"}}{{ctx.Locale.Tr "repo.dev_container_control.update"}}</a></div>
@@ -106,24 +109,14 @@
</div>
{{template "base/modal_actions_confirm" .}}
</div>
<!-- 确认 Dev Container 模态对话框 -->
<!-- 保存 Dev Container 模态对话框 -->
<div class="ui g-modal-confirm delete modal" style="width: 35%" id="updatemodal">
<div class="header">
{{ctx.Locale.Tr "repo.dev_container_control.update"}}
</div>
<div class="content">
<form class="ui form tw-max-w-2xl tw-m-auto" id="updateForm" onsubmit="submitForm(event)">
<div class="inline field">
<div class="ui checkbox">
{{if not .HasDevContainerDockerfile}}
<input type="checkbox" id="SaveMethod" name="SaveMethod" disabled>
{{else}}
<input type="checkbox" id="SaveMethod" name="SaveMethod" value="on">
{{end}}
<label for="SaveMethod">Build From Dockerfile</label>
</div>
</div>
<div class="required field ">
<label for="RepositoryAddress">Registry:</label>
<input style="border: 1px solid black;" type="text" id="RepositoryAddress" name="RepositoryAddress" value="{{.RepositoryAddress}}">
@@ -154,7 +147,18 @@
<label for="ImageName">Image(name:tag):</label>
<input style="border: 1px solid black;" type="text" id="ImageName" name="ImageName" value="{{.ImageName}}">
</div>
<div class="inline field">
<div class="ui checkbox">
{{if not .HasDevContainerDockerfile}}
<input type="checkbox" id="SaveMethod" name="SaveMethod" disabled>
<label for="SaveMethod">There is no Dockerfile</label>
{{else}}
<input type="checkbox" id="SaveMethod" name="SaveMethod" value="on">
<label for="SaveMethod">Build From Dockerfile: {{.DockerfilePath}}</label>
{{end}}
</div>
</div>
<div class="actions">
<button class="ui primary button" type="submit" id="updateSubmitButton" >Submit</button>
<button class="ui cancel button" id="updateCloseButton">Close</button>
@@ -276,7 +280,7 @@ function getStatus() {
window.location.reload();
}
else if(status !== '4' && status !== '-1' && data.status == '4'){
window.location.reload();
//window.location.reload();
}
else if (data.status == '-1' || data.status == '') {
if (loadingElement) {
@@ -372,7 +376,7 @@ function getStatus() {
console.error('Error:', error);
});
}
intervalID = setInterval(getStatus, 3000);
intervalID = setInterval(getStatus, 5000);
if (restartContainer) {
restartContainer.addEventListener('click', function(event) {
// 处理点击逻辑
@@ -381,7 +385,7 @@ if (restartContainer) {
loadingElement.style.display = 'block';
}
fetch('{{.Repository.Link}}' + '/devcontainer/restart')
.then(response => {intervalID = setInterval(getStatus, 3000);})
.then(response => {intervalID = setInterval(getStatus, 5000);})
});
}
if (stopContainer) {
@@ -392,7 +396,7 @@ if (stopContainer) {
}
// 处理点击逻辑
fetch('{{.Repository.Link}}' + '/devcontainer/stop')
.then(response => {intervalID = setInterval(getStatus, 3000);})
.then(response => {intervalID = setInterval(getStatus, 5000);})
});
}

repo.diff.view_file

@@ -25,6 +25,7 @@ case $DEVCONTAINER_STATUS in
;;
esac
git clone $RepoLink $WorkSpace
echo -e "033[31mCreation completed. Please refresh the page to connect to the devcontainer\033[0m";
sh -c "tail -f /dev/null"
;;
*)