Compare commits
	
		
			12 Commits
		
	
	
		
			0533731f69
			...
			master
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 53c39b6f3d | |||
| 55fd94c46b | |||
| def0553167 | |||
| b19af6d421 | |||
| 5014c7ea0a | |||
| 3cb97935eb | |||
| ad1cac023c | |||
| 1f4b1ba97e | |||
| 3165165e73 | |||
| 4f801eff26 | |||
| 8251e8b461 | |||
| c194f4956d | 
| @@ -1,70 +0,0 @@ | ||||
| version: 2 | ||||
| before: | ||||
|   hooks: | ||||
|     - go mod tidy | ||||
| builds: | ||||
|   - id: distrohop | ||||
|     env: | ||||
|       - CGO_ENABLED=0 | ||||
|     binary: distrohop | ||||
|     goos: | ||||
|       - linux | ||||
|     goarch: | ||||
|       - amd64 | ||||
|       - '386' | ||||
|       - arm64 | ||||
|       - arm | ||||
|       - riscv64 | ||||
| archives: | ||||
|   - files: | ||||
|     - distrohop.service | ||||
| nfpms: | ||||
|   - id: distrohop | ||||
|     description: "A utility for correlating and identifying equivalent software packages across different Linux distributions" | ||||
|     homepage: 'https://gitea.elara.ws/Elara6331/distrohop' | ||||
|     maintainer: 'Elara Ivy <elara@elara.ws>' | ||||
|     license: AGPLv3 | ||||
|     formats: | ||||
|       - deb | ||||
|       - rpm | ||||
|       - archlinux | ||||
|     provides: | ||||
|       - distrohop | ||||
|     conflicts: | ||||
|       - distrohop | ||||
|     contents: | ||||
|       - src: distrohop.service | ||||
|         dst: /etc/systemd/system/distrohop.service | ||||
| aurs: | ||||
|   - name: distrohop-bin | ||||
|     homepage: 'https://gitea.elara.ws/Elara6331/distrohop' | ||||
|     description: "A utility for correlating and identifying equivalent software packages across different Linux distributions" | ||||
|     maintainers: | ||||
|       - 'Elara Ivy <elara@elara.ws>' | ||||
|     license: AGPLv3 | ||||
|     private_key: '{{ .Env.AUR_KEY }}' | ||||
|     git_url: 'ssh://aur@aur.archlinux.org/distrohop-bin.git' | ||||
|     provides: | ||||
|       - distrohop | ||||
|     conflicts: | ||||
|       - distrohop | ||||
|     package: |- | ||||
|       # binaries | ||||
|       install -Dm755 ./distrohop "${pkgdir}/usr/bin/distrohop" | ||||
|        | ||||
|       # services | ||||
|       install -Dm644 ./distrohop.service "${pkgdir}/etc/systemd/system/distrohop.service" | ||||
| release: | ||||
|   gitea: | ||||
|     owner: Elara6331 | ||||
|     name: distrohop | ||||
| gitea_urls: | ||||
|   api: 'https://gitea.elara.ws/api/v1/' | ||||
|   download: 'https://gitea.elara.ws' | ||||
|   skip_tls_verify: false | ||||
| checksum: | ||||
|   name_template: 'checksums.txt' | ||||
| snapshot: | ||||
|   name_template: "{{ incpatch .Version }}-next" | ||||
| changelog: | ||||
|   sort: asc | ||||
| @@ -2,7 +2,6 @@ matrix: | ||||
|   platform: | ||||
|     - linux/amd64 | ||||
|     - linux/arm64 | ||||
|     - linux/riscv64 | ||||
|      | ||||
| labels: | ||||
|   platform: ${platform} | ||||
| @@ -12,6 +11,10 @@ steps: | ||||
|     image: golang:1.23.6 | ||||
|     commands: | ||||
|       - go build | ||||
|     environment: | ||||
|       CGO_ENABLED: 0 | ||||
|     when: | ||||
|       - event: tag | ||||
| 
 | ||||
|   - name: docker | ||||
|     image: woodpeckerci/plugin-kaniko | ||||
| @@ -22,7 +25,7 @@ steps: | ||||
|       cache: true | ||||
|       username: elara6331 | ||||
|       password: | ||||
|         from_secret: registry_password | ||||
|         from_secret: gitea_token | ||||
|     when: | ||||
|       event: tag | ||||
| 
 | ||||
