This commit is contained in:
init
2025-09-18 19:25:17 +08:00
repo.diff.parent 7bf829b599
repo.diff.commit 6ff9b9c665
repo.diff.stats_desc%!(EXTRA int=23, int=74, int=81)

repo.diff.view_file

@@ -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

repo.diff.view_file

@@ -58,6 +58,7 @@ func NewActionsUser() *User {
LoginName: ActionsUserName,
Type: UserTypeBot,
AllowCreateOrganization: true,
AllowCreateDevcontainer: false,
Visibility: structs.VisibleTypePublic,
}
}

repo.diff.view_file

@@ -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,
}

repo.diff.view_file

@@ -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)"`
}

repo.diff.view_file

@@ -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"

repo.diff.view_file

@@ -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=您不能删除自己

repo.diff.view_file

@@ -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),
}

repo.diff.view_file

@@ -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()

repo.diff.view_file

@@ -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),

repo.diff.view_file

@@ -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())

repo.diff.view_file

@@ -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)

repo.diff.view_file

@@ -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)

repo.diff.view_file

@@ -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{

repo.diff.view_file

@@ -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

repo.diff.view_file

@@ -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 {

repo.diff.view_file

@@ -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

repo.diff.view_file

@@ -37,8 +37,6 @@ type InstallForm struct {
AppURL string `binding:"Required"`
LogRootPath string `binding:"Required"`
DevcontainerEnable bool
K8sEnable bool
K8sUrl string
K8sToken string

repo.diff.view_file

@@ -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)

repo.diff.view_file

@@ -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()

repo.diff.view_file

@@ -148,6 +148,12 @@
</div>
</div>
{{end}}
<div class="inline field">
<div class="ui checkbox">
<label><strong>{{ctx.Locale.Tr "admin.users.allow_create_devcontainer"}}</strong></label>
<input name="allow_create_devcontainer" type="checkbox" {{if or (.User.IsAdmin) (.User.AllowCreateDevcontainer)}}checked{{end}}>
</div>
</div>
{{if .TwoFactorEnabled}}
<div class="divider"></div>

repo.diff.view_file

@@ -225,13 +225,7 @@
<summary class="right-content tw-py-2{{if .Err_Services}} text red{{end}}">
{{ctx.Locale.Tr "install.server_service_title"}}
</summary>
<!-- devcontainer -->
<div class="inline field">
<div class="ui checkbox" id="enable-devcontainer">
<label data-tooltip-content="{{ctx.Locale.Tr "install.devcontainer_enable"}}">{{ctx.Locale.Tr "install.devcontainer_enable"}}</label>
<input name="devcontainer_enable" type="checkbox" {{if .devcontainer_enable}}checked{{end}}>
</div>
</div>
<div class="inline field">
<div class="ui checkbox" id="offline-mode">
<label data-tooltip-content="{{ctx.Locale.Tr "install.offline_mode_popup"}}">{{ctx.Locale.Tr "install.offline_mode"}}</label>

repo.diff.view_file

@@ -63,14 +63,9 @@
{{else if .ValidateDevContainerConfiguration}}
<div class="item">
<div>
{{if .canRead}}
<form method="get" action="{{.Repository.Link}}/devcontainer/create" class="ui edit form">
<button class="flex-text-inline" type="submit">{{svg "octicon-terminal" 14 "tw-mr-2"}} Create Dev Container</button>
</form>
{{else}}
<div class="button flex-text-inline" disabled>Permission Denied</div>
{{end}}
</div>
</div>
{{end}}

repo.diff.view_file

@@ -142,7 +142,7 @@
{{end}}
<!-- 定义DevContainer tab -->
{{if .DevContainerEnable}}
{{if or (.AllowCreateDevcontainer) (.Permission.IsAdmin)}}
{{if .Permission.CanRead ctx.Consts.RepoUnitTypeCode}}
<a class="{{if .PageIsDevContainer}}active {{end}}item" href="{{.RepoLink}}/devcontainer">
{{svg "octicon-container"}} {{ctx.Locale.Tr "repo.dev_container"}}