gitea源码

repository.go 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  1. // Copyright 2024 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package arch
  4. import (
  5. "archive/tar"
  6. "bytes"
  7. "compress/gzip"
  8. "context"
  9. "encoding/base64"
  10. "errors"
  11. "fmt"
  12. "io"
  13. "os"
  14. "strconv"
  15. "strings"
  16. packages_model "code.gitea.io/gitea/models/packages"
  17. arch_model "code.gitea.io/gitea/models/packages/arch"
  18. user_model "code.gitea.io/gitea/models/user"
  19. "code.gitea.io/gitea/modules/container"
  20. "code.gitea.io/gitea/modules/globallock"
  21. "code.gitea.io/gitea/modules/json"
  22. packages_module "code.gitea.io/gitea/modules/packages"
  23. arch_module "code.gitea.io/gitea/modules/packages/arch"
  24. "code.gitea.io/gitea/modules/util"
  25. packages_service "code.gitea.io/gitea/services/packages"
  26. "github.com/ProtonMail/go-crypto/openpgp"
  27. "github.com/ProtonMail/go-crypto/openpgp/armor"
  28. "github.com/ProtonMail/go-crypto/openpgp/packet"
  29. )
  30. const (
  31. IndexArchiveFilename = "packages.db"
  32. )
  33. func AquireRegistryLock(ctx context.Context, ownerID int64) (globallock.ReleaseFunc, error) {
  34. return globallock.Lock(ctx, fmt.Sprintf("packages_arch_%d", ownerID))
  35. }
  36. // GetOrCreateRepositoryVersion gets or creates the internal repository package
  37. // The Arch registry needs multiple index files which are stored in this package.
  38. func GetOrCreateRepositoryVersion(ctx context.Context, ownerID int64) (*packages_model.PackageVersion, error) {
  39. return packages_service.GetOrCreateInternalPackageVersion(ctx, ownerID, packages_model.TypeArch, arch_module.RepositoryPackage, arch_module.RepositoryVersion)
  40. }
  41. // GetOrCreateKeyPair gets or creates the PGP keys used to sign repository files
  42. func GetOrCreateKeyPair(ctx context.Context, ownerID int64) (string, string, error) {
  43. priv, err := user_model.GetSetting(ctx, ownerID, arch_module.SettingKeyPrivate)
  44. if err != nil && !errors.Is(err, util.ErrNotExist) {
  45. return "", "", err
  46. }
  47. pub, err := user_model.GetSetting(ctx, ownerID, arch_module.SettingKeyPublic)
  48. if err != nil && !errors.Is(err, util.ErrNotExist) {
  49. return "", "", err
  50. }
  51. if priv == "" || pub == "" {
  52. priv, pub, err = generateKeypair()
  53. if err != nil {
  54. return "", "", err
  55. }
  56. if err := user_model.SetUserSetting(ctx, ownerID, arch_module.SettingKeyPrivate, priv); err != nil {
  57. return "", "", err
  58. }
  59. if err := user_model.SetUserSetting(ctx, ownerID, arch_module.SettingKeyPublic, pub); err != nil {
  60. return "", "", err
  61. }
  62. }
  63. return priv, pub, nil
  64. }
  65. func generateKeypair() (string, string, error) {
  66. e, err := openpgp.NewEntity("", "Arch Registry", "", nil)
  67. if err != nil {
  68. return "", "", err
  69. }
  70. var priv strings.Builder
  71. var pub strings.Builder
  72. w, err := armor.Encode(&priv, openpgp.PrivateKeyType, nil)
  73. if err != nil {
  74. return "", "", err
  75. }
  76. if err := e.SerializePrivate(w, nil); err != nil {
  77. return "", "", err
  78. }
  79. w.Close()
  80. w, err = armor.Encode(&pub, openpgp.PublicKeyType, nil)
  81. if err != nil {
  82. return "", "", err
  83. }
  84. if err := e.Serialize(w); err != nil {
  85. return "", "", err
  86. }
  87. w.Close()
  88. return priv.String(), pub.String(), nil
  89. }
  90. func SignData(ctx context.Context, ownerID int64, r io.Reader) ([]byte, error) {
  91. priv, _, err := GetOrCreateKeyPair(ctx, ownerID)
  92. if err != nil {
  93. return nil, err
  94. }
  95. block, err := armor.Decode(strings.NewReader(priv))
  96. if err != nil {
  97. return nil, err
  98. }
  99. e, err := openpgp.ReadEntity(packet.NewReader(block.Body))
  100. if err != nil {
  101. return nil, err
  102. }
  103. buf := &bytes.Buffer{}
  104. if err := openpgp.DetachSign(buf, e, r, nil); err != nil {
  105. return nil, err
  106. }
  107. return buf.Bytes(), nil
  108. }
  109. // BuildAllRepositoryFiles (re)builds all repository files for every available repositories and architectures
  110. func BuildAllRepositoryFiles(ctx context.Context, ownerID int64) error {
  111. pv, err := GetOrCreateRepositoryVersion(ctx, ownerID)
  112. if err != nil {
  113. return err
  114. }
  115. // 1. Delete all existing repository files
  116. pfs, err := packages_model.GetFilesByVersionID(ctx, pv.ID)
  117. if err != nil {
  118. return err
  119. }
  120. for _, pf := range pfs {
  121. if err := packages_service.DeletePackageFile(ctx, pf); err != nil {
  122. return err
  123. }
  124. }
  125. // 2. (Re)Build repository files for existing packages
  126. repositories, err := arch_model.GetRepositories(ctx, ownerID)
  127. if err != nil {
  128. return err
  129. }
  130. for _, repository := range repositories {
  131. architectures, err := arch_model.GetArchitectures(ctx, ownerID, repository)
  132. if err != nil {
  133. return err
  134. }
  135. for _, architecture := range architectures {
  136. if err := buildPackagesIndex(ctx, ownerID, pv, repository, architecture); err != nil {
  137. return fmt.Errorf("failed to build repository files [%s/%s]: %w", repository, architecture, err)
  138. }
  139. }
  140. }
  141. return nil
  142. }
  143. // BuildSpecificRepositoryFiles builds index files for the repository
  144. func BuildSpecificRepositoryFiles(ctx context.Context, ownerID int64, repository, architecture string) error {
  145. pv, err := GetOrCreateRepositoryVersion(ctx, ownerID)
  146. if err != nil {
  147. return err
  148. }
  149. architectures := container.SetOf(architecture)
  150. if architecture == arch_module.AnyArch {
  151. // Update all other architectures too when updating the any index
  152. additionalArchitectures, err := arch_model.GetArchitectures(ctx, ownerID, repository)
  153. if err != nil {
  154. return err
  155. }
  156. architectures.AddMultiple(additionalArchitectures...)
  157. }
  158. for architecture := range architectures {
  159. if err := buildPackagesIndex(ctx, ownerID, pv, repository, architecture); err != nil {
  160. return err
  161. }
  162. }
  163. return nil
  164. }
  165. func searchPackageFiles(ctx context.Context, ownerID int64, repository, architecture string) ([]*packages_model.PackageFile, error) {
  166. pfs, _, err := packages_model.SearchFiles(ctx, &packages_model.PackageFileSearchOptions{
  167. OwnerID: ownerID,
  168. PackageType: packages_model.TypeArch,
  169. Query: "%.pkg.tar.%",
  170. Properties: map[string]string{
  171. arch_module.PropertyRepository: repository,
  172. arch_module.PropertyArchitecture: architecture,
  173. },
  174. })
  175. if err != nil {
  176. return nil, err
  177. }
  178. return pfs, nil
  179. }
  180. func buildPackagesIndex(ctx context.Context, ownerID int64, repoVersion *packages_model.PackageVersion, repository, architecture string) error {
  181. pfs, err := searchPackageFiles(ctx, ownerID, repository, architecture)
  182. if err != nil {
  183. return err
  184. }
  185. if architecture != arch_module.AnyArch {
  186. // Add all any packages too
  187. anyarchFiles, err := searchPackageFiles(ctx, ownerID, repository, arch_module.AnyArch)
  188. if err != nil {
  189. return err
  190. }
  191. pfs = append(pfs, anyarchFiles...)
  192. }
  193. // Delete the package indices if there are no packages
  194. if len(pfs) == 0 {
  195. pf, err := packages_model.GetFileForVersionByName(ctx, repoVersion.ID, IndexArchiveFilename, fmt.Sprintf("%s|%s", repository, architecture))
  196. if err != nil && !errors.Is(err, util.ErrNotExist) {
  197. return err
  198. } else if pf == nil {
  199. return nil
  200. }
  201. return packages_service.DeletePackageFile(ctx, pf)
  202. }
  203. vpfs := make(map[int64]*entryOptions)
  204. for _, pf := range pfs {
  205. current := &entryOptions{
  206. File: pf,
  207. }
  208. current.Version, err = packages_model.GetVersionByID(ctx, pf.VersionID)
  209. if err != nil {
  210. return err
  211. }
  212. // here we compare the versions but not using SearchLatestVersions because we shouldn't allow "downgrading" to a older version by "latest" one.
  213. // https://wiki.archlinux.org/title/Downgrading_packages : randomly downgrading can mess up dependencies:
  214. // If a downgrade involves a soname change, all dependencies may need downgrading or rebuilding too.
  215. if old, ok := vpfs[current.Version.PackageID]; ok {
  216. if compareVersions(old.Version.Version, current.Version.Version) == -1 {
  217. vpfs[current.Version.PackageID] = current
  218. }
  219. } else {
  220. vpfs[current.Version.PackageID] = current
  221. }
  222. }
  223. indexContent, _ := packages_module.NewHashedBuffer()
  224. defer indexContent.Close()
  225. gw := gzip.NewWriter(indexContent)
  226. tw := tar.NewWriter(gw)
  227. cache := make(map[int64]*packages_model.Package)
  228. for _, opts := range vpfs {
  229. if err := json.Unmarshal([]byte(opts.Version.MetadataJSON), &opts.VersionMetadata); err != nil {
  230. return err
  231. }
  232. opts.Package = cache[opts.Version.PackageID]
  233. if opts.Package == nil {
  234. opts.Package, err = packages_model.GetPackageByID(ctx, opts.Version.PackageID)
  235. if err != nil {
  236. return err
  237. }
  238. cache[opts.Package.ID] = opts.Package
  239. }
  240. opts.Blob, err = packages_model.GetBlobByID(ctx, opts.File.BlobID)
  241. if err != nil {
  242. return err
  243. }
  244. sig, err := packages_model.GetPropertiesByName(ctx, packages_model.PropertyTypeFile, opts.File.ID, arch_module.PropertySignature)
  245. if err != nil {
  246. return err
  247. }
  248. if len(sig) == 0 {
  249. return util.ErrNotExist
  250. }
  251. opts.Signature = sig[0].Value
  252. meta, err := packages_model.GetPropertiesByName(ctx, packages_model.PropertyTypeFile, opts.File.ID, arch_module.PropertyMetadata)
  253. if err != nil {
  254. return err
  255. }
  256. if len(meta) == 0 {
  257. return util.ErrNotExist
  258. }
  259. if err := json.Unmarshal([]byte(meta[0].Value), &opts.FileMetadata); err != nil {
  260. return err
  261. }
  262. if err := writeFiles(tw, opts); err != nil {
  263. return err
  264. }
  265. if err := writeDescription(tw, opts); err != nil {
  266. return err
  267. }
  268. }
  269. tw.Close()
  270. gw.Close()
  271. signature, err := SignData(ctx, ownerID, indexContent)
  272. if err != nil {
  273. return err
  274. }
  275. if _, err := indexContent.Seek(0, io.SeekStart); err != nil {
  276. return err
  277. }
  278. _, err = packages_service.AddFileToPackageVersionInternal(
  279. ctx,
  280. repoVersion,
  281. &packages_service.PackageFileCreationInfo{
  282. PackageFileInfo: packages_service.PackageFileInfo{
  283. Filename: IndexArchiveFilename,
  284. CompositeKey: fmt.Sprintf("%s|%s", repository, architecture),
  285. },
  286. Creator: user_model.NewGhostUser(),
  287. Data: indexContent,
  288. IsLead: false,
  289. OverwriteExisting: true,
  290. Properties: map[string]string{
  291. arch_module.PropertyRepository: repository,
  292. arch_module.PropertyArchitecture: architecture,
  293. arch_module.PropertySignature: base64.StdEncoding.EncodeToString(signature),
  294. },
  295. },
  296. )
  297. return err
  298. }
  299. type entryOptions struct {
  300. Package *packages_model.Package
  301. Version *packages_model.PackageVersion
  302. VersionMetadata *arch_module.VersionMetadata
  303. File *packages_model.PackageFile
  304. FileMetadata *arch_module.FileMetadata
  305. Blob *packages_model.PackageBlob
  306. Signature string
  307. }
  308. type keyValue struct {
  309. Key string
  310. Value string
  311. }
  312. func writeFiles(tw *tar.Writer, opts *entryOptions) error {
  313. return writeFields(tw, fmt.Sprintf("%s-%s/files", opts.Package.Name, opts.Version.Version), []keyValue{
  314. {"FILES", strings.Join(opts.FileMetadata.Files, "\n")},
  315. })
  316. }
  317. // https://gitlab.archlinux.org/pacman/pacman/-/blob/master/lib/libalpm/be_sync.c#L562
  318. func writeDescription(tw *tar.Writer, opts *entryOptions) error {
  319. return writeFields(tw, fmt.Sprintf("%s-%s/desc", opts.Package.Name, opts.Version.Version), []keyValue{
  320. {"FILENAME", opts.File.Name},
  321. {"MD5SUM", opts.Blob.HashMD5},
  322. {"SHA256SUM", opts.Blob.HashSHA256},
  323. {"PGPSIG", opts.Signature},
  324. {"CSIZE", strconv.FormatInt(opts.Blob.Size, 10)},
  325. {"ISIZE", strconv.FormatInt(opts.FileMetadata.InstalledSize, 10)},
  326. {"NAME", opts.Package.Name},
  327. {"BASE", opts.FileMetadata.Base},
  328. {"ARCH", opts.FileMetadata.Architecture},
  329. {"VERSION", opts.Version.Version},
  330. {"DESC", opts.VersionMetadata.Description},
  331. {"URL", opts.VersionMetadata.ProjectURL},
  332. {"LICENSE", strings.Join(opts.VersionMetadata.Licenses, "\n")},
  333. {"GROUPS", strings.Join(opts.FileMetadata.Groups, "\n")},
  334. {"BUILDDATE", strconv.FormatInt(opts.FileMetadata.BuildDate, 10)},
  335. {"PACKAGER", opts.FileMetadata.Packager},
  336. {"PROVIDES", strings.Join(opts.FileMetadata.Provides, "\n")},
  337. {"REPLACES", strings.Join(opts.FileMetadata.Replaces, "\n")},
  338. {"CONFLICTS", strings.Join(opts.FileMetadata.Conflicts, "\n")},
  339. {"DEPENDS", strings.Join(opts.FileMetadata.Depends, "\n")},
  340. {"OPTDEPENDS", strings.Join(opts.FileMetadata.OptDepends, "\n")},
  341. {"MAKEDEPENDS", strings.Join(opts.FileMetadata.MakeDepends, "\n")},
  342. {"CHECKDEPENDS", strings.Join(opts.FileMetadata.CheckDepends, "\n")},
  343. })
  344. }
  345. func writeFields(tw *tar.Writer, filename string, fields []keyValue) error {
  346. buf := &bytes.Buffer{}
  347. for _, kv := range fields {
  348. if kv.Value == "" {
  349. continue
  350. }
  351. fmt.Fprintf(buf, "%%%s%%\n%s\n\n", kv.Key, kv.Value)
  352. }
  353. if err := tw.WriteHeader(&tar.Header{
  354. Name: filename,
  355. Size: int64(buf.Len()),
  356. Mode: int64(os.ModePerm),
  357. }); err != nil {
  358. return err
  359. }
  360. _, err := io.Copy(tw, buf)
  361. return err
  362. }