| @@ -39,7 +42,7 @@ steps: | ||||
|       event: tag | ||||
|    | ||||
|   - name: release | ||||
|     image: woodpeckerci/plugin-release | ||||
|     image: gitea.elara.ws/elara6331/woodpecker-release | ||||
|     settings: | ||||
|       title: "Version ${CI_COMMIT_TAG##v}" | ||||
|       files: | ||||
| @@ -50,11 +53,3 @@ steps: | ||||
|         from_secret: gitea_token | ||||
|     when: | ||||
|       event: tag | ||||
|        | ||||
|   - name: manifest | ||||
|     image: mplatform/manifest-tool | ||||
|     commands: | ||||
|       - manifest-tool push from-args --platforms linux/amd64,linux/arm64,linux/riscv64 --template gitea.elara.ws/elara6331/distrohop:ARCH --tags ${CI_COMMIT_TAG} --target gitea.elara.ws/elara6331/distrohop:latest | ||||
|     when: | ||||
|       event: tag | ||||
|       platform: linux/amd64 | ||||
							
								
								
									
										19
									
								
								.woodpecker/manifest.yml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								.woodpecker/manifest.yml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| labels: | ||||
|   platform: linux/amd64 | ||||
|    | ||||
| steps: | ||||
|   - name: manifest | ||||
|     image: gcr.io/go-containerregistry/crane:debug | ||||
|     entrypoint: ["/busybox/sh", "-c", "echo $CI_SCRIPT | base64 -d | /busybox/sh -e"] | ||||
|     commands: | ||||
|       - "crane auth login -u elara6331 -p $REGISTRY_TOKEN gitea.elara.ws" | ||||
|       - "crane index append -m gitea.elara.ws/elara6331/distrohop:amd64 -m gitea.elara.ws/elara6331/distrohop:arm64 -t gitea.elara.ws/elara6331/distrohop:latest" | ||||
|       - "crane index append -m gitea.elara.ws/elara6331/distrohop:amd64 -m gitea.elara.ws/elara6331/distrohop:arm64 -t gitea.elara.ws/elara6331/distrohop:${CI_COMMIT_TAG}" | ||||
|     environment: | ||||
|       REGISTRY_TOKEN: | ||||
|         from_secret: gitea_token | ||||
|     when: | ||||
|       - event: tag | ||||
|    | ||||
| depends_on: | ||||
|   - build | ||||
| @@ -1,10 +1,14 @@ | ||||
| <p align="center"> | ||||
|     <img width="250" src="assets/logo/distrohop-text-bottom.svg"> | ||||
| </p> | ||||
| <p align="center"> | ||||
|     <img alt="GitHub go.mod Go version" src="https://img.shields.io/github/go-mod/go-version/Elara6331/distrohop?style=for-the-badge">  | ||||
|     <a href="https://goreportcard.com/report/go.elara.ws/distrohop"><img src="https://goreportcard.com/badge/go.elara.ws/distrohop?style=for-the-badge"></a> | ||||
| </p> | ||||
|  | ||||
| --- | ||||
|  | ||||
| **DistroHop** is a tool that helps you compare Linux packages across different distributions. It lets you search for a package from one distro in another's repositories or look for a specific item you need. | ||||
| **DistroHop** is a tool that helps you compare Linux packages across different distributions. It lets you search for a package from one distro in another's repositories or look for a specific item you need. You can try it out at [hop.lure.sh](https://hop.lure.sh/) | ||||
|  | ||||
| ## How does it work? | ||||
|  | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
| iconset="${1%%:*}" | ||||
| icon="${1#*:}" | ||||
|  | ||||
| BASE_DIR="./cmd/distrohop/assets/icons" | ||||
| BASE_DIR="./assets/icons" | ||||
|  | ||||
| mkdir -p "$BASE_DIR/$iconset" | ||||
| wget -4 -O "$BASE_DIR/$iconset/$icon.svg" "https://api.iconify.design/$iconset/$icon.svg" | ||||
|   | ||||
							
								
								
									
										1
									
								
								assets/icons/material-symbols/info-outline.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								assets/icons/material-symbols/info-outline.svg
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| <svg id="icon" xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 24 24"><path fill="currentColor" d="M11 17h2v-6h-2zm1-8q.425 0 .713-.288T13 8t-.288-.712T12 7t-.712.288T11 8t.288.713T12 9m0 13q-2.075 0-3.9-.788t-3.175-2.137T2.788 15.9T2 12t.788-3.9t2.137-3.175T8.1 2.788T12 2t3.9.788t3.175 2.137T21.213 8.1T22 12t-.788 3.9t-2.137 3.175t-3.175 2.138T12 22m0-2q3.35 0 5.675-2.325T20 12t-2.325-5.675T12 4T6.325 6.325T4 12t2.325 5.675T12 20m0-8"/></svg> | ||||
| After Width: | Height: | Size: 474 B | 
							
								
								
									
										20
									
								
								internal/index/common.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								internal/index/common.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,20 @@ | ||||
