├── .devcontainer └── devcontainer.json ├── .github └── workflows │ ├── release.yml │ └── test.yml ├── .gitignore ├── LICENSE ├── README.md ├── go.mod ├── go.sum ├── main.go ├── pkg ├── arch.go ├── command_utils.go ├── constants.go ├── file_utils.go ├── http_client.go ├── log_utils.go ├── path_utils.go ├── sync.go ├── xbps.go └── xdeb.go ├── scripts ├── build.sh ├── changelog.sh └── test.sh └── tests ├── __init__.py ├── constants.py ├── helpers.py ├── test_00_general.py ├── test_10_xdeb.py ├── test_20_providers.py ├── test_30_sync.py ├── test_40_search.py ├── test_50_install.py └── test_60_clean.py /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "xdeb-install", 3 | "image": "ghcr.io/xdeb-org/xdeb-install:devcontainer", 4 | 5 | "customizations": { 6 | "vscode": { 7 | "extensions": [ 8 | "golang.Go", 9 | "yzhang.markdown-all-in-one", 10 | "ms-python.python" 11 | ], 12 | "settings": { 13 | "telemetry.telemetryLevel": "off", 14 | "security.workspace.trust.emptyWindow": false, 15 | "security.workspace.trust.enabled": false, 16 | "extensions.ignoreRecommendations": true, 17 | "dev.containers.copyGitConfig": false, 18 | "dev.containers.gitCredentialHelperConfigLocation": "none", 19 | "dev.containers.dockerCredentialHelper": false, 20 | "terminal.integrated.showExitAlert": false, 21 | "terminal.integrated.profiles.linux": { 22 | "bash": { 23 | "path": "bash", 24 | "icon": "terminal-bash" 25 | } 26 | }, 27 | "terminal.integrated.defaultProfile.linux": "bash", 28 | "explorer.autoReveal": false, 29 | "explorer.confirmDragAndDrop": false, 30 | "files.trimTrailingWhitespace": true, 31 | "markdown.extension.toc.levels": "2..3", 32 | "markdown.extension.toc.updateOnSave": false 33 | } 34 | } 35 | }, 36 | 37 | "mounts": [ 38 | "source=${localEnv:HOME}/.bash_history,target=/home/user/.bash_history,type=bind,consistency=cached", 39 | "source=${localEnv:HOME}/.gitconfig,target=/home/user/.gitconfig,type=bind,consistency=cached", 40 | "source=${localEnv:HOME}/.ssh,target=/home/user/.ssh,type=bind,consistency=cached", 41 | "source=/etc/resolv.conf,target=/etc/resolv.conf,type=bind,consistency=cached" 42 | ], 43 | 44 | "containerEnv": { 45 | "EDITOR": "vim", 46 | "GIT_EDITOR": "vim" 47 | }, 48 | 49 | "postCreateCommand": "go get", 50 | "remoteUser": "user" 51 | } 52 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # Copied and modified from k0sctl: https://github.com/k0sproject/k0sctl/blob/main/.github/workflows/release.yaml 2 | 3 | name: Release 4 | 5 | on: 6 | push: 7 | tags: 8 | - 'v*' 9 | 10 | jobs: 11 | build-and-test: 12 | permissions: 13 | contents: read 14 | packages: read 15 | runs-on: ubuntu-latest 16 | container: 17 | image: ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}:testcontainer 18 | steps: 19 | - name: Update Void Linux system 20 | run: xbps-install -Syu xbps && xbps-install -yu 21 | 22 | - name: Check out repository 23 | uses: actions/checkout@v4 24 | 25 | - name: Add safe git directory 26 | run: git config --global --add safe.directory /__w/xdeb-install/xdeb-install 27 | 28 | # Ugly hack to get the tag name 29 | # github.ref gives the full reference like refs.tags.v0.0.1-beta1 30 | - name: Branch name 31 | id: branch_name 32 | run: | 33 | echo "TAG_NAME=${GITHUB_REF#refs/tags/}" >> ${GITHUB_OUTPUT} 34 | 35 | - name: Unshallow git history 36 | env: 37 | TAG_NAME: ${{ steps.branch_name.outputs.TAG_NAME }} 38 | run: | 39 | git fetch --unshallow 40 | git checkout ${TAG_NAME} 41 | 42 | - name: Build binaries 43 | run: ./scripts/build.sh 44 | 45 | - name: Run tests and generate HTML report 46 | run: ./scripts/test.sh html 47 | 48 | - name: Upload HTML report 49 | uses: actions/upload-artifact@v3 50 | with: 51 | name: html 52 | path: html 53 | 54 | - name: Upload binaries 55 | uses: actions/upload-artifact@v3 56 | with: 57 | name: bin 58 | path: bin 59 | 60 | report: 61 | needs: build-and-test 62 | permissions: 63 | contents: read 64 | runs-on: ubuntu-latest 65 | steps: 66 | - name: Download HTML report artifact 67 | uses: actions/download-artifact@v3 68 | with: 69 | name: html 70 | path: html 71 | 72 | - name: Rename HTML report to index.html 73 | run: mv html/*.html html/index.html 74 | 75 | - name: Upload Pages artifact 76 | uses: actions/upload-pages-artifact@v2 77 | with: 78 | path: html 79 | 80 | deploy-report: 81 | needs: report 82 | runs-on: ubuntu-latest 83 | permissions: 84 | pages: write 85 | id-token: write 86 | environment: 87 | name: github-pages 88 | url: ${{ steps.deployment.outputs.page_url }} 89 | steps: 90 | - name: Deploy to GitHub Pages 91 | id: deployment 92 | uses: actions/deploy-pages@v2 93 | 94 | release: 95 | needs: deploy-report 96 | permissions: 97 | contents: write 98 | runs-on: ubuntu-latest 99 | steps: 100 | - name: Check out repository 101 | uses: actions/checkout@v4 102 | 103 | - name: Add safe git directory 104 | run: git config --global --add safe.directory /__w/xdeb-install/xdeb-install 105 | 106 | # Ugly hack to get the tag name 107 | # github.ref gives the full reference like refs.tags.v0.0.1-beta1 108 | - name: Branch name 109 | id: branch_name 110 | run: | 111 | echo "TAG_NAME=${GITHUB_REF#refs/tags/}" >> ${GITHUB_OUTPUT} 112 | 113 | - name: Unshallow git history 114 | env: 115 | TAG_NAME: ${{ steps.branch_name.outputs.TAG_NAME }} 116 | run: | 117 | git fetch --unshallow 118 | git checkout ${TAG_NAME} 119 | 120 | - name: Download binaries artifact 121 | uses: actions/download-artifact@v3 122 | with: 123 | name: bin 124 | path: bin 125 | 126 | - uses: actions/setup-go@v4 127 | with: 128 | go-version-file: 'go.mod' 129 | 130 | - name: Generate changelog 131 | run: ./scripts/changelog.sh 132 | 133 | - name: Create release and upload binaries 134 | uses: softprops/action-gh-release@v1 135 | if: startsWith(github.ref, 'refs/tags/') 136 | with: 137 | files: | 138 | bin/* 139 | body_path: bin/changelog.md 140 | tag_name: ${{ steps.branch_name.outputs.TAG_NAME }} 141 | name: ${{ steps.branch_name.outputs.TAG_NAME }} 142 | draft: true # So we can manually edit before publishing 143 | prerelease: ${{ contains(steps.branch_name.outputs.TAG_NAME, '-') }} # v0.1.2-beta1, 1.2.3-rc1 144 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Run tests 2 | 3 | # should also run on pull requests I guess 4 | on: 5 | # manually 6 | workflow_dispatch: 7 | # each night at 0:00 8 | schedule: 9 | - cron: "0 0 * * *" 10 | 11 | jobs: 12 | tests: 13 | name: tests 14 | permissions: 15 | contents: read 16 | packages: read 17 | runs-on: ubuntu-latest 18 | container: 19 | image: ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}:testcontainer 20 | steps: 21 | - name: Update Void Linux system 22 | run: xbps-install -Syu xbps && xbps-install -yu 23 | 24 | - name: Check out repository 25 | uses: actions/checkout@v4 26 | 27 | - name: Build binaries 28 | run: | 29 | git config --global --add safe.directory /__w/xdeb-install/xdeb-install 30 | ./scripts/build.sh 31 | 32 | - name: Run tests 33 | run: ./scripts/test.sh 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin 2 | html 3 | **/.pytest_cache 4 | **/__pycache__ 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Timo Reichl 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) 2 | [![devcontainer Status](https://img.shields.io/github/actions/workflow/status/xdeb-org/xdeb-install/devcontainer.yml?label=devcontainer)](https://github.com/xdeb-org/xdeb-install/actions/workflows/devcontainer.yml) 3 | [![testcontainer Status](https://img.shields.io/github/actions/workflow/status/xdeb-org/xdeb-install/testcontainer.yml?label=testcontainer)](https://github.com/xdeb-org/xdeb-install/actions/workflows/testcontainer.yml) 4 | [![nightly test Status](https://img.shields.io/github/actions/workflow/status/xdeb-org/xdeb-install/test.yml?label=nightly%20tests)](https://github.com/xdeb-org/xdeb-install/actions/workflows/test.yml?query=event%3Aschedule+branch%3Amain) 5 | [![release Status](https://img.shields.io/github/actions/workflow/status/xdeb-org/xdeb-install/release.yml?label=release)](https://github.com/xdeb-org/xdeb-install/actions/workflows/release.yml) 6 | 7 | # xdeb-install 8 | 9 | Simple tool to automatically download, convert, and install DEB packages on [Void Linux](https://voidlinux.org) via the awesome [`xdeb`](https://github.com/toluschr/xdeb) tool. Basically just a wrapper to automate the process. 10 | 11 | Each release generates a test report which is uploaded to https://xdeb-org.github.io/xdeb-install. 12 | 13 | # Table of Contents 14 | 15 | - [Known Limitations](#known-limitations) 16 | - [Help Page](#help-page) 17 | - [Installation](#installation) 18 | - [Using XBPS](#using-xbps) 19 | - [Using Go](#using-go) 20 | - [Manually](#manually) 21 | - [Listing available providers](#listing-available-providers) 22 | - [With details redacted](#with-details-redacted) 23 | - [With details (distributions and components)](#with-details-distributions-and-components) 24 | - [Managing package repositories](#managing-package-repositories) 25 | - [Syncing package repositories](#syncing-package-repositories) 26 | - [Supported package repositories](#supported-package-repositories) 27 | - [Searching for DEB packages](#searching-for-deb-packages) 28 | - [General instructions](#general-instructions) 29 | - [Search filtering by provider/distribution](#search-filtering-by-providerdistribution) 30 | - [Inexact matches](#inexact-matches) 31 | - [Installing DEB packages](#installing-deb-packages) 32 | - [From remote repositories](#from-remote-repositories) 33 | - [Directly from a URL](#directly-from-a-url) 34 | - [Directly from a local file](#directly-from-a-local-file) 35 | 36 | ## Known Limitations 37 | 38 | Before reading any further, I would like to make you aware of some known limitations of this tool. Please take a look at [issue #19](https://github.com/xdeb-org/xdeb-install/issues/19) for details. 39 | 40 | ## Help Page 41 | 42 | To display the help page, type: 43 | ``` 44 | $ xdeb-install -h 45 | ``` 46 | 47 | Example output: 48 | ``` 49 | NAME: 50 | xdeb-install - Automation wrapper for the xdeb utility 51 | 52 | USAGE: 53 | xdeb-install [global options (except --file)] 54 | xdeb-install [global options] command [command options] [arguments...] 55 | 56 | VERSION: 57 | v2.2.5-5-gd99358a 58 | 59 | DESCRIPTION: 60 | Simple tool to automatically download, convert, and install DEB packages via the awesome xdeb utility. 61 | Basically just a wrapper to automate the process. 62 | 63 | AUTHORS: 64 | Timo Reichl 65 | toluschr 66 | 67 | COMMANDS: 68 | xdeb install the xdeb utility to the system along with its dependencies 69 | providers, p list available providers 70 | sync, S synchronize remote repositories 71 | search, s search remote repositories for a package 72 | clean, c cleanup temporary xdeb context root path, optionally the repository lists as well 73 | help, h Shows a list of commands or help for one command 74 | 75 | GLOBAL OPTIONS: 76 | --file value, -f value install a package from a local DEB file or remote URL 77 | --provider value, -p value limit search results to a specific provider when --file is not passed 78 | --distribution value, --dist value, -d value limit search results to a specific distribution (requires --provider) 79 | --options value, -o value override XDEB_OPTS, '-i' will be removed if provided (default: "-Sde") 80 | --temp value, -t value set the temporary xdeb context root path (default: "/tmp/xdeb") 81 | --help, -h show help 82 | --version, -v print the version 83 | ``` 84 | 85 | #### xdeb 86 | 87 | ``` 88 | $ xdeb-install xdeb 89 | NAME: 90 | xdeb-install xdeb [release version] - install the xdeb utility to the system along with its dependencies 91 | 92 | USAGE: 93 | xdeb-install xdeb [release version] [command options] [arguments...] 94 | 95 | OPTIONS: 96 | --help, -h show help 97 | ``` 98 | 99 | To install the current `master` version of the `xdeb` utility, type: 100 | ``` 101 | $ xdeb-install xdeb 102 | ``` 103 | 104 | To install a specific version of the `xdeb` utility, type: 105 | ``` 106 | $ xdeb-install xdeb 107 | ``` 108 | 109 | For example: 110 | ``` 111 | $ xdeb-install xdeb 1.3 112 | ``` 113 | 114 | The tool also supports to figure out the latest release of `xdeb` when `latest` is provided as ``. Example: 115 | ``` 116 | $ xdeb-install xdeb latest 117 | ``` 118 | 119 | As of today, 2023-10-04, this will install release `1.3`. 120 | 121 | #### providers 122 | 123 | ``` 124 | $ xdeb-install providers -h 125 | NAME: 126 | xdeb-install providers - list available providers 127 | 128 | USAGE: 129 | xdeb-install providers [command options] [arguments...] 130 | 131 | OPTIONS: 132 | --details display provider details (distributions and components) (default: false) 133 | --help, -h show help 134 | ``` 135 | 136 | See [Listing available providers](#listing-available-providers) 137 | 138 | #### sync 139 | 140 | ``` 141 | $ xdeb-install sync -h 142 | NAME: 143 | xdeb-install sync [provider list] - synchronize remote repositories 144 | 145 | USAGE: 146 | xdeb-install sync [provider list] [command options] [arguments...] 147 | 148 | OPTIONS: 149 | --help, -h show help 150 | ``` 151 | 152 | See [Syncing package repositories](#syncing-package-repositories) 153 | 154 | #### search 155 | 156 | ``` 157 | $ xdeb-install search -h 158 | NAME: 159 | xdeb-install search - search remote repositories for a package 160 | 161 | USAGE: 162 | xdeb-install search [command options] [arguments...] 163 | 164 | OPTIONS: 165 | --exact, -e perform an exact match of the package name provided (default: false) 166 | --provider value, -p value limit search results to a specific provider 167 | --distribution value, --dist value, -d value limit search results to a specific distribution (requires --provider) 168 | --help, -h show help 169 | ``` 170 | 171 | See [Searching for DEB packages](#searching-for-deb-packages) 172 | 173 | #### file 174 | 175 | ``` 176 | $ xdeb-install file -h 177 | NAME: 178 | xdeb-install file [path or URL] - install a package from a local DEB file or remote URL 179 | 180 | USAGE: 181 | xdeb-install file [path or URL] [command options] [arguments...] 182 | 183 | OPTIONS: 184 | --help, -h show help 185 | ``` 186 | 187 | See 188 | - [Installing DEB packages/Directly from a URL](#directly-from-a-url) 189 | - [Installing DEB packages/Directly from a local file](#directly-from-a-local-file) 190 | 191 | #### clean 192 | 193 | ``` 194 | $ xdeb-install clean -h 195 | NAME: 196 | xdeb-install clean - cleanup temporary xdeb context root path, optionally the repository lists as well 197 | 198 | USAGE: 199 | xdeb-install clean [command options] [arguments...] 200 | 201 | OPTIONS: 202 | --lists, -l cleanup repository lists as well (default: false) 203 | --help, -h show help 204 | ``` 205 | 206 | ## Installation 207 | 208 | There are three ways you can install the tool: 209 | - [using XBPS](#using-xbps) 210 | - [using Go](#using-go) 211 | - [manually downloading a release binary](#manually) 212 | 213 | You can install `xdeb` using `xdeb-install` later, see [Help Page](#help-page). 214 | 215 | ### Using XBPS 216 | 217 | *Before you continue reading this section*, read up on https://docs.voidlinux.org/xbps/repositories/custom.html. You have been warned. 218 | 219 | Since [my PR over at void-linux/void-packages](https://github.com/void-linux/void-packages/pull/46352) didn't make it, you can't install the tool using any official XBPS repositories. 220 | 221 | To work around that problem, I created my own unofficial XBPS repository at https://xdeb-org.github.io/voidlinux-repository. See https://github.com/xdeb-org/voidlinux-repository for instructions on how to install it to your system. 222 | 223 | Afterwards, you can execute `xbps-install xdeb-install` to install the tool. 224 | 225 | ### Using Go 226 | 227 | If you have [Go](https://go.dev) installed, simply execute: 228 | ``` 229 | go install github.com/xdeb-org/xdeb-install/v2@latest 230 | ``` 231 | 232 | As long as the `GOPATH` is within your `PATH`, that's it. 233 | 234 | ### Manually 235 | 236 | Head over to the [releases](https://github.com/xdeb-org/xdeb-install/releases) page and download a release binary. Then move it to some place within your `PATH`, like `/usr/local/bin`. Make sure to make it executable afterwards: `sudo chmod +x /usr/local/bin/xdeb-install`. 237 | 238 | ## Listing available providers 239 | 240 | ### With details redacted 241 | 242 | To list available providers, type: 243 | ``` 244 | $ xdeb-install providers 245 | ``` 246 | 247 | Output: 248 | ``` 249 | [xdeb-install] Syncing lists: https://raw.githubusercontent.com/xdeb-org/xdeb-install-repositories/v1.1.1/repositories/x86_64/lists.yaml 250 | debian.org 251 | architecture: amd64 252 | url: http://ftp.debian.org/debian 253 | 254 | linuxmint.com 255 | architecture: amd64 256 | url: http://packages.linuxmint.com 257 | 258 | ubuntu.com 259 | architecture: amd64 260 | url: http://archive.ubuntu.com/ubuntu 261 | 262 | microsoft.com 263 | architecture: amd64 264 | url: https://raw.githubusercontent.com/xdeb-org/xdeb-install-repositories/v1.1.1/repositories/x86_64/microsoft.com 265 | 266 | google.com 267 | architecture: amd64 268 | url: https://raw.githubusercontent.com/xdeb-org/xdeb-install-repositories/v1.1.1/repositories/x86_64/google.com 269 | ``` 270 | 271 | ### With details (distributions and components) 272 | 273 | To list available providers along with their distributions and components, type: 274 | ``` 275 | $ xdeb-install providers --details 276 | ``` 277 | 278 | Output: 279 | ``` 280 | [xdeb-install] Syncing lists: https://raw.githubusercontent.com/xdeb-org/xdeb-install-repositories/v1.1.1/repositories/x86_64/lists.yaml 281 | debian.org 282 | architecture: amd64 283 | url: http://ftp.debian.org/debian 284 | distribution: bookworm 285 | component: main 286 | component: contrib 287 | component: non-free 288 | component: non-free-firmware 289 | distribution: bookworm-backports 290 | component: main 291 | component: contrib 292 | component: non-free 293 | component: non-free-firmware 294 | distribution: bullseye 295 | component: main 296 | component: contrib 297 | component: non-free 298 | component: non-free-firmware 299 | distribution: bullseye-backports 300 | component: main 301 | component: contrib 302 | component: non-free 303 | component: non-free-firmware 304 | distribution: buster 305 | component: main 306 | component: contrib 307 | component: non-free 308 | component: non-free-firmware 309 | distribution: buster-backports 310 | component: main 311 | component: contrib 312 | component: non-free 313 | component: non-free-firmware 314 | distribution: sid 315 | component: main 316 | component: contrib 317 | component: non-free 318 | component: non-free-firmware 319 | distribution: testing 320 | component: main 321 | component: contrib 322 | component: non-free 323 | component: non-free-firmware 324 | distribution: testing-backports 325 | component: main 326 | component: contrib 327 | component: non-free 328 | component: non-free-firmware 329 | 330 | linuxmint.com 331 | architecture: amd64 332 | url: http://packages.linuxmint.com 333 | distribution: victoria 334 | component: main 335 | component: backport 336 | component: import 337 | component: upstream 338 | distribution: vera 339 | component: main 340 | component: backport 341 | component: import 342 | component: upstream 343 | distribution: vanessa 344 | component: main 345 | component: backport 346 | component: import 347 | component: upstream 348 | distribution: faye 349 | component: main 350 | component: backport 351 | component: import 352 | component: upstream 353 | 354 | ubuntu.com 355 | architecture: amd64 356 | url: http://archive.ubuntu.com/ubuntu 357 | distribution: bionic 358 | component: main 359 | component: multiverse 360 | component: restricted 361 | component: universe 362 | distribution: focal 363 | component: main 364 | component: multiverse 365 | component: restricted 366 | component: universe 367 | distribution: jammy 368 | component: main 369 | component: multiverse 370 | component: restricted 371 | component: universe 372 | 373 | microsoft.com 374 | architecture: amd64 375 | url: https://raw.githubusercontent.com/xdeb-org/xdeb-install-repositories/v1.1.1/repositories/x86_64/microsoft.com 376 | distribution: current 377 | component: vscode.yaml 378 | 379 | google.com 380 | architecture: amd64 381 | url: https://raw.githubusercontent.com/xdeb-org/xdeb-install-repositories/v1.1.1/repositories/x86_64/google.com 382 | distribution: current 383 | component: google-chrome.yaml 384 | ``` 385 | 386 | ## Managing package repositories 387 | 388 | ### Syncing package repositories 389 | 390 | To sync package repositories, type: 391 | ``` 392 | $ xdeb-install sync 393 | ``` 394 | 395 | Output: 396 | ``` 397 | [xdeb-install] Syncing lists: https://raw.githubusercontent.com/xdeb-org/xdeb-install-repositories/v1.1.1/repositories/x86_64/lists.yaml 398 | [xdeb-install] Syncing repository debian.org/bullseye: contrib 399 | [xdeb-install] Syncing repository debian.org/bookworm: contrib 400 | [xdeb-install] Syncing repository debian.org/sid: non-free-firmware 401 | [xdeb-install] Syncing repository debian.org/bullseye-backports: non-free 402 | [xdeb-install] Syncing repository debian.org/bookworm: non-free-firmware 403 | [xdeb-install] Syncing repository debian.org/bullseye-backports: contrib 404 | [xdeb-install] Syncing repository debian.org/buster-backports: non-free 405 | [xdeb-install] Syncing repository debian.org/bookworm-backports: non-free 406 | [xdeb-install] Syncing repository debian.org/testing: contrib 407 | [xdeb-install] Syncing repository debian.org/testing: non-free-firmware 408 | [xdeb-install] Syncing repository debian.org/sid: contrib 409 | [xdeb-install] Syncing repository debian.org/buster: non-free 410 | [xdeb-install] Syncing repository debian.org/buster: contrib 411 | [xdeb-install] Syncing repository debian.org/bookworm-backports: contrib 412 | [xdeb-install] Syncing repository debian.org/bookworm-backports: main 413 | [xdeb-install] Syncing repository debian.org/bookworm: non-free 414 | [xdeb-install] Syncing repository debian.org/testing: non-free 415 | [xdeb-install] Syncing repository debian.org/buster-backports: contrib 416 | [xdeb-install] Syncing repository debian.org/bullseye: non-free 417 | [xdeb-install] Syncing repository debian.org/sid: non-free 418 | [xdeb-install] Syncing repository debian.org/bullseye-backports: main 419 | [xdeb-install] Syncing repository debian.org/buster-backports: main 420 | [xdeb-install] Syncing repository debian.org/buster: main 421 | [xdeb-install] Syncing repository debian.org/bullseye: main 422 | [xdeb-install] Syncing repository debian.org/testing: main 423 | [xdeb-install] Syncing repository debian.org/bookworm: main 424 | [xdeb-install] Syncing repository debian.org/sid: main 425 | [xdeb-install] Syncing repository linuxmint.com/victoria: import 426 | [xdeb-install] Syncing repository linuxmint.com/vanessa: import 427 | [xdeb-install] Syncing repository linuxmint.com/vera: import 428 | [xdeb-install] Syncing repository linuxmint.com/vanessa: main 429 | [xdeb-install] Syncing repository linuxmint.com/faye: main 430 | [xdeb-install] Syncing repository linuxmint.com/victoria: main 431 | [xdeb-install] Syncing repository linuxmint.com/faye: import 432 | [xdeb-install] Syncing repository linuxmint.com/vera: main 433 | [xdeb-install] Syncing repository linuxmint.com/vanessa: backport 434 | [xdeb-install] Syncing repository linuxmint.com/vera: backport 435 | [xdeb-install] Syncing repository linuxmint.com/victoria: backport 436 | [xdeb-install] Syncing repository linuxmint.com/faye: upstream 437 | [xdeb-install] Syncing repository linuxmint.com/faye: backport 438 | [xdeb-install] Syncing repository linuxmint.com/vanessa: upstream 439 | [xdeb-install] Syncing repository linuxmint.com/victoria: upstream 440 | [xdeb-install] Syncing repository linuxmint.com/vera: upstream 441 | [xdeb-install] Syncing repository ubuntu.com/bionic: restricted 442 | [xdeb-install] Syncing repository ubuntu.com/focal: restricted 443 | [xdeb-install] Syncing repository ubuntu.com/bionic: multiverse 444 | [xdeb-install] Syncing repository ubuntu.com/focal: multiverse 445 | [xdeb-install] Syncing repository ubuntu.com/jammy: multiverse 446 | [xdeb-install] Syncing repository ubuntu.com/jammy: restricted 447 | [xdeb-install] Syncing repository ubuntu.com/focal: main 448 | [xdeb-install] Syncing repository ubuntu.com/bionic: main 449 | [xdeb-install] Syncing repository ubuntu.com/jammy: main 450 | [xdeb-install] Syncing repository ubuntu.com/focal: universe 451 | [xdeb-install] Syncing repository ubuntu.com/bionic: universe 452 | [xdeb-install] Syncing repository ubuntu.com/jammy: universe 453 | [xdeb-install] Syncing repository microsoft.com/current: vscode.yaml 454 | [xdeb-install] Syncing repository google.com/current: google-chrome.yaml 455 | [xdeb-install] Finished syncing: ~/.config/xdeb-install/repositories/x86_64 456 | ``` 457 | 458 | The log output is not in order because syncing is parallelized. 459 | 460 | You can also filter the providers to sync, like so: 461 | ``` 462 | $ xdeb-install sync ubuntu.com 463 | ``` 464 | 465 | Output: 466 | ``` 467 | [xdeb-install] Syncing lists: https://raw.githubusercontent.com/xdeb-org/xdeb-install-repositories/v1.1.1/repositories/x86_64/lists.yaml 468 | [xdeb-install] Syncing repository ubuntu.com/jammy: main 469 | [xdeb-install] Syncing repository ubuntu.com/bionic: universe 470 | [xdeb-install] Syncing repository ubuntu.com/bionic: multiverse 471 | [xdeb-install] Syncing repository ubuntu.com/focal: main 472 | [xdeb-install] Syncing repository ubuntu.com/focal: restricted 473 | [xdeb-install] Syncing repository ubuntu.com/focal: multiverse 474 | [xdeb-install] Syncing repository ubuntu.com/jammy: multiverse 475 | [xdeb-install] Syncing repository ubuntu.com/bionic: main 476 | [xdeb-install] Syncing repository ubuntu.com/jammy: restricted 477 | [xdeb-install] Syncing repository ubuntu.com/bionic: restricted 478 | [xdeb-install] Syncing repository ubuntu.com/focal: universe 479 | [xdeb-install] Syncing repository ubuntu.com/jammy: universe 480 | [xdeb-install] Finished syncing: ~/.config/xdeb-install/repositories/x86_64 481 | ``` 482 | 483 | The package repository lists are stored at `$XDG_CONFIG_HOME/xdeb-install/repositories/`, where `$XDG_CONFIG_HOME` typically translates to `$HOME/.config`. 484 | 485 | ### Supported package repositories 486 | 487 | See https://github.com/xdeb-org/xdeb-install-repositories for details. 488 | 489 | ## Searching for DEB packages 490 | 491 | ### General instructions 492 | You can search for a specific package by its name, let's stay with `speedcrunch`: 493 | ``` 494 | $ xdeb-install search speedcrunch 495 | ``` 496 | 497 | Output: 498 | ``` 499 | [xdeb-install] Looking for package speedcrunch (exact: false) via provider * and distribution * ... 500 | debian.org/main 501 | package: speedcrunch 502 | distribution: bookworm 503 | version: 0.12.0-6 504 | url: http://ftp.debian.org/debian/pool/main/s/speedcrunch/speedcrunch_0.12.0-6_amd64.deb 505 | sha256: a306a478bdf923ad1206a1a76fdc1b2d6a745939663419b360febfa6350e96b6 506 | 507 | debian.org/main 508 | package: speedcrunch 509 | distribution: sid 510 | version: 0.12.0-6 511 | url: http://ftp.debian.org/debian/pool/main/s/speedcrunch/speedcrunch_0.12.0-6_amd64.deb 512 | sha256: a306a478bdf923ad1206a1a76fdc1b2d6a745939663419b360febfa6350e96b6 513 | 514 | debian.org/main 515 | package: speedcrunch 516 | distribution: testing 517 | version: 0.12.0-6 518 | url: http://ftp.debian.org/debian/pool/main/s/speedcrunch/speedcrunch_0.12.0-6_amd64.deb 519 | sha256: a306a478bdf923ad1206a1a76fdc1b2d6a745939663419b360febfa6350e96b6 520 | 521 | debian.org/main 522 | package: speedcrunch 523 | distribution: bullseye 524 | version: 0.12.0-5 525 | url: http://ftp.debian.org/debian/pool/main/s/speedcrunch/speedcrunch_0.12.0-5_amd64.deb 526 | sha256: 0c108597debfbc47e6eb384cfff5539627d0f0652202a63f82aa3c3e8f56aa5c 527 | 528 | ubuntu.com/universe 529 | package: speedcrunch 530 | distribution: jammy 531 | version: 0.12.0-5 532 | url: http://archive.ubuntu.com/ubuntu/pool/universe/s/speedcrunch/speedcrunch_0.12.0-5_amd64.deb 533 | sha256: 241d302af8d696032d11abbc6e46d045934c23461786c4876fcc82e1743eec33 534 | 535 | ubuntu.com/universe 536 | package: speedcrunch 537 | distribution: focal 538 | version: 0.12.0-4build1 539 | url: http://archive.ubuntu.com/ubuntu/pool/universe/s/speedcrunch/speedcrunch_0.12.0-4build1_amd64.deb 540 | sha256: 79c0075eea11b172d17963da185a0dffb9d2ab368fd5c64c812c695127579922 541 | 542 | debian.org/main 543 | package: speedcrunch 544 | distribution: buster 545 | version: 0.12.0-4 546 | url: http://ftp.debian.org/debian/pool/main/s/speedcrunch/speedcrunch_0.12.0-4_amd64.deb 547 | sha256: 8681da5ca651a6a7f5abb479c673d33ce3525212e34a2a33afcec7ad75c28aea 548 | 549 | ubuntu.com/universe 550 | package: speedcrunch 551 | distribution: bionic 552 | version: 0.12.0-3 553 | url: http://archive.ubuntu.com/ubuntu/pool/universe/s/speedcrunch/speedcrunch_0.12.0-3_amd64.deb 554 | sha256: 0206f112ac503393c984088817488aa21589c1c5f16f67df8d8836612f27f81 555 | ``` 556 | 557 | ### Search filtering by provider/distribution 558 | Filtering search results is also supported via `--provider [--distribution ]`: 559 | ``` 560 | $ xdeb-install search --provider ubuntu.com --distribution bionic speedcrunch 561 | ``` 562 | 563 | Output: 564 | ``` 565 | [xdeb-install] Looking for package speedcrunch (exact: false) via provider ubuntu.com and distribution bionic ... 566 | ubuntu.com/universe 567 | package: speedcrunch 568 | distribution: bionic 569 | version: 0.12.0-3 570 | url: http://archive.ubuntu.com/ubuntu/pool/universe/s/speedcrunch/speedcrunch_0.12.0-3_amd64.deb 571 | sha256: 0206f112ac503393c984088817488aa21589c1c5f16f67df8d8836612f27f810 572 | ``` 573 | 574 | ### Inexact matches 575 | Futhermore, the flag `--exact` (or `-e`) specifies whether the search will look for a package of the exact name as provided: 576 | ``` 577 | $ xdeb-install search --exact google-chrome 578 | ``` 579 | 580 | Output: 581 | ``` 582 | [xdeb-install] Looking for package google-chrome (exact: true) via provider * and distribution * ... 583 | google.com/google-chrome 584 | package: google-chrome 585 | distribution: current 586 | url: https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb 587 | ``` 588 | 589 | Omitting `--exact` yields: 590 | ``` 591 | $ xdeb-install search google-chrome 592 | [xdeb-install] Looking for package google-chrome (exact: false) via provider * and distribution * ... 593 | google.com/google-chrome 594 | package: google-chrome 595 | distribution: current 596 | url: https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb 597 | 598 | google.com/google-chrome 599 | package: google-chrome-unstable 600 | distribution: current 601 | url: https://dl.google.com/linux/direct/google-chrome-unstable_current_amd64.deb 602 | ``` 603 | 604 | Currently, the only pattern available is `startsWith`, effectively matching `google-chrome*` in the example above. 605 | 606 | ## Installing DEB packages 607 | 608 | ### From remote repositories 609 | 610 | To install `speedcrunch`, for example, type: 611 | ``` 612 | $ xdeb-install speedcrunch 613 | ``` 614 | 615 | This will install the most recent version of the package from the first provider and distribution it can find. 616 | 617 | You can also specify the provider and distribution, for example `debian.org` and `bookworm`, respectively: 618 | ``` 619 | $ xdeb-install --provider debian.org --distribution bookworm speedcrunch 620 | ``` 621 | 622 | ### Directly from a URL 623 | 624 | Let's stay with the `speedcrunch` example: 625 | ``` 626 | $ xdeb-install --file http://ftp.debian.org/debian/pool/main/s/speedcrunch/speedcrunch_0.12.0-6_amd64.deb 627 | ``` 628 | 629 | This will download the file `speedcrunch_0.12.0-6_amd64.deb` to `/tmp/xdeb/localhost/file/speedcrunch_0.12.0-6_amd64/speedcrunch_0.12.0-6_amd64.deb` and install it from there. 630 | 631 | ### Directly from a local file 632 | 633 | First, obviously download a DEB file from a remote location. Let's stay it's stored at `$HOME/Downloads/speedcrunch.deb`: 634 | ``` 635 | $ xdeb-install --file $HOME/Downloads/speedcrunch.deb 636 | ``` 637 | 638 | This will copy the file `speedcrunch.deb` to `/tmp/xdeb/localhost/file/speedcrunch/speedcrunch.deb` and install it from there. 639 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/xdeb-org/xdeb-install/v2 2 | 3 | go 1.21.4 4 | 5 | require ( 6 | github.com/adrg/xdg v0.4.0 7 | github.com/klauspost/compress v1.17.4 8 | github.com/knqyf263/go-deb-version v0.0.0-20230223133812-3ed183d23422 9 | github.com/ulikunitz/xz v0.5.11 10 | github.com/urfave/cli/v2 v2.26.0 11 | golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb 12 | gopkg.in/yaml.v2 v2.4.0 13 | ) 14 | 15 | require ( 16 | github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect 17 | github.com/russross/blackfriday/v2 v2.1.0 // indirect 18 | github.com/stretchr/testify v1.8.4 // indirect 19 | github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect 20 | golang.org/x/sys v0.15.0 // indirect 21 | ) 22 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls= 2 | github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E= 3 | github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM= 4 | github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 5 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 7 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= 9 | github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= 10 | github.com/knqyf263/go-deb-version v0.0.0-20230223133812-3ed183d23422 h1:PPPlUUqPP6fLudIK4n0l0VU4KT2cQGnheW9x8pNiCHI= 11 | github.com/knqyf263/go-deb-version v0.0.0-20230223133812-3ed183d23422/go.mod h1:ijAmSS4jErO6+KRzcK6ixsm3Vt96hMhJ+W+x+VmbrQA= 12 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 13 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 14 | github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= 15 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 16 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 17 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 18 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 19 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 20 | github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8= 21 | github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= 22 | github.com/urfave/cli/v2 v2.26.0 h1:3f3AMg3HpThFNT4I++TKOejZO8yU55t3JnnSr4S4QEI= 23 | github.com/urfave/cli/v2 v2.26.0/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= 24 | github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= 25 | github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= 26 | golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb h1:c0vyKkb6yr3KR7jEfJaOSv4lG7xPkbN6r52aJz1d8a8= 27 | golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= 28 | golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 29 | golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= 30 | golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 31 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 32 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 33 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 34 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 35 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 36 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 37 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 38 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/url" 7 | "os" 8 | "path/filepath" 9 | "strconv" 10 | "strings" 11 | "time" 12 | 13 | "golang.org/x/exp/slices" 14 | 15 | "github.com/urfave/cli/v2" 16 | xdeb "github.com/xdeb-org/xdeb-install/v2/pkg" 17 | ) 18 | 19 | func readPath(subdir string) ([]string, error) { 20 | path, err := xdeb.RepositoryPath() 21 | 22 | if err != nil { 23 | return nil, err 24 | } 25 | 26 | entriesPath := filepath.Join(path, subdir) 27 | entries, err := os.ReadDir(entriesPath) 28 | 29 | if err != nil { 30 | return nil, fmt.Errorf("no entries found, please sync the repositories first") 31 | } 32 | 33 | subdirs := []string{} 34 | 35 | for _, entry := range entries { 36 | if entry.IsDir() { 37 | subdirs = append(subdirs, entry.Name()) 38 | } 39 | } 40 | 41 | return subdirs, nil 42 | } 43 | 44 | func findDistribution(provider string, distribution string) (string, error) { 45 | if len(distribution) > 0 && distribution != "*" { 46 | distributions, err := readPath(provider) 47 | 48 | if err != nil { 49 | return "", err 50 | } 51 | 52 | if !slices.Contains(distributions, distribution) { 53 | return "", fmt.Errorf( 54 | "provider %s does not support distribution '%s', - omit or use any of %v", 55 | provider, distribution, distributions, 56 | ) 57 | } 58 | 59 | return distribution, nil 60 | } 61 | 62 | return "*", nil 63 | } 64 | 65 | func findProvider(provider string) (string, error) { 66 | if len(provider) > 0 { 67 | providers, err := readPath("") 68 | 69 | if err != nil { 70 | return "", err 71 | } 72 | 73 | if !slices.Contains(providers, provider) { 74 | return "", fmt.Errorf("provider '%s' not supported, omit or use any of %v", provider, providers) 75 | } 76 | 77 | return provider, nil 78 | } 79 | 80 | return "*", nil 81 | } 82 | 83 | func deb(context *cli.Context) error { 84 | filePath := context.String("file") 85 | 86 | if len(filePath) > 0 { 87 | return file(context, filePath) 88 | } 89 | 90 | _, err := xdeb.FindXdeb() 91 | 92 | if err != nil { 93 | return err 94 | } 95 | 96 | path, err := xdeb.RepositoryPath() 97 | 98 | if err != nil { 99 | return nil 100 | } 101 | 102 | provider, err := findProvider(context.String("provider")) 103 | 104 | if err != nil { 105 | return err 106 | } 107 | 108 | distribution, err := findDistribution(provider, context.String("distribution")) 109 | 110 | if err != nil { 111 | return err 112 | } 113 | 114 | packageName := strings.Trim(context.Args().First(), " ") 115 | 116 | if len(packageName) == 0 { 117 | return fmt.Errorf("no package provided to install") 118 | } 119 | 120 | packageDefinitions, err := xdeb.FindPackage(packageName, path, provider, distribution, true) 121 | 122 | if err != nil { 123 | return err 124 | } 125 | 126 | return xdeb.InstallPackage(packageDefinitions[0], context) 127 | } 128 | 129 | func file(context *cli.Context, filePath string) error { 130 | _, err := xdeb.FindXdeb() 131 | 132 | if err != nil { 133 | return err 134 | } 135 | 136 | var packageDefinition xdeb.XdebPackageDefinition 137 | 138 | fileUrl, err := url.Parse(filePath) 139 | isUrl := err == nil && fileUrl.Scheme != "" && fileUrl.Host != "" 140 | 141 | if !isUrl { 142 | if _, err := os.Stat(filePath); err != nil { 143 | if os.IsNotExist(err) { 144 | return fmt.Errorf("file '%s' does not exist", filePath) 145 | } 146 | 147 | return err 148 | } 149 | 150 | if !strings.HasSuffix(filePath, ".deb") { 151 | return fmt.Errorf("file '%s' is not a valid DEB package", filePath) 152 | } 153 | 154 | packageDefinition = xdeb.XdebPackageDefinition{ 155 | Name: xdeb.TrimPathExtension(filepath.Base(filePath), 1), 156 | } 157 | 158 | packageDefinition.Configure(context.String("temp")) 159 | 160 | if filePath != packageDefinition.FilePath { 161 | // copy file to temporary xdeb context path 162 | if err := os.MkdirAll(packageDefinition.Path, os.ModePerm); err != nil { 163 | return err 164 | } 165 | 166 | data, err := os.ReadFile(filePath) 167 | 168 | if err != nil { 169 | return err 170 | } 171 | 172 | if err = os.WriteFile(packageDefinition.FilePath, data, os.ModePerm); err != nil { 173 | return err 174 | } 175 | } 176 | } else { 177 | packageDefinition = xdeb.XdebPackageDefinition{ 178 | Name: xdeb.TrimPathExtension(filepath.Base(filePath), 1), 179 | Url: filePath, 180 | } 181 | } 182 | 183 | return xdeb.InstallPackage(&packageDefinition, context) 184 | } 185 | 186 | func search(context *cli.Context) error { 187 | packageName := context.Args().First() 188 | 189 | if len(packageName) == 0 { 190 | return fmt.Errorf("no package provided to search for") 191 | } 192 | 193 | path, err := xdeb.RepositoryPath() 194 | 195 | if err != nil { 196 | return err 197 | } 198 | 199 | provider, err := findProvider(context.String("provider")) 200 | 201 | if err != nil { 202 | return err 203 | } 204 | 205 | distribution, err := findDistribution(provider, context.String("distribution")) 206 | 207 | if err != nil { 208 | return err 209 | } 210 | 211 | packageDefinitions, err := xdeb.FindPackage(packageName, path, provider, distribution, context.Bool("exact")) 212 | 213 | if err != nil { 214 | return err 215 | } 216 | 217 | for _, packageDefinition := range packageDefinitions { 218 | fmt.Printf("%s/%s\n", packageDefinition.Provider, packageDefinition.Component) 219 | fmt.Printf(" package: %s\n", packageDefinition.Name) 220 | fmt.Printf(" distribution: %s\n", packageDefinition.Distribution) 221 | 222 | if len(packageDefinition.Version) > 0 { 223 | fmt.Printf(" version: %s\n", packageDefinition.Version) 224 | } 225 | 226 | fmt.Printf(" url: %s\n", packageDefinition.Url) 227 | 228 | if len(packageDefinition.Sha256) > 0 { 229 | fmt.Printf(" sha256: %s\n", packageDefinition.Sha256) 230 | } 231 | 232 | fmt.Println() 233 | } 234 | 235 | return nil 236 | } 237 | 238 | func sync(context *cli.Context) error { 239 | lists, err := xdeb.ParsePackageLists() 240 | 241 | if err != nil { 242 | return err 243 | } 244 | 245 | args := context.Args() 246 | providerNames := []string{} 247 | 248 | for i := 0; i < args.Len(); i++ { 249 | providerName := args.Get(i) 250 | providerNames = append(providerNames, providerName) 251 | } 252 | 253 | err = xdeb.SyncRepositories(lists, providerNames...) 254 | 255 | if err != nil { 256 | return err 257 | } 258 | 259 | xdeb.LogMessage("Finished syncing: %s", strings.ReplaceAll(lists.Path, os.Getenv("HOME"), "~")) 260 | return nil 261 | } 262 | 263 | func providers(context *cli.Context) error { 264 | lists, err := xdeb.ParsePackageLists() 265 | 266 | if err != nil { 267 | return err 268 | } 269 | 270 | showDetails := context.Bool("details") 271 | 272 | for _, provider := range lists.Providers { 273 | fmt.Println(provider.Name) 274 | fmt.Printf(" architecture: %s\n", provider.Architecture) 275 | 276 | if provider.Custom { 277 | fmt.Printf(" url: %s/%s/%s\n", xdeb.XDEB_INSTALL_REPOSITORIES_URL, xdeb.XDEB_INSTALL_REPOSITORIES_TAG, provider.Url) 278 | } else { 279 | fmt.Printf(" url: %s\n", provider.Url) 280 | } 281 | 282 | if showDetails { 283 | for _, distribution := range provider.Distributions { 284 | fmt.Printf(" distribution: %s\n", distribution) 285 | 286 | for _, component := range provider.Components { 287 | fmt.Printf(" component: %s\n", component) 288 | } 289 | } 290 | } 291 | 292 | fmt.Println() 293 | } 294 | 295 | return nil 296 | } 297 | 298 | func prepare(context *cli.Context) error { 299 | version := context.Args().First() 300 | var urlPrefix string 301 | 302 | if len(version) == 0 { 303 | // install master 304 | urlPrefix = xdeb.XDEB_MASTER_URL 305 | version = "master" 306 | } else { 307 | if version == "latest" { 308 | // find latest release tag 309 | urlPrefix = fmt.Sprintf("%s/latest", xdeb.XDEB_URL) 310 | 311 | client := xdeb.NewHttpClient() 312 | resp, err := client.Get(urlPrefix) 313 | 314 | if err != nil { 315 | return fmt.Errorf("could not follow URL '%s'", urlPrefix) 316 | } 317 | 318 | // get latest release version 319 | releaseUrl := resp.Request.URL.String() 320 | version = releaseUrl[strings.LastIndex(releaseUrl, "/")+1:] 321 | } 322 | 323 | urlPrefix = fmt.Sprintf("%s/download/%s", xdeb.XDEB_URL, version) 324 | } 325 | 326 | requestUrl := fmt.Sprintf("%s/xdeb", urlPrefix) 327 | path := filepath.Join(os.TempDir(), "xdeb-download", "xdeb") 328 | 329 | xdeb.LogMessage("Downloading xdeb [%s] from %s to %s ...", version, requestUrl, path) 330 | xdebFile, err := xdeb.DownloadFile(path, requestUrl, false, false) 331 | 332 | if err != nil { 333 | return err 334 | } 335 | 336 | // move to /usr/local/bin 337 | args := []string{} 338 | 339 | if os.Getuid() > 0 { 340 | xdeb.LogMessage("Detected non-root user, appending 'sudo' to command") 341 | args = append(args, "sudo") 342 | } 343 | 344 | targetFile := "/usr/local/bin/xdeb" 345 | args = append(args, "mv", xdebFile, targetFile) 346 | 347 | err = xdeb.ExecuteCommand("", args...) 348 | 349 | if err != nil { 350 | return err 351 | } 352 | 353 | args = make([]string, 0) 354 | 355 | if os.Getuid() > 0 { 356 | xdeb.LogMessage("Detected non-root user, appending 'sudo' to command") 357 | args = append(args, "sudo") 358 | } 359 | 360 | args = append(args, "chmod", "u+x", targetFile) 361 | err = xdeb.ExecuteCommand("", args...) 362 | 363 | if err != nil { 364 | return err 365 | } 366 | 367 | path = filepath.Dir(xdebFile) 368 | 369 | xdeb.LogMessage("Removing temporary download location %s ...", path) 370 | err = os.RemoveAll(path) 371 | 372 | if err != nil { 373 | return err 374 | } 375 | 376 | args = make([]string, 0) 377 | 378 | if os.Getuid() > 0 { 379 | args = append(args, "sudo") 380 | } 381 | 382 | args = append(args, "xbps-install", "-S", "binutils", "tar", "curl", "xz") 383 | return xdeb.ExecuteCommand("", args...) 384 | } 385 | 386 | func clean(context *cli.Context) error { 387 | tempPath := context.String("temp") 388 | 389 | if len(tempPath) == 0 { 390 | return fmt.Errorf("please provide a temporary xdeb context root path") 391 | } 392 | 393 | xdeb.LogMessage("Cleaning up temporary xdeb context root path: %s", tempPath) 394 | err := os.RemoveAll(tempPath) 395 | 396 | if err != nil { 397 | return err 398 | } 399 | 400 | if context.Bool("lists") { 401 | path, err := xdeb.RepositoryPath() 402 | 403 | if err != nil { 404 | return err 405 | } 406 | 407 | xdeb.LogMessage("Cleaning up repository path: %s", path) 408 | return os.RemoveAll(path) 409 | } 410 | 411 | return nil 412 | } 413 | 414 | func unixTime(epochString string) (*time.Time, error) { 415 | if epochString == "now" { 416 | now := time.Now().UTC() 417 | return &now, nil 418 | } 419 | 420 | parsed, err := strconv.ParseInt(epochString, 10, 64) 421 | 422 | if err != nil { 423 | return nil, err 424 | } 425 | 426 | epoch := time.Unix(parsed, 0) 427 | return &epoch, nil 428 | } 429 | 430 | var ( 431 | VersionString string = "dev" 432 | VersionCompiled string = "now" 433 | VersionAuthors string = "me " 434 | ) 435 | 436 | func main() { 437 | authors := []*cli.Author{} 438 | 439 | for _, authorString := range strings.Split(VersionAuthors, ",") { 440 | authorItems := strings.Split(authorString, " <") 441 | authorEmail := "" 442 | 443 | if len(authorItems) > 1 { 444 | authorEmail = authorItems[1] 445 | } 446 | 447 | authorEmail = strings.TrimSuffix(authorEmail, ">") 448 | 449 | authors = append(authors, &cli.Author{ 450 | Name: authorItems[0], 451 | Email: authorEmail, 452 | }) 453 | } 454 | 455 | compiled, err := unixTime(VersionCompiled) 456 | 457 | if err != nil { 458 | log.Fatalf("cannot parse compiled time from unix epoch: %s", err.Error()) 459 | } 460 | 461 | app := &cli.App{ 462 | Name: xdeb.APPLICATION_NAME, 463 | Usage: "Automation wrapper for the xdeb utility", 464 | UsageText: fmt.Sprintf("%s [global options (except --file)] \n%s [global options] command [command options] [arguments...]", xdeb.APPLICATION_NAME, xdeb.APPLICATION_NAME), 465 | Description: "Simple tool to automatically download, convert, and install DEB packages via the awesome xdeb utility.\nBasically just a wrapper to automate the process.", 466 | Version: VersionString, 467 | Compiled: *compiled, 468 | Authors: authors, 469 | Suggest: true, 470 | Action: deb, 471 | Flags: []cli.Flag{ 472 | &cli.StringFlag{ 473 | Name: "file", 474 | Usage: "install a package from a local DEB file or remote URL", 475 | Aliases: []string{"f"}, 476 | }, 477 | &cli.StringFlag{ 478 | Name: "provider", 479 | Usage: "limit search results to a specific provider when --file is not passed", 480 | Aliases: []string{"p"}, 481 | }, 482 | &cli.StringFlag{ 483 | Name: "distribution", 484 | Usage: "limit search results to a specific distribution (requires --provider)", 485 | Aliases: []string{"dist", "d"}, 486 | }, 487 | &cli.StringFlag{ 488 | Name: "options", 489 | Aliases: []string{"o"}, 490 | Usage: "override XDEB_OPTS, '-i' will be removed if provided", 491 | Value: "-Sde", 492 | }, 493 | &cli.StringFlag{ 494 | Name: "temp", 495 | Aliases: []string{"t"}, 496 | Usage: "set the temporary xdeb context root path", 497 | Value: filepath.Join(os.TempDir(), "xdeb"), 498 | }, 499 | }, 500 | Commands: []*cli.Command{ 501 | { 502 | Name: "xdeb", 503 | HelpName: "xdeb [release version]", 504 | Usage: "install the xdeb utility to the system along with its dependencies", 505 | Action: prepare, 506 | }, 507 | { 508 | Name: "providers", 509 | Usage: "list available providers", 510 | Aliases: []string{"p"}, 511 | Action: providers, 512 | Flags: []cli.Flag{ 513 | &cli.BoolFlag{ 514 | Name: "details", 515 | Usage: "display provider details (distributions and components)", 516 | }, 517 | }, 518 | }, 519 | { 520 | Name: "sync", 521 | HelpName: "sync [provider list]", 522 | Usage: "synchronize remote repositories", 523 | Aliases: []string{"S"}, 524 | Action: sync, 525 | }, 526 | { 527 | Name: "search", 528 | Usage: "search remote repositories for a package", 529 | Aliases: []string{"s"}, 530 | Action: search, 531 | Flags: []cli.Flag{ 532 | &cli.BoolFlag{ 533 | Name: "exact", 534 | Aliases: []string{"e"}, 535 | Usage: "perform an exact match of the package name provided", 536 | }, 537 | &cli.StringFlag{ 538 | Name: "provider", 539 | Usage: "limit search results to a specific provider", 540 | Aliases: []string{"p"}, 541 | }, 542 | &cli.StringFlag{ 543 | Name: "distribution", 544 | Usage: "limit search results to a specific distribution (requires --provider)", 545 | Aliases: []string{"dist", "d"}, 546 | }, 547 | }, 548 | }, 549 | { 550 | Name: "clean", 551 | Usage: "cleanup temporary xdeb context root path, optionally the repository lists as well", 552 | Aliases: []string{"c"}, 553 | Action: clean, 554 | Flags: []cli.Flag{ 555 | &cli.BoolFlag{ 556 | Name: "lists", 557 | Aliases: []string{"l"}, 558 | Usage: "cleanup repository lists as well", 559 | Value: false, 560 | }, 561 | }, 562 | }, 563 | }, 564 | } 565 | 566 | if err := app.Run(os.Args); err != nil { 567 | xdeb.LogMessage(err.Error()) 568 | os.Exit(1) 569 | } 570 | } 571 | -------------------------------------------------------------------------------- /pkg/arch.go: -------------------------------------------------------------------------------- 1 | package xdeb 2 | 3 | import ( 4 | "fmt" 5 | "runtime" 6 | ) 7 | 8 | var ARCHITECTURE_MAP = map[string]string{ 9 | "amd64": "x86_64", 10 | "arm64": "aarch64", 11 | "386": "i686", 12 | } 13 | 14 | func FindArchitecture() (string, error) { 15 | arch, ok := ARCHITECTURE_MAP[runtime.GOARCH] 16 | 17 | if !ok { 18 | return "", fmt.Errorf("architecture '%s' not supported (yet)", runtime.GOARCH) 19 | } 20 | 21 | return arch, nil 22 | } 23 | -------------------------------------------------------------------------------- /pkg/command_utils.go: -------------------------------------------------------------------------------- 1 | package xdeb 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/exec" 7 | "strings" 8 | ) 9 | 10 | func findArgumentIndex(command string, args ...string) int { 11 | for i := 0; i < len(args); i++ { 12 | if strings.HasSuffix(args[i], command) { 13 | return i 14 | } 15 | } 16 | 17 | return -1 18 | } 19 | 20 | func ExecuteCommand(workdir string, args ...string) error { 21 | LogMessage("Executing command: %s ...", strings.Join(args, " ")) 22 | 23 | command := exec.Command(args[0], args[1:]...) 24 | command.Dir = workdir 25 | command.Stdout = os.Stdout 26 | command.Stderr = os.Stderr 27 | command.Stdin = os.Stdin 28 | 29 | commandIndex := findArgumentIndex("xdeb", args...) 30 | 31 | if commandIndex > -1 { 32 | commandStringSlice := strings.Split(args[commandIndex], "/") 33 | 34 | // Add xdeb-specific environment to xdeb command 35 | if commandStringSlice[len(commandStringSlice)-1] == "xdeb" { 36 | for _, environmentVariable := range os.Environ() { 37 | if strings.HasPrefix(environmentVariable, "XDEB_") { 38 | command.Env = append(command.Env, environmentVariable) 39 | } 40 | } 41 | 42 | command.Env = append(command.Env, fmt.Sprintf("XDEB_PKGROOT=%s", workdir)) 43 | } 44 | } 45 | 46 | command.Env = append(command.Env, fmt.Sprintf("PATH=%s", os.Getenv("PATH"))) 47 | return command.Run() 48 | } 49 | -------------------------------------------------------------------------------- /pkg/constants.go: -------------------------------------------------------------------------------- 1 | package xdeb 2 | 3 | import "time" 4 | 5 | const APPLICATION_NAME = "xdeb-install" 6 | 7 | const LOG_MESSAGE_PREFIX = "[" + APPLICATION_NAME + "]" 8 | 9 | const XDEB_URL = "https://github.com/xdeb-org/xdeb/releases" 10 | const XDEB_MASTER_URL = "https://raw.githubusercontent.com/xdeb-org/xdeb/master" 11 | 12 | const XDEB_INSTALL_REPOSITORIES_TAG = "v1.1.2" 13 | const XDEB_INSTALL_REPOSITORIES_URL = "https://raw.githubusercontent.com/xdeb-org/xdeb-install-repositories" 14 | 15 | const HTTP_REQUEST_HEADERS_TIMEOUT = 10 * time.Second 16 | -------------------------------------------------------------------------------- /pkg/file_utils.go: -------------------------------------------------------------------------------- 1 | package xdeb 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "fmt" 7 | "io" 8 | "os" 9 | "path/filepath" 10 | 11 | "github.com/klauspost/compress/zstd" 12 | ) 13 | 14 | func compressData(in io.Reader, out io.Writer) error { 15 | // Copied from the library's usage example: 16 | // https://github.com/klauspost/compress/tree/master/zstd#usage 17 | enc, err := zstd.NewWriter(out, zstd.WithEncoderLevel(zstd.SpeedBestCompression)) 18 | 19 | if err != nil { 20 | return err 21 | } 22 | 23 | _, err = io.Copy(enc, in) 24 | 25 | if err != nil { 26 | enc.Close() 27 | return err 28 | } 29 | 30 | return enc.Close() 31 | } 32 | 33 | func decompressData(in io.Reader, out io.Writer) error { 34 | // Copied from the library's usage example: 35 | // https://github.com/klauspost/compress/tree/master/zstd#decompressor 36 | d, err := zstd.NewReader(in) 37 | 38 | if err != nil { 39 | return err 40 | } 41 | 42 | defer d.Close() 43 | 44 | // Copy content... 45 | _, err = io.Copy(out, d) 46 | return err 47 | } 48 | 49 | func decompressFile(path string) ([]byte, error) { 50 | file, err := os.ReadFile(path) 51 | 52 | if err != nil { 53 | return nil, err 54 | } 55 | 56 | reader := bytes.NewReader(file) 57 | 58 | var buffer bytes.Buffer 59 | writer := bufio.NewWriter(&buffer) 60 | 61 | if err = decompressData(reader, writer); err != nil { 62 | return nil, err 63 | } 64 | 65 | if err = writer.Flush(); err != nil { 66 | return nil, err 67 | } 68 | 69 | return buffer.Bytes(), nil 70 | } 71 | 72 | func writeFile(path string, data []byte) (string, error) { 73 | if err := os.MkdirAll(filepath.Dir(path), os.ModePerm); err != nil { 74 | return "", err 75 | } 76 | 77 | file, err := os.Create(path) 78 | 79 | if err != nil { 80 | return "", err 81 | } 82 | 83 | defer file.Close() 84 | _, err = file.Write(data) 85 | 86 | return path, err 87 | } 88 | 89 | func writeFileCompressed(path string, data []byte) (string, error) { 90 | reader := bytes.NewReader(data) 91 | 92 | var compressedData bytes.Buffer 93 | writer := bufio.NewWriter(&compressedData) 94 | 95 | if err := compressData(reader, writer); err != nil { 96 | return "", err 97 | } 98 | 99 | if err := writer.Flush(); err != nil { 100 | return "", err 101 | } 102 | 103 | return writeFile(fmt.Sprintf("%s.zst", path), compressedData.Bytes()) 104 | } 105 | 106 | func DownloadFile(path string, baseRequestUrl string, followRedirects bool, compress bool) (string, error) { 107 | requestUrl := baseRequestUrl 108 | 109 | client := NewHttpClient() 110 | resp, err := client.Get(requestUrl) 111 | 112 | if err != nil { 113 | return "", fmt.Errorf("could not download file '%s'", requestUrl) 114 | } 115 | 116 | if followRedirects { 117 | requestUrl = resp.Request.URL.String() 118 | resp, err = client.Get(requestUrl) 119 | 120 | if err != nil { 121 | return "", fmt.Errorf("could not download file '%s'", requestUrl) 122 | } 123 | } 124 | 125 | if resp.StatusCode != 200 { 126 | return "", fmt.Errorf("could not download file '%s'", requestUrl) 127 | } 128 | 129 | defer resp.Body.Close() 130 | 131 | body, err := io.ReadAll(resp.Body) 132 | 133 | if err != nil { 134 | return "", err 135 | } 136 | 137 | if compress { 138 | return writeFileCompressed(path, body) 139 | } 140 | 141 | return writeFile(path, body) 142 | } 143 | -------------------------------------------------------------------------------- /pkg/http_client.go: -------------------------------------------------------------------------------- 1 | package xdeb 2 | 3 | import "net/http" 4 | 5 | func NewHttpClient() *http.Client { 6 | return &http.Client{ 7 | Transport: &http.Transport{ 8 | ResponseHeaderTimeout: HTTP_REQUEST_HEADERS_TIMEOUT, 9 | }, 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /pkg/log_utils.go: -------------------------------------------------------------------------------- 1 | package xdeb 2 | 3 | import "fmt" 4 | 5 | func LogMessage(format string, args ...any) { 6 | message := fmt.Sprintf(format, args...) 7 | fmt.Printf("%s %s\n", LOG_MESSAGE_PREFIX, message) 8 | } 9 | -------------------------------------------------------------------------------- /pkg/path_utils.go: -------------------------------------------------------------------------------- 1 | package xdeb 2 | 3 | import ( 4 | "path/filepath" 5 | "strings" 6 | ) 7 | 8 | func TrimPathExtension(path string, count int) string { 9 | for i := 0; i < count; i++ { 10 | path = strings.TrimSuffix(path, filepath.Ext(path)) 11 | } 12 | 13 | return path 14 | } 15 | -------------------------------------------------------------------------------- /pkg/sync.go: -------------------------------------------------------------------------------- 1 | package xdeb 2 | 3 | import ( 4 | "bufio" 5 | "compress/gzip" 6 | "fmt" 7 | "io" 8 | "path/filepath" 9 | "strings" 10 | "sync" 11 | 12 | "github.com/ulikunitz/xz" 13 | "golang.org/x/exp/slices" 14 | "gopkg.in/yaml.v2" 15 | ) 16 | 17 | type PackageListsProvider struct { 18 | Name string `yaml:"name"` 19 | Custom bool `yaml:"custom"` 20 | Url string `yaml:"url"` 21 | Architecture string `yaml:"architecture"` 22 | Components []string `yaml:"components"` 23 | Distributions []string `yaml:"dists"` 24 | } 25 | 26 | type PackageListsDefinition struct { 27 | Path string `yaml:"path,omitempty"` 28 | Providers []PackageListsProvider `yaml:"providers"` 29 | } 30 | 31 | func parsePackagesFile(urlPrefix string, packagesFile string) *XdebProviderDefinition { 32 | definition := XdebProviderDefinition{} 33 | packages := strings.Split(packagesFile, "\n\n") 34 | 35 | for _, packageData := range packages { 36 | if len(packageData) == 0 { 37 | continue 38 | } 39 | 40 | packageDefinition := XdebPackageDefinition{} 41 | scanner := bufio.NewScanner(strings.NewReader(packageData)) 42 | 43 | for scanner.Scan() { 44 | line := scanner.Text() 45 | 46 | if strings.HasPrefix(line, "Package:") { 47 | packageDefinition.Name = line[strings.Index(line, ":")+2:] 48 | continue 49 | } 50 | 51 | if strings.HasPrefix(line, "Version:") { 52 | packageDefinition.Version = line[strings.Index(line, ":")+2:] 53 | continue 54 | } 55 | 56 | if strings.HasPrefix(line, "Filename:") { 57 | suffix := line[strings.Index(line, ":")+2:] 58 | packageDefinition.Url = fmt.Sprintf("%s/%s", urlPrefix, suffix) 59 | continue 60 | } 61 | 62 | if strings.HasPrefix(line, "SHA256:") { 63 | packageDefinition.Sha256 = line[strings.Index(line, ":")+2:] 64 | continue 65 | } 66 | } 67 | 68 | definition.Xdeb = append(definition.Xdeb, &packageDefinition) 69 | } 70 | 71 | return &definition 72 | } 73 | 74 | func pullPackagesFile(urlPrefix string, dist string, component string, architecture string) (*XdebProviderDefinition, error) { 75 | requestUrl := fmt.Sprintf( 76 | "%s/dists/%s/%s/binary-%s/Packages", 77 | urlPrefix, dist, component, architecture, 78 | ) 79 | 80 | client := NewHttpClient() 81 | resp, err := client.Get(requestUrl) 82 | 83 | if err != nil { 84 | return nil, err 85 | } 86 | 87 | if resp.StatusCode != 200 { 88 | resp, err = client.Get(fmt.Sprintf("%s.xz", requestUrl)) 89 | 90 | if err != nil { 91 | return nil, err 92 | } 93 | } 94 | 95 | if resp.StatusCode != 200 { 96 | resp, err = client.Get(fmt.Sprintf("%s.gz", requestUrl)) 97 | 98 | if err != nil { 99 | return nil, err 100 | } 101 | } 102 | 103 | if resp.StatusCode != 200 { 104 | return nil, nil 105 | } 106 | 107 | defer resp.Body.Close() 108 | 109 | requestUrl = fmt.Sprintf( 110 | "%s://%s%s", 111 | resp.Request.URL.Scheme, resp.Request.URL.Host, resp.Request.URL.Path, 112 | ) 113 | 114 | var reader io.Reader 115 | 116 | if strings.HasSuffix(requestUrl, ".xz") { 117 | reader, err = xz.NewReader(resp.Body) 118 | 119 | if err != nil { 120 | return nil, err 121 | } 122 | } else if strings.HasSuffix(requestUrl, ".gz") { 123 | reader, err = gzip.NewReader(resp.Body) 124 | 125 | if err != nil { 126 | return nil, err 127 | } 128 | } else { 129 | reader = resp.Body 130 | } 131 | 132 | output, err := io.ReadAll(reader) 133 | 134 | if err != nil { 135 | return nil, err 136 | } 137 | 138 | return parsePackagesFile(urlPrefix, string(output)), nil 139 | } 140 | 141 | func pullAptRepository(directory string, url string, dist string, component string, architecture string) error { 142 | definition, err := pullPackagesFile(url, dist, component, architecture) 143 | 144 | if err != nil { 145 | return err 146 | } 147 | 148 | if definition != nil && len(definition.Xdeb) > 0 { 149 | LogMessage("Syncing repository %s/%s: %s", filepath.Base(directory), dist, component) 150 | 151 | filePath := filepath.Join(directory, dist, fmt.Sprintf("%s.yaml", component)) 152 | data, err := yaml.Marshal(definition) 153 | 154 | if err != nil { 155 | return err 156 | } 157 | 158 | if _, err = writeFileCompressed(filePath, data); err != nil { 159 | return err 160 | } 161 | } 162 | 163 | return nil 164 | } 165 | 166 | func pullCustomRepository(directory string, urlPrefix string, dist string, component string) error { 167 | LogMessage("Syncing repository %s/%s: %s", filepath.Base(urlPrefix), dist, component) 168 | 169 | requestUrl := fmt.Sprintf("%s/%s/%s", urlPrefix, dist, component) 170 | _, err := DownloadFile(filepath.Join(directory, dist, fmt.Sprintf("%s.yaml", component)), requestUrl, false, true) 171 | 172 | return err 173 | } 174 | 175 | func ParsePackageLists() (*PackageListsDefinition, error) { 176 | arch, err := FindArchitecture() 177 | 178 | if err != nil { 179 | return nil, err 180 | } 181 | 182 | path, err := RepositoryPath() 183 | 184 | if err != nil { 185 | return nil, err 186 | } 187 | 188 | requestUrl := fmt.Sprintf( 189 | "%s/%s/repositories/%s/lists.yaml", 190 | XDEB_INSTALL_REPOSITORIES_URL, XDEB_INSTALL_REPOSITORIES_TAG, arch, 191 | ) 192 | 193 | LogMessage("Syncing lists: %s", requestUrl) 194 | listsFile, err := DownloadFile(filepath.Join(path, "lists.yaml"), requestUrl, true, true) 195 | 196 | if err != nil { 197 | return nil, err 198 | } 199 | 200 | yamlFile, err := decompressFile(listsFile) 201 | 202 | if err != nil { 203 | return nil, err 204 | } 205 | 206 | lists := &PackageListsDefinition{} 207 | err = yaml.Unmarshal(yamlFile, lists) 208 | 209 | if err != nil { 210 | return nil, err 211 | } 212 | 213 | lists.Path = path 214 | return lists, nil 215 | } 216 | 217 | func SyncRepositories(lists *PackageListsDefinition, providerNames ...string) error { 218 | availableProviderNames := []string{} 219 | 220 | for _, provider := range lists.Providers { 221 | availableProviderNames = append(availableProviderNames, provider.Name) 222 | } 223 | 224 | for _, providerName := range providerNames { 225 | if !slices.Contains(availableProviderNames, providerName) { 226 | return fmt.Errorf("provider %s not supported, omit or use any of %v", providerName, availableProviderNames) 227 | } 228 | } 229 | 230 | providers := []PackageListsProvider{} 231 | 232 | if len(providerNames) > 0 { 233 | for _, provider := range lists.Providers { 234 | if slices.Contains(providerNames, provider.Name) { 235 | providers = append(providers, provider) 236 | } 237 | } 238 | } else { 239 | providers = append(providers, lists.Providers...) 240 | } 241 | 242 | operations := len(providers) 243 | 244 | for _, provider := range providers { 245 | operations += len(provider.Distributions) * len(provider.Components) 246 | } 247 | 248 | for _, provider := range providers { 249 | errors := make(chan error, operations) 250 | var wg sync.WaitGroup 251 | 252 | for _, distribution := range provider.Distributions { 253 | for _, component := range provider.Components { 254 | wg.Add(1) 255 | 256 | go func(p PackageListsProvider, d string, c string) { 257 | defer wg.Done() 258 | directory := filepath.Join(lists.Path, p.Name) 259 | 260 | if p.Custom { 261 | urlPrefix := fmt.Sprintf("%s/%s/%s", XDEB_INSTALL_REPOSITORIES_URL, XDEB_INSTALL_REPOSITORIES_TAG, p.Url) 262 | errors <- pullCustomRepository(directory, urlPrefix, d, c) 263 | } else { 264 | errors <- pullAptRepository(directory, p.Url, d, c, p.Architecture) 265 | } 266 | }(provider, distribution, component) 267 | } 268 | } 269 | 270 | wg.Wait() 271 | close(errors) 272 | 273 | for i := 0; i < operations; i++ { 274 | err := <-errors 275 | 276 | if err != nil { 277 | return err 278 | } 279 | } 280 | } 281 | 282 | return nil 283 | } 284 | -------------------------------------------------------------------------------- /pkg/xbps.go: -------------------------------------------------------------------------------- 1 | package xdeb 2 | 3 | import ( 4 | "crypto/sha256" 5 | "encoding/hex" 6 | "fmt" 7 | "os" 8 | "path/filepath" 9 | 10 | "github.com/urfave/cli/v2" 11 | ) 12 | 13 | func comparePackageChecksums(path string, expected string) error { 14 | hasher := sha256.New() 15 | contents, err := os.ReadFile(path) 16 | 17 | if err != nil { 18 | return err 19 | } 20 | 21 | hasher.Write(contents) 22 | actual := hex.EncodeToString(hasher.Sum(nil)) 23 | 24 | if actual != expected { 25 | return fmt.Errorf("checksums don't match: actual=%s expected=%s", actual, expected) 26 | } 27 | 28 | return nil 29 | } 30 | 31 | func installPackage(path string) error { 32 | workdir := filepath.Dir(path) 33 | binpkgs := filepath.Join(workdir, "binpkgs") 34 | 35 | files, err := filepath.Glob(filepath.Join(binpkgs, "*.xbps")) 36 | 37 | if err != nil { 38 | return err 39 | } 40 | 41 | if len(files) == 0 { 42 | return fmt.Errorf("could not find any XBPS packages to install within '%s'", binpkgs) 43 | } 44 | 45 | xbps := TrimPathExtension(filepath.Base(files[0]), 2) 46 | args := []string{} 47 | 48 | if os.Getuid() > 0 { 49 | args = append(args, "sudo") 50 | } 51 | 52 | args = append(args, "xbps-install", "-R", "binpkgs", xbps) 53 | return ExecuteCommand(workdir, args...) 54 | } 55 | 56 | func InstallPackage(packageDefinition *XdebPackageDefinition, context *cli.Context) error { 57 | packageDefinition.Configure(context.String("temp")) 58 | 59 | if packageDefinition.Provider == "localhost" { 60 | LogMessage("Installing %s from %s", packageDefinition.Name, packageDefinition.FilePath) 61 | } else if packageDefinition.Provider == "remote" { 62 | LogMessage("Installing %s from %s", packageDefinition.Name, packageDefinition.Url) 63 | } else { 64 | LogMessage( 65 | "Installing %s from %s @ %s/%s", 66 | packageDefinition.Name, packageDefinition.Provider, packageDefinition.Distribution, packageDefinition.Component, 67 | ) 68 | } 69 | 70 | // download if an URL is provided 71 | if len(packageDefinition.Url) > 0 { 72 | err := os.RemoveAll(packageDefinition.Path) 73 | 74 | if err != nil { 75 | return err 76 | } 77 | 78 | packageDefinition.FilePath, err = DownloadFile( 79 | filepath.Join(packageDefinition.Path, fmt.Sprintf("%s.deb", packageDefinition.Name)), 80 | packageDefinition.Url, true, false, 81 | ) 82 | 83 | if err != nil { 84 | return err 85 | } 86 | } 87 | 88 | // compare checksums if available 89 | if len(packageDefinition.Sha256) > 0 { 90 | if err := comparePackageChecksums(packageDefinition.FilePath, packageDefinition.Sha256); err != nil { 91 | return err 92 | } 93 | } 94 | 95 | // xdeb convert 96 | if err := convertPackage(packageDefinition.FilePath, context.String("options")); err != nil { 97 | return err 98 | } 99 | 100 | // xbps-install 101 | if err := installPackage(packageDefinition.FilePath); err != nil { 102 | return err 103 | } 104 | 105 | // run post install hooks 106 | if err := packageDefinition.runPostInstallHooks(); err != nil { 107 | return err 108 | } 109 | 110 | // cleanup 111 | return os.RemoveAll(packageDefinition.Path) 112 | } 113 | -------------------------------------------------------------------------------- /pkg/xdeb.go: -------------------------------------------------------------------------------- 1 | package xdeb 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/exec" 7 | "path/filepath" 8 | "sort" 9 | "strconv" 10 | "strings" 11 | 12 | "github.com/adrg/xdg" 13 | version "github.com/knqyf263/go-deb-version" 14 | "gopkg.in/yaml.v2" 15 | ) 16 | 17 | type XdebPackagePostInstallCommandDefinition struct { 18 | Root bool `yaml:"root"` 19 | Command string `yaml:"command"` 20 | } 21 | 22 | type XdebPackagePostInstallDefinition struct { 23 | Name string `yaml:"name"` 24 | Commands []XdebPackagePostInstallCommandDefinition `yaml:"commands"` 25 | } 26 | 27 | type XdebPackageDefinition struct { 28 | Name string `yaml:"name"` 29 | Version string `yaml:"version"` 30 | Url string `yaml:"url"` 31 | Sha256 string `yaml:"sha256"` 32 | PostInstall []XdebPackagePostInstallDefinition `yaml:"post-install,omitempty"` 33 | Path string `yaml:"path,omitempty"` 34 | FilePath string `yaml:"filepath,omitempty"` 35 | Provider string `yaml:"provider,omitempty"` 36 | Distribution string `yaml:"distribution,omitempty"` 37 | Component string `yaml:"component,omitempty"` 38 | IsConfigured bool `yaml:"is_configured,omitempty"` 39 | } 40 | 41 | func (packageDefinition *XdebPackageDefinition) setProvider() { 42 | if len(packageDefinition.Provider) == 0 { 43 | if len(packageDefinition.Url) == 0 { 44 | packageDefinition.Provider = "localhost" 45 | } else { 46 | packageDefinition.Provider = "remote" 47 | } 48 | } 49 | } 50 | 51 | func (packageDefinition *XdebPackageDefinition) setDistribution() { 52 | if packageDefinition.Provider == "localhost" || packageDefinition.Provider == "remote" { 53 | packageDefinition.Distribution = "file" 54 | } 55 | } 56 | 57 | func (packageDefinition *XdebPackageDefinition) setComponent() { 58 | if strings.HasSuffix(packageDefinition.Component, ".yaml") { 59 | packageDefinition.Component = TrimPathExtension(packageDefinition.Component, 1) 60 | } 61 | } 62 | 63 | func (packageDefinition *XdebPackageDefinition) setPaths(rootPath string) { 64 | packageDefinition.Path = filepath.Join(rootPath, packageDefinition.Provider, packageDefinition.Distribution, packageDefinition.Component, packageDefinition.Name) 65 | 66 | if len(packageDefinition.Url) > 0 { 67 | packageDefinition.FilePath = filepath.Join(packageDefinition.Path, filepath.Base(packageDefinition.Url)) 68 | } else { 69 | packageDefinition.FilePath = filepath.Join(packageDefinition.Path, fmt.Sprintf("%s.deb", packageDefinition.Name)) 70 | } 71 | } 72 | 73 | func (packageDefinition *XdebPackageDefinition) Configure(rootPath string) { 74 | if !packageDefinition.IsConfigured { 75 | packageDefinition.setProvider() 76 | packageDefinition.setDistribution() 77 | packageDefinition.setComponent() 78 | packageDefinition.setPaths(rootPath) 79 | 80 | packageDefinition.IsConfigured = true 81 | } 82 | } 83 | 84 | func (packageDefinition *XdebPackageDefinition) runPostInstallHooks() error { 85 | for _, postInstallHook := range packageDefinition.PostInstall { 86 | for _, command := range postInstallHook.Commands { 87 | args := []string{} 88 | 89 | if command.Root && os.Getuid() > 0 { 90 | args = append(args, "sudo") 91 | } 92 | 93 | args = append(args, strings.Split(command.Command, " ")...) 94 | 95 | LogMessage("Running post-install hook: %s", postInstallHook.Name) 96 | 97 | if err := ExecuteCommand(packageDefinition.Path, args...); err != nil { 98 | return err 99 | } 100 | } 101 | } 102 | 103 | return nil 104 | } 105 | 106 | type XdebProviderDefinition struct { 107 | Xdeb []*XdebPackageDefinition `yaml:"xdeb"` 108 | } 109 | 110 | func parseYamlDefinition(path string) (*XdebProviderDefinition, error) { 111 | yamlFile, err := decompressFile(path) 112 | 113 | if err != nil { 114 | return nil, err 115 | } 116 | 117 | definition := XdebProviderDefinition{} 118 | err = yaml.Unmarshal(yamlFile, &definition) 119 | 120 | if err != nil { 121 | return nil, err 122 | } 123 | 124 | return &definition, nil 125 | } 126 | 127 | func FindPackage(name string, path string, provider string, distribution string, exact bool) ([]*XdebPackageDefinition, error) { 128 | LogMessage("Looking for package %s (exact: %s) via provider %s and distribution %s ...", name, strconv.FormatBool(exact), provider, distribution) 129 | 130 | globPattern := filepath.Join(path, provider, distribution, "*.yaml.zst") 131 | globbed, err := filepath.Glob(globPattern) 132 | 133 | if err != nil { 134 | return nil, err 135 | } 136 | 137 | if len(globbed) == 0 { 138 | return nil, fmt.Errorf("no repositories present on the system, please sync repositories first") 139 | } 140 | 141 | packageDefinitions := []*XdebPackageDefinition{} 142 | 143 | for _, match := range globbed { 144 | definition, err := parseYamlDefinition(match) 145 | 146 | if err != nil { 147 | return nil, err 148 | } 149 | 150 | for index := range definition.Xdeb { 151 | if (exact && definition.Xdeb[index].Name == name) || (!exact && strings.HasPrefix(definition.Xdeb[index].Name, name)) { 152 | distPath := filepath.Dir(match) 153 | 154 | definition.Xdeb[index].Component = TrimPathExtension(filepath.Base(match), 2) 155 | definition.Xdeb[index].Distribution = filepath.Base(distPath) 156 | definition.Xdeb[index].Provider = filepath.Base(filepath.Dir(distPath)) 157 | 158 | packageDefinitions = append(packageDefinitions, definition.Xdeb[index]) 159 | } 160 | } 161 | } 162 | 163 | if len(packageDefinitions) == 0 { 164 | return nil, fmt.Errorf("could not find package '%s'", name) 165 | } 166 | 167 | sort.Slice(packageDefinitions, func(i int, j int) bool { 168 | versionA, err := version.NewVersion(packageDefinitions[i].Version) 169 | 170 | if err != nil { 171 | return false 172 | } 173 | 174 | versionB, err := version.NewVersion(packageDefinitions[j].Version) 175 | 176 | if err != nil { 177 | return false 178 | } 179 | 180 | return versionA.GreaterThan(versionB) 181 | }) 182 | 183 | return packageDefinitions, nil 184 | } 185 | 186 | func RepositoryPath() (string, error) { 187 | arch, err := FindArchitecture() 188 | 189 | if err != nil { 190 | return "", err 191 | } 192 | 193 | return filepath.Join(xdg.ConfigHome, APPLICATION_NAME, "repositories", arch), nil 194 | } 195 | 196 | func FindXdeb() (string, error) { 197 | xdebPath, err := exec.LookPath("xdeb") 198 | 199 | if err != nil { 200 | return "", fmt.Errorf("xdeb is not installed, please install it via 'xdeb-install xdeb' or manually from '%s'", XDEB_URL) 201 | } 202 | 203 | LogMessage("Package xdeb found: %s", xdebPath) 204 | return xdebPath, nil 205 | } 206 | 207 | func convertPackage(path string, xdebArgs string) error { 208 | if strings.Contains(xdebArgs, "i") { 209 | xdebArgs = strings.ReplaceAll(xdebArgs, "i", "") 210 | } 211 | 212 | xdebPath, _ := FindXdeb() 213 | return ExecuteCommand(filepath.Dir(path), xdebPath, xdebArgs, path) 214 | } 215 | -------------------------------------------------------------------------------- /scripts/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export CGO_ENABLED=0 4 | export GOOS=linux 5 | 6 | export GIT_TAG=$(git describe --tags || git rev-parse HEAD) 7 | 8 | GIT_AUTHORS="" 9 | ga_all=$(git log --pretty="%an=%ae" | sort -u | paste -sd "," - | tr ' ' ':' | tr ',' ' ') 10 | 11 | # hide author email if author didn't sign-off any commits 12 | for ga in ${ga_all} 13 | do 14 | ga_name=$(echo "${ga}" | tr ':' ' ' | cut -d '=' -f 1) 15 | ga_email=$(echo "${ga}" | cut -d '=' -f 2) 16 | 17 | ga_commit_count=$(git --no-pager log --grep="Signed-off-by: ${ga_name} <${ga_email}>" | sort -u | wc -l) 18 | 19 | if [ ! -z "${GIT_AUTHORS}" ]; then 20 | GIT_AUTHORS+="," 21 | fi 22 | 23 | if [ ${ga_commit_count} -gt 0 ]; then 24 | GIT_AUTHORS+="${ga_name} <${ga_email}>" 25 | else 26 | GIT_AUTHORS+="${ga_name}" 27 | fi 28 | done 29 | 30 | export GO_PACKAGE_VERSION="main.VersionString=${GIT_TAG}" 31 | export GO_PACKAGE_COMPILED="main.VersionCompiled=$(date +%s)" 32 | export GO_PACKAGE_AUTHORS="main.VersionAuthors=${GIT_AUTHORS}" 33 | 34 | export GO_BUILD_FLAGS="-extldflags=-static -w -s" 35 | export GO_PACKAGE_FLAGS="-X '${GO_PACKAGE_VERSION}' -X '${GO_PACKAGE_COMPILED}' -X '${GO_PACKAGE_AUTHORS}'" 36 | 37 | BUILD_ARCHITECTURES="amd64/x86_64 386/i686 arm64/aarch64" 38 | 39 | go get 40 | 41 | for build_arch in ${BUILD_ARCHITECTURES}; do 42 | export GOARCH=$(echo "${build_arch}" | cut -d '/' -f 1) 43 | void_arch=$(echo "${build_arch}" | cut -d '/' -f 2) 44 | 45 | go build -ldflags="${GO_BUILD_FLAGS} ${GO_PACKAGE_FLAGS}" -o bin/xdeb-install-linux-${void_arch} 46 | done 47 | -------------------------------------------------------------------------------- /scripts/changelog.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | first_tag=$(git tag --sort=-version:refname | head -n 2 | tail -1) 4 | second_tag=$(git tag --sort=-version:refname | head -n 1) 5 | 6 | mkdir -p bin 7 | 8 | echo "## What's Changed" > bin/changelog.md 9 | 10 | echo >> bin/changelog.md 11 | git log ${first_tag}..${second_tag} --pretty=format:"- %s (%h)" >> bin/changelog.md 12 | echo >> bin/changelog.md 13 | echo >> bin/changelog.md 14 | 15 | go_module=$(go list -m | sed 's/\/v.*//') 16 | 17 | echo "Test report: https://xdeb-org.github.io/xdeb-install" >> bin/changelog.md 18 | echo >> bin/changelog.md 19 | 20 | echo "**Full Changelog**: [\`${first_tag}..${second_tag}\`](https://${go_module}/compare/${first_tag}..${second_tag})" >> bin/changelog.md 21 | echo >> bin/changelog.md 22 | 23 | echo "## SHA256 Checksums" >> bin/changelog.md 24 | echo '```' >> bin/changelog.md 25 | 26 | for binary in $(ls -d -1 bin/xdeb-*); do 27 | sha256sum ${binary} | sed 's/bin\///' >> bin/changelog.md 28 | done 29 | 30 | echo '```' >> bin/changelog.md 31 | -------------------------------------------------------------------------------- /scripts/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | # note: tests will run with 'latest' version of xdeb for now 6 | # because tests/test_xdeb.py installs 'latest' last 7 | 8 | mkdir -p html 9 | PYTEST_TEST_REPORT_ARGS="--html=html/test-report.html --self-contained-html" 10 | 11 | if [ "${1}" = "html" ]; then 12 | pytest ${PYTEST_TEST_REPORT_ARGS} 13 | else 14 | pytest 15 | fi 16 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xdeb-org/xdeb-install/0b41d9d022f046a2b2c68dc363ea8f2af329dd79/tests/__init__.py -------------------------------------------------------------------------------- /tests/constants.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | 4 | DEB_NONEXISTENT_PACKAGE = "DEFINITELY-NON-EXISTENT-PACKAGE" 5 | 6 | 7 | XDEB_BINARY_PATH = Path("/usr/local/bin/xdeb") 8 | XDEB_RELEASES = ( 9 | "1.0", 10 | "1.1", 11 | "1.2", 12 | "1.3" 13 | ) 14 | 15 | XDEB_INSTALL_BINARY_PATH = Path(__file__).parent.parent.joinpath("bin", "xdeb-install-linux-x86_64") 16 | 17 | XDEB_INSTALL_PROVIDERS = ( 18 | "debian.org", 19 | "linuxmint.com", 20 | "ubuntu.com", 21 | "microsoft.com", 22 | "google.com", 23 | ) 24 | 25 | XDEB_INSTALL_HAVE_PACKAGE = { 26 | "speedcrunch": { 27 | "debian.org": { 28 | "any": True, 29 | "distributions": { 30 | "bookworm": True, 31 | "bookworm-backports": False, 32 | "bullseye": True, 33 | "bullseye-backports": False, 34 | "buster": True, 35 | "buster-backports": False, 36 | "sid": True, 37 | "testing": False, 38 | "testing-backports": False 39 | }, 40 | }, 41 | "linuxmint.com": { 42 | "any": False 43 | }, 44 | "ubuntu.com": { 45 | "any": True, 46 | "distributions": { 47 | "bionic": True, 48 | "focal": True, 49 | "jammy": True 50 | } 51 | }, 52 | "microsoft.com": { 53 | "any": False 54 | }, 55 | "google.com": { 56 | "any": False 57 | }, 58 | }, 59 | "vscode": { 60 | "debian.org": { 61 | "any": False 62 | }, 63 | "linuxmint.com": { 64 | "any": False 65 | }, 66 | "ubuntu.com": { 67 | "any": False 68 | }, 69 | "microsoft.com": { 70 | "any": True, 71 | "distributions": { 72 | "current": True 73 | } 74 | }, 75 | "google.com": { 76 | "any": False 77 | } 78 | }, 79 | "google-chrome": { 80 | "debian.org": { 81 | "any": False 82 | }, 83 | "linuxmint.com": { 84 | "any": False 85 | }, 86 | "ubuntu.com": { 87 | "any": False 88 | }, 89 | "microsoft.com": { 90 | "any": False 91 | }, 92 | "google.com": { 93 | "any": True, 94 | "distributions": { 95 | "current": True 96 | } 97 | } 98 | }, 99 | "google-chrome-unstable": { 100 | "debian.org": { 101 | "any": False 102 | }, 103 | "linuxmint.com": { 104 | "any": False 105 | }, 106 | "ubuntu.com": { 107 | "any": False 108 | }, 109 | "microsoft.com": { 110 | "any": False 111 | }, 112 | "google.com": { 113 | "any": True, 114 | "distributions": { 115 | "current": True 116 | } 117 | } 118 | } 119 | } 120 | 121 | XDEB_INSTALL_PACKAGE_MAP = { 122 | "vscode": "code", 123 | "google-chrome": "google-chrome-stable" 124 | } 125 | -------------------------------------------------------------------------------- /tests/helpers.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | 3 | from . import constants 4 | 5 | 6 | def assert_xdeb_install_command(*args): 7 | subprocess.check_call([constants.XDEB_INSTALL_BINARY_PATH, *args]) 8 | 9 | 10 | def assert_command_assume_yes(returncode: int, *args): 11 | process = subprocess.run(*args, input="yes\n".encode(), stdout=subprocess.PIPE) 12 | assert process.returncode == returncode 13 | 14 | 15 | def assert_xdeb_install_xbps(returncode: int, *args): 16 | assert_command_assume_yes(returncode, [constants.XDEB_INSTALL_BINARY_PATH, *args]) 17 | 18 | if returncode == 0: 19 | package = args[-1] if args[-1] not in constants.XDEB_INSTALL_PACKAGE_MAP else constants.XDEB_INSTALL_PACKAGE_MAP[args[-1]] 20 | assert_command_assume_yes(0, ["sudo", "xbps-remove", package]) 21 | assert_command_assume_yes(0, ["sudo", "xbps-remove", "-Oo"]) 22 | 23 | 24 | def assert_xdeb_installation(version: str = None): 25 | # remove any previous xdeb binary 26 | subprocess.check_call(["sudo", "rm", "-rf", constants.XDEB_BINARY_PATH]) 27 | 28 | # install xdeb version 29 | args = ["xdeb"] 30 | 31 | if version is not None: 32 | args.append(version) 33 | 34 | assert_xdeb_install_command(*args) 35 | subprocess.check_call([constants.XDEB_BINARY_PATH, "-h"]) 36 | -------------------------------------------------------------------------------- /tests/test_00_general.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from . import helpers 4 | 5 | 6 | @pytest.mark.order(0) 7 | def test_version(): 8 | helpers.assert_xdeb_install_command("--version") 9 | 10 | 11 | @pytest.mark.order(1) 12 | def test_help(): 13 | helpers.assert_xdeb_install_command("--help") 14 | -------------------------------------------------------------------------------- /tests/test_10_xdeb.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from . import constants 4 | from . import helpers 5 | 6 | 7 | @pytest.mark.order(10) 8 | def test_xdeb(): 9 | helpers.assert_xdeb_installation() 10 | 11 | for release in constants.XDEB_RELEASES: 12 | helpers.assert_xdeb_installation(release) 13 | 14 | helpers.assert_xdeb_installation("latest") 15 | -------------------------------------------------------------------------------- /tests/test_20_providers.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from . import helpers 3 | 4 | 5 | @pytest.mark.order(20) 6 | def test_providers(): 7 | helpers.assert_xdeb_install_command("providers") 8 | 9 | 10 | @pytest.mark.order(21) 11 | def test_providers_details(): 12 | helpers.assert_xdeb_install_command("providers", "--details") 13 | -------------------------------------------------------------------------------- /tests/test_30_sync.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from . import constants 4 | from . import helpers 5 | 6 | 7 | @pytest.mark.order(30) 8 | def test_sync(): 9 | helpers.assert_xdeb_install_command("sync") 10 | 11 | 12 | @pytest.mark.order(31) 13 | def test_sync_single(): 14 | for provider in constants.XDEB_INSTALL_PROVIDERS: 15 | helpers.assert_xdeb_install_command("sync", provider) 16 | 17 | 18 | @pytest.mark.order(32) 19 | def test_sync_each(): 20 | helpers.assert_xdeb_install_command("sync", *constants.XDEB_INSTALL_PROVIDERS) 21 | -------------------------------------------------------------------------------- /tests/test_40_search.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import pytest 3 | 4 | from . import constants 5 | from . import helpers 6 | 7 | 8 | @pytest.mark.order(40) 9 | def test_search_nothing(): 10 | with pytest.raises(subprocess.CalledProcessError): 11 | helpers.assert_xdeb_install_command("search") 12 | 13 | 14 | @pytest.mark.order(41) 15 | def test_search_nonexistent(): 16 | with pytest.raises(subprocess.CalledProcessError): 17 | helpers.assert_xdeb_install_command("search", constants.DEB_NONEXISTENT_PACKAGE) 18 | 19 | 20 | @pytest.mark.order(42) 21 | def test_search_speedcrunch(): 22 | helpers.assert_xdeb_install_command("sync") 23 | helpers.assert_xdeb_install_command("search", "speedcrunch") 24 | 25 | 26 | @pytest.mark.order(43) 27 | def test_search_packages(): 28 | helpers.assert_xdeb_install_command("sync") 29 | 30 | for package, provider_data in constants.XDEB_INSTALL_HAVE_PACKAGE.items(): 31 | for provider, data in provider_data.items(): 32 | if not data["any"]: 33 | with pytest.raises(subprocess.CalledProcessError): 34 | helpers.assert_xdeb_install_command("search", "--provider", provider, package) 35 | continue 36 | 37 | helpers.assert_xdeb_install_command("search", "--provider", provider, package) 38 | 39 | for distribution, available in data["distributions"].items(): 40 | if not available: 41 | with pytest.raises(subprocess.CalledProcessError): 42 | helpers.assert_xdeb_install_command("search", "--provider", provider, "--distribution", distribution, package) 43 | continue 44 | 45 | helpers.assert_xdeb_install_command("search", "--provider", provider, "--distribution", distribution, package) 46 | -------------------------------------------------------------------------------- /tests/test_50_install.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from . import constants 4 | from . import helpers 5 | 6 | 7 | @pytest.mark.order(51) 8 | def test_install_nothing(): 9 | helpers.assert_xdeb_install_xbps(1) 10 | 11 | 12 | @pytest.mark.order(52) 13 | def test_install_nonexistent(): 14 | helpers.assert_xdeb_install_xbps(1, constants.DEB_NONEXISTENT_PACKAGE) 15 | 16 | 17 | @pytest.mark.order(53) 18 | def test_install_speedcrunch(): 19 | helpers.assert_xdeb_install_command("sync") 20 | helpers.assert_xdeb_install_xbps(0, "speedcrunch") 21 | 22 | 23 | @pytest.mark.order(54) 24 | def test_install_packages(): 25 | helpers.assert_xdeb_install_command("sync") 26 | 27 | for package, provider_data in constants.XDEB_INSTALL_HAVE_PACKAGE.items(): 28 | for provider, data in provider_data.items(): 29 | if not data["any"]: 30 | helpers.assert_xdeb_install_xbps(1, "--provider", provider, package) 31 | continue 32 | 33 | helpers.assert_xdeb_install_xbps(0, "--provider", provider, package) 34 | 35 | for distribution, available in data["distributions"].items(): 36 | if not available: 37 | helpers.assert_xdeb_install_xbps(1, "--provider", provider, "--distribution", distribution, package) 38 | continue 39 | 40 | helpers.assert_xdeb_install_xbps(0, "--provider", provider, "--distribution", distribution, package) 41 | -------------------------------------------------------------------------------- /tests/test_60_clean.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from . import helpers 4 | 5 | 6 | @pytest.mark.order(60) 7 | def test_clean(): 8 | helpers.assert_xdeb_install_command("clean") 9 | 10 | 11 | @pytest.mark.order(61) 12 | def test_clean_lists(): 13 | helpers.assert_xdeb_install_command("clean", "--lists") 14 | --------------------------------------------------------------------------------