loading up the forgejo repo on tangled to test page performance
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Alt Linux Apt-Rpm repository support for Forgejo packages. (#6351)

Co-authored-by: Aleksandr Gamzin alexgamz1119@gmail.com

Adds support for the Apt-Rpm registry of the Alt Lunux distribution.

Alt Linux uses RPM packages to store and distribute software to its users. But the logic of the Alt Linux package registry is different from the Red Hat package registry.
I have added support for the Alt Linux package registry.

## Checklist

The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org).

### Tests

- I added test coverage for Go changes...
- [ ] in their respective `*_test.go` for unit tests.
- [x] in the `tests/integration` directory if it involves interactions with a live Forgejo server.
- I added test coverage for JavaScript changes...
- [ ] in `web_src/js/*.test.js` if it can be unit tested.
- [ ] in `tests/e2e/*.test.e2e.js` if it requires interactions with a live Forgejo server (see also the [developer guide for JavaScript testing](https://codeberg.org/forgejo/forgejo/src/branch/forgejo/tests/e2e/README.md#end-to-end-tests)).

### Documentation

- [x] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change.
- [ ] I did not document these changes and I do not expect someone else to do it.

### Release notes

- [ ] I do not want this change to show in the release notes.
- [x] I want the title to show in the release notes with a link to this pull request.
- [ ] I want the content of the `release-notes/<pull request number>.md` to be be used for the release notes instead of the title.

Co-authored-by: Aleksandr Gamzin <gamzin@altlinux.org>
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/6351
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>
Co-authored-by: Alex619829 <alex619829@noreply.codeberg.org>
Co-committed-by: Alex619829 <alex619829@noreply.codeberg.org>

authored by

Alex619829
Aleksandr Gamzin
Alex619829
and committed by
Earl Warren
7ae53765 a40284be

+2156 -86
+2
models/forgejo_migrations/migrate.go
··· 88 88 NewMigration("Add `purpose` column to `forgejo_auth_token` table", AddPurposeToForgejoAuthToken), 89 89 // v25 -> v26 90 90 NewMigration("Migrate `secret` column to store keying material", MigrateTwoFactorToKeying), 91 + // v26 -> v27 92 + NewMigration("Add `hash_blake2b` column to `package_blob` table", AddHashBlake2bToPackageBlob), 91 93 } 92 94 93 95 // GetCurrentDBVersion returns the current Forgejo database version.
+14
models/forgejo_migrations/v26.go
··· 1 + // Copyright 2024 The Forgejo Authors. All rights reserved. 2 + // SPDX-License-Identifier: MIT 3 + 4 + package forgejo_migrations //nolint:revive 5 + 6 + import "xorm.io/xorm" 7 + 8 + func AddHashBlake2bToPackageBlob(x *xorm.Engine) error { 9 + type PackageBlob struct { 10 + ID int64 `xorm:"pk autoincr"` 11 + HashBlake2b string 12 + } 13 + return x.Sync(&PackageBlob{}) 14 + }
+29
models/packages/alt/search.go
··· 1 + // Copyright 2024 The Gitea Authors. All rights reserved. 2 + // SPDX-License-Identifier: MIT 3 + 4 + package alt 5 + 6 + import ( 7 + "context" 8 + 9 + packages_model "code.gitea.io/gitea/models/packages" 10 + rpm_module "code.gitea.io/gitea/modules/packages/rpm" 11 + ) 12 + 13 + type PackageSearchOptions struct { 14 + OwnerID int64 15 + GroupID int64 16 + Architecture string 17 + } 18 + 19 + // GetGroups gets all available groups 20 + func GetGroups(ctx context.Context, ownerID int64) ([]string, error) { 21 + return packages_model.GetDistinctPropertyValues( 22 + ctx, 23 + packages_model.TypeAlt, 24 + ownerID, 25 + packages_model.PropertyTypeFile, 26 + rpm_module.PropertyGroup, 27 + nil, 28 + ) 29 + }
+2
models/packages/descriptor.go
··· 187 187 metadata = &pypi.Metadata{} 188 188 case TypeRpm: 189 189 metadata = &rpm.VersionMetadata{} 190 + case TypeAlt: 191 + metadata = &rpm.VersionMetadata{} 190 192 case TypeRubyGems: 191 193 metadata = &rubygems.Metadata{} 192 194 case TypeSwift:
+6
models/packages/package.go
··· 51 51 TypePub Type = "pub" 52 52 TypePyPI Type = "pypi" 53 53 TypeRpm Type = "rpm" 54 + TypeAlt Type = "alt" 54 55 TypeRubyGems Type = "rubygems" 55 56 TypeSwift Type = "swift" 56 57 TypeVagrant Type = "vagrant" ··· 76 77 TypePub, 77 78 TypePyPI, 78 79 TypeRpm, 80 + TypeAlt, 79 81 TypeRubyGems, 80 82 TypeSwift, 81 83 TypeVagrant, ··· 122 124 return "PyPI" 123 125 case TypeRpm: 124 126 return "RPM" 127 + case TypeAlt: 128 + return "Alt" 125 129 case TypeRubyGems: 126 130 return "RubyGems" 127 131 case TypeSwift: ··· 173 177 return "gitea-python" 174 178 case TypeRpm: 175 179 return "gitea-rpm" 180 + case TypeAlt: 181 + return "gitea-alt" 176 182 case TypeRubyGems: 177 183 return "gitea-rubygems" 178 184 case TypeSwift:
+7 -5
models/packages/package_blob.go
··· 34 34 HashSHA1 string `xorm:"hash_sha1 char(40) UNIQUE(sha1) INDEX NOT NULL"` 35 35 HashSHA256 string `xorm:"hash_sha256 char(64) UNIQUE(sha256) INDEX NOT NULL"` 36 36 HashSHA512 string `xorm:"hash_sha512 char(128) UNIQUE(sha512) INDEX NOT NULL"` 37 + HashBlake2b string `xorm:"hash_blake2b char(128) UNIQUE(blake2b) INDEX"` 37 38 CreatedUnix timeutil.TimeStamp `xorm:"created INDEX NOT NULL"` 38 39 } 39 40 ··· 44 45 existing := &PackageBlob{} 45 46 46 47 has, err := e.Where(builder.Eq{ 47 - "size": pb.Size, 48 - "hash_md5": pb.HashMD5, 49 - "hash_sha1": pb.HashSHA1, 50 - "hash_sha256": pb.HashSHA256, 51 - "hash_sha512": pb.HashSHA512, 48 + "size": pb.Size, 49 + "hash_md5": pb.HashMD5, 50 + "hash_sha1": pb.HashSHA1, 51 + "hash_sha256": pb.HashSHA256, 52 + "hash_sha512": pb.HashSHA512, 53 + "hash_blake2b": pb.HashBlake2b, 52 54 }).Get(existing) 53 55 if err != nil { 54 56 return nil, false, err
+1 -1
modules/packages/arch/metadata.go
··· 94 94 95 95 // ParsePackage Function that receives arch package archive data and returns it's metadata. 96 96 func ParsePackage(r *packages.HashedBuffer) (*Package, error) { 97 - md5, _, sha256, _ := r.Sums() 97 + md5, _, sha256, _, _ := r.Sums() 98 98 _, err := r.Seek(0, io.SeekStart) 99 99 if err != nil { 100 100 return nil, err
+2 -2
modules/packages/hashed_buffer.go
··· 75 75 return b.combinedWriter.Write(p) 76 76 } 77 77 78 - // Sums gets the MD5, SHA1, SHA256 and SHA512 checksums of the data 79 - func (b *HashedBuffer) Sums() (hashMD5, hashSHA1, hashSHA256, hashSHA512 []byte) { 78 + // Sums gets the MD5, SHA1, SHA256, SHA512 and BLAKE2B checksums of the data 79 + func (b *HashedBuffer) Sums() (hashMD5, hashSHA1, hashSHA256, hashSHA512, hashBlake2b []byte) { 80 80 return b.hash.Sums() 81 81 }
+5 -3
modules/packages/hashed_buffer_test.go
··· 21 21 HashSHA1 string 22 22 HashSHA256 string 23 23 HashSHA512 string 24 + hashBlake2b string 24 25 }{ 25 - {5, "test", "098f6bcd4621d373cade4e832627b4f6", "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3", "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08", "ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff"}, 26 - {5, "testtest", "05a671c66aefea124cc08b76ea6d30bb", "51abb9636078defbf888d8457a7c76f85c8f114c", "37268335dd6931045bdcdf92623ff819a64244b53d0e746d438797349d4da578", "125d6d03b32c84d492747f79cf0bf6e179d287f341384eb5d6d3197525ad6be8e6df0116032935698f99a09e265073d1d6c32c274591bf1d0a20ad67cba921bc"}, 26 + {5, "test", "098f6bcd4621d373cade4e832627b4f6", "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3", "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08", "ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff", "a71079d42853dea26e453004338670a53814b78137ffbed07603a41d76a483aa9bc33b582f77d30a65e6f29a896c0411f38312e1d66e0bf16386c86a89bea572"}, 27 + {5, "testtest", "05a671c66aefea124cc08b76ea6d30bb", "51abb9636078defbf888d8457a7c76f85c8f114c", "37268335dd6931045bdcdf92623ff819a64244b53d0e746d438797349d4da578", "125d6d03b32c84d492747f79cf0bf6e179d287f341384eb5d6d3197525ad6be8e6df0116032935698f99a09e265073d1d6c32c274591bf1d0a20ad67cba921bc", "372a53b95f46e775b973031e40b844f24389657019f7b7540a9f0496f4ead4a2e4b050909664611fb0f4b7c7e92c3c04c84787be7f6b8edf7bf6bc31856b6c76"}, 27 28 } 28 29 29 30 for _, c := range cases { ··· 36 37 require.NoError(t, err) 37 38 assert.Equal(t, c.Data, string(data)) 38 39 39 - hashMD5, hashSHA1, hashSHA256, hashSHA512 := buf.Sums() 40 + hashMD5, hashSHA1, hashSHA256, hashSHA512, hashBlake2b := buf.Sums() 40 41 assert.Equal(t, c.HashMD5, hex.EncodeToString(hashMD5)) 41 42 assert.Equal(t, c.HashSHA1, hex.EncodeToString(hashSHA1)) 42 43 assert.Equal(t, c.HashSHA256, hex.EncodeToString(hashSHA256)) 43 44 assert.Equal(t, c.HashSHA512, hex.EncodeToString(hashSHA512)) 45 + assert.Equal(t, c.hashBlake2b, hex.EncodeToString(hashBlake2b)) 44 46 45 47 require.NoError(t, buf.Close()) 46 48 }
+31 -14
modules/packages/multi_hasher.go
··· 12 12 "errors" 13 13 "hash" 14 14 "io" 15 + 16 + "golang.org/x/crypto/blake2b" 15 17 ) 16 18 17 19 const ( 18 - marshaledSizeMD5 = 92 19 - marshaledSizeSHA1 = 96 20 - marshaledSizeSHA256 = 108 21 - marshaledSizeSHA512 = 204 20 + marshaledSizeMD5 = 92 21 + marshaledSizeSHA1 = 96 22 + marshaledSizeSHA256 = 108 23 + marshaledSizeSHA512 = 204 24 + marshaledSizeBlake2b = 213 22 25 23 - marshaledSize = marshaledSizeMD5 + marshaledSizeSHA1 + marshaledSizeSHA256 + marshaledSizeSHA512 26 + marshaledSize = marshaledSizeMD5 + marshaledSizeSHA1 + marshaledSizeSHA256 + marshaledSizeSHA512 + marshaledSizeBlake2b 24 27 ) 25 28 26 29 // HashSummer provide a Sums method 27 30 type HashSummer interface { 28 - Sums() (hashMD5, hashSHA1, hashSHA256, hashSHA512 []byte) 31 + Sums() (hashMD5, hashSHA1, hashSHA256, hashSHA512, hashBlake2b []byte) 29 32 } 30 33 31 34 // MultiHasher calculates multiple checksums 32 35 type MultiHasher struct { 33 - md5 hash.Hash 34 - sha1 hash.Hash 35 - sha256 hash.Hash 36 - sha512 hash.Hash 36 + md5 hash.Hash 37 + sha1 hash.Hash 38 + sha256 hash.Hash 39 + sha512 hash.Hash 40 + blake2b hash.Hash 37 41 38 42 combinedWriter io.Writer 39 43 } ··· 44 48 sha1 := sha1.New() 45 49 sha256 := sha256.New() 46 50 sha512 := sha512.New() 51 + blake2b, _ := blake2b.New512(nil) 47 52 48 - combinedWriter := io.MultiWriter(md5, sha1, sha256, sha512) 53 + combinedWriter := io.MultiWriter(md5, sha1, sha256, sha512, blake2b) 49 54 50 55 return &MultiHasher{ 51 56 md5, 52 57 sha1, 53 58 sha256, 54 59 sha512, 60 + blake2b, 55 61 combinedWriter, 56 62 } 57 63 } ··· 74 80 if err != nil { 75 81 return nil, err 76 82 } 83 + blake2bBytes, err := h.blake2b.(encoding.BinaryMarshaler).MarshalBinary() 84 + if err != nil { 85 + return nil, err 86 + } 77 87 78 88 b := make([]byte, 0, marshaledSize) 79 89 b = append(b, md5Bytes...) 80 90 b = append(b, sha1Bytes...) 81 91 b = append(b, sha256Bytes...) 82 92 b = append(b, sha512Bytes...) 93 + b = append(b, blake2bBytes...) 83 94 return b, nil 84 95 } 85 96 ··· 104 115 } 105 116 106 117 b = b[marshaledSizeSHA256:] 107 - return h.sha512.(encoding.BinaryUnmarshaler).UnmarshalBinary(b[:marshaledSizeSHA512]) 118 + if err := h.sha512.(encoding.BinaryUnmarshaler).UnmarshalBinary(b[:marshaledSizeSHA512]); err != nil { 119 + return err 120 + } 121 + 122 + b = b[marshaledSizeSHA512:] 123 + return h.blake2b.(encoding.BinaryUnmarshaler).UnmarshalBinary(b[:marshaledSizeBlake2b]) 108 124 } 109 125 110 126 // Write implements io.Writer ··· 113 129 } 114 130 115 131 // Sums gets the MD5, SHA1, SHA256 and SHA512 checksums of the data 116 - func (h *MultiHasher) Sums() (hashMD5, hashSHA1, hashSHA256, hashSHA512 []byte) { 132 + func (h *MultiHasher) Sums() (hashMD5, hashSHA1, hashSHA256, hashSHA512, hashBlake2b []byte) { 117 133 hashMD5 = h.md5.Sum(nil) 118 134 hashSHA1 = h.sha1.Sum(nil) 119 135 hashSHA256 = h.sha256.Sum(nil) 120 136 hashSHA512 = h.sha512.Sum(nil) 121 - return hashMD5, hashSHA1, hashSHA256, hashSHA512 137 + hashBlake2b = h.blake2b.Sum(nil) 138 + return hashMD5, hashSHA1, hashSHA256, hashSHA512, hashBlake2b 122 139 }
+9 -6
modules/packages/multi_hasher_test.go
··· 12 12 ) 13 13 14 14 const ( 15 - expectedMD5 = "e3bef03c5f3b7f6b3ab3e3053ed71e9c" 16 - expectedSHA1 = "060b3b99f88e96085b4a68e095bc9e3d1d91e1bc" 17 - expectedSHA256 = "6ccce4863b70f258d691f59609d31b4502e1ba5199942d3bc5d35d17a4ce771d" 18 - expectedSHA512 = "7f70e439ba8c52025c1f06cdf6ae443c4b8ed2e90059cdb9bbbf8adf80846f185a24acca9245b128b226d61753b0d7ed46580a69c8999eeff3bc13a4d0bd816c" 15 + expectedMD5 = "e3bef03c5f3b7f6b3ab3e3053ed71e9c" 16 + expectedSHA1 = "060b3b99f88e96085b4a68e095bc9e3d1d91e1bc" 17 + expectedSHA256 = "6ccce4863b70f258d691f59609d31b4502e1ba5199942d3bc5d35d17a4ce771d" 18 + expectedSHA512 = "7f70e439ba8c52025c1f06cdf6ae443c4b8ed2e90059cdb9bbbf8adf80846f185a24acca9245b128b226d61753b0d7ed46580a69c8999eeff3bc13a4d0bd816c" 19 + expectedBlake2b = "b3c3ad15c7e6cca543d651add9427727ffb525120eb23264ee35f16f408a369b599d4404a52d29f642fc0d869f9b55581b60e4e8b9b74997182705d3dcb01edb" 19 20 ) 20 21 21 22 func TestMultiHasherSums(t *testing.T) { ··· 23 24 h := NewMultiHasher() 24 25 h.Write([]byte("gitea")) 25 26 26 - hashMD5, hashSHA1, hashSHA256, hashSHA512 := h.Sums() 27 + hashMD5, hashSHA1, hashSHA256, hashSHA512, hashBlake2b := h.Sums() 27 28 28 29 assert.Equal(t, expectedMD5, hex.EncodeToString(hashMD5)) 29 30 assert.Equal(t, expectedSHA1, hex.EncodeToString(hashSHA1)) 30 31 assert.Equal(t, expectedSHA256, hex.EncodeToString(hashSHA256)) 31 32 assert.Equal(t, expectedSHA512, hex.EncodeToString(hashSHA512)) 33 + assert.Equal(t, expectedBlake2b, hex.EncodeToString(hashBlake2b)) 32 34 }) 33 35 34 36 t.Run("State", func(t *testing.T) { ··· 44 46 45 47 h2.Write([]byte("ea")) 46 48 47 - hashMD5, hashSHA1, hashSHA256, hashSHA512 := h2.Sums() 49 + hashMD5, hashSHA1, hashSHA256, hashSHA512, hashBlake2b := h2.Sums() 48 50 49 51 assert.Equal(t, expectedMD5, hex.EncodeToString(hashMD5)) 50 52 assert.Equal(t, expectedSHA1, hex.EncodeToString(hashSHA1)) 51 53 assert.Equal(t, expectedSHA256, hex.EncodeToString(hashSHA256)) 52 54 assert.Equal(t, expectedSHA512, hex.EncodeToString(hashSHA512)) 55 + assert.Equal(t, expectedBlake2b, hex.EncodeToString(hashBlake2b)) 53 56 }) 54 57 }
+54 -42
modules/packages/rpm/metadata.go
··· 78 78 } 79 79 80 80 type Entry struct { 81 - Name string `json:"name" xml:"name,attr"` 82 - Flags string `json:"flags,omitempty" xml:"flags,attr,omitempty"` 83 - Version string `json:"version,omitempty" xml:"ver,attr,omitempty"` 84 - Epoch string `json:"epoch,omitempty" xml:"epoch,attr,omitempty"` 85 - Release string `json:"release,omitempty" xml:"rel,attr,omitempty"` 81 + Name string `json:"name" xml:"name,attr"` 82 + Flags string `json:"flags,omitempty" xml:"flags,attr,omitempty"` 83 + AltFlags uint32 `json:"alt_flags,omitempty" xml:"alt_flags,attr,omitempty"` 84 + Version string `json:"version,omitempty" xml:"ver,attr,omitempty"` 85 + Epoch string `json:"epoch,omitempty" xml:"epoch,attr,omitempty"` 86 + Release string `json:"release,omitempty" xml:"rel,attr,omitempty"` 86 87 } 87 88 88 89 type File struct { ··· 98 99 } 99 100 100 101 // ParsePackage parses the RPM package file 101 - func ParsePackage(r io.Reader) (*Package, error) { 102 + func ParsePackage(r io.Reader, repoType string) (*Package, error) { 102 103 rpm, err := rpmutils.ReadRpm(r) 103 104 if err != nil { 104 105 return nil, err ··· 138 139 InstalledSize: getUInt64(rpm.Header, rpmutils.SIZE), 139 140 ArchiveSize: getUInt64(rpm.Header, rpmutils.SIG_PAYLOADSIZE), 140 141 141 - Provides: getEntries(rpm.Header, rpmutils.PROVIDENAME, rpmutils.PROVIDEVERSION, rpmutils.PROVIDEFLAGS), 142 - Requires: getEntries(rpm.Header, rpmutils.REQUIRENAME, rpmutils.REQUIREVERSION, rpmutils.REQUIREFLAGS), 143 - Conflicts: getEntries(rpm.Header, rpmutils.CONFLICTNAME, rpmutils.CONFLICTVERSION, rpmutils.CONFLICTFLAGS), 144 - Obsoletes: getEntries(rpm.Header, rpmutils.OBSOLETENAME, rpmutils.OBSOLETEVERSION, rpmutils.OBSOLETEFLAGS), 142 + Provides: getEntries(rpm.Header, rpmutils.PROVIDENAME, rpmutils.PROVIDEVERSION, rpmutils.PROVIDEFLAGS, repoType), 143 + Requires: getEntries(rpm.Header, rpmutils.REQUIRENAME, rpmutils.REQUIREVERSION, rpmutils.REQUIREFLAGS, repoType), 144 + Conflicts: getEntries(rpm.Header, rpmutils.CONFLICTNAME, rpmutils.CONFLICTVERSION, rpmutils.CONFLICTFLAGS, repoType), 145 + Obsoletes: getEntries(rpm.Header, rpmutils.OBSOLETENAME, rpmutils.OBSOLETEVERSION, rpmutils.OBSOLETEFLAGS, repoType), 145 146 Files: getFiles(rpm.Header), 146 147 Changelogs: getChangelogs(rpm.Header), 147 148 }, ··· 170 171 return values[0] 171 172 } 172 173 173 - func getEntries(h *rpmutils.RpmHeader, namesTag, versionsTag, flagsTag int) []*Entry { 174 + func getEntries(h *rpmutils.RpmHeader, namesTag, versionsTag, flagsTag int, repoType string) []*Entry { 174 175 names, err := h.GetStrings(namesTag) 175 176 if err != nil || len(names) == 0 { 176 177 return nil ··· 188 189 } 189 190 190 191 entries := make([]*Entry, 0, len(names)) 191 - for i := range names { 192 - e := &Entry{ 193 - Name: names[i], 194 - } 195 192 196 - flags := flags[i] 197 - if (flags&rpmutils.RPMSENSE_GREATER) != 0 && (flags&rpmutils.RPMSENSE_EQUAL) != 0 { 198 - e.Flags = "GE" 199 - } else if (flags&rpmutils.RPMSENSE_LESS) != 0 && (flags&rpmutils.RPMSENSE_EQUAL) != 0 { 200 - e.Flags = "LE" 201 - } else if (flags & rpmutils.RPMSENSE_GREATER) != 0 { 202 - e.Flags = "GT" 203 - } else if (flags & rpmutils.RPMSENSE_LESS) != 0 { 204 - e.Flags = "LT" 205 - } else if (flags & rpmutils.RPMSENSE_EQUAL) != 0 { 206 - e.Flags = "EQ" 207 - } 208 - 209 - version := versions[i] 210 - if version != "" { 211 - parts := strings.Split(version, "-") 193 + switch repoType { 194 + case "rpm": 195 + for i := range names { 196 + e := &Entry{ 197 + Name: names[i], 198 + } 212 199 213 - versionParts := strings.Split(parts[0], ":") 214 - if len(versionParts) == 2 { 215 - e.Version = versionParts[1] 216 - e.Epoch = versionParts[0] 217 - } else { 218 - e.Version = versionParts[0] 219 - e.Epoch = "0" 200 + flags := flags[i] 201 + if (flags&rpmutils.RPMSENSE_GREATER) != 0 && (flags&rpmutils.RPMSENSE_EQUAL) != 0 { 202 + e.Flags = "GE" 203 + } else if (flags&rpmutils.RPMSENSE_LESS) != 0 && (flags&rpmutils.RPMSENSE_EQUAL) != 0 { 204 + e.Flags = "LE" 205 + } else if (flags & rpmutils.RPMSENSE_GREATER) != 0 { 206 + e.Flags = "GT" 207 + } else if (flags & rpmutils.RPMSENSE_LESS) != 0 { 208 + e.Flags = "LT" 209 + } else if (flags & rpmutils.RPMSENSE_EQUAL) != 0 { 210 + e.Flags = "EQ" 220 211 } 221 212 222 - if len(parts) > 1 { 223 - e.Release = parts[1] 213 + version := versions[i] 214 + if version != "" { 215 + parts := strings.Split(version, "-") 216 + 217 + versionParts := strings.Split(parts[0], ":") 218 + if len(versionParts) == 2 { 219 + e.Version = versionParts[1] 220 + e.Epoch = versionParts[0] 221 + } else { 222 + e.Version = versionParts[0] 223 + e.Epoch = "0" 224 + } 225 + 226 + if len(parts) > 1 { 227 + e.Release = parts[1] 228 + } 224 229 } 230 + entries = append(entries, e) 225 231 } 226 - 227 - entries = append(entries, e) 232 + case "alt": 233 + for i := range names { 234 + e := &Entry{ 235 + AltFlags: uint32(flags[i]), 236 + } 237 + e.Version = versions[i] 238 + entries = append(entries, e) 239 + } 228 240 } 229 241 return entries 230 242 }
+1 -1
modules/packages/rpm/metadata_test.go
··· 48 48 zr, err := gzip.NewReader(bytes.NewReader(rpmPackageContent)) 49 49 require.NoError(t, err) 50 50 51 - p, err := ParsePackage(zr) 51 + p, err := ParsePackage(zr, "rpm") 52 52 assert.NotNil(t, p) 53 53 require.NoError(t, err) 54 54
+2
modules/setting/packages.go
··· 42 42 LimitSizePub int64 43 43 LimitSizePyPI int64 44 44 LimitSizeRpm int64 45 + LimitSizeAlt int64 45 46 LimitSizeRubyGems int64 46 47 LimitSizeSwift int64 47 48 LimitSizeVagrant int64 ··· 106 107 Packages.LimitSizeSwift = mustBytes(sec, "LIMIT_SIZE_SWIFT") 107 108 Packages.LimitSizeVagrant = mustBytes(sec, "LIMIT_SIZE_VAGRANT") 108 109 Packages.DefaultRPMSignEnabled = sec.Key("DEFAULT_RPM_SIGN_ENABLED").MustBool(false) 110 + Packages.LimitSizeAlt = mustBytes(sec, "LIMIT_SIZE_ALT") 109 111 return nil 110 112 } 111 113
+7
options/locale/locale_en-US.ini
··· 3724 3724 rpm.repository = Repository info 3725 3725 rpm.repository.architectures = Architectures 3726 3726 rpm.repository.multiple_groups = This package is available in multiple groups. 3727 + alt.registry = Setup this registry from the command line: 3728 + alt.registry.install = To install the package, run the following command: 3729 + alt.install = Install package 3730 + alt.setup = Add a repository to the list of connected repositories (choose the necessary architecture instead of '_arch_'): 3731 + alt.repository = Repository Info 3732 + alt.repository.architectures = Architectures 3733 + alt.repository.multiple_groups = This package is available in multiple groups. 3727 3734 rubygems.install = To install the package using gem, run the following command: 3728 3735 rubygems.install2 = or add it to the Gemfile: 3729 3736 rubygems.dependencies.runtime = Runtime dependencies
+1
public/assets/img/svg/gitea-alt.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" id="gitea-alt__Layer_1" x="0" y="0" version="1.1" viewBox="0 0 64 64" class="svg gitea-alt" width="16" height="16" aria-hidden="true"><style>.gitea-alt__st1{fill:#000}.gitea-alt__st2{fill:#fff}</style><path d="m59.43 53.244 4.157-5.973-.139-1.732c-.29-1.508-1.392-2.143-2.12-2.564l-.019-.01q-.123-.071-.25-.147c-.937-.563-1.377-1.073-1.423-1.183l-.047-.145c-.323-.86-.052-2.231.21-3.558l.005-.027c.118-.598.23-1.163.302-1.732.901-6.116.556-9.436-.558-14.559l-.167-.811-.053-.258c-.494-2.42-.921-4.512-1.737-6.776-.172-.474-.408-.904-.62-1.291a9 9 0 0 1-.355-.697l-.15-.279-.012-.019c-.063-.1-.176-.568-.214-.723l-.013-.056c-.1-.473-.133-.992-.166-1.496-.05-.77-.106-1.643-.39-2.516-.37-1.192-1.202-2.348-2.227-3.092-.515-.374-1.068-.54-1.514-.672l-.066-.02a5 5 0 0 0-.317-.08c-.242-.055-.471-.108-.947-.401l-.54-.337c-.792-.497-1.777-1.114-2.729-1.61-.966-.623-2.386-.761-3.396.33l-1.05 1.135 1.196.98c1.86 1.525 2.535 3.226 2.634 3.497q.022.113.046.22l.034.165c-1.78 1.243-2.605 2.63-2.986 3.59-.132.253-.317.65-.595 1.246l-.008.017c-.308.662-.72 1.549-1.186 2.497-1.157-.394-2.237-.586-3.295-.586a7.75 7.75 0 0 0-3.664.893c-2.277 1.095-4 2.934-5.14 5.427H9.892v10.926c-.595-.116-1.2-.16-1.78-.176-.871-.024-1.902-.032-2.937.085-.735.083-2.972.337-4.045 2.153-.29.496-.515 1.095-.71 1.883L0 36.47l1.205.127a3.9 3.9 0 0 0-.94 1.541c-.144.43-.216.9-.216 1.397 0 1.174.465 2.172 1.43 3.058l.044.039c.974.818 2.155 1.216 3.61 1.216.758 0 1.45-.086 2.09-.26h2.67V64h44.109v-4.68l.913-1.337 1.017 1.795H64z" style="fill:#fdc811"/><path d="M5.637 35.127q-.092.17-.18.377L1.91 35.13c.153-.62.324-1.098.545-1.477.58-.982 1.84-1.284 2.894-1.404.898-.101 1.818-.1 2.722-.075 1.262.032 2.697.19 3.534 1.258.494.633.732 1.537.732 2.33v4.221q-.001.278.005.513c.01.526.172 1.106.438 1.558H9.336a1.8 1.8 0 0 1-.395-1.147q-.265.24-.515.422c-.98.73-2.075.986-3.292.986-1.15 0-1.974-.31-2.625-.857-.6-.55-.927-1.151-.927-1.923 0-.33.046-.637.137-.912.268-.831.972-1.41 1.773-1.706a7 7 0 0 1 1.194-.314q.812-.14 1.616-.313c.63-.136 1.263-.297 1.884-.49.263-.085.498-.166.498-.191q.001-.261-.026-.452c-.097-.623-.83-.803-1.377-.803q-.321 0-.633.077c-.448.113-.777.282-1.01.696m3.047 2.334q-.299.123-.595.226c-.372.13-.768.256-1.162.345-.367.082-.74.153-1.072.343-.318.183-.55.447-.55.853 0 .67.643 1.013 1.235 1.013.72 0 1.566-.346 1.918-1.013.174-.325.226-.721.226-1.167zm5.693-8.732h3.721v13.325h-3.721zm10.339.001v3.604h2.076v2.796h-2.076v3.377c0 .396.05.721.155.878.069.15.295.305.6.305.326 0 .702-.084 1.166-.24l.308 2.555c-.922.157-1.78.31-2.622.31-.925 0-1.63-.153-2.009-.378a2.5 2.5 0 0 1-.993-1.079c-.258-.482-.326-1.252-.326-2.351V35.13h-1.373v-2.796h1.373v-1.748zM14.535 44.961h3.721v13.36h-3.721zm5.758 0h3.704v2.486h-3.704zm0 3.654h3.704v9.707h-3.704zm5.741 0h3.501v1.596q.357-.463.734-.803c.8-.717 1.685-.965 2.745-.965.976 0 1.834.327 2.384.925.6.55.926 1.546.926 2.798v6.156h-3.79v-5.334c0-.366.007-.775-.17-1.103-.302-.563-1.055-.727-1.614-.477-.449.203-.758.671-.877 1.133q-.12.458-.119 1.152v4.629h-3.72zm22.152 9.707h-3.429v-1.527q-.302.332-.593.599c-.587.532-1.222.9-2.004 1.059-.295.06-.616.09-.953.09-1.027 0-1.782-.323-2.4-.923-.547-.619-.857-1.525-.857-2.833v-6.172h3.704v5.401c0 .413.09.913.395 1.217.566.57 1.464.477 2.007-.067.43-.432.445-1.285.445-1.85v-4.7h3.685zm.894-9.707h4.477l1.473 2.676 1.75-2.676h4.097l-3.223 4.63 3.55 5.077h-4.423l-1.75-3.09-2.111 3.09h-4.012l3.551-5.076z" class="gitea-alt__st1"/><path d="M54.878 37.246c-.442-.983-.93-1.561-1.544-2.602-1.154-1.64-.923-4.588-.383-6.514a13.8 13.8 0 0 1 1.455-3.666c.357-.62-.216-.855-.498-.432-.63.946-1.01 1.82-1.355 2.78-.56 1.564-.853 3.389-.773 5.028.037.983.188 2.368.108 2.847s-.401.573-.655.401c-2.08-1.66-2.505-5.032-2.424-7.479 0-2.524.96-5.224 1.303-7.749.195-.792-.17-3.048-.115-4.395.023-.613.016-1.226 0-1.839-.026-1.006.034-1.961.115-2.961.084-.885.735-1.12 1.314-.735.27.196.349.446.714.676 1.08.58 1.91-.385 1.697-1.387-.173-.829-.713-1.85-1.617-1.502-.54.19-.463.983-.908 1.328-.618.368-1.274.096-1.83-.29-.398-.275-.586-.562-1.103-.324-.653.3-1.285.936-1.735 1.486-.432.527-.754 1.136-1.002 1.769-.71 1.717-1.541 3.199-2.348 4.878 1.116.693 2.08 1.484 3.064 2.37 1.617 1.447 2.505 3.74 2.003 5.898-.173.945-.79 1.735-1.136 2.583-.091.544-.136 1.287-.345 2.132-.136.55-.348 1.118-.676 1.4-.187.16-.66.237-.536-.131.124-.369.254-1.476.01-2.556-.245-1.08-.902-2.148-1.733-3.371-.57-.838-.588-1.393-.588-1.393s.35-.096.419-.535c.038-.249.29-.786.884-.828.538 0 .537.472 1.12.659.677.216.923-.08 1.042-.407.288-.714-.177-2.158-.716-2.582-.325-.305-.875-.462-1.294-.343-.326.093-.638.428-.572.843s.084.632-.207.459c-.16-.095-.308-.307-.414-.459a3.6 3.6 0 0 0-1.024-1.026c-.576-.397-1.07-.573-1.748-.632s-1.206.016-1.796.25c-1.503.597-2.466 1.889-2.872 3.317-.616 2.08.206 4.3 0 7.11-.192 2.61-.433 3.016-.809 4.237-.343 1.114-.583 1.541-.92 1.955-.27.328-.527.358-.748.056-.216-.296-.192-2.025-.192-2.574 0-.996-.066-1.706-.15-2.185-.085-.48-.522-.451-.595-.003a11.4 11.4 0 0 0-.141 1.746c0 1.59.264 3.536.74 5.117.696 2.2 1.313 3.814 1.313 5.185-.04 1.293-1.447 1.985-1.16 2.025 1.371.02 3.009.075 4.494.02.423 0 .366-.076.792-.504.42-.5.153-1.041.078-1.482-.37-2.7.96-5.86 1.75-8.31.755-2.255 1.467-4.606 1.967-7.383a.176.176 0 0 1 .234-.137c.072.024.113.12.133.197.329 1.275.751 3.18 1.176 4.837.308 1.197.636 2.562 1.501 3.49.85-.08 1.563.713 1.677 1.56.118.83-.114 1.677.33 2.526.463.788.984 1.486 1.293 2.158.423.847.792 1.83.52 3.087 1.484 0 2.505-.155 3.64-.81 2.142-1.274 3.182-3.836 1.776-6.884" class="gitea-alt__st2"/><path d="M47.42 43.048c-.583-.96-1.043-2.68-1.043-3.758 0-.616-.443-.406-.772-.406-.347 0-.695.02-.965-.093-.211-.098-.19-.234.04-.272.693-.174 1.1.272 1.58-.675.153-.31.194-.656.098-.965-.114-.364-.5-.557-.943-.578-.31 0-.869.076-.909.462-.022.214.096.504.404.542 0 .134.04.307-.112.366-.235.077-.714-.116-.64 0 .326.462.173 1.003.04 1.485-.151.366.042.925.308 1.462.25.525.56 1.023.656 1.349.173.582.036 1.196.154 1.68.038.25.29.346.346.636.078.193.368.153.444.153.348-.073.424-.443.68-.443.44-.057 1.037-.057 1.326.29.155 0 .25 0 .25-.095 0-.214-.597-.56-.943-1.14" class="gitea-alt__st2"/><path d="M46.106 19.803c-.325-.305-.875-.462-1.294-.343-.326.093-.638.428-.572.843s.084.632-.207.459c-.16-.095-.308-.307-.414-.459a3.6 3.6 0 0 0-1.024-1.026c-.576-.397-1.07-.573-1.748-.632l-.06-.005c1.272.352 1.83 1.13 2.166 2.157.28.704.166 1.572.328 2.511q.027.15.083.26l-.007-.072s.35-.096.419-.535c.038-.249.29-.786.884-.828.538 0 .537.472 1.12.658.677.217.923-.079 1.042-.406.288-.714-.177-2.158-.716-2.582m7.731-10.584c-.173-.829-.713-1.85-1.617-1.502-.54.19-.463.983-.908 1.328-.618.368-1.274.096-1.83-.29-.398-.275-.586-.562-1.103-.324a3.4 3.4 0 0 0-.633.392.1.1 0 0 0 .04.045c.42.364 1.126.243 1.567 1.129.388.767.587 1.753.647 2.52v-.008c.019-.613.061-1.22.112-1.844.084-.885.735-1.12 1.314-.735.27.196.349.446.714.676 1.08.58 1.91-.385 1.697-1.387M49.849 5.3c-1.05-1.597-2.819-2.917-3.918-3.438a16.1 16.1 0 0 1 3.294 3.692c.37.504.797.077.624-.254m-4.152 21.107c.293 1.343.798 2.52.293 4.324.627-.987.68-2.65.255-4.383-.078-.548-.684-.372-.548.059" style="fill:#ffc629"/><path d="M60.308 44.067c-.762-.459-1.871-1.281-2.075-2.042l-.007-.022c-.47-1.247-.158-2.826.144-4.353.116-.587.225-1.14.294-1.678.87-5.899.535-9.106-.542-14.063q-.114-.546-.22-1.07c-.484-2.368-.902-4.413-1.68-6.574-.134-.367-.332-.73-.524-1.08a9 9 0 0 1-.424-.839l-.023-.042c-.21-.308-.325-.782-.426-1.2l-.02-.082c-.123-.58-.161-1.16-.197-1.72-.047-.72-.095-1.462-.327-2.178a4.76 4.76 0 0 0-1.693-2.346c-.333-.241-.747-.356-1.142-.475-.329-.098-.749-.105-1.61-.637-.863-.532-2.1-1.339-3.247-1.93 0 0-.926-.65-1.585.063 2.45 2.008 3.121 4.246 3.121 4.246l.061.297c.184.873.235 1.238-.354 1.524l-.083.053c-1.604 1.083-2.307 2.274-2.614 3.088-.11.196-.32.648-.607 1.266-.44.945-1.095 2.35-1.805 3.726-1.46-.653-2.77-.971-3.998-.971a6.35 6.35 0 0 0-3.016.74c-4.637 2.22-5.79 7.617-5.939 11.754-.08 2.157.313 4.534.694 6.832.435 2.624.885 5.337.6 7.634-.26 1.71-1.21 2.694-2.99 3.096q-.383.008-.52.217a.38.38 0 0 0-.018.37l.063.144h10.623c.473 0 .685-.223.93-.48a4 4 0 0 1 .381-.365l.015-.013c.471-.457.363-1.01.237-1.652-.077-.395-.164-.843-.164-1.393 0-1.893.655-3.765 1.288-5.576l.24-.688c.757-2.189 1.293-3.991 1.687-5.69l.058.269c.159.738.34 1.574.551 2.377.236.89.61 2.135 1.2 3.04q-.074.037-.144.076c-.105.055-.204.108-.292.145-.259.097-.52.205-.774.31a28 28 0 0 1-.577.233.363.363 0 0 0-.222.47l.006.017a.45.45 0 0 0 .266.246c.089.045.225.116.253.153l.024.033c.35.404.483.596.483 1.018 0 .243-.053.41-.11.585-.075.234-.153.476-.078.83.08.426.288.846.508 1.29.267.54.543 1.1.549 1.634-.064.438-.289.585-.598.788-.187.123-.4.262-.581.482l-.026.038a1.37 1.37 0 0 0-.205.733q-.001.073-.01.158c-.018.217-.038.463.115.629.05.055.14.12.283.128a1 1 0 0 0 .085.008c.275 0 .433-.228.548-.395.028-.04.066-.094.095-.128.071.046.127.133.2.255.111.183.262.435.583.435h16.992c-.189-.98-.947-1.29-1.71-1.748M45.931 1.862c1.1.521 2.869 1.841 3.918 3.438.173.331-.255.758-.624.254a16.1 16.1 0 0 0-3.294-3.692m2.18 42.421c-.288-.347-.886-.347-1.326-.29-.255 0-.331.37-.68.443-.075 0-.365.04-.443-.153-.057-.29-.308-.387-.346-.636-.118-.484.02-1.098-.154-1.68-.097-.326-.407-.824-.656-1.349-.266-.537-.46-1.096-.307-1.462.132-.482.285-1.023-.041-1.485-.074-.116.405.077.64 0 .152-.059.112-.232.112-.366-.308-.038-.426-.328-.404-.542.04-.386.598-.462.91-.462.442.021.828.214.942.578.096.309.055.655-.097.965-.48.947-.888.501-1.58.675-.232.038-.252.174-.04.272.269.113.617.093.964.093.33 0 .772-.21.772.406 0 1.078.46 2.798 1.042 3.758.346.58.942.926.942 1.14 0 .095-.094.095-.25.095m4.99-.153c-1.134.655-2.155.81-3.639.81.272-1.258-.097-2.24-.52-3.087-.31-.672-.83-1.37-1.294-2.158-.443-.85-.21-1.697-.329-2.525-.114-.848-.827-1.64-1.677-1.562-.865-.927-1.193-2.292-1.501-3.489-.425-1.658-.847-3.562-1.176-4.837-.02-.077-.06-.173-.133-.197a.176.176 0 0 0-.234.137c-.5 2.777-1.212 5.128-1.968 7.383-.788 2.45-2.12 5.61-1.75 8.31.076.44.342.981-.077 1.482-.426.428-.37.504-.792.504-1.485.055-3.123 0-4.494-.02-.287-.04 1.12-.732 1.16-2.025 0-1.37-.617-2.986-1.312-5.185-.477-1.581-.741-3.527-.741-5.117 0-.66.068-1.298.141-1.746s.51-.476.594.003c.085.48.15 1.189.15 2.185 0 .55-.023 2.278.193 2.574.221.302.478.272.747-.056.338-.414.578-.841.921-1.955.376-1.221.617-1.626.809-4.236.206-2.81-.616-5.03 0-7.111.406-1.428 1.369-2.72 2.872-3.316.59-.235 1.12-.31 1.796-.251.677.06 1.172.235 1.748.632a3.6 3.6 0 0 1 1.024 1.026c.106.152.254.364.414.459.291.173.273-.044.207-.459s.246-.75.572-.843c.419-.12.969.038 1.294.343.54.424 1.004 1.868.716 2.582-.12.327-.365.623-1.041.406-.584-.186-.583-.658-1.121-.658-.594.042-.846.58-.884.828-.069.439-.42.535-.42.535s.02.555.589 1.393c.83 1.223 1.488 2.29 1.732 3.37.245 1.081.115 2.188-.01 2.557-.123.368.35.291.537.131.328-.282.54-.85.676-1.4.209-.845.254-1.588.345-2.132.347-.848.963-1.638 1.136-2.583.502-2.158-.386-4.451-2.003-5.899-.985-.885-1.948-1.676-3.064-2.369.807-1.679 1.638-3.161 2.348-4.878.248-.633.57-1.242 1.002-1.77.45-.55 1.082-1.185 1.735-1.485.517-.238.705.049 1.102.324.557.386 1.213.658 1.831.29.445-.345.368-1.138.908-1.328.904-.348 1.444.673 1.617 1.502.214 1.002-.617 1.966-1.697 1.387-.365-.23-.444-.48-.714-.676-.58-.385-1.23-.15-1.314.735-.081 1-.141 1.955-.115 2.961.016.613.023 1.226 0 1.84-.055 1.346.31 3.602.115 4.394-.342 2.525-1.303 5.225-1.303 7.749-.081 2.447.344 5.82 2.424 7.479.254.172.575.078.655-.401s-.07-1.864-.108-2.847c-.08-1.64.214-3.464.773-5.028.345-.96.725-1.834 1.355-2.78.282-.423.855-.188.498.432a13.8 13.8 0 0 0-1.455 3.666c-.54 1.926-.771 4.875.383 6.514.615 1.04 1.102 1.62 1.544 2.602 1.406 3.048.366 5.61-1.776 6.884m-7.404-17.723c-.136-.431.47-.607.548-.06.425 1.733.372 3.397-.255 4.384.505-1.804 0-2.981-.293-4.324" class="gitea-alt__st1"/><path d="M45.344 36.94c.254 0 .312.178.39.12.12-.12-.136-.37-.295-.37-.252 0-.426.115-.426.25 0 .12.174 0 .331 0" class="gitea-alt__st1"/></svg>
+260
routers/api/packages/alt/alt.go
··· 1 + // Copyright 2024 The Gitea Authors. All rights reserved. 2 + // SPDX-License-Identifier: MIT 3 + 4 + package alt 5 + 6 + import ( 7 + stdctx "context" 8 + "errors" 9 + "fmt" 10 + "io" 11 + "net/http" 12 + "strings" 13 + 14 + "code.gitea.io/gitea/models/db" 15 + packages_model "code.gitea.io/gitea/models/packages" 16 + "code.gitea.io/gitea/modules/json" 17 + packages_module "code.gitea.io/gitea/modules/packages" 18 + rpm_module "code.gitea.io/gitea/modules/packages/rpm" 19 + "code.gitea.io/gitea/modules/setting" 20 + "code.gitea.io/gitea/modules/util" 21 + "code.gitea.io/gitea/routers/api/packages/helper" 22 + "code.gitea.io/gitea/services/context" 23 + notify_service "code.gitea.io/gitea/services/notify" 24 + packages_service "code.gitea.io/gitea/services/packages" 25 + alt_service "code.gitea.io/gitea/services/packages/alt" 26 + ) 27 + 28 + func apiError(ctx *context.Context, status int, obj any) { 29 + helper.LogAndProcessError(ctx, status, obj, func(message string) { 30 + ctx.PlainText(status, message) 31 + }) 32 + } 33 + 34 + func GetRepositoryConfig(ctx *context.Context) { 35 + group := ctx.Params("group") 36 + 37 + var groupParts []string 38 + if group != "" { 39 + groupParts = strings.Split(group, "/") 40 + } 41 + 42 + url := fmt.Sprintf("%sapi/packages/%s/alt", setting.AppURL, ctx.Package.Owner.Name) 43 + 44 + ctx.PlainText(http.StatusOK, `[gitea-`+strings.Join(append([]string{ctx.Package.Owner.LowerName}, groupParts...), "-")+`] 45 + name=`+strings.Join(append([]string{ctx.Package.Owner.Name, setting.AppName}, groupParts...), " - ")+` 46 + baseurl=`+strings.Join(append([]string{url}, groupParts...), "/")+` 47 + enabled=1`) 48 + } 49 + 50 + // Gets a pre-generated repository metadata file 51 + func GetRepositoryFile(ctx *context.Context, arch string) { 52 + pv, err := alt_service.GetOrCreateRepositoryVersion(ctx, ctx.Package.Owner.ID) 53 + if err != nil { 54 + apiError(ctx, http.StatusInternalServerError, err) 55 + return 56 + } 57 + 58 + s, u, pf, err := packages_service.GetFileStreamByPackageVersion( 59 + ctx, 60 + pv, 61 + &packages_service.PackageFileInfo{ 62 + Filename: ctx.Params("filename"), 63 + CompositeKey: arch + "__" + ctx.Params("group"), 64 + }, 65 + ) 66 + if err != nil { 67 + if errors.Is(err, util.ErrNotExist) { 68 + apiError(ctx, http.StatusNotFound, err) 69 + } else { 70 + apiError(ctx, http.StatusInternalServerError, err) 71 + } 72 + return 73 + } 74 + 75 + helper.ServePackageFile(ctx, s, u, pf) 76 + } 77 + 78 + func UploadPackageFile(ctx *context.Context) { 79 + upload, needToClose, err := ctx.UploadStream() 80 + if err != nil { 81 + apiError(ctx, http.StatusInternalServerError, err) 82 + return 83 + } 84 + if needToClose { 85 + defer upload.Close() 86 + } 87 + 88 + buf, err := packages_module.CreateHashedBufferFromReader(upload) 89 + if err != nil { 90 + apiError(ctx, http.StatusInternalServerError, err) 91 + return 92 + } 93 + defer buf.Close() 94 + 95 + pck, err := rpm_module.ParsePackage(buf, "alt") 96 + if err != nil { 97 + if errors.Is(err, util.ErrInvalidArgument) { 98 + apiError(ctx, http.StatusBadRequest, err) 99 + } else { 100 + apiError(ctx, http.StatusInternalServerError, err) 101 + } 102 + return 103 + } 104 + if _, err := buf.Seek(0, io.SeekStart); err != nil { 105 + apiError(ctx, http.StatusInternalServerError, err) 106 + return 107 + } 108 + 109 + fileMetadataRaw, err := json.Marshal(pck.FileMetadata) 110 + if err != nil { 111 + apiError(ctx, http.StatusInternalServerError, err) 112 + return 113 + } 114 + group := ctx.Params("group") 115 + _, _, err = packages_service.CreatePackageOrAddFileToExisting( 116 + ctx, 117 + &packages_service.PackageCreationInfo{ 118 + PackageInfo: packages_service.PackageInfo{ 119 + Owner: ctx.Package.Owner, 120 + PackageType: packages_model.TypeAlt, 121 + Name: pck.Name, 122 + Version: pck.Version, 123 + }, 124 + Creator: ctx.Doer, 125 + Metadata: pck.VersionMetadata, 126 + }, 127 + &packages_service.PackageFileCreationInfo{ 128 + PackageFileInfo: packages_service.PackageFileInfo{ 129 + Filename: fmt.Sprintf("%s-%s.%s.rpm", pck.Name, pck.Version, pck.FileMetadata.Architecture), 130 + CompositeKey: group, 131 + }, 132 + Creator: ctx.Doer, 133 + Data: buf, 134 + IsLead: true, 135 + Properties: map[string]string{ 136 + rpm_module.PropertyGroup: group, 137 + rpm_module.PropertyArchitecture: pck.FileMetadata.Architecture, 138 + rpm_module.PropertyMetadata: string(fileMetadataRaw), 139 + }, 140 + }, 141 + ) 142 + if err != nil { 143 + switch err { 144 + case packages_model.ErrDuplicatePackageVersion, packages_model.ErrDuplicatePackageFile: 145 + apiError(ctx, http.StatusConflict, err) 146 + case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize: 147 + apiError(ctx, http.StatusForbidden, err) 148 + default: 149 + apiError(ctx, http.StatusInternalServerError, err) 150 + } 151 + return 152 + } 153 + 154 + if err := alt_service.BuildSpecificRepositoryFiles(ctx, ctx.Package.Owner.ID, group); err != nil { 155 + apiError(ctx, http.StatusInternalServerError, err) 156 + return 157 + } 158 + 159 + ctx.Status(http.StatusCreated) 160 + } 161 + 162 + func DownloadPackageFile(ctx *context.Context) { 163 + name := ctx.Params("name") 164 + version := ctx.Params("version") 165 + 166 + s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion( 167 + ctx, 168 + &packages_service.PackageInfo{ 169 + Owner: ctx.Package.Owner, 170 + PackageType: packages_model.TypeAlt, 171 + Name: name, 172 + Version: version, 173 + }, 174 + &packages_service.PackageFileInfo{ 175 + Filename: fmt.Sprintf("%s-%s.%s.rpm", name, version, ctx.Params("architecture")), 176 + CompositeKey: ctx.Params("group"), 177 + }, 178 + ) 179 + if err != nil { 180 + if errors.Is(err, util.ErrNotExist) { 181 + apiError(ctx, http.StatusNotFound, err) 182 + } else { 183 + apiError(ctx, http.StatusInternalServerError, err) 184 + } 185 + return 186 + } 187 + 188 + helper.ServePackageFile(ctx, s, u, pf) 189 + } 190 + 191 + func DeletePackageFile(webctx *context.Context) { 192 + group := webctx.Params("group") 193 + name := webctx.Params("name") 194 + version := webctx.Params("version") 195 + architecture := webctx.Params("architecture") 196 + 197 + var pd *packages_model.PackageDescriptor 198 + 199 + err := db.WithTx(webctx, func(ctx stdctx.Context) error { 200 + pv, err := packages_model.GetVersionByNameAndVersion(ctx, 201 + webctx.Package.Owner.ID, 202 + packages_model.TypeAlt, 203 + name, 204 + version, 205 + ) 206 + if err != nil { 207 + return err 208 + } 209 + 210 + pf, err := packages_model.GetFileForVersionByName( 211 + ctx, 212 + pv.ID, 213 + fmt.Sprintf("%s-%s.%s.rpm", name, version, architecture), 214 + group, 215 + ) 216 + if err != nil { 217 + return err 218 + } 219 + 220 + if err := packages_service.DeletePackageFile(ctx, pf); err != nil { 221 + return err 222 + } 223 + 224 + has, err := packages_model.HasVersionFileReferences(ctx, pv.ID) 225 + if err != nil { 226 + return err 227 + } 228 + if !has { 229 + pd, err = packages_model.GetPackageDescriptor(ctx, pv) 230 + if err != nil { 231 + return err 232 + } 233 + 234 + if err := packages_service.DeletePackageVersionAndReferences(ctx, pv); err != nil { 235 + return err 236 + } 237 + } 238 + 239 + return nil 240 + }) 241 + if err != nil { 242 + if errors.Is(err, util.ErrNotExist) { 243 + apiError(webctx, http.StatusNotFound, err) 244 + } else { 245 + apiError(webctx, http.StatusInternalServerError, err) 246 + } 247 + return 248 + } 249 + 250 + if pd != nil { 251 + notify_service.PackageDelete(webctx, webctx.Doer, pd) 252 + } 253 + 254 + if err := alt_service.BuildSpecificRepositoryFiles(webctx, webctx.Package.Owner.ID, group); err != nil { 255 + apiError(webctx, http.StatusInternalServerError, err) 256 + return 257 + } 258 + 259 + webctx.Status(http.StatusNoContent) 260 + }
+68
routers/api/packages/api.go
··· 15 15 "code.gitea.io/gitea/modules/setting" 16 16 "code.gitea.io/gitea/modules/web" 17 17 "code.gitea.io/gitea/routers/api/packages/alpine" 18 + "code.gitea.io/gitea/routers/api/packages/alt" 18 19 "code.gitea.io/gitea/routers/api/packages/arch" 19 20 "code.gitea.io/gitea/routers/api/packages/cargo" 20 21 "code.gitea.io/gitea/routers/api/packages/chef" ··· 617 618 return 618 619 } 619 620 rpm.DeletePackageFile(ctx) 621 + } 622 + return 623 + } 624 + 625 + ctx.Status(http.StatusNotFound) 626 + }) 627 + }, reqPackageAccess(perm.AccessModeRead)) 628 + r.Group("/alt", func() { 629 + var ( 630 + baseURLPattern = regexp.MustCompile(`\A(.*?)\.repo\z`) 631 + uploadPattern = regexp.MustCompile(`\A(.*?)/upload\z`) 632 + baseRepoPattern = regexp.MustCompile(`(\S+)\.repo/(\S+)\/base/(\S+)`) 633 + rpmsRepoPattern = regexp.MustCompile(`(\S+)\.repo/(\S+)\.(\S+)\/([a-zA-Z0-9_-]+)-([\d.]+-[a-zA-Z0-9_-]+)\.(\S+)\.rpm`) 634 + ) 635 + 636 + r.Methods("HEAD,GET,PUT,DELETE", "*", func(ctx *context.Context) { 637 + path := ctx.Params("*") 638 + isGetHead := ctx.Req.Method == "HEAD" || ctx.Req.Method == "GET" 639 + isPut := ctx.Req.Method == "PUT" 640 + isDelete := ctx.Req.Method == "DELETE" 641 + 642 + m := baseURLPattern.FindStringSubmatch(path) 643 + if len(m) == 2 && isGetHead { 644 + ctx.SetParams("group", strings.Trim(m[1], "/")) 645 + alt.GetRepositoryConfig(ctx) 646 + return 647 + } 648 + 649 + m = baseRepoPattern.FindStringSubmatch(path) 650 + if len(m) == 4 { 651 + if strings.Trim(m[1], "/") != "alt" { 652 + ctx.SetParams("group", strings.Trim(m[1], "/")) 653 + } 654 + ctx.SetParams("filename", m[3]) 655 + if isGetHead { 656 + alt.GetRepositoryFile(ctx, m[2]) 657 + } 658 + return 659 + } 660 + 661 + m = uploadPattern.FindStringSubmatch(path) 662 + if len(m) == 2 && isPut { 663 + reqPackageAccess(perm.AccessModeWrite)(ctx) 664 + if ctx.Written() { 665 + return 666 + } 667 + ctx.SetParams("group", strings.Trim(m[1], "/")) 668 + alt.UploadPackageFile(ctx) 669 + return 670 + } 671 + 672 + m = rpmsRepoPattern.FindStringSubmatch(path) 673 + if len(m) == 7 && (isGetHead || isDelete) { 674 + if strings.Trim(m[1], "/") != "alt" { 675 + ctx.SetParams("group", strings.Trim(m[1], "/")) 676 + } 677 + ctx.SetParams("name", m[4]) 678 + ctx.SetParams("version", m[5]) 679 + ctx.SetParams("architecture", m[6]) 680 + if isGetHead { 681 + alt.DownloadPackageFile(ctx) 682 + } else { 683 + reqPackageAccess(perm.AccessModeWrite)(ctx) 684 + if ctx.Written() { 685 + return 686 + } 687 + alt.DeletePackageFile(ctx) 620 688 } 621 689 return 622 690 }
+1 -1
routers/api/packages/container/blob.go
··· 193 193 } 194 194 195 195 func digestFromHashSummer(h packages_module.HashSummer) string { 196 - _, _, hashSHA256, _ := h.Sums() 196 + _, _, hashSHA256, _, _ := h.Sums() 197 197 return "sha256:" + hex.EncodeToString(hashSHA256) 198 198 } 199 199
+1 -1
routers/api/packages/pypi/pypi.go
··· 121 121 } 122 122 defer buf.Close() 123 123 124 - _, _, hashSHA256, _ := buf.Sums() 124 + _, _, hashSHA256, _, _ := buf.Sums() 125 125 126 126 if !strings.EqualFold(ctx.Req.FormValue("sha256_digest"), hex.EncodeToString(hashSHA256)) { 127 127 apiError(ctx, http.StatusBadRequest, "hash mismatch")
+1 -1
routers/api/packages/rpm/rpm.go
··· 149 149 buf = signedBuf 150 150 } 151 151 152 - pck, err := rpm_module.ParsePackage(buf) 152 + pck, err := rpm_module.ParsePackage(buf, "rpm") 153 153 if err != nil { 154 154 if errors.Is(err, util.ErrInvalidArgument) { 155 155 apiError(ctx, http.StatusBadRequest, err)
+1 -1
routers/web/user/package.go
··· 235 235 ctx.Data["Distributions"] = util.Sorted(distributions.Values()) 236 236 ctx.Data["Components"] = util.Sorted(components.Values()) 237 237 ctx.Data["Architectures"] = util.Sorted(architectures.Values()) 238 - case packages_model.TypeRpm: 238 + case packages_model.TypeRpm, packages_model.TypeAlt: 239 239 groups := make(container.Set[string]) 240 240 architectures := make(container.Set[string]) 241 241
+1 -1
services/forms/package_form.go
··· 15 15 type PackageCleanupRuleForm struct { 16 16 ID int64 17 17 Enabled bool 18 - Type string `binding:"Required;In(alpine,arch,cargo,chef,composer,conan,conda,container,cran,debian,generic,go,helm,maven,npm,nuget,pub,pypi,rpm,rubygems,swift,vagrant)"` 18 + Type string `binding:"Required;In(alpine,arch,cargo,chef,composer,conan,conda,container,cran,debian,generic,go,helm,maven,npm,nuget,pub,pypi,rpm,alt,rubygems,swift,vagrant)"` 19 19 KeepCount int `binding:"In(0,1,5,10,25,50,100)"` 20 20 KeepPattern string `binding:"RegexPattern"` 21 21 RemoveDays int `binding:"In(0,7,14,30,60,90,180)"`
+921
services/packages/alt/reposirory.go
··· 1 + // Copyright 2024 The Gitea Authors. All rights reserved. 2 + // SPDX-License-Identifier: MIT 3 + 4 + package alt 5 + 6 + import ( 7 + "bytes" 8 + "context" 9 + "crypto/sha256" 10 + "encoding/binary" 11 + "encoding/hex" 12 + "fmt" 13 + "io" 14 + "regexp" 15 + "slices" 16 + "strconv" 17 + "strings" 18 + "time" 19 + 20 + packages_model "code.gitea.io/gitea/models/packages" 21 + alt_model "code.gitea.io/gitea/models/packages/alt" 22 + user_model "code.gitea.io/gitea/models/user" 23 + "code.gitea.io/gitea/modules/json" 24 + packages_module "code.gitea.io/gitea/modules/packages" 25 + rpm_module "code.gitea.io/gitea/modules/packages/rpm" 26 + "code.gitea.io/gitea/modules/setting" 27 + packages_service "code.gitea.io/gitea/services/packages" 28 + 29 + "github.com/ulikunitz/xz" 30 + ) 31 + 32 + // GetOrCreateRepositoryVersion gets or creates the internal repository package 33 + // The RPM registry needs multiple metadata files which are stored in this package. 34 + func GetOrCreateRepositoryVersion(ctx context.Context, ownerID int64) (*packages_model.PackageVersion, error) { 35 + return packages_service.GetOrCreateInternalPackageVersion(ctx, ownerID, packages_model.TypeAlt, rpm_module.RepositoryPackage, rpm_module.RepositoryVersion) 36 + } 37 + 38 + // BuildAllRepositoryFiles (re)builds all repository files for every available group 39 + func BuildAllRepositoryFiles(ctx context.Context, ownerID int64) error { 40 + pv, err := GetOrCreateRepositoryVersion(ctx, ownerID) 41 + if err != nil { 42 + return err 43 + } 44 + 45 + // 1. Delete all existing repository files 46 + pfs, err := packages_model.GetFilesByVersionID(ctx, pv.ID) 47 + if err != nil { 48 + return err 49 + } 50 + 51 + for _, pf := range pfs { 52 + if err := packages_service.DeletePackageFile(ctx, pf); err != nil { 53 + return err 54 + } 55 + } 56 + 57 + // 2. (Re)Build repository files for existing packages 58 + groups, err := alt_model.GetGroups(ctx, ownerID) 59 + if err != nil { 60 + return err 61 + } 62 + for _, group := range groups { 63 + if err := BuildSpecificRepositoryFiles(ctx, ownerID, group); err != nil { 64 + return fmt.Errorf("failed to build repository files [%s]: %w", group, err) 65 + } 66 + } 67 + 68 + return nil 69 + } 70 + 71 + type repoChecksum struct { 72 + Value string `xml:",chardata"` 73 + Type string `xml:"type,attr"` 74 + } 75 + 76 + type repoLocation struct { 77 + Href string `xml:"href,attr"` 78 + } 79 + 80 + type repoData struct { 81 + Type string `xml:"type,attr"` 82 + Checksum repoChecksum `xml:"checksum"` 83 + MD5Checksum repoChecksum `xml:"md5checksum"` 84 + Blake2bHash repoChecksum `xml:"blake2bHash"` 85 + OpenChecksum repoChecksum `xml:"open-checksum"` 86 + Location repoLocation `xml:"location"` 87 + Timestamp int64 `xml:"timestamp"` 88 + Size int64 `xml:"size"` 89 + OpenSize int64 `xml:"open-size"` 90 + } 91 + 92 + type packageData struct { 93 + Package *packages_model.Package 94 + Version *packages_model.PackageVersion 95 + Blob *packages_model.PackageBlob 96 + VersionMetadata *rpm_module.VersionMetadata 97 + FileMetadata *rpm_module.FileMetadata 98 + } 99 + 100 + type packageCache = map[*packages_model.PackageFile]*packageData 101 + 102 + // BuildSpecificRepositoryFiles builds metadata files for the repository 103 + func BuildSpecificRepositoryFiles(ctx context.Context, ownerID int64, group string) error { 104 + pv, err := GetOrCreateRepositoryVersion(ctx, ownerID) 105 + if err != nil { 106 + return err 107 + } 108 + 109 + pfs, _, err := packages_model.SearchFiles(ctx, &packages_model.PackageFileSearchOptions{ 110 + OwnerID: ownerID, 111 + PackageType: packages_model.TypeAlt, 112 + Query: "%.rpm", 113 + CompositeKey: group, 114 + }) 115 + if err != nil { 116 + return err 117 + } 118 + 119 + // Delete the repository files if there are no packages 120 + if len(pfs) == 0 { 121 + pfs, err := packages_model.GetFilesByVersionID(ctx, pv.ID) 122 + if err != nil { 123 + return err 124 + } 125 + for _, pf := range pfs { 126 + if err := packages_service.DeletePackageFile(ctx, pf); err != nil { 127 + return err 128 + } 129 + } 130 + 131 + return nil 132 + } 133 + 134 + // Cache data needed for all repository files 135 + cache := make(packageCache) 136 + for _, pf := range pfs { 137 + pv, err := packages_model.GetVersionByID(ctx, pf.VersionID) 138 + if err != nil { 139 + return err 140 + } 141 + p, err := packages_model.GetPackageByID(ctx, pv.PackageID) 142 + if err != nil { 143 + return err 144 + } 145 + pb, err := packages_model.GetBlobByID(ctx, pf.BlobID) 146 + if err != nil { 147 + return err 148 + } 149 + pps, err := packages_model.GetPropertiesByName(ctx, packages_model.PropertyTypeFile, pf.ID, rpm_module.PropertyMetadata) 150 + if err != nil { 151 + return err 152 + } 153 + 154 + pd := &packageData{ 155 + Package: p, 156 + Version: pv, 157 + Blob: pb, 158 + } 159 + 160 + if err := json.Unmarshal([]byte(pv.MetadataJSON), &pd.VersionMetadata); err != nil { 161 + return err 162 + } 163 + if len(pps) > 0 { 164 + if err := json.Unmarshal([]byte(pps[0].Value), &pd.FileMetadata); err != nil { 165 + return err 166 + } 167 + } 168 + 169 + cache[pf] = pd 170 + } 171 + 172 + pkglist, err := buildPackageLists(ctx, pv, pfs, cache, group) 173 + if err != nil { 174 + return err 175 + } 176 + 177 + err = buildRelease(ctx, pv, pfs, cache, group, pkglist) 178 + if err != nil { 179 + return err 180 + } 181 + 182 + return nil 183 + } 184 + 185 + type RPMHeader struct { 186 + Magic [4]byte 187 + Reserved [4]byte 188 + NIndex uint32 189 + HSize uint32 190 + } 191 + 192 + type RPMHdrIndex struct { 193 + Tag uint32 194 + Type uint32 195 + Offset uint32 196 + Count uint32 197 + } 198 + 199 + // https://refspecs.linuxbase.org/LSB_4.0.0/LSB-Core-generic/LSB-Core-generic/pkgformat.html 200 + func buildPackageLists(ctx context.Context, pv *packages_model.PackageVersion, pfs []*packages_model.PackageFile, c packageCache, group string) (map[string][]any, error) { 201 + architectures := []string{} 202 + 203 + for _, pf := range pfs { 204 + pd := c[pf] 205 + 206 + if !slices.Contains(architectures, pd.FileMetadata.Architecture) { 207 + architectures = append(architectures, pd.FileMetadata.Architecture) 208 + } 209 + } 210 + 211 + repoDataListByArch := make(map[string][]any) 212 + repoDataList := []any{} 213 + orderedHeaders := []*RPMHeader{} 214 + 215 + for i := range architectures { 216 + headersWithIndexes := make(map[*RPMHeader]map[*RPMHdrIndex][]any) 217 + headersWithPtrs := make(map[*RPMHeader][]*RPMHdrIndex) 218 + indexPtrs := []*RPMHdrIndex{} 219 + indexes := make(map[*RPMHdrIndex][]any) 220 + 221 + for _, pf := range pfs { 222 + pd := c[pf] 223 + 224 + if pd.FileMetadata.Architecture == architectures[i] { 225 + var requireNames []any 226 + var requireVersions []any 227 + var requireFlags []any 228 + requireNamesSize := 0 229 + requireVersionsSize := 0 230 + requireFlagsSize := 0 231 + 232 + for _, entry := range pd.FileMetadata.Requires { 233 + if entry != nil { 234 + requireNames = append(requireNames, entry.Name) 235 + requireVersions = append(requireVersions, entry.Version) 236 + requireFlags = append(requireFlags, entry.AltFlags) 237 + requireNamesSize += len(entry.Name) + 1 238 + requireVersionsSize += len(entry.Version) + 1 239 + requireFlagsSize += 4 240 + } 241 + } 242 + 243 + var conflictNames []any 244 + var conflictVersions []any 245 + var conflictFlags []any 246 + conflictNamesSize := 0 247 + conflictVersionsSize := 0 248 + conflictFlagsSize := 0 249 + 250 + for _, entry := range pd.FileMetadata.Conflicts { 251 + if entry != nil { 252 + conflictNames = append(conflictNames, entry.Name) 253 + conflictVersions = append(conflictVersions, entry.Version) 254 + conflictFlags = append(conflictFlags, entry.AltFlags) 255 + conflictNamesSize += len(entry.Name) + 1 256 + conflictVersionsSize += len(entry.Version) + 1 257 + conflictFlagsSize += 4 258 + } 259 + } 260 + 261 + var baseNames []any 262 + var dirNames []any 263 + baseNamesSize := 0 264 + dirNamesSize := 0 265 + 266 + for _, entry := range pd.FileMetadata.Files { 267 + if entry != nil { 268 + re := regexp.MustCompile(`(.*?/)([^/]*)$`) 269 + matches := re.FindStringSubmatch(entry.Path) 270 + if len(matches) == 3 { 271 + baseNames = append(baseNames, matches[2]) 272 + dirNames = append(dirNames, matches[1]) 273 + baseNamesSize += len(matches[2]) + 1 274 + dirNamesSize += len(matches[1]) + 1 275 + } 276 + } 277 + } 278 + 279 + var provideNames []any 280 + var provideVersions []any 281 + var provideFlags []any 282 + provideNamesSize := 0 283 + provideVersionsSize := 0 284 + provideFlagsSize := 0 285 + 286 + for _, entry := range pd.FileMetadata.Provides { 287 + if entry != nil { 288 + provideNames = append(provideNames, entry.Name) 289 + provideVersions = append(provideVersions, entry.Version) 290 + provideFlags = append(provideFlags, entry.AltFlags) 291 + provideNamesSize += len(entry.Name) + 1 292 + provideVersionsSize += len(entry.Version) + 1 293 + provideFlagsSize += 4 294 + } 295 + } 296 + 297 + var obsoleteNames []any 298 + var obsoleteVersions []any 299 + var obsoleteFlags []any 300 + obsoleteNamesSize := 0 301 + obsoleteVersionsSize := 0 302 + obsoleteFlagsSize := 0 303 + 304 + for _, entry := range pd.FileMetadata.Obsoletes { 305 + if entry != nil { 306 + obsoleteNames = append(obsoleteNames, entry.Name) 307 + obsoleteVersions = append(obsoleteVersions, entry.Version) 308 + obsoleteFlags = append(obsoleteFlags, entry.AltFlags) 309 + obsoleteNamesSize += len(entry.Name) + 1 310 + obsoleteVersionsSize += len(entry.Version) + 1 311 + obsoleteFlagsSize += 4 312 + } 313 + } 314 + 315 + var changeLogTimes []any 316 + var changeLogNames []any 317 + var changeLogTexts []any 318 + changeLogTimesSize := 0 319 + changeLogNamesSize := 0 320 + changeLogTextsSize := 0 321 + 322 + for _, entry := range pd.FileMetadata.Changelogs { 323 + if entry != nil { 324 + changeLogNames = append(changeLogNames, entry.Author) 325 + changeLogTexts = append(changeLogTexts, entry.Text) 326 + changeLogTimes = append(changeLogTimes, uint32(int64(entry.Date))) 327 + changeLogNamesSize += len(entry.Author) + 1 328 + changeLogTextsSize += len(entry.Text) + 1 329 + changeLogTimesSize += 4 330 + } 331 + } 332 + 333 + /*Header*/ 334 + hdr := &RPMHeader{ 335 + Magic: [4]byte{0x8E, 0xAD, 0xE8, 0x01}, 336 + Reserved: [4]byte{0, 0, 0, 0}, 337 + NIndex: binary.BigEndian.Uint32([]byte{0, 0, 0, 0}), 338 + HSize: binary.BigEndian.Uint32([]byte{0, 0, 0, 0}), 339 + } 340 + orderedHeaders = append(orderedHeaders, hdr) 341 + 342 + /*Tags: */ 343 + 344 + nameInd := RPMHdrIndex{ 345 + Tag: binary.BigEndian.Uint32([]byte{0, 0, 3, 232}), 346 + Type: binary.BigEndian.Uint32([]byte{0, 0, 0, 6}), 347 + Offset: 0, 348 + Count: 1, 349 + } 350 + indexPtrs = append(indexPtrs, &nameInd) 351 + indexes[&nameInd] = append(indexes[&nameInd], pd.Package.Name) 352 + hdr.NIndex++ 353 + hdr.HSize += uint32(len(pd.Package.Name) + 1) 354 + 355 + // Индекс для версии пакета 356 + versionInd := RPMHdrIndex{ 357 + Tag: binary.BigEndian.Uint32([]byte{0, 0, 3, 233}), 358 + Type: binary.BigEndian.Uint32([]byte{0, 0, 0, 6}), 359 + Offset: hdr.HSize, 360 + Count: 1, 361 + } 362 + indexPtrs = append(indexPtrs, &versionInd) 363 + indexes[&versionInd] = append(indexes[&versionInd], pd.FileMetadata.Version) 364 + hdr.NIndex++ 365 + hdr.HSize += uint32(len(pd.FileMetadata.Version) + 1) 366 + 367 + summaryInd := RPMHdrIndex{ 368 + Tag: binary.BigEndian.Uint32([]byte{0, 0, 3, 236}), 369 + Type: binary.BigEndian.Uint32([]byte{0, 0, 0, 9}), 370 + Offset: hdr.HSize, 371 + Count: 1, 372 + } 373 + indexPtrs = append(indexPtrs, &summaryInd) 374 + indexes[&summaryInd] = append(indexes[&summaryInd], pd.VersionMetadata.Summary) 375 + hdr.NIndex++ 376 + hdr.HSize += uint32(len(pd.VersionMetadata.Summary) + 1) 377 + 378 + descriptionInd := RPMHdrIndex{ 379 + Tag: binary.BigEndian.Uint32([]byte{0, 0, 3, 237}), 380 + Type: binary.BigEndian.Uint32([]byte{0, 0, 0, 9}), 381 + Offset: hdr.HSize, 382 + Count: 1, 383 + } 384 + indexPtrs = append(indexPtrs, &descriptionInd) 385 + indexes[&descriptionInd] = append(indexes[&descriptionInd], pd.VersionMetadata.Description) 386 + hdr.NIndex++ 387 + hdr.HSize += uint32(len(pd.VersionMetadata.Description) + 1) 388 + 389 + releaseInd := RPMHdrIndex{ 390 + Tag: binary.BigEndian.Uint32([]byte{0, 0, 3, 234}), 391 + Type: binary.BigEndian.Uint32([]byte{0, 0, 0, 6}), 392 + Offset: hdr.HSize, 393 + Count: 1, 394 + } 395 + indexPtrs = append(indexPtrs, &releaseInd) 396 + indexes[&releaseInd] = append(indexes[&releaseInd], pd.FileMetadata.Release) 397 + hdr.NIndex++ 398 + hdr.HSize += uint32(len(pd.FileMetadata.Release) + 1) 399 + 400 + alignPadding(hdr, indexes, &releaseInd) 401 + 402 + sizeInd := RPMHdrIndex{ 403 + Tag: binary.BigEndian.Uint32([]byte{0, 0, 3, 241}), 404 + Type: binary.BigEndian.Uint32([]byte{0, 0, 0, 4}), 405 + Offset: hdr.HSize, 406 + Count: 1, 407 + } 408 + indexPtrs = append(indexPtrs, &sizeInd) 409 + indexes[&sizeInd] = append(indexes[&sizeInd], int32(pd.FileMetadata.InstalledSize)) 410 + hdr.NIndex++ 411 + hdr.HSize += 4 412 + 413 + buildTimeInd := RPMHdrIndex{ 414 + Tag: binary.BigEndian.Uint32([]byte{0, 0, 3, 238}), 415 + Type: binary.BigEndian.Uint32([]byte{0, 0, 0, 4}), 416 + Offset: hdr.HSize, 417 + Count: 1, 418 + } 419 + indexPtrs = append(indexPtrs, &buildTimeInd) 420 + indexes[&buildTimeInd] = append(indexes[&buildTimeInd], int32(pd.FileMetadata.BuildTime)) 421 + hdr.NIndex++ 422 + hdr.HSize += 4 423 + 424 + licenseInd := RPMHdrIndex{ 425 + Tag: binary.BigEndian.Uint32([]byte{0, 0, 3, 246}), 426 + Type: binary.BigEndian.Uint32([]byte{0, 0, 0, 6}), 427 + Offset: hdr.HSize, 428 + Count: 1, 429 + } 430 + indexPtrs = append(indexPtrs, &licenseInd) 431 + indexes[&licenseInd] = append(indexes[&licenseInd], pd.VersionMetadata.License) 432 + hdr.NIndex++ 433 + hdr.HSize += uint32(len(pd.VersionMetadata.License) + 1) 434 + 435 + packagerInd := RPMHdrIndex{ 436 + Tag: binary.BigEndian.Uint32([]byte{0, 0, 3, 247}), 437 + Type: binary.BigEndian.Uint32([]byte{0, 0, 0, 6}), 438 + Offset: hdr.HSize, 439 + Count: 1, 440 + } 441 + indexPtrs = append(indexPtrs, &packagerInd) 442 + indexes[&packagerInd] = append(indexes[&packagerInd], pd.FileMetadata.Packager) 443 + hdr.NIndex++ 444 + hdr.HSize += uint32(len(pd.FileMetadata.Packager) + 1) 445 + 446 + groupInd := RPMHdrIndex{ 447 + Tag: binary.BigEndian.Uint32([]byte{0, 0, 3, 248}), 448 + Type: binary.BigEndian.Uint32([]byte{0, 0, 0, 6}), 449 + Offset: hdr.HSize, 450 + Count: 1, 451 + } 452 + indexPtrs = append(indexPtrs, &groupInd) 453 + indexes[&groupInd] = append(indexes[&groupInd], pd.FileMetadata.Group) 454 + hdr.NIndex++ 455 + hdr.HSize += uint32(len(pd.FileMetadata.Group) + 1) 456 + 457 + urlInd := RPMHdrIndex{ 458 + Tag: binary.BigEndian.Uint32([]byte{0, 0, 3, 252}), 459 + Type: binary.BigEndian.Uint32([]byte{0, 0, 0, 6}), 460 + Offset: hdr.HSize, 461 + Count: 1, 462 + } 463 + indexPtrs = append(indexPtrs, &urlInd) 464 + indexes[&urlInd] = append(indexes[&urlInd], pd.VersionMetadata.ProjectURL) 465 + hdr.NIndex++ 466 + hdr.HSize += uint32(len(pd.VersionMetadata.ProjectURL) + 1) 467 + 468 + if len(changeLogNames) != 0 && len(changeLogTexts) != 0 && len(changeLogTimes) != 0 { 469 + alignPadding(hdr, indexes, &urlInd) 470 + 471 + addRPMHdrIndex(hdr, &indexPtrs, indexes, []byte{0x00, 0x00, 0x04, 0x38}, []byte{0, 0, 0, 4}, changeLogTimes, changeLogTimesSize) 472 + addRPMHdrIndex(hdr, &indexPtrs, indexes, []byte{0x00, 0x00, 0x04, 0x39}, []byte{0, 0, 0, 8}, changeLogNames, changeLogNamesSize) 473 + addRPMHdrIndex(hdr, &indexPtrs, indexes, []byte{0x00, 0x00, 0x04, 0x3A}, []byte{0, 0, 0, 8}, changeLogTexts, changeLogTextsSize) 474 + } 475 + 476 + archInd := RPMHdrIndex{ 477 + Tag: binary.BigEndian.Uint32([]byte{0, 0, 3, 254}), 478 + Type: binary.BigEndian.Uint32([]byte{0, 0, 0, 6}), 479 + Offset: hdr.HSize, 480 + Count: 1, 481 + } 482 + indexPtrs = append(indexPtrs, &archInd) 483 + indexes[&archInd] = append(indexes[&archInd], pd.FileMetadata.Architecture) 484 + hdr.NIndex++ 485 + hdr.HSize += uint32(len(pd.FileMetadata.Architecture) + 1) 486 + 487 + if len(provideNames) != 0 && len(provideVersions) != 0 && len(provideFlags) != 0 { 488 + alignPadding(hdr, indexes, &archInd) 489 + 490 + addRPMHdrIndex(hdr, &indexPtrs, indexes, []byte{0x00, 0x00, 0x04, 0x58}, []byte{0, 0, 0, 4}, provideFlags, provideFlagsSize) 491 + addRPMHdrIndex(hdr, &indexPtrs, indexes, []byte{0x00, 0x00, 0x04, 0x17}, []byte{0, 0, 0, 8}, provideNames, provideNamesSize) 492 + addRPMHdrIndex(hdr, &indexPtrs, indexes, []byte{0x00, 0x00, 0x04, 0x59}, []byte{0, 0, 0, 8}, provideVersions, provideVersionsSize) 493 + } 494 + 495 + sourceRpmInd := RPMHdrIndex{ 496 + Tag: binary.BigEndian.Uint32([]byte{0x00, 0x00, 0x04, 0x14}), 497 + Type: binary.BigEndian.Uint32([]byte{0, 0, 0, 6}), 498 + Offset: hdr.HSize, 499 + Count: 1, 500 + } 501 + indexPtrs = append(indexPtrs, &sourceRpmInd) 502 + indexes[&sourceRpmInd] = append(indexes[&sourceRpmInd], pd.FileMetadata.SourceRpm) 503 + hdr.NIndex++ 504 + hdr.HSize += binary.BigEndian.Uint32([]byte{0, 0, 0, uint8(len(pd.FileMetadata.SourceRpm) + 1)}) 505 + 506 + if len(requireNames) != 0 && len(requireVersions) != 0 && len(requireFlags) != 0 { 507 + alignPadding(hdr, indexes, &sourceRpmInd) 508 + 509 + addRPMHdrIndex(hdr, &indexPtrs, indexes, []byte{0x00, 0x00, 0x04, 0x18}, []byte{0, 0, 0, 4}, requireFlags, requireFlagsSize) 510 + addRPMHdrIndex(hdr, &indexPtrs, indexes, []byte{0, 0, 4, 25}, []byte{0, 0, 0, 8}, requireNames, requireNamesSize) 511 + addRPMHdrIndex(hdr, &indexPtrs, indexes, []byte{0x00, 0x00, 0x04, 0x1A}, []byte{0, 0, 0, 8}, requireVersions, requireVersionsSize) 512 + } 513 + 514 + if len(baseNames) != 0 { 515 + baseNamesInd := RPMHdrIndex{ 516 + Tag: binary.BigEndian.Uint32([]byte{0x00, 0x00, 0x04, 0x5D}), 517 + Type: binary.BigEndian.Uint32([]byte{0, 0, 0, 8}), 518 + Offset: hdr.HSize, 519 + Count: uint32(len(baseNames)), 520 + } 521 + indexPtrs = append(indexPtrs, &baseNamesInd) 522 + indexes[&baseNamesInd] = baseNames 523 + hdr.NIndex++ 524 + hdr.HSize += uint32(baseNamesSize) 525 + } 526 + 527 + if len(dirNames) != 0 { 528 + dirnamesInd := RPMHdrIndex{ 529 + Tag: binary.BigEndian.Uint32([]byte{0x00, 0x00, 0x04, 0x5E}), 530 + Type: binary.BigEndian.Uint32([]byte{0, 0, 0, 8}), 531 + Offset: hdr.HSize, 532 + Count: uint32(len(dirNames)), 533 + } 534 + indexPtrs = append(indexPtrs, &dirnamesInd) 535 + indexes[&dirnamesInd] = dirNames 536 + hdr.NIndex++ 537 + hdr.HSize += uint32(dirNamesSize) 538 + } 539 + 540 + filenameInd := RPMHdrIndex{ 541 + Tag: binary.BigEndian.Uint32([]byte{0x00, 0x0F, 0x42, 0x40}), 542 + Type: binary.BigEndian.Uint32([]byte{0, 0, 0, 6}), 543 + Offset: hdr.HSize, 544 + Count: 1, 545 + } 546 + indexPtrs = append(indexPtrs, &filenameInd) 547 + indexes[&filenameInd] = append(indexes[&filenameInd], pf.Name) 548 + hdr.NIndex++ 549 + hdr.HSize += uint32(len(pf.Name) + 1) 550 + 551 + alignPadding(hdr, indexes, &filenameInd) 552 + 553 + filesizeInd := RPMHdrIndex{ 554 + Tag: binary.BigEndian.Uint32([]byte{0x00, 0x0F, 0x42, 0x41}), 555 + Type: binary.BigEndian.Uint32([]byte{0, 0, 0, 4}), 556 + Offset: hdr.HSize, 557 + Count: 1, 558 + } 559 + indexPtrs = append(indexPtrs, &filesizeInd) 560 + indexes[&filesizeInd] = append(indexes[&filesizeInd], int32(pd.Blob.Size)) 561 + hdr.NIndex++ 562 + hdr.HSize += 4 563 + 564 + md5Ind := RPMHdrIndex{ 565 + Tag: binary.BigEndian.Uint32([]byte{0x00, 0x0F, 0x42, 0x45}), 566 + Type: binary.BigEndian.Uint32([]byte{0, 0, 0, 6}), 567 + Offset: hdr.HSize, 568 + Count: 1, 569 + } 570 + indexPtrs = append(indexPtrs, &md5Ind) 571 + indexes[&md5Ind] = append(indexes[&md5Ind], pd.Blob.HashMD5) 572 + hdr.NIndex++ 573 + hdr.HSize += uint32(len(pd.Blob.HashMD5) + 1) 574 + 575 + blake2bInd := RPMHdrIndex{ 576 + Tag: binary.BigEndian.Uint32([]byte{0x00, 0x0F, 0x42, 0x49}), 577 + Type: binary.BigEndian.Uint32([]byte{0, 0, 0, 6}), 578 + Offset: hdr.HSize, 579 + Count: 1, 580 + } 581 + indexPtrs = append(indexPtrs, &blake2bInd) 582 + indexes[&blake2bInd] = append(indexes[&blake2bInd], pd.Blob.HashBlake2b) 583 + hdr.NIndex++ 584 + hdr.HSize += uint32(len(pd.Blob.HashBlake2b) + 1) 585 + 586 + if len(conflictNames) != 0 && len(conflictVersions) != 0 && len(conflictFlags) != 0 { 587 + alignPadding(hdr, indexes, &blake2bInd) 588 + 589 + addRPMHdrIndex(hdr, &indexPtrs, indexes, []byte{0x00, 0x00, 0x04, 0x1D}, []byte{0, 0, 0, 4}, conflictFlags, conflictFlagsSize) 590 + addRPMHdrIndex(hdr, &indexPtrs, indexes, []byte{0x00, 0x00, 0x04, 0x1E}, []byte{0, 0, 0, 8}, conflictNames, conflictNamesSize) 591 + addRPMHdrIndex(hdr, &indexPtrs, indexes, []byte{0x00, 0x00, 0x04, 0x1F}, []byte{0, 0, 0, 8}, conflictVersions, conflictVersionsSize) 592 + } 593 + 594 + directoryInd := RPMHdrIndex{ 595 + Tag: binary.BigEndian.Uint32([]byte{0x00, 0x0F, 0x42, 0x4A}), 596 + Type: binary.BigEndian.Uint32([]byte{0, 0, 0, 6}), 597 + Offset: hdr.HSize, 598 + Count: 1, 599 + } 600 + indexPtrs = append(indexPtrs, &directoryInd) 601 + indexes[&directoryInd] = append(indexes[&directoryInd], "RPMS.classic") 602 + hdr.NIndex++ 603 + hdr.HSize += binary.BigEndian.Uint32([]byte{0, 0, 0, uint8(len("RPMS.classic") + 1)}) 604 + 605 + if len(obsoleteNames) != 0 && len(obsoleteVersions) != 0 && len(obsoleteFlags) != 0 { 606 + alignPadding(hdr, indexes, &directoryInd) 607 + 608 + addRPMHdrIndex(hdr, &indexPtrs, indexes, []byte{0x00, 0x00, 0x04, 0x5A}, []byte{0, 0, 0, 4}, obsoleteFlags, obsoleteFlagsSize) 609 + addRPMHdrIndex(hdr, &indexPtrs, indexes, []byte{0x00, 0x00, 0x04, 0x42}, []byte{0, 0, 0, 8}, obsoleteNames, obsoleteNamesSize) 610 + addRPMHdrIndex(hdr, &indexPtrs, indexes, []byte{0x00, 0x00, 0x04, 0x5B}, []byte{0, 0, 0, 8}, obsoleteVersions, obsoleteVersionsSize) 611 + } 612 + 613 + headersWithIndexes[hdr] = indexes 614 + headersWithPtrs[hdr] = indexPtrs 615 + 616 + indexPtrs = []*RPMHdrIndex{} 617 + indexes = make(map[*RPMHdrIndex][]any) 618 + } 619 + } 620 + 621 + files := []string{"pkglist.classic", "pkglist.classic.xz"} 622 + for file := range files { 623 + fileInfo, err := addPkglistAsFileToRepo(ctx, pv, files[file], headersWithIndexes, headersWithPtrs, orderedHeaders, group, architectures[i]) 624 + if err != nil { 625 + return nil, err 626 + } 627 + repoDataList = append(repoDataList, fileInfo) 628 + repoDataListByArch[architectures[i]] = repoDataList 629 + } 630 + repoDataList = []any{} 631 + orderedHeaders = []*RPMHeader{} 632 + } 633 + return repoDataListByArch, nil 634 + } 635 + 636 + func alignPadding(hdr *RPMHeader, indexes map[*RPMHdrIndex][]any, lastIndex *RPMHdrIndex) { 637 + /* Align to 4-bytes to add a 4-byte element. */ 638 + padding := (4 - (hdr.HSize % 4)) % 4 639 + if padding == 4 { 640 + padding = 0 641 + } 642 + hdr.HSize += binary.BigEndian.Uint32([]byte{0, 0, 0, uint8(padding)}) 643 + 644 + for i := uint32(0); i < padding; i++ { 645 + for _, elem := range indexes[lastIndex] { 646 + if str, ok := elem.(string); ok { 647 + indexes[lastIndex][len(indexes[lastIndex])-1] = str + "\x00" 648 + } 649 + } 650 + } 651 + } 652 + 653 + func addRPMHdrIndex(hdr *RPMHeader, indexPtrs *[]*RPMHdrIndex, indexes map[*RPMHdrIndex][]any, tag, typeByte []byte, data []any, dataSize int) { 654 + index := RPMHdrIndex{ 655 + Tag: binary.BigEndian.Uint32(tag), 656 + Type: binary.BigEndian.Uint32(typeByte), 657 + Offset: hdr.HSize, 658 + Count: uint32(len(data)), 659 + } 660 + *indexPtrs = append(*indexPtrs, &index) 661 + indexes[&index] = data 662 + hdr.NIndex++ 663 + hdr.HSize += uint32(dataSize) 664 + } 665 + 666 + // https://www.altlinux.org/APT_в_ALT_Linux/CreateRepository 667 + func buildRelease(ctx context.Context, pv *packages_model.PackageVersion, pfs []*packages_model.PackageFile, c packageCache, group string, pkglist map[string][]any) error { 668 + var buf bytes.Buffer 669 + 670 + architectures := []string{} 671 + 672 + for _, pf := range pfs { 673 + pd := c[pf] 674 + if !slices.Contains(architectures, pd.FileMetadata.Architecture) { 675 + architectures = append(architectures, pd.FileMetadata.Architecture) 676 + } 677 + } 678 + 679 + for i := range architectures { 680 + archive := "Alt Linux Team" 681 + component := "classic" 682 + version := strconv.FormatInt(time.Now().Unix(), 10) 683 + architectures := architectures[i] 684 + origin := "Alt Linux Team" 685 + label := setting.AppName 686 + notautomatic := "false" 687 + data := fmt.Sprintf("Archive: %s\nComponent: %s\nVersion: %s\nOrigin: %s\nLabel: %s\nArchitecture: %s\nNotAutomatic: %s", 688 + archive, component, version, origin, label, architectures, notautomatic) 689 + buf.WriteString(data + "\n") 690 + fileInfo, err := addReleaseAsFileToRepo(ctx, pv, "release.classic", buf.String(), group, architectures) 691 + if err != nil { 692 + return err 693 + } 694 + buf.Reset() 695 + 696 + origin = setting.AppName 697 + suite := "Sisyphus" 698 + codename := strconv.FormatInt(time.Now().Unix(), 10) 699 + date := time.Now().UTC().Format(time.RFC1123) 700 + 701 + var md5Sum string 702 + var blake2b string 703 + 704 + for _, pkglistByArch := range pkglist[architectures] { 705 + md5Sum += fmt.Sprintf(" %s %s %s\n", pkglistByArch.([]string)[2], pkglistByArch.([]string)[4], "base/"+pkglistByArch.([]string)[0]) 706 + blake2b += fmt.Sprintf(" %s %s %s\n", pkglistByArch.([]string)[3], pkglistByArch.([]string)[4], "base/"+pkglistByArch.([]string)[0]) 707 + } 708 + md5Sum += fmt.Sprintf(" %s %s %s\n", fileInfo[2], fileInfo[4], "base/"+fileInfo[0]) 709 + blake2b += fmt.Sprintf(" %s %s %s\n", fileInfo[3], fileInfo[4], "base/"+fileInfo[0]) 710 + 711 + data = fmt.Sprintf("Origin: %s\nLabel: %s\nSuite: %s\nCodename: %s\nDate: %s\nArchitectures: %s\nMD5Sum:\n%sBLAKE2b:\n%s\n", 712 + origin, label, suite, codename, date, architectures, md5Sum, blake2b) 713 + buf.WriteString(data + "\n") 714 + _, err = addReleaseAsFileToRepo(ctx, pv, "release", buf.String(), group, architectures) 715 + if err != nil { 716 + return err 717 + } 718 + buf.Reset() 719 + } 720 + return nil 721 + } 722 + 723 + func addReleaseAsFileToRepo(ctx context.Context, pv *packages_model.PackageVersion, filename, obj, group, arch string) ([]string, error) { 724 + content, _ := packages_module.NewHashedBuffer() 725 + defer content.Close() 726 + 727 + h := sha256.New() 728 + 729 + w := io.MultiWriter(content, h) 730 + if _, err := w.Write([]byte(obj)); err != nil { 731 + return nil, err 732 + } 733 + 734 + _, err := packages_service.AddFileToPackageVersionInternal( 735 + ctx, 736 + pv, 737 + &packages_service.PackageFileCreationInfo{ 738 + PackageFileInfo: packages_service.PackageFileInfo{ 739 + Filename: filename, 740 + CompositeKey: arch + "__" + group, 741 + }, 742 + Creator: user_model.NewGhostUser(), 743 + Data: content, 744 + IsLead: false, 745 + OverwriteExisting: true, 746 + }, 747 + ) 748 + if err != nil { 749 + return nil, err 750 + } 751 + 752 + hashMD5, _, hashSHA256, _, hashBlake2b := content.Sums() 753 + 754 + if group == "" { 755 + group = "alt" 756 + } 757 + 758 + repoData := &repoData{ 759 + Type: filename, 760 + Checksum: repoChecksum{ 761 + Type: "sha256", 762 + Value: hex.EncodeToString(hashSHA256), 763 + }, 764 + MD5Checksum: repoChecksum{ 765 + Type: "md5", 766 + Value: hex.EncodeToString(hashMD5), 767 + }, 768 + OpenChecksum: repoChecksum{ 769 + Type: "sha256", 770 + Value: hex.EncodeToString(h.Sum(nil)), 771 + }, 772 + Blake2bHash: repoChecksum{ 773 + Type: "blake2b", 774 + Value: hex.EncodeToString(hashBlake2b), 775 + }, 776 + Location: repoLocation{ 777 + Href: group + ".repo/" + arch + "/base/" + filename, 778 + }, 779 + Size: content.Size(), 780 + /* Unused values: 781 + Timestamp: time.Now().Unix(), 782 + OpenSize: content.Size(), */ 783 + } 784 + 785 + data := []string{ 786 + repoData.Type, repoData.Checksum.Value, 787 + repoData.MD5Checksum.Value, repoData.Blake2bHash.Value, strconv.Itoa(int(repoData.Size)), 788 + } 789 + 790 + return data, nil 791 + } 792 + 793 + func addPkglistAsFileToRepo(ctx context.Context, pv *packages_model.PackageVersion, filename string, headersWithIndexes map[*RPMHeader]map[*RPMHdrIndex][]any, headersWithPtrs map[*RPMHeader][]*RPMHdrIndex, orderedHeaders []*RPMHeader, group, arch string) ([]string, error) { 794 + content, _ := packages_module.NewHashedBuffer() 795 + defer content.Close() 796 + 797 + h := sha256.New() 798 + w := io.MultiWriter(content, h) 799 + buf := &bytes.Buffer{} 800 + 801 + for _, hdr := range orderedHeaders { 802 + if err := binary.Write(buf, binary.BigEndian, hdr); err != nil { 803 + return nil, err 804 + } 805 + 806 + for _, indexPtr := range headersWithPtrs[hdr] { 807 + index := *indexPtr 808 + 809 + if err := binary.Write(buf, binary.BigEndian, index); err != nil { 810 + return nil, err 811 + } 812 + } 813 + 814 + for _, indexPtr := range headersWithPtrs[hdr] { 815 + for _, indexValue := range headersWithIndexes[hdr][indexPtr] { 816 + switch v := indexValue.(type) { 817 + case string: 818 + if _, err := buf.WriteString(v + "\x00"); err != nil { 819 + return nil, err 820 + } 821 + case int, int32, int64, uint32: 822 + if err := binary.Write(buf, binary.BigEndian, v); err != nil { 823 + return nil, err 824 + } 825 + } 826 + } 827 + } 828 + } 829 + 830 + parts := strings.Split(filename, ".") 831 + 832 + if len(parts) == 3 && parts[len(parts)-1] == "xz" { 833 + xzContent, err := compressXZ(buf.Bytes()) 834 + if err != nil { 835 + return nil, err 836 + } 837 + if _, err := w.Write(xzContent); err != nil { 838 + return nil, err 839 + } 840 + } else { 841 + if _, err := w.Write(buf.Bytes()); err != nil { 842 + return nil, err 843 + } 844 + } 845 + 846 + _, err := packages_service.AddFileToPackageVersionInternal( 847 + ctx, 848 + pv, 849 + &packages_service.PackageFileCreationInfo{ 850 + PackageFileInfo: packages_service.PackageFileInfo{ 851 + Filename: filename, 852 + CompositeKey: arch + "__" + group, 853 + }, 854 + Creator: user_model.NewGhostUser(), 855 + Data: content, 856 + IsLead: false, 857 + OverwriteExisting: true, 858 + }, 859 + ) 860 + if err != nil { 861 + return nil, err 862 + } 863 + 864 + hashMD5, _, hashSHA256, _, hashBlake2b := content.Sums() 865 + 866 + if group == "" { 867 + group = "alt" 868 + } 869 + 870 + repoData := &repoData{ 871 + Type: filename, 872 + Checksum: repoChecksum{ 873 + Type: "sha256", 874 + Value: hex.EncodeToString(hashSHA256), 875 + }, 876 + MD5Checksum: repoChecksum{ 877 + Type: "md5", 878 + Value: hex.EncodeToString(hashMD5), 879 + }, 880 + OpenChecksum: repoChecksum{ 881 + Type: "sha256", 882 + Value: hex.EncodeToString(h.Sum(nil)), 883 + }, 884 + Blake2bHash: repoChecksum{ 885 + Type: "blake2b", 886 + Value: hex.EncodeToString(hashBlake2b), 887 + }, 888 + Location: repoLocation{ 889 + Href: group + ".repo/" + arch + "/base/" + filename, 890 + }, 891 + Size: content.Size(), 892 + /* Unused values: 893 + Timestamp: time.Now().Unix(), 894 + OpenSize: content.Size(), */ 895 + } 896 + 897 + data := []string{ 898 + repoData.Type, repoData.Checksum.Value, 899 + repoData.MD5Checksum.Value, repoData.Blake2bHash.Value, strconv.Itoa(int(repoData.Size)), 900 + } 901 + 902 + return data, nil 903 + } 904 + 905 + func compressXZ(data []byte) ([]byte, error) { 906 + var xzContent bytes.Buffer 907 + xzWriter, err := xz.NewWriter(&xzContent) 908 + if err != nil { 909 + return nil, err 910 + } 911 + defer xzWriter.Close() 912 + 913 + if _, err := xzWriter.Write(data); err != nil { 914 + return nil, err 915 + } 916 + if err := xzWriter.Close(); err != nil { 917 + return nil, err 918 + } 919 + 920 + return xzContent.Bytes(), nil 921 + }
+5
services/packages/cleanup/cleanup.go
··· 16 16 packages_module "code.gitea.io/gitea/modules/packages" 17 17 packages_service "code.gitea.io/gitea/services/packages" 18 18 alpine_service "code.gitea.io/gitea/services/packages/alpine" 19 + alt_service "code.gitea.io/gitea/services/packages/alt" 19 20 arch_service "code.gitea.io/gitea/services/packages/arch" 20 21 cargo_service "code.gitea.io/gitea/services/packages/cargo" 21 22 container_service "code.gitea.io/gitea/services/packages/container" ··· 136 137 } else if pcr.Type == packages_model.TypeArch { 137 138 if err := arch_service.BuildAllRepositoryFiles(ctx, pcr.OwnerID); err != nil { 138 139 return fmt.Errorf("CleanupRule [%d]: arch.BuildAllRepositoryFiles failed: %w", pcr.ID, err) 140 + } 141 + } else if pcr.Type == packages_model.TypeAlt { 142 + if err := alt_service.BuildAllRepositoryFiles(ctx, pcr.OwnerID); err != nil { 143 + return fmt.Errorf("CleanupRule [%d]: alt.BuildAllRepositoryFiles failed: %w", pcr.ID, err) 139 144 } 140 145 } 141 146 }
+9 -6
services/packages/packages.go
··· 244 244 245 245 // NewPackageBlob creates a package blob instance 246 246 func NewPackageBlob(hsr packages_module.HashedSizeReader) *packages_model.PackageBlob { 247 - hashMD5, hashSHA1, hashSHA256, hashSHA512 := hsr.Sums() 247 + hashMD5, hashSHA1, hashSHA256, hashSHA512, hashBlake2b := hsr.Sums() 248 248 249 249 return &packages_model.PackageBlob{ 250 - Size: hsr.Size(), 251 - HashMD5: hex.EncodeToString(hashMD5), 252 - HashSHA1: hex.EncodeToString(hashSHA1), 253 - HashSHA256: hex.EncodeToString(hashSHA256), 254 - HashSHA512: hex.EncodeToString(hashSHA512), 250 + Size: hsr.Size(), 251 + HashMD5: hex.EncodeToString(hashMD5), 252 + HashSHA1: hex.EncodeToString(hashSHA1), 253 + HashSHA256: hex.EncodeToString(hashSHA256), 254 + HashSHA512: hex.EncodeToString(hashSHA512), 255 + HashBlake2b: hex.EncodeToString(hashBlake2b), 255 256 } 256 257 } 257 258 ··· 395 396 typeSpecificSize = setting.Packages.LimitSizePyPI 396 397 case packages_model.TypeRpm: 397 398 typeSpecificSize = setting.Packages.LimitSizeRpm 399 + case packages_model.TypeAlt: 400 + typeSpecificSize = setting.Packages.LimitSizeAlt 398 401 case packages_model.TypeRubyGems: 399 402 typeSpecificSize = setting.Packages.LimitSizeRubyGems 400 403 case packages_model.TypeSwift:
+1 -1
services/packages/rpm/repository.go
··· 622 622 return nil, err 623 623 } 624 624 625 - _, _, hashSHA256, _ := content.Sums() 625 + _, _, hashSHA256, _, _ := content.Sums() 626 626 627 627 return &repoData{ 628 628 Type: filetype,
+49
templates/package/content/alt.tmpl
··· 1 + {{if eq .PackageDescriptor.Package.Type "alt"}} 2 + <h4 class="ui top attached header">{{ctx.Locale.Tr "packages.installation"}}</h4> 3 + <div class="ui attached segment"> 4 + <div class="ui form"> 5 + <div class="field"> 6 + <label>{{svg "octicon-terminal"}} {{ctx.Locale.Tr "packages.alt.registry"}}</label> 7 + <div class="markup"><pre class="code-block"><code>{{- if gt (len .Groups) 1 -}} 8 + # {{ctx.Locale.Tr "packages.alt.repository.multiple_groups"}} 9 + 10 + {{end -}} 11 + # {{ctx.Locale.Tr "packages.alt.setup"}} 12 + {{- range $group := .Groups}} 13 + {{- if $group}}{{$group = print "/" $group}}{{end}} 14 + apt-repo add rpm <origin-url data-url="{{AppSubUrl}}/api/packages/{{$.PackageDescriptor.Owner.Name}}/alt{{- if $group}}{{$group}}{{- else}}/alt{{- end}}.repo"></origin-url> _arch_ classic 15 + 16 + {{- end}}</code></pre></div> 17 + </div> 18 + <div class="field"> 19 + <label>{{svg "octicon-terminal"}} {{ctx.Locale.Tr "packages.alt.install"}}</label> 20 + <div class="markup"> 21 + <pre class="code-block"><code># {{ctx.Locale.Tr "packages.alt.registry.install"}} 22 + apt-get update 23 + apt-get install {{$.PackageDescriptor.Package.Name}}</code></pre> 24 + </div> 25 + </div> 26 + <div class="field"> 27 + <label>{{ctx.Locale.Tr "packages.registry.documentation" "ALT" "https://docs.gitea.com/usage/packages/alt/"}}</label> 28 + </div> 29 + </div> 30 + </div> 31 + 32 + <h4 class="ui top attached header">{{ctx.Locale.Tr "packages.alt.repository"}}</h4> 33 + <div class="ui attached segment"> 34 + <table class="ui single line very basic table"> 35 + <tbody> 36 + <tr> 37 + <td class="collapsing"><h5>{{ctx.Locale.Tr "packages.alt.repository.architectures"}}</h5></td> 38 + <td>{{StringUtils.Join .Architectures ", "}}</td> 39 + </tr> 40 + </tbody> 41 + </table> 42 + </div> 43 + 44 + {{if or .PackageDescriptor.Metadata.Summary .PackageDescriptor.Metadata.Description}} 45 + <h4 class="ui top attached header">{{ctx.Locale.Tr "packages.about"}}</h4> 46 + {{if .PackageDescriptor.Metadata.Summary}}<div class="ui attached segment">{{.PackageDescriptor.Metadata.Summary}}</div>{{end}} 47 + {{if .PackageDescriptor.Metadata.Description}}<div class="ui attached segment">{{.PackageDescriptor.Metadata.Description}}</div>{{end}} 48 + {{end}} 49 + {{end}}
+4
templates/package/metadata/alt.tmpl
··· 1 + {{if eq .PackageDescriptor.Package.Type "alt"}} 2 + {{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external" 16 "tw-mr-2"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{ctx.Locale.Tr "packages.details.project_site"}}</a></div>{{end}} 3 + {{if .PackageDescriptor.Metadata.License}}<div class="item" title="{{ctx.Locale.Tr "packages.details.license"}}">{{svg "octicon-law" 16 "tw-mr-2"}} {{.PackageDescriptor.Metadata.License}}</div>{{end}} 4 + {{end}}
+2
templates/package/view.tmpl
··· 37 37 {{template "package/content/pub" .}} 38 38 {{template "package/content/pypi" .}} 39 39 {{template "package/content/rpm" .}} 40 + {{template "package/content/alt" .}} 40 41 {{template "package/content/rubygems" .}} 41 42 {{template "package/content/swift" .}} 42 43 {{template "package/content/vagrant" .}} ··· 68 69 {{template "package/metadata/pub" .}} 69 70 {{template "package/metadata/pypi" .}} 70 71 {{template "package/metadata/rpm" .}} 72 + {{template "package/metadata/alt" .}} 71 73 {{template "package/metadata/rubygems" .}} 72 74 {{template "package/metadata/swift" .}} 73 75 {{template "package/metadata/vagrant" .}}
+658
tests/integration/api_packages_alt_test.go
··· 1 + // Copyright 2023 The Gitea Authors. All rights reserved. 2 + // SPDX-License-Identifier: MIT 3 + 4 + package integration 5 + 6 + import ( 7 + "bytes" 8 + "compress/gzip" 9 + "crypto/sha256" 10 + "encoding/base64" 11 + "encoding/binary" 12 + "fmt" 13 + "io" 14 + "net/http" 15 + "strconv" 16 + "strings" 17 + "testing" 18 + 19 + "code.gitea.io/gitea/models/db" 20 + "code.gitea.io/gitea/models/packages" 21 + "code.gitea.io/gitea/models/unittest" 22 + user_model "code.gitea.io/gitea/models/user" 23 + packages_module "code.gitea.io/gitea/modules/packages" 24 + rpm_module "code.gitea.io/gitea/modules/packages/rpm" 25 + "code.gitea.io/gitea/modules/setting" 26 + "code.gitea.io/gitea/modules/util" 27 + "code.gitea.io/gitea/tests" 28 + 29 + "github.com/stretchr/testify/assert" 30 + "github.com/stretchr/testify/require" 31 + "github.com/ulikunitz/xz" 32 + ) 33 + 34 + func TestPackageAlt(t *testing.T) { 35 + defer tests.PrepareTestEnv(t)() 36 + 37 + packageName := "gitea-test" 38 + packageVersion := "1.0.2-1" 39 + packageArchitecture := "x86_64" 40 + 41 + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) 42 + 43 + base64RpmPackageContent := `H4sICFayB2QCAGdpdGVhLXRlc3QtMS4wLjItMS14ODZfNjQucnBtAO2YV4gTQRjHJzl7wbNhhxVF 44 + VNwk2zd2PdvZ9Sxnd3Z3NllNsmF3o6congVFsWFHRWwIImIXfRER0QcRfPBJEXvvBQvWSfZTT0VQ 45 + 8TF/MuU33zcz3+zOJGEe73lyuQBRBWKWRzDrEddjuVAkxLMc+lsFUOWfm5bvvReAalWECg/TsivU 46 + dyKa0U61aVnl6wj0Uxe4nc8F92hZiaYE8CO/P0r7/Quegr0c7M/AvoCaGZEIWNGUqMHrhhGROIUT 47 + Zc7gOAOraoQzCNZ0WdU0HpEI5jiB4zlek3gT85wqCBomhomxoGCs8wImWMImbxqKgXVNUKKaqShR 48 + STKVKK9glFUNcf2g+/t27xs16v5x/eyOKftVGlIhyiuvvPLKK6+88sorr7zyyiuvvPKCO5HPnz+v 49 + pGVhhXsTsFVeSstuWR9anwU+Bk3Vch5wTwL3JkHg+8C1gR8A169wj1KdpobAj4HbAT+Be5VewE+h 50 + fz/g52AvBX4N9vHAb4AnA7+F8ePAH8BuA38ELgf+BLzQ50oIeBlw0OdAOXAlP57AGuCsbwGtbgCu 51 + DrwRuAb4bwau6T/PwFbgWsDXgWuD/y3gOmC/B1wI/Bi4AcT3Arih3z9YCNzI9w9m/YKUG4Nd9N9z 52 + pSZgHwrcFPgccFt//OADGE+F/q+Ao+D/FrijzwV1gbv4/QvaAHcFDgF3B5aB+wB3Be7rz1dQCtwP 53 + eDxwMcw3GbgU7AasdwzYE8DjwT4L/CeAvRx4IvBCYA3iWQds+FzpDjABfghsAj8BTgA/A/b8+StX 54 + A84A1wKe5s9fuRB4JpzHZv55rL8a/Dv49vpn/PErR4BvQX8Z+Db4l2W5CH2/f0W5+1fEoeFDBzFp 55 + rE/FMcK4mWQSOzN+aDOIqztW2rPsFKIyqh7sQERR42RVMSKihnzVHlQ8Ag0YLBYNEIajkhmuR5Io 56 + 7nlpt2M4nJs0ZNkoYaUyZahMlSfJImr1n1WjFVNCPCaTZgYNGdGL8YN2mX8WHfA/C7ViHJK0pxHG 57 + SrkeTiSI4T+7ubf85yrzRCQRQ5EVxVAjvIBVRY/KRFAVReIkhfARSddNSceayQkGliIKb0q8RAxJ 58 + 5QWNVxHIsW3Pz369bw+5jh5y0klE9Znqm0dF57b0HbGy2A5lVUBTZZrqZjdUjYoprFmpsBtHP5d0 59 + +ISltS2yk2mHuC4x+lgJMhgnidvuqy3b0suK0bm+tw3FMxI2zjm7/fA0MtQhplX2s7nYLZ2ZC0yg 60 + CxJZDokhORTJlrlcCvG5OieGBERlVCs7CfuS6WzQ/T2j+9f92BWxTFEcp2IkYccYGp2LYySEfreq 61 + irue4WRF5XkpKovw2wgpq2rZBI8bQZkzxEkiYaNwxnXCCVvHidzIiB3CM2yMYdNWmjDsaLovaE4c 62 + x3a6mLaTxB7rEj3jWN4M2p7uwPaa1GfI8BHFfcZMKhkycnhR7y781/a+A4t7FpWWTupRUtKbegwZ 63 + XMKwJinTSe70uhRcj55qNu3YHtE922Fdz7FTMTq9Q3TbMdiYrrPudMvT44S6u2miu138eC0tTN9D 64 + 2CFGHHtQsHHsGCRFDFbXuT9wx6mUTZfseydlkWZeJkW6xOgYjqXT+LA7I6XHaUx2xmUzqelWymA9 65 + rCXI9+D1BHbjsITssqhBNysw0tOWjcpmIh6+aViYPfftw8ZSGfRVPUqKiosZj5R5qGmk/8AjjRbZ 66 + d8b3vvngdPHx3HvMeCarIk7VVSwbgoZVkceEVyOmyUmGxBGNYDVKSFSOGlIkGqWnUZFkiY/wsmhK 67 + Mu0UFYgZ/bYnuvn/vz4wtCz8qMwsHUvP0PX3tbYFUctAPdrY6tiiDtcCddDECahx7SuVNP5dpmb5 68 + 9tMDyaXb7OAlk5acuPn57ss9mw6Wym0m1Fq2cej7tUt2LL4/b8enXU2fndk+fvv57ndnt55/cQob 69 + 7tpp/pEjDS7cGPZ6BY430+7danDq6f42Nw49b9F7zp6BiKpJb9s5P0AYN2+L159cnrur636rx+v1 70 + 7ae1K28QbMMcqI8CqwIrgwg9nTOp8Oj9q81plUY7ZuwXN8Vvs8wbAAA=` 71 + rpmPackageContent, err := base64.StdEncoding.DecodeString(base64RpmPackageContent) 72 + require.NoError(t, err) 73 + 74 + zr, err := gzip.NewReader(bytes.NewReader(rpmPackageContent)) 75 + require.NoError(t, err) 76 + 77 + content, err := io.ReadAll(zr) 78 + require.NoError(t, err) 79 + 80 + rootURL := fmt.Sprintf("/api/packages/%s/alt", user.Name) 81 + 82 + for _, group := range []string{"", "el9", "el9/stable"} { 83 + t.Run(fmt.Sprintf("[Group:%s]", group), func(t *testing.T) { 84 + var groupParts []string 85 + uploadURL := rootURL 86 + if group != "" { 87 + groupParts = strings.Split(group, "/") 88 + uploadURL = strings.Join(append([]string{rootURL}, groupParts...), "/") 89 + } else { 90 + groupParts = strings.Split("alt", "/") 91 + } 92 + groupURL := strings.Join(append([]string{rootURL}, groupParts...), "/") 93 + 94 + t.Run("RepositoryConfig", func(t *testing.T) { 95 + defer tests.PrintCurrentTest(t)() 96 + 97 + req := NewRequest(t, "GET", groupURL+".repo") 98 + resp := MakeRequest(t, req, http.StatusOK) 99 + 100 + expected := fmt.Sprintf(`[gitea-%s] 101 + name=%s 102 + baseurl=%s 103 + enabled=1`, 104 + strings.Join(append([]string{user.LowerName}, groupParts...), "-"), 105 + strings.Join(append([]string{user.Name, setting.AppName}, groupParts...), " - "), 106 + util.URLJoin(setting.AppURL, groupURL), 107 + ) 108 + 109 + assert.Equal(t, expected, resp.Body.String()) 110 + }) 111 + 112 + t.Run("Upload", func(t *testing.T) { 113 + url := uploadURL + "/upload" 114 + 115 + req := NewRequestWithBody(t, "PUT", url, bytes.NewReader(content)) 116 + MakeRequest(t, req, http.StatusUnauthorized) 117 + 118 + req = NewRequestWithBody(t, "PUT", url, bytes.NewReader(content)). 119 + AddBasicAuth(user.Name) 120 + MakeRequest(t, req, http.StatusCreated) 121 + 122 + pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeAlt) 123 + require.NoError(t, err) 124 + assert.Len(t, pvs, 1) 125 + 126 + pd, err := packages.GetPackageDescriptor(db.DefaultContext, pvs[0]) 127 + require.NoError(t, err) 128 + assert.Nil(t, pd.SemVer) 129 + assert.IsType(t, &rpm_module.VersionMetadata{}, pd.Metadata) 130 + assert.Equal(t, packageName, pd.Package.Name) 131 + assert.Equal(t, packageVersion, pd.Version.Version) 132 + 133 + pfs, err := packages.GetFilesByVersionID(db.DefaultContext, pvs[0].ID) 134 + require.NoError(t, err) 135 + assert.Len(t, pfs, 1) 136 + assert.Equal(t, fmt.Sprintf("%s-%s.%s.rpm", packageName, packageVersion, packageArchitecture), pfs[0].Name) 137 + assert.True(t, pfs[0].IsLead) 138 + 139 + pb, err := packages.GetBlobByID(db.DefaultContext, pfs[0].BlobID) 140 + require.NoError(t, err) 141 + assert.Equal(t, int64(len(content)), pb.Size) 142 + 143 + req = NewRequestWithBody(t, "PUT", url, bytes.NewReader(content)). 144 + AddBasicAuth(user.Name) 145 + MakeRequest(t, req, http.StatusConflict) 146 + }) 147 + 148 + t.Run("Download", func(t *testing.T) { 149 + defer tests.PrintCurrentTest(t)() 150 + 151 + req := NewRequest(t, "GET", fmt.Sprintf("%s.repo/%s/RPMS.classic/%s-%s.%s.rpm", groupURL, packageArchitecture, packageName, packageVersion, packageArchitecture)) 152 + resp := MakeRequest(t, req, http.StatusOK) 153 + 154 + assert.Equal(t, content, resp.Body.Bytes()) 155 + }) 156 + 157 + t.Run("Repository", func(t *testing.T) { 158 + defer tests.PrintCurrentTest(t)() 159 + 160 + url := fmt.Sprintf("%s.repo/%s/base", groupURL, packageArchitecture) 161 + 162 + req := NewRequest(t, "HEAD", url+"/dummy.xml") 163 + MakeRequest(t, req, http.StatusNotFound) 164 + 165 + req = NewRequest(t, "GET", url+"/dummy.xml") 166 + MakeRequest(t, req, http.StatusNotFound) 167 + 168 + t.Run("release.classic", func(t *testing.T) { 169 + defer tests.PrintCurrentTest(t)() 170 + 171 + req = NewRequest(t, "HEAD", url+"/release.classic") 172 + MakeRequest(t, req, http.StatusOK) 173 + 174 + req = NewRequest(t, "GET", url+"/release.classic") 175 + resp := MakeRequest(t, req, http.StatusOK).Body.String() 176 + 177 + type ReleaseClassic struct { 178 + Archive string 179 + Component string 180 + Origin string 181 + Label string 182 + Architecture string 183 + NotAutomatic bool 184 + } 185 + 186 + var result ReleaseClassic 187 + 188 + lines := strings.Split(resp, "\n") 189 + 190 + for _, line := range lines { 191 + parts := strings.SplitN(line, ": ", 2) 192 + if len(parts) < 2 { 193 + continue 194 + } 195 + 196 + switch parts[0] { 197 + case "Archive": 198 + result.Archive = parts[1] 199 + case "Component": 200 + result.Component = parts[1] 201 + case "Origin": 202 + result.Origin = parts[1] 203 + case "Label": 204 + result.Label = parts[1] 205 + case "Architecture": 206 + result.Architecture = parts[1] 207 + case "NotAutomatic": 208 + notAuto, err := strconv.ParseBool(parts[1]) 209 + if err != nil { 210 + require.NoError(t, err) 211 + } 212 + result.NotAutomatic = notAuto 213 + } 214 + } 215 + 216 + assert.Equal(t, "classic", result.Component) 217 + assert.Equal(t, "Alt Linux Team", result.Origin) 218 + assert.Equal(t, "Forgejo", result.Label) 219 + assert.Equal(t, "x86_64", result.Architecture) 220 + assert.False(t, result.NotAutomatic) 221 + assert.NotEmpty(t, result.Archive) 222 + }) 223 + 224 + t.Run("release", func(t *testing.T) { 225 + defer tests.PrintCurrentTest(t)() 226 + 227 + req = NewRequest(t, "HEAD", url+"/release") 228 + MakeRequest(t, req, http.StatusOK) 229 + 230 + req = NewRequest(t, "GET", url+"/release") 231 + resp := MakeRequest(t, req, http.StatusOK).Body.String() 232 + 233 + type Checksum struct { 234 + Hash string 235 + Size int 236 + File string 237 + } 238 + 239 + type Release struct { 240 + Origin string 241 + Label string 242 + Suite string 243 + Architectures string 244 + MD5Sum []Checksum 245 + BLAKE2B []Checksum 246 + } 247 + 248 + var result Release 249 + 250 + lines := strings.Split(resp, "\n") 251 + 252 + var isMD5Sum, isBLAKE2b bool 253 + 254 + for _, line := range lines { 255 + line = strings.TrimSpace(line) 256 + 257 + if line == "" { 258 + continue 259 + } 260 + switch { 261 + case strings.HasPrefix(line, "Origin:"): 262 + result.Origin = strings.TrimSpace(strings.TrimPrefix(line, "Origin:")) 263 + case strings.HasPrefix(line, "Label:"): 264 + result.Label = strings.TrimSpace(strings.TrimPrefix(line, "Label:")) 265 + case strings.HasPrefix(line, "Suite:"): 266 + result.Suite = strings.TrimSpace(strings.TrimPrefix(line, "Suite:")) 267 + case strings.HasPrefix(line, "Architectures:"): 268 + result.Architectures = strings.TrimSpace(strings.TrimPrefix(line, "Architectures:")) 269 + case line == "MD5Sum:": 270 + isMD5Sum = true 271 + isBLAKE2b = false 272 + case line == "BLAKE2b:": 273 + isBLAKE2b = true 274 + isMD5Sum = false 275 + case isMD5Sum || isBLAKE2b: 276 + parts := strings.Fields(line) 277 + if len(parts) >= 3 { 278 + hash := parts[0] 279 + size, err := strconv.Atoi(parts[1]) 280 + if err != nil { 281 + continue 282 + } 283 + file := parts[2] 284 + 285 + checksum := Checksum{ 286 + Hash: hash, 287 + Size: size, 288 + File: file, 289 + } 290 + 291 + if isMD5Sum { 292 + result.MD5Sum = append(result.MD5Sum, checksum) 293 + } else if isBLAKE2b { 294 + result.BLAKE2B = append(result.BLAKE2B, checksum) 295 + } 296 + } 297 + } 298 + } 299 + 300 + assert.Equal(t, "Forgejo", result.Origin) 301 + assert.Equal(t, "Forgejo", result.Label) 302 + assert.Equal(t, "Sisyphus", result.Suite) 303 + assert.Equal(t, "x86_64", result.Architectures) 304 + 305 + assert.Len(t, result.MD5Sum, 3) 306 + assert.Equal(t, "bbf7ae6b2f540673ed1cfc0266b5f319", result.MD5Sum[0].Hash) 307 + assert.Equal(t, 1003, result.MD5Sum[0].Size) 308 + assert.Equal(t, "base/pkglist.classic", result.MD5Sum[0].File) 309 + 310 + assert.Len(t, result.BLAKE2B, 3) 311 + assert.Equal(t, "b527bf038895ce29107ec3a6d2eebd7c365e8ce5ab767276eeddd7c549a159025225cb0ecfdbf7b71da13db7e865e77bcb0e2dae4d21335df01a4a17e0056a70", result.BLAKE2B[0].Hash) 312 + assert.Equal(t, 1003, result.BLAKE2B[0].Size) 313 + assert.Equal(t, "base/pkglist.classic", result.BLAKE2B[0].File) 314 + }) 315 + 316 + t.Run("pkglist.classic", func(t *testing.T) { 317 + defer tests.PrintCurrentTest(t)() 318 + 319 + req = NewRequest(t, "GET", url+"/pkglist.classic") 320 + resp := MakeRequest(t, req, http.StatusOK) 321 + 322 + body := resp.Body 323 + defer body.Reset() 324 + 325 + type RpmHeader struct { 326 + Magic [8]byte 327 + Nindex uint32 328 + Hsize uint32 329 + } 330 + 331 + type RpmHdrIndex struct { 332 + Tag uint32 333 + Type uint32 334 + Offset uint32 335 + Count uint32 336 + } 337 + 338 + type Metadata struct { 339 + Name string 340 + Version string 341 + Release string 342 + Summary []string 343 + Description []string 344 + BuildTime int 345 + Size int 346 + License string 347 + Packager string 348 + Group []string 349 + URL string 350 + Arch string 351 + SourceRpm string 352 + ProvideNames []string 353 + RequireFlags []int 354 + RequireNames []string 355 + RequireVersions []string 356 + ChangeLogTimes []int 357 + ChangeLogNames []string 358 + ChangeLogTexts []string 359 + ProvideFlags []int 360 + ProvideVersions []string 361 + DirIndexes []int 362 + BaseNames []string 363 + DirNames []string 364 + DistTag string 365 + AptIndexLegacyFileName string 366 + AptIndexLegacyFileSize int 367 + MD5Sum string 368 + BLAKE2B string 369 + AptIndexLegacyDirectory string 370 + } 371 + 372 + var result Metadata 373 + 374 + const rpmHeaderMagic = "\x8e\xad\xe8\x01\x00\x00\x00\x00" 375 + 376 + var hdr RpmHeader 377 + for { 378 + if err := binary.Read(body, binary.BigEndian, &hdr); err != nil { 379 + if err == io.EOF { 380 + break 381 + } 382 + require.NoError(t, err) 383 + } 384 + 385 + if !bytes.Equal(hdr.Magic[:], []byte(rpmHeaderMagic)) { 386 + require.NoError(t, err) 387 + } 388 + 389 + nindex := hdr.Nindex 390 + index := make([]RpmHdrIndex, nindex) 391 + if err := binary.Read(body, binary.BigEndian, &index); err != nil { 392 + require.NoError(t, err) 393 + } 394 + 395 + data := make([]byte, hdr.Hsize) 396 + if err := binary.Read(body, binary.BigEndian, &data); err != nil { 397 + require.NoError(t, err) 398 + } 399 + 400 + var indexPtrs []*RpmHdrIndex 401 + for i := range index { 402 + indexPtrs = append(indexPtrs, &index[i]) 403 + } 404 + 405 + for _, idx := range indexPtrs { 406 + tag := binary.BigEndian.Uint32([]byte{byte(idx.Tag >> 24), byte(idx.Tag >> 16), byte(idx.Tag >> 8), byte(idx.Tag)}) 407 + typ := binary.BigEndian.Uint32([]byte{byte(idx.Type >> 24), byte(idx.Type >> 16), byte(idx.Type >> 8), byte(idx.Type)}) 408 + offset := binary.BigEndian.Uint32([]byte{byte(idx.Offset >> 24), byte(idx.Offset >> 16), byte(idx.Offset >> 8), byte(idx.Offset)}) 409 + count := binary.BigEndian.Uint32([]byte{byte(idx.Count >> 24), byte(idx.Count >> 16), byte(idx.Count >> 8), byte(idx.Count)}) 410 + 411 + if typ == 6 || typ == 8 || typ == 9 { 412 + elem := data[offset:] 413 + for j := uint32(0); j < count; j++ { 414 + strEnd := bytes.IndexByte(elem, 0) 415 + if strEnd == -1 { 416 + require.NoError(t, err) 417 + } 418 + switch tag { 419 + case 1000: 420 + result.Name = string(elem[:strEnd]) 421 + case 1001: 422 + result.Version = string(elem[:strEnd]) 423 + case 1002: 424 + result.Release = string(elem[:strEnd]) 425 + case 1004: 426 + var summaries []string 427 + for i := uint32(0); i < count; i++ { 428 + summaries = append(summaries, string(elem[:strEnd])) 429 + } 430 + result.Summary = summaries 431 + case 1005: 432 + var descriptions []string 433 + for i := uint32(0); i < count; i++ { 434 + descriptions = append(descriptions, string(elem[:strEnd])) 435 + } 436 + result.Description = descriptions 437 + case 1014: 438 + result.License = string(elem[:strEnd]) 439 + case 1015: 440 + result.Packager = string(elem[:strEnd]) 441 + case 1016: 442 + var groups []string 443 + for i := uint32(0); i < count; i++ { 444 + groups = append(groups, string(elem[:strEnd])) 445 + } 446 + result.Group = groups 447 + case 1020: 448 + result.URL = string(elem[:strEnd]) 449 + case 1022: 450 + result.Arch = string(elem[:strEnd]) 451 + case 1044: 452 + result.SourceRpm = string(elem[:strEnd]) 453 + case 1047: 454 + var provideNames []string 455 + for i := uint32(0); i < count; i++ { 456 + provideNames = append(provideNames, string(elem[:strEnd])) 457 + } 458 + result.ProvideNames = provideNames 459 + case 1049: 460 + var requireNames []string 461 + for i := uint32(0); i < count; i++ { 462 + requireNames = append(requireNames, string(elem[:strEnd])) 463 + } 464 + result.RequireNames = requireNames 465 + case 1050: 466 + var requireVersions []string 467 + for i := uint32(0); i < count; i++ { 468 + requireVersions = append(requireVersions, string(elem[:strEnd])) 469 + } 470 + result.RequireVersions = requireVersions 471 + case 1081: 472 + var changeLogNames []string 473 + for i := uint32(0); i < count; i++ { 474 + changeLogNames = append(changeLogNames, string(elem[:strEnd])) 475 + } 476 + result.ChangeLogNames = changeLogNames 477 + case 1082: 478 + var changeLogTexts []string 479 + for i := uint32(0); i < count; i++ { 480 + changeLogTexts = append(changeLogTexts, string(elem[:strEnd])) 481 + } 482 + result.ChangeLogTexts = changeLogTexts 483 + case 1113: 484 + var provideVersions []string 485 + for i := uint32(0); i < count; i++ { 486 + provideVersions = append(provideVersions, string(elem[:strEnd])) 487 + } 488 + result.ProvideVersions = provideVersions 489 + case 1117: 490 + var baseNames []string 491 + for i := uint32(0); i < count; i++ { 492 + baseNames = append(baseNames, string(elem[:strEnd])) 493 + } 494 + result.BaseNames = baseNames 495 + case 1118: 496 + var dirNames []string 497 + for i := uint32(0); i < count; i++ { 498 + dirNames = append(dirNames, string(elem[:strEnd])) 499 + } 500 + result.DirNames = dirNames 501 + case 1155: 502 + result.DistTag = string(elem[:strEnd]) 503 + case 1000000: 504 + result.AptIndexLegacyFileName = string(elem[:strEnd]) 505 + case 1000005: 506 + result.MD5Sum = string(elem[:strEnd]) 507 + case 1000009: 508 + result.BLAKE2B = string(elem[:strEnd]) 509 + case 1000010: 510 + result.AptIndexLegacyDirectory = string(elem[:strEnd]) 511 + } 512 + elem = elem[strEnd+1:] 513 + } 514 + } else if typ == 4 { 515 + elem := data[offset:] 516 + for j := uint32(0); j < count; j++ { 517 + val := binary.BigEndian.Uint32(elem) 518 + switch tag { 519 + case 1006: 520 + result.BuildTime = int(val) 521 + case 1009: 522 + result.Size = int(val) 523 + case 1048: 524 + var requireFlags []int 525 + for i := uint32(0); i < count; i++ { 526 + requireFlags = append(requireFlags, int(val)) 527 + } 528 + result.RequireFlags = requireFlags 529 + case 1080: 530 + var changeLogTimes []int 531 + for i := uint32(0); i < count; i++ { 532 + changeLogTimes = append(changeLogTimes, int(val)) 533 + } 534 + result.ChangeLogTimes = changeLogTimes 535 + case 1112: 536 + var provideFlags []int 537 + for i := uint32(0); i < count; i++ { 538 + provideFlags = append(provideFlags, int(val)) 539 + } 540 + result.ProvideFlags = provideFlags 541 + case 1116: 542 + var dirIndexes []int 543 + for i := uint32(0); i < count; i++ { 544 + dirIndexes = append(dirIndexes, int(val)) 545 + } 546 + result.DirIndexes = dirIndexes 547 + case 1000001: 548 + result.AptIndexLegacyFileSize = int(val) 549 + } 550 + elem = elem[4:] 551 + } 552 + } else { 553 + require.NoError(t, err) 554 + } 555 + } 556 + } 557 + assert.Equal(t, "gitea-test", result.Name) 558 + assert.Equal(t, "1.0.2", result.Version) 559 + assert.Equal(t, "1", result.Release) 560 + assert.Equal(t, []string{"RPM package summary"}, result.Summary) 561 + assert.Equal(t, []string{"RPM package description"}, result.Description) 562 + assert.Equal(t, 1678225964, result.BuildTime) 563 + assert.Equal(t, 13, result.Size) 564 + assert.Equal(t, "MIT", result.License) 565 + assert.Equal(t, "KN4CK3R", result.Packager) 566 + assert.Equal(t, []string{"System"}, result.Group) 567 + assert.Equal(t, "https://gitea.io", result.URL) 568 + assert.Equal(t, "x86_64", result.Arch) 569 + assert.Equal(t, "gitea-test-1.0.2-1.src.rpm", result.SourceRpm) 570 + assert.Equal(t, []string{"", ""}, result.ProvideNames) 571 + assert.Equal(t, []int{16777226, 16777226, 16777226, 16777226, 16777226, 16777226, 16777226}, result.RequireFlags) 572 + assert.Equal(t, []string{"", "", "", "", "", "", ""}, result.RequireNames) 573 + assert.Equal(t, []string{"5.2-1", "5.2-1", "5.2-1", "5.2-1", "5.2-1", "5.2-1", "5.2-1"}, result.RequireVersions) 574 + assert.Equal(t, []int{1678276800}, result.ChangeLogTimes) 575 + assert.Equal(t, []string{"KN4CK3R <dummy@gitea.io>"}, result.ChangeLogNames) 576 + assert.Equal(t, []string{"- Changelog message."}, result.ChangeLogTexts) 577 + assert.Equal(t, []int{8, 8}, result.ProvideFlags) 578 + assert.Equal(t, []string{"1.0.2-1", "1.0.2-1"}, result.ProvideVersions) 579 + assert.Equal(t, []int(nil), result.DirIndexes) 580 + assert.Equal(t, []string{"hello"}, result.BaseNames) 581 + assert.Equal(t, []string{"/usr/local/bin/"}, result.DirNames) 582 + assert.Equal(t, "", result.DistTag) 583 + assert.Equal(t, "gitea-test-1.0.2-1.x86_64.rpm", result.AptIndexLegacyFileName) 584 + assert.Equal(t, 7116, result.AptIndexLegacyFileSize) 585 + assert.Equal(t, "9ea82dd62968719aea19c08cd2ced79a", result.MD5Sum) 586 + assert.Equal(t, "8ba7f1f52a47b23997aa2de21b305cc71974d51f0c54fb53cb927156284dafdcc233d514a46c020e4a0666e218529e0284933c5873d24c2555830d7627140f7d", result.BLAKE2B) 587 + assert.Equal(t, "RPMS.classic", result.AptIndexLegacyDirectory) 588 + }) 589 + 590 + t.Run("pkglist.classic.xz", func(t *testing.T) { 591 + defer tests.PrintCurrentTest(t)() 592 + 593 + req := NewRequest(t, "GET", url+"/pkglist.classic.xz") 594 + pkglistXZResp := MakeRequest(t, req, http.StatusOK) 595 + pkglistXZ := pkglistXZResp.Body 596 + defer pkglistXZ.Reset() 597 + 598 + req2 := NewRequest(t, "GET", url+"/pkglist.classic") 599 + pkglistResp := MakeRequest(t, req2, http.StatusOK) 600 + pkglist := pkglistResp.Body 601 + defer pkglist.Reset() 602 + 603 + assert.Less(t, pkglistXZ.Len(), pkglist.Len()) 604 + 605 + xzReader, err := xz.NewReader(pkglistXZ) 606 + require.NoError(t, err) 607 + 608 + var unxzData bytes.Buffer 609 + _, err = io.Copy(&unxzData, xzReader) 610 + require.NoError(t, err) 611 + 612 + assert.Equal(t, unxzData.Len(), pkglist.Len()) 613 + 614 + content, _ := packages_module.NewHashedBuffer() 615 + defer content.Close() 616 + 617 + h := sha256.New() 618 + w := io.MultiWriter(content, h) 619 + 620 + _, err = io.Copy(w, pkglist) 621 + require.NoError(t, err) 622 + 623 + hashMD5Classic, _, hashSHA256Classic, _, hashBlake2bClassic := content.Sums() 624 + 625 + contentUnxz, _ := packages_module.NewHashedBuffer() 626 + defer contentUnxz.Close() 627 + 628 + _, err = io.Copy(io.MultiWriter(contentUnxz, sha256.New()), &unxzData) 629 + require.NoError(t, err) 630 + 631 + hashMD5Unxz, _, hashSHA256Unxz, _, hashBlake2bUnxz := contentUnxz.Sums() 632 + 633 + assert.Equal(t, fmt.Sprintf("%x", hashSHA256Classic), fmt.Sprintf("%x", hashSHA256Unxz)) 634 + assert.Equal(t, fmt.Sprintf("%x", hashBlake2bClassic), fmt.Sprintf("%x", hashBlake2bUnxz)) 635 + assert.Equal(t, fmt.Sprintf("%x", hashMD5Classic), fmt.Sprintf("%x", hashMD5Unxz)) 636 + }) 637 + }) 638 + 639 + t.Run("Delete", func(t *testing.T) { 640 + defer tests.PrintCurrentTest(t)() 641 + 642 + req := NewRequest(t, "DELETE", fmt.Sprintf("%s.repo/%s/RPMS.classic/%s-%s.%s.rpm", groupURL, packageArchitecture, packageName, packageVersion, packageArchitecture)) 643 + MakeRequest(t, req, http.StatusUnauthorized) 644 + 645 + req = NewRequest(t, "DELETE", fmt.Sprintf("%s.repo/%s/RPMS.classic/%s-%s.%s.rpm", groupURL, packageArchitecture, packageName, packageVersion, packageArchitecture)). 646 + AddBasicAuth(user.Name) 647 + MakeRequest(t, req, http.StatusNoContent) 648 + 649 + pvs, err := packages.GetVersionsByPackageType(db.DefaultContext, user.ID, packages.TypeAlt) 650 + require.NoError(t, err) 651 + assert.Empty(t, pvs) 652 + req = NewRequest(t, "DELETE", fmt.Sprintf("%s.repo/%s/RPMS.classic/%s-%s.%s.rpm", groupURL, packageArchitecture, packageName, packageVersion, packageArchitecture)). 653 + AddBasicAuth(user.Name) 654 + MakeRequest(t, req, http.StatusNotFound) 655 + }) 656 + }) 657 + } 658 + }
+1
web_src/svg/gitea-alt.svg
··· 1 + <svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" id="Layer_1" x="0" y="0" version="1.1" viewBox="0 0 64 64"><style>.st1{fill:#000}.st2{fill:#fff}.st3{fill:#ffc629}</style><path d="m59.43 53.244 4.157-5.973-.139-1.732c-.29-1.508-1.392-2.143-2.12-2.564l-.019-.01q-.123-.071-.25-.147c-.937-.563-1.377-1.073-1.423-1.183l-.047-.145c-.323-.86-.052-2.231.21-3.558l.005-.027c.118-.598.23-1.163.302-1.732.901-6.116.556-9.436-.558-14.559l-.167-.811-.053-.258c-.494-2.42-.921-4.512-1.737-6.776-.172-.474-.408-.904-.62-1.291a9 9 0 0 1-.355-.697l-.15-.279-.012-.019c-.063-.1-.176-.568-.214-.723l-.013-.056c-.1-.473-.133-.992-.166-1.496-.05-.77-.106-1.643-.39-2.516-.37-1.192-1.202-2.348-2.227-3.092-.515-.374-1.068-.54-1.514-.672l-.066-.02a5 5 0 0 0-.317-.08c-.242-.055-.471-.108-.947-.401l-.54-.337c-.792-.497-1.777-1.114-2.729-1.61-.966-.623-2.386-.761-3.396.33l-1.05 1.135 1.196.98c1.86 1.525 2.535 3.226 2.634 3.497q.022.113.046.22l.034.165c-1.78 1.243-2.605 2.63-2.986 3.59-.132.253-.317.65-.595 1.246l-.008.017c-.308.662-.72 1.549-1.186 2.497-1.157-.394-2.237-.586-3.295-.586a7.75 7.75 0 0 0-3.664.893c-2.277 1.095-4 2.934-5.14 5.427H9.892v10.926c-.595-.116-1.2-.16-1.78-.176-.871-.024-1.902-.032-2.937.085-.735.083-2.972.337-4.045 2.153-.29.496-.515 1.095-.71 1.883L0 36.47l1.205.127a3.9 3.9 0 0 0-.94 1.541c-.144.43-.216.9-.216 1.397 0 1.174.465 2.172 1.43 3.058l.044.039c.974.818 2.155 1.216 3.61 1.216.758 0 1.45-.086 2.09-.26h2.67V64h44.109v-4.68l.913-1.337 1.017 1.795H64z" style="fill:#fdc811"/><path d="M5.637 35.127q-.092.17-.18.377L1.91 35.13c.153-.62.324-1.098.545-1.477.58-.982 1.84-1.284 2.894-1.404.898-.101 1.818-.1 2.722-.075 1.262.032 2.697.19 3.534 1.258.494.633.732 1.537.732 2.33v4.221q-.001.278.005.513c.01.526.172 1.106.438 1.558H9.336a1.8 1.8 0 0 1-.395-1.147q-.265.24-.515.422c-.98.73-2.075.986-3.292.986-1.15 0-1.974-.31-2.625-.857-.6-.55-.927-1.151-.927-1.923 0-.33.046-.637.137-.912.268-.831.972-1.41 1.773-1.706a7 7 0 0 1 1.194-.314q.812-.14 1.616-.313c.63-.136 1.263-.297 1.884-.49.263-.085.498-.166.498-.191q.001-.261-.026-.452c-.097-.623-.83-.803-1.377-.803q-.321 0-.633.077c-.448.113-.777.282-1.01.696m3.047 2.334q-.299.123-.595.226c-.372.13-.768.256-1.162.345-.367.082-.74.153-1.072.343-.318.183-.55.447-.55.853 0 .67.643 1.013 1.235 1.013.72 0 1.566-.346 1.918-1.013.174-.325.226-.721.226-1.167zM14.378 28.729h3.721v13.325h-3.721zM24.717 28.73v3.604h2.076v2.796h-2.076v3.377c0 .396.05.721.155.878.069.15.295.305.6.305.326 0 .702-.084 1.166-.24l.308 2.555c-.922.157-1.78.31-2.622.31-.925 0-1.63-.153-2.009-.378a2.5 2.5 0 0 1-.993-1.079c-.258-.482-.326-1.252-.326-2.351V35.13h-1.373v-2.796h1.373v-1.748zM14.535 44.961h3.721v13.36h-3.721zM20.293 44.961h3.704v2.486h-3.704zm0 3.654h3.704v9.707h-3.704zM26.034 48.615h3.501v1.596q.357-.463.734-.803c.8-.717 1.685-.965 2.745-.965.976 0 1.834.327 2.384.925.6.55.926 1.546.926 2.798v6.156h-3.79v-5.334c0-.366.007-.775-.17-1.103-.302-.563-1.055-.727-1.614-.477-.449.203-.758.671-.877 1.133q-.12.458-.119 1.152v4.629h-3.72zM48.186 58.322h-3.429v-1.527q-.302.332-.593.599c-.587.532-1.222.9-2.004 1.059-.295.06-.616.09-.953.09-1.027 0-1.782-.323-2.4-.923-.547-.619-.857-1.525-.857-2.833v-6.172h3.704v5.401c0 .413.09.913.395 1.217.566.57 1.464.477 2.007-.067.43-.432.445-1.285.445-1.85v-4.7h3.685zM49.08 48.615h4.477l1.473 2.676 1.75-2.676h4.097l-3.223 4.63 3.55 5.077h-4.423l-1.75-3.09-2.111 3.09h-4.012l3.551-5.076z" class="st1"/><path d="M54.878 37.246c-.442-.983-.93-1.561-1.544-2.602-1.154-1.64-.923-4.588-.383-6.514a13.8 13.8 0 0 1 1.455-3.666c.357-.62-.216-.855-.498-.432-.63.946-1.01 1.82-1.355 2.78-.56 1.564-.853 3.389-.773 5.028.037.983.188 2.368.108 2.847s-.401.573-.655.401c-2.08-1.66-2.505-5.032-2.424-7.479 0-2.524.96-5.224 1.303-7.749.195-.792-.17-3.048-.115-4.395.023-.613.016-1.226 0-1.839-.026-1.006.034-1.961.115-2.961.084-.885.735-1.12 1.314-.735.27.196.349.446.714.676 1.08.58 1.91-.385 1.697-1.387-.173-.829-.713-1.85-1.617-1.502-.54.19-.463.983-.908 1.328-.618.368-1.274.096-1.83-.29-.398-.275-.586-.562-1.103-.324-.653.3-1.285.936-1.735 1.486-.432.527-.754 1.136-1.002 1.769-.71 1.717-1.541 3.199-2.348 4.878 1.116.693 2.08 1.484 3.064 2.37 1.617 1.447 2.505 3.74 2.003 5.898-.173.945-.79 1.735-1.136 2.583-.091.544-.136 1.287-.345 2.132-.136.55-.348 1.118-.676 1.4-.187.16-.66.237-.536-.131.124-.369.254-1.476.01-2.556-.245-1.08-.902-2.148-1.733-3.371-.57-.838-.588-1.393-.588-1.393s.35-.096.419-.535c.038-.249.29-.786.884-.828.538 0 .537.472 1.12.659.677.216.923-.08 1.042-.407.288-.714-.177-2.158-.716-2.582-.325-.305-.875-.462-1.294-.343-.326.093-.638.428-.572.843s.084.632-.207.459c-.16-.095-.308-.307-.414-.459a3.6 3.6 0 0 0-1.024-1.026c-.576-.397-1.07-.573-1.748-.632s-1.206.016-1.796.25c-1.503.597-2.466 1.889-2.872 3.317-.616 2.08.206 4.3 0 7.11-.192 2.61-.433 3.016-.809 4.237-.343 1.114-.583 1.541-.92 1.955-.27.328-.527.358-.748.056-.216-.296-.192-2.025-.192-2.574 0-.996-.066-1.706-.15-2.185-.085-.48-.522-.451-.595-.003a11.4 11.4 0 0 0-.141 1.746c0 1.59.264 3.536.74 5.117.696 2.2 1.313 3.814 1.313 5.185-.04 1.293-1.447 1.985-1.16 2.025 1.371.02 3.009.075 4.494.02.423 0 .366-.076.792-.504.42-.5.153-1.041.078-1.482-.37-2.7.96-5.86 1.75-8.31.755-2.255 1.467-4.606 1.967-7.383a.176.176 0 0 1 .234-.137c.072.024.113.12.133.197.329 1.275.751 3.18 1.176 4.837.308 1.197.636 2.562 1.501 3.49.85-.08 1.563.713 1.677 1.56.118.83-.114 1.677.33 2.526.463.788.984 1.486 1.293 2.158.423.847.792 1.83.52 3.087 1.484 0 2.505-.155 3.64-.81 2.142-1.274 3.182-3.836 1.776-6.884" class="st2"/><path d="M47.42 43.048c-.583-.96-1.043-2.68-1.043-3.758 0-.616-.443-.406-.772-.406-.347 0-.695.02-.965-.093-.211-.098-.19-.234.04-.272.693-.174 1.1.272 1.58-.675.153-.31.194-.656.098-.965-.114-.364-.5-.557-.943-.578-.31 0-.869.076-.909.462-.022.214.096.504.404.542 0 .134.04.307-.112.366-.235.077-.714-.116-.64 0 .326.462.173 1.003.04 1.485-.151.366.042.925.308 1.462.25.525.56 1.023.656 1.349.173.582.036 1.196.154 1.68.038.25.29.346.346.636.078.193.368.153.444.153.348-.073.424-.443.68-.443.44-.057 1.037-.057 1.326.29.155 0 .25 0 .25-.095 0-.214-.597-.56-.943-1.14" class="st2"/><path d="M46.106 19.803c-.325-.305-.875-.462-1.294-.343-.326.093-.638.428-.572.843s.084.632-.207.459c-.16-.095-.308-.307-.414-.459a3.6 3.6 0 0 0-1.024-1.026c-.576-.397-1.07-.573-1.748-.632l-.06-.005c1.272.352 1.83 1.13 2.166 2.157.28.704.166 1.572.328 2.511q.027.15.083.26l-.007-.072s.35-.096.419-.535c.038-.249.29-.786.884-.828.538 0 .537.472 1.12.658.677.217.923-.079 1.042-.406.288-.714-.177-2.158-.716-2.582M53.837 9.219c-.173-.829-.713-1.85-1.617-1.502-.54.19-.463.983-.908 1.328-.618.368-1.274.096-1.83-.29-.398-.275-.586-.562-1.103-.324-.215.099-.428.235-.633.392a.1.1 0 0 0 .04.045c.42.364 1.126.243 1.567 1.129.388.767.587 1.753.647 2.52v-.008c.019-.613.061-1.22.112-1.844.084-.885.735-1.12 1.314-.735.27.196.349.446.714.676 1.08.58 1.91-.385 1.697-1.387M49.849 5.3c-1.05-1.597-2.819-2.917-3.918-3.438a16.1 16.1 0 0 1 3.294 3.692c.37.504.797.077.624-.254M45.697 26.407c.293 1.343.798 2.52.293 4.324.627-.987.68-2.65.255-4.383-.078-.548-.684-.372-.548.059" class="st3"/><path d="M60.308 44.067c-.762-.459-1.871-1.281-2.075-2.042l-.007-.022c-.47-1.247-.158-2.826.144-4.353.116-.587.225-1.14.294-1.678.87-5.899.535-9.106-.542-14.063q-.114-.546-.22-1.07c-.484-2.368-.902-4.413-1.68-6.574-.134-.367-.332-.73-.524-1.08-.152-.277-.308-.563-.424-.839l-.023-.042c-.21-.308-.325-.782-.426-1.2l-.02-.082c-.123-.58-.161-1.16-.197-1.72-.047-.72-.095-1.462-.327-2.178a4.76 4.76 0 0 0-1.693-2.346c-.333-.241-.747-.356-1.142-.475-.329-.098-.749-.105-1.61-.637-.863-.532-2.1-1.339-3.247-1.93 0 0-.926-.65-1.585.063 2.45 2.008 3.121 4.246 3.121 4.246l.061.297c.184.873.235 1.238-.354 1.524l-.083.053c-1.604 1.083-2.307 2.274-2.614 3.088-.11.196-.32.648-.607 1.266-.44.945-1.095 2.35-1.805 3.726-1.46-.653-2.77-.971-3.998-.971a6.35 6.35 0 0 0-3.016.74c-4.637 2.22-5.79 7.617-5.939 11.754-.08 2.157.313 4.534.694 6.832.435 2.624.885 5.337.6 7.634-.26 1.71-1.21 2.694-2.99 3.096q-.383.008-.52.217a.38.38 0 0 0-.018.37l.063.144h10.623c.473 0 .685-.223.93-.48a4 4 0 0 1 .381-.365l.015-.013c.471-.457.363-1.01.237-1.652-.077-.395-.164-.843-.164-1.393 0-1.893.655-3.765 1.288-5.576l.24-.688c.757-2.189 1.293-3.991 1.687-5.69l.058.269c.159.738.34 1.574.551 2.377.236.89.61 2.135 1.2 3.04q-.074.037-.144.076c-.105.055-.204.108-.292.145-.259.097-.52.205-.774.31-.201.083-.396.163-.577.233a.363.363 0 0 0-.222.47l.006.017c.045.111.142.2.266.246.089.045.225.116.253.153l.024.033c.35.404.483.596.483 1.018 0 .243-.053.41-.11.585-.075.234-.153.476-.078.83.08.426.288.846.508 1.29.267.54.543 1.1.549 1.634-.064.438-.289.585-.598.788-.187.123-.4.262-.581.482l-.026.038a1.37 1.37 0 0 0-.205.733q-.001.073-.01.158c-.018.217-.038.463.115.629.05.055.14.12.283.128a.5.5 0 0 0 .085.008c.275 0 .433-.228.548-.395.028-.04.066-.094.095-.128.071.046.127.133.2.255.111.183.262.435.583.435h16.992c-.189-.98-.947-1.29-1.71-1.748M45.931 1.862c1.1.521 2.869 1.841 3.918 3.438.173.331-.255.758-.624.254a16.1 16.1 0 0 0-3.294-3.692m2.18 42.421c-.288-.347-.886-.347-1.326-.29-.255 0-.331.37-.68.443-.075 0-.365.04-.443-.153-.057-.29-.308-.387-.346-.636-.118-.484.02-1.098-.154-1.68-.097-.326-.407-.824-.656-1.349-.266-.537-.46-1.096-.307-1.462.132-.482.285-1.023-.041-1.485-.074-.116.405.077.64 0 .152-.059.112-.232.112-.366-.308-.038-.426-.328-.404-.542.04-.386.598-.462.91-.462.442.021.828.214.942.578.096.309.055.655-.097.965-.48.947-.888.501-1.58.675-.232.038-.252.174-.04.272.269.113.617.093.964.093.33 0 .772-.21.772.406 0 1.078.46 2.798 1.042 3.758.346.58.942.926.942 1.14 0 .095-.094.095-.25.095m4.99-.153c-1.134.655-2.155.81-3.639.81.272-1.258-.097-2.24-.52-3.087-.31-.672-.83-1.37-1.294-2.158-.443-.85-.21-1.697-.329-2.525-.114-.848-.827-1.64-1.677-1.562-.865-.927-1.193-2.292-1.501-3.489-.425-1.658-.847-3.562-1.176-4.837-.02-.077-.06-.173-.133-.197a.176.176 0 0 0-.234.137c-.5 2.777-1.212 5.128-1.968 7.383-.788 2.45-2.12 5.61-1.75 8.31.076.44.342.981-.077 1.482-.426.428-.37.504-.792.504-1.485.055-3.123 0-4.494-.02-.287-.04 1.12-.732 1.16-2.025 0-1.37-.617-2.986-1.312-5.185-.477-1.581-.741-3.527-.741-5.117 0-.66.068-1.298.141-1.746s.51-.476.594.003c.085.48.15 1.189.15 2.185 0 .55-.023 2.278.193 2.574.221.302.478.272.747-.056.338-.414.578-.841.921-1.955.376-1.221.617-1.626.809-4.236.206-2.81-.616-5.03 0-7.111.406-1.428 1.369-2.72 2.872-3.316.59-.235 1.12-.31 1.796-.251.677.06 1.172.235 1.748.632a3.6 3.6 0 0 1 1.024 1.026c.106.152.254.364.414.459.291.173.273-.044.207-.459s.246-.75.572-.843c.419-.12.969.038 1.294.343.54.424 1.004 1.868.716 2.582-.12.327-.365.623-1.041.406-.584-.186-.583-.658-1.121-.658-.594.042-.846.58-.884.828-.069.439-.42.535-.42.535s.02.555.589 1.393c.83 1.223 1.488 2.29 1.732 3.37.245 1.081.115 2.188-.01 2.557-.123.368.35.291.537.131.328-.282.54-.85.676-1.4.209-.845.254-1.588.345-2.132.347-.848.963-1.638 1.136-2.583.502-2.158-.386-4.451-2.003-5.899-.985-.885-1.948-1.676-3.064-2.369.807-1.679 1.638-3.161 2.348-4.878.248-.633.57-1.242 1.002-1.77.45-.55 1.082-1.185 1.735-1.485.517-.238.705.049 1.102.324.557.386 1.213.658 1.831.29.445-.345.368-1.138.908-1.328.904-.348 1.444.673 1.617 1.502.214 1.002-.617 1.966-1.697 1.387-.365-.23-.444-.48-.714-.676-.58-.385-1.23-.15-1.314.735-.081 1-.141 1.955-.115 2.961.016.613.023 1.226 0 1.84-.055 1.346.31 3.602.115 4.394-.342 2.525-1.303 5.225-1.303 7.749-.081 2.447.344 5.82 2.424 7.479.254.172.575.078.655-.401s-.07-1.864-.108-2.847c-.08-1.64.214-3.464.773-5.028.345-.96.725-1.834 1.355-2.78.282-.423.855-.188.498.432a13.8 13.8 0 0 0-1.455 3.666c-.54 1.926-.771 4.875.383 6.514.615 1.04 1.102 1.62 1.544 2.602 1.406 3.048.366 5.61-1.776 6.884m-7.404-17.723c-.136-.431.47-.607.548-.06.425 1.733.372 3.397-.255 4.384.505-1.804 0-2.981-.293-4.324" class="st1"/><path d="M45.344 36.94c.254 0 .312.178.39.12.12-.12-.136-.37-.295-.37-.252 0-.426.115-.426.25 0 .12.174 0 .331 0" class="st1"/></svg>