| package index | ||||
|  | ||||
| import "strings" | ||||
|  | ||||
| type repomd struct { | ||||
| 	Locations []location `xml:"data>location"` | ||||
| } | ||||
|  | ||||
| type location struct { | ||||
| 	Href string `xml:"href,attr"` | ||||
| } | ||||
|  | ||||
| func (r repomd) getFilelists() string { | ||||
| 	for _, loc := range r.Locations { | ||||
| 		if strings.Contains(loc.Href, "filelists.xml") { | ||||
| 			return loc.Href | ||||
| 		} | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
| @@ -27,7 +27,6 @@ import ( | ||||
| 	"context" | ||||
| 	"encoding/xml" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
| @@ -37,23 +36,6 @@ import ( | ||||
| 	"go.elara.ws/distrohop/internal/tags" | ||||
| ) | ||||
|  | ||||
| type repomd struct { | ||||
| 	Locations []location `xml:"data>location"` | ||||
| } | ||||
|  | ||||
| type location struct { | ||||
| 	Href string `xml:"href,attr"` | ||||
| } | ||||
|  | ||||
| func (r repomd) getGzipFile() string { | ||||
| 	for _, loc := range r.Locations { | ||||
| 		if strings.HasSuffix(loc.Href, "filelists.xml.gz") { | ||||
| 			return loc.Href | ||||
| 		} | ||||
| 	} | ||||
| 	return "<unknown>" | ||||
| } | ||||
|  | ||||
| type DNF struct{} | ||||
|  | ||||
| func (DNF) Name() string { | ||||
| @@ -65,10 +47,9 @@ func (DNF) IndexURL(baseURL, version, repo, arch string) ([]string, error) { | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	repomdPath := fmt.Sprintf("/pub/fedora/linux/releases/%s/%s/%s/os/repodata/repomd.xml", version, repo, arch) | ||||
| 	u.Path = repomdPath | ||||
| 	 | ||||
| 	res, err := http.Get(u.String()) | ||||
| 	repomdURL := u.JoinPath("linux/releases", version, repo, arch, "os/repodata/repomd.xml") | ||||
| 	res, err := http.Get(repomdURL.String()) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| @@ -80,13 +61,13 @@ func (DNF) IndexURL(baseURL, version, repo, arch string) ([]string, error) { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	gzipFile := data.getGzipFile() | ||||
| 	if gzipFile == "" { | ||||
| 		return nil, errors.New("no gzip file found in repomd.xml") | ||||
| 	filelists := data.getFilelists() | ||||
| 	if filelists == "" { | ||||
| 		return nil, errors.New("no filelists found in repomd.xml") | ||||
| 	} | ||||
| 	 | ||||
| 	u.Path = fmt.Sprintf("/pub/fedora/linux/releases/%s/%s/%s/os/%s", version, repo, arch, gzipFile) | ||||
| 	return []string{u.String()}, nil | ||||
| 	filelistsURL := u.JoinPath("linux/releases", version, repo, arch, "os", filelists) | ||||
| 	return []string{filelistsURL.String()}, nil | ||||
| } | ||||
|  | ||||
| func (DNF) ReadPkgData(r io.Reader, out chan Record) { | ||||
|   | ||||
| @@ -47,6 +47,7 @@ var importers = []Importer{ | ||||
| 	APT{}, | ||||
| 	DNF{}, | ||||
| 	Pacman{}, | ||||
| 	Zypper{}, | ||||
| } | ||||
|  | ||||
| // GetImporter gets an importer by its name | ||||
|   | ||||
							
								
								
									
										69
									
								
								internal/index/zypper.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								internal/index/zypper.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,69 @@ | ||||
| /* | ||||
|  * distrohop - A utility for correlating and identifying equivalent software | ||||
|  * packages across different Linux distributions | ||||
|  * | ||||
|  * Copyright (C) 2025 Elara Ivy <elara@elara.ws> | ||||
|  * | ||||
|  * This file is part of distrohop. | ||||
|  * | ||||
|  * distrohop is free software: you can redistribute it and/or modify | ||||
|  * it under the terms of the GNU Affero General Public License as | ||||
|  * published by the Free Software Foundation, either version 3 of the | ||||
|  * License, or (at your option) any later version. | ||||
|  * | ||||
|  * distrohop is distributed in the hope that it will be useful, | ||||
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|  * GNU Affero General Public License for more details. | ||||
|  * | ||||
|  * You should have received a copy of the GNU Affero General Public License | ||||
|  * along with distrohop.  If not, see <http://www.gnu.org/licenses/>. | ||||
|  */ | ||||
|  | ||||
| package index | ||||
|  | ||||
| import ( | ||||
| 	"encoding/xml" | ||||
| 	"errors" | ||||
| 	"io" | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
| ) | ||||
|  | ||||
|  type Zypper struct{} | ||||
|   | ||||
|  func (Zypper) Name() string { | ||||
| 	return "zypper" | ||||
|  } | ||||
|   | ||||
|  func (Zypper) IndexURL(baseURL, version, repo, _ string) ([]string, error) { | ||||
| 	u, err := url.ParseRequestURI(baseURL) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	 | ||||
| 	repomdURL := u.JoinPath(version, "repo", repo, "repodata/repomd.xml") | ||||
| 	res, err := http.Get(repomdURL.String()) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	defer res.Body.Close() | ||||
|   | ||||
| 	var data repomd | ||||
| 	err = xml.NewDecoder(res.Body).Decode(&data) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|   | ||||
| 	gzipFile := data.getFilelists() | ||||
| 	if gzipFile == "" { | ||||
| 		return nil, errors.New("no filelists found in repomd.xml") | ||||
| 	} | ||||
|   | ||||
| 	filelistURL := u.JoinPath(version, "repo", repo, gzipFile) | ||||
| 	return []string{filelistURL.String()}, nil | ||||
|  } | ||||
|   | ||||
|  func (Zypper) ReadPkgData(r io.Reader, out chan Record) { | ||||
|  	DNF{}.ReadPkgData(r, out) | ||||
|  } | ||||
							
								
								
									
										2
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								main.go
									
									
									
									
									
								
							| @@ -97,7 +97,7 @@ func main() { | ||||
| 				if err == nil { | ||||
| 					// Add the index store to the combined store for the repo | ||||
| 					cs.Add(s) | ||||
| 				} else if err != nil { | ||||
| 				} else { | ||||
| 					log.Error("Error opening database", slog.Any("error", err)) | ||||
| 					os.Exit(1) | ||||
| 				} | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| <!DOCTYPE html> | ||||
| <html> | ||||
| <html data-theme="dark"> | ||||
|     <head> | ||||
|         <meta charset="utf-8"> | ||||
|         <meta name="viewport" content="width=device-width, initial-scale=1"> | ||||
|   | ||||
| @@ -54,7 +54,15 @@ | ||||
|  | ||||
|     <div x-cloak x-transition:enter x-show="activeTab == 'pkg'" class="columns"> | ||||
|         <form x-data="{'suggestions': []}" class="column is-half is-offset-one-quarter has-text-centered" action="/search/pkg"> | ||||
|             <label class="label" for="from">Search For:</label> | ||||
|             <label class="label mb-0" for="from">Search For:</label> | ||||
|             <div class="icon-text has-text-grey"> | ||||
|                 <span class="icon is-aligned">#icon("material-symbols/info-outline")</span> | ||||
|                 <p class="is-size-7 has-text-grey"> | ||||
|                     Try searching for archlinux | ||||
|                     <a href="/search/pkg?from=archlinux&pkg=firefox&in=debian-bookworm"><code>firefox</code></a> | ||||
|                     in debian-bookworm. | ||||
|                 </p> | ||||
|             </div> | ||||
|             <div class="field has-addons is-align-self-stretch" id="from"> | ||||
|                 <div class="control"> | ||||
|                     <span class="select"> | ||||
| @@ -80,12 +88,11 @@ | ||||
|                 </div> | ||||
|             </div> | ||||
|  | ||||
|             <label class="label" for="in">In:</label> | ||||
|             <div class="field is-align-self-stretch" id="in"> | ||||
|                 <p class="control"> | ||||
|                     <span class="select is-fullwidth"> | ||||
|                         <select name="in" autocomplete="off" required> | ||||
|                             <option selected disabled value="">Select Repo...</option> | ||||
|                             <option selected disabled value="">Search In...</option> | ||||
|                             #for(repo in cfg.Repos): | ||||
|                                 <option>#(repo.Name)</option> | ||||
|                             #!for | ||||
| @@ -96,7 +103,7 @@ | ||||
|  | ||||
|             <div class="field mt-4 is-align-self-stretch"> | ||||
|                 <p class="control"> | ||||
|                     <button class="button is-dark is-rounded is-fullwidth" type="submit"> | ||||
|                     <button class="button is-info is-inverted is-rounded is-fullwidth" type="submit"> | ||||
|                         <div class="icon-text"> | ||||
|                             <span class="icon is-aligned m-0">#icon("map/search")</span> | ||||
|                             <span>Search</span> | ||||
| @@ -110,11 +117,17 @@ | ||||
|     <div x-cloak x-data="{tags: []}" x-transition:enter x-show="activeTab == 'tags'" class="columns"> | ||||
|         <div class="column is-half is-offset-one-quarter has-text-centered"> | ||||
|             <form action="/search/tags" x-ref="tagsForm"> | ||||
|                 <template x-if="tags.length == 0"> | ||||
|                     <div class="has-text-centered"> | ||||
|                         <p class="is-size-5">Tags you add will appear here...</p> | ||||
|                     </div> | ||||
|                 </template> | ||||
|                  | ||||
|                 <div class="field is-grouped is-grouped-multiline"> | ||||
|                     <template x-for="(tag, idx) in tags"> | ||||
|                         <div> | ||||
|                             <div class="tags has-addons"> | ||||
|                                 <span class="tag is-dark has-background-info-dark has-text-info-light" x-text="tag[0]"></span><span class="tag is-dark" x-text="tag[1]"></span><a class="tag is-delete m-0" @click.prevent="tags.splice(idx, 1)"></a> | ||||
|                                 <span class="tag is-dark has-background-info-dark has-text-info-light" x-text="tag[0]"></span><span class="tag is-dark" x-text="tag[1]"></span><a class="tag is-dark is-delete m-0" @click.prevent="tags.splice(idx, 1)"></a> | ||||
|                             </div> | ||||
|                             <input class="is-hidden" name="tag" :value="tag.join('=')"> | ||||
|                         </div> | ||||
| @@ -122,7 +135,17 @@ | ||||
|                     <span></span> | ||||
|                 </div> | ||||
|                  | ||||
|                 <div class="mt-5 field has-addons"> | ||||
|                 <div class="icon-text mt-5 has-text-grey"> | ||||
|                     <span class="icon is-aligned">#icon("material-symbols/info-outline")</span> | ||||
|                     <p class="is-size-7 has-text-grey"> | ||||
|                         Try searching for | ||||
|                         <code class="is-clickable" @click="tags.push(['lib', 'pcre2-8'])">lib=pcre2-8</code>, | ||||
|                         <code class="is-clickable" @click="tags.push(['lib', 'libaudit.so.1'])">lib=libaudit.so.1</code>, | ||||
|                         <code class="is-clickable" @click="tags.push(['bin', 'firefox'])">bin=firefox</code>, | ||||
|                         etc. | ||||
|                     </p> | ||||
|                 </div> | ||||
|                 <div class="mt-0 field has-addons"> | ||||
|                     <div class="control is-expanded"> | ||||
|                         <input @keydown.comma.prevent="pushTag(tags, $refs.newTagInput)" class="input" x-ref="newTagInput" placeholder="bin=nano"> | ||||
|                     </div> | ||||
| @@ -149,7 +172,7 @@ | ||||
|                     </p> | ||||
|                 </div> | ||||
|                  | ||||
|                 <button class="button is-dark is-rounded is-fullwidth" type="submit"> | ||||
|                 <button class="button is-info is-inverted is-rounded is-fullwidth" type="submit"> | ||||
|                     <div class="icon-text"> | ||||
|                         <span class="icon is-aligned m-0">#icon("map/search")</span> | ||||
|                         <span>Search</span> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user