diff --git a/models/user/user.go b/models/user/user.go index c362cbc6d2..4dbaea4f85 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -124,6 +124,7 @@ type User struct { AllowGitHook bool AllowImportLocal bool // Allow migrate repository by local path AllowCreateOrganization bool `xorm:"DEFAULT true"` + AllowCreateDevcontainer 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"` @@ -268,6 +269,11 @@ func (u *User) CanCreateOrganization() bool { return u.IsAdmin || (u.AllowCreateOrganization && !setting.Admin.DisableRegularOrgCreation) } +// CanCreateDevcontianer returns true if user can create organisation. +func (u *User) CanCreateDevcontainer() bool { + return u.IsAdmin || u.AllowCreateDevcontainer +} + // CanEditGitHook returns true if user can edit Git hooks. func (u *User) CanEditGitHook() bool { return !setting.DisableGitHooks && (u.IsAdmin || u.AllowGitHook) @@ -633,6 +639,7 @@ type CreateUserOverwriteOptions struct { KeepEmailPrivate optional.Option[bool] Visibility *structs.VisibleType AllowCreateOrganization optional.Option[bool] + AllowCreateDevcontainer optional.Option[bool] EmailNotificationsPreference *string MaxRepoCreation *int Theme *string diff --git a/models/user/user_system.go b/models/user/user_system.go index e07274d291..cc7068551a 100644 --- a/models/user/user_system.go +++ b/models/user/user_system.go @@ -58,6 +58,7 @@ func NewActionsUser() *User { LoginName: ActionsUserName, Type: UserTypeBot, AllowCreateOrganization: true, + AllowCreateDevcontainer: false, Visibility: structs.VisibleTypePublic, } } diff --git a/modules/docker/docker_api.go b/modules/docker/docker_api.go index 249a33db1b..fcaaeb5398 100644 --- a/modules/docker/docker_api.go +++ b/modules/docker/docker_api.go @@ -141,7 +141,10 @@ func CreateAndStartContainer(ctx context.Context, cli *client.Client, imageName } hostConfig := &container.HostConfig{ - Binds: binds, + Binds: binds, + RestartPolicy: container.RestartPolicy{ + Name: "always", // 设置为 always + }, PublishAllPorts: true, } diff --git a/modules/structs/admin_user.go b/modules/structs/admin_user.go index c68b59a897..3b4ef8b50b 100644 --- a/modules/structs/admin_user.go +++ b/modules/structs/admin_user.go @@ -54,6 +54,7 @@ type EditUserOption struct { MaxRepoCreation *int `json:"max_repo_creation"` ProhibitLogin *bool `json:"prohibit_login"` AllowCreateOrganization *bool `json:"allow_create_organization"` + AllowCreateDevcontainer *bool `json:"allow_create_devcontainer"` Restricted *bool `json:"restricted"` Visibility string `json:"visibility" binding:"In(,public,limited,private)"` } diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index a4402928e3..9e5f0ac0ff 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -3155,6 +3155,7 @@ users.allow_git_hook = May Create Git Hooks users.allow_git_hook_tooltip = Git Hooks are executed as the OS user running Gitea and will have the same level of host access. As a result, users with this special Git Hook privilege can access and modify all Gitea repositories as well as the database used by Gitea. Consequently they are also able to gain Gitea administrator privileges. users.allow_import_local = May Import Local Repositories users.allow_create_organization = May Create Organizations +users.allow_create_devcontainer= May Create Devcontainers users.update_profile = Update User Account users.delete_account = Delete User Account users.cannot_delete_self = "You cannot delete yourself" diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index 3b126ece68..ec46e2c457 100644 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -3144,6 +3144,7 @@ users.allow_git_hook=允许创建 Git 钩子 users.allow_git_hook_tooltip=Git 钩子将会以操作系统用户运行,拥有同样的主机访问权限。因此,拥有此特殊的 Git 钩子权限将能够访问合修改所有的 Gitea 仓库或者 Gitea 的数据库。同时也能获得 Gitea 的管理员权限。 users.allow_import_local=允许导入本地仓库 users.allow_create_organization=允许创建组织 +users.allow_create_devcontainer=允许创建开发容器 users.update_profile=更新帐户 users.delete_account=删除帐户 users.cannot_delete_self=您不能删除自己 diff --git a/routers/api/v1/admin/user.go b/routers/api/v1/admin/user.go index 494bace585..c14a24ef02 100644 --- a/routers/api/v1/admin/user.go +++ b/routers/api/v1/admin/user.go @@ -245,6 +245,7 @@ func EditUser(ctx *context.APIContext) { AllowImportLocal: optional.FromPtr(form.AllowImportLocal), MaxRepoCreation: optional.FromPtr(form.MaxRepoCreation), AllowCreateOrganization: optional.FromPtr(form.AllowCreateOrganization), + AllowCreateDevcontainer: optional.FromPtr(form.AllowCreateDevcontainer), IsRestricted: optional.FromPtr(form.Restricted), } diff --git a/routers/install/install.go b/routers/install/install.go index 5cf227b877..118775a23d 100644 --- a/routers/install/install.go +++ b/routers/install/install.go @@ -126,8 +126,6 @@ func Install(ctx *context.Context) { form.AppURL = setting.AppURL form.LogRootPath = setting.Log.RootPath - form.DevcontainerEnable = setting.DevContainerConfig.Enable - form.K8sEnable = setting.K8sConfig.Enable form.K8sUrl = setting.K8sConfig.Url form.K8sToken = setting.K8sConfig.Token @@ -469,14 +467,6 @@ func SubmitInstall(ctx *context.Context) { ctx.Data["EnableWechatQRSignIn"] = form.EnableWechatQRSignIn cfg.Section("wechat").Key("ENABLED_WECHAT_QR_SIGNIN").SetValue("false") } - if form.DevcontainerEnable { - log.Info("DevContainer Enable!") - ctx.Data["DevContainerEnable"] = form.DevcontainerEnable - cfg.Section("devcontainer").Key("ENABLE").SetValue("true") - } else { - ctx.Data["DevContainerEnable"] = form.DevcontainerEnable - cfg.Section("devcontainer").Key("ENABLE").SetValue("false") - } if form.K8sEnable { ctx.Data["K8sEnable"] = form.K8sEnable @@ -635,20 +625,20 @@ func SubmitInstall(ctx *context.Context) { return } } - if form.DevcontainerEnable { - if form.K8sEnable { - //K8s环境检测 - } else { - if !checkDocker(ctx, &form) { - return - } - err = devcontainer_service.RegistWebTerminal(ctx) - if err != nil { - ctx.RenderWithErr(ctx.Tr("install.web_terminal_failed", err), tplInstall, &form) - return - } + + if form.K8sEnable { + //K8s环境检测 + } else { + if !checkDocker(ctx, &form) { + return + } + err = devcontainer_service.RegistWebTerminal(ctx) + if err != nil { + ctx.RenderWithErr(ctx.Tr("install.web_terminal_failed", err), tplInstall, &form) + return } } + runners_service.RegistGlobalRunner(ctx) setting.ClearEnvConfigKeys() diff --git a/routers/web/admin/users.go b/routers/web/admin/users.go index 27577cd35b..5905d37755 100644 --- a/routers/web/admin/users.go +++ b/routers/web/admin/users.go @@ -224,7 +224,6 @@ func prepareUserInfo(ctx *context.Context) *user_model.User { return nil } ctx.Data["User"] = u - if u.LoginSource > 0 { ctx.Data["LoginSource"], err = auth.GetSourceByID(ctx, u.LoginSource) if err != nil { @@ -437,6 +436,7 @@ func EditUserPost(ctx *context.Context) { AllowImportLocal: optional.Some(form.AllowImportLocal), MaxRepoCreation: optional.Some(form.MaxRepoCreation), AllowCreateOrganization: optional.Some(form.AllowCreateOrganization), + AllowCreateDevcontainer: optional.Some(form.AllowCreateDevcontainer), IsRestricted: optional.Some(form.Restricted), Visibility: optional.Some(form.Visibility), Language: optional.Some(form.Language), diff --git a/routers/web/devcontainer/devcontainer.go b/routers/web/devcontainer/devcontainer.go index cec1123c8f..4322f4d373 100644 --- a/routers/web/devcontainer/devcontainer.go +++ b/routers/web/devcontainer/devcontainer.go @@ -31,11 +31,7 @@ func GetDevContainerDetails(ctx *context.Context) { if err != nil { ctx.Flash.Error(err.Error(), true) } - ctx.Data["canRead"], err = devcontainer_service.CanReadDevcontainer(ctx.Req.Context(), ctx.Doer, ctx.Repo.Repository.ID) - if err != nil { - log.Info(err.Error()) - ctx.Flash.Error(err.Error(), true) - } + ctx.Data["isAdmin"], err = devcontainer_service.IsAdmin(ctx, ctx.Doer, ctx.Repo.Repository.ID) if err != nil { log.Info(err.Error()) diff --git a/routers/web/web.go b/routers/web/web.go index f59133ed2d..a444230cb5 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -867,7 +867,6 @@ func registerWebRoutes(m *web.Router) { reqUnitPullsReader := context.RequireUnitReader(unit.TypePullRequests) reqUnitWikiReader := context.RequireUnitReader(unit.TypeWiki) reqUnitWikiWriter := context.RequireUnitWriter(unit.TypeWiki) - reqPackageAccess := func(accessMode perm.AccessMode) func(ctx *context.Context) { return func(ctx *context.Context) { if ctx.Package.AccessMode < accessMode && !ctx.IsUserSiteAdmin() { @@ -1403,7 +1402,7 @@ func registerWebRoutes(m *web.Router) { m.Post("/update", devcontainer_web.UpdateDevContainer) }, // 已登录 - reqSignIn) + context.RequireDevcontainerAccess, reqSignIn) m.Get("/status", devcontainer_web.GetDevContainerStatus) m.Get("/command", devcontainer_web.GetTerminalCommand) m.Get("/output", devcontainer_web.GetDevContainerOutput) diff --git a/services/context/repo.go b/services/context/repo.go index aa2119a689..c3fe8cffc4 100644 --- a/services/context/repo.go +++ b/services/context/repo.go @@ -396,7 +396,7 @@ func repoAssignment(ctx *Context, repo *repo_model.Repository) { return } ctx.Data["Permission"] = &ctx.Repo.Permission - + ctx.Data["AllowCreateDevcontainer"] = ctx.Doer.AllowCreateDevcontainer if repo.IsMirror { pullMirror, err := repo_model.GetMirrorByRepoID(ctx, repo.ID) if err == nil { @@ -412,6 +412,18 @@ func repoAssignment(ctx *Context, repo *repo_model.Repository) { ctx.Data["IsEmptyRepo"] = ctx.Repo.Repository.IsEmpty } +func RequireDevcontainerAccess(ctx *Context) { + if ctx.Doer == nil { + ctx.HTTPError(http.StatusUnauthorized, "Devcontainer access requires login") + return + } + + if !ctx.Doer.CanCreateDevcontainer() { + ctx.HTTPError(http.StatusForbidden, "User cannot create devcontainers") + return + } +} + // RepoAssignment returns a middleware to handle repository assignment func RepoAssignment(ctx *Context) { if ctx.Data["Repository"] != nil { @@ -542,7 +554,7 @@ func RepoAssignment(ctx *Context) { ctx.Data["CanWriteIssues"] = ctx.Repo.CanWrite(unit_model.TypeIssues) ctx.Data["CanWritePulls"] = ctx.Repo.CanWrite(unit_model.TypePullRequests) ctx.Data["CanWriteActions"] = ctx.Repo.CanWrite(unit_model.TypeActions) - ctx.Data["DevContainerEnable"] = setting.DevContainerConfig.Enable + canSignedUserFork, err := repo_module.CanUserForkRepo(ctx, ctx.Doer, ctx.Repo.Repository) if err != nil { ctx.ServerError("CanUserForkRepo", err) diff --git a/services/devcontainer/devcontainer.go b/services/devcontainer/devcontainer.go index 990f2a3094..6050ded27c 100644 --- a/services/devcontainer/devcontainer.go +++ b/services/devcontainer/devcontainer.go @@ -113,12 +113,11 @@ func CreateDevcontainerConfiguration(repo *repo.Repository, doer *user.User) err }, "initializeCommand": "echo \"init\";", "postCreateCommand": [ - "echo \"created\";", - "echo \"test\";" + "echo \"created\"", + "echo \"test\"" ], "runArgs": [ - "-p", - "8888:8888" + "-p 8888" ] }` _, err := files_service.ChangeRepoFiles(db.DefaultContext, repo, doer, &files_service.ChangeRepoFilesOptions{ diff --git a/services/devcontainer/devcontainer_utils.go b/services/devcontainer/devcontainer_utils.go index 71d4f4317d..b3c03d43c5 100644 --- a/services/devcontainer/devcontainer_utils.go +++ b/services/devcontainer/devcontainer_utils.go @@ -12,7 +12,6 @@ import ( "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/perm" "code.gitea.io/gitea/models/repo" - "code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/charset" "code.gitea.io/gitea/modules/git" @@ -24,28 +23,6 @@ import ( "github.com/google/uuid" ) -func CanReadDevcontainer(ctx context.Context, doer *user.User, repoID int64) (bool, error) { - if doer.IsAdmin { - return true, nil - } - //检测是否组织成员及权限 - e := db.GetEngine(ctx) - teamMember, err := e.Table("team_user"). - Join("INNER", "team_repo", "`team_repo`.team_id = `team_user`.team_id"). - Join("INNER", "team_unit", "`team_unit`.team_id = `team_user`.team_id"). - Where("`team_repo`.repo_id = ? AND (`team_unit`.access_mode >= ? OR (`team_unit`.access_mode = ? AND `team_unit`.`type` = ?))", - repoID, perm.AccessModeWrite, perm.AccessModeRead, unit.TypeCode). - And("team_user.uid = ?", doer.ID).Exist() - if err != nil { - return false, err - } - if teamMember { - return true, nil - } - //检测是否有仓库协作者权限 - return repo.IsCollaborator(ctx, repoID, doer.ID) - -} func IsAdmin(ctx context.Context, doer *user.User, repoID int64) (bool, error) { if doer.IsAdmin { return true, nil diff --git a/services/devcontainer/docker_agent.go b/services/devcontainer/docker_agent.go index 968839e031..ae5268eb48 100644 --- a/services/devcontainer/docker_agent.go +++ b/services/devcontainer/docker_agent.go @@ -134,8 +134,7 @@ func CreateDevContainerByDockerAPI(ctx context.Context, newDevcontainer *devcont nil, nil, nat.PortSet{ - nat.Port("22/tcp"): {}, - nat.Port("7681/tcp"): {}, + nat.Port("22/tcp"): {}, }, newDevcontainer.Name) _, err = dbEngine.Table("devcontainer"). @@ -146,7 +145,7 @@ func CreateDevContainerByDockerAPI(ctx context.Context, newDevcontainer *devcont } log.Info("ExecCommandInContainerExecCommandInContainerExecCommandInContainerExecCommandInContainerExecCommandInContainer") output, err := docker_module.ExecCommandInContainer(ctx, cli, newDevcontainer.Name, - `echo "`+newDevcontainer.DevcontainerHost+` host.docker.internal" | tee -a /etc/hosts;apt update;apt install -y git ;git clone `+strings.TrimSuffix(setting.AppURL, "/")+repo.Link()+" "+newDevcontainer.DevcontainerWorkDir+repo.Name+`; apt install -y ssh;echo "PubkeyAuthentication yes `+"\n"+`PermitRootLogin yes `+"\n"+`" | tee -a /etc/ssh/sshd_config;rm -f /etc/ssh/ssh_host_*; ssh-keygen -A; service ssh restart;mkdir -p ~/.ssh;chmod 700 ~/.ssh;echo "`+strings.Join(publicKeyList, "\n")+`" > ~/.ssh/authorized_keys;chmod 600 ~/.ssh/authorized_keys;`, + `echo "`+newDevcontainer.DevcontainerHost+` host.docker.internal" | tee -a /etc/hosts;apt update;apt install -y git ;git clone `+strings.TrimSuffix(setting.AppURL, "/")+repo.Link()+" "+newDevcontainer.DevcontainerWorkDir+"/"+repo.Name+`; apt install -y ssh;echo "PubkeyAuthentication yes `+"\n"+`PermitRootLogin yes `+"\n"+`" | tee -a /etc/ssh/sshd_config;rm -f /etc/ssh/ssh_host_*; ssh-keygen -A; service ssh restart;mkdir -p ~/.ssh;chmod 700 ~/.ssh;echo "`+strings.Join(publicKeyList, "\n")+`" > ~/.ssh/authorized_keys;chmod 600 ~/.ssh/authorized_keys;`, ) if err != nil { return err @@ -303,13 +302,20 @@ func CreateDevContainerByDockerCommand(ctx context.Context, newDevcontainer *dev return err } //安装基本工具的命令 + onCreateCommand := strings.TrimSpace(strings.Join(configurationModel.ParseCommand(configurationModel.OnCreateCommand), ";")) + if !strings.HasSuffix(onCreateCommand, ";") { + onCreateCommand += ";" + } + if onCreateCommand == ";" { + onCreateCommand = "" + } if _, err := dbEngine.Table("devcontainer_output").Insert(&devcontainer_models.DevcontainerOutput{ Output: "", Status: "waitting", UserId: newDevcontainer.UserId, RepoId: newDevcontainer.RepoId, Command: `docker -H ` + dockerSocket + ` exec ` + newDevcontainer.Name + ` /home/webTerminal.sh start; ` + - strings.Join(configurationModel.ParseCommand(configurationModel.OnCreateCommand), ";") + ";\n", + onCreateCommand + "\n", ListId: 3, DevcontainerId: newDevcontainer.Id, }); err != nil { diff --git a/services/forms/admin.go b/services/forms/admin.go index 81276f8f46..a1fd976ec2 100644 --- a/services/forms/admin.go +++ b/services/forms/admin.go @@ -49,6 +49,7 @@ type AdminEditUserForm struct { AllowGitHook bool AllowImportLocal bool AllowCreateOrganization bool + AllowCreateDevcontainer bool ProhibitLogin bool Reset2FA bool `form:"reset_2fa"` Visibility structs.VisibleType diff --git a/services/forms/user_form.go b/services/forms/user_form.go index 183fd3dd37..e72df81144 100644 --- a/services/forms/user_form.go +++ b/services/forms/user_form.go @@ -37,8 +37,6 @@ type InstallForm struct { AppURL string `binding:"Required"` LogRootPath string `binding:"Required"` - DevcontainerEnable bool - K8sEnable bool K8sUrl string K8sToken string diff --git a/services/repository/init.go b/services/repository/init.go index 49b2dc57d0..32588c8c60 100644 --- a/services/repository/init.go +++ b/services/repository/init.go @@ -38,8 +38,7 @@ func initRepoCommit(ctx context.Context, tmpPath string, repo *repo_model.Reposi return fmt.Errorf("git add --all: %w", err) } - cmd := git.NewCommand("commit"). - AddOptionFormat("--message=%s", commitMessage). // 使用内联格式化字符串 + cmd := git.NewCommand("commit").AddOptionFormat(" %s ", commitMessage). AddOptionFormat("--author='%s <%s>'", sig.Name, sig.Email) sign, key, signer, _ := asymkey_service.SignInitialCommit(ctx, tmpPath, u) diff --git a/services/user/update.go b/services/user/update.go index d7354542bf..116c4e43a2 100644 --- a/services/user/update.go +++ b/services/user/update.go @@ -51,6 +51,7 @@ type UpdateOptions struct { Theme optional.Option[string] DiffViewStyle optional.Option[string] AllowCreateOrganization optional.Option[bool] + AllowCreateDevcontainer optional.Option[bool] IsActive optional.Option[bool] IsAdmin optional.Option[UpdateOptionField[bool]] EmailNotificationsPreference optional.Option[string] @@ -164,6 +165,11 @@ func UpdateUser(ctx context.Context, u *user_model.User, opts *UpdateOptions) er cols = append(cols, "allow_create_organization") } + if opts.AllowCreateDevcontainer.Has() { + u.AllowCreateDevcontainer = opts.AllowCreateDevcontainer.Value() + + cols = append(cols, "allow_create_devcontainer") + } if opts.RepoAdminChangeTeamAccess.Has() { u.RepoAdminChangeTeamAccess = opts.RepoAdminChangeTeamAccess.Value() diff --git a/templates/admin/user/edit.tmpl b/templates/admin/user/edit.tmpl index 879b5cb550..d16cc02de5 100644 --- a/templates/admin/user/edit.tmpl +++ b/templates/admin/user/edit.tmpl @@ -148,6 +148,12 @@ {{end}} +