From 0cef6cd314b6fc2fa611a96516bbf5750bdcb52d Mon Sep 17 00:00:00 2001 From: Brian Hong Date: Wed, 4 Jan 2023 20:51:02 -0500 Subject: [PATCH 01/27] Add basic Debian package type Upload/download/delete --- models/packages/descriptor.go | 3 + models/packages/package.go | 6 + modules/packages/debian/metadata.go | 23 +++ public/img/svg/gitea-debian.svg | 8 + routers/api/packages/api.go | 8 + routers/api/packages/debian/debian.go | 194 +++++++++++++++++++++++++ routers/api/v1/packages/package.go | 2 +- templates/package/content/debian.tmpl | 28 ++++ templates/package/metadata/debian.tmpl | 2 + templates/package/view.tmpl | 2 + templates/swagger/v1_json.tmpl | 1 + web_src/svg/gitea-debian.svg | 8 + 12 files changed, 284 insertions(+), 1 deletion(-) create mode 100644 modules/packages/debian/metadata.go create mode 100644 public/img/svg/gitea-debian.svg create mode 100644 routers/api/packages/debian/debian.go create mode 100644 templates/package/content/debian.tmpl create mode 100644 templates/package/metadata/debian.tmpl create mode 100644 web_src/svg/gitea-debian.svg diff --git a/models/packages/descriptor.go b/models/packages/descriptor.go index 34f1cad87dc45..64c63a7433b50 100644 --- a/models/packages/descriptor.go +++ b/models/packages/descriptor.go @@ -14,6 +14,7 @@ import ( "code.gitea.io/gitea/modules/packages/composer" "code.gitea.io/gitea/modules/packages/conan" "code.gitea.io/gitea/modules/packages/container" + "code.gitea.io/gitea/modules/packages/debian" "code.gitea.io/gitea/modules/packages/helm" "code.gitea.io/gitea/modules/packages/maven" "code.gitea.io/gitea/modules/packages/npm" @@ -134,6 +135,8 @@ func GetPackageDescriptor(ctx context.Context, pv *PackageVersion) (*PackageDesc metadata = &conan.Metadata{} case TypeContainer: metadata = &container.Metadata{} + case TypeDebian: + metadata = &debian.Metadata{} case TypeGeneric: // generic packages have no metadata case TypeHelm: diff --git a/models/packages/package.go b/models/packages/package.go index a804f35de3526..ebdb719cd9541 100644 --- a/models/packages/package.go +++ b/models/packages/package.go @@ -33,6 +33,7 @@ const ( TypeComposer Type = "composer" TypeConan Type = "conan" TypeContainer Type = "container" + TypeDebian Type = "debian" TypeGeneric Type = "generic" TypeHelm Type = "helm" TypeMaven Type = "maven" @@ -48,6 +49,7 @@ var TypeList = []Type{ TypeComposer, TypeConan, TypeContainer, + TypeDebian, TypeGeneric, TypeHelm, TypeMaven, @@ -68,6 +70,8 @@ func (pt Type) Name() string { return "Conan" case TypeContainer: return "Container" + case TypeDebian: + return "Debian" case TypeGeneric: return "Generic" case TypeHelm: @@ -99,6 +103,8 @@ func (pt Type) SVGName() string { return "gitea-conan" case TypeContainer: return "octicon-container" + case TypeDebian: + return "gitea-debian" case TypeGeneric: return "octicon-package" case TypeHelm: diff --git a/modules/packages/debian/metadata.go b/modules/packages/debian/metadata.go new file mode 100644 index 0000000000000..63c4dd3bf7a8b --- /dev/null +++ b/modules/packages/debian/metadata.go @@ -0,0 +1,23 @@ +// Copyright 2022 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package debian + +import ( + "errors" +) + +var ( + ErrInvalidName = errors.New("Package name is invalid") + ErrInvalidVersion = errors.New("Package version is invalid") + ErrInvalidArch = errors.New("Package architecture is invalid") +) + +type Package struct { + Name string + Version string + Metadata *Metadata +} + +type Metadata struct { +} diff --git a/public/img/svg/gitea-debian.svg b/public/img/svg/gitea-debian.svg new file mode 100644 index 0000000000000..1c042510f3c4f --- /dev/null +++ b/public/img/svg/gitea-debian.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/routers/api/packages/api.go b/routers/api/packages/api.go index 78eb5e860be26..a4a8eed8aa957 100644 --- a/routers/api/packages/api.go +++ b/routers/api/packages/api.go @@ -17,6 +17,7 @@ import ( "code.gitea.io/gitea/routers/api/packages/composer" "code.gitea.io/gitea/routers/api/packages/conan" "code.gitea.io/gitea/routers/api/packages/container" + "code.gitea.io/gitea/routers/api/packages/debian" "code.gitea.io/gitea/routers/api/packages/generic" "code.gitea.io/gitea/routers/api/packages/helm" "code.gitea.io/gitea/routers/api/packages/maven" @@ -167,6 +168,13 @@ func CommonRoutes(ctx gocontext.Context) *web.Route { }) }) }, reqPackageAccess(perm.AccessModeRead)) + r.Group("/debian", func() { + r.Group("/{packagename}/{packageversion}/{arch}", func() { + r.Put("", debian.PutPackage) + r.Delete("", debian.DeletePackage) + }, reqPackageAccess(perm.AccessModeWrite)) + r.Get("/pool/{filename}", debian.GetPackage) + }, reqPackageAccess(perm.AccessModeRead)) r.Group("/generic", func() { r.Group("/{packagename}/{packageversion}", func() { r.Delete("", reqPackageAccess(perm.AccessModeWrite), generic.DeletePackage) diff --git a/routers/api/packages/debian/debian.go b/routers/api/packages/debian/debian.go new file mode 100644 index 0000000000000..adb1b080cb28f --- /dev/null +++ b/routers/api/packages/debian/debian.go @@ -0,0 +1,194 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package debian + +import ( + "errors" + "fmt" + "net/http" + "regexp" + "strings" + + packages_model "code.gitea.io/gitea/models/packages" + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/log" + packages_module "code.gitea.io/gitea/modules/packages" + "code.gitea.io/gitea/routers/api/packages/helper" + packages_service "code.gitea.io/gitea/services/packages" +) + +var ( + namePattern = regexp.MustCompile(`\A[a-z0-9][a-z0-9\+\-\.]+\z`) + versionPattern = regexp.MustCompile(`\A([0-9]:)?[a-zA-Z0-9\.\+\~]+(-[a-zA-Z0-9\.\+\~])?\z`) // TODO: hypens should be allowed if revision is present + archPattern = regexp.MustCompile(`\A[a-z0-9\-]+\z`) +) + +func apiError(ctx *context.Context, status int, obj interface{}) { + helper.LogAndProcessError(ctx, status, obj, func(message string) { + ctx.PlainText(status, message) + }) +} + +func GetPackage(ctx *context.Context) { + // packageName := ctx.Params("packagename") + // packageVersion := ctx.Params("packageversion") + // packageArch := ctx.Params("arch") + // filename := fmt.Sprintf("%s_%s_%s.deb", packageName, packageVersion, packageArch) + filename := ctx.Params("filename") + log.Info("Filename: %s", filename) + + splitter := regexp.MustCompile(`^([^_]+)_([^_]+)_([^.]+).deb$`) + matches := splitter.FindStringSubmatch(filename) + if matches == nil { + apiError(ctx, http.StatusBadRequest, "Invalid filename") + return + } + packageName := matches[1] + packageVersion := matches[2] + + s, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( + ctx, + &packages_service.PackageInfo{ + Owner: ctx.Package.Owner, + PackageType: packages_model.TypeDebian, + Name: packageName, + Version: packageVersion, + }, + &packages_service.PackageFileInfo{ + Filename: filename, + }, + ) + if err != nil { + if err == packages_model.ErrPackageNotExist || err == packages_model.ErrPackageFileNotExist { + apiError(ctx, http.StatusNotFound, err) + return + } + apiError(ctx, http.StatusInternalServerError, err) + return + } + defer s.Close() + + ctx.ServeContent(s, &context.ServeHeaderOptions{ + Filename: pf.Name, + LastModified: pf.CreatedUnix.AsLocalTime(), + }) +} + +func PutPackage(ctx *context.Context) { + packageName := ctx.Params("packagename") + + if !namePattern.MatchString(packageName) { + apiError(ctx, http.StatusBadRequest, errors.New("Invalid package name")) + return + } + + packageVersion := ctx.Params("packageversion") + if packageVersion != strings.TrimSpace(packageVersion) { + apiError(ctx, http.StatusBadRequest, errors.New("Invalid package version")) + return + } + + packageArch := ctx.Params("arch") + // TODO Check arch + + upload, close, err := ctx.UploadStream() + if err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } + if close { + defer upload.Close() + } + + buf, err := packages_module.CreateHashedBufferFromReader(upload, 32*1024*1024) + if err != nil { + log.Error("Error creating hashed buffer: %v", err) + apiError(ctx, http.StatusInternalServerError, err) + return + } + defer buf.Close() + + filename := fmt.Sprintf("%s_%s_%s.deb", packageName, packageVersion, packageArch) + _, _, err = packages_service.CreatePackageOrAddFileToExisting( + &packages_service.PackageCreationInfo{ + PackageInfo: packages_service.PackageInfo{ + Owner: ctx.Package.Owner, + PackageType: packages_model.TypeDebian, + Name: packageName, + Version: packageVersion, + }, + Creator: ctx.Doer, + }, + &packages_service.PackageFileCreationInfo{ + PackageFileInfo: packages_service.PackageFileInfo{ + Filename: filename, + }, + Creator: ctx.Doer, + Data: buf, + IsLead: true, + }, + ) + if err != nil { + switch err { + case packages_model.ErrDuplicatePackageFile: + apiError(ctx, http.StatusConflict, err) + case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize: + apiError(ctx, http.StatusForbidden, err) + default: + apiError(ctx, http.StatusInternalServerError, err) + } + return + } + + ctx.Status(http.StatusCreated) +} + +func DeletePackage(ctx *context.Context) { + packageName := ctx.Params("packagename") + packageVersion := ctx.Params("packageversion") + packageArch := ctx.Params("arch") + filename := fmt.Sprintf("%s_%s_%s.deb", packageName, packageVersion, packageArch) + + pv, pf, err := func() (*packages_model.PackageVersion, *packages_model.PackageFile, error) { + pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeDebian, packageName, packageVersion) + if err != nil { + return nil, nil, err + } + + pf, err := packages_model.GetFileForVersionByName(ctx, pv.ID, filename, packages_model.EmptyFileKey) + if err != nil { + return nil, nil, err + } + + return pv, pf, nil + }() + if err != nil { + if err == packages_model.ErrPackageNotExist || err == packages_model.ErrPackageFileNotExist { + apiError(ctx, http.StatusNotFound, err) + return + } + apiError(ctx, http.StatusInternalServerError, err) + return + } + + pfs, err := packages_model.GetFilesByVersionID(ctx, pv.ID) + if err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } + + if len(pfs) == 1 { + if err := packages_service.RemovePackageVersion(ctx.Doer, pv); err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } + } else { + if err := packages_service.DeletePackageFile(ctx, pf); err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } + } + + ctx.Status(http.StatusNoContent) +} diff --git a/routers/api/v1/packages/package.go b/routers/api/v1/packages/package.go index 6f9083ba327b5..4868ba0933318 100644 --- a/routers/api/v1/packages/package.go +++ b/routers/api/v1/packages/package.go @@ -40,7 +40,7 @@ func ListPackages(ctx *context.APIContext) { // in: query // description: package type filter // type: string - // enum: [composer, conan, container, generic, helm, maven, npm, nuget, pub, pypi, rubygems, vagrant] + // enum: [composer, conan, container, debian, generic, helm, maven, npm, nuget, pub, pypi, rubygems, vagrant] // - name: q // in: query // description: name filter diff --git a/templates/package/content/debian.tmpl b/templates/package/content/debian.tmpl new file mode 100644 index 0000000000000..9e5ce239d6875 --- /dev/null +++ b/templates/package/content/debian.tmpl @@ -0,0 +1,28 @@ +{{if eq .PackageDescriptor.Package.Type "debian"}} +

{{.locale.Tr "packages.installation"}}

+
+
+
+ +
{{range .PackageDescriptor.Files}}curl -O {{AppUrl}}api/packages/{{$.PackageDescriptor.Owner.Name}}/debian/pool/{{.File.Name}}
+{{end}}
+# install one version
+{{range .PackageDescriptor.Files}}sudo dpkg -i {{.File.Name}}
+{{end}}
+
+
+
+ +
+
echo "deb {{AppUrl}}api/packages/{{$.PackageDescriptor.Owner.Name}}/debian gitea main" \
+  | sudo tee /etc/apt/sources.list.d/gitea.list
+sudo apt update
+sudo apt install {{$.PackageDescriptor.Package.Name}}
+
+
+
+ +
+
+
+{{end}} diff --git a/templates/package/metadata/debian.tmpl b/templates/package/metadata/debian.tmpl new file mode 100644 index 0000000000000..4cfe2be507982 --- /dev/null +++ b/templates/package/metadata/debian.tmpl @@ -0,0 +1,2 @@ +{{if eq .PackageDescriptor.Package.Type "debian"}} +{{end}} diff --git a/templates/package/view.tmpl b/templates/package/view.tmpl index a5b2a2ef68a39..90a21434615f7 100644 --- a/templates/package/view.tmpl +++ b/templates/package/view.tmpl @@ -22,6 +22,7 @@ {{template "package/content/composer" .}} {{template "package/content/conan" .}} {{template "package/content/container" .}} + {{template "package/content/debian" .}} {{template "package/content/generic" .}} {{template "package/content/helm" .}} {{template "package/content/maven" .}} @@ -45,6 +46,7 @@ {{template "package/metadata/composer" .}} {{template "package/metadata/conan" .}} {{template "package/metadata/container" .}} + {{template "package/metadata/debian" .}} {{template "package/metadata/generic" .}} {{template "package/metadata/helm" .}} {{template "package/metadata/maven" .}} diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index c2232825966d3..f41749713793b 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -1955,6 +1955,7 @@ "composer", "conan", "container", + "debian", "generic", "helm", "maven", diff --git a/web_src/svg/gitea-debian.svg b/web_src/svg/gitea-debian.svg new file mode 100644 index 0000000000000..1c042510f3c4f --- /dev/null +++ b/web_src/svg/gitea-debian.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file From 909118df17f6c22a99493072fab4fd1a5c3dd5fc Mon Sep 17 00:00:00 2001 From: Brian Hong Date: Sat, 7 Jan 2023 06:32:37 -0500 Subject: [PATCH 02/27] Debian repo index directory structure TODO: Release/Package files --- routers/api/packages/api.go | 27 +++- routers/api/packages/debian/debian.go | 176 ++++++++++++++++++++++- templates/api/packages/debian/index.tmpl | 14 ++ 3 files changed, 211 insertions(+), 6 deletions(-) create mode 100644 templates/api/packages/debian/index.tmpl diff --git a/routers/api/packages/api.go b/routers/api/packages/api.go index a4a8eed8aa957..e6f71f6897749 100644 --- a/routers/api/packages/api.go +++ b/routers/api/packages/api.go @@ -169,11 +169,34 @@ func CommonRoutes(ctx gocontext.Context) *web.Route { }) }, reqPackageAccess(perm.AccessModeRead)) r.Group("/debian", func() { - r.Group("/{packagename}/{packageversion}/{arch}", func() { + r.Group("/files/{packagename}/{packageversion}/{arch}", func() { r.Put("", debian.PutPackage) r.Delete("", debian.DeletePackage) }, reqPackageAccess(perm.AccessModeWrite)) - r.Get("/pool/{filename}", debian.GetPackage) + + r.Get("/", debian.GetIndex) + r.Group("/pool", func() { + r.Get("/", debian.GetIndex) + r.Get("/{filename}", debian.GetPackage) + }) + r.Group("/dists", func() { + r.Get("/", debian.GetIndex) + r.Group("/gitea", func() { + r.Get("/", debian.GetIndex) + r.Group("/main", func() { + r.Get("/", debian.GetIndex) + r.Group("/{packagearch}", func() { + r.Get("/", debian.GetArchIndex) + r.Get("/Packages", debian.GetPackages) + r.Get("/Packages.gz", debian.GetPackagesGZ) + r.Get("/Release", debian.GetRelease) + }) + }) + r.Get("/Release", debian.GetRelease) + r.Get("/Release.gpg", debian.GetReleaseGPG) + r.Get("/InRelease", debian.GetInRelease) + }) + }) }, reqPackageAccess(perm.AccessModeRead)) r.Group("/generic", func() { r.Group("/{packagename}/{packageversion}", func() { diff --git a/routers/api/packages/debian/debian.go b/routers/api/packages/debian/debian.go index adb1b080cb28f..3428b1bc59c8a 100644 --- a/routers/api/packages/debian/debian.go +++ b/routers/api/packages/debian/debian.go @@ -7,6 +7,7 @@ import ( "errors" "fmt" "net/http" + "path/filepath" "regexp" "strings" @@ -31,10 +32,7 @@ func apiError(ctx *context.Context, status int, obj interface{}) { } func GetPackage(ctx *context.Context) { - // packageName := ctx.Params("packagename") - // packageVersion := ctx.Params("packageversion") - // packageArch := ctx.Params("arch") - // filename := fmt.Sprintf("%s_%s_%s.deb", packageName, packageVersion, packageArch) + // Need to parse filename bc of how it's routed filename := ctx.Params("filename") log.Info("Filename: %s", filename) @@ -192,3 +190,173 @@ func DeletePackage(ctx *context.Context) { ctx.Status(http.StatusNoContent) } + +func GetDebianFileDescriptors(ctx *context.Context) ([]*packages_model.PackageFileDescriptor, error) { + pvs, err := packages_model.GetVersionsByPackageType(ctx, ctx.Package.Owner.ID, packages_model.TypeDebian) + if err != nil { + return nil, err + } + + pds, err := packages_model.GetPackageDescriptors(ctx, pvs) + if err != nil { + return nil, err + } + + files := make([]*packages_model.PackageFileDescriptor, 0) + for _, pd := range pds { + files = append(files, pd.Files...) + } + + return files, nil +} + +func GetDebianFilesByArch(ctx *context.Context) (map[string][]*packages_model.PackageFileDescriptor, error) { + pfds, err := GetDebianFileDescriptors(ctx) + if err != nil { + return nil, err + } + + splitter := regexp.MustCompile(`^([^_]+)_([^_]+)_([^.]+).deb$`) + + files := make(map[string][]*packages_model.PackageFileDescriptor) + + for _, pfd := range pfds { + filename := pfd.File.Name + matches := splitter.FindStringSubmatch(filename) + if matches == nil || len(matches) != 4 { + log.Error("Found invalid filename: %s", filename) + return nil, errors.New("Found invalid filename") + } + + arch := matches[3] + files[arch] = append(files[arch], pfd) + } + + return files, nil +} + +func GetArchIndex(ctx *context.Context) { + ctx.Data["IndexFiles"] = map[string]string{ + "../": "../", + "Packages": "Packages", + "Packages.gz": "Packages.gz", + "Release": "Release", + } + + // This does mean that "amd64" and "binary-amd64" can both be used + // Don't think that's an issue (?) + arch := ctx.Params("packagearch") + if len(arch) > 7 && arch[:7] == "binary-" { + arch = arch[7:] + } + + archs, err := GetDebianFilesByArch(ctx) + if err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } + + _, exists := archs[arch] + if !exists { + ctx.Status(http.StatusNotFound) + return + } + + ctx.Data["IndexPath"] = ctx.Req.URL.Path + ctx.HTML(http.StatusOK, "api/packages/debian/index") +} + +func GetIndex(ctx *context.Context) { + basePath := "/api/packages/" + ctx.Params("username") + "/debian" + + relPath, err := filepath.Rel(basePath, ctx.Req.URL.Path) + if err != nil { + apiError(ctx, http.StatusInternalServerError, err) + log.Error("Path '%s' is not inside '%s'?", ctx.Req.URL.Path, basePath) + return + } + + log.Info("RelPath: %s", relPath) + + switch relPath { + case ".": + ctx.Data["IndexFiles"] = map[string]string{ + "../": "./", + "dists/": "dists/", + "pool/": "pool/", + } + case "pool": + files := map[string]string{ + "../": "../", + } + + // Add all the Debian package files + pfds, err := GetDebianFileDescriptors(ctx) + if err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } + + for _, pfd := range pfds { + files[pfd.File.Name] = pfd.File.Name + } + + ctx.Data["IndexFiles"] = files + case "dists": + ctx.Data["IndexFiles"] = map[string]string{ + "../": "../", + "gitea/": "gitea/", + } + case "dists/gitea": + ctx.Data["IndexFiles"] = map[string]string{ + "../": "../", + "main/": "main/", + "InRelease": "InRelease", + "Release": "Release", + "Release.gpg": "Release.gpg", + } + case "dists/gitea/main": + files := map[string]string{ + "../": "../", + } + + // Add directory for each arch + archs, err := GetDebianFilesByArch(ctx) + if err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } + + for a := range archs { + var name string + switch a { + case "source": + name = a + "/" + default: + name = "binary-" + a + "/" + } + files[name] = name + } + + ctx.Data["IndexFiles"] = files + } + ctx.Data["IndexPath"] = ctx.Req.URL.Path + ctx.HTML(http.StatusOK, "api/packages/debian/index") +} + +// TODO +func GetPackages(ctx *context.Context) { + ctx.PlainText(http.StatusNotImplemented, "Not yet implemented") +} +func GetPackagesGZ(ctx *context.Context) { + ctx.PlainText(http.StatusNotImplemented, "Not yet implemented") +} +func GetRelease(ctx *context.Context) { + ctx.PlainText(http.StatusNotImplemented, "Not yet implemented") +} +func GetReleaseGPG(ctx *context.Context) { + ctx.PlainText(http.StatusNotImplemented, "Not yet implemented") +} +func GetInRelease(ctx *context.Context) { + ctx.PlainText(http.StatusNotImplemented, "Not yet implemented") +} diff --git a/templates/api/packages/debian/index.tmpl b/templates/api/packages/debian/index.tmpl new file mode 100644 index 0000000000000..fe0dd1a9128ea --- /dev/null +++ b/templates/api/packages/debian/index.tmpl @@ -0,0 +1,14 @@ + + + + Gitea Debian Repository + + +

Index of {{.IndexPath}}

+
+
+{{range $fn, $fp := .IndexFiles}}{{$fn}}
+{{end}}
+
+ + \ No newline at end of file From d4daf6ea18073f3ccf06f60da8ae94a9a2337ce1 Mon Sep 17 00:00:00 2001 From: Brian Hong Date: Sat, 7 Jan 2023 09:36:48 -0500 Subject: [PATCH 03/27] Implement Debian repo Releases/Packages Referenced from github.com/esell/deb-simple --- go.mod | 1 + go.sum | 1 + routers/api/packages/api.go | 3 +- routers/api/packages/debian/debian.go | 92 +++++++++-- routers/api/packages/debian/packages.go | 199 ++++++++++++++++++++++++ routers/api/packages/debian/releases.go | 157 +++++++++++++++++++ templates/package/content/debian.tmpl | 3 +- 7 files changed, 445 insertions(+), 11 deletions(-) create mode 100644 routers/api/packages/debian/packages.go create mode 100644 routers/api/packages/debian/releases.go diff --git a/go.mod b/go.mod index 64c293bc25cdc..1a87cef67890d 100644 --- a/go.mod +++ b/go.mod @@ -129,6 +129,7 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/speakeasy v0.1.0 // indirect github.com/bits-and-blooms/bitset v1.3.3 // indirect + github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb // indirect github.com/blevesearch/bleve_index_api v1.0.4 // indirect github.com/blevesearch/geo v0.1.15 // indirect github.com/blevesearch/go-porterstemmer v1.0.3 // indirect diff --git a/go.sum b/go.sum index bde61a8c5b154..f61cfa46d3b75 100644 --- a/go.sum +++ b/go.sum @@ -223,6 +223,7 @@ github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edY github.com/bits-and-blooms/bitset v1.3.3 h1:R1XWiopGiXf66xygsiLpzLo67xEYvMkHw3w+rCOSAwg= github.com/bits-and-blooms/bitset v1.3.3/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4= github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI= github.com/blevesearch/bleve/v2 v2.0.5/go.mod h1:ZjWibgnbRX33c+vBRgla9QhPb4QOjD6fdVJ+R1Bk8LM= github.com/blevesearch/bleve/v2 v2.3.5 h1:1wuR7eB8Fk9UaCaBUfnQt5V7zIpi4VDok9ExN7Rl+/8= diff --git a/routers/api/packages/api.go b/routers/api/packages/api.go index e6f71f6897749..d1d46dba52ff4 100644 --- a/routers/api/packages/api.go +++ b/routers/api/packages/api.go @@ -175,6 +175,7 @@ func CommonRoutes(ctx gocontext.Context) *web.Route { }, reqPackageAccess(perm.AccessModeWrite)) r.Get("/", debian.GetIndex) + r.Get("/debian.key", debian.GetPublicKey) r.Group("/pool", func() { r.Get("/", debian.GetIndex) r.Get("/{filename}", debian.GetPackage) @@ -189,7 +190,7 @@ func CommonRoutes(ctx gocontext.Context) *web.Route { r.Get("/", debian.GetArchIndex) r.Get("/Packages", debian.GetPackages) r.Get("/Packages.gz", debian.GetPackagesGZ) - r.Get("/Release", debian.GetRelease) + r.Get("/Release", debian.GetArchRelease) }) }) r.Get("/Release", debian.GetRelease) diff --git a/routers/api/packages/debian/debian.go b/routers/api/packages/debian/debian.go index 3428b1bc59c8a..55cfaeeb2bdd2 100644 --- a/routers/api/packages/debian/debian.go +++ b/routers/api/packages/debian/debian.go @@ -7,6 +7,7 @@ import ( "errors" "fmt" "net/http" + "os" "path/filepath" "regexp" "strings" @@ -15,6 +16,7 @@ import ( "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/log" packages_module "code.gitea.io/gitea/modules/packages" + "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/routers/api/packages/helper" packages_service "code.gitea.io/gitea/services/packages" ) @@ -139,6 +141,7 @@ func PutPackage(ctx *context.Context) { return } + DebianRepoUpdate(ctx, packageArch) ctx.Status(http.StatusCreated) } @@ -188,6 +191,7 @@ func DeletePackage(ctx *context.Context) { } } + DebianRepoUpdate(ctx, packageArch) ctx.Status(http.StatusNoContent) } @@ -281,9 +285,10 @@ func GetIndex(ctx *context.Context) { switch relPath { case ".": ctx.Data["IndexFiles"] = map[string]string{ - "../": "./", - "dists/": "dists/", - "pool/": "pool/", + "../": "./", + "dists/": "dists/", + "pool/": "pool/", + "debian.key": "debian.key", } case "pool": files := map[string]string{ @@ -344,19 +349,88 @@ func GetIndex(ctx *context.Context) { ctx.HTML(http.StatusOK, "api/packages/debian/index") } -// TODO +func DebianRepoUpdate(ctx *context.Context, arch string) { + if err := CreatePackagesGz(ctx, arch); err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } + if err := CreateRelease(ctx); err != nil { + apiError(ctx, http.StatusInternalServerError, err) + return + } +} + +func GetArchRelease(ctx *context.Context) { + data := fmt.Sprintf("Component: main\nArchitecture: %s\n", ctx.Params("packagearch")) + ctx.PlainText(http.StatusOK, data) +} + func GetPackages(ctx *context.Context) { - ctx.PlainText(http.StatusNotImplemented, "Not yet implemented") + basePath := filepath.Join(setting.AppDataPath, "debian_repo") + archPath := filepath.Join(basePath, ctx.Package.Owner.Name, ctx.Params("packagearch")) + packagesPath := filepath.Join(archPath, "Packages") + f, err := os.Open(packagesPath) + if err != nil { + apiError(ctx, http.StatusInternalServerError, err) + } + ctx.ServeContent(f, &context.ServeHeaderOptions{ + Filename: "Packages", + }) } + func GetPackagesGZ(ctx *context.Context) { - ctx.PlainText(http.StatusNotImplemented, "Not yet implemented") + basePath := filepath.Join(setting.AppDataPath, "debian_repo") + archPath := filepath.Join(basePath, ctx.Package.Owner.Name, ctx.Params("packagearch")) + packagesGzPath := filepath.Join(archPath, "Packages.gz") + f, err := os.Open(packagesGzPath) + if err != nil { + apiError(ctx, http.StatusInternalServerError, err) + } + ctx.ServeContent(f, &context.ServeHeaderOptions{ + Filename: "Packages.gz", + }) } + func GetRelease(ctx *context.Context) { - ctx.PlainText(http.StatusNotImplemented, "Not yet implemented") + path := filepath.Join(setting.AppDataPath, "debian_repo", ctx.Package.Owner.Name, "Release") + f, err := os.Open(path) + if err != nil { + apiError(ctx, http.StatusInternalServerError, err) + } + ctx.ServeContent(f, &context.ServeHeaderOptions{ + Filename: "Release", + }) } + func GetReleaseGPG(ctx *context.Context) { - ctx.PlainText(http.StatusNotImplemented, "Not yet implemented") + path := filepath.Join(setting.AppDataPath, "debian_repo", ctx.Package.Owner.Name, "Release.gpg") + f, err := os.Open(path) + if err != nil { + apiError(ctx, http.StatusInternalServerError, err) + } + ctx.ServeContent(f, &context.ServeHeaderOptions{ + Filename: "Release.gpg", + }) } + func GetInRelease(ctx *context.Context) { - ctx.PlainText(http.StatusNotImplemented, "Not yet implemented") + path := filepath.Join(setting.AppDataPath, "debian_repo", ctx.Package.Owner.Name, "InRelease") + f, err := os.Open(path) + if err != nil { + apiError(ctx, http.StatusInternalServerError, err) + } + ctx.ServeContent(f, &context.ServeHeaderOptions{ + Filename: "InRelease", + }) +} + +func GetPublicKey(ctx *context.Context) { + path := filepath.Join(setting.AppDataPath, "debian_public.gpg") + f, err := os.Open(path) + if err != nil { + apiError(ctx, http.StatusInternalServerError, err) + } + ctx.ServeContent(f, &context.ServeHeaderOptions{ + Filename: "debian.key", + }) } diff --git a/routers/api/packages/debian/packages.go b/routers/api/packages/debian/packages.go new file mode 100644 index 0000000000000..f5542b5ab6121 --- /dev/null +++ b/routers/api/packages/debian/packages.go @@ -0,0 +1,199 @@ +package debian + +import ( + "archive/tar" + "bytes" + "compress/gzip" + "errors" + "fmt" + "io" + "os" + "path/filepath" + "strings" + + packages_model "code.gitea.io/gitea/models/packages" + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/services/packages" + "github.com/blakesmith/ar" + lzma "github.com/xi2/xz" +) + +type Compression int + +const ( + LZMA Compression = iota + GZIP +) + +func CreatePackagesGz(ctx *context.Context, arch string) error { + basePath := filepath.Join(setting.AppDataPath, "debian_repo") + + archDir := arch + if arch != "source" { + archDir = "binary-" + archDir + } + archPath := filepath.Join(basePath, ctx.Package.Owner.Name, archDir) + + info, err := os.Stat(archPath) + if err != nil { + if os.IsNotExist(err) { + if err2 := os.MkdirAll(archPath, 0775); err2 != nil { + return fmt.Errorf("Cannot create Debian repo path: %s", err) + } + } else { + return fmt.Errorf("Cannot stat Debian repo path: %s", err) + } + } else { + if !info.IsDir() { + return fmt.Errorf("Debian repo path is not a directory!") + } + } + + packagesPath := filepath.Join(archPath, "Packages") + packagesGzPath := filepath.Join(archPath, "Packages.gz") + + packagesFile, err := os.Create(packagesPath) + if err != nil { + return fmt.Errorf("Failed to create Packages file %s: %s", packagesPath, err) + } + defer packagesFile.Close() + + packagesGzFile, err := os.Create(packagesGzPath) + if err != nil { + return fmt.Errorf("Failed to create Packages.gz file %s: %s", packagesGzPath, err) + } + defer packagesGzFile.Close() + + gzOut := gzip.NewWriter(packagesGzFile) + defer gzOut.Close() + + writer := io.MultiWriter(packagesFile, gzOut) + + allFiles, err := GetDebianFilesByArch(ctx) + if err != nil { + return err + } + + archFiles, exists := allFiles[arch] + if !exists { + return fmt.Errorf("No files in arch \"%s\"!", arch) + } + + for i, file := range archFiles { + var packBuf bytes.Buffer + tempCtlData, err := InspectPackage(ctx, file) + if err != nil { + return err + } + + blob, err := packages_model.GetBlobByID(ctx, file.File.BlobID) + if err != nil { + return err + } + + packBuf.WriteString(tempCtlData) + dir := filepath.Join("/pool", file.File.Name) + fmt.Fprintf(&packBuf, "Filename: %s\n", dir) + fmt.Fprintf(&packBuf, "Size: %d\n", blob.Size) + fmt.Fprintf(&packBuf, "MD5sum: %s\n", blob.HashMD5) + fmt.Fprintf(&packBuf, "SHA1: %s\n", blob.HashSHA1) + fmt.Fprintf(&packBuf, "SHA256: %s\n", blob.HashSHA256) + + if i != (len(archFiles) - 1) { + packBuf.WriteString("\n\n") + } + writer.Write(packBuf.Bytes()) + } + + return nil +} + +func InspectPackage(ctx *context.Context, fd *packages_model.PackageFileDescriptor) (string, error) { + // blob, err := packages_model.GetBlobByID(ctx, file.File.BlobID) + // if err != nil { + // return "", err + // } + + fstream, _, err := packages.GetFileStreamByPackageVersionAndFileID(ctx, ctx.Package.Owner, fd.File.VersionID, fd.File.ID) + if err != nil { + return "", err + } + defer fstream.Close() + reader := ar.NewReader(fstream) + + var controlBuf bytes.Buffer + for { + header, err := reader.Next() + if err == io.EOF { + break + } + + if err != nil { + return "", fmt.Errorf("Error in inspect loop: %s", err) + } + + if strings.Contains(header.Name, "control.tar") { + var compression Compression + switch strings.TrimRight(header.Name, "/") { + case "control.tar.gz": + compression = GZIP + case "control.tar.xz": + compression = LZMA + default: + return "", errors.New("No control file found") + } + + io.Copy(&controlBuf, reader) + return InspectPackageControl(compression, controlBuf) + } + } + + return "", errors.New("Unreachable?") +} + +func InspectPackageControl(comp Compression, data bytes.Buffer) (string, error) { + var tarReader *tar.Reader + var err error + + switch comp { + case GZIP: + var compFile *gzip.Reader + compFile, err = gzip.NewReader(bytes.NewReader(data.Bytes())) + tarReader = tar.NewReader(compFile) + case LZMA: + var compFile *lzma.Reader + compFile, err = lzma.NewReader(bytes.NewReader(data.Bytes()), lzma.DefaultDictMax) + tarReader = tar.NewReader(compFile) + } + + if err != nil { + return "", fmt.Errorf("Error creating gzip/lzma reader: %s", err) + } + + var controlBuf bytes.Buffer + for { + header, err := tarReader.Next() + if err == io.EOF { + break + } + + if err != nil { + return "", fmt.Errorf("Failed to inspect package: %s", err) + } + + name := header.Name + switch header.Typeflag { + case tar.TypeDir: + continue + case tar.TypeReg: + switch name { + case "control", "./control": + io.Copy(&controlBuf, tarReader) + return strings.TrimRight(controlBuf.String(), "\n") + "\n", nil + } + } + } + + return "", nil +} diff --git a/routers/api/packages/debian/releases.go b/routers/api/packages/debian/releases.go new file mode 100644 index 0000000000000..74b877fdd40ae --- /dev/null +++ b/routers/api/packages/debian/releases.go @@ -0,0 +1,157 @@ +package debian + +import ( + "crypto/md5" + "crypto/sha1" + "crypto/sha256" + "encoding/hex" + "fmt" + "io" + "os" + "path/filepath" + "strings" + "time" + + "code.gitea.io/gitea/modules/context" + "code.gitea.io/gitea/modules/setting" + "github.com/keybase/go-crypto/openpgp" + "github.com/keybase/go-crypto/openpgp/clearsign" + "github.com/keybase/go-crypto/openpgp/packet" +) + +func CreateRelease(ctx *context.Context) error { + basePath := filepath.Join(setting.AppDataPath, "debian_repo", ctx.Package.Owner.Name) + releasePath := filepath.Join(basePath, "Release") + + releaseFile, err := os.Create(releasePath) + if err != nil { + return fmt.Errorf("Failed to create Release file: %s", err) + } + defer releaseFile.Close() + + allFiles, err := GetDebianFilesByArch(ctx) + if err != nil { + return err + } + + archs := make([]string, 0) + for a := range allFiles { + archs = append(archs, a) + } + + currentTime := time.Now().UTC() + fmt.Fprintf(releaseFile, "Suite: gitea\n") + fmt.Fprintf(releaseFile, "Codename: gitea\n") + fmt.Fprintf(releaseFile, "Components: main\n") + fmt.Fprintf(releaseFile, "Architectures: %s\n", strings.Join(archs, " ")) + fmt.Fprintf(releaseFile, "Date: %s\n", currentTime.Format(time.RFC1123)) + + var md5sums, sha1sums, sha256sums strings.Builder + + err = filepath.Walk(basePath, func(path string, file os.FileInfo, err error) error { + if err != nil { + return err + } + + if strings.HasSuffix(path, "Packages.gz") || strings.HasSuffix(path, "Packages") { + var ( + md5hash = md5.New() + sha1hash = sha1.New() + sha256hash = sha256.New() + ) + + relPath, _ := filepath.Rel(basePath, path) + relPath = filepath.Join("main", relPath) + f, err := os.Open(path) + if err != nil { + return fmt.Errorf("Error opening Packages file for reading: %s", err) + } + + if _, err = io.Copy(io.MultiWriter(md5hash, sha1hash, sha256hash), f); err != nil { + return fmt.Errorf("Error hashing file for Release list: %s", err) + } + + fmt.Fprintf(&md5sums, " %s %d %s\n", hex.EncodeToString(md5hash.Sum(nil)), file.Size(), relPath) + fmt.Fprintf(&sha1sums, " %s %d %s\n", hex.EncodeToString(sha1hash.Sum(nil)), file.Size(), relPath) + fmt.Fprintf(&sha256sums, " %s %d %s\n", hex.EncodeToString(sha256hash.Sum(nil)), file.Size(), relPath) + f = nil + } + return nil + }) + + if err != nil { + return fmt.Errorf("Error scanning for Package files: %s", err) + } + + releaseFile.WriteString("MD5Sum:\n") + releaseFile.WriteString(md5sums.String()) + releaseFile.WriteString("SHA1:\n") + releaseFile.WriteString(sha1sums.String()) + releaseFile.WriteString("SHA256:\n") + releaseFile.WriteString(sha256sums.String()) + + if err = SignRelease(ctx); err != nil { + return fmt.Errorf("Error signing Release file: %s", err) + } + + return nil +} + +func GetEntity() (*openpgp.Entity, error) { + keyPath := filepath.Join(setting.AppDataPath, "debian.gpg") + keyFile, err := os.Open(keyPath) + if err != nil { + return nil, fmt.Errorf("Error opening key file: %s", err) + } + defer keyFile.Close() + + r := packet.NewReader(keyFile) + return openpgp.ReadEntity(r) +} + +func SignRelease(ctx *context.Context) error { + e, err := GetEntity() + if err != nil { + return fmt.Errorf("Cannot read entity from key file: %s", err) + } + + basePath := filepath.Join(setting.AppDataPath, "debian_repo", ctx.Package.Owner.Name) + releasePath := filepath.Join(basePath, "Release") + releaseGzPath := filepath.Join(basePath, "Release.gpg") + inreleasePath := filepath.Join(basePath, "InRelease") + + releaseFile, err := os.Open(releasePath) + if err != nil { + return fmt.Errorf("Error opening Release file (%s) for reading: %s", releasePath, err) + } + defer releaseFile.Close() + + releaseGzFile, err := os.Create(releaseGzPath) + if err != nil { + return fmt.Errorf("Error opening Release.gpg file (%s) for writing: %s", releaseGzPath, err) + } + defer releaseGzFile.Close() + + err = openpgp.ArmoredDetachSign(releaseGzFile, e, releaseFile, nil) + if err != nil { + return fmt.Errorf("Error writing signature to Release.gpg file: %s", err) + } + + releaseFile.Seek(0, 0) + + inreleaseFile, err := os.Create(inreleasePath) + if err != nil { + return fmt.Errorf("Error opening InRelease file (%s) for writing: %s", inreleasePath, err) + } + defer inreleaseFile.Close() + + writer, err := clearsign.Encode(inreleaseFile, e.PrivateKey, nil) + if err != nil { + return fmt.Errorf("Error signing InRelease file: %s", err) + } + + io.Copy(writer, releaseFile) + writer.Close() + + return nil +} diff --git a/templates/package/content/debian.tmpl b/templates/package/content/debian.tmpl index 9e5ce239d6875..77963a1c344b0 100644 --- a/templates/package/content/debian.tmpl +++ b/templates/package/content/debian.tmpl @@ -14,7 +14,8 @@
-
echo "deb {{AppUrl}}api/packages/{{$.PackageDescriptor.Owner.Name}}/debian gitea main" \
+					
curl {{AppUrl}}api/packages/{{$.PackageDescriptor.Owner.Name}}/debian/debian.key | sudo tee /etc/apt/trusted.gpg.d/gitea-repo.asc
+echo "deb {{AppUrl}}api/packages/{{$.PackageDescriptor.Owner.Name}}/debian gitea main" \
   | sudo tee /etc/apt/sources.list.d/gitea.list
 sudo apt update
 sudo apt install {{$.PackageDescriptor.Package.Name}}
From bc2f42f2ad24eeb5a109b51e6c39d7dbdbbbbc47 Mon Sep 17 00:00:00 2001 From: Brian Hong Date: Sat, 7 Jan 2023 10:10:21 -0500 Subject: [PATCH 04/27] Add docs --- docs/content/doc/packages/debian.en-us.md | 157 ++++++++++++++++++++ docs/content/doc/packages/overview.en-us.md | 1 + templates/package/content/debian.tmpl | 3 +- 3 files changed, 160 insertions(+), 1 deletion(-) create mode 100644 docs/content/doc/packages/debian.en-us.md diff --git a/docs/content/doc/packages/debian.en-us.md b/docs/content/doc/packages/debian.en-us.md new file mode 100644 index 0000000000000..d50271afa0f8b --- /dev/null +++ b/docs/content/doc/packages/debian.en-us.md @@ -0,0 +1,157 @@ +--- +date: "2023-01-07T00:00:00+00:00" +title: "Debian Packages Repository" +slug: "packages/debian" +draft: false +toc: false +menu: + sidebar: + parent: "packages" + name: "Debian" + weight: 45 + identifier: "debian" +--- + +# Debian Packages Repository + +Publish Debian packages to a APT repository for your user or organization. + +**Table of Contents** + +{{< toc >}} + +## Authenticate to the package registry + +To authenticate to the Package Registry, you need to provide [custom HTTP headers or use HTTP Basic authentication]({{< relref "doc/developers/api-usage.en-us.md#authentication" >}}). + +## Publish a package + +To publish a Debian package, perform a HTTP PUT operation with the package content in the request body. +You cannot publish a file with the same name twice to a package. You must delete the existing package version first. + +``` +PUT https://gitea.example.com/api/packages/{owner}/debian/files/{package_name}/{package_version}/{package_architecture} +``` + +| Parameter | Description | +| --------------------- | ----------- | +| `owner` | The owner of the package. | +| `package_name` | The package name. It can contain only lowercase letters (`a-z`), uppercase letter (`A-Z`), numbers (`0-9`), dots (`.`), hyphens (`-`), pluses (`+`), or underscores (`_`). | +| `package_version` | The package version, a non-empty string without trailing or leading whitespaces. | +| `package_architecture` | The package architecture. Can be a Debian machine architecture as described in [Debian Architecture specifications](https://www.debian.org/doc/debian-policy/ch-customized-programs.html#s-arch-spec), `all`, or `source`. | + +Example request using HTTP Basic authentication: + +```shell +curl --user your_username:your_password_or_token \ + --upload-file path/to/file.deb \ + https://gitea.example.com/api/packages/testuser/debian/files/test-package/1.0.0/amd64 +``` + +If you are using 2FA or OAuth use a [personal access token]({{< relref "doc/developers/api-usage.en-us.md#authentication" >}}) instead of the password. + +The server reponds with the following HTTP Status codes. + +| HTTP Status Code | Meaning | +| ----------------- | ------- | +| `201 Created` | The package has been published. | +| `400 Bad Request` | The package name and/or version and/or file name are invalid. | +| `409 Conflict` | A file with the same name exist already in the package. | + +## Delete a package file + +To delete a file of a generic package perform a HTTP DELETE operation. This will delete the package version too if there is no file left. + +``` +DELETE https://gitea.example.com/api/packages/{owner}/debian/files/{package_name}/{package_version}/{package_architecture} +``` + +| Parameter | Description | +| ---------------------- | ----------- | +| `owner` | The owner of the package. | +| `package_name` | The package name. | +| `package_version` | The package version. | +| `package_architecture` | The package architecture. | + +Example request using HTTP Basic authentication: + +```shell +curl --user your_username:your_token_or_password -X DELETE \ + https://gitea.example.com/api/packages/testuser/debian/files/test-package/1.0.0/amd64 +``` + +The server reponds with the following HTTP Status codes. + +| HTTP Status Code | Meaning | +| ----------------- | ------- | +| `204 No Content` | Success | +| `404 Not Found` | The package or file was not found. | + +## Download a package + +To download a generic package perform a HTTP GET operation. + +``` +GET https://gitea.example.com/api/packages/{owner}/debian/pool/{package_name}_{package_version}_{package_architecture}.deb +``` + +| Parameter | Description | +| ---------------------- | ----------- | +| `owner` | The owner of the package. | +| `package_name` | The package name. | +| `package_version` | The package version. | +| `package_architecture` | The package architecture. | + +The file content is served in the response body. The response content type is `application/octet-stream`. + +Example request using HTTP Basic authentication: + +```shell +curl https://gitea.example.com/api/packages/testuser/debian/pool/test-package_1.0.0_amd64.deb +``` + +The server reponds with the following HTTP Status codes. + +| HTTP Status Code | Meaning | +| ----------------- | ------- | +| `200 OK` | Success | +| `404 Not Found` | The package or file was not found. | + +## Using APT to download and install packages + +### Generating a PGP key pair + +The APT repository needs a PGP keypair to sign the Release files. With openpgp installed, generate a keypair with the following: + +```shell +gpg --full-gen-key +``` + +### Adding signing keys to Gitea Debian repository + +The private and public keys can be exported and should be placed in Gitea's data directory: + +```shell +gpg --export-secret-key > /debian.gpg +gpg --export --armor > /debian_public.gpg +``` + +Once the keys have been added, the Release and Packages files are generated after upload or deletion of a package file. + +### Adding the key and repository to APT + +To add the key from the server to the APT keyring: + +```shell +curl https://gitea.example.com/api/packages/test-user/debian/debian.key \ + | sudo tee /etc/apt/trusted.gpg.d/gitea-repo.asc +``` + +The URL of the repository is: `https:///api/packages//debian`. +The "distro" portion should be `gitea` and so far, only `main` component is supported. + +```shell +echo "deb https://gitea.example.com/api/packages/test-user/debian gitea main" \ + | sudo tee /etc/apt/sources.list.d/gitea.list +sudo apt update +``` diff --git a/docs/content/doc/packages/overview.en-us.md b/docs/content/doc/packages/overview.en-us.md index 239ba6834e88d..06ac9e961b6f4 100644 --- a/docs/content/doc/packages/overview.en-us.md +++ b/docs/content/doc/packages/overview.en-us.md @@ -29,6 +29,7 @@ The following package managers are currently supported: | [Composer]({{< relref "doc/packages/composer.en-us.md" >}}) | PHP | `composer` | | [Conan]({{< relref "doc/packages/conan.en-us.md" >}}) | C++ | `conan` | | [Container]({{< relref "doc/packages/container.en-us.md" >}}) | - | any OCI compliant client | +| [Debian]({{< relref "doc/packages/debian.en-us.md" >}}) | - | any HTTP client | | [Generic]({{< relref "doc/packages/generic.en-us.md" >}}) | - | any HTTP client | | [Helm]({{< relref "doc/packages/helm.en-us.md" >}}) | - | any HTTP client, `cm-push` | | [Maven]({{< relref "doc/packages/maven.en-us.md" >}}) | Java | `mvn`, `gradle` | diff --git a/templates/package/content/debian.tmpl b/templates/package/content/debian.tmpl index 77963a1c344b0..cc24e790a3459 100644 --- a/templates/package/content/debian.tmpl +++ b/templates/package/content/debian.tmpl @@ -14,7 +14,8 @@
-
curl {{AppUrl}}api/packages/{{$.PackageDescriptor.Owner.Name}}/debian/debian.key | sudo tee /etc/apt/trusted.gpg.d/gitea-repo.asc
+					
curl {{AppUrl}}api/packages/{{$.PackageDescriptor.Owner.Name}}/debian/debian.key \
+  | sudo tee /etc/apt/trusted.gpg.d/gitea-repo.asc
 echo "deb {{AppUrl}}api/packages/{{$.PackageDescriptor.Owner.Name}}/debian gitea main" \
   | sudo tee /etc/apt/sources.list.d/gitea.list
 sudo apt update

From d8d69fa838291a3e8d2448a60a5dbb3e4abd078f Mon Sep 17 00:00:00 2001
From: Brian Hong 
Date: Wed, 11 Jan 2023 10:55:14 -0500
Subject: [PATCH 05/27] Include "binary-all" packages in "binary-*"

---
 routers/api/packages/debian/packages.go | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/routers/api/packages/debian/packages.go b/routers/api/packages/debian/packages.go
index f5542b5ab6121..fda3904729ce9 100644
--- a/routers/api/packages/debian/packages.go
+++ b/routers/api/packages/debian/packages.go
@@ -80,6 +80,10 @@ func CreatePackagesGz(ctx *context.Context, arch string) error {
 		return fmt.Errorf("No files in arch \"%s\"!", arch)
 	}
 
+	if arch != "source" {
+		archFiles = append(archFiles, allFiles["all"]...)
+	}
+
 	for i, file := range archFiles {
 		var packBuf bytes.Buffer
 		tempCtlData, err := InspectPackage(ctx, file)

From 6d50fda65cd9ca23d1f153c435cfc5949ca295f7 Mon Sep 17 00:00:00 2001
From: KN4CK3R 
Date: Sat, 4 Feb 2023 15:57:51 +0000
Subject: [PATCH 06/27] Add support for Debian packages.

---
 assets/go-licenses.json                       |   5 +
 cmd/migrate_storage_test.go                   |   2 +-
 go.mod                                        |   4 +-
 go.sum                                        |   1 -
 models/migrations/migrations.go               |   6 +
 models/migrations/v1_20/v240.go               |  23 +
 models/packages/container/search.go           |  11 +-
 models/packages/debian/search.go              | 133 +++++
 models/packages/descriptor.go                 |  23 +-
 models/packages/package.go                    |  13 +-
 models/packages/package_file.go               |  25 +-
 models/packages/package_version.go            |   6 +-
 modules/packages/debian/metadata.go           | 208 +++++++-
 modules/packages/debian/metadata_test.go      | 171 ++++++
 modules/packages/hashed_buffer.go             |  22 +-
 modules/packages/nuget/symbol_extractor.go    |   2 +-
 modules/setting/packages.go                   |   2 +
 modules/util/filebuffer/file_backed_buffer.go |   5 +-
 options/locale/locale_en-US.ini               |   8 +
 routers/api/packages/api.go                   |  41 +-
 routers/api/packages/composer/composer.go     |   2 +-
 routers/api/packages/conan/conan.go           |  13 +-
 routers/api/packages/container/container.go   |   4 +-
 routers/api/packages/debian/debian.go         | 496 +++++++-----------
 routers/api/packages/debian/packages.go       | 203 -------
 routers/api/packages/debian/releases.go       | 157 ------
 routers/api/packages/generic/generic.go       |   2 +-
 routers/api/packages/helm/helm.go             |   2 +-
 routers/api/packages/maven/maven.go           |   2 +-
 routers/api/packages/npm/npm.go               |   2 +-
 routers/api/packages/nuget/nuget.go           |   6 +-
 routers/api/packages/pub/pub.go               |   2 +-
 routers/api/packages/pypi/pypi.go             |   2 +-
 routers/api/packages/rubygems/rubygems.go     |   2 +-
 routers/api/packages/vagrant/vagrant.go       |   2 +-
 routers/web/user/package.go                   |  29 +-
 services/packages/debian/repository.go        | 378 +++++++++++++
 services/packages/packages.go                 |  48 +-
 templates/package/content/debian.tmpl         |  65 ++-
 templates/package/metadata/debian.tmpl        |   2 +
 40 files changed, 1329 insertions(+), 801 deletions(-)
 create mode 100644 models/migrations/v1_20/v240.go
 create mode 100644 models/packages/debian/search.go
 create mode 100644 modules/packages/debian/metadata_test.go
 delete mode 100644 routers/api/packages/debian/packages.go
 delete mode 100644 routers/api/packages/debian/releases.go
 create mode 100644 services/packages/debian/repository.go

diff --git a/assets/go-licenses.json b/assets/go-licenses.json
index 7f5b647796da0..bb54aae21c0a3 100644
--- a/assets/go-licenses.json
+++ b/assets/go-licenses.json
@@ -104,6 +104,11 @@
     "path": "github.com/bits-and-blooms/bitset/LICENSE",
     "licenseText": "Copyright (c) 2014 Will Fitzgerald. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are\nmet:\n\n   * Redistributions of source code must retain the above copyright\nnotice, this list of conditions and the following disclaimer.\n   * Redistributions in binary form must reproduce the above\ncopyright notice, this list of conditions and the following disclaimer\nin the documentation and/or other materials provided with the\ndistribution.\n   * Neither the name of Google Inc. nor the names of its\ncontributors may be used to endorse or promote products derived from\nthis software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\nLIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR\nA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT\nOWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\nSPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\nLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\nDATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY\nTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
   },
+  {
+    "name": "github.com/blakesmith/ar",
+    "path": "github.com/blakesmith/ar/COPYING",
+    "licenseText": "Copyright (c) 2013 Blake Smith \u003cblakesmith0@gmail.com\u003e\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE.\n\n"
+  },
   {
     "name": "github.com/blevesearch/bleve/v2",
     "path": "github.com/blevesearch/bleve/v2/LICENSE",
diff --git a/cmd/migrate_storage_test.go b/cmd/migrate_storage_test.go
index aae366c0cf524..c13205810600b 100644
--- a/cmd/migrate_storage_test.go
+++ b/cmd/migrate_storage_test.go
@@ -25,7 +25,7 @@ func TestMigratePackages(t *testing.T) {
 	creator := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
 
 	content := "package main\n\nfunc main() {\nfmt.Println(\"hi\")\n}\n"
-	buf, err := packages_module.CreateHashedBufferFromReader(strings.NewReader(content), 1024)
+	buf, err := packages_module.CreateHashedBufferFromReaderWithSize(strings.NewReader(content), 1024)
 	assert.NoError(t, err)
 	defer buf.Close()
 
diff --git a/go.mod b/go.mod
index 6c4f0ef3abdad..cc18302ef1406 100644
--- a/go.mod
+++ b/go.mod
@@ -16,6 +16,7 @@ require (
 	github.com/NYTimes/gziphandler v1.1.1
 	github.com/PuerkitoBio/goquery v1.8.0
 	github.com/alecthomas/chroma/v2 v2.4.0
+	github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb
 	github.com/blevesearch/bleve/v2 v2.3.6
 	github.com/buildkite/terminal-to-html/v3 v3.7.0
 	github.com/caddyserver/certmagic v0.17.2
@@ -88,6 +89,7 @@ require (
 	github.com/stretchr/testify v1.8.1
 	github.com/syndtr/goleveldb v1.0.0
 	github.com/tstranex/u2f v1.0.0
+	github.com/ulikunitz/xz v0.5.11
 	github.com/unrolled/render v1.5.0
 	github.com/urfave/cli v1.22.10
 	github.com/xanzy/go-gitlab v0.78.0
@@ -129,7 +131,6 @@ require (
 	github.com/aymerick/douceur v0.2.0 // indirect
 	github.com/beorn7/perks v1.0.1 // indirect
 	github.com/bits-and-blooms/bitset v1.3.3 // indirect
-	github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb // indirect
 	github.com/blevesearch/bleve_index_api v1.0.5 // indirect
 	github.com/blevesearch/geo v0.1.16 // indirect
 	github.com/blevesearch/go-porterstemmer v1.0.3 // indirect
@@ -246,7 +247,6 @@ require (
 	github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
 	github.com/subosito/gotenv v1.3.0 // indirect
 	github.com/toqueteos/webbrowser v1.2.0 // indirect
-	github.com/ulikunitz/xz v0.5.11 // 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
diff --git a/go.sum b/go.sum
index 344757e62922d..a4925b54d02a3 100644
--- a/go.sum
+++ b/go.sum
@@ -174,7 +174,6 @@ github.com/bits-and-blooms/bitset v1.1.10/go.mod h1:w0XsmFg8qg6cmpTtJ0z3pKgjTDBM
 github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
 github.com/bits-and-blooms/bitset v1.3.3 h1:R1XWiopGiXf66xygsiLpzLo67xEYvMkHw3w+rCOSAwg=
 github.com/bits-and-blooms/bitset v1.3.3/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
-github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
 github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4=
 github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI=
 github.com/blevesearch/bleve/v2 v2.0.5/go.mod h1:ZjWibgnbRX33c+vBRgla9QhPb4QOjD6fdVJ+R1Bk8LM=
diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go
index 2058fcec0fe05..bf2002bf31eff 100644
--- a/models/migrations/migrations.go
+++ b/models/migrations/migrations.go
@@ -19,6 +19,7 @@ import (
 	"code.gitea.io/gitea/models/migrations/v1_17"
 	"code.gitea.io/gitea/models/migrations/v1_18"
 	"code.gitea.io/gitea/models/migrations/v1_19"
+	"code.gitea.io/gitea/models/migrations/v1_20"
 	"code.gitea.io/gitea/models/migrations/v1_6"
 	"code.gitea.io/gitea/models/migrations/v1_7"
 	"code.gitea.io/gitea/models/migrations/v1_8"
@@ -453,6 +454,11 @@ var migrations = []Migration{
 	NewMigration("Add updated unix to LFSMetaObject", v1_19.AddUpdatedUnixToLFSMetaObject),
 	// v239 -> v240
 	NewMigration("Add scope for access_token", v1_19.AddScopeForAccessTokens),
+
+	// Gitea 1.19.0 ends at v240
+
+	// v240 -> v241
+	NewMigration("Add is_internal column to package", v1_20.AddIsInternalColumnToPackage),
 }
 
 // GetCurrentDBVersion returns the current db version
diff --git a/models/migrations/v1_20/v240.go b/models/migrations/v1_20/v240.go
new file mode 100644
index 0000000000000..ddb487f6844f4
--- /dev/null
+++ b/models/migrations/v1_20/v240.go
@@ -0,0 +1,23 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package v1_20 //nolint
+
+import (
+	"xorm.io/xorm"
+)
+
+func AddIsInternalColumnToPackage(x *xorm.Engine) error {
+	type Package struct {
+		ID               int64  `xorm:"pk autoincr"`
+		OwnerID          int64  `xorm:"UNIQUE(s) INDEX NOT NULL"`
+		RepoID           int64  `xorm:"INDEX"`
+		Type             string `xorm:"UNIQUE(s) INDEX NOT NULL"`
+		Name             string `xorm:"NOT NULL"`
+		LowerName        string `xorm:"UNIQUE(s) INDEX NOT NULL"`
+		SemverCompatible bool   `xorm:"NOT NULL DEFAULT false"`
+		IsInternal       bool   `xorm:"INDEX NOT NULL DEFAULT false"`
+	}
+
+	return x.Sync2(new(Package))
+}
diff --git a/models/packages/container/search.go b/models/packages/container/search.go
index b65c8634d6544..0d3664d384349 100644
--- a/models/packages/container/search.go
+++ b/models/packages/container/search.go
@@ -101,16 +101,7 @@ func getContainerBlobsLimit(ctx context.Context, opts *BlobSearchOptions, limit
 		return nil, err
 	}
 
-	pfds := make([]*packages.PackageFileDescriptor, 0, len(pfs))
-	for _, pf := range pfs {
-		pfd, err := packages.GetPackageFileDescriptor(ctx, pf)
-		if err != nil {
-			return nil, err
-		}
-		pfds = append(pfds, pfd)
-	}
-
-	return pfds, nil
+	return packages.GetPackageFileDescriptors(ctx, pfs)
 }
 
 // GetManifestVersions gets all package versions representing the matching manifest
diff --git a/models/packages/debian/search.go b/models/packages/debian/search.go
new file mode 100644
index 0000000000000..8900eb21c7134
--- /dev/null
+++ b/models/packages/debian/search.go
@@ -0,0 +1,133 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package debian
+
+import (
+	"context"
+	"strconv"
+
+	"code.gitea.io/gitea/models/db"
+	"code.gitea.io/gitea/models/packages"
+	debian_module "code.gitea.io/gitea/modules/packages/debian"
+
+	"xorm.io/builder"
+)
+
+type PackageSearchOptions struct {
+	OwnerID      int64
+	Distribution string
+	Component    string
+	Architecture string
+}
+
+// SearchLatestPackages gets the latest packages matching the search options
+func SearchLatestPackages(ctx context.Context, opts *PackageSearchOptions) ([]*packages.PackageFileDescriptor, error) {
+	var cond builder.Cond = builder.Eq{
+		"package_file.is_lead":        true,
+		"package.type":                packages.TypeDebian,
+		"package.owner_id":            opts.OwnerID,
+		"package.is_internal":         false,
+		"package_version.is_internal": false,
+	}
+
+	props := make(map[string]string)
+	if opts.Distribution != "" {
+		props[debian_module.PropertyDistribution] = opts.Distribution
+	}
+	if opts.Component != "" {
+		props[debian_module.PropertyComponent] = opts.Component
+	}
+	if opts.Architecture != "" {
+		props[debian_module.PropertyArchitecture] = opts.Architecture
+	}
+
+	if len(props) > 0 {
+		var propsCond builder.Cond = builder.Eq{
+			"package_property.ref_type": packages.PropertyTypeFile,
+		}
+		propsCond = propsCond.And(builder.Expr("package_property.ref_id = package_file.id"))
+
+		propsCondBlock := builder.NewCond()
+		for name, value := range props {
+			propsCondBlock = propsCondBlock.Or(builder.Eq{
+				"package_property.name":  name,
+				"package_property.value": value,
+			})
+		}
+		propsCond = propsCond.And(propsCondBlock)
+
+		cond = cond.And(builder.Eq{
+			strconv.Itoa(len(props)): builder.Select("COUNT(*)").Where(propsCond).From("package_property"),
+		})
+	}
+
+	cond = cond.
+		And(builder.Expr("pv2.id IS NULL"))
+
+	joinCond := builder.
+		Expr("package_version.package_id = pv2.package_id AND (package_version.created_unix < pv2.created_unix OR (package_version.created_unix = pv2.created_unix AND package_version.id < pv2.id))").
+		And(builder.Eq{"pv2.is_internal": false})
+
+	pfs := make([]*packages.PackageFile, 0, 10)
+	err := db.GetEngine(ctx).
+		Table("package_file").
+		Select("package_file.*").
+		Join("INNER", "package_version", "package_version.id = package_file.version_id").
+		Join("LEFT", "package_version pv2", joinCond).
+		Join("INNER", "package", "package.id = package_version.package_id").
+		Where(cond).
+		Desc("package_version.created_unix").
+		Find(&pfs)
+	if err != nil {
+		return nil, err
+	}
+
+	return packages.GetPackageFileDescriptors(ctx, pfs)
+}
+
+// GetDistributions gets all available distributions
+func GetDistributions(ctx context.Context, ownerID int64) ([]string, error) {
+	return getDistinctPropertyValues(ctx, ownerID, "", debian_module.PropertyDistribution)
+}
+
+// GetComponents gets all available components for the given distribution
+func GetComponents(ctx context.Context, ownerID int64, distribution string) ([]string, error) {
+	return getDistinctPropertyValues(ctx, ownerID, distribution, debian_module.PropertyComponent)
+}
+
+// GetArchitectures gets all available architectures for the given distribution
+func GetArchitectures(ctx context.Context, ownerID int64, distribution string) ([]string, error) {
+	return getDistinctPropertyValues(ctx, ownerID, distribution, debian_module.PropertyArchitecture)
+}
+
+func getDistinctPropertyValues(ctx context.Context, ownerID int64, distribution, propName string) ([]string, error) {
+	var cond builder.Cond = builder.Eq{
+		"package_property.ref_type":   packages.PropertyTypeFile,
+		"package_property.name":       propName,
+		"package_version.is_internal": true,
+		"package.type":                packages.TypeDebian,
+		"package.owner_id":            ownerID,
+		"package.is_internal":         true,
+	}
+	if distribution != "" {
+		var innerCond builder.Cond = builder.
+			Expr("pp.ref_id = package_property.ref_id").
+			And(builder.Eq{
+				"package_property.ref_type": packages.PropertyTypeFile,
+				"package_property.name":     debian_module.PropertyDistribution,
+				"package_property.value":    distribution,
+			})
+		cond = cond.And(builder.Exists(builder.Select("pp.ref_id").From("package_property pp").Where(innerCond)))
+	}
+
+	values := make([]string, 0, 5)
+	return values, db.GetEngine(ctx).
+		Table("package_property").
+		Distinct("package_property.value").
+		Join("INNER", "package_file", "package_file.id = package_property.ref_id").
+		Join("INNER", "package_version", "package_version.id = package_file.version_id").
+		Join("INNER", "package", "package.id = package_version.package_id").
+		Where(cond).
+		Find(&values)
+}
diff --git a/models/packages/descriptor.go b/models/packages/descriptor.go
index 64c63a7433b50..d9f0b200d20c7 100644
--- a/models/packages/descriptor.go
+++ b/models/packages/descriptor.go
@@ -118,13 +118,9 @@ func GetPackageDescriptor(ctx context.Context, pv *PackageVersion) (*PackageDesc
 		return nil, err
 	}
 
-	pfds := make([]*PackageFileDescriptor, 0, len(pfs))
-	for _, pf := range pfs {
-		pfd, err := GetPackageFileDescriptor(ctx, pf)
-		if err != nil {
-			return nil, err
-		}
-		pfds = append(pfds, pfd)
+	pfds, err := GetPackageFileDescriptors(ctx, pfs)
+	if err != nil {
+		return nil, err
 	}
 
 	var metadata interface{}
@@ -195,6 +191,19 @@ func GetPackageFileDescriptor(ctx context.Context, pf *PackageFile) (*PackageFil
 	}, nil
 }
 
+// GetPackageFileDescriptors gets the package file descriptors for the package files
+func GetPackageFileDescriptors(ctx context.Context, pfs []*PackageFile) ([]*PackageFileDescriptor, error) {
+	pfds := make([]*PackageFileDescriptor, 0, len(pfs))
+	for _, pf := range pfs {
+		pfd, err := GetPackageFileDescriptor(ctx, pf)
+		if err != nil {
+			return nil, err
+		}
+		pfds = append(pfds, pfd)
+	}
+	return pfds, nil
+}
+
 // GetPackageDescriptors gets the package descriptions for the versions
 func GetPackageDescriptors(ctx context.Context, pvs []*PackageVersion) ([]*PackageDescriptor, error) {
 	pds := make([]*PackageDescriptor, 0, len(pvs))
diff --git a/models/packages/package.go b/models/packages/package.go
index ebdb719cd9541..c2d61bc9d7f9b 100644
--- a/models/packages/package.go
+++ b/models/packages/package.go
@@ -136,6 +136,7 @@ type Package struct {
 	Name             string `xorm:"NOT NULL"`
 	LowerName        string `xorm:"UNIQUE(s) INDEX NOT NULL"`
 	SemverCompatible bool   `xorm:"NOT NULL DEFAULT false"`
+	IsInternal       bool   `xorm:"INDEX NOT NULL DEFAULT false"`
 }
 
 // TryInsertPackage inserts a package. If a package exists already, ErrDuplicatePackage is returned
@@ -196,9 +197,10 @@ func GetPackageByID(ctx context.Context, packageID int64) (*Package, error) {
 // GetPackageByName gets a package by name
 func GetPackageByName(ctx context.Context, ownerID int64, packageType Type, name string) (*Package, error) {
 	var cond builder.Cond = builder.Eq{
-		"package.owner_id":   ownerID,
-		"package.type":       packageType,
-		"package.lower_name": strings.ToLower(name),
+		"package.owner_id":    ownerID,
+		"package.type":        packageType,
+		"package.lower_name":  strings.ToLower(name),
+		"package.is_internal": false,
 	}
 
 	p := &Package{}
@@ -218,8 +220,9 @@ func GetPackageByName(ctx context.Context, ownerID int64, packageType Type, name
 // GetPackagesByType gets all packages of a specific type
 func GetPackagesByType(ctx context.Context, ownerID int64, packageType Type) ([]*Package, error) {
 	var cond builder.Cond = builder.Eq{
-		"package.owner_id": ownerID,
-		"package.type":     packageType,
+		"package.owner_id":    ownerID,
+		"package.type":        packageType,
+		"package.is_internal": false,
 	}
 
 	ps := make([]*Package, 0, 10)
diff --git a/models/packages/package_file.go b/models/packages/package_file.go
index 97e7a0d4070a9..c90ccf9a2d149 100644
--- a/models/packages/package_file.go
+++ b/models/packages/package_file.go
@@ -117,13 +117,15 @@ func DeleteFileByID(ctx context.Context, fileID int64) error {
 
 // PackageFileSearchOptions are options for SearchXXX methods
 type PackageFileSearchOptions struct {
-	OwnerID      int64
-	PackageType  string
-	VersionID    int64
-	Query        string
-	CompositeKey string
-	Properties   map[string]string
-	OlderThan    time.Duration
+	OwnerID        int64
+	PackageType    string
+	VersionID      int64
+	Query          string
+	CompositeKey   string
+	Properties     map[string]string
+	OlderThan      time.Duration
+	HashAlgorithmn string
+	Hash           string
 	db.Paginator
 }
 
@@ -182,6 +184,15 @@ func (opts *PackageFileSearchOptions) toConds() builder.Cond {
 		cond = cond.And(builder.Lt{"package_file.created_unix": time.Now().Add(-opts.OlderThan).Unix()})
 	}
 
+	if opts.Hash != "" && (opts.HashAlgorithmn == "md5" || opts.HashAlgorithmn == "sha1" || opts.HashAlgorithmn == "sha256" || opts.HashAlgorithmn == "sha512") {
+		var innerCond builder.Cond = builder.
+			Expr("package_blob.id = package_file.blob_id").
+			And(builder.Eq{
+				"package_blob.hash_" + opts.HashAlgorithmn: opts.Hash,
+			})
+		cond = cond.And(builder.Exists(builder.Select("package_blob.id").From("package_blob").Where(innerCond)))
+	}
+
 	return cond
 }
 
diff --git a/models/packages/package_version.go b/models/packages/package_version.go
index 759c20abed22b..ab1bcddae5818 100644
--- a/models/packages/package_version.go
+++ b/models/packages/package_version.go
@@ -173,7 +173,7 @@ const (
 )
 
 // PackageSearchOptions are options for SearchXXX methods
-// Besides IsInternal are all fields optional and are not used if they have their default value (nil, "", 0)
+// All fields optional and are not used if they have their default value (nil, "", 0)
 type PackageSearchOptions struct {
 	OwnerID         int64
 	RepoID          int64
@@ -192,7 +192,9 @@ type PackageSearchOptions struct {
 func (opts *PackageSearchOptions) toConds() builder.Cond {
 	cond := builder.NewCond()
 	if !opts.IsInternal.IsNone() {
-		cond = builder.Eq{"package_version.is_internal": opts.IsInternal.IsTrue()}
+		cond = builder.Eq{
+			"package_version.is_internal": opts.IsInternal.IsTrue(),
+		}
 	}
 
 	if opts.OwnerID != 0 {
diff --git a/modules/packages/debian/metadata.go b/modules/packages/debian/metadata.go
index 63c4dd3bf7a8b..d7fc3c9ff625d 100644
--- a/modules/packages/debian/metadata.go
+++ b/modules/packages/debian/metadata.go
@@ -1,23 +1,215 @@
-// Copyright 2022 The Gitea Authors. All rights reserved.
+// Copyright 2023 The Gitea Authors. All rights reserved.
 // SPDX-License-Identifier: MIT
 
 package debian
 
 import (
-	"errors"
+	"archive/tar"
+	"bufio"
+	"compress/gzip"
+	"io"
+	"net/mail"
+	"regexp"
+	"strings"
+
+	"code.gitea.io/gitea/modules/util"
+	"code.gitea.io/gitea/modules/validation"
+
+	"github.com/blakesmith/ar"
+	"github.com/klauspost/compress/zstd"
+	"github.com/ulikunitz/xz"
+)
+
+const (
+	PropertyDistribution               = "debian.distribution"
+	PropertyComponent                  = "debian.component"
+	PropertyArchitecture               = "debian.architecture"
+	PropertyControl                    = "debian.control"
+	PropertyKeyPrivate                 = "debian.key.private"
+	PropertyKeyPublic                  = "debian.key.public"
+	PropertyRepositoryIncludeInRelease = "debian.repository.include_in_release"
+
+	RepositoryPackage = "_debian"
+	RepositoryVersion = "_repository"
 )
 
 var (
-	ErrInvalidName    = errors.New("Package name is invalid")
-	ErrInvalidVersion = errors.New("Package version is invalid")
-	ErrInvalidArch    = errors.New("Package architecture is invalid")
+	ErrMissingControlFile     = util.NewInvalidArgumentErrorf("control file is missing")
+	ErrUnsupportedCompression = util.NewInvalidArgumentErrorf("unsupported compression algorithmn")
+	ErrInvalidName            = util.NewInvalidArgumentErrorf("package name is invalid")
+	ErrInvalidVersion         = util.NewInvalidArgumentErrorf("package version is invalid")
+	ErrInvalidArchitecture    = util.NewInvalidArgumentErrorf("package architecture is invalid")
+
+	// https://www.debian.org/doc/debian-policy/ch-controlfields.html#source
+	namePattern = regexp.MustCompile(`\A[a-z0-9][a-z0-9+-.]+\z`)
+	// https://www.debian.org/doc/debian-policy/ch-controlfields.html#version
+	versionPattern = regexp.MustCompile(`\A(?:[0-9]:)?[a-zA-Z0-9.+~]+(?:-[a-zA-Z0-9.+-~]+)?\z`)
 )
 
 type Package struct {
-	Name     string
-	Version  string
-	Metadata *Metadata
+	Name         string
+	Version      string
+	Architecture string
+	Control      string
+	Metadata     *Metadata
 }
 
 type Metadata struct {
+	Maintainer   string   `json:"maintainer,omitempty"`
+	ProjectURL   string   `json:"project_url,omitempty"`
+	Description  string   `json:"description,omitempty"`
+	Dependencies []string `json:"dependencies,omitempty"`
+}
+
+// ParsePackage parses the Debian package file
+// https://manpages.debian.org/bullseye/dpkg-dev/deb.5.en.html
+func ParsePackage(r io.Reader) (*Package, error) {
+	arr := ar.NewReader(r)
+
+	for {
+		hd, err := arr.Next()
+		if err == io.EOF {
+			break
+		}
+		if err != nil {
+			return nil, err
+		}
+
+		if strings.HasPrefix(hd.Name, "control.tar") {
+			var inner io.Reader
+			switch hd.Name[11:] {
+			case "":
+				inner = arr
+			case ".gz":
+				gzr, err := gzip.NewReader(arr)
+				if err != nil {
+					return nil, err
+				}
+				defer gzr.Close()
+
+				inner = gzr
+			case ".xz":
+				xzr, err := xz.NewReader(arr)
+				if err != nil {
+					return nil, err
+				}
+
+				inner = xzr
+			case ".zst":
+				zr, err := zstd.NewReader(arr)
+				if err != nil {
+					return nil, err
+				}
+				defer zr.Close()
+
+				inner = zr
+			default:
+				return nil, ErrUnsupportedCompression
+			}
+
+			tr := tar.NewReader(inner)
+			for {
+				hd, err := tr.Next()
+				if err == io.EOF {
+					break
+				}
+				if err != nil {
+					return nil, err
+				}
+
+				if hd.Typeflag != tar.TypeReg {
+					continue
+				}
+
+				if hd.FileInfo().Name() == "control" {
+					return ParseControlFile(tr)
+				}
+			}
+		}
+	}
+
+	return nil, ErrMissingControlFile
+}
+
+// ParseControlFile parses a Debian control file to retrieve the metadata
+func ParseControlFile(r io.Reader) (*Package, error) {
+	p := &Package{
+		Metadata: &Metadata{},
+	}
+
+	key := ""
+	var depends strings.Builder
+	var control strings.Builder
+
+	s := bufio.NewScanner(io.TeeReader(r, &control))
+	for s.Scan() {
+		line := s.Text()
+
+		trimmed := strings.TrimSpace(line)
+		if trimmed == "" {
+			continue
+		}
+
+		if line[0] == ' ' || line[0] == '\t' {
+			switch key {
+			case "Description":
+				p.Metadata.Description += line
+			case "Depends":
+				depends.WriteString(trimmed)
+			}
+		} else {
+			parts := strings.SplitN(trimmed, ":", 2)
+			if len(parts) < 2 {
+				continue
+			}
+
+			key = parts[0]
+			value := strings.TrimSpace(parts[1])
+			switch key {
+			case "Package":
+				if !namePattern.MatchString(value) {
+					return nil, ErrInvalidName
+				}
+				p.Name = value
+			case "Version":
+				if !versionPattern.MatchString(value) {
+					return nil, ErrInvalidVersion
+				}
+				p.Version = value
+			case "Architecture":
+				if value == "" {
+					return nil, ErrInvalidArchitecture
+				}
+				p.Architecture = value
+			case "Maintainer":
+				a, err := mail.ParseAddress(value)
+				if err != nil || a.Name == "" {
+					p.Metadata.Maintainer = value
+				} else {
+					p.Metadata.Maintainer = a.Name
+				}
+			case "Description":
+				p.Metadata.Description = value
+			case "Depends":
+				depends.WriteString(value)
+			case "Homepage":
+				if validation.IsValidURL(value) {
+					p.Metadata.ProjectURL = value
+				}
+			}
+		}
+	}
+	if err := s.Err(); err != nil {
+		return nil, err
+	}
+
+	dependencies := strings.Split(depends.String(), ",")
+	for i := range dependencies {
+		dependencies[i] = strings.TrimSpace(dependencies[i])
+	}
+	p.Metadata.Dependencies = dependencies
+
+	p.Control = control.String()
+
+	return p, nil
 }
diff --git a/modules/packages/debian/metadata_test.go b/modules/packages/debian/metadata_test.go
new file mode 100644
index 0000000000000..69fd51ea79002
--- /dev/null
+++ b/modules/packages/debian/metadata_test.go
@@ -0,0 +1,171 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package debian
+
+import (
+	"archive/tar"
+	"bytes"
+	"compress/gzip"
+	"io"
+	"testing"
+
+	"github.com/blakesmith/ar"
+	"github.com/klauspost/compress/zstd"
+	"github.com/stretchr/testify/assert"
+	"github.com/ulikunitz/xz"
+)
+
+const (
+	packageName         = "gitea"
+	packageVersion      = "0:1.0.1-te~st"
+	packageArchitecture = "amd64"
+	packageAuthor       = "KN4CK3R"
+	description         = "Description with multiple lines."
+	projectURL          = "https://gitea.io"
+)
+
+func TestParsePackage(t *testing.T) {
+	createArchive := func(files map[string][]byte) io.Reader {
+		var buf bytes.Buffer
+		aw := ar.NewWriter(&buf)
+		aw.WriteGlobalHeader()
+		for filename, content := range files {
+			hdr := &ar.Header{
+				Name: filename,
+				Mode: 0o600,
+				Size: int64(len(content)),
+			}
+			aw.WriteHeader(hdr)
+			aw.Write(content)
+		}
+		return &buf
+	}
+
+	t.Run("MissingControlFile", func(t *testing.T) {
+		data := createArchive(map[string][]byte{"dummy.txt": {}})
+
+		p, err := ParsePackage(data)
+		assert.Nil(t, p)
+		assert.ErrorIs(t, err, ErrMissingControlFile)
+	})
+
+	t.Run("Compression", func(t *testing.T) {
+		t.Run("Unsupported", func(t *testing.T) {
+			data := createArchive(map[string][]byte{"control.tar.foo": {}})
+
+			p, err := ParsePackage(data)
+			assert.Nil(t, p)
+			assert.ErrorIs(t, err, ErrUnsupportedCompression)
+		})
+
+		var buf bytes.Buffer
+		tw := tar.NewWriter(&buf)
+		tw.WriteHeader(&tar.Header{
+			Name: "control",
+			Mode: 0o600,
+			Size: 50,
+		})
+		tw.Write([]byte("Package: gitea\nVersion: 1.0.0\nArchitecture: amd64\n"))
+		tw.Close()
+
+		t.Run("None", func(t *testing.T) {
+			data := createArchive(map[string][]byte{"control.tar": buf.Bytes()})
+
+			p, err := ParsePackage(data)
+			assert.NotNil(t, p)
+			assert.NoError(t, err)
+			assert.Equal(t, "gitea", p.Name)
+		})
+
+		t.Run("gz", func(t *testing.T) {
+			var zbuf bytes.Buffer
+			zw := gzip.NewWriter(&zbuf)
+			zw.Write(buf.Bytes())
+			zw.Close()
+
+			data := createArchive(map[string][]byte{"control.tar.gz": zbuf.Bytes()})
+
+			p, err := ParsePackage(data)
+			assert.NotNil(t, p)
+			assert.NoError(t, err)
+			assert.Equal(t, "gitea", p.Name)
+		})
+
+		t.Run("xz", func(t *testing.T) {
+			var xbuf bytes.Buffer
+			xw, _ := xz.NewWriter(&xbuf)
+			xw.Write(buf.Bytes())
+			xw.Close()
+
+			data := createArchive(map[string][]byte{"control.tar.xz": xbuf.Bytes()})
+
+			p, err := ParsePackage(data)
+			assert.NotNil(t, p)
+			assert.NoError(t, err)
+			assert.Equal(t, "gitea", p.Name)
+		})
+
+		t.Run("zst", func(t *testing.T) {
+			var zbuf bytes.Buffer
+			zw, _ := zstd.NewWriter(&zbuf)
+			zw.Write(buf.Bytes())
+			zw.Close()
+
+			data := createArchive(map[string][]byte{"control.tar.zst": zbuf.Bytes()})
+
+			p, err := ParsePackage(data)
+			assert.NotNil(t, p)
+			assert.NoError(t, err)
+			assert.Equal(t, "gitea", p.Name)
+		})
+	})
+}
+
+func TestParseControlFile(t *testing.T) {
+	buildContent := func(name, version, architecture string) *bytes.Buffer {
+		var buf bytes.Buffer
+		buf.WriteString("Package: " + name + "\nVersion: " + version + "\nArchitecture: " + architecture + "\nMaintainer: " + packageAuthor + " \nHomepage: " + projectURL + "\nDepends: a,\n b\nDescription: Description\n with multiple\n lines.")
+		return &buf
+	}
+
+	t.Run("InvalidName", func(t *testing.T) {
+		for _, name := range []string{"", "-cd"} {
+			p, err := ParseControlFile(buildContent(name, packageVersion, packageArchitecture))
+			assert.Nil(t, p)
+			assert.ErrorIs(t, err, ErrInvalidName)
+		}
+	})
+
+	t.Run("InvalidVersion", func(t *testing.T) {
+		for _, version := range []string{"", "1-", ":1.0", "1_0"} {
+			p, err := ParseControlFile(buildContent(packageName, version, packageArchitecture))
+			assert.Nil(t, p)
+			assert.ErrorIs(t, err, ErrInvalidVersion)
+		}
+	})
+
+	t.Run("InvalidArchitecture", func(t *testing.T) {
+		p, err := ParseControlFile(buildContent(packageName, packageVersion, ""))
+		assert.Nil(t, p)
+		assert.ErrorIs(t, err, ErrInvalidArchitecture)
+	})
+
+	t.Run("Valid", func(t *testing.T) {
+		content := buildContent(packageName, packageVersion, packageArchitecture)
+		full := content.String()
+
+		p, err := ParseControlFile(content)
+		assert.NoError(t, err)
+		assert.NotNil(t, p)
+
+		assert.Equal(t, packageName, p.Name)
+		assert.Equal(t, packageVersion, p.Version)
+		assert.Equal(t, packageArchitecture, p.Architecture)
+		assert.Equal(t, description, p.Metadata.Description)
+		assert.Equal(t, projectURL, p.Metadata.ProjectURL)
+		assert.Equal(t, packageAuthor, p.Metadata.Maintainer)
+		assert.Equal(t, []string{"a", "b"}, p.Metadata.Dependencies)
+		assert.Equal(t, full, p.Control)
+	})
+}
diff --git a/modules/packages/hashed_buffer.go b/modules/packages/hashed_buffer.go
index ef00a45057f7c..017ddf1c8f5a5 100644
--- a/modules/packages/hashed_buffer.go
+++ b/modules/packages/hashed_buffer.go
@@ -25,8 +25,15 @@ type HashedBuffer struct {
 	combinedWriter io.Writer
 }
 
-// NewHashedBuffer creates a hashed buffer with a specific maximum memory size
-func NewHashedBuffer(maxMemorySize int) (*HashedBuffer, error) {
+const DefaultMemorySize = 32 * 1024 * 1024
+
+// NewHashedBuffer creates a hashed buffer with the default memory size
+func NewHashedBuffer() (*HashedBuffer, error) {
+	return NewHashedBufferWithSize(DefaultMemorySize)
+}
+
+// NewHashedBuffer creates a hashed buffer with a specific memory size
+func NewHashedBufferWithSize(maxMemorySize int) (*HashedBuffer, error) {
 	b, err := filebuffer.New(maxMemorySize)
 	if err != nil {
 		return nil, err
@@ -43,9 +50,14 @@ func NewHashedBuffer(maxMemorySize int) (*HashedBuffer, error) {
 	}, nil
 }
 
-// CreateHashedBufferFromReader creates a hashed buffer and copies the provided reader data into it.
-func CreateHashedBufferFromReader(r io.Reader, maxMemorySize int) (*HashedBuffer, error) {
-	b, err := NewHashedBuffer(maxMemorySize)
+// CreateHashedBufferFromReader creates a hashed buffer with the default memory size and copies the provided reader data into it.
+func CreateHashedBufferFromReader(r io.Reader) (*HashedBuffer, error) {
+	return CreateHashedBufferFromReaderWithSize(r, DefaultMemorySize)
+}
+
+// CreateHashedBufferFromReaderWithSize creates a hashed buffer and copies the provided reader data into it.
+func CreateHashedBufferFromReaderWithSize(r io.Reader, maxMemorySize int) (*HashedBuffer, error) {
+	b, err := NewHashedBufferWithSize(maxMemorySize)
 	if err != nil {
 		return nil, err
 	}
diff --git a/modules/packages/nuget/symbol_extractor.go b/modules/packages/nuget/symbol_extractor.go
index b709eac4c1965..81bf0371a0b1a 100644
--- a/modules/packages/nuget/symbol_extractor.go
+++ b/modules/packages/nuget/symbol_extractor.go
@@ -63,7 +63,7 @@ func ExtractPortablePdb(r io.ReaderAt, size int64) (PortablePdbList, error) {
 					return err
 				}
 
-				buf, err := packages.CreateHashedBufferFromReader(f, 32*1024*1024)
+				buf, err := packages.CreateHashedBufferFromReader(f)
 
 				f.Close()
 
diff --git a/modules/setting/packages.go b/modules/setting/packages.go
index 120fbb5bda8e7..1f61d32e7c1c1 100644
--- a/modules/setting/packages.go
+++ b/modules/setting/packages.go
@@ -28,6 +28,7 @@ var (
 		LimitSizeComposer    int64
 		LimitSizeConan       int64
 		LimitSizeContainer   int64
+		LimitSizeDebian      int64
 		LimitSizeGeneric     int64
 		LimitSizeHelm        int64
 		LimitSizeMaven       int64
@@ -67,6 +68,7 @@ func newPackages() {
 	Packages.LimitSizeComposer = mustBytes(sec, "LIMIT_SIZE_COMPOSER")
 	Packages.LimitSizeConan = mustBytes(sec, "LIMIT_SIZE_CONAN")
 	Packages.LimitSizeContainer = mustBytes(sec, "LIMIT_SIZE_CONTAINER")
+	Packages.LimitSizeDebian = mustBytes(sec, "LIMIT_SIZE_DEBIAN")
 	Packages.LimitSizeGeneric = mustBytes(sec, "LIMIT_SIZE_GENERIC")
 	Packages.LimitSizeHelm = mustBytes(sec, "LIMIT_SIZE_HELM")
 	Packages.LimitSizeMaven = mustBytes(sec, "LIMIT_SIZE_MAVEN")
diff --git a/modules/util/filebuffer/file_backed_buffer.go b/modules/util/filebuffer/file_backed_buffer.go
index bfddf90e92bcc..6b07bd0413864 100644
--- a/modules/util/filebuffer/file_backed_buffer.go
+++ b/modules/util/filebuffer/file_backed_buffer.go
@@ -7,11 +7,10 @@ import (
 	"bytes"
 	"errors"
 	"io"
+	"math"
 	"os"
 )
 
-const maxInt = int(^uint(0) >> 1) // taken from bytes.Buffer
-
 var (
 	// ErrInvalidMemorySize occurs if the memory size is not in a valid range
 	ErrInvalidMemorySize = errors.New("Memory size must be greater 0 and lower math.MaxInt32")
@@ -37,7 +36,7 @@ type FileBackedBuffer struct {
 
 // New creates a file backed buffer with a specific maximum memory size
 func New(maxMemorySize int) (*FileBackedBuffer, error) {
-	if maxMemorySize < 0 || maxMemorySize > maxInt {
+	if maxMemorySize < 0 || maxMemorySize > math.MaxInt32 {
 		return nil, ErrInvalidMemorySize
 	}
 
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index 74ba85ca90161..9e8eb5597967b 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -3163,6 +3163,14 @@ container.layers = Image Layers
 container.labels = Labels
 container.labels.key = Key
 container.labels.value = Value
+debian.registry = Setup this registry from the command line:
+debian.registry.info = Choose <distribution> and <component> from the list below.
+debian.install = To install the package, run the following command:
+debian.documentation = For more information on the Debian registry, see the documentation.
+debian.repository = Repository Info
+debian.repository.distributions = Distributions
+debian.repository.components = Components
+debian.repository.architectures = Architectures
 generic.download = Download package from the command line:
 generic.documentation = For more information on the generic registry, see the documentation.
 helm.registry = Setup this registry from the command line:
diff --git a/routers/api/packages/api.go b/routers/api/packages/api.go
index d1d46dba52ff4..2d6a58b572ec9 100644
--- a/routers/api/packages/api.go
+++ b/routers/api/packages/api.go
@@ -169,35 +169,22 @@ func CommonRoutes(ctx gocontext.Context) *web.Route {
 			})
 		}, reqPackageAccess(perm.AccessModeRead))
 		r.Group("/debian", func() {
-			r.Group("/files/{packagename}/{packageversion}/{arch}", func() {
-				r.Put("", debian.PutPackage)
-				r.Delete("", debian.DeletePackage)
-			}, reqPackageAccess(perm.AccessModeWrite))
-
-			r.Get("/", debian.GetIndex)
-			r.Get("/debian.key", debian.GetPublicKey)
-			r.Group("/pool", func() {
-				r.Get("/", debian.GetIndex)
-				r.Get("/{filename}", debian.GetPackage)
-			})
-			r.Group("/dists", func() {
-				r.Get("/", debian.GetIndex)
-				r.Group("/gitea", func() {
-					r.Get("/", debian.GetIndex)
-					r.Group("/main", func() {
-						r.Get("/", debian.GetIndex)
-						r.Group("/{packagearch}", func() {
-							r.Get("/", debian.GetArchIndex)
-							r.Get("/Packages", debian.GetPackages)
-							r.Get("/Packages.gz", debian.GetPackagesGZ)
-							r.Get("/Release", debian.GetArchRelease)
-						})
-					})
-					r.Get("/Release", debian.GetRelease)
-					r.Get("/Release.gpg", debian.GetReleaseGPG)
-					r.Get("/InRelease", debian.GetInRelease)
+			r.Get("/repository.key", debian.GetRepositoryKey)
+			r.Group("/dists/{distribution}", func() {
+				r.Get("/{filename}", debian.GetRepositoryFile)
+				r.Get("/by-hash/{algorithmn}/{hash}", debian.GetRepositoryFileByHash)
+				r.Group("/{component}/{architecture}", func() {
+					r.Get("/{filename}", debian.GetRepositoryFile)
+					r.Get("/by-hash/{algorithmn}/{hash}", debian.GetRepositoryFileByHash)
 				})
 			})
+			r.Group("/pool/{distribution}/{component}", func() {
+				r.Get("/{name}_{version}_{architecture}.deb", debian.DownloadPackageFile)
+				r.Group("", func() {
+					r.Put("/upload", debian.UploadPackageFile)
+					r.Delete("/{name}/{version}/{architecture}", debian.DeletePackageFile)
+				}, reqPackageAccess(perm.AccessModeWrite))
+			})
 		}, reqPackageAccess(perm.AccessModeRead))
 		r.Group("/generic", func() {
 			r.Group("/{packagename}/{packageversion}", func() {
diff --git a/routers/api/packages/composer/composer.go b/routers/api/packages/composer/composer.go
index a623952aa7387..d93b11efdf66d 100644
--- a/routers/api/packages/composer/composer.go
+++ b/routers/api/packages/composer/composer.go
@@ -192,7 +192,7 @@ func DownloadPackageFile(ctx *context.Context) {
 
 // UploadPackage creates a new package
 func UploadPackage(ctx *context.Context) {
-	buf, err := packages_module.CreateHashedBufferFromReader(ctx.Req.Body, 32*1024*1024)
+	buf, err := packages_module.CreateHashedBufferFromReader(ctx.Req.Body)
 	if err != nil {
 		apiError(ctx, http.StatusInternalServerError, err)
 		return
diff --git a/routers/api/packages/conan/conan.go b/routers/api/packages/conan/conan.go
index d538cc7d397db..caeb8c11bc4c8 100644
--- a/routers/api/packages/conan/conan.go
+++ b/routers/api/packages/conan/conan.go
@@ -318,7 +318,7 @@ func uploadFile(ctx *context.Context, fileFilter container.Set[string], fileKey
 		defer upload.Close()
 	}
 
-	buf, err := packages_module.CreateHashedBufferFromReader(upload, 32*1024*1024)
+	buf, err := packages_module.CreateHashedBufferFromReader(upload)
 	if err != nil {
 		apiError(ctx, http.StatusInternalServerError, err)
 		return
@@ -648,10 +648,7 @@ func deleteRecipeOrPackage(apictx *context.Context, rref *conan_module.RecipeRef
 	}
 
 	for _, pf := range pfs {
-		if err := packages_model.DeleteAllProperties(ctx, packages_model.PropertyTypeFile, pf.ID); err != nil {
-			return err
-		}
-		if err := packages_model.DeleteFileByID(ctx, pf.ID); err != nil {
+		if err := packages_service.DeletePackageFile(ctx, pf); err != nil {
 			return err
 		}
 	}
@@ -664,11 +661,7 @@ func deleteRecipeOrPackage(apictx *context.Context, rref *conan_module.RecipeRef
 	if !has {
 		versionDeleted = true
 
-		if err := packages_model.DeleteAllProperties(ctx, packages_model.PropertyTypeVersion, pv.ID); err != nil {
-			return err
-		}
-
-		if err := packages_model.DeleteVersionByID(ctx, pv.ID); err != nil {
+		if err := packages_service.DeletePackageVersionAndReferences(ctx, pv); err != nil {
 			return err
 		}
 	}
diff --git a/routers/api/packages/container/container.go b/routers/api/packages/container/container.go
index 8b2c4e6bb2bc9..0983e2ce441d0 100644
--- a/routers/api/packages/container/container.go
+++ b/routers/api/packages/container/container.go
@@ -215,7 +215,7 @@ func InitiateUploadBlob(ctx *context.Context) {
 
 	digest := ctx.FormTrim("digest")
 	if digest != "" {
-		buf, err := packages_module.CreateHashedBufferFromReader(ctx.Req.Body, 32*1024*1024)
+		buf, err := packages_module.CreateHashedBufferFromReader(ctx.Req.Body)
 		if err != nil {
 			apiError(ctx, http.StatusInternalServerError, err)
 			return
@@ -506,7 +506,7 @@ func UploadManifest(ctx *context.Context) {
 	}
 
 	maxSize := maxManifestSize + 1
-	buf, err := packages_module.CreateHashedBufferFromReader(&io.LimitedReader{R: ctx.Req.Body, N: int64(maxSize)}, maxSize)
+	buf, err := packages_module.CreateHashedBufferFromReaderWithSize(&io.LimitedReader{R: ctx.Req.Body, N: int64(maxSize)}, maxSize)
 	if err != nil {
 		apiError(ctx, http.StatusInternalServerError, err)
 		return
diff --git a/routers/api/packages/debian/debian.go b/routers/api/packages/debian/debian.go
index 55cfaeeb2bdd2..fdc3f3f85604c 100644
--- a/routers/api/packages/debian/debian.go
+++ b/routers/api/packages/debian/debian.go
@@ -1,30 +1,26 @@
-// Copyright 2021 The Gitea Authors. All rights reserved.
+// Copyright 2023 The Gitea Authors. All rights reserved.
 // SPDX-License-Identifier: MIT
 
 package debian
 
 import (
+	stdctx "context"
 	"errors"
 	"fmt"
+	"io"
 	"net/http"
-	"os"
-	"path/filepath"
-	"regexp"
 	"strings"
 
+	"code.gitea.io/gitea/models/db"
 	packages_model "code.gitea.io/gitea/models/packages"
 	"code.gitea.io/gitea/modules/context"
-	"code.gitea.io/gitea/modules/log"
+	"code.gitea.io/gitea/modules/notification"
 	packages_module "code.gitea.io/gitea/modules/packages"
-	"code.gitea.io/gitea/modules/setting"
+	debian_module "code.gitea.io/gitea/modules/packages/debian"
+	"code.gitea.io/gitea/modules/util"
 	"code.gitea.io/gitea/routers/api/packages/helper"
 	packages_service "code.gitea.io/gitea/services/packages"
-)
-
-var (
-	namePattern    = regexp.MustCompile(`\A[a-z0-9][a-z0-9\+\-\.]+\z`)
-	versionPattern = regexp.MustCompile(`\A([0-9]:)?[a-zA-Z0-9\.\+\~]+(-[a-zA-Z0-9\.\+\~])?\z`) // TODO: hypens should be allowed if revision is present
-	archPattern    = regexp.MustCompile(`\A[a-z0-9\-]+\z`)
+	debian_service "code.gitea.io/gitea/services/packages/debian"
 )
 
 func apiError(ctx *context.Context, status int, obj interface{}) {
@@ -33,38 +29,60 @@ func apiError(ctx *context.Context, status int, obj interface{}) {
 	})
 }
 
-func GetPackage(ctx *context.Context) {
-	// Need to parse filename bc of how it's routed
-	filename := ctx.Params("filename")
-	log.Info("Filename: %s", filename)
+func GetRepositoryKey(ctx *context.Context) {
+	pv, err := debian_service.GetOrCreateRepositoryVersion(ctx.Package.Owner)
+	if err != nil {
+		apiError(ctx, http.StatusInternalServerError, err)
+		return
+	}
 
-	splitter := regexp.MustCompile(`^([^_]+)_([^_]+)_([^.]+).deb$`)
-	matches := splitter.FindStringSubmatch(filename)
-	if matches == nil {
-		apiError(ctx, http.StatusBadRequest, "Invalid filename")
+	pps, err := packages_model.GetPropertiesByName(ctx, packages_model.PropertyTypeVersion, pv.ID, debian_module.PropertyKeyPublic)
+	if err != nil {
+		apiError(ctx, http.StatusInternalServerError, err)
+		return
+	}
+	if len(pps) != 1 {
+		apiError(ctx, http.StatusInternalServerError, "unknown property")
 		return
 	}
-	packageName := matches[1]
-	packageVersion := matches[2]
 
-	s, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
+	ctx.ServeContent(strings.NewReader(pps[0].Value), &context.ServeHeaderOptions{
+		ContentType: "application/pgp-keys",
+		Filename:    "repository.key",
+	})
+}
+
+// https://wiki.debian.org/DebianRepository/Format#A.22Release.22_files
+// https://wiki.debian.org/DebianRepository/Format#A.22Packages.22_Indices
+func GetRepositoryFile(ctx *context.Context) {
+	pv, err := debian_service.GetOrCreateRepositoryVersion(ctx.Package.Owner)
+	if err != nil {
+		apiError(ctx, http.StatusInternalServerError, err)
+		return
+	}
+
+	key := ctx.Params("distribution")
+
+	component := ctx.Params("component")
+	architecture := strings.TrimPrefix(ctx.Params("architecture"), "binary-")
+	if component != "" && architecture != "" {
+		key += "|" + component + "|" + architecture
+	}
+
+	s, pf, err := packages_service.GetFileStreamByPackageVersion(
 		ctx,
-		&packages_service.PackageInfo{
-			Owner:       ctx.Package.Owner,
-			PackageType: packages_model.TypeDebian,
-			Name:        packageName,
-			Version:     packageVersion,
-		},
+		pv,
 		&packages_service.PackageFileInfo{
-			Filename: filename,
+			Filename:     ctx.Params("filename"),
+			CompositeKey: key,
 		},
 	)
 	if err != nil {
 		if err == packages_model.ErrPackageNotExist || err == packages_model.ErrPackageFileNotExist {
 			apiError(ctx, http.StatusNotFound, err)
-			return
+		} else {
+			apiError(ctx, http.StatusInternalServerError, err)
 		}
-		apiError(ctx, http.StatusInternalServerError, err)
 		return
 	}
 	defer s.Close()
@@ -75,22 +93,57 @@ func GetPackage(ctx *context.Context) {
 	})
 }
 
-func PutPackage(ctx *context.Context) {
-	packageName := ctx.Params("packagename")
+// https://wiki.debian.org/DebianRepository/Format#indices_acquisition_via_hashsums_.28by-hash.29
+func GetRepositoryFileByHash(ctx *context.Context) {
+	pv, err := debian_service.GetOrCreateRepositoryVersion(ctx.Package.Owner)
+	if err != nil {
+		apiError(ctx, http.StatusInternalServerError, err)
+		return
+	}
+
+	algorithmn := strings.ToLower(ctx.Params("algorithmn"))
+	if algorithmn == "md5sum" {
+		algorithmn = "md5"
+	}
 
-	if !namePattern.MatchString(packageName) {
-		apiError(ctx, http.StatusBadRequest, errors.New("Invalid package name"))
+	pfs, _, err := packages_model.SearchFiles(ctx, &packages_model.PackageFileSearchOptions{
+		VersionID:      pv.ID,
+		Hash:           strings.ToLower(ctx.Params("hash")),
+		HashAlgorithmn: algorithmn,
+	})
+	if err != nil {
+		apiError(ctx, http.StatusInternalServerError, err)
+		return
+	}
+	if len(pfs) != 1 {
+		apiError(ctx, http.StatusNotFound, nil)
 		return
 	}
 
-	packageVersion := ctx.Params("packageversion")
-	if packageVersion != strings.TrimSpace(packageVersion) {
-		apiError(ctx, http.StatusBadRequest, errors.New("Invalid package version"))
+	s, pf, err := packages_service.GetPackageFileStream(ctx, pfs[0])
+	if err != nil {
+		if errors.Is(err, util.ErrNotExist) {
+			apiError(ctx, http.StatusNotFound, err)
+		} else {
+			apiError(ctx, http.StatusInternalServerError, err)
+		}
 		return
 	}
+	defer s.Close()
 
-	packageArch := ctx.Params("arch")
-	// TODO Check arch
+	ctx.ServeContent(s, &context.ServeHeaderOptions{
+		Filename:     pf.Name,
+		LastModified: pf.CreatedUnix.AsLocalTime(),
+	})
+}
+
+func UploadPackageFile(ctx *context.Context) {
+	distribution := strings.TrimSpace(ctx.Params("distribution"))
+	component := strings.TrimSpace(ctx.Params("component"))
+	if distribution == "" || component == "" {
+		apiError(ctx, http.StatusBadRequest, "invalid distribution or component")
+		return
+	}
 
 	upload, close, err := ctx.UploadStream()
 	if err != nil {
@@ -101,38 +154,59 @@ func PutPackage(ctx *context.Context) {
 		defer upload.Close()
 	}
 
-	buf, err := packages_module.CreateHashedBufferFromReader(upload, 32*1024*1024)
+	buf, err := packages_module.CreateHashedBufferFromReader(upload)
 	if err != nil {
-		log.Error("Error creating hashed buffer: %v", err)
 		apiError(ctx, http.StatusInternalServerError, err)
 		return
 	}
 	defer buf.Close()
 
-	filename := fmt.Sprintf("%s_%s_%s.deb", packageName, packageVersion, packageArch)
+	pck, err := debian_module.ParsePackage(buf)
+	if err != nil {
+		if errors.Is(err, util.ErrInvalidArgument) {
+			apiError(ctx, http.StatusBadRequest, err)
+		} else {
+			apiError(ctx, http.StatusInternalServerError, err)
+		}
+		return
+	}
+
+	if _, err := buf.Seek(0, io.SeekStart); err != nil {
+		apiError(ctx, http.StatusInternalServerError, err)
+		return
+	}
+
 	_, _, err = packages_service.CreatePackageOrAddFileToExisting(
 		&packages_service.PackageCreationInfo{
 			PackageInfo: packages_service.PackageInfo{
 				Owner:       ctx.Package.Owner,
 				PackageType: packages_model.TypeDebian,
-				Name:        packageName,
-				Version:     packageVersion,
+				Name:        pck.Name,
+				Version:     pck.Version,
 			},
-			Creator: ctx.Doer,
+			Creator:  ctx.Doer,
+			Metadata: pck.Metadata,
 		},
 		&packages_service.PackageFileCreationInfo{
 			PackageFileInfo: packages_service.PackageFileInfo{
-				Filename: filename,
+				Filename:     fmt.Sprintf("%s_%s_%s.deb", pck.Name, pck.Version, pck.Architecture),
+				CompositeKey: fmt.Sprintf("%s|%s", distribution, component),
 			},
 			Creator: ctx.Doer,
 			Data:    buf,
 			IsLead:  true,
+			Properties: map[string]string{
+				debian_module.PropertyDistribution: distribution,
+				debian_module.PropertyComponent:    component,
+				debian_module.PropertyArchitecture: pck.Architecture,
+				debian_module.PropertyControl:      pck.Control,
+			},
 		},
 	)
 	if err != nil {
 		switch err {
-		case packages_model.ErrDuplicatePackageFile:
-			apiError(ctx, http.StatusConflict, err)
+		case packages_model.ErrDuplicatePackageVersion:
+			apiError(ctx, http.StatusBadRequest, err)
 		case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize:
 			apiError(ctx, http.StatusForbidden, err)
 		default:
@@ -141,296 +215,112 @@ func PutPackage(ctx *context.Context) {
 		return
 	}
 
-	DebianRepoUpdate(ctx, packageArch)
-	ctx.Status(http.StatusCreated)
-}
-
-func DeletePackage(ctx *context.Context) {
-	packageName := ctx.Params("packagename")
-	packageVersion := ctx.Params("packageversion")
-	packageArch := ctx.Params("arch")
-	filename := fmt.Sprintf("%s_%s_%s.deb", packageName, packageVersion, packageArch)
-
-	pv, pf, err := func() (*packages_model.PackageVersion, *packages_model.PackageFile, error) {
-		pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeDebian, packageName, packageVersion)
-		if err != nil {
-			return nil, nil, err
-		}
-
-		pf, err := packages_model.GetFileForVersionByName(ctx, pv.ID, filename, packages_model.EmptyFileKey)
-		if err != nil {
-			return nil, nil, err
-		}
-
-		return pv, pf, nil
-	}()
-	if err != nil {
-		if err == packages_model.ErrPackageNotExist || err == packages_model.ErrPackageFileNotExist {
-			apiError(ctx, http.StatusNotFound, err)
-			return
-		}
-		apiError(ctx, http.StatusInternalServerError, err)
-		return
-	}
-
-	pfs, err := packages_model.GetFilesByVersionID(ctx, pv.ID)
-	if err != nil {
+	if err := debian_service.GenerateRepositoryFiles(ctx, ctx.Package.Owner, distribution, component, pck.Architecture); err != nil {
 		apiError(ctx, http.StatusInternalServerError, err)
 		return
 	}
 
-	if len(pfs) == 1 {
-		if err := packages_service.RemovePackageVersion(ctx.Doer, pv); err != nil {
-			apiError(ctx, http.StatusInternalServerError, err)
-			return
-		}
-	} else {
-		if err := packages_service.DeletePackageFile(ctx, pf); err != nil {
-			apiError(ctx, http.StatusInternalServerError, err)
-			return
-		}
-	}
-
-	DebianRepoUpdate(ctx, packageArch)
-	ctx.Status(http.StatusNoContent)
+	ctx.Status(http.StatusCreated)
 }
 
-func GetDebianFileDescriptors(ctx *context.Context) ([]*packages_model.PackageFileDescriptor, error) {
-	pvs, err := packages_model.GetVersionsByPackageType(ctx, ctx.Package.Owner.ID, packages_model.TypeDebian)
-	if err != nil {
-		return nil, err
-	}
-
-	pds, err := packages_model.GetPackageDescriptors(ctx, pvs)
-	if err != nil {
-		return nil, err
-	}
-
-	files := make([]*packages_model.PackageFileDescriptor, 0)
-	for _, pd := range pds {
-		files = append(files, pd.Files...)
-	}
-
-	return files, nil
-}
+func DownloadPackageFile(ctx *context.Context) {
+	name := ctx.Params("name")
+	version := ctx.Params("version")
 
-func GetDebianFilesByArch(ctx *context.Context) (map[string][]*packages_model.PackageFileDescriptor, error) {
-	pfds, err := GetDebianFileDescriptors(ctx)
+	s, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
+		ctx,
+		&packages_service.PackageInfo{
+			Owner:       ctx.Package.Owner,
+			PackageType: packages_model.TypeDebian,
+			Name:        name,
+			Version:     version,
+		},
+		&packages_service.PackageFileInfo{
+			Filename:     fmt.Sprintf("%s_%s_%s.deb", name, version, ctx.Params("architecture")),
+			CompositeKey: fmt.Sprintf("%s|%s", ctx.Params("distribution"), ctx.Params("component")),
+		},
+	)
 	if err != nil {
-		return nil, err
-	}
-
-	splitter := regexp.MustCompile(`^([^_]+)_([^_]+)_([^.]+).deb$`)
-
-	files := make(map[string][]*packages_model.PackageFileDescriptor)
-
-	for _, pfd := range pfds {
-		filename := pfd.File.Name
-		matches := splitter.FindStringSubmatch(filename)
-		if matches == nil || len(matches) != 4 {
-			log.Error("Found invalid filename: %s", filename)
-			return nil, errors.New("Found invalid filename")
+		if err == packages_model.ErrPackageNotExist || err == packages_model.ErrPackageFileNotExist {
+			apiError(ctx, http.StatusNotFound, err)
+		} else {
+			apiError(ctx, http.StatusInternalServerError, err)
 		}
-
-		arch := matches[3]
-		files[arch] = append(files[arch], pfd)
-	}
-
-	return files, nil
-}
-
-func GetArchIndex(ctx *context.Context) {
-	ctx.Data["IndexFiles"] = map[string]string{
-		"../":         "../",
-		"Packages":    "Packages",
-		"Packages.gz": "Packages.gz",
-		"Release":     "Release",
-	}
-
-	// This does mean that "amd64" and "binary-amd64" can both be used
-	// Don't think that's an issue (?)
-	arch := ctx.Params("packagearch")
-	if len(arch) > 7 && arch[:7] == "binary-" {
-		arch = arch[7:]
-	}
-
-	archs, err := GetDebianFilesByArch(ctx)
-	if err != nil {
-		apiError(ctx, http.StatusInternalServerError, err)
-		return
-	}
-
-	_, exists := archs[arch]
-	if !exists {
-		ctx.Status(http.StatusNotFound)
 		return
 	}
+	defer s.Close()
 
-	ctx.Data["IndexPath"] = ctx.Req.URL.Path
-	ctx.HTML(http.StatusOK, "api/packages/debian/index")
+	ctx.ServeContent(s, &context.ServeHeaderOptions{
+		Filename:     pf.Name,
+		LastModified: pf.CreatedUnix.AsLocalTime(),
+	})
 }
 
-func GetIndex(ctx *context.Context) {
-	basePath := "/api/packages/" + ctx.Params("username") + "/debian"
-
-	relPath, err := filepath.Rel(basePath, ctx.Req.URL.Path)
-	if err != nil {
-		apiError(ctx, http.StatusInternalServerError, err)
-		log.Error("Path '%s' is not inside '%s'?", ctx.Req.URL.Path, basePath)
-		return
-	}
+func DeletePackageFile(ctx *context.Context) {
+	distribution := ctx.Params("distribution")
+	component := ctx.Params("component")
+	name := ctx.Params("name")
+	version := ctx.Params("version")
+	architecture := ctx.Params("architecture")
 
-	log.Info("RelPath: %s", relPath)
+	owner := ctx.Package.Owner
 
-	switch relPath {
-	case ".":
-		ctx.Data["IndexFiles"] = map[string]string{
-			"../":        "./",
-			"dists/":     "dists/",
-			"pool/":      "pool/",
-			"debian.key": "debian.key",
-		}
-	case "pool":
-		files := map[string]string{
-			"../": "../",
-		}
+	var pd *packages_model.PackageDescriptor
 
-		// Add all the Debian package files
-		pfds, err := GetDebianFileDescriptors(ctx)
+	err := db.WithTx(ctx, func(ctx stdctx.Context) error {
+		pv, err := packages_model.GetVersionByNameAndVersion(ctx, owner.ID, packages_model.TypeDebian, name, version)
 		if err != nil {
-			apiError(ctx, http.StatusInternalServerError, err)
-			return
+			return err
 		}
 
-		for _, pfd := range pfds {
-			files[pfd.File.Name] = pfd.File.Name
+		pf, err := packages_model.GetFileForVersionByName(
+			ctx,
+			pv.ID,
+			fmt.Sprintf("%s_%s_%s.deb", name, version, architecture),
+			fmt.Sprintf("%s|%s", distribution, component),
+		)
+		if err != nil {
+			return err
 		}
 
-		ctx.Data["IndexFiles"] = files
-	case "dists":
-		ctx.Data["IndexFiles"] = map[string]string{
-			"../":    "../",
-			"gitea/": "gitea/",
-		}
-	case "dists/gitea":
-		ctx.Data["IndexFiles"] = map[string]string{
-			"../":         "../",
-			"main/":       "main/",
-			"InRelease":   "InRelease",
-			"Release":     "Release",
-			"Release.gpg": "Release.gpg",
-		}
-	case "dists/gitea/main":
-		files := map[string]string{
-			"../": "../",
+		if err := packages_service.DeletePackageFile(ctx, pf); err != nil {
+			return err
 		}
 
-		// Add directory for each arch
-		archs, err := GetDebianFilesByArch(ctx)
+		has, err := packages_model.HasVersionFileReferences(ctx, pv.ID)
 		if err != nil {
-			apiError(ctx, http.StatusInternalServerError, err)
-			return
+			return err
 		}
+		if !has {
+			pd, err = packages_model.GetPackageDescriptor(ctx, pv)
+			if err != nil {
+				return err
+			}
 
-		for a := range archs {
-			var name string
-			switch a {
-			case "source":
-				name = a + "/"
-			default:
-				name = "binary-" + a + "/"
+			if err := packages_service.DeletePackageVersionAndReferences(ctx, pv); err != nil {
+				return err
 			}
-			files[name] = name
 		}
 
-		ctx.Data["IndexFiles"] = files
-	}
-	ctx.Data["IndexPath"] = ctx.Req.URL.Path
-	ctx.HTML(http.StatusOK, "api/packages/debian/index")
-}
-
-func DebianRepoUpdate(ctx *context.Context, arch string) {
-	if err := CreatePackagesGz(ctx, arch); err != nil {
-		apiError(ctx, http.StatusInternalServerError, err)
-		return
-	}
-	if err := CreateRelease(ctx); err != nil {
-		apiError(ctx, http.StatusInternalServerError, err)
-		return
-	}
-}
-
-func GetArchRelease(ctx *context.Context) {
-	data := fmt.Sprintf("Component: main\nArchitecture: %s\n", ctx.Params("packagearch"))
-	ctx.PlainText(http.StatusOK, data)
-}
-
-func GetPackages(ctx *context.Context) {
-	basePath := filepath.Join(setting.AppDataPath, "debian_repo")
-	archPath := filepath.Join(basePath, ctx.Package.Owner.Name, ctx.Params("packagearch"))
-	packagesPath := filepath.Join(archPath, "Packages")
-	f, err := os.Open(packagesPath)
-	if err != nil {
-		apiError(ctx, http.StatusInternalServerError, err)
-	}
-	ctx.ServeContent(f, &context.ServeHeaderOptions{
-		Filename: "Packages",
-	})
-}
-
-func GetPackagesGZ(ctx *context.Context) {
-	basePath := filepath.Join(setting.AppDataPath, "debian_repo")
-	archPath := filepath.Join(basePath, ctx.Package.Owner.Name, ctx.Params("packagearch"))
-	packagesGzPath := filepath.Join(archPath, "Packages.gz")
-	f, err := os.Open(packagesGzPath)
-	if err != nil {
-		apiError(ctx, http.StatusInternalServerError, err)
-	}
-	ctx.ServeContent(f, &context.ServeHeaderOptions{
-		Filename: "Packages.gz",
+		return nil
 	})
-}
-
-func GetRelease(ctx *context.Context) {
-	path := filepath.Join(setting.AppDataPath, "debian_repo", ctx.Package.Owner.Name, "Release")
-	f, err := os.Open(path)
 	if err != nil {
-		apiError(ctx, http.StatusInternalServerError, err)
+		if errors.Is(err, util.ErrNotExist) {
+			apiError(ctx, http.StatusNotFound, err)
+		} else {
+			apiError(ctx, http.StatusInternalServerError, err)
+		}
+		return
 	}
-	ctx.ServeContent(f, &context.ServeHeaderOptions{
-		Filename: "Release",
-	})
-}
 
-func GetReleaseGPG(ctx *context.Context) {
-	path := filepath.Join(setting.AppDataPath, "debian_repo", ctx.Package.Owner.Name, "Release.gpg")
-	f, err := os.Open(path)
-	if err != nil {
-		apiError(ctx, http.StatusInternalServerError, err)
+	if pd != nil {
+		notification.NotifyPackageDelete(ctx, ctx.Doer, pd)
 	}
-	ctx.ServeContent(f, &context.ServeHeaderOptions{
-		Filename: "Release.gpg",
-	})
-}
 
-func GetInRelease(ctx *context.Context) {
-	path := filepath.Join(setting.AppDataPath, "debian_repo", ctx.Package.Owner.Name, "InRelease")
-	f, err := os.Open(path)
-	if err != nil {
+	if err := debian_service.GenerateRepositoryFiles(ctx, ctx.Package.Owner, distribution, component, architecture); err != nil {
 		apiError(ctx, http.StatusInternalServerError, err)
+		return
 	}
-	ctx.ServeContent(f, &context.ServeHeaderOptions{
-		Filename: "InRelease",
-	})
-}
 
-func GetPublicKey(ctx *context.Context) {
-	path := filepath.Join(setting.AppDataPath, "debian_public.gpg")
-	f, err := os.Open(path)
-	if err != nil {
-		apiError(ctx, http.StatusInternalServerError, err)
-	}
-	ctx.ServeContent(f, &context.ServeHeaderOptions{
-		Filename: "debian.key",
-	})
+	ctx.Status(http.StatusNoContent)
 }
diff --git a/routers/api/packages/debian/packages.go b/routers/api/packages/debian/packages.go
deleted file mode 100644
index fda3904729ce9..0000000000000
--- a/routers/api/packages/debian/packages.go
+++ /dev/null
@@ -1,203 +0,0 @@
-package debian
-
-import (
-	"archive/tar"
-	"bytes"
-	"compress/gzip"
-	"errors"
-	"fmt"
-	"io"
-	"os"
-	"path/filepath"
-	"strings"
-
-	packages_model "code.gitea.io/gitea/models/packages"
-	"code.gitea.io/gitea/modules/context"
-	"code.gitea.io/gitea/modules/setting"
-	"code.gitea.io/gitea/services/packages"
-	"github.com/blakesmith/ar"
-	lzma "github.com/xi2/xz"
-)
-
-type Compression int
-
-const (
-	LZMA Compression = iota
-	GZIP
-)
-
-func CreatePackagesGz(ctx *context.Context, arch string) error {
-	basePath := filepath.Join(setting.AppDataPath, "debian_repo")
-
-	archDir := arch
-	if arch != "source" {
-		archDir = "binary-" + archDir
-	}
-	archPath := filepath.Join(basePath, ctx.Package.Owner.Name, archDir)
-
-	info, err := os.Stat(archPath)
-	if err != nil {
-		if os.IsNotExist(err) {
-			if err2 := os.MkdirAll(archPath, 0775); err2 != nil {
-				return fmt.Errorf("Cannot create Debian repo path: %s", err)
-			}
-		} else {
-			return fmt.Errorf("Cannot stat Debian repo path: %s", err)
-		}
-	} else {
-		if !info.IsDir() {
-			return fmt.Errorf("Debian repo path is not a directory!")
-		}
-	}
-
-	packagesPath := filepath.Join(archPath, "Packages")
-	packagesGzPath := filepath.Join(archPath, "Packages.gz")
-
-	packagesFile, err := os.Create(packagesPath)
-	if err != nil {
-		return fmt.Errorf("Failed to create Packages file %s: %s", packagesPath, err)
-	}
-	defer packagesFile.Close()
-
-	packagesGzFile, err := os.Create(packagesGzPath)
-	if err != nil {
-		return fmt.Errorf("Failed to create Packages.gz file %s: %s", packagesGzPath, err)
-	}
-	defer packagesGzFile.Close()
-
-	gzOut := gzip.NewWriter(packagesGzFile)
-	defer gzOut.Close()
-
-	writer := io.MultiWriter(packagesFile, gzOut)
-
-	allFiles, err := GetDebianFilesByArch(ctx)
-	if err != nil {
-		return err
-	}
-
-	archFiles, exists := allFiles[arch]
-	if !exists {
-		return fmt.Errorf("No files in arch \"%s\"!", arch)
-	}
-
-	if arch != "source" {
-		archFiles = append(archFiles, allFiles["all"]...)
-	}
-
-	for i, file := range archFiles {
-		var packBuf bytes.Buffer
-		tempCtlData, err := InspectPackage(ctx, file)
-		if err != nil {
-			return err
-		}
-
-		blob, err := packages_model.GetBlobByID(ctx, file.File.BlobID)
-		if err != nil {
-			return err
-		}
-
-		packBuf.WriteString(tempCtlData)
-		dir := filepath.Join("/pool", file.File.Name)
-		fmt.Fprintf(&packBuf, "Filename: %s\n", dir)
-		fmt.Fprintf(&packBuf, "Size: %d\n", blob.Size)
-		fmt.Fprintf(&packBuf, "MD5sum: %s\n", blob.HashMD5)
-		fmt.Fprintf(&packBuf, "SHA1: %s\n", blob.HashSHA1)
-		fmt.Fprintf(&packBuf, "SHA256: %s\n", blob.HashSHA256)
-
-		if i != (len(archFiles) - 1) {
-			packBuf.WriteString("\n\n")
-		}
-		writer.Write(packBuf.Bytes())
-	}
-
-	return nil
-}
-
-func InspectPackage(ctx *context.Context, fd *packages_model.PackageFileDescriptor) (string, error) {
-	// blob, err := packages_model.GetBlobByID(ctx, file.File.BlobID)
-	// if err != nil {
-	// 	return "", err
-	// }
-
-	fstream, _, err := packages.GetFileStreamByPackageVersionAndFileID(ctx, ctx.Package.Owner, fd.File.VersionID, fd.File.ID)
-	if err != nil {
-		return "", err
-	}
-	defer fstream.Close()
-	reader := ar.NewReader(fstream)
-
-	var controlBuf bytes.Buffer
-	for {
-		header, err := reader.Next()
-		if err == io.EOF {
-			break
-		}
-
-		if err != nil {
-			return "", fmt.Errorf("Error in inspect loop: %s", err)
-		}
-
-		if strings.Contains(header.Name, "control.tar") {
-			var compression Compression
-			switch strings.TrimRight(header.Name, "/") {
-			case "control.tar.gz":
-				compression = GZIP
-			case "control.tar.xz":
-				compression = LZMA
-			default:
-				return "", errors.New("No control file found")
-			}
-
-			io.Copy(&controlBuf, reader)
-			return InspectPackageControl(compression, controlBuf)
-		}
-	}
-
-	return "", errors.New("Unreachable?")
-}
-
-func InspectPackageControl(comp Compression, data bytes.Buffer) (string, error) {
-	var tarReader *tar.Reader
-	var err error
-
-	switch comp {
-	case GZIP:
-		var compFile *gzip.Reader
-		compFile, err = gzip.NewReader(bytes.NewReader(data.Bytes()))
-		tarReader = tar.NewReader(compFile)
-	case LZMA:
-		var compFile *lzma.Reader
-		compFile, err = lzma.NewReader(bytes.NewReader(data.Bytes()), lzma.DefaultDictMax)
-		tarReader = tar.NewReader(compFile)
-	}
-
-	if err != nil {
-		return "", fmt.Errorf("Error creating gzip/lzma reader: %s", err)
-	}
-
-	var controlBuf bytes.Buffer
-	for {
-		header, err := tarReader.Next()
-		if err == io.EOF {
-			break
-		}
-
-		if err != nil {
-			return "", fmt.Errorf("Failed to inspect package: %s", err)
-		}
-
-		name := header.Name
-		switch header.Typeflag {
-		case tar.TypeDir:
-			continue
-		case tar.TypeReg:
-			switch name {
-			case "control", "./control":
-				io.Copy(&controlBuf, tarReader)
-				return strings.TrimRight(controlBuf.String(), "\n") + "\n", nil
-			}
-		}
-	}
-
-	return "", nil
-}
diff --git a/routers/api/packages/debian/releases.go b/routers/api/packages/debian/releases.go
deleted file mode 100644
index 74b877fdd40ae..0000000000000
--- a/routers/api/packages/debian/releases.go
+++ /dev/null
@@ -1,157 +0,0 @@
-package debian
-
-import (
-	"crypto/md5"
-	"crypto/sha1"
-	"crypto/sha256"
-	"encoding/hex"
-	"fmt"
-	"io"
-	"os"
-	"path/filepath"
-	"strings"
-	"time"
-
-	"code.gitea.io/gitea/modules/context"
-	"code.gitea.io/gitea/modules/setting"
-	"github.com/keybase/go-crypto/openpgp"
-	"github.com/keybase/go-crypto/openpgp/clearsign"
-	"github.com/keybase/go-crypto/openpgp/packet"
-)
-
-func CreateRelease(ctx *context.Context) error {
-	basePath := filepath.Join(setting.AppDataPath, "debian_repo", ctx.Package.Owner.Name)
-	releasePath := filepath.Join(basePath, "Release")
-
-	releaseFile, err := os.Create(releasePath)
-	if err != nil {
-		return fmt.Errorf("Failed to create Release file: %s", err)
-	}
-	defer releaseFile.Close()
-
-	allFiles, err := GetDebianFilesByArch(ctx)
-	if err != nil {
-		return err
-	}
-
-	archs := make([]string, 0)
-	for a := range allFiles {
-		archs = append(archs, a)
-	}
-
-	currentTime := time.Now().UTC()
-	fmt.Fprintf(releaseFile, "Suite: gitea\n")
-	fmt.Fprintf(releaseFile, "Codename: gitea\n")
-	fmt.Fprintf(releaseFile, "Components: main\n")
-	fmt.Fprintf(releaseFile, "Architectures: %s\n", strings.Join(archs, " "))
-	fmt.Fprintf(releaseFile, "Date: %s\n", currentTime.Format(time.RFC1123))
-
-	var md5sums, sha1sums, sha256sums strings.Builder
-
-	err = filepath.Walk(basePath, func(path string, file os.FileInfo, err error) error {
-		if err != nil {
-			return err
-		}
-
-		if strings.HasSuffix(path, "Packages.gz") || strings.HasSuffix(path, "Packages") {
-			var (
-				md5hash    = md5.New()
-				sha1hash   = sha1.New()
-				sha256hash = sha256.New()
-			)
-
-			relPath, _ := filepath.Rel(basePath, path)
-			relPath = filepath.Join("main", relPath)
-			f, err := os.Open(path)
-			if err != nil {
-				return fmt.Errorf("Error opening Packages file for reading: %s", err)
-			}
-
-			if _, err = io.Copy(io.MultiWriter(md5hash, sha1hash, sha256hash), f); err != nil {
-				return fmt.Errorf("Error hashing file for Release list: %s", err)
-			}
-
-			fmt.Fprintf(&md5sums, " %s %d %s\n", hex.EncodeToString(md5hash.Sum(nil)), file.Size(), relPath)
-			fmt.Fprintf(&sha1sums, " %s %d %s\n", hex.EncodeToString(sha1hash.Sum(nil)), file.Size(), relPath)
-			fmt.Fprintf(&sha256sums, " %s %d %s\n", hex.EncodeToString(sha256hash.Sum(nil)), file.Size(), relPath)
-			f = nil
-		}
-		return nil
-	})
-
-	if err != nil {
-		return fmt.Errorf("Error scanning for Package files: %s", err)
-	}
-
-	releaseFile.WriteString("MD5Sum:\n")
-	releaseFile.WriteString(md5sums.String())
-	releaseFile.WriteString("SHA1:\n")
-	releaseFile.WriteString(sha1sums.String())
-	releaseFile.WriteString("SHA256:\n")
-	releaseFile.WriteString(sha256sums.String())
-
-	if err = SignRelease(ctx); err != nil {
-		return fmt.Errorf("Error signing Release file: %s", err)
-	}
-
-	return nil
-}
-
-func GetEntity() (*openpgp.Entity, error) {
-	keyPath := filepath.Join(setting.AppDataPath, "debian.gpg")
-	keyFile, err := os.Open(keyPath)
-	if err != nil {
-		return nil, fmt.Errorf("Error opening key file: %s", err)
-	}
-	defer keyFile.Close()
-
-	r := packet.NewReader(keyFile)
-	return openpgp.ReadEntity(r)
-}
-
-func SignRelease(ctx *context.Context) error {
-	e, err := GetEntity()
-	if err != nil {
-		return fmt.Errorf("Cannot read entity from key file: %s", err)
-	}
-
-	basePath := filepath.Join(setting.AppDataPath, "debian_repo", ctx.Package.Owner.Name)
-	releasePath := filepath.Join(basePath, "Release")
-	releaseGzPath := filepath.Join(basePath, "Release.gpg")
-	inreleasePath := filepath.Join(basePath, "InRelease")
-
-	releaseFile, err := os.Open(releasePath)
-	if err != nil {
-		return fmt.Errorf("Error opening Release file (%s) for reading: %s", releasePath, err)
-	}
-	defer releaseFile.Close()
-
-	releaseGzFile, err := os.Create(releaseGzPath)
-	if err != nil {
-		return fmt.Errorf("Error opening Release.gpg file (%s) for writing: %s", releaseGzPath, err)
-	}
-	defer releaseGzFile.Close()
-
-	err = openpgp.ArmoredDetachSign(releaseGzFile, e, releaseFile, nil)
-	if err != nil {
-		return fmt.Errorf("Error writing signature to Release.gpg file: %s", err)
-	}
-
-	releaseFile.Seek(0, 0)
-
-	inreleaseFile, err := os.Create(inreleasePath)
-	if err != nil {
-		return fmt.Errorf("Error opening InRelease file (%s) for writing: %s", inreleasePath, err)
-	}
-	defer inreleaseFile.Close()
-
-	writer, err := clearsign.Encode(inreleaseFile, e.PrivateKey, nil)
-	if err != nil {
-		return fmt.Errorf("Error signing InRelease file: %s", err)
-	}
-
-	io.Copy(writer, releaseFile)
-	writer.Close()
-
-	return nil
-}
diff --git a/routers/api/packages/generic/generic.go b/routers/api/packages/generic/generic.go
index 5fba02cacd468..0c873119ef54d 100644
--- a/routers/api/packages/generic/generic.go
+++ b/routers/api/packages/generic/generic.go
@@ -84,7 +84,7 @@ func UploadPackage(ctx *context.Context) {
 		defer upload.Close()
 	}
 
-	buf, err := packages_module.CreateHashedBufferFromReader(upload, 32*1024*1024)
+	buf, err := packages_module.CreateHashedBufferFromReader(upload)
 	if err != nil {
 		log.Error("Error creating hashed buffer: %v", err)
 		apiError(ctx, http.StatusInternalServerError, err)
diff --git a/routers/api/packages/helm/helm.go b/routers/api/packages/helm/helm.go
index 3bcce6bdf5855..b7edc8b7fef8a 100644
--- a/routers/api/packages/helm/helm.go
+++ b/routers/api/packages/helm/helm.go
@@ -155,7 +155,7 @@ func UploadPackage(ctx *context.Context) {
 		defer upload.Close()
 	}
 
-	buf, err := packages_module.CreateHashedBufferFromReader(upload, 32*1024*1024)
+	buf, err := packages_module.CreateHashedBufferFromReader(upload)
 	if err != nil {
 		apiError(ctx, http.StatusInternalServerError, err)
 		return
diff --git a/routers/api/packages/maven/maven.go b/routers/api/packages/maven/maven.go
index d0c9983cbf1f2..e6a4a742e3bb4 100644
--- a/routers/api/packages/maven/maven.go
+++ b/routers/api/packages/maven/maven.go
@@ -244,7 +244,7 @@ func UploadPackageFile(ctx *context.Context) {
 
 	packageName := params.GroupID + "-" + params.ArtifactID
 
-	buf, err := packages_module.CreateHashedBufferFromReader(ctx.Req.Body, 32*1024*1024)
+	buf, err := packages_module.CreateHashedBufferFromReader(ctx.Req.Body)
 	if err != nil {
 		apiError(ctx, http.StatusInternalServerError, err)
 		return
diff --git a/routers/api/packages/npm/npm.go b/routers/api/packages/npm/npm.go
index 0d25f173e922b..6b3de5704041b 100644
--- a/routers/api/packages/npm/npm.go
+++ b/routers/api/packages/npm/npm.go
@@ -166,7 +166,7 @@ func UploadPackage(ctx *context.Context) {
 		return
 	}
 
-	buf, err := packages_module.CreateHashedBufferFromReader(bytes.NewReader(npmPackage.Data), 32*1024*1024)
+	buf, err := packages_module.CreateHashedBufferFromReader(bytes.NewReader(npmPackage.Data))
 	if err != nil {
 		apiError(ctx, http.StatusInternalServerError, err)
 		return
diff --git a/routers/api/packages/nuget/nuget.go b/routers/api/packages/nuget/nuget.go
index 6423db7d3a214..daed838c1b861 100644
--- a/routers/api/packages/nuget/nuget.go
+++ b/routers/api/packages/nuget/nuget.go
@@ -432,7 +432,7 @@ func UploadSymbolPackage(ctx *context.Context) {
 		Version:     np.Version,
 	}
 
-	_, _, err = packages_service.AddFileToExistingPackage(
+	_, err = packages_service.AddFileToExistingPackage(
 		pi,
 		&packages_service.PackageFileCreationInfo{
 			PackageFileInfo: packages_service.PackageFileInfo{
@@ -458,7 +458,7 @@ func UploadSymbolPackage(ctx *context.Context) {
 	}
 
 	for _, pdb := range pdbs {
-		_, _, err := packages_service.AddFileToExistingPackage(
+		_, err := packages_service.AddFileToExistingPackage(
 			pi,
 			&packages_service.PackageFileCreationInfo{
 				PackageFileInfo: packages_service.PackageFileInfo{
@@ -502,7 +502,7 @@ func processUploadedFile(ctx *context.Context, expectedType nuget_module.Package
 		closables = append(closables, upload)
 	}
 
-	buf, err := packages_module.CreateHashedBufferFromReader(upload, 32*1024*1024)
+	buf, err := packages_module.CreateHashedBufferFromReader(upload)
 	if err != nil {
 		apiError(ctx, http.StatusInternalServerError, err)
 		return nil, nil, closables
diff --git a/routers/api/packages/pub/pub.go b/routers/api/packages/pub/pub.go
index 1ece4e18ed6d4..ae0c6e7859898 100644
--- a/routers/api/packages/pub/pub.go
+++ b/routers/api/packages/pub/pub.go
@@ -166,7 +166,7 @@ func UploadPackageFile(ctx *context.Context) {
 	}
 	defer file.Close()
 
-	buf, err := packages_module.CreateHashedBufferFromReader(file, 32*1024*1024)
+	buf, err := packages_module.CreateHashedBufferFromReader(file)
 	if err != nil {
 		apiError(ctx, http.StatusInternalServerError, err)
 		return
diff --git a/routers/api/packages/pypi/pypi.go b/routers/api/packages/pypi/pypi.go
index 609c63dc64f5a..a84261d44428e 100644
--- a/routers/api/packages/pypi/pypi.go
+++ b/routers/api/packages/pypi/pypi.go
@@ -111,7 +111,7 @@ func UploadPackageFile(ctx *context.Context) {
 	}
 	defer file.Close()
 
-	buf, err := packages_module.CreateHashedBufferFromReader(file, 32*1024*1024)
+	buf, err := packages_module.CreateHashedBufferFromReader(file)
 	if err != nil {
 		apiError(ctx, http.StatusInternalServerError, err)
 		return
diff --git a/routers/api/packages/rubygems/rubygems.go b/routers/api/packages/rubygems/rubygems.go
index af358fb82fbad..740efa9baba8b 100644
--- a/routers/api/packages/rubygems/rubygems.go
+++ b/routers/api/packages/rubygems/rubygems.go
@@ -209,7 +209,7 @@ func UploadPackageFile(ctx *context.Context) {
 		defer upload.Close()
 	}
 
-	buf, err := packages_module.CreateHashedBufferFromReader(upload, 32*1024*1024)
+	buf, err := packages_module.CreateHashedBufferFromReader(upload)
 	if err != nil {
 		apiError(ctx, http.StatusInternalServerError, err)
 		return
diff --git a/routers/api/packages/vagrant/vagrant.go b/routers/api/packages/vagrant/vagrant.go
index 7b76ab79b0580..cefdc45b10ff4 100644
--- a/routers/api/packages/vagrant/vagrant.go
+++ b/routers/api/packages/vagrant/vagrant.go
@@ -158,7 +158,7 @@ func UploadPackageFile(ctx *context.Context) {
 		defer upload.Close()
 	}
 
-	buf, err := packages_module.CreateHashedBufferFromReader(upload, 32*1024*1024)
+	buf, err := packages_module.CreateHashedBufferFromReader(upload)
 	if err != nil {
 		apiError(ctx, http.StatusInternalServerError, err)
 		return
diff --git a/routers/web/user/package.go b/routers/web/user/package.go
index ed4f0dd79727e..328848a7f241a 100644
--- a/routers/web/user/package.go
+++ b/routers/web/user/package.go
@@ -10,6 +10,7 @@ import (
 	org_model "code.gitea.io/gitea/models/organization"
 	packages_model "code.gitea.io/gitea/models/packages"
 	container_model "code.gitea.io/gitea/models/packages/container"
+	debian_model "code.gitea.io/gitea/models/packages/debian"
 	"code.gitea.io/gitea/models/perm"
 	access_model "code.gitea.io/gitea/models/perm/access"
 	repo_model "code.gitea.io/gitea/models/repo"
@@ -163,6 +164,28 @@ func ViewPackageVersion(ctx *context.Context) {
 	ctx.Data["IsPackagesPage"] = true
 	ctx.Data["PackageDescriptor"] = pd
 
+	switch pd.Package.Type {
+	case packages_model.TypeContainer:
+		ctx.Data["RegistryHost"] = setting.Packages.RegistryHost
+	case packages_model.TypeDebian:
+		var err error
+		ctx.Data["Distributions"], err = debian_model.GetDistributions(ctx, pd.Owner.ID)
+		if err != nil {
+			ctx.ServerError("GetDistributions", err)
+			return
+		}
+		ctx.Data["Components"], err = debian_model.GetComponents(ctx, pd.Owner.ID, "")
+		if err != nil {
+			ctx.ServerError("GetComponents", err)
+			return
+		}
+		ctx.Data["Architectures"], err = debian_model.GetArchitectures(ctx, pd.Owner.ID, "")
+		if err != nil {
+			ctx.ServerError("GetArchitectures", err)
+			return
+		}
+	}
+
 	var (
 		total int64
 		pvs   []*packages_model.PackageVersion
@@ -170,8 +193,6 @@ func ViewPackageVersion(ctx *context.Context) {
 	)
 	switch pd.Package.Type {
 	case packages_model.TypeContainer:
-		ctx.Data["RegistryHost"] = setting.Packages.RegistryHost
-
 		pvs, total, err = container_model.SearchImageTags(ctx, &container_model.ImageTagsSearchOptions{
 			Paginator: db.NewAbsoluteListOptions(0, 5),
 			PackageID: pd.Package.ID,
@@ -183,10 +204,6 @@ func ViewPackageVersion(ctx *context.Context) {
 			PackageID:  pd.Package.ID,
 			IsInternal: util.OptionalBoolFalse,
 		})
-		if err != nil {
-			ctx.ServerError("SearchVersions", err)
-			return
-		}
 	}
 	if err != nil {
 		ctx.ServerError("", err)
diff --git a/services/packages/debian/repository.go b/services/packages/debian/repository.go
new file mode 100644
index 0000000000000..ea964ceb8e02c
--- /dev/null
+++ b/services/packages/debian/repository.go
@@ -0,0 +1,378 @@
+// Copyright 2023 The Gitea Authors. All rights reserved.
+// SPDX-License-Identifier: MIT
+
+package debian
+
+import (
+	"bytes"
+	"compress/gzip"
+	"context"
+	"errors"
+	"fmt"
+	"io"
+	"strings"
+	"time"
+
+	"code.gitea.io/gitea/models/db"
+	packages_model "code.gitea.io/gitea/models/packages"
+	debian_model "code.gitea.io/gitea/models/packages/debian"
+	user_model "code.gitea.io/gitea/models/user"
+	"code.gitea.io/gitea/modules/log"
+	packages_module "code.gitea.io/gitea/modules/packages"
+	debian_module "code.gitea.io/gitea/modules/packages/debian"
+	"code.gitea.io/gitea/modules/setting"
+	"code.gitea.io/gitea/modules/util"
+	packages_service "code.gitea.io/gitea/services/packages"
+
+	"github.com/keybase/go-crypto/openpgp"
+	"github.com/keybase/go-crypto/openpgp/armor"
+	"github.com/keybase/go-crypto/openpgp/clearsign"
+	"github.com/keybase/go-crypto/openpgp/packet"
+	"github.com/ulikunitz/xz"
+)
+
+// GetOrCreateRepositoryVersion gets or creates the internal repository package
+// The Debian registry needs multiple index files which are stored in this package.
+func GetOrCreateRepositoryVersion(owner *user_model.User) (*packages_model.PackageVersion, error) {
+	var repositoryVersion *packages_model.PackageVersion
+
+	return repositoryVersion, db.WithTx(db.DefaultContext, func(ctx context.Context) error {
+		p := &packages_model.Package{
+			OwnerID:    owner.ID,
+			Type:       packages_model.TypeDebian,
+			Name:       debian_module.RepositoryPackage,
+			LowerName:  debian_module.RepositoryPackage,
+			IsInternal: true,
+		}
+		var err error
+		if p, err = packages_model.TryInsertPackage(ctx, p); err != nil {
+			if err != packages_model.ErrDuplicatePackage {
+				log.Error("Error inserting package: %v", err)
+				return err
+			}
+		}
+
+		created := true
+		pv := &packages_model.PackageVersion{
+			PackageID:    p.ID,
+			CreatorID:    owner.ID,
+			Version:      debian_module.RepositoryVersion,
+			LowerVersion: debian_module.RepositoryVersion,
+			IsInternal:   true,
+			MetadataJSON: "null",
+		}
+		if pv, err = packages_model.GetOrInsertVersion(ctx, pv); err != nil {
+			if err == packages_model.ErrDuplicatePackageVersion {
+				created = false
+			} else {
+				log.Error("Error inserting package version: %v", err)
+				return err
+			}
+		}
+
+		if created {
+			priv, pub, err := generateKeypair()
+			if err != nil {
+				return err
+			}
+
+			_, err = packages_model.InsertProperty(ctx, packages_model.PropertyTypeVersion, pv.ID, debian_module.PropertyKeyPrivate, priv)
+			if err != nil {
+				return err
+			}
+
+			_, err = packages_model.InsertProperty(ctx, packages_model.PropertyTypeVersion, pv.ID, debian_module.PropertyKeyPublic, pub)
+			if err != nil {
+				return err
+			}
+		}
+
+		repositoryVersion = pv
+
+		return nil
+	})
+}
+
+func generateKeypair() (string, string, error) {
+	e, err := openpgp.NewEntity(setting.AppName, "Debian Registry", "", nil)
+	if err != nil {
+		return "", "", err
+	}
+
+	var priv strings.Builder
+	var pub strings.Builder
+
+	w, err := armor.Encode(&priv, openpgp.PrivateKeyType, nil)
+	if err != nil {
+		return "", "", err
+	}
+	if err := e.SerializePrivate(w, nil); err != nil {
+		return "", "", err
+	}
+	w.Close()
+
+	w, err = armor.Encode(&pub, openpgp.PublicKeyType, nil)
+	if err != nil {
+		return "", "", err
+	}
+	if err := e.Serialize(w); err != nil {
+		return "", "", err
+	}
+	w.Close()
+
+	return priv.String(), pub.String(), nil
+}
+
+// GenerateRepositoryFiles generates index files for the repository
+func GenerateRepositoryFiles(ctx context.Context, owner *user_model.User, distribution, component, architecture string) error {
+	pv, err := GetOrCreateRepositoryVersion(owner)
+	if err != nil {
+		return err
+	}
+
+	if err := buildPackagesIndices(ctx, owner, pv, distribution, component, architecture); err != nil {
+		return err
+	}
+
+	return buildReleaseFiles(ctx, owner, pv, distribution)
+}
+
+// https://wiki.debian.org/DebianRepository/Format#A.22Packages.22_Indices
+func buildPackagesIndices(ctx context.Context, owner *user_model.User, repoVersion *packages_model.PackageVersion, distribution, component, architecture string) error {
+	pfds, err := debian_model.SearchLatestPackages(ctx, &debian_model.PackageSearchOptions{
+		OwnerID:      owner.ID,
+		Distribution: distribution,
+		Component:    component,
+		Architecture: architecture,
+	})
+	if err != nil {
+		return err
+	}
+
+	// Delete the package indices if there are no packages
+	if len(pfds) == 0 {
+		key := fmt.Sprintf("%s|%s|%s", distribution, component, architecture)
+		for _, filename := range []string{"Packages", "Packages.gz", "Packages.xz"} {
+			pf, err := packages_model.GetFileForVersionByName(ctx, repoVersion.ID, filename, key)
+			if err != nil && !errors.Is(err, util.ErrNotExist) {
+				return err
+			}
+
+			if err := packages_model.DeleteAllProperties(ctx, packages_model.PropertyTypeFile, pf.ID); err != nil {
+				return err
+			}
+			if err := packages_model.DeleteFileByID(ctx, pf.ID); err != nil {
+				return err
+			}
+		}
+
+		return nil
+	}
+
+	packagesContent, _ := packages_module.NewHashedBuffer()
+
+	packagesGzipContent, _ := packages_module.NewHashedBuffer()
+	gzw := gzip.NewWriter(packagesGzipContent)
+
+	packagesXzContent, _ := packages_module.NewHashedBuffer()
+	xzw, _ := xz.NewWriter(packagesXzContent)
+
+	w := io.MultiWriter(packagesContent, gzw, xzw)
+
+	addSeperator := false
+	for _, pfd := range pfds {
+		if addSeperator {
+			fmt.Fprintln(w)
+		}
+		addSeperator = true
+
+		io.WriteString(w, pfd.Properties.GetByName(debian_module.PropertyControl))
+
+		fmt.Fprintf(w, "Filename: pool/%s/%s/%s\n", distribution, component, pfd.File.Name)
+		fmt.Fprintf(w, "Size: %d\n", pfd.Blob.Size)
+		fmt.Fprintf(w, "MD5sum: %s\n", pfd.Blob.HashMD5)
+		fmt.Fprintf(w, "SHA1: %s\n", pfd.Blob.HashSHA1)
+		fmt.Fprintf(w, "SHA256: %s\n", pfd.Blob.HashSHA256)
+		fmt.Fprintf(w, "SHA512: %s\n", pfd.Blob.HashSHA512)
+	}
+
+	gzw.Close()
+	xzw.Close()
+
+	for _, file := range []struct {
+		Name string
+		Data packages_module.HashedSizeReader
+	}{
+		{"Packages", packagesContent},
+		{"Packages.gz", packagesGzipContent},
+		{"Packages.xz", packagesXzContent},
+	} {
+		_, err = packages_service.AddFileToPackageVersionInternal(
+			repoVersion,
+			&packages_service.PackageFileCreationInfo{
+				PackageFileInfo: packages_service.PackageFileInfo{
+					Filename:     file.Name,
+					CompositeKey: fmt.Sprintf("%s|%s|%s", distribution, component, architecture),
+				},
+				Creator:           user_model.NewGhostUser(),
+				Data:              file.Data,
+				IsLead:            false,
+				OverwriteExisting: true,
+				Properties: map[string]string{
+					debian_module.PropertyRepositoryIncludeInRelease: "",
+					debian_module.PropertyDistribution:               distribution,
+					debian_module.PropertyComponent:                  component,
+					debian_module.PropertyArchitecture:               architecture,
+				},
+			},
+		)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+// https://wiki.debian.org/DebianRepository/Format#A.22Release.22_files
+func buildReleaseFiles(ctx context.Context, owner *user_model.User, repoVersion *packages_model.PackageVersion, distribution string) error {
+	pfs, _, err := packages_model.SearchFiles(ctx, &packages_model.PackageFileSearchOptions{
+		VersionID: repoVersion.ID,
+		Properties: map[string]string{
+			debian_module.PropertyRepositoryIncludeInRelease: "",
+			debian_module.PropertyDistribution:               distribution,
+		},
+	})
+	if err != nil {
+		return err
+	}
+
+	// Delete the release files if there are no packages
+	if len(pfs) == 0 {
+		for _, filename := range []string{"Release", "Release.gpg", "InRelease"} {
+			pf, err := packages_model.GetFileForVersionByName(ctx, repoVersion.ID, filename, distribution)
+			if err != nil && !errors.Is(err, util.ErrNotExist) {
+				return err
+			}
+
+			if err := packages_model.DeleteAllProperties(ctx, packages_model.PropertyTypeFile, pf.ID); err != nil {
+				return err
+			}
+			if err := packages_model.DeleteFileByID(ctx, pf.ID); err != nil {
+				return err
+			}
+		}
+
+		return nil
+	}
+
+	components, err := debian_model.GetComponents(ctx, owner.ID, distribution)
+	if err != nil {
+		return err
+	}
+
+	architectures, err := debian_model.GetArchitectures(ctx, owner.ID, distribution)
+	if err != nil {
+		return err
+	}
+
+	pps, err := packages_model.GetPropertiesByName(ctx, packages_model.PropertyTypeVersion, repoVersion.ID, debian_module.PropertyKeyPrivate)
+	if err != nil {
+		return err
+	}
+	if len(pps) != 1 {
+		panic("should have one private key in repository")
+	}
+
+	block, err := armor.Decode(strings.NewReader(pps[0].Value))
+	if err != nil {
+		return err
+	}
+
+	e, err := openpgp.ReadEntity(packet.NewReader(block.Body))
+	if err != nil {
+		return err
+	}
+
+	inReleaseContent, _ := packages_module.NewHashedBuffer()
+	sw, err := clearsign.Encode(inReleaseContent, e.PrivateKey, nil)
+	if err != nil {
+		return err
+	}
+
+	var buf bytes.Buffer
+
+	w := io.MultiWriter(sw, &buf)
+
+	fmt.Fprintf(w, "Origin: %s\n", setting.AppName)
+	fmt.Fprintf(w, "Label: %s\n", setting.AppName)
+	fmt.Fprintf(w, "Suite: %s\n", distribution)
+	fmt.Fprintf(w, "Codename: %s\n", distribution)
+	fmt.Fprintf(w, "Components: %s\n", strings.Join(components, " "))
+	fmt.Fprintf(w, "Architectures: %s\n", strings.Join(architectures, " "))
+	fmt.Fprintf(w, "Date: %s\n", time.Now().UTC().Format(time.RFC1123))
+	fmt.Fprint(w, "Acquire-By-Hash: yes")
+
+	pfds, err := packages_model.GetPackageFileDescriptors(ctx, pfs)
+	if err != nil {
+		return err
+	}
+
+	var md5, sha1, sha256, sha512 strings.Builder
+	for _, pfd := range pfds {
+		path := fmt.Sprintf("%s/binary-%s/%s", pfd.Properties.GetByName(debian_module.PropertyComponent), pfd.Properties.GetByName(debian_module.PropertyArchitecture), pfd.File.Name)
+		fmt.Fprintf(&md5, " %s %d %s\n", pfd.Blob.HashMD5, pfd.Blob.Size, path)
+		fmt.Fprintf(&sha1, " %s %d %s\n", pfd.Blob.HashSHA1, pfd.Blob.Size, path)
+		fmt.Fprintf(&sha256, " %s %d %s\n", pfd.Blob.HashSHA256, pfd.Blob.Size, path)
+		fmt.Fprintf(&sha512, " %s %d %s\n", pfd.Blob.HashSHA512, pfd.Blob.Size, path)
+	}
+
+	fmt.Fprintln(w, "MD5Sum:")
+	fmt.Fprint(w, md5.String())
+	fmt.Fprintln(w, "SHA1:")
+	fmt.Fprint(w, sha1.String())
+	fmt.Fprintln(w, "SHA256:")
+	fmt.Fprint(w, sha256.String())
+	fmt.Fprintln(w, "SHA512:")
+	fmt.Fprint(w, sha512.String())
+
+	sw.Close()
+
+	releaseGpgContent, _ := packages_module.NewHashedBuffer()
+	if err := openpgp.ArmoredDetachSign(releaseGpgContent, e, bytes.NewReader(buf.Bytes()), nil); err != nil {
+		return err
+	}
+
+	releaseContent, _ := packages_module.CreateHashedBufferFromReader(&buf)
+
+	for _, file := range []struct {
+		Name string
+		Data packages_module.HashedSizeReader
+	}{
+		{"Release", releaseContent},
+		{"Release.gpg", releaseGpgContent},
+		{"InRelease", inReleaseContent},
+	} {
+		_, err = packages_service.AddFileToPackageVersionInternal(
+			repoVersion,
+			&packages_service.PackageFileCreationInfo{
+				PackageFileInfo: packages_service.PackageFileInfo{
+					Filename:     file.Name,
+					CompositeKey: distribution,
+				},
+				Creator:           user_model.NewGhostUser(),
+				Data:              file.Data,
+				IsLead:            false,
+				OverwriteExisting: true,
+				Properties: map[string]string{
+					debian_module.PropertyDistribution: distribution,
+				},
+			},
+		)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
diff --git a/services/packages/packages.go b/services/packages/packages.go
index 410e73c048516..fb6f6a8f7d138 100644
--- a/services/packages/packages.go
+++ b/services/packages/packages.go
@@ -189,19 +189,33 @@ func createPackageAndVersion(ctx context.Context, pvci *PackageCreationInfo, all
 }
 
 // AddFileToExistingPackage adds a file to an existing package. If the package does not exist, ErrPackageNotExist is returned
-func AddFileToExistingPackage(pvi *PackageInfo, pfci *PackageFileCreationInfo) (*packages_model.PackageVersion, *packages_model.PackageFile, error) {
+func AddFileToExistingPackage(pvi *PackageInfo, pfci *PackageFileCreationInfo) (*packages_model.PackageFile, error) {
+	return addFileToPackageWrapper(func(ctx context.Context) (*packages_model.PackageFile, *packages_model.PackageBlob, bool, error) {
+		pv, err := packages_model.GetVersionByNameAndVersion(ctx, pvi.Owner.ID, pvi.PackageType, pvi.Name, pvi.Version)
+		if err != nil {
+			return nil, nil, false, err
+		}
+
+		return addFileToPackageVersion(ctx, pv, pvi, pfci)
+	})
+}
+
+// AddFileToPackageVersionInternal adds a file to the package
+// This method skips quota checks and should only be used for system-managed packages.
+func AddFileToPackageVersionInternal(pv *packages_model.PackageVersion, pfci *PackageFileCreationInfo) (*packages_model.PackageFile, error) {
+	return addFileToPackageWrapper(func(ctx context.Context) (*packages_model.PackageFile, *packages_model.PackageBlob, bool, error) {
+		return addFileToPackageVersionUnchecked(ctx, pv, pfci)
+	})
+}
+
+func addFileToPackageWrapper(fn func(ctx context.Context) (*packages_model.PackageFile, *packages_model.PackageBlob, bool, error)) (*packages_model.PackageFile, error) {
 	ctx, committer, err := db.TxContext(db.DefaultContext)
 	if err != nil {
-		return nil, nil, err
+		return nil, err
 	}
 	defer committer.Close()
 
-	pv, err := packages_model.GetVersionByNameAndVersion(ctx, pvi.Owner.ID, pvi.PackageType, pvi.Name, pvi.Version)
-	if err != nil {
-		return nil, nil, err
-	}
-
-	pf, pb, blobCreated, err := addFileToPackageVersion(ctx, pv, pvi, pfci)
+	pf, pb, blobCreated, err := fn(ctx)
 	removeBlob := false
 	defer func() {
 		if removeBlob {
@@ -213,15 +227,15 @@ func AddFileToExistingPackage(pvi *PackageInfo, pfci *PackageFileCreationInfo) (
 	}()
 	if err != nil {
 		removeBlob = blobCreated
-		return nil, nil, err
+		return nil, err
 	}
 
 	if err := committer.Commit(); err != nil {
 		removeBlob = blobCreated
-		return nil, nil, err
+		return nil, err
 	}
 
-	return pv, pf, nil
+	return pf, nil
 }
 
 // NewPackageBlob creates a package blob instance
@@ -238,12 +252,16 @@ func NewPackageBlob(hsr packages_module.HashedSizeReader) *packages_model.Packag
 }
 
 func addFileToPackageVersion(ctx context.Context, pv *packages_model.PackageVersion, pvi *PackageInfo, pfci *PackageFileCreationInfo) (*packages_model.PackageFile, *packages_model.PackageBlob, bool, error) {
-	log.Trace("Adding package file: %v, %s", pv.ID, pfci.Filename)
-
-	if err := checkSizeQuotaExceeded(ctx, pfci.Creator, pvi.Owner, pvi.PackageType, pfci.Data.Size()); err != nil {
+	if err := checkSizeQuotaExceeded(db.DefaultContext, pfci.Creator, pvi.Owner, pvi.PackageType, pfci.Data.Size()); err != nil {
 		return nil, nil, false, err
 	}
 
+	return addFileToPackageVersionUnchecked(ctx, pv, pfci)
+}
+
+func addFileToPackageVersionUnchecked(ctx context.Context, pv *packages_model.PackageVersion, pfci *PackageFileCreationInfo) (*packages_model.PackageFile, *packages_model.PackageBlob, bool, error) {
+	log.Trace("Adding package file: %v, %s", pv.ID, pfci.Filename)
+
 	pb, exists, err := packages_model.GetOrInsertBlob(ctx, NewPackageBlob(pfci.Data))
 	if err != nil {
 		log.Error("Error inserting package blob: %v", err)
@@ -337,6 +355,8 @@ func checkSizeQuotaExceeded(ctx context.Context, doer, owner *user_model.User, p
 		typeSpecificSize = setting.Packages.LimitSizeConan
 	case packages_model.TypeContainer:
 		typeSpecificSize = setting.Packages.LimitSizeContainer
+	case packages_model.TypeDebian:
+		typeSpecificSize = setting.Packages.LimitSizeDebian
 	case packages_model.TypeGeneric:
 		typeSpecificSize = setting.Packages.LimitSizeGeneric
 	case packages_model.TypeHelm:
diff --git a/templates/package/content/debian.tmpl b/templates/package/content/debian.tmpl
index cc24e790a3459..073b1a15a6681 100644
--- a/templates/package/content/debian.tmpl
+++ b/templates/package/content/debian.tmpl
@@ -3,28 +3,63 @@
 	
- -
{{range .PackageDescriptor.Files}}curl -O {{AppUrl}}api/packages/{{$.PackageDescriptor.Owner.Name}}/debian/pool/{{.File.Name}}
-{{end}}
-# install one version
-{{range .PackageDescriptor.Files}}sudo dpkg -i {{.File.Name}}
-{{end}}
+ +
curl {{AppUrl}}api/packages/{{$.PackageDescriptor.Owner.Name}}/debian/repository.key | sudo apt-key add -
+echo "deb {{AppUrl}}api/packages/{{$.PackageDescriptor.Owner.Name}}/debian <distribution> <component>" >> /etc/apt/sources.list
+sudo apt update
+

{{.locale.Tr "packages.debian.registry.info" | Safe}}

-
- +
-
curl {{AppUrl}}api/packages/{{$.PackageDescriptor.Owner.Name}}/debian/debian.key \
-  | sudo tee /etc/apt/trusted.gpg.d/gitea-repo.asc
-echo "deb {{AppUrl}}api/packages/{{$.PackageDescriptor.Owner.Name}}/debian gitea main" \
-  | sudo tee /etc/apt/sources.list.d/gitea.list
-sudo apt update
-sudo apt install {{$.PackageDescriptor.Package.Name}}
+
sudo apt install {{$.PackageDescriptor.Package.Name}}={{$.PackageDescriptor.Version.Version}}
- +
+ +

{{.locale.Tr "packages.debian.repository"}}

+
+ + + + + + + + + + + + + + + +
{{.locale.Tr "packages.debian.repository.distributions"}}
{{Join .Distributions ", "}}
{{.locale.Tr "packages.debian.repository.components"}}
{{Join .Components ", "}}
{{.locale.Tr "packages.debian.repository.architectures"}}
{{Join .Architectures ", "}}
+
+ + {{if .PackageDescriptor.Metadata.Description}} +

{{.locale.Tr "packages.about"}}

+
+ {{.PackageDescriptor.Metadata.Description}} +
+ {{end}} + + {{if .PackageDescriptor.Metadata.Dependencies}} +

{{.locale.Tr "packages.dependencies"}}

+
+ + + {{range .PackageDescriptor.Metadata.Dependencies}} + + + + {{end}} + +
{{.}}
+
+ {{end}} {{end}} diff --git a/templates/package/metadata/debian.tmpl b/templates/package/metadata/debian.tmpl index 4cfe2be507982..93b6db3bd240e 100644 --- a/templates/package/metadata/debian.tmpl +++ b/templates/package/metadata/debian.tmpl @@ -1,2 +1,4 @@ {{if eq .PackageDescriptor.Package.Type "debian"}} + {{if .PackageDescriptor.Metadata.Maintainer}}
{{svg "octicon-person" 16 "mr-3"}} {{.PackageDescriptor.Metadata.Maintainer}}
{{end}} + {{if .PackageDescriptor.Metadata.ProjectURL}}
{{svg "octicon-link-external" 16 "mr-3"}} {{.locale.Tr "packages.details.project_site"}}
{{end}} {{end}} From 0f1021f1d773f76863b2269941f1fa1f99d5127d Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Sun, 5 Feb 2023 15:18:12 +0000 Subject: [PATCH 07/27] Rebuild repository after cleanup. --- routers/api/packages/debian/debian.go | 10 ++-- services/packages/cleanup/cleanup.go | 11 ++++ services/packages/debian/repository.go | 78 +++++++++++++++++++++----- 3 files changed, 81 insertions(+), 18 deletions(-) diff --git a/routers/api/packages/debian/debian.go b/routers/api/packages/debian/debian.go index fdc3f3f85604c..936c1e6644952 100644 --- a/routers/api/packages/debian/debian.go +++ b/routers/api/packages/debian/debian.go @@ -30,7 +30,7 @@ func apiError(ctx *context.Context, status int, obj interface{}) { } func GetRepositoryKey(ctx *context.Context) { - pv, err := debian_service.GetOrCreateRepositoryVersion(ctx.Package.Owner) + pv, err := debian_service.GetOrCreateRepositoryVersion(ctx.Package.Owner.ID) if err != nil { apiError(ctx, http.StatusInternalServerError, err) return @@ -55,7 +55,7 @@ func GetRepositoryKey(ctx *context.Context) { // https://wiki.debian.org/DebianRepository/Format#A.22Release.22_files // https://wiki.debian.org/DebianRepository/Format#A.22Packages.22_Indices func GetRepositoryFile(ctx *context.Context) { - pv, err := debian_service.GetOrCreateRepositoryVersion(ctx.Package.Owner) + pv, err := debian_service.GetOrCreateRepositoryVersion(ctx.Package.Owner.ID) if err != nil { apiError(ctx, http.StatusInternalServerError, err) return @@ -95,7 +95,7 @@ func GetRepositoryFile(ctx *context.Context) { // https://wiki.debian.org/DebianRepository/Format#indices_acquisition_via_hashsums_.28by-hash.29 func GetRepositoryFileByHash(ctx *context.Context) { - pv, err := debian_service.GetOrCreateRepositoryVersion(ctx.Package.Owner) + pv, err := debian_service.GetOrCreateRepositoryVersion(ctx.Package.Owner.ID) if err != nil { apiError(ctx, http.StatusInternalServerError, err) return @@ -215,7 +215,7 @@ func UploadPackageFile(ctx *context.Context) { return } - if err := debian_service.GenerateRepositoryFiles(ctx, ctx.Package.Owner, distribution, component, pck.Architecture); err != nil { + if err := debian_service.BuildSpecificRepositoryFiles(ctx, ctx.Package.Owner.ID, distribution, component, pck.Architecture); err != nil { apiError(ctx, http.StatusInternalServerError, err) return } @@ -317,7 +317,7 @@ func DeletePackageFile(ctx *context.Context) { notification.NotifyPackageDelete(ctx, ctx.Doer, pd) } - if err := debian_service.GenerateRepositoryFiles(ctx, ctx.Package.Owner, distribution, component, architecture); err != nil { + if err := debian_service.BuildSpecificRepositoryFiles(ctx, ctx.Package.Owner.ID, distribution, component, architecture); err != nil { apiError(ctx, http.StatusInternalServerError, err) return } diff --git a/services/packages/cleanup/cleanup.go b/services/packages/cleanup/cleanup.go index 2d62a028a4c6d..43fbc1ad9b113 100644 --- a/services/packages/cleanup/cleanup.go +++ b/services/packages/cleanup/cleanup.go @@ -17,6 +17,7 @@ import ( packages_service "code.gitea.io/gitea/services/packages" cargo_service "code.gitea.io/gitea/services/packages/cargo" container_service "code.gitea.io/gitea/services/packages/container" + debian_service "code.gitea.io/gitea/services/packages/debian" ) // Cleanup removes expired package data @@ -45,6 +46,7 @@ func Cleanup(taskCtx context.Context, olderThan time.Duration) error { return fmt.Errorf("CleanupRule [%d]: GetPackagesByType failed: %w", pcr.ID, err) } + anyVersionDeleted := false for _, p := range packages { pvs, _, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{ PackageID: p.ID, @@ -91,6 +93,7 @@ func Cleanup(taskCtx context.Context, olderThan time.Duration) error { } versionDeleted = true + anyVersionDeleted = true } if versionDeleted { @@ -105,6 +108,14 @@ func Cleanup(taskCtx context.Context, olderThan time.Duration) error { } } } + + if anyVersionDeleted { + if pcr.Type == packages_model.TypeDebian { + if err := debian_service.BuildAllRepositoryFiles(ctx, pcr.OwnerID); err != nil { + return fmt.Errorf("CleanupRule [%d]: debian.BuildAllRepositoryFiles failed: %w", pcr.ID, err) + } + } + } return nil }) if err != nil { diff --git a/services/packages/debian/repository.go b/services/packages/debian/repository.go index b7c796c6b4f55..55770c9b00cc4 100644 --- a/services/packages/debian/repository.go +++ b/services/packages/debian/repository.go @@ -33,12 +33,12 @@ import ( // GetOrCreateRepositoryVersion gets or creates the internal repository package // The Debian registry needs multiple index files which are stored in this package. -func GetOrCreateRepositoryVersion(owner *user_model.User) (*packages_model.PackageVersion, error) { +func GetOrCreateRepositoryVersion(ownerID int64) (*packages_model.PackageVersion, error) { var repositoryVersion *packages_model.PackageVersion return repositoryVersion, db.WithTx(db.DefaultContext, func(ctx context.Context) error { p := &packages_model.Package{ - OwnerID: owner.ID, + OwnerID: ownerID, Type: packages_model.TypeDebian, Name: debian_module.RepositoryPackage, LowerName: debian_module.RepositoryPackage, @@ -55,7 +55,7 @@ func GetOrCreateRepositoryVersion(owner *user_model.User) (*packages_model.Packa created := true pv := &packages_model.PackageVersion{ PackageID: p.ID, - CreatorID: owner.ID, + CreatorID: ownerID, Version: debian_module.RepositoryVersion, LowerVersion: debian_module.RepositoryVersion, IsInternal: true, @@ -123,24 +123,76 @@ func generateKeypair() (string, string, error) { return priv.String(), pub.String(), nil } -// GenerateRepositoryFiles generates index files for the repository -func GenerateRepositoryFiles(ctx context.Context, owner *user_model.User, distribution, component, architecture string) error { - pv, err := GetOrCreateRepositoryVersion(owner) +func BuildAllRepositoryFiles(ctx context.Context, ownerID int64) error { + pv, err := GetOrCreateRepositoryVersion(ownerID) if err != nil { return err } - if err := buildPackagesIndices(ctx, owner, pv, distribution, component, architecture); err != nil { + // 1. Delete all existing repository files + pfs, err := packages_model.GetFilesByVersionID(ctx, pv.ID) + if err != nil { + return err + } + + for _, pf := range pfs { + if err := packages_model.DeleteAllProperties(ctx, packages_model.PropertyTypeFile, pf.ID); err != nil { + return err + } + if err := packages_model.DeleteFileByID(ctx, pf.ID); err != nil { + return err + } + } + + // 2. (Re)Build repository files for existing packages + distributions, err := debian_model.GetDistributions(ctx, ownerID) + if err != nil { + return err + } + for _, distribution := range distributions { + components, err := debian_model.GetComponents(ctx, ownerID, distribution) + if err != nil { + return err + } + architectures, err := debian_model.GetArchitectures(ctx, ownerID, distribution) + if err != nil { + return err + } + + for _, component := range components { + for _, architecture := range architectures { + if err := buildRepositoryFiles(ctx, ownerID, pv, distribution, component, architecture); err != nil { + return fmt.Errorf("failed to build repository files [%s/%s/%s]: %w", distribution, component, architecture, err) + } + } + } + } + + return nil +} + +// BuildSpecificRepositoryFiles builds index files for the repository +func BuildSpecificRepositoryFiles(ctx context.Context, ownerID int64, distribution, component, architecture string) error { + pv, err := GetOrCreateRepositoryVersion(ownerID) + if err != nil { + return err + } + + return buildRepositoryFiles(ctx, ownerID, pv, distribution, component, architecture) +} + +func buildRepositoryFiles(ctx context.Context, ownerID int64, repoVersion *packages_model.PackageVersion, distribution, component, architecture string) error { + if err := buildPackagesIndices(ctx, ownerID, repoVersion, distribution, component, architecture); err != nil { return err } - return buildReleaseFiles(ctx, owner, pv, distribution) + return buildReleaseFiles(ctx, ownerID, repoVersion, distribution) } // https://wiki.debian.org/DebianRepository/Format#A.22Packages.22_Indices -func buildPackagesIndices(ctx context.Context, owner *user_model.User, repoVersion *packages_model.PackageVersion, distribution, component, architecture string) error { +func buildPackagesIndices(ctx context.Context, ownerID int64, repoVersion *packages_model.PackageVersion, distribution, component, architecture string) error { pfds, err := debian_model.SearchLatestPackages(ctx, &debian_model.PackageSearchOptions{ - OwnerID: owner.ID, + OwnerID: ownerID, Distribution: distribution, Component: component, Architecture: architecture, @@ -235,7 +287,7 @@ func buildPackagesIndices(ctx context.Context, owner *user_model.User, repoVersi } // https://wiki.debian.org/DebianRepository/Format#A.22Release.22_files -func buildReleaseFiles(ctx context.Context, owner *user_model.User, repoVersion *packages_model.PackageVersion, distribution string) error { +func buildReleaseFiles(ctx context.Context, ownerID int64, repoVersion *packages_model.PackageVersion, distribution string) error { pfs, _, err := packages_model.SearchFiles(ctx, &packages_model.PackageFileSearchOptions{ VersionID: repoVersion.ID, Properties: map[string]string{ @@ -266,12 +318,12 @@ func buildReleaseFiles(ctx context.Context, owner *user_model.User, repoVersion return nil } - components, err := debian_model.GetComponents(ctx, owner.ID, distribution) + components, err := debian_model.GetComponents(ctx, ownerID, distribution) if err != nil { return err } - architectures, err := debian_model.GetArchitectures(ctx, owner.ID, distribution) + architectures, err := debian_model.GetArchitectures(ctx, ownerID, distribution) if err != nil { return err } From baf8f5e64f4e5865ec2e5bfb3ff1d2d19143762c Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Thu, 9 Feb 2023 15:19:18 +0000 Subject: [PATCH 08/27] Add integration tests. --- custom/conf/app.example.ini | 2 + .../doc/advanced/config-cheat-sheet.en-us.md | 1 + models/packages/debian/search.go | 16 +- models/user/setting.go | 5 + routers/api/packages/debian/debian.go | 15 +- services/packages/debian/repository.go | 63 +++-- tests/integration/api_packages_debian_test.go | 252 ++++++++++++++++++ 7 files changed, 308 insertions(+), 46 deletions(-) create mode 100644 tests/integration/api_packages_debian_test.go diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index b478785a07709..6bada150433b6 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -2470,6 +2470,8 @@ ROUTER = console ;LIMIT_SIZE_CONDA = -1 ;; Maximum size of a Container upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`) ;LIMIT_SIZE_CONTAINER = -1 +;; Maximum size of a Debian upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`) +;LIMIT_SIZE_DEBIAN = -1 ;; Maximum size of a Generic upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`) ;LIMIT_SIZE_GENERIC = -1 ;; Maximum size of a Helm upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`) diff --git a/docs/content/doc/advanced/config-cheat-sheet.en-us.md b/docs/content/doc/advanced/config-cheat-sheet.en-us.md index 04344b15dc73f..262282842a3b8 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md +++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md @@ -1219,6 +1219,7 @@ Task queue configuration has been moved to `queue.task`. However, the below conf - `LIMIT_SIZE_CONAN`: **-1**: Maximum size of a Conan upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`) - `LIMIT_SIZE_CONDA`: **-1**: Maximum size of a Conda upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`) - `LIMIT_SIZE_CONTAINER`: **-1**: Maximum size of a Container upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`) +- `LIMIT_SIZE_DEBIAN`: **-1**: Maximum size of a Debian upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`) - `LIMIT_SIZE_GENERIC`: **-1**: Maximum size of a Generic upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`) - `LIMIT_SIZE_HELM`: **-1**: Maximum size of a Helm upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`) - `LIMIT_SIZE_MAVEN`: **-1**: Maximum size of a Maven upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`) diff --git a/models/packages/debian/search.go b/models/packages/debian/search.go index 790c019061c76..332a4f7040c51 100644 --- a/models/packages/debian/search.go +++ b/models/packages/debian/search.go @@ -103,20 +103,18 @@ func GetArchitectures(ctx context.Context, ownerID int64, distribution string) ( func getDistinctPropertyValues(ctx context.Context, ownerID int64, distribution, propName string) ([]string, error) { var cond builder.Cond = builder.Eq{ - "package_property.ref_type": packages.PropertyTypeFile, - "package_property.name": propName, - "package_version.is_internal": true, - "package.type": packages.TypeDebian, - "package.owner_id": ownerID, - "package.is_internal": true, + "package_property.ref_type": packages.PropertyTypeFile, + "package_property.name": propName, + "package.type": packages.TypeDebian, + "package.owner_id": ownerID, } if distribution != "" { innerCond := builder. Expr("pp.ref_id = package_property.ref_id"). And(builder.Eq{ - "package_property.ref_type": packages.PropertyTypeFile, - "package_property.name": debian_module.PropertyDistribution, - "package_property.value": distribution, + "pp.ref_type": packages.PropertyTypeFile, + "pp.name": debian_module.PropertyDistribution, + "pp.value": distribution, }) cond = cond.And(builder.Exists(builder.Select("pp.ref_id").From("package_property pp").Where(innerCond))) } diff --git a/models/user/setting.go b/models/user/setting.go index aec79b756bf14..a41e494db9b54 100644 --- a/models/user/setting.go +++ b/models/user/setting.go @@ -11,6 +11,7 @@ import ( "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/cache" setting_module "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" "xorm.io/builder" ) @@ -42,6 +43,10 @@ func (err ErrUserSettingIsNotExist) Error() string { return fmt.Sprintf("Setting[%s] is not exist", err.Key) } +func (err ErrUserSettingIsNotExist) Unwrap() error { + return util.ErrNotExist +} + // IsErrUserSettingIsNotExist return true if err is ErrSettingIsNotExist func IsErrUserSettingIsNotExist(err error) bool { _, ok := err.(ErrUserSettingIsNotExist) diff --git a/routers/api/packages/debian/debian.go b/routers/api/packages/debian/debian.go index 936c1e6644952..3cda04f96fe4f 100644 --- a/routers/api/packages/debian/debian.go +++ b/routers/api/packages/debian/debian.go @@ -30,23 +30,13 @@ func apiError(ctx *context.Context, status int, obj interface{}) { } func GetRepositoryKey(ctx *context.Context) { - pv, err := debian_service.GetOrCreateRepositoryVersion(ctx.Package.Owner.ID) - if err != nil { - apiError(ctx, http.StatusInternalServerError, err) - return - } - - pps, err := packages_model.GetPropertiesByName(ctx, packages_model.PropertyTypeVersion, pv.ID, debian_module.PropertyKeyPublic) + _, pub, err := debian_service.GetOrCreateKeyPair(ctx.Package.Owner.ID) if err != nil { apiError(ctx, http.StatusInternalServerError, err) return } - if len(pps) != 1 { - apiError(ctx, http.StatusInternalServerError, "unknown property") - return - } - ctx.ServeContent(strings.NewReader(pps[0].Value), &context.ServeHeaderOptions{ + ctx.ServeContent(strings.NewReader(pub), &context.ServeHeaderOptions{ ContentType: "application/pgp-keys", Filename: "repository.key", }) @@ -251,6 +241,7 @@ func DownloadPackageFile(ctx *context.Context) { defer s.Close() ctx.ServeContent(s, &context.ServeHeaderOptions{ + ContentType: "application/vnd.debian.binary-package", Filename: pf.Name, LastModified: pf.CreatedUnix.AsLocalTime(), }) diff --git a/services/packages/debian/repository.go b/services/packages/debian/repository.go index 55770c9b00cc4..7430252a913d0 100644 --- a/services/packages/debian/repository.go +++ b/services/packages/debian/repository.go @@ -10,6 +10,7 @@ import ( "errors" "fmt" "io" + "sort" "strings" "time" @@ -52,7 +53,6 @@ func GetOrCreateRepositoryVersion(ownerID int64) (*packages_model.PackageVersion } } - created := true pv := &packages_model.PackageVersion{ PackageID: p.ID, CreatorID: ownerID, @@ -62,35 +62,46 @@ func GetOrCreateRepositoryVersion(ownerID int64) (*packages_model.PackageVersion MetadataJSON: "null", } if pv, err = packages_model.GetOrInsertVersion(ctx, pv); err != nil { - if err == packages_model.ErrDuplicatePackageVersion { - created = false - } else { + if err != packages_model.ErrDuplicatePackageVersion { log.Error("Error inserting package version: %v", err) return err } } - if created { - priv, pub, err := generateKeypair() - if err != nil { - return err - } + repositoryVersion = pv - _, err = packages_model.InsertProperty(ctx, packages_model.PropertyTypeVersion, pv.ID, debian_module.PropertyKeyPrivate, priv) - if err != nil { - return err - } + return nil + }) +} - _, err = packages_model.InsertProperty(ctx, packages_model.PropertyTypeVersion, pv.ID, debian_module.PropertyKeyPublic, pub) - if err != nil { - return err - } +// GetOrCreateKeyPair gets or creates the PGP keys used to sign repository files +func GetOrCreateKeyPair(ownerID int64) (string, string, error) { + priv, err := user_model.GetSetting(ownerID, debian_module.PropertyKeyPrivate) + if err != nil && !errors.Is(err, util.ErrNotExist) { + return "", "", err + } + + pub, err := user_model.GetSetting(ownerID, debian_module.PropertyKeyPublic) + if err != nil && !errors.Is(err, util.ErrNotExist) { + return "", "", err + } + + if priv == "" || pub == "" { + priv, pub, err = generateKeypair() + if err != nil { + return "", "", err } - repositoryVersion = pv + if err := user_model.SetUserSetting(ownerID, debian_module.PropertyKeyPrivate, priv); err != nil { + return "", "", err + } - return nil - }) + if err := user_model.SetUserSetting(ownerID, debian_module.PropertyKeyPublic, pub); err != nil { + return "", "", err + } + } + + return priv, pub, nil } func generateKeypair() (string, string, error) { @@ -123,6 +134,7 @@ func generateKeypair() (string, string, error) { return priv.String(), pub.String(), nil } +// BuildAllRepositoryFiles (re)builds all repository files for every available distributions, components and architectures func BuildAllRepositoryFiles(ctx context.Context, ownerID int64) error { pv, err := GetOrCreateRepositoryVersion(ownerID) if err != nil { @@ -323,20 +335,21 @@ func buildReleaseFiles(ctx context.Context, ownerID int64, repoVersion *packages return err } + sort.Strings(components) + architectures, err := debian_model.GetArchitectures(ctx, ownerID, distribution) if err != nil { return err } - pps, err := packages_model.GetPropertiesByName(ctx, packages_model.PropertyTypeVersion, repoVersion.ID, debian_module.PropertyKeyPrivate) + sort.Strings(architectures) + + priv, _, err := GetOrCreateKeyPair(ownerID) if err != nil { return err } - if len(pps) != 1 { - panic("should have one private key in repository") - } - block, err := armor.Decode(strings.NewReader(pps[0].Value)) + block, err := armor.Decode(strings.NewReader(priv)) if err != nil { return err } diff --git a/tests/integration/api_packages_debian_test.go b/tests/integration/api_packages_debian_test.go new file mode 100644 index 0000000000000..3e25acd8cff2c --- /dev/null +++ b/tests/integration/api_packages_debian_test.go @@ -0,0 +1,252 @@ +// Copyright 2023 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package integration + +import ( + "archive/tar" + "bytes" + "compress/gzip" + "fmt" + "io" + "net/http" + "strings" + "testing" + + "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/packages" + "code.gitea.io/gitea/models/unittest" + user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/base" + debian_module "code.gitea.io/gitea/modules/packages/debian" + "code.gitea.io/gitea/tests" + + "github.com/blakesmith/ar" + "github.com/stretchr/testify/assert" +) + +func TestPackageDebian(t *testing.T) { + defer tests.PrepareTestEnv(t)() + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + + packageName := "gitea" + packageVersion := "1.0.3" + packageDescription := "Package Description" + + createArchive := func(name, version, architecture string) io.Reader { + var cbuf bytes.Buffer + zw := gzip.NewWriter(&cbuf) + tw := tar.NewWriter(zw) + tw.WriteHeader(&tar.Header{ + Name: "control", + Mode: 0o600, + Size: 50, + }) + fmt.Fprintf(tw, "Package: %s\nVersion: %s\nArchitecture: %s\nDescription: %s\n", name, version, architecture, packageDescription) + tw.Close() + zw.Close() + + var buf bytes.Buffer + aw := ar.NewWriter(&buf) + aw.WriteGlobalHeader() + hdr := &ar.Header{ + Name: "control.tar.gz", + Mode: 0o600, + Size: int64(cbuf.Len()), + } + aw.WriteHeader(hdr) + aw.Write(cbuf.Bytes()) + return &buf + } + + distributions := []string{"test", "gitea"} + components := []string{"main", "stable"} + architectures := []string{"all", "amd64"} + + rootURL := fmt.Sprintf("/api/packages/%s/debian", user.Name) + + t.Run("RepositoryKey", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", rootURL+"/repository.key") + resp := MakeRequest(t, req, http.StatusOK) + + assert.Equal(t, "application/pgp-keys", resp.Header().Get("Content-Type")) + assert.Contains(t, resp.Body.String(), "-----BEGIN PGP PUBLIC KEY BLOCK-----") + }) + + for _, distribution := range distributions { + t.Run(fmt.Sprintf("[Distribution:%s]", distribution), func(t *testing.T) { + for _, component := range components { + for _, architecture := range architectures { + t.Run(fmt.Sprintf("[Component:%s,Architecture:%s]", component, architecture), func(t *testing.T) { + t.Run("Upload", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + uploadURL := fmt.Sprintf("%s/pool/%s/%s/upload", rootURL, distribution, component) + + req := NewRequestWithBody(t, "PUT", uploadURL, bytes.NewReader([]byte{})) + MakeRequest(t, req, http.StatusUnauthorized) + + req = NewRequestWithBody(t, "PUT", uploadURL, bytes.NewReader([]byte{})) + AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, http.StatusBadRequest) + + req = NewRequestWithBody(t, "PUT", uploadURL, createArchive("", "", "")) + AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, http.StatusBadRequest) + + req = NewRequestWithBody(t, "PUT", uploadURL, createArchive(packageName, packageVersion, architecture)) + AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, http.StatusCreated) + + pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeDebian) + assert.NoError(t, err) + assert.Len(t, pvs, 1) + + pd, err := packages.GetPackageDescriptor(db.DefaultContext, pvs[0]) + assert.NoError(t, err) + assert.Nil(t, pd.SemVer) + assert.IsType(t, &debian_module.Metadata{}, pd.Metadata) + assert.Equal(t, packageName, pd.Package.Name) + assert.Equal(t, packageVersion, pd.Version.Version) + + pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID) + assert.NoError(t, err) + assert.NotEmpty(t, pfs) + assert.Condition(t, func() bool { + seen := false + expectedFilename := fmt.Sprintf("%s_%s_%s.deb", packageName, packageVersion, architecture) + expectedCompositeKey := fmt.Sprintf("%s|%s", distribution, component) + for _, pf := range pfs { + if pf.Name == expectedFilename && pf.CompositeKey == expectedCompositeKey { + if seen { + return false + } + seen = true + + assert.True(t, pf.IsLead) + + pfps, err := packages.GetProperties(db.DefaultContext, packages.PropertyTypeFile, pf.ID) + assert.NoError(t, err) + + for _, pfp := range pfps { + switch pfp.Name { + case debian_module.PropertyDistribution: + assert.Equal(t, distribution, pfp.Value) + case debian_module.PropertyComponent: + assert.Equal(t, component, pfp.Value) + case debian_module.PropertyArchitecture: + assert.Equal(t, architecture, pfp.Value) + } + } + } + } + return seen + }) + }) + + t.Run("Download", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", fmt.Sprintf("%s/pool/%s/%s/%s_%s_%s.deb", rootURL, distribution, component, packageName, packageVersion, architecture)) + resp := MakeRequest(t, req, http.StatusOK) + + assert.Equal(t, "application/vnd.debian.binary-package", resp.Header().Get("Content-Type")) + }) + + t.Run("Packages", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + url := fmt.Sprintf("%s/dists/%s/%s/binary-%s/Packages", rootURL, distribution, component, architecture) + + req := NewRequest(t, "GET", url) + resp := MakeRequest(t, req, http.StatusOK) + + body := resp.Body.String() + + assert.Contains(t, body, "Package: "+packageName) + assert.Contains(t, body, "Version: "+packageVersion) + assert.Contains(t, body, "Architecture: "+architecture) + assert.Contains(t, body, fmt.Sprintf("Filename: pool/%s/%s/%s_%s_%s.deb", distribution, component, packageName, packageVersion, architecture)) + + req = NewRequest(t, "GET", url+".gz") + MakeRequest(t, req, http.StatusOK) + + req = NewRequest(t, "GET", url+".xz") + MakeRequest(t, req, http.StatusOK) + + url = fmt.Sprintf("%s/dists/%s/%s/%s/by-hash/SHA256/%s", rootURL, distribution, component, architecture, base.EncodeSha256(body)) + req = NewRequest(t, "GET", url) + resp = MakeRequest(t, req, http.StatusOK) + + assert.Equal(t, body, resp.Body.String()) + }) + }) + } + } + + t.Run("Release", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", fmt.Sprintf("%s/dists/%s/Release", rootURL, distribution)) + resp := MakeRequest(t, req, http.StatusOK) + + body := resp.Body.String() + + assert.Contains(t, body, "Components: "+strings.Join(components, " ")) + assert.Contains(t, body, "Architectures: "+strings.Join(architectures, " ")) + + for _, component := range components { + for _, architecture := range architectures { + assert.Contains(t, body, fmt.Sprintf("%s/binary-%s/Packages", component, architecture)) + assert.Contains(t, body, fmt.Sprintf("%s/binary-%s/Packages.gz", component, architecture)) + assert.Contains(t, body, fmt.Sprintf("%s/binary-%s/Packages.xz", component, architecture)) + } + } + + req = NewRequest(t, "GET", fmt.Sprintf("%s/dists/%s/by-hash/SHA256/%s", rootURL, distribution, base.EncodeSha256(body))) + resp = MakeRequest(t, req, http.StatusOK) + + assert.Equal(t, body, resp.Body.String()) + + req = NewRequest(t, "GET", fmt.Sprintf("%s/dists/%s/Release.gpg", rootURL, distribution)) + resp = MakeRequest(t, req, http.StatusOK) + + assert.Contains(t, resp.Body.String(), "-----BEGIN PGP SIGNATURE-----") + + req = NewRequest(t, "GET", fmt.Sprintf("%s/dists/%s/InRelease", rootURL, distribution)) + resp = MakeRequest(t, req, http.StatusOK) + + assert.Contains(t, resp.Body.String(), "-----BEGIN PGP SIGNED MESSAGE-----") + }) + }) + } + + t.Run("Delete", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + distribution := distributions[0] + architecture := architectures[0] + + for _, component := range components { + req := NewRequest(t, "DELETE", fmt.Sprintf("%s/pool/%s/%s/%s/%s/%s", rootURL, distribution, component, packageName, packageVersion, architecture)) + MakeRequest(t, req, http.StatusUnauthorized) + + req = NewRequest(t, "DELETE", fmt.Sprintf("%s/pool/%s/%s/%s/%s/%s", rootURL, distribution, component, packageName, packageVersion, architecture)) + AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, http.StatusNoContent) + + req = NewRequest(t, "GET", fmt.Sprintf("%s/dists/%s/%s/binary-%s/Packages", rootURL, distribution, component, architecture)) + MakeRequest(t, req, http.StatusNotFound) + } + + req := NewRequest(t, "GET", fmt.Sprintf("%s/dists/%s/Release", rootURL, distribution)) + resp := MakeRequest(t, req, http.StatusOK) + + body := resp.Body.String() + + assert.Contains(t, body, "Components: "+strings.Join(components, " ")) + assert.Contains(t, body, "Architectures: "+architectures[1]) + }) +} From ccdf14449671c2fc77cf33d8d3b5639825fbae2d Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Fri, 10 Feb 2023 15:08:38 +0000 Subject: [PATCH 09/27] Update docs. --- docs/content/doc/packages/debian.en-us.md | 149 +++++++++------------- 1 file changed, 63 insertions(+), 86 deletions(-) diff --git a/docs/content/doc/packages/debian.en-us.md b/docs/content/doc/packages/debian.en-us.md index d50271afa0f8b..5834bb2b17bb2 100644 --- a/docs/content/doc/packages/debian.en-us.md +++ b/docs/content/doc/packages/debian.en-us.md @@ -8,150 +8,127 @@ menu: sidebar: parent: "packages" name: "Debian" - weight: 45 + weight: 35 identifier: "debian" --- # Debian Packages Repository -Publish Debian packages to a APT repository for your user or organization. +Publish [Debian](https://www.debian.org/distrib/packages) packages for your user or organization. **Table of Contents** {{< toc >}} -## Authenticate to the package registry +## Requirements -To authenticate to the Package Registry, you need to provide [custom HTTP headers or use HTTP Basic authentication]({{< relref "doc/developers/api-usage.en-us.md#authentication" >}}). +To work with the Debian registry, you need to use a HTTP client like `curl` to upload and a package manager like `apt` to consume packages. -## Publish a package +The following examples use `apt`. -To publish a Debian package, perform a HTTP PUT operation with the package content in the request body. -You cannot publish a file with the same name twice to a package. You must delete the existing package version first. +## Configuring the package registry -``` -PUT https://gitea.example.com/api/packages/{owner}/debian/files/{package_name}/{package_version}/{package_architecture} +To register the Debian registry add the url to the list of known apt sources: + +```shell +echo "deb https://gitea.example.com/api/packages/{owner}/debian {distribution} {component}" >> /etc/apt/sources.list ``` -| Parameter | Description | -| --------------------- | ----------- | -| `owner` | The owner of the package. | -| `package_name` | The package name. It can contain only lowercase letters (`a-z`), uppercase letter (`A-Z`), numbers (`0-9`), dots (`.`), hyphens (`-`), pluses (`+`), or underscores (`_`). | -| `package_version` | The package version, a non-empty string without trailing or leading whitespaces. | -| `package_architecture` | The package architecture. Can be a Debian machine architecture as described in [Debian Architecture specifications](https://www.debian.org/doc/debian-policy/ch-customized-programs.html#s-arch-spec), `all`, or `source`. | +| Placeholder | Description | +| -------------- | ----------- | +| `owner` | The owner of the package. | +| `distribution` | The distribution to use. | +| `component` | The component to use. | -Example request using HTTP Basic authentication: +If the registry is private, provide credentials in the url: ```shell -curl --user your_username:your_password_or_token \ - --upload-file path/to/file.deb \ - https://gitea.example.com/api/packages/testuser/debian/files/test-package/1.0.0/amd64 +echo "deb https://{username}:{password}@gitea.example.com/api/packages/{owner}/debian {distribution} {component}" >> /etc/apt/sources.list ``` -If you are using 2FA or OAuth use a [personal access token]({{< relref "doc/developers/api-usage.en-us.md#authentication" >}}) instead of the password. +The Debian registry files are signed with a PGP key which must be known to apt: -The server reponds with the following HTTP Status codes. +```shell +curl https://gitea.example.com/api/packages/{owner}/debian/repository.key | sudo apt-key add - +``` -| HTTP Status Code | Meaning | -| ----------------- | ------- | -| `201 Created` | The package has been published. | -| `400 Bad Request` | The package name and/or version and/or file name are invalid. | -| `409 Conflict` | A file with the same name exist already in the package. | +Afterwards update the local package index: + +```shell +apt update +``` -## Delete a package file +## Publish a package -To delete a file of a generic package perform a HTTP DELETE operation. This will delete the package version too if there is no file left. +To publish a Debian package (`*.deb`), perform a HTTP PUT operation with the package content in the request body. ``` -DELETE https://gitea.example.com/api/packages/{owner}/debian/files/{package_name}/{package_version}/{package_architecture} +PUT https://gitea.example.com/api/packages/{owner}/debian/pool/{distribution}/{component}/upload ``` -| Parameter | Description | -| ---------------------- | ----------- | -| `owner` | The owner of the package. | -| `package_name` | The package name. | -| `package_version` | The package version. | -| `package_architecture` | The package architecture. | +| Parameter | Description | +| -------------- | ----------- | +| `owner` | The owner of the package. | +| `distribution` | The distribution may match the release name of the OS, ex: `bionic`. | +| `component` | The component can be used to group packages or just `main` or similar. | Example request using HTTP Basic authentication: ```shell -curl --user your_username:your_token_or_password -X DELETE \ - https://gitea.example.com/api/packages/testuser/debian/files/test-package/1.0.0/amd64 +curl --user your_username:your_password_or_token \ + --upload-file path/to/file.deb \ + https://gitea.example.com/api/packages/testuser/debian/pool/bionic/main/upload ``` +If you are using 2FA or OAuth use a [personal access token]({{< relref "doc/developers/api-usage.en-us.md#authentication" >}}) instead of the password. +You cannot publish a file with the same name twice to a package. You must delete the existing package version first. + The server reponds with the following HTTP Status codes. | HTTP Status Code | Meaning | | ----------------- | ------- | -| `204 No Content` | Success | -| `404 Not Found` | The package or file was not found. | +| `201 Created` | The package has been published. | +| `400 Bad Request` | The package name, version, distribution, component or architecture are invalid. | +| `409 Conflict` | A package file with the same combination of parameters exist already in the package. | -## Download a package +## Delete a package -To download a generic package perform a HTTP GET operation. +To delete a Debian package perform a HTTP DELETE operation. This will delete the package version too if there is no file left. ``` -GET https://gitea.example.com/api/packages/{owner}/debian/pool/{package_name}_{package_version}_{package_architecture}.deb +DELETE https://gitea.example.com/api/packages/{owner}/debian/pool/{distribution}/{component}/{package_name}/{package_version}/{architecture} ``` -| Parameter | Description | -| ---------------------- | ----------- | -| `owner` | The owner of the package. | -| `package_name` | The package name. | -| `package_version` | The package version. | -| `package_architecture` | The package architecture. | - -The file content is served in the response body. The response content type is `application/octet-stream`. +| Parameter | Description | +| ----------------- | ----------- | +| `owner` | The owner of the package. | +| `package_name` | The package name. | +| `package_version` | The package version. | +| `distribution` | The package distribution. | +| `component` | The package component. | +| `architecture` | The package architecture. | Example request using HTTP Basic authentication: ```shell -curl https://gitea.example.com/api/packages/testuser/debian/pool/test-package_1.0.0_amd64.deb +curl --user your_username:your_token_or_password -X DELETE \ + https://gitea.example.com/api/packages/testuser/debian/pools/bionic/main/test-package/1.0.0/amd64 ``` The server reponds with the following HTTP Status codes. | HTTP Status Code | Meaning | | ----------------- | ------- | -| `200 OK` | Success | +| `204 No Content` | Success | | `404 Not Found` | The package or file was not found. | -## Using APT to download and install packages - -### Generating a PGP key pair - -The APT repository needs a PGP keypair to sign the Release files. With openpgp installed, generate a keypair with the following: - -```shell -gpg --full-gen-key -``` - -### Adding signing keys to Gitea Debian repository - -The private and public keys can be exported and should be placed in Gitea's data directory: - -```shell -gpg --export-secret-key > /debian.gpg -gpg --export --armor > /debian_public.gpg -``` - -Once the keys have been added, the Release and Packages files are generated after upload or deletion of a package file. - -### Adding the key and repository to APT - -To add the key from the server to the APT keyring: - -```shell -curl https://gitea.example.com/api/packages/test-user/debian/debian.key \ - | sudo tee /etc/apt/trusted.gpg.d/gitea-repo.asc -``` +## Install a package -The URL of the repository is: `https:///api/packages//debian`. -The "distro" portion should be `gitea` and so far, only `main` component is supported. +To install a package from the Debian registry, execute the following commands: ```shell -echo "deb https://gitea.example.com/api/packages/test-user/debian gitea main" \ - | sudo tee /etc/apt/sources.list.d/gitea.list -sudo apt update +# use latest version +apt install {package_name} +# use specific version +apt install {package_name}={package_version} ``` From 04c4c90f97d1fb8b9a22bc765144111218fa93e3 Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Fri, 10 Feb 2023 15:21:23 +0000 Subject: [PATCH 10/27] Change name of constant. --- modules/packages/debian/metadata.go | 5 +++-- services/packages/debian/repository.go | 8 ++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/modules/packages/debian/metadata.go b/modules/packages/debian/metadata.go index d7fc3c9ff625d..08daaf082e126 100644 --- a/modules/packages/debian/metadata.go +++ b/modules/packages/debian/metadata.go @@ -25,10 +25,11 @@ const ( PropertyComponent = "debian.component" PropertyArchitecture = "debian.architecture" PropertyControl = "debian.control" - PropertyKeyPrivate = "debian.key.private" - PropertyKeyPublic = "debian.key.public" PropertyRepositoryIncludeInRelease = "debian.repository.include_in_release" + SettingKeyPrivate = "debian.key.private" + SettingKeyPublic = "debian.key.public" + RepositoryPackage = "_debian" RepositoryVersion = "_repository" ) diff --git a/services/packages/debian/repository.go b/services/packages/debian/repository.go index 7430252a913d0..eac878256ab7b 100644 --- a/services/packages/debian/repository.go +++ b/services/packages/debian/repository.go @@ -76,12 +76,12 @@ func GetOrCreateRepositoryVersion(ownerID int64) (*packages_model.PackageVersion // GetOrCreateKeyPair gets or creates the PGP keys used to sign repository files func GetOrCreateKeyPair(ownerID int64) (string, string, error) { - priv, err := user_model.GetSetting(ownerID, debian_module.PropertyKeyPrivate) + priv, err := user_model.GetSetting(ownerID, debian_module.SettingKeyPrivate) if err != nil && !errors.Is(err, util.ErrNotExist) { return "", "", err } - pub, err := user_model.GetSetting(ownerID, debian_module.PropertyKeyPublic) + pub, err := user_model.GetSetting(ownerID, debian_module.SettingKeyPublic) if err != nil && !errors.Is(err, util.ErrNotExist) { return "", "", err } @@ -92,11 +92,11 @@ func GetOrCreateKeyPair(ownerID int64) (string, string, error) { return "", "", err } - if err := user_model.SetUserSetting(ownerID, debian_module.PropertyKeyPrivate, priv); err != nil { + if err := user_model.SetUserSetting(ownerID, debian_module.SettingKeyPrivate, priv); err != nil { return "", "", err } - if err := user_model.SetUserSetting(ownerID, debian_module.PropertyKeyPublic, pub); err != nil { + if err := user_model.SetUserSetting(ownerID, debian_module.SettingKeyPublic, pub); err != nil { return "", "", err } } From 472929abd5d57c79099fc845f51667f8bb3c882f Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Fri, 10 Feb 2023 15:32:51 +0000 Subject: [PATCH 11/27] Fix svgs. --- public/img/svg/gitea-debian.svg | 9 +-------- web_src/svg/gitea-debian.svg | 17 +++++++++-------- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/public/img/svg/gitea-debian.svg b/public/img/svg/gitea-debian.svg index 1c042510f3c4f..cb974fe20c224 100644 --- a/public/img/svg/gitea-debian.svg +++ b/public/img/svg/gitea-debian.svg @@ -1,8 +1 @@ - - - - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/web_src/svg/gitea-debian.svg b/web_src/svg/gitea-debian.svg index 1c042510f3c4f..4046f7fc7cb17 100644 --- a/web_src/svg/gitea-debian.svg +++ b/web_src/svg/gitea-debian.svg @@ -1,8 +1,9 @@ - - - - - - - - \ No newline at end of file + + + + + + + + + From 113bbcb3b0dd3c9fd9c528b4adbc7ae3cb3aafd9 Mon Sep 17 00:00:00 2001 From: Brian Hong Date: Sat, 11 Feb 2023 13:33:50 -0500 Subject: [PATCH 12/27] Update instructions adding APT repo (#5) Apt-key is deprecated; use trusted.gpg.d instead. Use sources.list.d instead. --- docs/content/doc/packages/debian.en-us.md | 6 +++--- templates/package/content/debian.tmpl | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/content/doc/packages/debian.en-us.md b/docs/content/doc/packages/debian.en-us.md index 5834bb2b17bb2..305f0836a6e3a 100644 --- a/docs/content/doc/packages/debian.en-us.md +++ b/docs/content/doc/packages/debian.en-us.md @@ -31,7 +31,7 @@ The following examples use `apt`. To register the Debian registry add the url to the list of known apt sources: ```shell -echo "deb https://gitea.example.com/api/packages/{owner}/debian {distribution} {component}" >> /etc/apt/sources.list +echo "deb https://gitea.example.com/api/packages/{owner}/debian {distribution} {component}" | sudo tee -a /etc/apt/sources.list.d/gitea.list ``` | Placeholder | Description | @@ -43,13 +43,13 @@ echo "deb https://gitea.example.com/api/packages/{owner}/debian {distribution} { If the registry is private, provide credentials in the url: ```shell -echo "deb https://{username}:{password}@gitea.example.com/api/packages/{owner}/debian {distribution} {component}" >> /etc/apt/sources.list +echo "deb https://{username}:{password}@gitea.example.com/api/packages/{owner}/debian {distribution} {component}" | sudo tee -a /etc/apt/sources.list.d/gitea.list ``` The Debian registry files are signed with a PGP key which must be known to apt: ```shell -curl https://gitea.example.com/api/packages/{owner}/debian/repository.key | sudo apt-key add - +sudo curl https://gitea.example.com/api/packages/{owner}/debian/repository.key -o /etc/apt/trusted.gpg.d/gitea-{owner}.asc ``` Afterwards update the local package index: diff --git a/templates/package/content/debian.tmpl b/templates/package/content/debian.tmpl index 073b1a15a6681..2d036e9b330d6 100644 --- a/templates/package/content/debian.tmpl +++ b/templates/package/content/debian.tmpl @@ -4,8 +4,8 @@
-
curl {{AppUrl}}api/packages/{{$.PackageDescriptor.Owner.Name}}/debian/repository.key | sudo apt-key add -
-echo "deb {{AppUrl}}api/packages/{{$.PackageDescriptor.Owner.Name}}/debian <distribution> <component>" >> /etc/apt/sources.list
+				
sudo curl {{AppUrl}}api/packages/{{$.PackageDescriptor.Owner.Name}}/debian/repository.key -o /etc/apt/trusted.gpg.d/gitea-{{$.PackageDescriptor.Owner.Name}}.asc
+echo "deb {{AppUrl}}api/packages/{{$.PackageDescriptor.Owner.Name}}/debian <distribution> <component>" | sudo tee -a /etc/apt/sources.list.d/gitea.list
 sudo apt update

{{.locale.Tr "packages.debian.registry.info" | Safe}}

From c436df5e400f8a7128a3eb9e3ea2de1b66695b76 Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Thu, 9 Mar 2023 15:46:26 +0000 Subject: [PATCH 13/27] Fix CI. --- go.sum | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/go.sum b/go.sum index 22887f3bad8a4..13bef23a2fe05 100644 --- a/go.sum +++ b/go.sum @@ -158,8 +158,8 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bits-and-blooms/bitset v1.1.10/go.mod h1:w0XsmFg8qg6cmpTtJ0z3pKgjTDBMMnI/+I2syrE6XBE= github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= -github.com/bits-and-blooms/bitset v1.3.3 h1:R1XWiopGiXf66xygsiLpzLo67xEYvMkHw3w+rCOSAwg= -github.com/bits-and-blooms/bitset v1.3.3/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= +github.com/bits-and-blooms/bitset v1.5.0 h1:NpE8frKRLGHIcEzkR+gZhiioW1+WbYV6fKwD6ZIpQT8= +github.com/bits-and-blooms/bitset v1.5.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4= github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI= github.com/blevesearch/bleve/v2 v2.0.5/go.mod h1:ZjWibgnbRX33c+vBRgla9QhPb4QOjD6fdVJ+R1Bk8LM= From 5b6376c0c27e9ba19417c090f86fb090c0aa8641 Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Fri, 10 Mar 2023 11:36:03 +0000 Subject: [PATCH 14/27] Mention token in documentation. --- docs/content/doc/packages/debian.en-us.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/content/doc/packages/debian.en-us.md b/docs/content/doc/packages/debian.en-us.md index 305f0836a6e3a..9e32af5af37aa 100644 --- a/docs/content/doc/packages/debian.en-us.md +++ b/docs/content/doc/packages/debian.en-us.md @@ -40,10 +40,10 @@ echo "deb https://gitea.example.com/api/packages/{owner}/debian {distribution} { | `distribution` | The distribution to use. | | `component` | The component to use. | -If the registry is private, provide credentials in the url: +If the registry is private, provide credentials in the url. You can use a password or a [personal access token]({{< relref "doc/developers/api-usage.en-us.md#authentication" >}}): ```shell -echo "deb https://{username}:{password}@gitea.example.com/api/packages/{owner}/debian {distribution} {component}" | sudo tee -a /etc/apt/sources.list.d/gitea.list +echo "deb https://{username}:{your_password_or_token}@gitea.example.com/api/packages/{owner}/debian {distribution} {component}" | sudo tee -a /etc/apt/sources.list.d/gitea.list ``` The Debian registry files are signed with a PGP key which must be known to apt: From 8ed3b4ed7e4746d27780e2ffa63dcb8b9c605b4a Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Mon, 13 Mar 2023 15:24:32 +0000 Subject: [PATCH 15/27] Use relative url. --- templates/package/content/debian.tmpl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/package/content/debian.tmpl b/templates/package/content/debian.tmpl index 2d036e9b330d6..c10c3042b92c4 100644 --- a/templates/package/content/debian.tmpl +++ b/templates/package/content/debian.tmpl @@ -4,8 +4,8 @@
-
sudo curl {{AppUrl}}api/packages/{{$.PackageDescriptor.Owner.Name}}/debian/repository.key -o /etc/apt/trusted.gpg.d/gitea-{{$.PackageDescriptor.Owner.Name}}.asc
-echo "deb {{AppUrl}}api/packages/{{$.PackageDescriptor.Owner.Name}}/debian <distribution> <component>" | sudo tee -a /etc/apt/sources.list.d/gitea.list
+				
sudo curl  -o /etc/apt/trusted.gpg.d/gitea-{{$.PackageDescriptor.Owner.Name}}.asc
+echo "deb  <distribution> <component>" | sudo tee -a /etc/apt/sources.list.d/gitea.list
 sudo apt update

{{.locale.Tr "packages.debian.registry.info" | Safe}}

From f781a8c2d1a38d89df1558f2bb31c5dd0e763d8b Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Mon, 27 Mar 2023 10:38:07 +0000 Subject: [PATCH 16/27] Read values from current package. --- routers/api/packages/swift/swift.go | 2 +- routers/web/user/package.go | 37 ++++++++++++++++------------- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/routers/api/packages/swift/swift.go b/routers/api/packages/swift/swift.go index f78f703778ba0..06f592dd648f9 100644 --- a/routers/api/packages/swift/swift.go +++ b/routers/api/packages/swift/swift.go @@ -300,7 +300,7 @@ func UploadPackageFile(ctx *context.Context) { } defer file.Close() - buf, err := packages_module.CreateHashedBufferFromReader(file, 32*1024*1024) + buf, err := packages_module.CreateHashedBufferFromReader(file) if err != nil { apiError(ctx, http.StatusInternalServerError, err) return diff --git a/routers/web/user/package.go b/routers/web/user/package.go index 912e12e96bc5f..37ee0b86319b0 100644 --- a/routers/web/user/package.go +++ b/routers/web/user/package.go @@ -10,13 +10,14 @@ import ( org_model "code.gitea.io/gitea/models/organization" packages_model "code.gitea.io/gitea/models/packages" container_model "code.gitea.io/gitea/models/packages/container" - debian_model "code.gitea.io/gitea/models/packages/debian" "code.gitea.io/gitea/models/perm" access_model "code.gitea.io/gitea/models/perm/access" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/modules/base" + "code.gitea.io/gitea/modules/container" "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/log" + debian_module "code.gitea.io/gitea/modules/packages/debian" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web" @@ -168,22 +169,26 @@ func ViewPackageVersion(ctx *context.Context) { case packages_model.TypeContainer: ctx.Data["RegistryHost"] = setting.Packages.RegistryHost case packages_model.TypeDebian: - var err error - ctx.Data["Distributions"], err = debian_model.GetDistributions(ctx, pd.Owner.ID) - if err != nil { - ctx.ServerError("GetDistributions", err) - return - } - ctx.Data["Components"], err = debian_model.GetComponents(ctx, pd.Owner.ID, "") - if err != nil { - ctx.ServerError("GetComponents", err) - return - } - ctx.Data["Architectures"], err = debian_model.GetArchitectures(ctx, pd.Owner.ID, "") - if err != nil { - ctx.ServerError("GetArchitectures", err) - return + distributions := make(container.Set[string]) + components := make(container.Set[string]) + architectures := make(container.Set[string]) + + for _, f := range pd.Files { + for _, pp := range f.Properties { + switch pp.Name { + case debian_module.PropertyDistribution: + distributions.Add(pp.Value) + case debian_module.PropertyComponent: + components.Add(pp.Value) + case debian_module.PropertyArchitecture: + architectures.Add(pp.Value) + } + } } + + ctx.Data["Distributions"] = distributions.Values() + ctx.Data["Components"] = components.Values() + ctx.Data["Architectures"] = architectures.Values() } var ( From f3b17ba9b2761e74809903bf60b1f92514a2fdd3 Mon Sep 17 00:00:00 2001 From: techknowlogick Date: Mon, 17 Apr 2023 10:49:17 -0400 Subject: [PATCH 17/27] move migrate to 254 --- models/migrations/v1_20/{v250.go => v254.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename models/migrations/v1_20/{v250.go => v254.go} (100%) diff --git a/models/migrations/v1_20/v250.go b/models/migrations/v1_20/v254.go similarity index 100% rename from models/migrations/v1_20/v250.go rename to models/migrations/v1_20/v254.go From ffd1e03cc67550b7dabb784bb1b086572bb0faf2 Mon Sep 17 00:00:00 2001 From: techknowlogick Date: Mon, 17 Apr 2023 10:52:59 -0400 Subject: [PATCH 18/27] no size --- routers/api/packages/npm/npm.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routers/api/packages/npm/npm.go b/routers/api/packages/npm/npm.go index 51b34d3e2721e..89476a776a0dd 100644 --- a/routers/api/packages/npm/npm.go +++ b/routers/api/packages/npm/npm.go @@ -189,7 +189,7 @@ func UploadPackage(ctx *context.Context) { } } - buf, err := packages_module.CreateHashedBufferFromReader(bytes.NewReader(npmPackage.Data), 32*1024*1024) + buf, err := packages_module.CreateHashedBufferFromReader(bytes.NewReader(npmPackage.Data)) if err != nil { apiError(ctx, http.StatusInternalServerError, err) return From aaa77569ba38fe25abab62f2119207bfa27bf7c4 Mon Sep 17 00:00:00 2001 From: techknowlogick Date: Mon, 17 Apr 2023 10:55:50 -0400 Subject: [PATCH 19/27] make svg --- public/img/svg/gitea-debian.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/img/svg/gitea-debian.svg b/public/img/svg/gitea-debian.svg index cb974fe20c224..96f8f468e506a 100644 --- a/public/img/svg/gitea-debian.svg +++ b/public/img/svg/gitea-debian.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file From 5eec5eec48a0536bb0e6176c5ab4f2385f9be811 Mon Sep 17 00:00:00 2001 From: techknowlogick Date: Tue, 18 Apr 2023 10:54:31 -0400 Subject: [PATCH 20/27] update path to document --- docs/content/doc/packages/debian.en-us.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/doc/packages/debian.en-us.md b/docs/content/doc/packages/debian.en-us.md index 9e32af5af37aa..b8d254ca79ec3 100644 --- a/docs/content/doc/packages/debian.en-us.md +++ b/docs/content/doc/packages/debian.en-us.md @@ -80,7 +80,7 @@ curl --user your_username:your_password_or_token \ https://gitea.example.com/api/packages/testuser/debian/pool/bionic/main/upload ``` -If you are using 2FA or OAuth use a [personal access token]({{< relref "doc/developers/api-usage.en-us.md#authentication" >}}) instead of the password. +If you are using 2FA or OAuth use a [personal access token]({{< relref "doc/development/api-usage.en-us.md#authentication" >}}) instead of the password. You cannot publish a file with the same name twice to a package. You must delete the existing package version first. The server reponds with the following HTTP Status codes. From 26c468ecdb7196ffbcf6bfe0fa277b9bb230a4e8 Mon Sep 17 00:00:00 2001 From: techknowlogick Date: Tue, 18 Apr 2023 10:56:26 -0400 Subject: [PATCH 21/27] update path to docs --- docs/content/doc/packages/debian.en-us.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/doc/packages/debian.en-us.md b/docs/content/doc/packages/debian.en-us.md index b8d254ca79ec3..ac1abed18e912 100644 --- a/docs/content/doc/packages/debian.en-us.md +++ b/docs/content/doc/packages/debian.en-us.md @@ -40,7 +40,7 @@ echo "deb https://gitea.example.com/api/packages/{owner}/debian {distribution} { | `distribution` | The distribution to use. | | `component` | The component to use. | -If the registry is private, provide credentials in the url. You can use a password or a [personal access token]({{< relref "doc/developers/api-usage.en-us.md#authentication" >}}): +If the registry is private, provide credentials in the url. You can use a password or a [personal access token]({{< relref "doc/development/api-usage.en-us.md#authentication" >}}): ```shell echo "deb https://{username}:{your_password_or_token}@gitea.example.com/api/packages/{owner}/debian {distribution} {component}" | sudo tee -a /etc/apt/sources.list.d/gitea.list From ae2e64c083321ee5e102f89ccf0a2afd5855946e Mon Sep 17 00:00:00 2001 From: techknowlogick Date: Fri, 28 Apr 2023 16:27:57 -0400 Subject: [PATCH 22/27] bump version number --- models/migrations/v1_20/{v255.go => v256.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename models/migrations/v1_20/{v255.go => v256.go} (100%) diff --git a/models/migrations/v1_20/v255.go b/models/migrations/v1_20/v256.go similarity index 100% rename from models/migrations/v1_20/v255.go rename to models/migrations/v1_20/v256.go From 8902760d90e7dbb961d1268e170dfc444023d131 Mon Sep 17 00:00:00 2001 From: techknowlogick Date: Fri, 28 Apr 2023 16:34:22 -0400 Subject: [PATCH 23/27] fix lint --- docs/content/doc/usage/packages/debian.en-us.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/doc/usage/packages/debian.en-us.md b/docs/content/doc/usage/packages/debian.en-us.md index 72a372b55da8b..ac1abed18e912 100644 --- a/docs/content/doc/usage/packages/debian.en-us.md +++ b/docs/content/doc/usage/packages/debian.en-us.md @@ -131,4 +131,4 @@ To install a package from the Debian registry, execute the following commands: apt install {package_name} # use specific version apt install {package_name}={package_version} -``` \ No newline at end of file +``` From f50f651bedb1bb28f58d2ab61e57a0d44545d4e2 Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Sat, 29 Apr 2023 10:20:40 +0000 Subject: [PATCH 24/27] Add suggestions. --- docs/content/doc/usage/packages/debian.en-us.md | 6 +++--- models/migrations/v1_20/v256.go | 4 ++-- models/packages/package.go | 2 +- modules/packages/debian/metadata.go | 8 +++++--- options/locale/locale_en-US.ini | 2 +- routers/api/packages/api.go | 4 ++-- routers/api/packages/debian/debian.go | 10 +++++----- services/packages/debian/repository.go | 6 +++--- templates/package/content/debian.tmpl | 8 ++++---- 9 files changed, 26 insertions(+), 24 deletions(-) diff --git a/docs/content/doc/usage/packages/debian.en-us.md b/docs/content/doc/usage/packages/debian.en-us.md index ac1abed18e912..7506b5ce2fdfb 100644 --- a/docs/content/doc/usage/packages/debian.en-us.md +++ b/docs/content/doc/usage/packages/debian.en-us.md @@ -60,7 +60,7 @@ apt update ## Publish a package -To publish a Debian package (`*.deb`), perform a HTTP PUT operation with the package content in the request body. +To publish a Debian package (`*.deb`), perform a HTTP `PUT` operation with the package content in the request body. ``` PUT https://gitea.example.com/api/packages/{owner}/debian/pool/{distribution}/{component}/upload @@ -89,11 +89,11 @@ The server reponds with the following HTTP Status codes. | ----------------- | ------- | | `201 Created` | The package has been published. | | `400 Bad Request` | The package name, version, distribution, component or architecture are invalid. | -| `409 Conflict` | A package file with the same combination of parameters exist already in the package. | +| `409 Conflict` | A package file with the same combination of parameters exists already. | ## Delete a package -To delete a Debian package perform a HTTP DELETE operation. This will delete the package version too if there is no file left. +To delete a Debian package perform a HTTP `DELETE` operation. This will delete the package version too if there is no file left. ``` DELETE https://gitea.example.com/api/packages/{owner}/debian/pool/{distribution}/{component}/{package_name}/{package_version}/{architecture} diff --git a/models/migrations/v1_20/v256.go b/models/migrations/v1_20/v256.go index ddb487f6844f4..aa1246dd56255 100644 --- a/models/migrations/v1_20/v256.go +++ b/models/migrations/v1_20/v256.go @@ -16,8 +16,8 @@ func AddIsInternalColumnToPackage(x *xorm.Engine) error { Name string `xorm:"NOT NULL"` LowerName string `xorm:"UNIQUE(s) INDEX NOT NULL"` SemverCompatible bool `xorm:"NOT NULL DEFAULT false"` - IsInternal bool `xorm:"INDEX NOT NULL DEFAULT false"` + IsInternal bool `xorm:"UNIQUE(s) INDEX NOT NULL DEFAULT false"` } - return x.Sync2(new(Package)) + return x.Sync(new(Package)) } diff --git a/models/packages/package.go b/models/packages/package.go index 579e9e4d53351..c3533e267b067 100644 --- a/models/packages/package.go +++ b/models/packages/package.go @@ -160,7 +160,7 @@ type Package struct { Name string `xorm:"NOT NULL"` LowerName string `xorm:"UNIQUE(s) INDEX NOT NULL"` SemverCompatible bool `xorm:"NOT NULL DEFAULT false"` - IsInternal bool `xorm:"INDEX NOT NULL DEFAULT false"` + IsInternal bool `xorm:"UNIQUE(s) INDEX NOT NULL DEFAULT false"` } // TryInsertPackage inserts a package. If a package exists already, ErrDuplicatePackage is returned diff --git a/modules/packages/debian/metadata.go b/modules/packages/debian/metadata.go index 08daaf082e126..dee524c8ff8bf 100644 --- a/modules/packages/debian/metadata.go +++ b/modules/packages/debian/metadata.go @@ -32,11 +32,13 @@ const ( RepositoryPackage = "_debian" RepositoryVersion = "_repository" + + controlTar = "control.tar" ) var ( ErrMissingControlFile = util.NewInvalidArgumentErrorf("control file is missing") - ErrUnsupportedCompression = util.NewInvalidArgumentErrorf("unsupported compression algorithmn") + ErrUnsupportedCompression = util.NewInvalidArgumentErrorf("unsupported compression algorithm") ErrInvalidName = util.NewInvalidArgumentErrorf("package name is invalid") ErrInvalidVersion = util.NewInvalidArgumentErrorf("package version is invalid") ErrInvalidArchitecture = util.NewInvalidArgumentErrorf("package architecture is invalid") @@ -76,9 +78,9 @@ func ParsePackage(r io.Reader) (*Package, error) { return nil, err } - if strings.HasPrefix(hd.Name, "control.tar") { + if strings.HasPrefix(hd.Name, controlTar) { var inner io.Reader - switch hd.Name[11:] { + switch hd.Name[len(controlTar):] { case "": inner = arr case ".gz": diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 76d8e5d4d7ced..9a4aecb10ecdf 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -3259,7 +3259,7 @@ container.labels = Labels container.labels.key = Key container.labels.value = Value debian.registry = Setup this registry from the command line: -debian.registry.info = Choose <distribution> and <component> from the list below. +debian.registry.info = Choose $distribution and $component from the list below. debian.install = To install the package, run the following command: debian.documentation = For more information on the Debian registry, see the documentation. debian.repository = Repository Info diff --git a/routers/api/packages/api.go b/routers/api/packages/api.go index 2ce233171c4d0..04b0c0ab08dfc 100644 --- a/routers/api/packages/api.go +++ b/routers/api/packages/api.go @@ -277,10 +277,10 @@ func CommonRoutes(ctx gocontext.Context) *web.Route { r.Get("/repository.key", debian.GetRepositoryKey) r.Group("/dists/{distribution}", func() { r.Get("/{filename}", debian.GetRepositoryFile) - r.Get("/by-hash/{algorithmn}/{hash}", debian.GetRepositoryFileByHash) + r.Get("/by-hash/{algorithm}/{hash}", debian.GetRepositoryFileByHash) r.Group("/{component}/{architecture}", func() { r.Get("/{filename}", debian.GetRepositoryFile) - r.Get("/by-hash/{algorithmn}/{hash}", debian.GetRepositoryFileByHash) + r.Get("/by-hash/{algorithm}/{hash}", debian.GetRepositoryFileByHash) }) }) r.Group("/pool/{distribution}/{component}", func() { diff --git a/routers/api/packages/debian/debian.go b/routers/api/packages/debian/debian.go index 3cda04f96fe4f..7682074fab609 100644 --- a/routers/api/packages/debian/debian.go +++ b/routers/api/packages/debian/debian.go @@ -91,15 +91,15 @@ func GetRepositoryFileByHash(ctx *context.Context) { return } - algorithmn := strings.ToLower(ctx.Params("algorithmn")) - if algorithmn == "md5sum" { - algorithmn = "md5" + algorithm := strings.ToLower(ctx.Params("algorithm")) + if algorithm == "md5sum" { + algorithm = "md5" } pfs, _, err := packages_model.SearchFiles(ctx, &packages_model.PackageFileSearchOptions{ VersionID: pv.ID, Hash: strings.ToLower(ctx.Params("hash")), - HashAlgorithmn: algorithmn, + HashAlgorithmn: algorithm, }) if err != nil { apiError(ctx, http.StatusInternalServerError, err) @@ -231,7 +231,7 @@ func DownloadPackageFile(ctx *context.Context) { }, ) if err != nil { - if err == packages_model.ErrPackageNotExist || err == packages_model.ErrPackageFileNotExist { + if errors.Is(err, util.ErrNotExist) { apiError(ctx, http.StatusNotFound, err) } else { apiError(ctx, http.StatusInternalServerError, err) diff --git a/services/packages/debian/repository.go b/services/packages/debian/repository.go index eac878256ab7b..69d00086a0833 100644 --- a/services/packages/debian/repository.go +++ b/services/packages/debian/repository.go @@ -243,12 +243,12 @@ func buildPackagesIndices(ctx context.Context, ownerID int64, repoVersion *packa w := io.MultiWriter(packagesContent, gzw, xzw) - addSeperator := false + addSeparator := false for _, pfd := range pfds { - if addSeperator { + if addSeparator { fmt.Fprintln(w) } - addSeperator = true + addSeparator = true fmt.Fprint(w, pfd.Properties.GetByName(debian_module.PropertyControl)) diff --git a/templates/package/content/debian.tmpl b/templates/package/content/debian.tmpl index c10c3042b92c4..a35417a19b6b3 100644 --- a/templates/package/content/debian.tmpl +++ b/templates/package/content/debian.tmpl @@ -5,7 +5,7 @@
sudo curl  -o /etc/apt/trusted.gpg.d/gitea-{{$.PackageDescriptor.Owner.Name}}.asc
-echo "deb  <distribution> <component>" | sudo tee -a /etc/apt/sources.list.d/gitea.list
+echo "deb  $distribution $component" | sudo tee -a /etc/apt/sources.list.d/gitea.list
 sudo apt update

{{.locale.Tr "packages.debian.registry.info" | Safe}}

@@ -27,15 +27,15 @@ sudo apt update
{{.locale.Tr "packages.debian.repository.distributions"}}
- {{Join .Distributions ", "}} + {{StringUtils.Join .Distributions ", "}}
{{.locale.Tr "packages.debian.repository.components"}}
- {{Join .Components ", "}} + {{StringUtils.Join .Components ", "}}
{{.locale.Tr "packages.debian.repository.architectures"}}
- {{Join .Architectures ", "}} + {{StringUtils.Join .Architectures ", "}} From 84c01879584eb84e10432d1ae71aae20de3e5add Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Sun, 30 Apr 2023 13:43:28 +0000 Subject: [PATCH 25/27] Change hash search logic. --- models/packages/package_file.go | 21 ++++++++++++++++----- routers/api/packages/debian/debian.go | 6 +++--- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/models/packages/package_file.go b/models/packages/package_file.go index 337ab1135a2f6..79ffb053d4c97 100644 --- a/models/packages/package_file.go +++ b/models/packages/package_file.go @@ -124,7 +124,7 @@ type PackageFileSearchOptions struct { CompositeKey string Properties map[string]string OlderThan time.Duration - HashAlgorithmn string + HashAlgorithm string Hash string db.Paginator } @@ -184,12 +184,23 @@ func (opts *PackageFileSearchOptions) toConds() builder.Cond { cond = cond.And(builder.Lt{"package_file.created_unix": time.Now().Add(-opts.OlderThan).Unix()}) } - if opts.Hash != "" && (opts.HashAlgorithmn == "md5" || opts.HashAlgorithmn == "sha1" || opts.HashAlgorithmn == "sha256" || opts.HashAlgorithmn == "sha512") { + if opts.Hash != "" { + var field string + switch strings.ToLower(opts.HashAlgorithm) { + case "md5": + field = "package_blob.hash_md5" + case "sha1": + field = "package_blob.hash_sha1" + case "sha256": + field = "package_blob.hash_sha256" + case "sha512": + fallthrough + default: // default to SHA512 if not specified or unknown + field = "package_blob.hash_sha512" + } innerCond := builder. Expr("package_blob.id = package_file.blob_id"). - And(builder.Eq{ - "package_blob.hash_" + opts.HashAlgorithmn: opts.Hash, - }) + And(builder.Eq{field: opts.Hash}) cond = cond.And(builder.Exists(builder.Select("package_blob.id").From("package_blob").Where(innerCond))) } diff --git a/routers/api/packages/debian/debian.go b/routers/api/packages/debian/debian.go index 7682074fab609..cfc03ae522a6f 100644 --- a/routers/api/packages/debian/debian.go +++ b/routers/api/packages/debian/debian.go @@ -97,9 +97,9 @@ func GetRepositoryFileByHash(ctx *context.Context) { } pfs, _, err := packages_model.SearchFiles(ctx, &packages_model.PackageFileSearchOptions{ - VersionID: pv.ID, - Hash: strings.ToLower(ctx.Params("hash")), - HashAlgorithmn: algorithm, + VersionID: pv.ID, + Hash: strings.ToLower(ctx.Params("hash")), + HashAlgorithm: algorithm, }) if err != nil { apiError(ctx, http.StatusInternalServerError, err) From e2a28070d48305e5c5dd58e77b4deced77f60f3f Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Sun, 30 Apr 2023 16:31:31 +0000 Subject: [PATCH 26/27] Drop IsInternal index. --- models/migrations/v1_20/v256.go | 2 +- models/packages/package.go | 2 +- models/packages/package_file.go | 18 +++++++++--------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/models/migrations/v1_20/v256.go b/models/migrations/v1_20/v256.go index aa1246dd56255..822153b93e568 100644 --- a/models/migrations/v1_20/v256.go +++ b/models/migrations/v1_20/v256.go @@ -16,7 +16,7 @@ func AddIsInternalColumnToPackage(x *xorm.Engine) error { Name string `xorm:"NOT NULL"` LowerName string `xorm:"UNIQUE(s) INDEX NOT NULL"` SemverCompatible bool `xorm:"NOT NULL DEFAULT false"` - IsInternal bool `xorm:"UNIQUE(s) INDEX NOT NULL DEFAULT false"` + IsInternal bool `xorm:"NOT NULL DEFAULT false"` } return x.Sync(new(Package)) diff --git a/models/packages/package.go b/models/packages/package.go index c3533e267b067..cc860b9a907cf 100644 --- a/models/packages/package.go +++ b/models/packages/package.go @@ -160,7 +160,7 @@ type Package struct { Name string `xorm:"NOT NULL"` LowerName string `xorm:"UNIQUE(s) INDEX NOT NULL"` SemverCompatible bool `xorm:"NOT NULL DEFAULT false"` - IsInternal bool `xorm:"UNIQUE(s) INDEX NOT NULL DEFAULT false"` + IsInternal bool `xorm:"NOT NULL DEFAULT false"` } // TryInsertPackage inserts a package. If a package exists already, ErrDuplicatePackage is returned diff --git a/models/packages/package_file.go b/models/packages/package_file.go index 79ffb053d4c97..28e2a0111a5e4 100644 --- a/models/packages/package_file.go +++ b/models/packages/package_file.go @@ -117,15 +117,15 @@ func DeleteFileByID(ctx context.Context, fileID int64) error { // PackageFileSearchOptions are options for SearchXXX methods type PackageFileSearchOptions struct { - OwnerID int64 - PackageType string - VersionID int64 - Query string - CompositeKey string - Properties map[string]string - OlderThan time.Duration - HashAlgorithm string - Hash string + OwnerID int64 + PackageType string + VersionID int64 + Query string + CompositeKey string + Properties map[string]string + OlderThan time.Duration + HashAlgorithm string + Hash string db.Paginator } From 5978b5e2f114622247ecd6aee843ecb6f62292a6 Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Sun, 30 Apr 2023 17:56:28 +0000 Subject: [PATCH 27/27] Use url format. --- options/locale/locale_en-US.ini | 2 +- templates/package/content/debian.tmpl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index cd790ced1b61f..8c517a3340e14 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -3270,7 +3270,7 @@ container.labels.value = Value debian.registry = Setup this registry from the command line: debian.registry.info = Choose $distribution and $component from the list below. debian.install = To install the package, run the following command: -debian.documentation = For more information on the Debian registry, see the documentation. +debian.documentation = For more information on the Debian registry, see the documentation. debian.repository = Repository Info debian.repository.distributions = Distributions debian.repository.components = Components diff --git a/templates/package/content/debian.tmpl b/templates/package/content/debian.tmpl index a35417a19b6b3..8b60b33bcc81b 100644 --- a/templates/package/content/debian.tmpl +++ b/templates/package/content/debian.tmpl @@ -16,7 +16,7 @@ sudo apt update
- +