217 Commits

Author SHA1 Message Date
e8202060d8 chore: remove debug slog.Warn
All checks were successful
Pre-commit / pre-commit (pull_request) Successful in 6m0s
Update alr-git / changelog (push) Successful in 26s
Create Release / changelog (push) Successful in 2m54s
2025-06-22 17:27:57 +03:00
c4a92c67d4 fix parsing overrides
All checks were successful
Pre-commit / pre-commit (pull_request) Successful in 6m34s
Update alr-git / changelog (push) Successful in 29s
2025-06-22 12:44:21 +03:00
85878f69d3 feat: add checksum for torrent downloader
All checks were successful
Pre-commit / pre-commit (pull_request) Successful in 6m0s
Update alr-git / changelog (push) Successful in 28s
2025-06-20 20:12:43 +03:00
6bccce1db4 feat: add checksum for git downloader 2025-06-20 19:35:22 +03:00
b5474b1eb4 ci: disable building alr-bin
All checks were successful
Pre-commit / pre-commit (pull_request) Successful in 7m5s
Update alr-git / changelog (push) Successful in 30s
2025-06-20 09:21:03 +03:00
51fdea781b fix: correct pull for multiple repos 2025-06-20 09:08:34 +03:00
4c1f2ea90f feat: support mirrors
All checks were successful
Pre-commit / pre-commit (pull_request) Successful in 5m45s
Update alr-git / changelog (push) Successful in 25s
2025-06-19 19:00:08 +03:00
7fa7f8ba82 security: update vulnerable packages
All checks were successful
Pre-commit / pre-commit (pull_request) Successful in 6m11s
Update alr-git / changelog (push) Successful in 29s
Vulnerabilities detected by Trivy scan:
- github.com/cloudflare/circl (GHSA-2x5j-vhc8-9cwm)
2025-06-19 12:10:31 +03:00
25d001c1c9 fix: add find-files (#109)
All checks were successful
Update alr-git / changelog (push) Successful in 31s
closes #96

Reviewed-on: #109
Co-authored-by: Maxim Slipenko <no-reply@maxim.slipenko.com>
Co-committed-by: Maxim Slipenko <no-reply@maxim.slipenko.com>
2025-06-19 09:03:37 +00:00
f86b3003b1 fix: add symlink handling in createFirejailedBinary (#108)
All checks were successful
Update alr-git / changelog (push) Successful in 30s
closes #107

Reviewed-on: #108
Co-authored-by: Maxim Slipenko <no-reply@maxim.slipenko.com>
Co-committed-by: Maxim Slipenko <no-reply@maxim.slipenko.com>
2025-06-17 18:56:19 +00:00
bd79dcf401 feat: add firejailed support (#106)
Some checks failed
Update alr-git / changelog (push) Successful in 27s
Create Release / changelog (push) Failing after 3m15s
Reviewed-on: #106
Co-authored-by: Maxim Slipenko <no-reply@maxim.slipenko.com>
Co-committed-by: Maxim Slipenko <no-reply@maxim.slipenko.com>
2025-06-15 13:16:02 +00:00
d1fe02fa57 feat: support single package repository
All checks were successful
Pre-commit / pre-commit (pull_request) Successful in 7m49s
Update alr-git / changelog (push) Successful in 31s
2025-06-14 22:26:33 +03:00
1ca7801fba fix(altlinux): use APT::Install::Virtual=true for install
All checks were successful
Pre-commit / pre-commit (pull_request) Successful in 8m40s
Update alr-git / changelog (push) Successful in 34s
2025-06-14 15:50:58 +03:00
661d79ce24 fix: remove debug logs and fix cache clearing 2025-06-14 15:05:18 +03:00
bece64c132 сi: update alr-spec
All checks were successful
Pre-commit / pre-commit (pull_request) Successful in 7m12s
Update alr-git / changelog (push) Successful in 28s
2025-06-12 22:42:15 +03:00
6d29b98cf7 ci: fix update-alr-git and disable e2e
Some checks failed
Pre-commit / pre-commit (pull_request) Successful in 6m26s
Update alr-git / changelog (push) Failing after 26s
2025-06-12 22:05:37 +03:00
d286041864 ci: add alr-git update
Some checks failed
E2E / tests (pull_request) Has been cancelled
Pre-commit / pre-commit (pull_request) Successful in 10m12s
Update alr-git / changelog (push) Failing after 58s
2025-06-12 16:57:35 +03:00
392a522723 refactor: keep only one struct for package 2025-06-12 16:25:18 +03:00
e259184a89 refactor: migrate to xorm 2025-06-10 14:12:40 +03:00
65ab4de561 refactor: move distro from internal to pkg 2025-06-09 22:58:34 +03:00
1cdab8dfed refactor: move alr.sh parsing to pkg 2025-06-09 17:56:46 +03:00
237e2c338d refactor: move types from internal to pkg 2025-06-09 13:34:43 +03:00
703ab8e8c4 refactor: move pkg/ to internal/ and update imports
Some checks failed
E2E / tests (pull_request) Has been cancelled
Pre-commit / pre-commit (pull_request) Successful in 5m35s
Restructure project by relocating package contents from pkg/ to internal/ to better reflect internal-only usage. This commit is initial step to prepare project for public api
2025-06-09 10:15:47 +03:00
06fcab4ce7 fix: prevent for building dependencies twice (#99)
closes #94

Reviewed-on: #99
Co-authored-by: Maxim Slipenko <no-reply@maxim.slipenko.com>
Co-committed-by: Maxim Slipenko <no-reply@maxim.slipenko.com>
2025-06-08 17:57:18 +00:00
7741c7368b исправление ссылки на alr-LG 2025-06-06 13:49:14 +00:00
69f4af0a4d ci: fix
Some checks failed
Create Release / changelog (push) Failing after 2m2s
2025-05-30 20:01:16 +03:00
bcf627f176 ci: try add privileged true
Some checks failed
Create Release / changelog (push) Has been cancelled
2025-05-30 20:00:15 +03:00
6ec95e4bd9 ci: add bindfs install
Some checks failed
Create Release / changelog (push) Failing after 2m5s
2025-05-30 19:50:59 +03:00
578da7ff52 Revert "fix: use mount only for non-root users"
This reverts commit c51caf5c52.
2025-05-30 19:50:38 +03:00
c51caf5c52 fix: use mount only for non-root users
Some checks failed
Create Release / changelog (push) Failing after 2m0s
2025-05-30 19:41:17 +03:00
09dba577c6 ci: fix
Some checks failed
Create Release / changelog (push) Failing after 2m0s
2025-05-30 19:31:12 +03:00
ca82bf3024 ci: fix
Some checks failed
Create Release / changelog (push) Failing after 2m3s
2025-05-30 19:26:08 +03:00
c0023db6cd chore: fix install
Some checks failed
Create Release / changelog (push) Failing after 2m12s
2025-05-30 19:02:04 +03:00
152e5077ec ci(release): add make install
Some checks failed
Create Release / changelog (push) Failing after 2m0s
2025-05-30 18:53:17 +03:00
15ba8700e8 ci: set gitea/runner-images:ubuntu-latest for release
Some checks failed
Create Release / changelog (push) Failing after 1m50s
2025-05-30 17:36:51 +03:00
a8aefc0524 chore: replace git urls in tests
Some checks failed
E2E / tests (pull_request) Failing after 2m4s
Pre-commit / pre-commit (pull_request) Successful in 14m15s
Create Release / changelog (push) Has been cancelled
2025-05-30 07:49:58 +03:00
9540030579 изменение: скрипт установки берёт бинарники из релиза, README.md 2025-05-29 17:48:40 +03:00
4f9d4260b8 добавление изменения версии в репозитории alr-default + сборка бинарников для релиза
Some checks failed
Create Release / changelog (push) Failing after 32s
2025-05-17 19:41:50 +03:00
38b5e6f581 добавление изменения версии в репозитории alr-default + сборка бинарников для релиза
Some checks failed
Create Release / changelog (push) Failing after 33s
2025-05-17 19:19:09 +03:00
408bd12302 добавление изменения версии в репозитории alr-default + сборка бинарников для релиза
Some checks failed
Create Release / changelog (push) Failing after 33s
2025-05-17 19:12:19 +03:00
fb83d544de добавление изменения версии в репозитории alr-default + сборка бинарников для релиза
Some checks failed
Create Release / changelog (push) Failing after 32s
2025-05-17 19:08:54 +03:00
2cb963d4b2 добавление изменения версии в репозитории alr-default + сборка бинарников для релиза
Some checks failed
Create Release / changelog (push) Failing after 33s
2025-05-17 19:00:04 +03:00
e74d74cdf6 добавление изменения версии в репозитории alr-default + сборка бинарников для релиза
Some checks failed
Create Release / changelog (push) Failing after 35s
2025-05-17 18:54:59 +03:00
5b3d53d253 добавление изменения версии в репозитории alr-default + сборка бинарников для релиза
Some checks failed
Create Release / changelog (push) Failing after 34s
2025-05-17 18:41:57 +03:00
36e704f735 добавление изменения версии в репозитории alr-default + сборка бинарников для релиза 2025-05-17 18:32:25 +03:00
07356d5e55 добавление изменения версии в репозитории alr-default + сборка бинарников для релиза
Some checks failed
Create Release / changelog (push) Failing after 1m5s
2025-05-17 18:06:49 +03:00
52bd6aca93 Исправление README.md 2025-05-17 12:11:57 +03:00
2f1770b43b Дополнение README.md 2025-05-16 23:07:18 +03:00
9d5b5b51ff Дополнение README.md 2025-05-16 23:05:58 +03:00
c88478a450 ci: fix release workflow
All checks were successful
Create Release / changelog (push) Successful in 54s
2025-05-16 21:45:14 +03:00
3e61fec67c ci: fix release workflow
Some checks failed
Create Release / changelog (push) Has been cancelled
2025-05-16 21:42:56 +03:00
6f484a1169 ci: fix release workflow
All checks were successful
Create Release / changelog (push) Successful in 49s
2025-05-16 21:30:57 +03:00
dddcb9b7b0 ci: fix release workflow
Some checks failed
Create Release / changelog (push) Failing after 40s
2025-05-16 21:27:04 +03:00
b03d94e48b ci: add release workflow
Some checks failed
E2E / tests (pull_request) Successful in 1m45s
Pre-commit / pre-commit (pull_request) Successful in 1m38s
Create Release / changelog (push) Failing after 11s
2025-05-16 21:14:37 +03:00
f92bd7089a add set-ref command and refactor tests
All checks were successful
E2E / tests (pull_request) Successful in 1m42s
Pre-commit / pre-commit (pull_request) Successful in 1m23s
2025-05-16 20:48:14 +03:00
eb2356458c ci: add e2e (#90)
Reviewed-on: #90
Co-authored-by: Maxim Slipenko <no-reply@maxim.slipenko.com>
Co-committed-by: Maxim Slipenko <no-reply@maxim.slipenko.com>
2025-05-16 16:19:24 +00:00
131f455eff add repo subcommand
All checks were successful
Pre-commit / pre-commit (pull_request) Successful in 1m36s
2025-05-14 23:04:28 +03:00
1e52d30f4c fix list command
All checks were successful
Pre-commit / pre-commit (pull_request) Successful in 1m35s
2025-05-13 23:31:56 +03:00
40ec0ac6e1 add --upgradable option for list
All checks were successful
Pre-commit / pre-commit (pull_request) Successful in 1m36s
2025-05-13 23:26:12 +03:00
443e481561 fix support of multiple packages in one alr.sh
All checks were successful
Pre-commit / pre-commit (pull_request) Successful in 1m37s
2025-05-13 21:55:23 +03:00
c892310f69 fix Makefile
All checks were successful
Pre-commit / pre-commit (pull_request) Successful in 2m49s
2025-05-12 20:11:55 +03:00
750513b119 fix ci
Some checks failed
Pre-commit / pre-commit (pull_request) Failing after 2m36s
2025-05-12 19:46:52 +03:00
ce1836b646 ci: use go 1.24
Some checks failed
Pre-commit / pre-commit (pull_request) Failing after 3m52s
2025-05-12 19:30:15 +03:00
56b9f3211c ci: add simple workflow for pre-commit
Some checks failed
Pre-commit / pre-commit (pull_request) Failing after 5m29s
2025-05-12 19:22:50 +03:00
fae63e28f9 fix license-header.tmpl 2025-05-12 19:22:28 +03:00
c632ddb354 add the ability to specify repository ref (#80)
closes #75

Reviewed-on: #80
Co-authored-by: Maxim Slipenko <no-reply@maxim.slipenko.com>
Co-committed-by: Maxim Slipenko <no-reply@maxim.slipenko.com>
2025-05-08 18:04:51 +00:00
76234bf00d Merge pull request 'adds a rootCmd call if necessary' (#79) from Maks1mS/ALR:add-root-cmd into master
Reviewed-on: #79
2025-05-08 17:24:55 +00:00
f8c510ab9f adds a rootCmd call if necessary 2025-05-08 20:23:24 +03:00
849a08a791 Merge pull request 'add AUTHORS' (#77) from Maks1mS/ALR:add-authors into master
Reviewed-on: #77
2025-05-07 21:07:20 +00:00
952dd26f5f add AUTHORS 2025-05-07 21:08:12 +03:00
080c9f42ff Merge pull request 'fix installing packages with deps' (#73) from Maks1mS/ALR:fix-72 into master
Reviewed-on: #73
2025-05-03 11:16:50 +00:00
3c3ee286ce fix installing packages with deps 2025-05-03 08:28:03 +03:00
d0d8930491 Дополнено: шаблон генерации пакетов pip 2025-04-29 11:22:43 +03:00
93508647e0 Merge pull request 'fix: correct forward stdout / stderr from script executor' (#71) from Maks1mS/ALR:issue-70 into master
Reviewed-on: #71
2025-04-27 15:29:57 +00:00
6135e55f92 fix: correct forward stdout / stderr from script executor 2025-04-27 18:28:20 +03:00
2b7c2bbbb3 Merge pull request 'feat: add group and summary fields' (#69) from Maks1mS/ALR:add-new-fileds into master
Reviewed-on: #69
2025-04-20 06:45:41 +00:00
afe35f407e security: update vulnerable packages
Vulnerabilities detected by Trivy scan:
- github.com/go-git/go-git/v5 (CVE-2025-21613)
- github.com/go-git/go-git/v5 (CVE-2025-21614)
- golang.org/x/crypto (CVE-2025-22869)
- golang.org/x/net (CVE-2025-22870)
- golang.org/x/net (CVE-2025-22872)
2025-04-20 09:04:46 +03:00
c51d1d9202 add group and summary fields 2025-04-20 09:04:46 +03:00
b46dd41ada fix tests 2025-04-20 09:04:46 +03:00
f623cba5c0 use fakeroot from gitea.plemya-x.ru/Plemya-x/fakeroot 2025-04-20 09:04:39 +03:00
e552663442 Merge pull request 'i18n: russian translation' (#68) from Maks1mS/ALR:i18n-russian-translation into master
Reviewed-on: #68
2025-04-19 08:20:31 +00:00
7bbceb76c9 i18n: russian translation 2025-04-18 07:40:15 +03:00
bd6e3bbe27 Merge pull request 'some fixes' (#67) from Maks1mS/ALR:fix/pass-lang-to-child into master
Reviewed-on: #67
2025-04-17 08:01:25 +00:00
0d917190ab fix non-interactive install and add fallback in HandleExitCoder 2025-04-17 10:15:51 +03:00
83b8f3b047 fix(i18n): pass LANG vars to _internal 2025-04-16 08:33:40 +03:00
3483cf57f8 Merge pull request 'feat: migrate to system cache with changing core logic' (#66) from Maks1mS/ALR:migrate-to-global-cache into master
Reviewed-on: #66
2025-04-15 19:38:58 +00:00
efa4f59403 feat: migrate to system cache with changing core logic 2025-04-15 21:41:21 +03:00
c59ed6d505 Исправлено: определение корректной версии .rpm
Дополнено: шаблон генерации пакетов pip
2025-04-06 15:48:28 +03:00
2bbc308810 Merge pull request 'feat: add completion for remove command' (#65) from Maks1mS/ALR:master into master
Reviewed-on: #65
2025-04-06 10:41:53 +00:00
5ca34a572a feat: add completion for remove command 2025-04-06 10:27:27 +03:00
67e63d1831 Merge pull request 'feat: add skiplists for auto_req and auto_prov' (#64) from Maks1mS/ALR:feat/add-skiplist into master
Reviewed-on: #64
2025-04-05 21:30:23 +00:00
0bfe88beed feat: add skiplists for auto_req and auto_prov 2025-04-05 20:19:00 +03:00
3ce9f0db35 Merge pull request 'fix install bash completion' (#63) from Maks1mS/ALR:fix-install-bash-complete into master
Reviewed-on: #63
2025-03-30 12:51:36 +00:00
469a05105a test: add bash completion test 2025-03-30 13:10:48 +03:00
70aca61750 fix install bash complete 2025-03-30 13:09:34 +03:00
4b35f5e4e6 Исправление фильтрации пакетов в зависимости от дистрибутива. 2025-03-28 11:17:58 +03:00
7770675a8d Merge pull request 'fix: add interactive=false handling in remove command' (#60) from Maks1mS/ALR:fix/32 into master
Reviewed-on: #60
2025-03-26 07:28:06 +00:00
bd79d56776 fix: add interactive=false handling in remove command 2025-03-26 10:22:56 +03:00
fff8b777fe Merge pull request 'fix installing multiple packages' (#58) from Maks1mS/ALR:fix/50 into master
Reviewed-on: #58
2025-03-26 07:14:18 +00:00
6bee268ea9 fix installing multiple packages 2025-03-26 10:11:24 +03:00
4b53e819d8 Merge pull request 'chore: enable autoPull' (#57) from Maks1mS/ALR:enable-autopull into master
Reviewed-on: #57
 [Maks1mS]
chore: enable autoPull
2025-03-23 10:30:34 +00:00
6c0e8aeb3f chore: enable autoPull 2025-03-23 13:28:33 +03:00
cbc6b9f452 Merge pull request 'Update config module' (#56) from Maks1mS/ALR:fix/update-config-module into master
Reviewed-on: #56

    Конфигурация явно загружается методом Load, убрана лишняя передача контекста в методы config.

    Конфигурация определяется как комбинация из нескольких источников (частично решает задачу #55):

    defaultConfig

    /etc/alr/alr.toml

    $HOME/.config/alr.toml

    ENV переменных

    2.1. addrepo / removerepo сохраняют конфигурацию в $HOME/.config/alr.toml

    Добавлена возможность задать уровень логов (закрывает #49). Теперь при дальнейшей работе в старые и новые методы желательно добавлять debug логирование.
2025-03-22 12:47:16 +00:00
c705c25613 fix: add config load and directory creation 2025-03-22 13:41:41 +03:00
8f4b021a93 update config module 2025-03-22 12:58:10 +03:00
5e7d4033e4 Merge pull request 'fix: save config after removerepo and LC_ALL=C for info command' (#54) from Maks1mS/ALR:fix-53-and-removerepo into master
Reviewed-on: #54
2025-03-19 05:46:17 +00:00
4ac2432770 fix: removerepo and LC_ALL=C for info command 2025-03-19 08:26:53 +03:00
3c37310f0d Merge pull request 'test: add e2e tests' (#51) from Maks1mS/ALR:tests/add-e2e-test into master
Reviewed-on: #51
2025-03-15 10:17:38 +00:00
d300ab197b test: add e2e tests 2025-03-15 12:52:56 +03:00
eb2cc3c1e6 Merge pull request 'fix: add handling len(pkgs) == 0' (#48) from Maks1mS/ALR:fix/handle-notfound-package into master
Reviewed-on: #48
2025-03-14 16:40:24 +00:00
7a3acfe5c1 fix: add handling len(pkgs) == 0 2025-03-14 19:38:58 +03:00
9cf8af08ab Merge pull request 'fix: remove duplicates correctly' (#46) from Maks1mS/ALR:fix/remove-duplicates-correctly into master
Reviewed-on: #46
2025-03-13 13:44:40 +00:00
86940e8962 tests: add TestRemoveDuplicatesSources 2025-03-13 16:38:36 +03:00
db244204c7 fix: remove duplicates correctly 2025-03-13 16:24:37 +03:00
9cb0a5e9ad Merge pull request 'chore: update package name in Makefile' (#45) from Maks1mS/ALR:fix/correct-set-version into master
Reviewed-on: #45
2025-03-12 15:18:01 +00:00
1a57ccdb83 chore: update package name in Makefile 2025-03-12 15:11:19 +03:00
615cd83fb7 Merge pull request 'fix: resolve absolute path of ScriptDir' (#44) from Maks1mS/ALR:fix/use-abs-scriptdir-path into master
Reviewed-on: #44
2025-03-11 17:30:49 +00:00
27e2f54653 fix: resolve absolute path of ScriptDir 2025-03-11 20:28:41 +03:00
af57165c89 Небольшие правки и исправления. 2025-03-10 14:50:02 +03:00
3770c82240 Merge pull request 'fix: add db.Init() in bash completion' (#42) from Maks1mS/ALR:fix/add-db-init-in-completion into master
Reviewed-on: #42
2025-03-09 20:59:05 +00:00
2dff463303 i18n: update ru translation 2025-03-09 17:32:19 +03:00
9085e38454 fix: add db.Init() in bash completion 2025-03-09 17:30:02 +03:00
a7d016abc9 Замена устаревшего метода установки в шаблоне alr gen pip 2025-03-02 13:50:11 +03:00
4a5cca2d0f Замена устаревшего метода установки в шаблоне alr gen pip 2025-03-02 13:34:22 +03:00
71000fd3cd Merge pull request 'fix: use unique names for packages' (#40) from Maks1mS/ALR:fix/use-unique-package-name into master
Reviewed-on: #40
2025-03-02 06:49:52 +00:00
71968bbe13 fix: fix config saving 2025-02-28 21:14:59 +03:00
29c1a31066 fix: fix list and upgrade commands with new naming 2025-02-28 21:14:21 +03:00
8f94b61a0e fix: use +alr-{reponame} suffix 2025-02-28 20:15:02 +03:00
ae8e2d2807 Merge pull request 'chore: make the application more internationalized' (#39) from Maks1mS/ALR:chore/i18n into master
Reviewed-on: #39

    добавлен badge для русского языка
    переведены все строки
    переведены выводы help
2025-02-28 07:09:22 +00:00
0fa288b8a2 chore: make the application more internationalized 2025-02-27 16:41:18 +03:00
dcac0b9ee5 i18n: translate all strings 2025-02-27 11:43:20 +03:00
4e6e1f524a chore: make the application more internationalized 2025-02-27 11:18:13 +03:00
47a9b9a96c Merge pull request 'chore: refactor code' (#38) from Maks1mS/ALR:chore/refactor-code into master
Reviewed-on: #38

    remove legacy code
    refactor search and add tests
2025-02-22 15:34:57 +00:00
9bb14312bd chore: refactor code
- remove legacy code
- refactor search and add tests
2025-02-22 09:44:59 +03:00
88b8d2fbf3 Merge pull request 'feat: add search command' (#37) from Maks1mS/ALR:feat/add-search-command into master
Reviewed-on: #37
Добавлена команда search.
2025-02-18 18:18:48 +00:00
04523775f1 feat: add search command 2025-02-18 17:55:25 +03:00
adc4a42800 Merge pull request 'fix: remove default repo and disable autoPull by default' (#36) from Maks1mS/ALR:fix/change-default-config into master
Reviewed-on: #36
2025-02-13 15:46:13 +00:00
81651af20d Merge pull request 'feat: add support for multiple packages in one alr.sh' (#35) from Maks1mS/ALR:feat/add-multiple-package-build into master
Reviewed-on: #35
В этом PR добавлена поддержка сборки нескольких пакетов из одного alr.sh, которая скорее всего потребует доработки в дальнейшем.
Пример репозитория с пакетами, содержащие несколько package_: https://gitea.plemya-x.ru/Maks1mS/multipackage-test-repo
2025-02-13 15:45:28 +00:00
f04ebbaf14 chore: update info about xpamych-alr-repo 2025-02-12 19:19:08 +03:00
be1a137eab fix: remove default repo and disable autoPull by default 2025-02-12 19:13:50 +03:00
719a5b7fe7 Merge branch 'master' into feat/add-multiple-package-build 2025-02-12 18:52:16 +03:00
e05bb07f23 feat: add support for multiple packages in one alr.sh 2025-02-12 18:34:35 +03:00
ec053f7e6a Корректировка скрипта первичной установки с учётом ALT linux 2025-02-09 12:44:56 +03:00
ad1696d507 Перевод fakeroot на gitea - fix 2025-02-09 12:13:28 +03:00
a57602a278 Перевод fakeroot на gitea 2025-02-06 14:17:57 +03:00
606cd5473a Merge pull request 'chore: add tests for dl' (#34) from Maks1mS/ALR:chore/add-tests into master
Reviewed-on: #34
2025-01-31 16:33:16 +00:00
d2bcb4e345 chore: remove comment 2025-01-31 18:14:28 +03:00
1fcb88976c tests: add more tests for dl 2025-01-31 18:12:22 +03:00
55feea9b25 chore: remove code duplication 2025-01-31 17:05:14 +03:00
0d1db212e1 tests: add tests for dl 2025-01-31 16:56:26 +03:00
99ec48c4c6 Merge pull request 'chore: fix formatting and add pre-commit' (#33) from Maks1mS/ALR:chore/fix-fmt into master
Reviewed-on: #33
2025-01-31 10:43:56 +00:00
d201aae6e0 chore: fix formatting 2025-01-30 10:10:42 +03:00
4463a32ae7 Merge pull request 'fix: do not expand variables in output of files()' (#31) from Maks1mS/ALR:fix-files into master
Reviewed-on: #31
2025-01-29 07:42:28 +00:00
52fd4a5a00 fix: do not expand variables in output of files() 2025-01-29 00:19:02 +03:00
c437346957 Изменение работы логики аргумента -p (fix) 2025-01-28 18:37:41 +03:00
bf1fc0d878 Merge pull request 'fix: quit after done in progress' (#28) from Maks1mS/ALR:fix/quit-from-progress-after-done into master
Reviewed-on: #28
2025-01-26 14:13:30 +00:00
c3f879b379 fix: quit after done in progress 2025-01-26 17:11:25 +03:00
aa90dfa983 Merge pull request 'fix: add download cancel via context and update progressbar' (#27) from Maks1mS/ALR:fix/migrate-to-new-progressbar into master
Reviewed-on: #27
2025-01-26 13:39:32 +00:00
bba1ed52c5 fix: add download cancel via context and update progressbar 2025-01-26 16:33:00 +03:00
dc1fac29d5 Изменение работы логики аргумента -p 2025-01-26 11:36:03 +03:00
99857efb01 Merge pull request 'po(RU): fix translation' (#26) from x1z53/ALR:master into master
Reviewed-on: #26
2025-01-25 11:35:41 +00:00
19bb87981c po(RU): fix translation 2025-01-25 14:34:45 +03:00
1c78adcca1 Merge pull request 'fix: use platform specific Release in upgrade' (#22) from Maks1mS/ALR:fix/use-platform-specific-release into master
Reviewed-on: #22
2025-01-25 09:30:11 +00:00
a98bd44305 Merge pull request 'fix: use shell.Fields instead strings.Fields' (#21) from Maks1mS/ALR:fix/use-shell-fields into master
Reviewed-on: #21
2025-01-25 08:54:50 +00:00
3deb6c9455 tests: add tests for ReleasePlatformSpecific 2025-01-25 11:54:02 +03:00
981f49587b fix: use platform specific release in compare 2025-01-25 11:16:33 +03:00
35656d63a1 fix: use shell.Fields 2025-01-25 09:39:33 +03:00
6410f7547b Merge pull request 'po(RU): update translation' (#20) from x1z53/ALR:master into master
Reviewed-on: #20
2025-01-24 19:04:33 +00:00
53e783df31 po(RU): update translation 2025-01-24 21:20:45 +03:00
fcc9ef5474 Merge pull request 'feat: add files() function' (#19) from Maks1mS/ALR:feat/files-function into master
Reviewed-on: #19
2025-01-24 16:40:21 +00:00
f6ba4a1c26 feat: add files() function
also add files-find-lang and files-find-doc helpers
2025-01-24 19:23:41 +03:00
b5bf6ab61d Merge pull request 'fix: completely remove old logger' (#18) from Maks1mS/ALR:fix/remove-old-logger into master
Reviewed-on: #18
2025-01-23 08:43:59 +00:00
18e90e4afc fix: completely remove old logger 2025-01-23 11:42:48 +03:00
a09863dfcb Merge pull request 'feat: add autoPull in config' (#17) from Maks1mS/ALR:feat/add-auto-pull-to-config into master
Reviewed-on: #17
2025-01-23 07:37:52 +00:00
fd643ea6cd chore: run make fmt and make i18n 2025-01-22 18:12:26 +03:00
309ecf784f feat: add autoPull in config 2025-01-22 18:10:57 +03:00
30f95a4cbf chore: make usage strings translatable 2025-01-22 17:16:15 +03:00
b9bf908007 chore: remove legacy translation system 2025-01-22 16:53:30 +03:00
a6076b1253 chore: replace old logger with new 2025-01-22 16:37:16 +03:00
ac35b4d71d feat: add new logger and translation 2025-01-22 14:40:11 +03:00
945f920654 Merge pull request 'fix: rename module from plemya-x.ru/alr to gitea.plemya-x.ru/Plemya-x/ALR' (#12) from Maks1mS/ALR:fix/rename-module into master
Reviewed-on: #12
2025-01-20 21:17:58 +00:00
84ac2377fb fix: rename module from plemya-x.ru/alr to gitea.plemya-x.ru/Plemya-x/ALR 2025-01-20 19:58:24 +03:00
de1db25202 Merge pull request 'fix: removeAlreadyInstalled before FindPkgs' (#11) from Maks1mS/ALR:fix/resolve-deps into master
Reviewed-on: #11
2025-01-19 08:55:09 +00:00
2d6504b329 fix: removeAlreadyInstalled before FindPkgs 2025-01-19 11:49:00 +03:00
4ca557402a Merge pull request 'chore: add fmt and update-license' (#10) from Maks1mS/ALR:chore/linting into master
Reviewed-on: #10
2025-01-19 07:41:15 +00:00
e497d41030 chore: run make update-license fmt 2025-01-18 19:30:02 +03:00
d46414a67c Merge branch 'master' into chore/linting 2025-01-18 19:22:18 +03:00
29e2f85eeb Исправление ссылки на скрипт установки 2025-01-18 18:30:26 +03:00
c9c872abbc Merge remote-tracking branch 'gitea/master' 2025-01-18 18:29:50 +03:00
fb93864d09 Revert "Исправление ссылки на скрипт установки"
This reverts commit 9fcd618a83.
2025-01-18 18:29:42 +03:00
9fcd618a83 Исправление ссылки на скрипт установки 2025-01-18 18:29:17 +03:00
1fb9c6b574 Merge pull request 'refactor(db, config, repos): migrate from functions to struct' (#9) from Maks1mS/ALR:refactor/db into master
Reviewed-on: #9
2025-01-18 15:27:10 +00:00
fb5c875713 Merge pull request 'fix: add auto_req and auto_prov' (#7) from Maks1mS/ALR:fix/make-findprovides-findrequires-optional into master
Reviewed-on: #7
2025-01-18 15:24:07 +00:00
3f428ab7b5 chore: add golangci-lint 2025-01-14 15:45:29 +03:00
5b7af1f6b5 chore: refactor license update script 2025-01-14 15:28:54 +03:00
3224d7c6e4 chore: add license update script 2025-01-14 15:24:24 +03:00
e1829c4824 refactor: migrate repos find to struct 2025-01-14 13:18:51 +03:00
12d83f2015 test: fix decoder and handlers tests 2025-01-14 13:04:10 +03:00
6bc6bfdcd9 refactor: migrate dlcache to struct 2025-01-14 12:59:00 +03:00
eeb25c239b refactor: move defaultConfig from config_legacy to config 2025-01-14 11:54:00 +03:00
91937a1fc5 refactor: migrate repo to struct 2025-01-14 11:49:42 +03:00
e827fb8049 refactor: use context logger 2025-01-14 10:41:38 +03:00
a13acc5ed0 refactor: migrate list command to struct API 2025-01-14 10:35:23 +03:00
52d3ab7791 refactor: migrate db and config packages to use struct-based API
Removed global variables in favor of instance variables. This makes the code more maintainable and making it easier to write unit tests without relying on global state.

Marked the old functions with global state as obsolete, redirecting them to use a new API based on struct in order to rewrite the code using these functions gradually.
2025-01-14 10:11:17 +03:00
a345a24b95 fix: add auto_req and auto_prov 2024-12-27 21:15:37 +03:00
5d1d3d7c45 Merge pull request 'feat: add find-provides and find-requires (rpm only)' (#5) from Maks1mS/ALR:feat/auto-provides-requires into master
Reviewed-on: #5
2024-12-26 08:07:15 +00:00
a711edbcc0 fix: ignore empty dependencies 2024-12-23 21:12:08 +03:00
d5636e8094 feat: add find-provides and find-requires (rpm only) 2024-12-19 19:21:41 +03:00
5d17875813 Merge pull request 'Исправление file exists при распаковке архива' (#2) from Maks1mS/ALR:fix/unpack-file-exists-error into master
Reviewed-on: xpamych/ALR#2
2024-11-17 18:25:29 +00:00
41eec2fc98 fix: use MkdirAll instead Mkdir to ignore existing dirs 2024-11-17 21:11:54 +03:00
1273aeae39 Merge branch 'Maks1mS-master'
# Conflicts:
#	pkg/build/build.go
2024-11-16 11:55:20 +03:00
49785d4dc8 Комментирование кода, добавление возможности сборки нескольких пакетов package_* из одного alr.sh 2024-11-16 11:32:47 +03:00
1890311d11 fix: add alt only for rpm on altlinux 2024-11-15 16:35:31 +03:00
eb1c1a1d8c fix: add alt prefix for release 2024-11-15 16:25:09 +03:00
c105cf2cbb chore: go mod tidy 2024-11-15 16:21:57 +03:00
94048184c1 chore: update goreleaser/nfpm 2024-11-15 15:50:45 +03:00
3f1c1b6795 feat: add apt-rpm manager 2024-11-15 15:21:34 +03:00
182 changed files with 16621 additions and 5570 deletions

View File

@ -0,0 +1,57 @@
# ALR - Any Linux Repository
# Copyright (C) 2025 The ALR Authors
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
name: E2E
# on:
# push:
# branches: [ main ]
# pull_request:
on:
workflow_dispatch:
jobs:
tests:
runs-on: ubuntu-latest
container:
image: altlinux.space/maks1ms/actions-container-runner:latest
steps:
- name: Checkout
uses: https://github.com/actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
uses: https://github.com/actions/setup-go@v5
with:
go-version: '1.24'
cache: false
# - name: Cache Podman images
# uses: actions/cache@v4
# with:
# path: |
# ~/.local/share/containers/storage
# /var/lib/containers/storage
# key: ${{ runner.os }}-primes
- name: Run E2E tests
env:
IGNORE_ROOT_CHECK: 1
run: |
make e2e-test

View File

@ -0,0 +1,51 @@
# ALR - Any Linux Repository
# Copyright (C) 2025 The ALR Authors
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
name: Pre-commit
on:
push:
branches: [ main ]
pull_request:
jobs:
pre-commit:
runs-on: ubuntu-latest
container:
image: docker.gitea.com/runner-images:ubuntu-latest
steps:
- name: Checkout
uses: https://github.com/actions/checkout@v4
- name: Set up Go
uses: https://github.com/actions/setup-go@v5
with:
go-version: '1.24'
- name: Set up Python for pre-commit
uses: https://github.com/actions/setup-python@v5
with:
python-version: '3.12'
- name: Install deps
run: apt-get update && apt-get install -y gettext bc
- run: pip install pre-commit
- run: pre-commit install
- run: pre-commit run --all-files

View File

@ -0,0 +1,120 @@
# ALR - Any Linux Repository
# Copyright (C) 2025 The ALR Authors
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
name: Create Release
on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'
jobs:
changelog:
runs-on: ubuntu-latest
steps:
- name: Checkout this repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.24'
- name: Get Changes between Tags
id: changes
uses: simbo/changes-between-tags-action@v1
- name: Set version
run: |
version=$(echo "${GITHUB_REF##*/}" | sed 's/^v//')
echo "Version - $version"
echo "VERSION=$version" >> $GITHUB_ENV
- name: Prepare for install
run: |
apt-get update && apt-get install -y libcap2-bin bindfs
- name: Build alr
env:
IGNORE_ROOT_CHECK: 1
run: |
make build
- name: Create tar.gz
run: |
mkdir -p ./out/completion
cp alr ./out
cp scripts/completion/bash ./out/completion/alr
cp scripts/completion/zsh ./out/completion/_alr
( cd out && tar -czvf ../alr-${{ env.VERSION }}-linux-x86_64.tar.gz * )
- name: Release
uses: akkuman/gitea-release-action@v1
with:
body: ${{ steps.changes.outputs.changes }}
files: |-
alr-${{ env.VERSION }}-linux-x86_64.tar.gz
- name: Checkout alr-default repository
uses: actions/checkout@v4
with:
repository: Plemya-x/alr-default
token: ${{ secrets.GITEAPUBLIC }}
path: alr-default
- name: Update version in alr-bin
run: |
# Замените значения в файле с конфигурацией
sed -i "s/version='[0-9]\+\.[0-9]\+\.[0-9]\+'/version='${{ env.VERSION }}'/g" alr-default/alr-bin/alr.sh
sed -i "s/release='[0-9]\+'/release='1'/g" alr-default/alr-bin/alr.sh
# - name: Install alr
# run: |
# make install
#
# # temporary fix
# groupadd wheel
# usermod -aG wheel root
# - name: Build packages
# run: |
# SCRIPT_PATH=alr-default/alr-bin/alr.sh
# ALR_DISTRO=altlinux ALR_PKG_FORMAT=rpm alr build -s "$SCRIPT_PATH"
# ALR_PKG_FORMAT=rpm alr build -s "$SCRIPT_PATH"
# ALR_PKG_FORMAT=deb alr build -s "$SCRIPT_PATH"
# ALR_PKG_FORMAT=archlinux alr build -s "$SCRIPT_PATH"
# - name: Upload assets
# uses: akkuman/gitea-release-action@v1
# with:
# body: ${{ steps.changes.outputs.changes }}
# files: |-
# alr-bin+alr-default_${{ env.VERSION }}-1.red80_amd64.deb \
# alr-bin+alr-default-${{ env.VERSION }}-1-x86_64.pkg.tar.zst \
# alr-bin+alr-default-${{ env.VERSION }}-1.red80.x86_64.rpm \
# alr-bin+alr-default-${{ env.VERSION }}-alt1.x86_64.rpm
- name: Commit changes
run: |
cd alr-default
git config user.name "gitea"
git config user.email "admin@plemya-x.ru"
git add .
git commit -m "Обновление версии до ${{ env.VERSION }}"
git push

View File

@ -0,0 +1,69 @@
# ALR - Any Linux Repository
# Copyright (C) 2025 The ALR Authors
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
name: Update alr-git
on:
push:
branches:
- master
jobs:
changelog:
runs-on: ubuntu-latest
steps:
- name: Install the latest version of uv
uses: astral-sh/setup-uv@v6
- name: Setup alr-spec
run: |
uv tool install alr-spec==0.0.5
- name: Install alr
run: |
apt-get update && apt-get install -y libcap2-bin
curl -fsS https://gitea.plemya-x.ru/Plemya-x/ALR/raw/branch/master/scripts/install.sh | bash
- name: Checkout this repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set ALR version
run: |
echo "NEW_ALR_VERSION=$(alr helper git-version)" >> $GITHUB_ENV
- name: Checkout alr-default repository
uses: actions/checkout@v4
with:
repository: Plemya-x/alr-default
token: ${{ secrets.GITEAPUBLIC }}
path: alr-default
- name: Update version
working-directory: ./alr-default/alr-git
run: |
alr-spec set-field version $NEW_ALR_VERSION
alr-spec set-field release 1
- name: Commit changes
run: |
cd alr-default
git config user.name "gitea"
git config user.email "admin@plemya-x.ru"
git add .
git commit -m "Обновление версии до $NEW_ALR_VERSION"
git push

6
.gitignore vendored
View File

@ -5,3 +5,9 @@
/internal/config/version.txt
.fleet
.idea
.gigaide
*.out
e2e-tests/alr
commit_msg.txt

50
.golangci.yml Normal file
View File

@ -0,0 +1,50 @@
# ALR - Any Linux Repository
# Copyright (C) 2025 The ALR Authors
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
run:
timeout: 5m
linters-settings:
goimports:
local-prefixes: "gitea.plemya-x.ru/Plemya-x/ALR"
gofmt:
simplify: true
gofumpt:
extra-rules: true
linters:
enable:
- gofmt
- gofumpt
- goimports
- gocritic
- govet
- staticcheck
- unused
- errcheck
- typecheck
# - forbidigo
issues:
fix: true
exclude-rules:
- path: _test\.go
linters:
- errcheck
# TODO: remove
- linters:
- staticcheck
text: "SA1019: interp.ExecHandler"

View File

@ -1,99 +0,0 @@
before:
hooks:
- go mod tidy
builds:
- id: alr
env:
- CGO_ENABLED=0
binary: alr
ldflags:
- -X gitea.plemya-x.ru/xpamych/ALR/src/branch/master/internal/config.Version={{.Version}}
goos:
- linux
goarch:
- amd64
- 386
- arm64
- arm
- riscv64
archives:
- name_template: >-
{{- .ProjectName}}-
{{- .Version}}-
{{- .Os}}-
{{- if .Arch | eq "amd64"}}x86_64
{{- else if .Arch | eq "386"}}i386
{{- else if .Arch | eq "arm64"}}aarch64
{{- else }}{{ .Arch }}{{ end -}}
files:
- scripts/completion/*
nfpms:
- id: alr
package_name: linux-user-repository
file_name_template: >-
{{- .PackageName}}-
{{- .Version}}-
{{- .Os}}-
{{- if .Arch | eq "amd64"}}x86_64
{{- else if .Arch | eq "386"}}i386
{{- else if .Arch | eq "arm64"}}aarch64
{{- else }}{{ .Arch }}{{ end -}}
description: "Any Linux Repository"
homepage: 'https://gitea.plemya-x.ru/xpamych/ALR'
maintainer: 'Евгений Храмов <xpamych@yandex.ru>'
license: GPLv3
formats:
- apk
- deb
- rpm
- archlinux
provides:
- linux-user-repository
conflicts:
- linux-user-repository
recommends:
- aria2
contents:
- src: scripts/completion/bash
dst: /usr/share/bash-completion/completions/alr
- src: scripts/completion/zsh
dst: /usr/share/zsh/site-functions/_alr
aurs:
- name: linux-user-repository-bin
homepage: 'https://gitea.plemya-x.ru/xpamych/ALR'
description: "Any Linux Repository"
maintainers:
- 'Евгений Храмов <xpamych@yandex.ru>'
license: GPLv3
private_key: '{{ .Env.AUR_KEY }}'
git_url: 'ssh://aur@aur.archlinux.org/linux-user-repository-bin.git'
provides:
- alr
conflicts:
- alr
depends:
- sudo
- pacman
optdepends:
- 'aria2: for downloading torrent sources'
package: |-
# binaries
install -Dm755 ./alr "${pkgdir}/usr/bin/alr"
# completions
install -Dm755 ./scripts/completion/bash ${pkgdir}/usr/share/bash-completion/completions/alr
install -Dm755 ./scripts/completion/zsh ${pkgdir}/usr/share/zsh/site-functions/_alr
release:
gitea:
owner: alr
name: alr
gitea_urls:
api: 'https://gitea.elara.ws/api/v1/'
download: 'https://gitea.elara.ws'
skip_tls_verify: false
checksum:
name_template: 'checksums.txt'
snapshot:
name_template: "{{ incpatch .Version }}-next"
changelog:
sort: asc

42
.pre-commit-config.yaml Normal file
View File

@ -0,0 +1,42 @@
# ALR - Any Linux Repository
# Copyright (C) 2025 The ALR Authors
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
repos:
- repo: local
hooks:
- id: test-coverage
name: Run test coverage
entry: make test-coverage
language: system
pass_filenames: false
- id: fmt
name: Format code
entry: make fmt
language: system
pass_filenames: false
- id: update-license
name: Update license
entry: make update-license
language: system
pass_filenames: false
- id: i18n
name: Update i18n
entry: make i18n
language: system
pass_filenames: false

View File

@ -1,9 +0,0 @@
platform: linux/amd64
pipeline:
release:
image: goreleaser/goreleaser
commands:
- goreleaser release
secrets: [ gitea_token, aur_key ]
when:
event: tag

2
AUTHORS Normal file
View File

@ -0,0 +1,2 @@
Евгений Храмов
Maxim Slipenko

View File

@ -1,6 +1,6 @@
NAME := alr
GIT_VERSION = $(shell git describe --tags )
IGNORE_ROOT_CHECK ?= 0
DESTDIR ?=
PREFIX ?= /usr/local
BIN := ./$(NAME)
@ -11,17 +11,23 @@ ZSH_COMPLETION := $(COMPLETIONS_DIR)/zsh
INSTALLED_BASH_COMPLETION := $(DESTDIR)$(PREFIX)/share/bash-completion/completions/$(NAME)
INSTALLED_ZSH_COMPLETION := $(DESTDIR)$(PREFIX)/share/zsh/site-functions/_$(NAME)
ADD_LICENSE_BIN := go run github.com/google/addlicense@4caba19b7ed7818bb86bc4cd20411a246aa4a524
GOLANGCI_LINT_BIN := go run github.com/golangci/golangci-lint/cmd/golangci-lint@v1.63.4
XGOTEXT_BIN := go run github.com/Tom5521/xgotext@v1.2.0
.PHONY: build install clean clear uninstall check-no-root
build: check-no-root $(BIN)
export CGO_ENABLED := 0
$(BIN):
go build -ldflags="-X 'gitea.plemya-x.ru/xpamych/ALR/internal/config.Version=$(GIT_VERSION)'" -o $@
go generate ./...
go build -ldflags="-X 'gitea.plemya-x.ru/Plemya-x/ALR/internal/config.Version=$(GIT_VERSION)'" -o $@
check-no-root:
@if [[ "$$(whoami)" == 'root' ]]; then \
@if [ "$$IGNORE_ROOT_CHECK" != "1" ] && [ "`whoami`" = "root" ]; then \
echo "This target shouldn't run as root" 1>&2; \
echo "Set IGNORE_ROOT_CHECK=1 to override" 1>&2; \
exit 1; \
fi
@ -33,6 +39,13 @@ install: \
$(INSTALED_BIN): $(BIN)
install -Dm755 $< $@
setcap cap_setuid,cap_setgid+ep $(INSTALED_BIN)
@if id alr >/dev/null 2>&1; then \
echo "User 'alr' already exists. Skipping."; \
else \
useradd -r -s /usr/sbin/nologin alr; \
fi
install -d -o alr -g alr -m 755 /var/cache/alr /etc/alr
$(INSTALLED_BASH_COMPLETION): $(BASH_COMPLETION)
install -Dm755 $< $@
@ -48,3 +61,32 @@ uninstall:
clean clear:
rm -f $(BIN)
OLD_FILES=$(shell cat old-files)
IGNORE_OLD_FILES := $(foreach file,$(shell cat old-files),-ignore $(file))
update-license:
$(ADD_LICENSE_BIN) -v -f license-header-old-files.tmpl $(OLD_FILES)
$(ADD_LICENSE_BIN) -v -f license-header.tmpl $(IGNORE_OLD_FILES) .
fmt:
$(GOLANGCI_LINT_BIN) run --fix
i18n:
$(XGOTEXT_BIN) --output ./internal/translations/default.pot
msguniq --use-first -o ./internal/translations/default.pot ./internal/translations/default.pot
msgmerge --backup=off -U ./internal/translations/po/ru/default.po ./internal/translations/default.pot
bash scripts/i18n-badge.sh
test-coverage:
go test ./... -v -coverpkg=./... -coverprofile=coverage.out
bash scripts/coverage-badge.sh
update-deps-cve:
bash scripts/update-deps-cve.sh
prepare-for-e2e-test: clean build
rm -f ./e2e-tests/alr
cp alr e2e-tests
e2e-test: prepare-for-e2e-test
go test -tags=e2e ./...

View File

@ -3,11 +3,13 @@
</p>
<b></b>
[![Go Report Card](https://goreportcard.com/badge/gitea.plemya-x.ru/Plemya-x/ALR)](https://goreportcard.com/report/gitea.plemya-x.ru/Plemya-x/ALR) ![Test coverage](./assets/coverage-badge.svg) ![ru translate](./assets/i18n-ru-badge.svg)
# ALR (Any Linux Repository)
ALR - это независимая от дистрибутива система сборки для Linux, аналогичная [AUR](https://wiki.archlinux.org/title/Arch_User_Repository). В настоящее время она находится в стадии бета-тестирования. Исправлено большинство основных ошибок и добавлено большинство важных функций. alr готов к общему использованию, но все еще может время от времени ломаться или заменяться.
ALR - это независимая от дистрибутива система сборки для Linux (форк [LURE](https://github.com/lure-sh/lure), аналогичная [AUR](https://wiki.archlinux.org/title/Arch_User_Repository). В настоящее время она находится в стадии бета-тестирования. Исправлено большинство основных ошибок и добавлено большинство важных функций. ALR готов к общему использованию, но все еще может время от времени ломаться или изменяться.
ALR написан на чистом Go и после сборки не имеет зависимостей. Единственное, для повышения привилегий ALR требуется команда, такая как `sudo`, `doas` и т.д., а также поддерживаемый менеджер пакетов. В настоящее время ALR поддерживает `apt`, `pacman`, `apk`, `dnf`, `yum`, and `zypper`. Если в вашей системе существует поддерживаемый менеджер пакетов, он будет обнаружен и использован автоматически.
ALR написан на чистом Go и после сборки не имеет зависимостей. Для повышения привилегий ALR требуется команда, такая как `sudo`, `doas` и т.д., а также поддерживаемый менеджер пакетов. В настоящее время ALR поддерживает `apt`, `apt-get` `pacman`, `apk`, `dnf`, `yum`, and `zypper`. Если в вашей системе используется поддерживаемый менеджер пакетов, то он будет обнаружен и использован автоматически.
---
@ -18,17 +20,17 @@ ALR написан на чистом Go и после сборки не имее
Установочный скрипт автоматически загрузит и установит соответствующий пакет ALR в вашей системе. Чтобы использовать его, просто выполните следующую команду:
```bash
curl -fsSL plemya-x.ru/alr/install.sh | bash
curl -fsSL https://gitea.plemya-x.ru/Plemya-x/ALR/raw/branch/master/scripts/install.sh | bash
```
**ВАЖНО**: При этом скрипт будет загружен и запущен с <https://gitea.plemya-x.ru/xpamych/ALR/install>. Пожалуйста, просматривайте любые скрипты, которые вы скачиваете из Интернета (включая этот), прежде чем запускать их.
**ВАЖНО**: При этом скрипт будет загружен и запущен [скрипт](https://gitea.plemya-x.ru/Plemya-x/ALR/src/branch/master/scripts/install.sh). Пожалуйста, просматривайте любые скрипты, которые вы скачиваете из Интернета (включая этот), прежде чем запускать их.
### Сборка из исходного кода
Чтобы собрать ALR из исходного кода, вам понадобится версия Go 1.18 или новее. Как только Go будет установлен, клонируйте это репозиторий и запустите:
```shell
make build
make build -B
sudo make install
```
@ -42,20 +44,31 @@ ALR был создан потому, что упаковка программн
## Документация
Документация по всем этим вопросам находится в [Wiki](https://gitea.plemya-x.ru/xpamych/ALR/wiki/Home).
Документация находится в [Wiki](https://disc.plemya-x.ru/c/alr/wiki-alr).
---
## Репозитории
Репозитории alr - это git-хранилища, которые содержат каталог для каждого пакета с файлом `alr.sh` внутри. Файл `alr.sh` содержит все инструкции по сборке пакета и информацию о нем. Скрипты `alr.sh` аналогичны скриптам Aur PKGBUILD. Репозиторий [по-умолчанию](https://gitea.plemya-x.ru/xpamych/xpamych-alr-repo.git).
Репозитории alr - это git-хранилища, которые содержат каталог для каждого пакета с файлом `alr.sh` внутри. Файл `alr.sh` содержит все инструкции по сборке пакета и информацию о нем. Скрипты `alr.sh` аналогичны скриптам Aur PKGBUILD.
Например, репозиторий с ALR [Plemya-x/alr-default](https://gitea.plemya-x.ru/Plemya-x/alr-default.git)
```
alr repo add alr-default https://gitea.plemya-x.ru/Plemya-x/alr-default.git
```
Репозиторий пакетов [Plemya-x/alr-repo](https://gitea.plemya-x.ru/Plemya-x/alr-repo.git) можно подключить так:
```
alr repo add alr-repo https://gitea.plemya-x.ru/Plemya-x/alr-repo.git
```
Репозиторий Linux-Gaming [Plemya-x/alr-LG](https://gitea.plemya-x.ru/Plemya-x/alr-LG.git) можно подключить так:
```
alr repo add alr-LG https://gitea.plemya-x.ru/Plemya-x/alr-LG.git
```
---
## Соцсети
VK - https://vk.com/plemya_kh
Discord - https://discord.com/channels/817759634105827358/1261631565084233749
Telegram - https://t.me/plemyakh
## Спасибы
@ -68,3 +81,6 @@ Telegram - https://t.me/plemyakh
- <https://github.com/goreleaser/nfpm>
- <https://github.com/charmbracelet/bubbletea>
- <https://gitlab.com/cznic/sqlite>
Благодарим за активное участие в развитии проекта:
- Maks1mS <maxim@slipenko.com>

17
assets/coverage-badge.svg Normal file
View File

@ -0,0 +1,17 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="109" height="20">
<linearGradient id="smooth" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
<stop offset="1" stop-opacity=".1"/></linearGradient>
<mask id="round">
<rect width="109" height="20" rx="3" fill="#fff"/>
</mask>
<g mask="url(#round)"><rect width="65" height="20" fill="#555"/>
<rect x="65" width="44" height="20" fill="#e05d44"/>
<rect width="109" height="20" fill="url(#smooth)"/>
</g>
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
<text x="33.5" y="15" fill="#010101" fill-opacity=".3">coverage</text>
<text x="33.5" y="14">coverage</text>
<text x="86" y="15" fill="#010101" fill-opacity=".3">19.7%</text>
<text x="86" y="14">19.7%</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 926 B

18
assets/i18n-ru-badge.svg Normal file
View File

@ -0,0 +1,18 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="129" height="20">
<linearGradient id="smooth" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
<stop offset="1" stop-opacity=".1"/></linearGradient>
<mask id="round">
<rect width="129" height="20" rx="3" fill="#fff"/>
</mask>
<g mask="url(#round)">
<rect width="75" height="20" fill="#555"/>
<rect x="75" width="64" height="20" fill="#4c1"/>
<rect width="129" height="20" fill="url(#smooth)"/>
</g>
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
<text x="37" y="15" fill="#010101" fill-opacity=".3">ru translate</text>
<text x="37" y="14">ru translate</text>
<text x="100" y="15" fill="#010101" fill-opacity=".3">100.00%</text>
<text x="100" y="14">100.00%</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 942 B

258
build.go
View File

@ -1,100 +1,238 @@
/*
* ALR - Any Linux Repository
* Copyright (C) 2024 Евгений Храмов
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
// It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors.
//
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package main
import (
"log/slog"
"os"
"path/filepath"
"strings"
"github.com/leonelquinteros/gotext"
"github.com/urfave/cli/v2"
"plemya-x.ru/alr/internal/config"
"plemya-x.ru/alr/internal/osutils"
"plemya-x.ru/alr/internal/types"
"plemya-x.ru/alr/pkg/build"
"plemya-x.ru/alr/pkg/loggerctx"
"plemya-x.ru/alr/pkg/manager"
"plemya-x.ru/alr/pkg/repos"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/build"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/osutils"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
)
var buildCmd = &cli.Command{
func BuildCmd() *cli.Command {
return &cli.Command{
Name: "build",
Usage: "Build a local package",
Usage: gotext.Get("Build a local package"),
Flags: []cli.Flag{
&cli.StringFlag{
Name: "script",
Aliases: []string{"s"},
Value: "alr.sh",
Usage: "Path to the build script",
Usage: gotext.Get("Path to the build script"),
},
&cli.StringFlag{
Name: "subpackage",
Aliases: []string{"sb"},
Usage: gotext.Get("Specify subpackage in script (for multi package script only)"),
},
&cli.StringFlag{
Name: "package",
Aliases: []string{"p"},
Usage: "Name of the package to build and its repo (example: default/go-bin)",
Usage: gotext.Get("Name of the package to build and its repo (example: default/go-bin)"),
},
&cli.BoolFlag{
Name: "clean",
Aliases: []string{"c"},
Usage: "Build package from scratch even if there's an already built package available",
Usage: gotext.Get("Build package from scratch even if there's an already built package available"),
},
},
Action: func(c *cli.Context) error {
ctx := c.Context
log := loggerctx.From(ctx)
script := c.String("script")
if c.String("package") != "" {
script = filepath.Join(config.GetPaths(ctx).RepoDir, c.String("package"), "alr.sh")
}
err := repos.Pull(ctx, config.Config(ctx).Repos)
if err != nil {
log.Fatal("Error pulling repositories").Err(err).Send()
}
mgr := manager.Detect()
if mgr == nil {
log.Fatal("Unable to detect a supported package manager on the system").Send()
}
pkgPaths, _, err := build.BuildPackage(ctx, types.BuildOpts{
Script: script,
Manager: mgr,
Clean: c.Bool("clean"),
Interactive: c.Bool("interactive"),
})
if err != nil {
log.Fatal("Error building package").Err(err).Send()
if err := utils.EnuseIsPrivilegedGroupMember(); err != nil {
return err
}
wd, err := os.Getwd()
if err != nil {
log.Fatal("Error getting working directory").Err(err).Send()
return cliutils.FormatCliExit(gotext.Get("Error getting working directory"), err)
}
for _, pkgPath := range pkgPaths {
name := filepath.Base(pkgPath)
err = osutils.Move(pkgPath, filepath.Join(wd, name))
wd, wdCleanup, err := Mount(wd)
if err != nil {
log.Fatal("Error moving the package").Err(err).Send()
return err
}
defer wdCleanup()
ctx := c.Context
deps, err := appbuilder.
New(ctx).
WithConfig().
WithDB().
WithReposNoPull().
WithDistroInfo().
WithManager().
Build()
if err != nil {
return cli.Exit(err, 1)
}
defer deps.Defer()
var script string
var packages []string
var res []*build.BuiltDep
var scriptArgs *build.BuildPackageFromScriptArgs
var dbArgs *build.BuildPackageFromDbArgs
buildArgs := &build.BuildArgs{
Opts: &types.BuildOpts{
Clean: c.Bool("clean"),
Interactive: c.Bool("interactive"),
},
PkgFormat_: build.GetPkgFormat(deps.Manager),
Info: deps.Info,
}
switch {
case c.IsSet("script"):
script, err = filepath.Abs(c.String("script"))
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Cannot get absolute script path"), err)
}
subpackage := c.String("subpackage")
if subpackage != "" {
packages = append(packages, subpackage)
}
scriptArgs = &build.BuildPackageFromScriptArgs{
Script: script,
Packages: packages,
BuildArgs: *buildArgs,
}
case c.IsSet("package"):
// TODO: handle multiple packages
packageInput := c.String("package")
arr := strings.Split(packageInput, "/")
var packageSearch string
if len(arr) == 2 {
packageSearch = arr[1]
} else {
packageSearch = arr[0]
}
pkgs, _, err := deps.Repos.FindPkgs(ctx, []string{packageSearch})
if err != nil {
return cliutils.FormatCliExit("failed to find pkgs", err)
}
pkg := cliutils.FlattenPkgs(ctx, pkgs, "build", c.Bool("interactive"))
if len(pkg) < 1 {
return cliutils.FormatCliExit(gotext.Get("Package not found"), nil)
}
if pkg[0].BasePkgName != "" {
packages = append(packages, pkg[0].Name)
}
dbArgs = &build.BuildPackageFromDbArgs{
Package: &pkg[0],
Packages: packages,
BuildArgs: *buildArgs,
}
default:
return cliutils.FormatCliExit(gotext.Get("Nothing to build"), nil)
}
if scriptArgs != nil {
scriptFile := filepath.Base(scriptArgs.Script)
newScriptDir, scriptDirCleanup, err := Mount(filepath.Dir(scriptArgs.Script))
if err != nil {
return err
}
defer scriptDirCleanup()
scriptArgs.Script = filepath.Join(newScriptDir, scriptFile)
}
if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil {
return err
}
installer, installerClose, err := build.GetSafeInstaller()
if err != nil {
return err
}
defer installerClose()
if err := utils.ExitIfCantSetNoNewPrivs(); err != nil {
return err
}
scripter, scripterClose, err := build.GetSafeScriptExecutor()
if err != nil {
return err
}
defer scripterClose()
builder, err := build.NewMainBuilder(
deps.Cfg,
deps.Manager,
deps.Repos,
scripter,
installer,
)
if err != nil {
return err
}
if scriptArgs != nil {
res, err = builder.BuildPackageFromScript(
ctx,
scriptArgs,
)
} else if dbArgs != nil {
res, err = builder.BuildPackageFromDb(
ctx,
dbArgs,
)
}
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error building package"), err)
}
for _, pkg := range res {
name := filepath.Base(pkg.Path)
err = osutils.Move(pkg.Path, filepath.Join(wd, name))
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error moving the package"), err)
}
}
slog.Info(gotext.Get("Done"))
return nil
},
}
}

72
e2e-tests/addrepo_test.go Normal file
View File

@ -0,0 +1,72 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//go:build e2e
package e2etests_test
import (
"bytes"
"testing"
"github.com/efficientgo/e2e"
"github.com/stretchr/testify/assert"
)
func TestE2EAlrAddRepo(t *testing.T) {
dockerMultipleRun(
t,
"add-repo-remove-repo",
COMMON_SYSTEMS,
func(t *testing.T, r e2e.Runnable) {
err := r.Exec(e2e.NewCommand(
"sudo",
"alr",
"addrepo",
"--name",
"alr-repo",
"--url",
"https://gitea.plemya-x.ru/Plemya-x/alr-repo.git",
))
assert.NoError(t, err)
err = r.Exec(e2e.NewCommand(
"bash",
"-c",
"cat /etc/alr/alr.toml",
))
assert.NoError(t, err)
err = r.Exec(e2e.NewCommand(
"sudo",
"alr",
"removerepo",
"--name",
"alr-repo",
))
assert.NoError(t, err)
var buf bytes.Buffer
err = r.Exec(e2e.NewCommand(
"bash",
"-c",
"cat /etc/alr/alr.toml",
), e2e.WithExecOptionStdout(&buf))
assert.NoError(t, err)
assert.Contains(t, buf.String(), "rootCmd")
},
)
}

View File

@ -0,0 +1,36 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//go:build e2e
package e2etests_test
import (
"testing"
"github.com/efficientgo/e2e"
)
func TestE2EBashCompletion(t *testing.T) {
dockerMultipleRun(
t,
"bash-completion",
COMMON_SYSTEMS,
func(t *testing.T, r e2e.Runnable) {
execShouldNoError(t, r, "alr", "install", "--generate-bash-completion")
},
)
}

202
e2e-tests/common_test.go Normal file
View File

@ -0,0 +1,202 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//go:build e2e
package e2etests_test
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"log"
"os"
"testing"
"time"
"github.com/efficientgo/e2e"
"github.com/stretchr/testify/assert"
expect "github.com/tailscale/goexpect"
)
// DebugWriter оборачивает io.Writer и логирует все записываемые данные.
type DebugWriter struct {
prefix string
writer io.Writer
}
func (d *DebugWriter) Write(p []byte) (n int, err error) {
log.Printf("%s: Writing data: %q", d.prefix, p) // Логируем данные
return d.writer.Write(p)
}
// DebugReader оборачивает io.Reader и логирует все читаемые данные.
type DebugReader struct {
prefix string
reader io.Reader
}
func (d *DebugReader) Read(p []byte) (n int, err error) {
n, err = d.reader.Read(p)
if n > 0 {
log.Printf("%s: Read data: %q", d.prefix, p[:n]) // Логируем данные
}
return n, err
}
func e2eSpawn(runnable e2e.Runnable, command e2e.Command, timeout time.Duration, opts ...expect.Option) (expect.Expecter, <-chan error, error, *io.PipeWriter) {
resCh := make(chan error)
// Создаем pipe для stdin и stdout
stdinReader, stdinWriter := io.Pipe()
stdoutReader, stdoutWriter := io.Pipe()
debugStdinReader := &DebugReader{prefix: "STDIN", reader: stdinReader}
debugStdoutWriter := &DebugWriter{prefix: "STDOUT", writer: stdoutWriter}
go func() {
err := runnable.Exec(
command,
e2e.WithExecOptionStdout(debugStdoutWriter),
e2e.WithExecOptionStdin(debugStdinReader),
e2e.WithExecOptionStderr(debugStdoutWriter),
)
resCh <- err
}()
exp, chnErr, err := expect.SpawnGeneric(&expect.GenOptions{
In: stdinWriter,
Out: stdoutReader,
Wait: func() error {
return <-resCh
},
Close: func() error {
stdinWriter.Close()
stdoutReader.Close()
return nil
},
Check: func() bool { return true },
}, timeout, expect.Verbose(true), expect.VerboseWriter(os.Stdout))
return exp, chnErr, err, stdinWriter
}
var ALL_SYSTEMS []string = []string{
"ubuntu-24.04",
"alt-sisyphus",
"fedora-41",
// "archlinux",
// "alpine",
// "opensuse-leap",
// "redos-8",
}
var AUTOREQ_AUTOPROV_SYSTEMS []string = []string{
// "alt-sisyphus",
"fedora-41",
}
var RPM_SYSTEMS []string = []string{
"fedora-41",
}
var COMMON_SYSTEMS []string = []string{
"ubuntu-24.04",
}
func dockerMultipleRun(t *testing.T, name string, ids []string, f func(t *testing.T, runnable e2e.Runnable)) {
t.Run(name, func(t *testing.T) {
for _, id := range ids {
t.Run(id, func(t *testing.T) {
t.Parallel()
dockerName := fmt.Sprintf("alr-test-%s-%s", name, id)
hash := sha256.New()
hash.Write([]byte(dockerName))
hashSum := hash.Sum(nil)
hashString := hex.EncodeToString(hashSum)
truncatedHash := hashString[:8]
e, err := e2e.New(e2e.WithVerbose(), e2e.WithName(fmt.Sprintf("alr-%s", truncatedHash)))
assert.NoError(t, err)
t.Cleanup(e.Close)
imageId := fmt.Sprintf("ghcr.io/maks1ms/alr-e2e-test-image-%s", id)
runnable := e.Runnable(dockerName).Init(
e2e.StartOptions{
Image: imageId,
Volumes: []string{
"./alr:/tmp/alr",
},
Privileged: true,
},
)
assert.NoError(t, e2e.StartAndWaitReady(runnable))
err = runnable.Exec(e2e.NewCommand("/bin/alr-test-setup", "alr-install"))
if err != nil {
panic(err)
}
err = runnable.Exec(e2e.NewCommand("/bin/alr-test-setup", "passwordless-sudo-setup"))
if err != nil {
panic(err)
}
f(t, runnable)
})
}
})
}
func execShouldNoError(t *testing.T, r e2e.Runnable, cmd string, args ...string) {
assert.NoError(t, r.Exec(e2e.NewCommand(cmd, args...)))
}
func execShouldError(t *testing.T, r e2e.Runnable, cmd string, args ...string) {
assert.Error(t, r.Exec(e2e.NewCommand(cmd, args...)))
}
func runTestCommands(t *testing.T, r e2e.Runnable, timeout time.Duration, expects []expect.Batcher) {
exp, _, err, _ := e2eSpawn(
r,
e2e.NewCommand("/bin/bash"), 25*time.Second,
expect.Verbose(true),
)
assert.NoError(t, err)
_, err = exp.ExpectBatch(
expects,
timeout,
)
assert.NoError(t, err)
}
const REPO_NAME_FOR_E2E_TESTS = "alr-repo"
const REPO_URL_FOR_E2E_TESTS = "https://gitea.plemya-x.ru/Plemya-x/repo-for-tests.git"
func defaultPrepare(t *testing.T, r e2e.Runnable) {
execShouldNoError(t, r,
"sudo",
"alr",
"repo",
"add",
REPO_NAME_FOR_E2E_TESTS,
REPO_URL_FOR_E2E_TESTS,
)
execShouldNoError(t, r,
"sudo",
"alr",
"ref",
)
}

View File

@ -0,0 +1,41 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//go:build e2e
package e2etests_test
import (
"fmt"
"testing"
"github.com/efficientgo/e2e"
)
func TestE2EFirejailedPackage(t *testing.T) {
dockerMultipleRun(
t,
"firejailed-package",
COMMON_SYSTEMS,
func(t *testing.T, r e2e.Runnable) {
defaultPrepare(t, r)
execShouldNoError(t, r, "alr", "build", "-p", fmt.Sprintf("%s/firejailed-pkg", REPO_NAME_FOR_E2E_TESTS))
execShouldError(t, r, "alr", "build", "-p", fmt.Sprintf("%s/firejailed-pkg-incorrect", REPO_NAME_FOR_E2E_TESTS))
execShouldNoError(t, r, "sh", "-c", "dpkg -c *.deb | grep -q '/usr/lib/alr/firejailed/_usr_bin_danger.sh'")
execShouldNoError(t, r, "sh", "-c", "dpkg -c *.deb | grep -q '/usr/lib/alr/firejailed/_usr_bin_danger.sh.profile'")
},
)
}

43
e2e-tests/fix_test.go Normal file
View File

@ -0,0 +1,43 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//go:build e2e
package e2etests_test
import (
"testing"
"time"
"github.com/efficientgo/e2e"
expect "github.com/tailscale/goexpect"
)
func TestE2EAlrFix(t *testing.T) {
dockerMultipleRun(
t,
"run-fix",
COMMON_SYSTEMS,
func(t *testing.T, r e2e.Runnable) {
runTestCommands(t, r, time.Second*30, []expect.Batcher{
&expect.BSnd{S: "alr fix\n"},
&expect.BExp{R: `--> Done`},
&expect.BSnd{S: "echo $?\n"},
&expect.BExp{R: `^0\n$`},
})
},
)
}

View File

@ -0,0 +1,38 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//go:build e2e
package e2etests_test
import (
"testing"
"github.com/efficientgo/e2e"
)
func TestE2EGroupAndSummaryField(t *testing.T) {
dockerMultipleRun(
t,
"group-and-summary-field",
RPM_SYSTEMS,
func(t *testing.T, r e2e.Runnable) {
defaultPrepare(t, r)
execShouldNoError(t, r, "sh", "-c", "alr search --name test-group-and-summary --format \"{{.Group.Resolved}}\" | grep ^System/Base$")
execShouldNoError(t, r, "sh", "-c", "alr search --name test-group-and-summary --format \"{{.Summary.Resolved}}\" | grep \"^Custom summary$\"")
},
)
}

View File

@ -0,0 +1,40 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//go:build e2e
package e2etests_test
import (
"testing"
"github.com/efficientgo/e2e"
)
func TestE2EIssue32Interactive(t *testing.T) {
dockerMultipleRun(
t,
"issue-32-interactive",
COMMON_SYSTEMS,
func(t *testing.T, r e2e.Runnable) {
execShouldNoError(t, r, "alr", "--interactive=false", "remove", "ca-certificates")
execShouldNoError(t, r, "sudo", "alr", "--interactive=false", "remove", "openssl")
execShouldNoError(t, r, "alr", "fix")
execShouldNoError(t, r, "sudo", "apt-get", "update")
execShouldNoError(t, r, "sudo", "alr", "--interactive=false", "install", "ca-certificates")
},
)
}

View File

@ -0,0 +1,40 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//go:build e2e
package e2etests_test
import (
"testing"
"github.com/efficientgo/e2e"
)
func TestE2EIssue41AutoreqSkiplist(t *testing.T) {
dockerMultipleRun(
t,
"issue-41-autoreq-skiplist",
AUTOREQ_AUTOPROV_SYSTEMS,
func(t *testing.T, r e2e.Runnable) {
defaultPrepare(t, r)
execShouldNoError(t, r, "alr", "build", "-p", "alr-repo/test-autoreq-autoprov")
execShouldNoError(t, r, "sh", "-c", "rpm -qp --requires *.rpm | grep \"^/bin/sh$\"")
execShouldError(t, r, "sh", "-c", "rpm -qp --requires *.rpm | grep \"^/bin/bash$\"")
execShouldError(t, r, "sh", "-c", "rpm -qp --requires *.rpm | grep \"^/bin/zsh$\"")
},
)
}

View File

@ -0,0 +1,39 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//go:build e2e
package e2etests_test
import (
"testing"
"github.com/efficientgo/e2e"
)
func TestE2EIssue50InstallMultiple(t *testing.T) {
dockerMultipleRun(
t,
"issue-50-install-multiple",
COMMON_SYSTEMS,
func(t *testing.T, r e2e.Runnable) {
defaultPrepare(t, r)
execShouldNoError(t, r, "sudo", "alr", "in", "foo-pkg", "bar-pkg")
execShouldNoError(t, r, "cat", "/opt/foo")
execShouldNoError(t, r, "cat", "/opt/bar")
},
)
}

View File

@ -0,0 +1,37 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//go:build e2e
package e2etests_test
import (
"testing"
"github.com/efficientgo/e2e"
)
func TestE2EIssue53LcAllCInfo(t *testing.T) {
dockerMultipleRun(
t,
"issue-53-lc-all-c-info",
COMMON_SYSTEMS,
func(t *testing.T, r e2e.Runnable) {
defaultPrepare(t, r)
execShouldNoError(t, r, "bash", "-c", "LANG=C alr info foo-pkg")
},
)
}

View File

@ -0,0 +1,40 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//go:build e2e
package e2etests_test
import (
"testing"
"github.com/efficientgo/e2e"
)
func TestE2EIssue59RmCompletion(t *testing.T) {
dockerMultipleRun(
t,
"issue-59-rm-completion",
COMMON_SYSTEMS,
func(t *testing.T, r e2e.Runnable) {
defaultPrepare(t, r)
execShouldNoError(t, r, "sudo", "alr", "in", "foo-pkg", "bar-pkg")
execShouldNoError(t, r, "sh", "-c", "alr rm --generate-bash-completion | grep ^foo-pkg$")
execShouldNoError(t, r, "sh", "-c", "alr rm --generate-bash-completion | grep ^bar-pkg$")
execShouldError(t, r, "sh", "-c", "alr rm --generate-bash-completion | grep ^test-autoreq-autoprov$")
},
)
}

View File

@ -0,0 +1,37 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//go:build e2e
package e2etests_test
import (
"testing"
"github.com/efficientgo/e2e"
)
func TestE2EIssue72InstallWithDeps(t *testing.T) {
dockerMultipleRun(
t,
"issue-72-install-with-deps",
COMMON_SYSTEMS,
func(t *testing.T, r e2e.Runnable) {
defaultPrepare(t, r)
execShouldNoError(t, r, "sudo", "alr", "in", "test-app-with-lib")
},
)
}

View File

@ -0,0 +1,43 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//go:build e2e
package e2etests_test
import (
"testing"
"github.com/efficientgo/e2e"
)
func TestE2EIssue74Upgradable(t *testing.T) {
dockerMultipleRun(
t,
"issue-74-upgradable",
COMMON_SYSTEMS,
func(t *testing.T, r e2e.Runnable) {
defaultPrepare(t, r)
execShouldNoError(t, r, "sudo", "alr", "repo", "set-ref", "alr-repo", "bd26236cd7")
execShouldNoError(t, r, "alr", "ref")
execShouldNoError(t, r, "sudo", "alr", "in", "bar-pkg")
execShouldNoError(t, r, "sh", "-c", "test $(alr list -U | wc -l) -eq 0 || exit 1")
execShouldNoError(t, r, "sudo", "alr", "repo", "set-ref", "alr-repo", "d9a3541561")
execShouldNoError(t, r, "sudo", "alr", "ref")
execShouldNoError(t, r, "sh", "-c", "test $(alr list -U | wc -l) -eq 1 || exit 1")
},
)
}

View File

@ -0,0 +1,38 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//go:build e2e
package e2etests_test
import (
"testing"
"github.com/efficientgo/e2e"
)
func TestE2EIssue75InstallWithDeps(t *testing.T) {
dockerMultipleRun(
t,
"issue-75-ref-specify",
COMMON_SYSTEMS,
func(t *testing.T, r e2e.Runnable) {
defaultPrepare(t, r)
execShouldNoError(t, r, "sudo", "alr", "repo", "set-ref", "alr-repo", "bd26236cd7")
execShouldNoError(t, r, "sh", "-c", "test $(alr list | wc -l) -eq 2 || exit 1")
},
)
}

View File

@ -0,0 +1,53 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//go:build e2e
package e2etests_test
import (
"testing"
"github.com/efficientgo/e2e"
)
func Test75SinglePackageRepo(t *testing.T) {
dockerMultipleRun(
t,
"issue-76-single-package-repo",
COMMON_SYSTEMS,
func(t *testing.T, r e2e.Runnable) {
execShouldNoError(t, r,
"sudo",
"alr",
"repo",
"add",
REPO_NAME_FOR_E2E_TESTS,
"https://gitea.plemya-x.ru/Maks1mS/test-single-package-alr-repo.git",
)
execShouldNoError(t, r, "sudo", "alr", "repo", "set-ref", REPO_NAME_FOR_E2E_TESTS, "1075c918be")
execShouldNoError(t, r, "alr", "ref")
execShouldNoError(t, r, "sudo", "alr", "in", "test-single-repo")
execShouldNoError(t, r, "sh", "-c", "alr list -U")
execShouldNoError(t, r, "sh", "-c", "test $(alr list -U | wc -l) -eq 0 || exit 1")
execShouldNoError(t, r, "sudo", "alr", "repo", "set-ref", REPO_NAME_FOR_E2E_TESTS, "5e361c50d7")
execShouldNoError(t, r, "sudo", "alr", "ref")
execShouldNoError(t, r, "sh", "-c", "test $(alr list -U | wc -l) -eq 1 || exit 1")
execShouldNoError(t, r, "sudo", "alr", "up")
execShouldNoError(t, r, "sh", "-c", "test $(alr list -U | wc -l) -eq 0 || exit 1")
},
)
}

View File

@ -0,0 +1,53 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//go:build e2e
package e2etests_test
import (
"testing"
"github.com/efficientgo/e2e"
)
func TestE2EIssue78Mirrors(t *testing.T) {
dockerMultipleRun(
t,
"issue-78-mirrors",
COMMON_SYSTEMS,
func(t *testing.T, r e2e.Runnable) {
defaultPrepare(t, r)
execShouldNoError(t, r, "sudo", "alr", "repo", "mirror", "add", REPO_NAME_FOR_E2E_TESTS, "https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git")
execShouldNoError(t, r, "sudo", "alr", "repo", "set-url", REPO_NAME_FOR_E2E_TESTS, "https://example.com")
execShouldNoError(t, r, "sudo", "alr", "ref")
execShouldNoError(t, r, "sudo", "alr", "repo", "mirror", "clear", REPO_NAME_FOR_E2E_TESTS)
execShouldError(t, r, "sudo", "alr", "ref")
execShouldNoError(t, r, "sudo", "alr", "repo", "mirror", "add", REPO_NAME_FOR_E2E_TESTS, "https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git")
execShouldNoError(t, r, "sudo", "alr", "repo", "mirror", "rm", "--partial", REPO_NAME_FOR_E2E_TESTS, "gitea.plemya-x.ru/Maks1mS")
execShouldError(t, r, "sudo", "alr", "ref")
execShouldNoError(t, r, "sudo", "alr", "repo", "mirror", "add", REPO_NAME_FOR_E2E_TESTS, "https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git")
execShouldNoError(t, r, "sudo", "alr", "repo", "mirror", "rm", REPO_NAME_FOR_E2E_TESTS, "https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git")
execShouldError(t, r, "sudo", "alr", "ref")
execShouldNoError(t, r, "sudo", "alr", "repo", "mirror", "add", REPO_NAME_FOR_E2E_TESTS, "https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git")
execShouldNoError(t, r, "sudo", "alr", "repo", "mirror", "rm", REPO_NAME_FOR_E2E_TESTS, "https://gitea.plemya-x.ru/Maks1mS/repo-for-tests.git")
execShouldError(t, r, "sudo", "alr", "ref")
},
)
}

View File

@ -0,0 +1,38 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//go:build e2e
package e2etests_test
import (
"testing"
"github.com/efficientgo/e2e"
)
func TestE2EIssue81MultiplePackages(t *testing.T) {
dockerMultipleRun(
t,
"issue-81-multiple-packages",
COMMON_SYSTEMS,
func(t *testing.T, r e2e.Runnable) {
defaultPrepare(t, r)
execShouldNoError(t, r, "sudo", "alr", "in", "first-package-with-dashes")
execShouldNoError(t, r, "cat", "/opt/first-package")
},
)
}

View File

@ -0,0 +1,40 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//go:build e2e
package e2etests_test
import (
"testing"
"github.com/efficientgo/e2e"
)
func TestE2EIssue91MultiplePackages(t *testing.T) {
dockerMultipleRun(
t,
"issue-91-set-repo-ref",
COMMON_SYSTEMS,
func(t *testing.T, r e2e.Runnable) {
defaultPrepare(t, r)
execShouldError(t, r, "sudo", "alr", "repo", "set-ref")
execShouldError(t, r, "sudo", "alr", "repo", "set-ref", "alr-repo")
execShouldNoError(t, r, "sudo", "alr", "repo", "set-ref", "alr-repo", "bd26236cd7")
execShouldNoError(t, r, "sh", "-c", "test $(alr list | wc -l) -eq 2 || exit 1")
},
)
}

View File

@ -0,0 +1,49 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//go:build e2e
package e2etests_test
import (
"bytes"
"strings"
"testing"
"github.com/efficientgo/e2e"
"github.com/stretchr/testify/assert"
)
func TestE2EIssue94TwiceBuild(t *testing.T) {
dockerMultipleRun(
t,
"issue-94-twice-build",
COMMON_SYSTEMS,
func(t *testing.T, r e2e.Runnable) {
defaultPrepare(t, r)
var stderr bytes.Buffer
err := r.Exec(
e2e.NewCommand("sudo", "alr", "in", "test-94-app"),
e2e.WithExecOptionStderr(&stderr),
)
assert.NoError(t, err, "command failed")
output := stderr.String()
assert.Equal(t, 1, strings.Count(output, "Building package name=test-94-dep"))
},
)
}

44
e2e-tests/version_test.go Normal file
View File

@ -0,0 +1,44 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
//go:build e2e
package e2etests_test
import (
"testing"
"time"
"github.com/efficientgo/e2e"
expect "github.com/tailscale/goexpect"
)
func TestE2EAlrVersion(t *testing.T) {
dockerMultipleRun(
t,
"check-version",
COMMON_SYSTEMS,
func(t *testing.T, r e2e.Runnable) {
runTestCommands(t, r, time.Second*10, []expect.Batcher{
&expect.BSnd{S: "alr version\n"},
&expect.BExp{R: `^v\d+\.\d+\.\d+(?:-\d+-g[a-f0-9]+)?\n$`},
&expect.BSnd{S: "echo $?\n"},
&expect.BExp{R: `^0\n$`},
})
},
)
}

143
fix.go
View File

@ -1,64 +1,129 @@
/*
* ALR - Any Linux Repository
* Copyright (C) 2024 Евгений Храмов
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
// It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors.
//
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package main
import (
"io/fs"
"log/slog"
"os"
"path/filepath"
"github.com/leonelquinteros/gotext"
"github.com/urfave/cli/v2"
"plemya-x.ru/alr/internal/config"
"plemya-x.ru/alr/internal/db"
"plemya-x.ru/alr/pkg/loggerctx"
"plemya-x.ru/alr/pkg/repos"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
)
var fixCmd = &cli.Command{
func FixCmd() *cli.Command {
return &cli.Command{
Name: "fix",
Usage: "Attempt to fix problems with ALR",
Usage: gotext.Get("Attempt to fix problems with ALR"),
Action: func(c *cli.Context) error {
ctx := c.Context
log := loggerctx.From(ctx)
db.Close()
paths := config.GetPaths(ctx)
log.Info("Removing cache directory").Send()
err := os.RemoveAll(paths.CacheDir)
if err != nil {
log.Fatal("Unable to remove cache directory").Err(err).Send()
if err := utils.ExitIfCantDropCapsToAlrUserNoPrivs(); err != nil {
return err
}
log.Info("Rebuilding cache").Send()
ctx := c.Context
deps, err := appbuilder.
New(ctx).
WithConfig().
Build()
if err != nil {
return cli.Exit(err, 1)
}
defer deps.Defer()
cfg := deps.Cfg
paths := cfg.GetPaths()
slog.Info(gotext.Get("Clearing cache directory"))
dir, err := os.Open(paths.CacheDir)
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Unable to open cache directory"), err)
}
defer dir.Close()
entries, err := dir.Readdirnames(-1)
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Unable to read cache directory contents"), err)
}
for _, entry := range entries {
fullPath := filepath.Join(paths.CacheDir, entry)
if err := makeWritableRecursive(fullPath); err != nil {
slog.Debug("Failed to make path writable", "path", fullPath, "error", err)
}
err = os.RemoveAll(fullPath)
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Unable to remove cache item (%s)", entry), err)
}
}
slog.Info(gotext.Get("Rebuilding cache"))
err = os.MkdirAll(paths.CacheDir, 0o755)
if err != nil {
log.Fatal("Unable to create new cache directory").Err(err).Send()
return cliutils.FormatCliExit(gotext.Get("Unable to create new cache directory"), err)
}
err = repos.Pull(ctx, config.Config(ctx).Repos)
deps, err = appbuilder.
New(ctx).
WithConfig().
WithDB().
WithReposForcePull().
Build()
if err != nil {
log.Fatal("Error pulling repos").Err(err).Send()
return cli.Exit(err, 1)
}
defer deps.Defer()
log.Info("Done").Send()
slog.Info(gotext.Get("Done"))
return nil
},
}
}
func makeWritableRecursive(path string) error {
return filepath.WalkDir(path, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
info, err := d.Info()
if err != nil {
return err
}
newMode := info.Mode() | 0o200
if d.IsDir() {
newMode |= 0o100
}
return os.Chmod(path, newMode)
})
}

39
gen.go
View File

@ -1,24 +1,42 @@
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
// It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors.
//
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package main
import (
"os"
"github.com/leonelquinteros/gotext"
"github.com/urfave/cli/v2"
"plemya-x.ru/alr/pkg/gen"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/gen"
)
var genCmd = &cli.Command{
func GenCmd() *cli.Command {
return &cli.Command{
Name: "generate",
Usage: "Generate a ALR script from a template",
Usage: gotext.Get("Generate a ALR script from a template"),
Aliases: []string{"gen"},
Subcommands: []*cli.Command{
genPipCmd,
},
}
var genPipCmd = &cli.Command{
{
Name: "pip",
Usage: "Generate a ALR script for a pip module",
Usage: gotext.Get("Generate a ALR script for a pip module"),
Flags: []cli.Flag{
&cli.StringFlag{
Name: "name",
@ -42,4 +60,7 @@ var genPipCmd = &cli.Command{
Description: c.String("description"),
})
},
},
},
}
}

View File

@ -0,0 +1,251 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package main
import (
"bytes"
"fmt"
"go/ast"
"go/format"
"go/parser"
"go/token"
"log"
"os"
"reflect"
"strings"
"text/template"
)
func resolvedStructGenerator(buf *bytes.Buffer, fields []*ast.Field) {
contentTemplate := template.Must(template.New("").Parse(`
type {{ .EntityNameLower }}Resolved struct {
{{ .StructFields }}
}
`))
var structFieldsBuilder strings.Builder
for _, field := range fields {
for _, name := range field.Names {
// Поле с типом
fieldTypeStr := exprToString(field.Type)
// Структура поля
var buf bytes.Buffer
buf.WriteString("\t")
buf.WriteString(name.Name)
buf.WriteString(" ")
buf.WriteString(fieldTypeStr)
// Обработка json-тега
jsonTag := ""
if field.Tag != nil {
raw := strings.Trim(field.Tag.Value, "`")
tag := reflect.StructTag(raw)
if val := tag.Get("json"); val != "" {
jsonTag = val
}
}
if jsonTag == "" {
jsonTag = strings.ToLower(name.Name)
}
buf.WriteString(fmt.Sprintf(" `json:\"%s\"`", jsonTag))
buf.WriteString("\n")
structFieldsBuilder.Write(buf.Bytes())
}
}
params := struct {
EntityNameLower string
StructFields string
}{
EntityNameLower: "package",
StructFields: structFieldsBuilder.String(),
}
err := contentTemplate.Execute(buf, params)
if err != nil {
log.Fatalf("execute template: %v", err)
}
}
func toResolvedFuncGenerator(buf *bytes.Buffer, fields []*ast.Field) {
contentTemplate := template.Must(template.New("").Parse(`
func {{ .EntityName }}ToResolved(src *{{ .EntityName }}) {{ .EntityNameLower }}Resolved {
return {{ .EntityNameLower }}Resolved{
{{ .Assignments }}
}
}
`))
var assignmentsBuilder strings.Builder
for _, field := range fields {
for _, name := range field.Names {
var assignBuf bytes.Buffer
assignBuf.WriteString("\t\t")
assignBuf.WriteString(name.Name)
assignBuf.WriteString(": ")
if isOverridableField(field.Type) {
assignBuf.WriteString(fmt.Sprintf("src.%s.Resolved()", name.Name))
} else {
assignBuf.WriteString(fmt.Sprintf("src.%s", name.Name))
}
assignBuf.WriteString(",\n")
assignmentsBuilder.Write(assignBuf.Bytes())
}
}
params := struct {
EntityName string
EntityNameLower string
Assignments string
}{
EntityName: "Package",
EntityNameLower: "package",
Assignments: assignmentsBuilder.String(),
}
err := contentTemplate.Execute(buf, params)
if err != nil {
log.Fatalf("execute template: %v", err)
}
}
func resolveFuncGenerator(buf *bytes.Buffer, fields []*ast.Field) {
contentTemplate := template.Must(template.New("").Parse(`
func Resolve{{ .EntityName }}(pkg *{{ .EntityName }}, overrides []string) {
{{.Code}}}
`))
var codeBuilder strings.Builder
for _, field := range fields {
for _, name := range field.Names {
if isOverridableField(field.Type) {
var buf bytes.Buffer
buf.WriteString(fmt.Sprintf("\t\tpkg.%s.Resolve(overrides)\n", name.Name))
codeBuilder.Write(buf.Bytes())
}
}
}
params := struct {
EntityName string
Code string
}{
EntityName: "Package",
Code: codeBuilder.String(),
}
err := contentTemplate.Execute(buf, params)
if err != nil {
log.Fatalf("execute template: %v", err)
}
}
func main() {
path := os.Getenv("GOFILE")
if path == "" {
log.Fatal("GOFILE must be set")
}
fset := token.NewFileSet()
node, err := parser.ParseFile(fset, path, nil, parser.AllErrors)
if err != nil {
log.Fatalf("parsing file: %v", err)
}
entityName := "Package" // имя структуры, которую анализируем
found := false
fields := make([]*ast.Field, 0)
// Ищем структуру с нужным именем
for _, decl := range node.Decls {
genDecl, ok := decl.(*ast.GenDecl)
if !ok || genDecl.Tok != token.TYPE {
continue
}
for _, spec := range genDecl.Specs {
typeSpec := spec.(*ast.TypeSpec)
if typeSpec.Name.Name != entityName {
continue
}
structType, ok := typeSpec.Type.(*ast.StructType)
if !ok {
continue
}
fields = structType.Fields.List
found = true
}
}
if !found {
log.Fatalf("struct %s not found", entityName)
}
var buf bytes.Buffer
buf.WriteString("// DO NOT EDIT MANUALLY. This file is generated.\n")
buf.WriteString("package alrsh")
resolvedStructGenerator(&buf, fields)
toResolvedFuncGenerator(&buf, fields)
resolveFuncGenerator(&buf, fields)
// Форматируем вывод
formatted, err := format.Source(buf.Bytes())
if err != nil {
log.Fatalf("formatting: %v", err)
}
outPath := strings.TrimSuffix(path, ".go") + "_gen.go"
outFile, err := os.Create(outPath)
if err != nil {
log.Fatalf("create file: %v", err)
}
_, err = outFile.Write(formatted)
if err != nil {
log.Fatalf("writing output: %v", err)
}
outFile.Close()
}
func exprToString(expr ast.Expr) string {
if t, ok := expr.(*ast.IndexExpr); ok {
if ident, ok := t.X.(*ast.Ident); ok && ident.Name == "OverridableField" {
return exprToString(t.Index) // T
}
}
var buf bytes.Buffer
if err := format.Node(&buf, token.NewFileSet(), expr); err != nil {
return "<invalid>"
}
return buf.String()
}
func isOverridableField(expr ast.Expr) bool {
indexExpr, ok := expr.(*ast.IndexExpr)
if !ok {
return false
}
ident, ok := indexExpr.X.(*ast.Ident)
return ok && ident.Name == "OverridableField"
}

127
go.mod
View File

@ -1,50 +1,57 @@
module plemya-x.ru/alr
module gitea.plemya-x.ru/Plemya-x/ALR
go 1.21
go 1.23.0
toolchain go1.21.3
toolchain go1.24.2
require (
gitea.plemya-x.ru/Plemya-x/fakeroot v0.0.2-0.20250408104831-427aaa7713c3
github.com/AlecAivazis/survey/v2 v2.3.7
github.com/PuerkitoBio/purell v1.2.0
github.com/alecthomas/chroma/v2 v2.9.1
github.com/charmbracelet/bubbles v0.16.1
github.com/charmbracelet/bubbletea v0.24.2
github.com/charmbracelet/lipgloss v0.8.0
github.com/go-git/go-billy/v5 v5.5.0
github.com/go-git/go-git/v5 v5.9.0
github.com/goreleaser/nfpm/v2 v2.33.0
github.com/jmoiron/sqlx v1.3.5
github.com/mattn/go-isatty v0.0.19
github.com/bmatcuk/doublestar/v4 v4.8.1
github.com/caarlos0/env v3.5.0+incompatible
github.com/charmbracelet/bubbles v0.20.0
github.com/charmbracelet/bubbletea v1.2.4
github.com/charmbracelet/lipgloss v1.0.0
github.com/charmbracelet/log v0.4.0
github.com/efficientgo/e2e v0.14.1-0.20240418111536-97db25a0c6c0
github.com/go-git/go-billy/v5 v5.6.0
github.com/go-git/go-git/v5 v5.13.0
github.com/goccy/go-yaml v1.18.0
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
github.com/goreleaser/nfpm/v2 v2.41.0
github.com/hashicorp/go-hclog v0.14.1
github.com/hashicorp/go-plugin v1.6.3
github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08
github.com/leonelquinteros/gotext v1.7.0
github.com/mattn/go-isatty v0.0.20
github.com/mholt/archiver/v4 v4.0.0-alpha.8
github.com/mitchellh/mapstructure v1.5.0
github.com/muesli/reflow v0.3.0
github.com/pelletier/go-toml/v2 v2.1.0
github.com/schollz/progressbar/v3 v3.13.1
github.com/stretchr/testify v1.10.0
github.com/tailscale/goexpect v0.0.0-20210902213824-6e8c725cea41
github.com/urfave/cli/v2 v2.25.7
github.com/vmihailenco/msgpack/v5 v5.3.5
go.elara.ws/logger v0.0.0-20230421022458-e80700db2090
go.elara.ws/translate v0.0.0-20230421025926-32ccfcd110e6
go.elara.ws/vercmp v0.0.0-20230622214216-0b2b067575c4
golang.org/x/crypto v0.13.0
golang.org/x/exp v0.0.0-20230905200255-921286631fa9
golang.org/x/sys v0.12.0
golang.org/x/text v0.13.0
gopkg.in/yaml.v3 v3.0.1
golang.org/x/crypto v0.36.0
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
golang.org/x/sys v0.31.0
golang.org/x/text v0.23.0
modernc.org/sqlite v1.25.0
mvdan.cc/sh/v3 v3.7.0
plemya-x.ru/fakeroot v0.0.0-20240601131003-c638a3543283
mvdan.cc/sh/v3 v3.10.0
xorm.io/xorm v1.3.9
)
require (
dario.cat/mergo v1.0.0 // indirect
dario.cat/mergo v1.0.1 // indirect
github.com/AlekSi/pointer v1.2.0 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.2.1 // indirect
github.com/Masterminds/semver/v3 v3.3.0 // indirect
github.com/Masterminds/sprig/v3 v3.2.3 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect
github.com/acomagu/bufpipe v1.0.4 // indirect
github.com/ProtonMail/go-crypto v1.1.3 // indirect
github.com/andybalholm/brotli v1.0.4 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb // indirect
@ -52,68 +59,87 @@ require (
github.com/bodgit/sevenzip v1.3.0 // indirect
github.com/bodgit/windows v1.0.0 // indirect
github.com/cavaliergopher/cpio v1.0.1 // indirect
github.com/cloudflare/circl v1.3.3 // indirect
github.com/charmbracelet/harmonica v0.2.0 // indirect
github.com/charmbracelet/x/ansi v0.4.5 // indirect
github.com/charmbracelet/x/term v0.2.1 // indirect
github.com/cloudflare/circl v1.6.1 // indirect
github.com/connesc/cipherio v0.2.1 // indirect
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
github.com/creack/pty v1.1.24 // indirect
github.com/cyphar/filepath-securejoin v0.2.5 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dlclark/regexp2 v1.10.0 // indirect
github.com/dsnet/compress v0.0.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/efficientgo/core v1.0.0-rc.0 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/fatih/color v1.7.0 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/goccy/go-json v0.8.1 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/rpmpack v0.5.0 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/gookit/color v1.5.1 // indirect
github.com/goreleaser/chglog v0.5.0 // indirect
github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f // indirect
github.com/google/rpmpack v0.6.1-0.20240329070804-c2247cbb881a // indirect
github.com/google/uuid v1.4.0 // indirect
github.com/goreleaser/chglog v0.6.1 // indirect
github.com/goreleaser/fileglob v1.3.0 // indirect
github.com/hashicorp/errwrap v1.0.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/yamux v0.1.1 // indirect
github.com/huandu/xstrings v1.3.3 // indirect
github.com/imdario/mergo v0.3.16 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/klauspost/compress v1.17.0 // indirect
github.com/klauspost/compress v1.17.11 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-colorable v0.1.2 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/termenv v0.15.2 // indirect
github.com/nwaples/rardecode/v2 v2.0.0-beta.2 // indirect
github.com/oklog/run v1.0.0 // indirect
github.com/pierrec/lz4/v4 v4.1.15 // indirect
github.com/pjbgf/sha1cd v0.3.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rivo/uniseg v0.4.4 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sergi/go-diff v1.2.0 // indirect
github.com/shopspring/decimal v1.2.0 // indirect
github.com/skeema/knownhosts v1.2.0 // indirect
github.com/spf13/cast v1.5.1 // indirect
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
github.com/shopspring/decimal v1.3.1 // indirect
github.com/skeema/knownhosts v1.3.0 // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/syndtr/goleveldb v1.0.0 // indirect
github.com/therootcompany/xz v1.0.1 // indirect
github.com/ulikunitz/xz v0.5.11 // indirect
github.com/ulikunitz/xz v0.5.12 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
gitlab.com/digitalxero/go-conventional-commit v1.0.7 // indirect
go4.org v0.0.0-20200411211856-f5505b9728dd // indirect
golang.org/x/mod v0.12.0 // indirect
golang.org/x/net v0.15.0 // indirect
golang.org/x/sync v0.3.0 // indirect
golang.org/x/term v0.12.0 // indirect
golang.org/x/tools v0.13.0 // indirect
golang.org/x/mod v0.19.0 // indirect
golang.org/x/net v0.38.0 // indirect
golang.org/x/sync v0.12.0 // indirect
golang.org/x/term v0.30.0 // indirect
golang.org/x/tools v0.23.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect
google.golang.org/grpc v1.58.3 // indirect
google.golang.org/protobuf v1.36.1 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
lukechampine.com/uint128 v1.2.0 // indirect
modernc.org/cc/v3 v3.40.0 // indirect
modernc.org/ccgo/v3 v3.16.13 // indirect
@ -123,4 +149,5 @@ require (
modernc.org/opt v0.1.3 // indirect
modernc.org/strutil v1.1.3 // indirect
modernc.org/token v1.0.1 // indirect
xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978 // indirect
)

366
go.sum
View File

@ -14,22 +14,26 @@ cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2k
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
gitea.plemya-x.ru/Plemya-x/fakeroot v0.0.2-0.20250408104831-427aaa7713c3 h1:56BjRJJ2Sv50DfSvNUydUMJwwFuiBMWC1uYtH2GYjk8=
gitea.plemya-x.ru/Plemya-x/fakeroot v0.0.2-0.20250408104831-427aaa7713c3/go.mod h1:iKQM6uttMJgE5CFrPw6SQqAV7TKtlJNICRAie/dTciw=
github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ=
github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo=
github.com/AlekSi/pointer v1.2.0 h1:glcy/gc4h8HnG2Z3ZECSzZ1IX1x2JxRVuDzaJwQE0+w=
github.com/AlekSi/pointer v1.2.0/go.mod h1:gZGfd3dpW4vEc/UlyfKKi1roIqcCgwOIvb0tSNSBle0=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ=
github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ=
github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0=
github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0=
github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA=
github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM=
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
@ -37,16 +41,14 @@ github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migc
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s=
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg=
github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
github.com/ProtonMail/go-crypto v1.1.3 h1:nRBOetoydLeUb4nHajyO2bKqMLfWQ/ZPwkXqXxPxCFk=
github.com/ProtonMail/go-crypto v1.1.3/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f h1:tCbYj7/299ekTTXpdwKYF8eBlsYsDVoggDAuAjoK66k=
github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw=
github.com/ProtonMail/gopenpgp/v2 v2.7.1 h1:Awsg7MPc2gD3I7IFac2qE3Gdls0lZW8SzrFZ3k1oz0s=
github.com/ProtonMail/gopenpgp/v2 v2.7.1/go.mod h1:/BU5gfAVwqyd8EfC3Eu7zmuhwYQpKs+cGD8M//iiaxs=
github.com/PuerkitoBio/purell v1.2.0 h1:/Jdm5QfyM8zdlqT6WVZU4cfP23sot6CEHA4CS49Ezig=
github.com/PuerkitoBio/purell v1.2.0/go.mod h1:OhLRTaaIzhvIyofkJfB24gokC7tM42Px5UhoT32THBk=
github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ=
github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4=
github.com/alecthomas/assert/v2 v2.2.1 h1:XivOgYcduV98QCahG8T5XTezV5bylXe+lBxLG2K2ink=
github.com/alecthomas/assert/v2 v2.2.1/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ=
github.com/alecthomas/chroma/v2 v2.9.1 h1:0O3lTQh9FxazJ4BYE/MOi/vDGuHn7B+6Bu902N2UZvU=
@ -61,45 +63,58 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4=
github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI=
github.com/bmatcuk/doublestar/v4 v4.8.1 h1:54Bopc5c2cAvhLRAzqOGCYHYyhcDHsFF4wWIR5wKP38=
github.com/bmatcuk/doublestar/v4 v4.8.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/bodgit/plumbing v1.2.0 h1:gg4haxoKphLjml+tgnecR4yLBV5zo4HAZGCtAh3xCzM=
github.com/bodgit/plumbing v1.2.0/go.mod h1:b9TeRi7Hvc6Y05rjm8VML3+47n4XTZPtQ/5ghqic2n8=
github.com/bodgit/sevenzip v1.3.0 h1:1ljgELgtHqvgIp8W8kgeEGHIWP4ch3xGI8uOBZgLVKY=
github.com/bodgit/sevenzip v1.3.0/go.mod h1:omwNcgZTEooWM8gA/IJ2Nk/+ZQ94+GsytRzOJJ8FBlM=
github.com/bodgit/windows v1.0.0 h1:rLQ/XjsleZvx4fR1tB/UxQrK+SJ2OFHzfPjLWWOhDIA=
github.com/bodgit/windows v1.0.0/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM=
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/caarlos0/go-rpmutils v0.2.1-0.20211112020245-2cd62ff89b11 h1:IRrDwVlWQr6kS1U8/EtyA1+EHcc4yl8pndcqXWrEamg=
github.com/caarlos0/go-rpmutils v0.2.1-0.20211112020245-2cd62ff89b11/go.mod h1:je2KZ+LxaCNvCoKg32jtOIULcFogJKcL1ZWUaIBjKj0=
github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA=
github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8=
github.com/caarlos0/env v3.5.0+incompatible h1:Yy0UN8o9Wtr/jGHZDpCBLpNrzcFLLM2yixi/rBrKyJs=
github.com/caarlos0/env v3.5.0+incompatible/go.mod h1:tdCsowwCzMLdkqRYDlHpZCp2UooDD3MspDBjZ2AD02Y=
github.com/caarlos0/testfs v0.4.4 h1:3PHvzHi5Lt+g332CiShwS8ogTgS3HjrmzZxCm6JCDr8=
github.com/caarlos0/testfs v0.4.4/go.mod h1:bRN55zgG4XCUVVHZCeU+/Tz1Q6AxEJOEJTliBy+1DMk=
github.com/cavaliergopher/cpio v1.0.1 h1:KQFSeKmZhv0cr+kawA3a0xTQCU4QxXF1vhU7P7av2KM=
github.com/cavaliergopher/cpio v1.0.1/go.mod h1:pBdaqQjnvXxdS/6CvNDwIANIFSP0xRKI16PX4xejRQc=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/charmbracelet/bubbles v0.16.1 h1:6uzpAAaT9ZqKssntbvZMlksWHruQLNxg49H5WdeuYSY=
github.com/charmbracelet/bubbles v0.16.1/go.mod h1:2QCp9LFlEsBQMvIYERr7Ww2H2bA7xen1idUDIzm/+Xc=
github.com/charmbracelet/bubbletea v0.24.2 h1:uaQIKx9Ai6Gdh5zpTbGiWpytMU+CfsPp06RaW2cx/SY=
github.com/charmbracelet/bubbletea v0.24.2/go.mod h1:XdrNrV4J8GiyshTtx3DNuYkR1FDaJmO3l2nejekbsgg=
github.com/charmbracelet/lipgloss v0.8.0 h1:IS00fk4XAHcf8uZKc3eHeMUTCxUH6NkaTrdyCQk84RU=
github.com/charmbracelet/lipgloss v0.8.0/go.mod h1:p4eYUZZJ/0oXTuCQKFF8mqyKCz0ja6y+7DniDDw5KKU=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE=
github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU=
github.com/charmbracelet/bubbletea v1.2.4 h1:KN8aCViA0eps9SCOThb2/XPIlea3ANJLUkv3KnQRNCE=
github.com/charmbracelet/bubbletea v1.2.4/go.mod h1:Qr6fVQw+wX7JkWWkVyXYk/ZUQ92a6XNekLXa3rR18MM=
github.com/charmbracelet/harmonica v0.2.0 h1:8NxJWRWg/bzKqqEaaeFNipOu77YR5t8aSwG4pgaUBiQ=
github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao=
github.com/charmbracelet/lipgloss v1.0.0 h1:O7VkGDvqEdGi93X+DeqsQ7PKHDgtQfF8j8/O2qFMQNg=
github.com/charmbracelet/lipgloss v1.0.0/go.mod h1:U5fy9Z+C38obMs+T+tJqst9VGzlOYGj4ri9reL3qUlo=
github.com/charmbracelet/log v0.4.0 h1:G9bQAcx8rWA2T3pWvx7YtPTPwgqpk7D68BX21IRW8ZM=
github.com/charmbracelet/log v0.4.0/go.mod h1:63bXt/djrizTec0l11H20t8FDSvA4CRZJ1KH22MdptM=
github.com/charmbracelet/x/ansi v0.4.5 h1:LqK4vwBNaXw2AyGIICa5/29Sbdq58GbGdFngSexTdRM=
github.com/charmbracelet/x/ansi v0.4.5/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs=
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/connesc/cipherio v0.2.1 h1:FGtpTPMbKNNWByNrr9aEBtaJtXjqOzkIXNYJp6OEycw=
github.com/connesc/cipherio v0.2.1/go.mod h1:ukY0MWJDFnJEbXMQtOcn2VmTpRfzcTz4OoVrWGGJZcA=
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY=
github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
github.com/cyphar/filepath-securejoin v0.2.5 h1:6iR5tXJ/e6tJZzzdMc1km3Sa7RRIVBKAK32O2s7AYfo=
github.com/cyphar/filepath-securejoin v0.2.5/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -110,30 +125,47 @@ github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5Jflh
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU=
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
github.com/efficientgo/core v1.0.0-rc.0 h1:jJoA0N+C4/knWYVZ6GrdHOtDyrg8Y/TR4vFpTaqTsqs=
github.com/efficientgo/core v1.0.0-rc.0/go.mod h1:kQa0V74HNYMfuJH6jiPiwNdpWXl4xd/K4tzlrcvYDQI=
github.com/efficientgo/e2e v0.14.1-0.20240418111536-97db25a0c6c0 h1:C/FNIs+MtAJgQYLJ9FX/ACFYyDRuLYoXTmueErrOJyA=
github.com/efficientgo/e2e v0.14.1-0.20240418111536-97db25a0c6c0/go.mod h1:plsKU0YHE9uX+7utvr7SiDtVBSHJyEfHRO4UnUgDmts=
github.com/elazarl/goproxy v1.2.1 h1:njjgvO6cRG9rIqN2ebkqy6cQz2Njkx7Fsfv/zIZqgug=
github.com/elazarl/goproxy v1.2.1/go.mod h1:YfEbZtqP4AetfO6d40vWchF3znWX7C7Vd6ZMfdL8z64=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA=
github.com/frankban/quicktest v1.14.5/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY=
github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU=
github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f h1:Pz0DHeFij3XFhoBRGUDPzSJ+w2UcK5/0JvF8DRI58r8=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f/go.mod h1:8LHG1a3SRW71ettAD/jW13h8c6AqjVSeL11RAdgaqpo=
github.com/go-git/go-git/v5 v5.9.0 h1:cD9SFA7sHVRdJ7AYck1ZaAa/yeuBvGPxwXDL8cxrObY=
github.com/go-git/go-git/v5 v5.9.0/go.mod h1:RKIqga24sWdMGZF+1Ekv9kylsDz6LzdTSI2s/OsZWE0=
github.com/go-git/go-billy/v5 v5.6.0 h1:w2hPNtoehvJIxR00Vb4xX94qHQi/ApZfX+nBE2Cjio8=
github.com/go-git/go-billy/v5 v5.6.0/go.mod h1:sFDq7xD3fn3E0GOwUSZqHo9lrkmx8xJhA0ZrfvjBRGM=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
github.com/go-git/go-git/v5 v5.13.0 h1:vLn5wlGIh/X78El6r3Jr+30W16Blk0CTcxTYcYPWi5E=
github.com/go-git/go-git/v5 v5.13.0/go.mod h1:Wjo7/JyVKtQgUNdXYXIepzWfJQkUEIGvkvVkiXRR/zw=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-quicktest/qt v1.101.0 h1:O1K29Txy5P2OK0dGo59b7b0LR6wKfIhttaAhHUyn7eI=
github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow=
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/goccy/go-json v0.8.1 h1:4/Wjm0JIJaTDm8K1KcGrLHJoa8EsJ13YWeX+6Kfq6uI=
github.com/goccy/go-json v0.8.1/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@ -150,6 +182,10 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
@ -158,8 +194,12 @@ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5a
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f h1:5CjVwnuUcp5adK4gmY6i72gpVFVnZDP2h5TmPScB6u4=
github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f/go.mod h1:nOFQdrUlIlx6M6ODdSpBj1NVA+VgLC6kmw60mkw34H4=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
@ -167,33 +207,41 @@ github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hf
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/rpmpack v0.5.0 h1:L16KZ3QvkFGpYhmp23iQip+mx1X39foEsqszjMNBm8A=
github.com/google/rpmpack v0.5.0/go.mod h1:uqVAUVQLq8UY2hCDfmJ/+rtO3aw7qyhc90rCVEabEfI=
github.com/google/rpmpack v0.6.1-0.20240329070804-c2247cbb881a h1:JJBdjSfqSy3mnDT0940ASQFghwcZ4y4cb6ttjAoXqwE=
github.com/google/rpmpack v0.6.1-0.20240329070804-c2247cbb881a/go.mod h1:uqVAUVQLq8UY2hCDfmJ/+rtO3aw7qyhc90rCVEabEfI=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gookit/color v1.5.1 h1:Vjg2VEcdHpwq+oY63s/ksHrgJYCTo0bwWvmmYWdE9fQ=
github.com/gookit/color v1.5.1/go.mod h1:wZFzea4X8qN6vHOSP2apMb4/+w/orMznEzYsIHPaqKM=
github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g=
github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k=
github.com/goreleaser/chglog v0.5.0 h1:Sk6BMIpx8+vpAf8KyPit34OgWui8c7nKTMHhYx88jJ4=
github.com/goreleaser/chglog v0.5.0/go.mod h1:Ri46M3lrMuv76FHszs3vtABR8J8k1w9JHYAzxeeOl28=
github.com/goreleaser/chglog v0.6.1 h1:NZKiX8l0FTQPRzBgKST7knvNZmZ04f7PEGkN2wInfhE=
github.com/goreleaser/chglog v0.6.1/go.mod h1:Bnnfo07jMZkaAb0uRNASMZyOsX6ROW6X1qbXqN3guUo=
github.com/goreleaser/fileglob v1.3.0 h1:/X6J7U8lbDpQtBvGcwwPS6OpzkNVlVEsFUVRx9+k+7I=
github.com/goreleaser/fileglob v1.3.0/go.mod h1:Jx6BoXv3mbYkEzwm9THo7xbr5egkAraxkGorbJb4RxU=
github.com/goreleaser/nfpm/v2 v2.33.0 h1:yBv6jgkPwih4va/S42rceSjJ2Znt3Og/Ntc76oP0tfI=
github.com/goreleaser/nfpm/v2 v2.33.0/go.mod h1:8wwWWvJWmn84xo/Sqiv0aMvEGTHlHZTXTEuVSgQpkIM=
github.com/goreleaser/nfpm/v2 v2.41.0 h1:JyMzS/EwqaWbFs+7Z9oZ4Hkk4or00gUTqwm9Dgr8QYg=
github.com/goreleaser/nfpm/v2 v2.41.0/go.mod h1:VPc5kF5OgfA+BosV/A2aB+Vg34honjWvp0Vt8ogsSi0=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-hclog v0.14.1 h1:nQcJDQwIAGnmoUWp8ubocEX40cCml/17YkF6csQLReU=
github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-plugin v1.6.3 h1:xgHB+ZUSYeuJi96WtxEjzi23uh7YQpznjGh0U0UUrwg=
github.com/hashicorp/go-plugin v1.6.3/go.mod h1:MRobyh+Wc/nYy1V4KAXUiYfzxoYhs7V1mlH1Z7iY2h0=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4=
github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
@ -202,21 +250,26 @@ github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4=
github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08 h1:wMeVzrPO3mfHIWLZtDcSaGAe2I4PW9B/P5nMkRSwCAc=
github.com/jeandeaual/go-locale v0.0.0-20241217141322-fcc2cadd6f08/go.mod h1:ZDXo8KHryOWSIqnsb/CiDq7hQUYryCgdVnxbj8tDG7o=
github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c=
github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo=
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/k0kubun/go-ansi v0.0.0-20180517002512-3bf9e2903213/go.mod h1:vNUNkEQ1e29fT/6vq2aBdFsgNPmy8qMdSay1npru+Sw=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM=
github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
@ -227,34 +280,34 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/leonelquinteros/gotext v1.7.0 h1:jcJmF4AXqyamP7vuw2MMIKs+O3jAEmvrc5JQiI8Ht/8=
github.com/leonelquinteros/gotext v1.7.0/go.mod h1:qJdoQuERPpccw7L70uoU+K/BvTfRBHYsisCQyFLXyvw=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/mholt/archiver/v4 v4.0.0-alpha.8 h1:tRGQuDVPh66WCOelqe6LIGh0gwmfwxUrSSDunscGsRM=
github.com/mholt/archiver/v4 v4.0.0-alpha.8/go.mod h1:5f7FUYGXdJWUjESffJaYR4R60VhnHxb2X3T1teMyv5A=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
@ -263,18 +316,31 @@ github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34=
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nwaples/rardecode/v2 v2.0.0-beta.2 h1:e3mzJFJs4k83GXBEiTaQ5HgSc/kOK8q0rDaRO0MPaOk=
github.com/nwaples/rardecode/v2 v2.0.0-beta.2/go.mod h1:yntwv/HfMc/Hbvtq9I19D1n58te3h6KsqCf3GxyfBGY=
github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0=
@ -285,36 +351,45 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk=
github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.36.0 h1:78hJTing+BLYLjhXE+Z2BubeEymH5Lr0/Mt8FKkxxYo=
github.com/prometheus/common v0.36.0/go.mod h1:phzohg0JFMnBEFGxTDbfu3QyL5GI8gTQJFhYO5B3mfA=
github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU=
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
github.com/schollz/progressbar/v3 v3.13.1 h1:o8rySDYiQ59Mwzy2FELeHY5ZARXZTVJC7iHD6PEFUiE=
github.com/schollz/progressbar/v3 v3.13.1/go.mod h1:xvrbki8kfT1fzWzBT/UZd9L6GA+jdL7HAgq2RFnO6fQ=
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
github.com/sassoftware/go-rpmutils v0.4.0 h1:ojND82NYBxgwrV+mX1CWsd5QJvvEZTKddtCdFLPWhpg=
github.com/sassoftware/go-rpmutils v0.4.0/go.mod h1:3goNWi7PGAT3/dlql2lv3+MSN5jNYPjT5mVcQcIsYzI=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/skeema/knownhosts v1.2.0 h1:h9r9cf0+u7wSE+M183ZtMGgOJKiL96brpaz5ekfJCpM=
github.com/skeema/knownhosts v1.2.0/go.mod h1:g4fPeYpque7P0xefxtGzV81ihjC8sX2IqpAoNkjxbMo=
github.com/smartystreets/assertions v1.13.1 h1:Ef7KhSmjZcK6AVf9YbJdvPYG9avaF0ZxudX+ThRdWfU=
github.com/smartystreets/assertions v1.13.1/go.mod h1:cXr/IwVfSo/RbCSPhoAPv73p3hlSdrBH/b3SdnW/LMY=
github.com/smartystreets/goconvey v1.8.0 h1:Oi49ha/2MURE0WexF052Z0m+BNSGirfjg5RL+JXWq3w=
github.com/smartystreets/goconvey v1.8.0/go.mod h1:EdX8jtrTIj26jmjCOVNMVSIYAtgexqXKHOXW2Dx9JLg=
github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY=
github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M=
github.com/smarty/assertions v1.15.0 h1:cR//PqUBUiQRakZWqBiFFQ9wb8emQGDb0HeGdqGByCY=
github.com/smarty/assertions v1.15.0/go.mod h1:yABtdzeQs6l1brC900WlRNwj6ZR55d7B+E8C6HtKdec=
github.com/smartystreets/goconvey v1.8.1 h1:qGjIddxOk4grTu9JPOU31tVfq3cNdBlNa5sSznIX1xY=
github.com/smartystreets/goconvey v1.8.1/go.mod h1:+/u4qLyY6x1jReYOp7GOM2FSt8aP9CzCZL03bI28W60=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48=
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
@ -324,15 +399,19 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
github.com/tailscale/goexpect v0.0.0-20210902213824-6e8c725cea41 h1:/V2rCMMWcsjYaYO2MeovLw+ClP63OtXgCF2Y1eb8+Ns=
github.com/tailscale/goexpect v0.0.0-20210902213824-6e8c725cea41/go.mod h1:/roCdA6gg6lQyw/Oz6gIIGu3ggJKYhF+WC/AQReE5XQ=
github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw=
github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY=
github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=
github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8=
github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs=
github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU=
@ -343,17 +422,11 @@ github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos=
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 h1:QldyIu/L63oPpyvQmHgvgickp1Yw510KJOqX7H24mg8=
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
gitlab.com/digitalxero/go-conventional-commit v1.0.7 h1:8/dO6WWG+98PMhlZowt/YjuiKhqhGlOCwlIV8SqqGh8=
gitlab.com/digitalxero/go-conventional-commit v1.0.7/go.mod h1:05Xc2BFsSyC5tKhK0y+P3bs0AwUtNuTp+mTpbCU/DZ0=
go.elara.ws/logger v0.0.0-20230421022458-e80700db2090 h1:RVC8XvWo6Yw4HUshqx4TSzuBDScDghafU6QFRJ4xPZg=
go.elara.ws/logger v0.0.0-20230421022458-e80700db2090/go.mod h1:qng49owViqsW5Aey93lwBXONw20oGbJIoLVscB16mPM=
go.elara.ws/translate v0.0.0-20230421025926-32ccfcd110e6 h1:4xCBxLPBn3Y2DuIcj8zQ1tQOFLrpu6tEIGUWn/Q6zPM=
go.elara.ws/translate v0.0.0-20230421025926-32ccfcd110e6/go.mod h1:NmfCFqwq7X/aqa/ZVkIysj17JyMEY4Bb5E921kMswNo=
go.elara.ws/vercmp v0.0.0-20230622214216-0b2b067575c4 h1:Ep54XceQlKhcCHl9awG+wWP4kz4kIP3c3Lzw/Gc/zwY=
go.elara.ws/vercmp v0.0.0-20230622214216-0b2b067575c4/go.mod h1:/7PNW7nFnDR5W7UXZVc04gdVLR/wBNgkm33KgIz0OBk=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
@ -369,10 +442,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@ -381,8 +452,8 @@ golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@ -401,11 +472,11 @@ golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@ -422,15 +493,15 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8=
golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -438,10 +509,10 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -451,6 +522,7 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -458,27 +530,22 @@ golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -487,10 +554,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@ -518,9 +583,8 @@ golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapK
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=
golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -537,6 +601,8 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
@ -550,6 +616,8 @@ google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvx
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 h1:bVf09lpb+OJbByTj913DRJioFFAjf/ZGxEz7MajTp2U=
google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@ -557,17 +625,29 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ=
google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=
google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
@ -604,10 +684,12 @@ modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg=
modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
modernc.org/z v1.7.3 h1:zDJf6iHjrnB+WRD88stbXokugjyc0/pB91ri1gO6LZY=
modernc.org/z v1.7.3/go.mod h1:Ipv4tsdxZRbQyLq9Q1M6gdbkxYzdlrciF2Hi/lS7nWE=
mvdan.cc/sh/v3 v3.7.0 h1:lSTjdP/1xsddtaKfGg7Myu7DnlHItd3/M2tomOcNNBg=
mvdan.cc/sh/v3 v3.7.0/go.mod h1:K2gwkaesF/D7av7Kxl0HbF5kGOd2ArupNTX3X44+8l8=
plemya-x.ru/fakeroot v0.0.0-20240601131003-c638a3543283 h1:BXCLPeA8g2M6qYngicyxyB/2Bo4J54Q9Rb+8jMmE3ik=
plemya-x.ru/fakeroot v0.0.0-20240601131003-c638a3543283/go.mod h1:itzL9Jx52VXOhRaucFHuMpa3y7iwjnuLGdNvypoh/S4=
mvdan.cc/sh/v3 v3.10.0 h1:v9z7N1DLZ7owyLM/SXZQkBSXcwr2IGMm2LY2pmhVXj4=
mvdan.cc/sh/v3 v3.10.0/go.mod h1:z/mSSVyLFGZzqb3ZIKojjyqIx/xbmz/UHdCSv9HmqXY=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978 h1:bvLlAPW1ZMTWA32LuZMBEGHAUOcATZjzHcotf3SWweM=
xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
xorm.io/xorm v1.3.9 h1:TUovzS0ko+IQ1XnNLfs5dqK1cJl1H5uHpWbWqAQ04nU=
xorm.io/xorm v1.3.9/go.mod h1:LsCCffeeYp63ssk0pKumP6l96WZcHix7ChpurcLNuMw=

View File

@ -1,35 +1,69 @@
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
// It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors.
//
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package main
import (
"fmt"
"log/slog"
"os"
"strings"
"github.com/leonelquinteros/gotext"
"github.com/urfave/cli/v2"
"plemya-x.ru/alr/internal/cpu"
"plemya-x.ru/alr/internal/shutils/helpers"
"plemya-x.ru/alr/pkg/distro"
"plemya-x.ru/alr/pkg/loggerctx"
"mvdan.cc/sh/v3/expand"
"mvdan.cc/sh/v3/interp"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cpu"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/helpers"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
)
var helperCmd = &cli.Command{
func HelperCmd() *cli.Command {
helperListCmd := &cli.Command{
Name: "list",
Usage: gotext.Get("List all the available helper commands"),
Aliases: []string{"ls"},
Action: func(ctx *cli.Context) error {
for name := range helpers.Helpers {
fmt.Println(name)
}
return nil
},
}
return &cli.Command{
Name: "helper",
Usage: "Run a ALR helper command",
Usage: gotext.Get("Run a ALR helper command"),
ArgsUsage: `<helper_name|"list">`,
Subcommands: []*cli.Command{helperListCmd},
Flags: []cli.Flag{
&cli.StringFlag{
Name: "dest-dir",
Aliases: []string{"d"},
Usage: "The directory that the install commands will install to",
Usage: gotext.Get("The directory that the install commands will install to"),
Value: "dest",
},
},
Action: func(c *cli.Context) error {
ctx := c.Context
log := loggerctx.From(ctx)
if c.Args().Len() < 1 {
cli.ShowSubcommandHelpAndExit(c, 1)
@ -37,17 +71,18 @@ var helperCmd = &cli.Command{
helper, ok := helpers.Helpers[c.Args().First()]
if !ok {
log.Fatal("No such helper command").Str("name", c.Args().First()).Send()
slog.Error(gotext.Get("No such helper command"), "name", c.Args().First())
return cli.Exit(gotext.Get("No such helper command"), 1)
}
wd, err := os.Getwd()
if err != nil {
log.Fatal("Error getting working directory").Err(err).Send()
return cliutils.FormatCliExit(gotext.Get("Error getting working directory"), err)
}
info, err := distro.ParseOSRelease(ctx)
if err != nil {
log.Fatal("Error getting working directory").Err(err).Send()
return cliutils.FormatCliExit(gotext.Get("Error parsing os-release file"), err)
}
hc := interp.HandlerContext{
@ -72,15 +107,4 @@ var helperCmd = &cli.Command{
}
},
}
var helperListCmd = &cli.Command{
Name: "list",
Usage: "List all the available helper commands",
Aliases: []string{"ls"},
Action: func(ctx *cli.Context) error {
for name := range helpers.Helpers {
fmt.Println(name)
}
return nil
},
}

149
info.go
View File

@ -1,20 +1,21 @@
/*
* ALR - Any Linux Repository
* Copyright (C) 2024 Евгений Храмов
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
// It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors.
//
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package main
@ -22,47 +23,89 @@ import (
"fmt"
"os"
"github.com/goccy/go-yaml"
"github.com/jeandeaual/go-locale"
"github.com/leonelquinteros/gotext"
"github.com/urfave/cli/v2"
"plemya-x.ru/alr/internal/cliutils"
"plemya-x.ru/alr/internal/config"
"plemya-x.ru/alr/internal/overrides"
"plemya-x.ru/alr/pkg/distro"
"plemya-x.ru/alr/pkg/loggerctx"
"plemya-x.ru/alr/pkg/repos"
"gopkg.in/yaml.v3"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/overrides"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
)
var infoCmd = &cli.Command{
func InfoCmd() *cli.Command {
return &cli.Command{
Name: "info",
Usage: "Print information about a package",
Usage: gotext.Get("Print information about a package"),
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "all",
Aliases: []string{"a"},
Usage: "Show all information, not just for the current distro",
Usage: gotext.Get("Show all information, not just for the current distro"),
},
},
Action: func(c *cli.Context) error {
BashComplete: cliutils.BashCompleteWithError(func(c *cli.Context) error {
if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil {
return err
}
ctx := c.Context
log := loggerctx.From(ctx)
deps, err := appbuilder.
New(ctx).
WithConfig().
WithDB().
Build()
if err != nil {
return err
}
defer deps.Defer()
result, err := deps.DB.GetPkgs(c.Context, "true")
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error getting packages"), err)
}
for _, pkg := range result {
fmt.Println(pkg.Name)
}
return nil
}),
Action: func(c *cli.Context) error {
if err := utils.ExitIfCantDropCapsToAlrUserNoPrivs(); err != nil {
return err
}
args := c.Args()
if args.Len() < 1 {
log.Fatalf("Command info expected at least 1 argument, got %d", args.Len()).Send()
return cli.Exit(gotext.Get("Command info expected at least 1 argument, got %d", args.Len()), 1)
}
err := repos.Pull(ctx, config.Config(ctx).Repos)
if err != nil {
log.Fatal("Error pulling repositories").Err(err).Send()
}
ctx := c.Context
found, _, err := repos.FindPkgs(ctx, args.Slice())
deps, err := appbuilder.
New(ctx).
WithConfig().
WithDB().
WithDistroInfo().
WithRepos().
Build()
if err != nil {
log.Fatal("Error finding packages").Err(err).Send()
return cli.Exit(err, 1)
}
defer deps.Defer()
rs := deps.Repos
found, _, err := rs.FindPkgs(ctx, args.Slice())
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error finding packages"), err)
}
if len(found) == 0 {
os.Exit(1)
return cliutils.FormatCliExit(gotext.Get("Package not found"), err)
}
pkgs := cliutils.FlattenPkgs(ctx, found, "show", c.Bool("interactive"))
@ -70,37 +113,39 @@ var infoCmd = &cli.Command{
var names []string
all := c.Bool("all")
if !all {
systemLang, err := locale.GetLanguage()
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Can't detect system language"), err)
}
if systemLang == "" {
systemLang = "en"
}
info, err := distro.ParseOSRelease(ctx)
if err != nil {
log.Fatal("Error parsing os-release file").Err(err).Send()
return cliutils.FormatCliExit(gotext.Get("Error parsing os-release file"), err)
}
names, err = overrides.Resolve(
info,
overrides.DefaultOpts.
WithLanguages([]string{config.SystemLang()}),
WithLanguages([]string{systemLang}),
)
if err != nil {
log.Fatal("Error resolving overrides").Err(err).Send()
}
return cliutils.FormatCliExit(gotext.Get("Error resolving overrides"), err)
}
for _, pkg := range pkgs {
if !all {
err = yaml.NewEncoder(os.Stdout).Encode(overrides.ResolvePackage(&pkg, names))
alrsh.ResolvePackage(&pkg, names)
view := alrsh.NewPackageView(pkg)
view.Resolved = !all
err = yaml.NewEncoder(os.Stdout, yaml.UseJSONMarshaler(), yaml.OmitEmpty()).Encode(view)
if err != nil {
log.Fatal("Error encoding script variables").Err(err).Send()
return cliutils.FormatCliExit(gotext.Get("Error encoding script variables"), err)
}
} else {
err = yaml.NewEncoder(os.Stdout).Encode(pkg)
if err != nil {
log.Fatal("Error encoding script variables").Err(err).Send()
}
}
fmt.Println("---")
}
return nil
},
}
}

View File

@ -1,122 +1,220 @@
/*
* ALR - Any Linux Repository
* Copyright (C) 2024 Евгений Храмов
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
// It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors.
//
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package main
import (
"fmt"
"github.com/leonelquinteros/gotext"
"github.com/urfave/cli/v2"
"plemya-x.ru/alr/internal/cliutils"
"plemya-x.ru/alr/internal/config"
"plemya-x.ru/alr/internal/db"
"plemya-x.ru/alr/internal/types"
"plemya-x.ru/alr/pkg/build"
"plemya-x.ru/alr/pkg/loggerctx"
"plemya-x.ru/alr/pkg/manager"
"plemya-x.ru/alr/pkg/repos"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/build"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
)
var installCmd = &cli.Command{
func InstallCmd() *cli.Command {
return &cli.Command{
Name: "install",
Usage: "Install a new package",
Usage: gotext.Get("Install a new package"),
Aliases: []string{"in"},
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "clean",
Aliases: []string{"c"},
Usage: "Build package from scratch even if there's an already built package available",
Usage: gotext.Get("Build package from scratch even if there's an already built package available"),
},
},
Action: func(c *cli.Context) error {
ctx := c.Context
log := loggerctx.From(ctx)
Action: utils.RootNeededAction(func(c *cli.Context) error {
args := c.Args()
if args.Len() < 1 {
log.Fatalf("Command install expected at least 1 argument, got %d", args.Len()).Send()
return cliutils.FormatCliExit(gotext.Get("Command install expected at least 1 argument, got %d", args.Len()), nil)
}
mgr := manager.Detect()
if mgr == nil {
log.Fatal("Unable to detect a supported package manager on the system").Send()
if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil {
return err
}
err := repos.Pull(ctx, config.Config(ctx).Repos)
installer, installerClose, err := build.GetSafeInstaller()
if err != nil {
log.Fatal("Error pulling repositories").Err(err).Send()
return err
}
defer installerClose()
if err := utils.ExitIfCantSetNoNewPrivs(); err != nil {
return err
}
found, notFound, err := repos.FindPkgs(ctx, args.Slice())
scripter, scripterClose, err := build.GetSafeScriptExecutor()
if err != nil {
log.Fatal("Error finding packages").Err(err).Send()
return err
}
defer scripterClose()
ctx := c.Context
deps, err := appbuilder.
New(ctx).
WithConfig().
WithDB().
WithRepos().
WithDistroInfo().
WithManager().
Build()
if err != nil {
return err
}
defer deps.Defer()
builder, err := build.NewMainBuilder(
deps.Cfg,
deps.Manager,
deps.Repos,
scripter,
installer,
)
if err != nil {
return err
}
pkgs := cliutils.FlattenPkgs(ctx, found, "install", c.Bool("interactive"))
build.InstallPkgs(ctx, pkgs, notFound, types.BuildOpts{
Manager: mgr,
_, err = builder.InstallPkgs(
ctx,
&build.BuildArgs{
Opts: &types.BuildOpts{
Clean: c.Bool("clean"),
Interactive: c.Bool("interactive"),
})
return nil
},
BashComplete: func(c *cli.Context) {
log := loggerctx.From(c.Context)
result, err := db.GetPkgs(c.Context, "true")
Info: deps.Info,
PkgFormat_: build.GetPkgFormat(deps.Manager),
},
args.Slice(),
)
if err != nil {
log.Fatal("Error getting packages").Err(err).Send()
}
defer result.Close()
for result.Next() {
var pkg db.Package
err = result.StructScan(&pkg)
if err != nil {
log.Fatal("Error iterating over packages").Err(err).Send()
return cliutils.FormatCliExit(gotext.Get("Error when installing the package"), err)
}
return nil
}),
BashComplete: cliutils.BashCompleteWithError(func(c *cli.Context) error {
if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil {
return err
}
ctx := c.Context
deps, err := appbuilder.
New(ctx).
WithConfig().
WithDB().
Build()
if err != nil {
return err
}
defer deps.Defer()
result, err := deps.DB.GetPkgs(c.Context, "true")
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error getting packages"), err)
}
for _, pkg := range result {
fmt.Println(pkg.Name)
}
},
return nil
}),
}
}
var removeCmd = &cli.Command{
func RemoveCmd() *cli.Command {
return &cli.Command{
Name: "remove",
Usage: "Remove an installed package",
Usage: gotext.Get("Remove an installed package"),
Aliases: []string{"rm"},
Action: func(c *cli.Context) error {
log := loggerctx.From(c.Context)
BashComplete: cliutils.BashCompleteWithError(func(c *cli.Context) error {
ctx := c.Context
args := c.Args()
if args.Len() < 1 {
log.Fatalf("Command remove expected at least 1 argument, got %d", args.Len()).Send()
}
mgr := manager.Detect()
if mgr == nil {
log.Fatal("Unable to detect a supported package manager on the system").Send()
}
err := mgr.Remove(nil, c.Args().Slice()...)
deps, err := appbuilder.
New(ctx).
WithConfig().
WithDB().
WithManager().
Build()
if err != nil {
log.Fatal("Error removing packages").Err(err).Send()
return cli.Exit(err, 1)
}
defer deps.Defer()
installedAlrPackages := map[string]string{}
installed, err := deps.Manager.ListInstalled(&manager.Opts{})
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error listing installed packages"), err)
}
for pkgName, version := range installed {
matches := build.RegexpALRPackageName.FindStringSubmatch(pkgName)
if matches != nil {
packageName := matches[build.RegexpALRPackageName.SubexpIndex("package")]
repoName := matches[build.RegexpALRPackageName.SubexpIndex("repo")]
installedAlrPackages[fmt.Sprintf("%s/%s", repoName, packageName)] = version
}
}
result, err := deps.DB.GetPkgs(c.Context, "true")
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error getting packages"), err)
}
for _, pkg := range result {
_, ok := installedAlrPackages[fmt.Sprintf("%s/%s", pkg.Repository, pkg.Name)]
if !ok {
continue
}
fmt.Println(pkg.Name)
}
return nil
},
}),
Action: utils.RootNeededAction(func(c *cli.Context) error {
args := c.Args()
if args.Len() < 1 {
return cliutils.FormatCliExit(gotext.Get("Command remove expected at least 1 argument, got %d", args.Len()), nil)
}
deps, err := appbuilder.
New(c.Context).
WithManager().
Build()
if err != nil {
return err
}
defer deps.Defer()
if err := deps.Manager.Remove(&manager.Opts{
NoConfirm: !c.Bool("interactive"),
}, c.Args().Slice()...); err != nil {
return cliutils.FormatCliExit(gotext.Get("Error removing packages"), err)
}
return nil
}),
}
}

280
internal.go Normal file
View File

@ -0,0 +1,280 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package main
import (
"bufio"
"errors"
"fmt"
"log/slog"
"os"
"os/exec"
"os/user"
"path/filepath"
"syscall"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-plugin"
"github.com/leonelquinteros/gotext"
"github.com/urfave/cli/v2"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/build"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
appbuilder "gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils/app_builder"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/constants"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/logger"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/utils"
)
func InternalBuildCmd() *cli.Command {
return &cli.Command{
Name: "_internal-safe-script-executor",
HideHelp: true,
Hidden: true,
Action: func(c *cli.Context) error {
logger.SetupForGoPlugin()
slog.Debug("start _internal-safe-script-executor", "uid", syscall.Getuid(), "gid", syscall.Getgid())
if err := utils.ExitIfCantDropCapsToAlrUser(); err != nil {
return err
}
cfg := config.New()
err := cfg.Load()
if err != nil {
return cliutils.FormatCliExit(gotext.Get("Error loading config"), err)
}
logger := hclog.New(&hclog.LoggerOptions{
Name: "plugin",
Output: os.Stderr,
Level: hclog.Debug,
JSONFormat: false,
DisableTime: true,
})
plugin.Serve(&plugin.ServeConfig{
HandshakeConfig: build.HandshakeConfig,
Plugins: map[string]plugin.Plugin{
"script-executor": &build.ScriptExecutorPlugin{
Impl: build.NewLocalScriptExecutor(cfg),
},
},
Logger: logger,
})
return nil
},
}
}
func InternalInstallCmd() *cli.Command {
return &cli.Command{
Name: "_internal-installer",
HideHelp: true,
Hidden: true,
Action: func(c *cli.Context) error {
logger.SetupForGoPlugin()
if err := utils.EnsureIsAlrUser(); err != nil {
return err
}
// Before escalating the rights, we made sure that
// this is an ALR user, so it looks safe.
err := utils.EscalateToRootUid()
if err != nil {
return cliutils.FormatCliExit("cannot escalate to root", err)
}
deps, err := appbuilder.
New(c.Context).
WithConfig().
WithDB().
WithReposNoPull().
Build()
if err != nil {
return err
}
defer deps.Defer()
logger := hclog.New(&hclog.LoggerOptions{
Name: "plugin",
Output: os.Stderr,
Level: hclog.Trace,
JSONFormat: true,
DisableTime: true,
})
plugin.Serve(&plugin.ServeConfig{
HandshakeConfig: build.HandshakeConfig,
Plugins: map[string]plugin.Plugin{
"installer": &build.InstallerPlugin{
Impl: build.NewInstaller(
manager.Detect(),
),
},
},
Logger: logger,
})
return nil
},
}
}
func Mount(target string) (string, func(), error) {
exe, err := os.Executable()
if err != nil {
return "", nil, fmt.Errorf("failed to get executable path: %w", err)
}
cmd := exec.Command(exe, "_internal-temporary-mount", target)
stdoutPipe, err := cmd.StdoutPipe()
if err != nil {
return "", nil, fmt.Errorf("failed to get stdout pipe: %w", err)
}
stdinPipe, err := cmd.StdinPipe()
if err != nil {
return "", nil, fmt.Errorf("failed to get stdin pipe: %w", err)
}
cmd.Stderr = os.Stderr
if err := cmd.Start(); err != nil {
return "", nil, fmt.Errorf("failed to start mount: %w", err)
}
scanner := bufio.NewScanner(stdoutPipe)
var mountPath string
if scanner.Scan() {
mountPath = scanner.Text()
}
if err := scanner.Err(); err != nil {
_ = cmd.Process.Kill()
return "", nil, fmt.Errorf("failed to read mount output: %w", err)
}
if mountPath == "" {
_ = cmd.Process.Kill()
return "", nil, errors.New("mount failed: no target path returned")
}
cleanup := func() {
slog.Debug("cleanup triggered")
_, _ = fmt.Fprintln(stdinPipe, "")
_ = cmd.Wait()
}
return mountPath, cleanup, nil
}
func InternalMountCmd() *cli.Command {
return &cli.Command{
Name: "_internal-temporary-mount",
HideHelp: true,
Hidden: true,
Action: func(c *cli.Context) error {
logger.SetupForGoPlugin()
sourceDir := c.Args().First()
u, err := user.Current()
if err != nil {
return cliutils.FormatCliExit("cannot get current user", err)
}
_, alrGid, err := utils.GetUidGidAlrUser()
if err != nil {
return cliutils.FormatCliExit("cannot get alr user", err)
}
if _, err := os.Stat(sourceDir); err != nil {
return cliutils.FormatCliExit(fmt.Sprintf("cannot read %s", sourceDir), err)
}
if err := utils.EnuseIsPrivilegedGroupMember(); err != nil {
return err
}
// Before escalating the rights, we made sure that
// 1. user in wheel group
// 2. user can access sourceDir
if err := utils.EscalateToRootUid(); err != nil {
return err
}
if err := syscall.Setgid(alrGid); err != nil {
return err
}
if err := os.MkdirAll(constants.AlrRunDir, 0o770); err != nil {
return cliutils.FormatCliExit(fmt.Sprintf("failed to create %s", constants.AlrRunDir), err)
}
if err := os.Chown(constants.AlrRunDir, 0, alrGid); err != nil {
return cliutils.FormatCliExit(fmt.Sprintf("failed to chown %s", constants.AlrRunDir), err)
}
targetDir := filepath.Join(constants.AlrRunDir, fmt.Sprintf("bindfs-%d", os.Getpid()))
// 0750: owner (root) and group (alr)
if err := os.MkdirAll(targetDir, 0o750); err != nil {
return cliutils.FormatCliExit("error creating bindfs target directory", err)
}
// chown AlrRunDir/mounts/bindfs-* to (root:alr),
// so alr user can access dir
if err := os.Chown(targetDir, 0, alrGid); err != nil {
return cliutils.FormatCliExit("failed to chown bindfs directory", err)
}
bindfsCmd := exec.Command(
"bindfs",
fmt.Sprintf("--map=%s/alr:@%s/@alr", u.Uid, u.Gid),
sourceDir,
targetDir,
)
bindfsCmd.Stderr = os.Stderr
if err := bindfsCmd.Run(); err != nil {
return cliutils.FormatCliExit("failed to strart bindfs", err)
}
fmt.Println(targetDir)
_, _ = bufio.NewReader(os.Stdin).ReadString('\n')
slog.Debug("start unmount", "dir", targetDir)
umountCmd := exec.Command("umount", targetDir)
umountCmd.Stderr = os.Stderr
if err := umountCmd.Run(); err != nil {
return cliutils.FormatCliExit(fmt.Sprintf("failed to unmount %s", targetDir), err)
}
if err := os.Remove(targetDir); err != nil {
return cliutils.FormatCliExit(fmt.Sprintf("error removing directory %s", targetDir), err)
}
return nil
},
}
}

697
internal/build/build.go Normal file
View File

@ -0,0 +1,697 @@
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
// It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors.
//
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package build
import (
"bytes"
"context"
"encoding/gob"
"errors"
"fmt"
"log/slog"
"github.com/leonelquinteros/gotext"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
)
type BuildInput struct {
opts *types.BuildOpts
info *distro.OSRelease
pkgFormat string
script string
repository string
packages []string
}
func (bi *BuildInput) GobEncode() ([]byte, error) {
w := new(bytes.Buffer)
encoder := gob.NewEncoder(w)
if err := encoder.Encode(bi.opts); err != nil {
return nil, err
}
if err := encoder.Encode(bi.info); err != nil {
return nil, err
}
if err := encoder.Encode(bi.pkgFormat); err != nil {
return nil, err
}
if err := encoder.Encode(bi.script); err != nil {
return nil, err
}
if err := encoder.Encode(bi.repository); err != nil {
return nil, err
}
if err := encoder.Encode(bi.packages); err != nil {
return nil, err
}
return w.Bytes(), nil
}
func (bi *BuildInput) GobDecode(data []byte) error {
r := bytes.NewBuffer(data)
decoder := gob.NewDecoder(r)
if err := decoder.Decode(&bi.opts); err != nil {
return err
}
if err := decoder.Decode(&bi.info); err != nil {
return err
}
if err := decoder.Decode(&bi.pkgFormat); err != nil {
return err
}
if err := decoder.Decode(&bi.script); err != nil {
return err
}
if err := decoder.Decode(&bi.repository); err != nil {
return err
}
if err := decoder.Decode(&bi.packages); err != nil {
return err
}
return nil
}
func (b *BuildInput) Repository() string {
return b.repository
}
func (b *BuildInput) BuildOpts() *types.BuildOpts {
return b.opts
}
func (b *BuildInput) OSRelease() *distro.OSRelease {
return b.info
}
func (b *BuildInput) PkgFormat() string {
return b.pkgFormat
}
type BuildOptsProvider interface {
BuildOpts() *types.BuildOpts
}
type OsInfoProvider interface {
OSRelease() *distro.OSRelease
}
type PkgFormatProvider interface {
PkgFormat() string
}
type RepositoryProvider interface {
Repository() string
}
// ================================================
type BuiltDep struct {
Name string
Path string
}
func Map[T, R any](items []T, f func(T) R) []R {
res := make([]R, len(items))
for i, item := range items {
res[i] = f(item)
}
return res
}
func GetBuiltPaths(deps []*BuiltDep) []string {
return Map(deps, func(dep *BuiltDep) string {
return dep.Path
})
}
func GetBuiltName(deps []*BuiltDep) []string {
return Map(deps, func(dep *BuiltDep) string {
return dep.Name
})
}
type PackageFinder interface {
FindPkgs(ctx context.Context, pkgs []string) (map[string][]alrsh.Package, []string, error)
}
type Config interface {
GetPaths() *config.Paths
PagerStyle() string
}
type FunctionsOutput struct {
Contents *[]string
}
// EXECUTORS
type ScriptResolverExecutor interface {
ResolveScript(ctx context.Context, pkg *alrsh.Package) *ScriptInfo
}
type ScriptExecutor interface {
ReadScript(ctx context.Context, scriptPath string) (*alrsh.ScriptFile, error)
ExecuteFirstPass(ctx context.Context, input *BuildInput, sf *alrsh.ScriptFile) (string, []*alrsh.Package, error)
PrepareDirs(
ctx context.Context,
input *BuildInput,
basePkg string,
) error
ExecuteSecondPass(
ctx context.Context,
input *BuildInput,
sf *alrsh.ScriptFile,
varsOfPackages []*alrsh.Package,
repoDeps []string,
builtDeps []*BuiltDep,
basePkg string,
) ([]*BuiltDep, error)
}
type CacheExecutor interface {
CheckForBuiltPackage(ctx context.Context, input *BuildInput, vars *alrsh.Package) (string, bool, error)
}
type ScriptViewerExecutor interface {
ViewScript(ctx context.Context, input *BuildInput, sf *alrsh.ScriptFile, basePkg string) error
}
type CheckerExecutor interface {
PerformChecks(
ctx context.Context,
input *BuildInput,
vars *alrsh.Package,
) (bool, error)
}
type InstallerExecutor interface {
InstallLocal(paths []string, opts *manager.Opts) error
Install(pkgs []string, opts *manager.Opts) error
RemoveAlreadyInstalled(pkgs []string) ([]string, error)
}
type SourcesInput struct {
Sources []string
Checksums []string
}
type SourceDownloaderExecutor interface {
DownloadSources(
ctx context.Context,
input *BuildInput,
basePkg string,
si SourcesInput,
) error
}
//
func NewBuilder(
scriptResolver ScriptResolverExecutor,
scriptExecutor ScriptExecutor,
cacheExecutor CacheExecutor,
scriptViewerExecutor ScriptViewerExecutor,
checkerExecutor CheckerExecutor,
installerExecutor InstallerExecutor,
sourceExecutor SourceDownloaderExecutor,
) *Builder {
return &Builder{
scriptResolver: scriptResolver,
scriptExecutor: scriptExecutor,
cacheExecutor: cacheExecutor,
scriptViewerExecutor: scriptViewerExecutor,
checkerExecutor: checkerExecutor,
installerExecutor: installerExecutor,
sourceExecutor: sourceExecutor,
}
}
type Builder struct {
scriptResolver ScriptResolverExecutor
scriptExecutor ScriptExecutor
cacheExecutor CacheExecutor
scriptViewerExecutor ScriptViewerExecutor
checkerExecutor CheckerExecutor
installerExecutor InstallerExecutor
sourceExecutor SourceDownloaderExecutor
repos PackageFinder
// mgr manager.Manager
}
type BuildArgs struct {
Opts *types.BuildOpts
Info *distro.OSRelease
PkgFormat_ string
}
func (b *BuildArgs) BuildOpts() *types.BuildOpts {
return b.Opts
}
func (b *BuildArgs) OSRelease() *distro.OSRelease {
return b.Info
}
func (b *BuildArgs) PkgFormat() string {
return b.PkgFormat_
}
type BuildPackageFromDbArgs struct {
BuildArgs
Package *alrsh.Package
Packages []string
}
type BuildPackageFromScriptArgs struct {
BuildArgs
Script string
Packages []string
}
func (b *Builder) BuildPackageFromDb(
ctx context.Context,
args *BuildPackageFromDbArgs,
) ([]*BuiltDep, error) {
scriptInfo := b.scriptResolver.ResolveScript(ctx, args.Package)
return b.BuildPackage(ctx, &BuildInput{
script: scriptInfo.Script,
repository: scriptInfo.Repository,
packages: args.Packages,
pkgFormat: args.PkgFormat(),
opts: args.Opts,
info: args.Info,
})
}
func (b *Builder) BuildPackageFromScript(
ctx context.Context,
args *BuildPackageFromScriptArgs,
) ([]*BuiltDep, error) {
return b.BuildPackage(ctx, &BuildInput{
script: args.Script,
repository: "default",
packages: args.Packages,
pkgFormat: args.PkgFormat(),
opts: args.Opts,
info: args.Info,
})
}
func (b *Builder) BuildPackage(
ctx context.Context,
input *BuildInput,
) ([]*BuiltDep, error) {
scriptPath := input.script
slog.Debug("ReadScript")
sf, err := b.scriptExecutor.ReadScript(ctx, scriptPath)
if err != nil {
return nil, fmt.Errorf("failed reading script: %w", err)
}
slog.Debug("ExecuteFirstPass")
basePkg, varsOfPackages, err := b.scriptExecutor.ExecuteFirstPass(ctx, input, sf)
if err != nil {
return nil, fmt.Errorf("failed ExecuteFirstPass: %w", err)
}
var builtDeps []*BuiltDep
if !input.opts.Clean {
var remainingVars []*alrsh.Package
for _, vars := range varsOfPackages {
builtPkgPath, ok, err := b.cacheExecutor.CheckForBuiltPackage(ctx, input, vars)
if err != nil {
return nil, err
}
if ok {
builtDeps = append(builtDeps, &BuiltDep{
Path: builtPkgPath,
})
} else {
remainingVars = append(remainingVars, vars)
}
}
if len(remainingVars) == 0 {
return builtDeps, nil
}
}
slog.Debug("ViewScript")
slog.Debug("", "varsOfPackages", varsOfPackages[0])
err = b.scriptViewerExecutor.ViewScript(ctx, input, sf, basePkg)
if err != nil {
return nil, err
}
slog.Info(gotext.Get("Building package"), "name", basePkg)
for _, vars := range varsOfPackages {
cont, err := b.checkerExecutor.PerformChecks(ctx, input, vars)
if err != nil {
return nil, err
}
if !cont {
return nil, errors.New("exit...")
}
}
buildDepends := []string{}
optDepends := []string{}
depends := []string{}
sources := []string{}
checksums := []string{}
for _, vars := range varsOfPackages {
buildDepends = append(buildDepends, vars.BuildDepends.Resolved()...)
optDepends = append(optDepends, vars.OptDepends.Resolved()...)
depends = append(depends, vars.Depends.Resolved()...)
sources = append(sources, vars.Sources.Resolved()...)
checksums = append(checksums, vars.Checksums.Resolved()...)
}
buildDepends = removeDuplicates(buildDepends)
optDepends = removeDuplicates(optDepends)
depends = removeDuplicates(depends)
if len(sources) != len(checksums) {
slog.Error(gotext.Get("The checksums array must be the same length as sources"))
return nil, errors.New("exit...")
}
sources, checksums = removeDuplicatesSources(sources, checksums)
slog.Debug("installBuildDeps")
alrBuildDeps, err := b.installBuildDeps(ctx, input, buildDepends)
if err != nil {
return nil, err
}
slog.Debug("installOptDeps")
_, err = b.installOptDeps(ctx, input, optDepends)
if err != nil {
return nil, err
}
depNames := make(map[string]struct{})
for _, dep := range alrBuildDeps {
depNames[dep.Name] = struct{}{}
}
// We filter so as not to re-build what has already been built at the `installBuildDeps` stage.
var filteredDepends []string
for _, d := range depends {
if _, found := depNames[d]; !found {
filteredDepends = append(filteredDepends, d)
}
}
slog.Debug("BuildALRDeps")
newBuiltDeps, repoDeps, err := b.BuildALRDeps(ctx, input, filteredDepends)
if err != nil {
return nil, err
}
slog.Debug("PrepareDirs")
err = b.scriptExecutor.PrepareDirs(ctx, input, basePkg)
if err != nil {
return nil, err
}
slog.Info(gotext.Get("Downloading sources"))
slog.Debug("DownloadSources")
err = b.sourceExecutor.DownloadSources(
ctx,
input,
basePkg,
SourcesInput{
Sources: sources,
Checksums: checksums,
},
)
if err != nil {
return nil, err
}
builtDeps = removeDuplicates(append(builtDeps, newBuiltDeps...))
slog.Debug("ExecuteSecondPass")
res, err := b.scriptExecutor.ExecuteSecondPass(
ctx,
input,
sf,
varsOfPackages,
repoDeps,
builtDeps,
basePkg,
)
if err != nil {
return nil, err
}
builtDeps = removeDuplicates(append(builtDeps, res...))
return builtDeps, nil
}
type InstallPkgsArgs struct {
BuildArgs
AlrPkgs []alrsh.Package
NativePkgs []string
}
func (b *Builder) InstallALRPackages(
ctx context.Context,
input interface {
OsInfoProvider
BuildOptsProvider
PkgFormatProvider
},
alrPkgs []alrsh.Package,
) error {
for _, pkg := range alrPkgs {
res, err := b.BuildPackageFromDb(
ctx,
&BuildPackageFromDbArgs{
Package: &pkg,
Packages: []string{},
BuildArgs: BuildArgs{
Opts: input.BuildOpts(),
Info: input.OSRelease(),
PkgFormat_: input.PkgFormat(),
},
},
)
if err != nil {
return err
}
err = b.installerExecutor.InstallLocal(
GetBuiltPaths(res),
&manager.Opts{
NoConfirm: !input.BuildOpts().Interactive,
},
)
if err != nil {
return err
}
}
return nil
}
func (b *Builder) BuildALRDeps(
ctx context.Context,
input interface {
OsInfoProvider
BuildOptsProvider
PkgFormatProvider
},
depends []string,
) (buildDeps []*BuiltDep, repoDeps []string, err error) {
if len(depends) > 0 {
slog.Info(gotext.Get("Installing dependencies"))
found, notFound, err := b.repos.FindPkgs(ctx, depends) // Поиск зависимостей
if err != nil {
return nil, nil, fmt.Errorf("failed FindPkgs: %w", err)
}
repoDeps = notFound
// Если для некоторых пакетов есть несколько опций, упрощаем их все в один срез
pkgs := cliutils.FlattenPkgs(
ctx,
found,
"install",
input.BuildOpts().Interactive,
)
type item struct {
pkg *alrsh.Package
packages []string
}
pkgsMap := make(map[string]*item)
for _, pkg := range pkgs {
name := pkg.BasePkgName
if name == "" {
name = pkg.Name
}
if pkgsMap[name] == nil {
pkgsMap[name] = &item{
pkg: &pkg,
}
}
pkgsMap[name].packages = append(
pkgsMap[name].packages,
pkg.Name,
)
}
for basePkgName := range pkgsMap {
pkg := pkgsMap[basePkgName].pkg
res, err := b.BuildPackageFromDb(
ctx,
&BuildPackageFromDbArgs{
Package: pkg,
Packages: pkgsMap[basePkgName].packages,
BuildArgs: BuildArgs{
Opts: input.BuildOpts(),
Info: input.OSRelease(),
PkgFormat_: input.PkgFormat(),
},
},
)
if err != nil {
return nil, nil, fmt.Errorf("failed build package from db: %w", err)
}
buildDeps = append(buildDeps, res...)
}
}
repoDeps = removeDuplicates(repoDeps)
buildDeps = removeDuplicates(buildDeps)
return buildDeps, repoDeps, nil
}
func (i *Builder) installBuildDeps(
ctx context.Context,
input interface {
OsInfoProvider
BuildOptsProvider
PkgFormatProvider
},
pkgs []string,
) ([]*BuiltDep, error) {
var builtDeps []*BuiltDep
if len(pkgs) > 0 {
deps, err := i.installerExecutor.RemoveAlreadyInstalled(pkgs)
if err != nil {
return nil, err
}
builtDeps, err = i.InstallPkgs(ctx, input, deps) // Устанавливаем выбранные пакеты
if err != nil {
return nil, err
}
}
return builtDeps, nil
}
func (i *Builder) installOptDeps(
ctx context.Context,
input interface {
OsInfoProvider
BuildOptsProvider
PkgFormatProvider
},
pkgs []string,
) ([]*BuiltDep, error) {
var builtDeps []*BuiltDep
optDeps, err := i.installerExecutor.RemoveAlreadyInstalled(pkgs)
if err != nil {
return nil, err
}
if len(optDeps) > 0 {
optDeps, err := cliutils.ChooseOptDepends(
ctx,
optDeps,
"install",
input.BuildOpts().Interactive,
) // Пользователя просят выбрать опциональные зависимости
if err != nil {
return nil, err
}
if len(optDeps) == 0 {
return builtDeps, nil
}
builtDeps, err = i.InstallPkgs(ctx, input, optDeps) // Устанавливаем выбранные пакеты
if err != nil {
return nil, err
}
}
return builtDeps, nil
}
func (i *Builder) InstallPkgs(
ctx context.Context,
input interface {
OsInfoProvider
BuildOptsProvider
PkgFormatProvider
},
pkgs []string,
) ([]*BuiltDep, error) {
builtDeps, repoDeps, err := i.BuildALRDeps(ctx, input, pkgs)
if err != nil {
return nil, err
}
if len(builtDeps) > 0 {
err = i.installerExecutor.InstallLocal(GetBuiltPaths(builtDeps), &manager.Opts{
NoConfirm: !input.BuildOpts().Interactive,
})
if err != nil {
return nil, err
}
}
if len(repoDeps) > 0 {
err = i.installerExecutor.Install(repoDeps, &manager.Opts{
NoConfirm: !input.BuildOpts().Interactive,
})
if err != nil {
return nil, err
}
}
return builtDeps, nil
}

View File

@ -0,0 +1,286 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package build
import (
"context"
"fmt"
"os"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"mvdan.cc/sh/v3/syntax"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager"
)
type TestPackageFinder struct {
FindPkgsFunc func(ctx context.Context, pkgs []string) (map[string][]db.Package, []string, error)
}
func (pf *TestPackageFinder) FindPkgs(ctx context.Context, pkgs []string) (map[string][]db.Package, []string, error) {
if pf.FindPkgsFunc != nil {
return pf.FindPkgsFunc(ctx, pkgs)
}
return map[string][]db.Package{}, []string{}, nil
}
type TestManager struct {
NameFunc func() string
FormatFunc func() string
ExistsFunc func() bool
SetRootCmdFunc func(cmd string)
SyncFunc func(opts *manager.Opts) error
InstallFunc func(opts *manager.Opts, pkgs ...string) error
RemoveFunc func(opts *manager.Opts, pkgs ...string) error
UpgradeFunc func(opts *manager.Opts, pkgs ...string) error
InstallLocalFunc func(opts *manager.Opts, files ...string) error
UpgradeAllFunc func(opts *manager.Opts) error
ListInstalledFunc func(opts *manager.Opts) (map[string]string, error)
IsInstalledFunc func(pkg string) (bool, error)
}
func (m *TestManager) Name() string {
if m.NameFunc != nil {
return m.NameFunc()
}
return "TestManager"
}
func (m *TestManager) Format() string {
if m.FormatFunc != nil {
return m.FormatFunc()
}
return "testpkg"
}
func (m *TestManager) Exists() bool {
if m.ExistsFunc != nil {
return m.ExistsFunc()
}
return true
}
func (m *TestManager) SetRootCmd(cmd string) {
if m.SetRootCmdFunc != nil {
m.SetRootCmdFunc(cmd)
}
}
func (m *TestManager) Sync(opts *manager.Opts) error {
if m.SyncFunc != nil {
return m.SyncFunc(opts)
}
return nil
}
func (m *TestManager) Install(opts *manager.Opts, pkgs ...string) error {
if m.InstallFunc != nil {
return m.InstallFunc(opts, pkgs...)
}
return nil
}
func (m *TestManager) Remove(opts *manager.Opts, pkgs ...string) error {
if m.RemoveFunc != nil {
return m.RemoveFunc(opts, pkgs...)
}
return nil
}
func (m *TestManager) Upgrade(opts *manager.Opts, pkgs ...string) error {
if m.UpgradeFunc != nil {
return m.UpgradeFunc(opts, pkgs...)
}
return nil
}
func (m *TestManager) InstallLocal(opts *manager.Opts, files ...string) error {
if m.InstallLocalFunc != nil {
return m.InstallLocalFunc(opts, files...)
}
return nil
}
func (m *TestManager) UpgradeAll(opts *manager.Opts) error {
if m.UpgradeAllFunc != nil {
return m.UpgradeAllFunc(opts)
}
return nil
}
func (m *TestManager) ListInstalled(opts *manager.Opts) (map[string]string, error) {
if m.ListInstalledFunc != nil {
return m.ListInstalledFunc(opts)
}
return map[string]string{}, nil
}
func (m *TestManager) IsInstalled(pkg string) (bool, error) {
if m.IsInstalledFunc != nil {
return m.IsInstalledFunc(pkg)
}
return true, nil
}
type TestConfig struct{}
func (c *TestConfig) PagerStyle() string {
return "native"
}
func (c *TestConfig) GetPaths() *config.Paths {
return &config.Paths{
CacheDir: "/tmp",
}
}
func TestExecuteFirstPassIsSecure(t *testing.T) {
cfg := &TestConfig{}
pf := &TestPackageFinder{}
info := &distro.OSRelease{}
m := &TestManager{}
opts := types.BuildOpts{
Manager: m,
Interactive: false,
}
ctx := context.Background()
b := NewBuilder(
ctx,
opts,
pf,
info,
cfg,
)
tmpFile, err := os.CreateTemp("", "testfile-")
assert.NoError(t, err)
tmpFilePath := tmpFile.Name()
defer os.Remove(tmpFilePath)
_, err = os.Stat(tmpFilePath)
assert.NoError(t, err)
testScript := fmt.Sprintf(`name='test'
version=1.0.0
release=1
rm -f %s`, tmpFilePath)
fl, err := syntax.NewParser().Parse(strings.NewReader(testScript), "alr.sh")
assert.NoError(t, err)
_, _, err = b.executeFirstPass(fl)
assert.NoError(t, err)
_, err = os.Stat(tmpFilePath)
assert.NoError(t, err)
}
func TestExecuteFirstPassIsCorrect(t *testing.T) {
type testCase struct {
Name string
Script string
Opts types.BuildOpts
Expected func(t *testing.T, vars []*types.BuildVars)
}
for _, tc := range []testCase{{
Name: "single package",
Script: `name='test'
version='1.0.0'
release=1
epoch=2
desc="Test package"
homepage='https://example.com'
maintainer='Ivan Ivanov'
`,
Opts: types.BuildOpts{
Manager: &TestManager{},
Interactive: false,
},
Expected: func(t *testing.T, vars []*types.BuildVars) {
assert.Equal(t, 1, len(vars))
assert.Equal(t, vars[0].Name, "test")
assert.Equal(t, vars[0].Version, "1.0.0")
assert.Equal(t, vars[0].Release, int(1))
assert.Equal(t, vars[0].Epoch, uint(2))
assert.Equal(t, vars[0].Description, "Test package")
},
}, {
Name: "multiple packages",
Script: `name=(
foo
bar
)
version='0.0.1'
release=1
epoch=2
desc="Test package"
meta_foo() {
desc="Foo package"
}
meta_bar() {
}
`,
Opts: types.BuildOpts{
Packages: []string{"foo"},
Manager: &TestManager{},
Interactive: false,
},
Expected: func(t *testing.T, vars []*types.BuildVars) {
assert.Equal(t, 1, len(vars))
assert.Equal(t, vars[0].Name, "foo")
assert.Equal(t, vars[0].Description, "Foo package")
},
}} {
t.Run(tc.Name, func(t *testing.T) {
cfg := &TestConfig{}
pf := &TestPackageFinder{}
info := &distro.OSRelease{}
ctx := context.Background()
b := NewBuilder(
ctx,
tc.Opts,
pf,
info,
cfg,
)
fl, err := syntax.NewParser().Parse(strings.NewReader(tc.Script), "alr.sh")
assert.NoError(t, err)
_, allVars, err := b.scriptExecutor.ExecuteSecondPass(fl)
assert.NoError(t, err)
tc.Expected(t, allVars)
})
}
}

69
internal/build/cache.go Normal file
View File

@ -0,0 +1,69 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package build
import (
"context"
"os"
"path/filepath"
"github.com/goreleaser/nfpm/v2"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
)
type Cache struct {
cfg Config
}
func (c *Cache) CheckForBuiltPackage(
ctx context.Context,
input *BuildInput,
vars *alrsh.Package,
) (string, bool, error) {
filename, err := pkgFileName(input, vars)
if err != nil {
return "", false, err
}
pkgPath := filepath.Join(getBaseDir(c.cfg, vars.Name), filename)
_, err = os.Stat(pkgPath)
if err != nil {
return "", false, nil
}
return pkgPath, true, nil
}
func pkgFileName(
input interface {
OsInfoProvider
PkgFormatProvider
RepositoryProvider
},
vars *alrsh.Package,
) (string, error) {
pkgInfo := getBasePkgInfo(vars, input)
packager, err := nfpm.Get(input.PkgFormat())
if err != nil {
return "", err
}
return packager.ConventionalFileName(pkgInfo), nil
}

74
internal/build/checker.go Normal file
View File

@ -0,0 +1,74 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package build
import (
"context"
"log/slog"
"github.com/leonelquinteros/gotext"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cpu"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
)
type Checker struct {
mgr manager.Manager
}
func (c *Checker) PerformChecks(
ctx context.Context,
input *BuildInput,
vars *alrsh.Package,
) (bool, error) {
if !cpu.IsCompatibleWith(cpu.Arch(), vars.Architectures) { // Проверяем совместимость архитектуры
cont, err := cliutils.YesNoPrompt(
ctx,
gotext.Get("Your system's CPU architecture doesn't match this package. Do you want to build anyway?"),
input.opts.Interactive,
true,
)
if err != nil {
return false, err
}
if !cont {
return false, nil
}
}
installed, err := c.mgr.ListInstalled(nil)
if err != nil {
return false, err
}
filename, err := pkgFileName(input, vars)
if err != nil {
return false, err
}
if instVer, ok := installed[filename]; ok { // Если пакет уже установлен, выводим предупреждение
slog.Warn(gotext.Get("This package is already installed"),
"name", vars.Name,
"version", instVer,
)
}
return true, nil
}

71
internal/build/dirs.go Normal file
View File

@ -0,0 +1,71 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package build
import (
"path/filepath"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
)
type BaseDirProvider interface {
BaseDir() string
}
type SrcDirProvider interface {
SrcDir() string
}
type PkgDirProvider interface {
PkgDir() string
}
type ScriptDirProvider interface {
ScriptDir() string
}
func getDirs(
cfg Config,
scriptPath string,
basePkg string,
) (types.Directories, error) {
pkgsDir := cfg.GetPaths().PkgsDir
scriptPath, err := filepath.Abs(scriptPath)
if err != nil {
return types.Directories{}, err
}
baseDir := filepath.Join(pkgsDir, basePkg)
return types.Directories{
BaseDir: getBaseDir(cfg, basePkg),
SrcDir: getSrcDir(cfg, basePkg),
PkgDir: filepath.Join(baseDir, "pkg"),
ScriptDir: getScriptDir(scriptPath),
}, nil
}
func getBaseDir(cfg Config, basePkg string) string {
return filepath.Join(cfg.GetPaths().PkgsDir, basePkg)
}
func getSrcDir(cfg Config, basePkg string) string {
return filepath.Join(getBaseDir(cfg, basePkg), "src")
}
func getScriptDir(scriptPath string) string {
return filepath.Dir(scriptPath)
}

View File

@ -0,0 +1,96 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package finddeps
import (
"bytes"
"context"
"log/slog"
"os/exec"
"path"
"strings"
"github.com/goreleaser/nfpm/v2"
"github.com/leonelquinteros/gotext"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
)
func rpmFindDependenciesALTLinux(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, command string, envs []string, updateFunc func(string)) error {
if _, err := exec.LookPath(command); err != nil {
slog.Info(gotext.Get("Command not found on the system"), "command", command)
return nil
}
var paths []string
for _, content := range pkgInfo.Contents {
if content.Type != "dir" {
paths = append(paths,
path.Join(dirs.PkgDir, content.Destination),
)
}
}
if len(paths) == 0 {
return nil
}
cmd := exec.CommandContext(ctx, command)
cmd.Stdin = bytes.NewBufferString(strings.Join(paths, "\n") + "\n")
cmd.Env = append(cmd.Env,
"RPM_BUILD_ROOT="+dirs.PkgDir,
"RPM_FINDPROV_METHOD=",
"RPM_FINDREQ_METHOD=",
"RPM_DATADIR=",
"RPM_SUBPACKAGE_NAME=",
)
cmd.Env = append(cmd.Env, envs...)
var out bytes.Buffer
var stderr bytes.Buffer
cmd.Stdout = &out
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
slog.Error(stderr.String())
return err
}
slog.Debug(stderr.String())
dependencies := strings.Split(strings.TrimSpace(out.String()), "\n")
for _, dep := range dependencies {
if dep != "" {
updateFunc(dep)
}
}
return nil
}
type ALTLinuxFindProvReq struct{}
func (o *ALTLinuxFindProvReq) FindProvides(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, skiplist []string) error {
return rpmFindDependenciesALTLinux(ctx, pkgInfo, dirs, "/usr/lib/rpm/find-provides", []string{"RPM_FINDPROV_SKIPLIST=" + strings.Join(skiplist, "\n")}, func(dep string) {
slog.Info(gotext.Get("Provided dependency found"), "dep", dep)
pkgInfo.Overridables.Provides = append(pkgInfo.Overridables.Provides, dep)
})
}
func (o *ALTLinuxFindProvReq) FindRequires(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, skiplist []string) error {
return rpmFindDependenciesALTLinux(ctx, pkgInfo, dirs, "/usr/lib/rpm/find-requires", []string{"RPM_FINDREQ_SKIPLIST=" + strings.Join(skiplist, "\n")}, func(dep string) {
slog.Info(gotext.Get("Required dependency found"), "dep", dep)
pkgInfo.Overridables.Depends = append(pkgInfo.Overridables.Depends, dep)
})
}

View File

@ -0,0 +1,39 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package finddeps
import (
"context"
"log/slog"
"github.com/goreleaser/nfpm/v2"
"github.com/leonelquinteros/gotext"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
)
type EmptyFindProvReq struct{}
func (o *EmptyFindProvReq) FindProvides(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, skiplist []string) error {
slog.Info(gotext.Get("AutoProv is not implemented for this package format, so it's skipped"))
return nil
}
func (o *EmptyFindProvReq) FindRequires(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, skiplist []string) error {
slog.Info(gotext.Get("AutoReq is not implemented for this package format, so it's skipped"))
return nil
}

View File

@ -0,0 +1,118 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package finddeps
import (
"bytes"
"context"
"fmt"
"log/slog"
"os/exec"
"path"
"strings"
"github.com/goreleaser/nfpm/v2"
"github.com/leonelquinteros/gotext"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
)
type FedoraFindProvReq struct{}
func rpmFindDependenciesFedora(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, command string, args []string, updateFunc func(string)) error {
if _, err := exec.LookPath(command); err != nil {
slog.Info(gotext.Get("Command not found on the system"), "command", command)
return nil
}
var paths []string
for _, content := range pkgInfo.Contents {
if content.Type != "dir" {
paths = append(paths,
path.Join(dirs.PkgDir, content.Destination),
)
}
}
if len(paths) == 0 {
return nil
}
cmd := exec.CommandContext(ctx, command, args...)
cmd.Stdin = bytes.NewBufferString(strings.Join(paths, "\n") + "\n")
cmd.Env = append(cmd.Env,
"RPM_BUILD_ROOT="+dirs.PkgDir,
)
var out bytes.Buffer
var stderr bytes.Buffer
cmd.Stdout = &out
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
slog.Error(stderr.String())
return err
}
slog.Debug(stderr.String())
dependencies := strings.Split(strings.TrimSpace(out.String()), "\n")
for _, dep := range dependencies {
if dep != "" {
updateFunc(dep)
}
}
return nil
}
func (o *FedoraFindProvReq) FindProvides(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, skiplist []string) error {
return rpmFindDependenciesFedora(
ctx,
pkgInfo,
dirs,
"/usr/lib/rpm/rpmdeps",
[]string{
"--define=_use_internal_dependency_generator 1",
"--provides",
fmt.Sprintf(
"--define=__provides_exclude_from %s\"",
strings.Join(skiplist, "|"),
),
},
func(dep string) {
slog.Info(gotext.Get("Provided dependency found"), "dep", dep)
pkgInfo.Overridables.Provides = append(pkgInfo.Overridables.Provides, dep)
})
}
func (o *FedoraFindProvReq) FindRequires(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, skiplist []string) error {
return rpmFindDependenciesFedora(
ctx,
pkgInfo,
dirs,
"/usr/lib/rpm/rpmdeps",
[]string{
"--define=_use_internal_dependency_generator 1",
"--requires",
fmt.Sprintf(
"--define=__requires_exclude_from %s",
strings.Join(skiplist, "|"),
),
},
func(dep string) {
slog.Info(gotext.Get("Required dependency found"), "dep", dep)
pkgInfo.Overridables.Depends = append(pkgInfo.Overridables.Depends, dep)
})
}

View File

@ -0,0 +1,58 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package finddeps
import (
"context"
"github.com/goreleaser/nfpm/v2"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
)
type ProvReqFinder interface {
FindProvides(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, skiplist []string) error
FindRequires(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, skiplist []string) error
}
type ProvReqService struct {
finder ProvReqFinder
}
func New(info *distro.OSRelease, pkgFormat string) *ProvReqService {
s := &ProvReqService{
finder: &EmptyFindProvReq{},
}
if pkgFormat == "rpm" {
switch info.ID {
case "altlinux":
s.finder = &ALTLinuxFindProvReq{}
case "fedora":
s.finder = &FedoraFindProvReq{}
}
}
return s
}
func (s *ProvReqService) FindProvides(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, skiplist []string) error {
return s.finder.FindProvides(ctx, pkgInfo, dirs, skiplist)
}
func (s *ProvReqService) FindRequires(ctx context.Context, pkgInfo *nfpm.Info, dirs types.Directories, skiplist []string) error {
return s.finder.FindRequires(ctx, pkgInfo, dirs, skiplist)
}

325
internal/build/firejail.go Normal file
View File

@ -0,0 +1,325 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package build
import (
"errors"
"fmt"
"io"
"log/slog"
"os"
"path/filepath"
"strings"
"github.com/goreleaser/nfpm/v2/files"
"github.com/leonelquinteros/gotext"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
)
const (
firejailedDir = "/usr/lib/alr/firejailed"
defaultDirMode = 0o755
defaultScriptMode = 0o755
)
var (
ErrInvalidDestination = errors.New("invalid destination path")
ErrMissingProfile = errors.New("default profile is missing")
ErrEmptyPackageName = errors.New("package name cannot be empty")
)
var binaryDirectories = []string{
"/usr/bin/",
"/bin/",
"/usr/local/bin/",
}
func moveWithSymlinkHandling(src, dst string) error {
srcInfo, err := os.Lstat(src)
if err != nil {
return fmt.Errorf("failed to get source info: %w", err)
}
if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil {
return fmt.Errorf("failed to create destination directory: %w", err)
}
if srcInfo.Mode()&os.ModeSymlink != 0 {
return moveSymlink(src, dst)
}
if err := os.Rename(src, dst); err != nil {
return copyAndRemove(src, dst)
}
return nil
}
func moveSymlink(src, dst string) error {
target, err := os.Readlink(src)
if err != nil {
return fmt.Errorf("failed to read symlink: %w", err)
}
if err := os.Symlink(target, dst); err != nil {
return fmt.Errorf("failed to create symlink: %w", err)
}
if err := os.Remove(src); err != nil {
os.Remove(dst)
return fmt.Errorf("failed to remove original symlink: %w", err)
}
return nil
}
func copyAndRemove(src, dst string) error {
srcFile, err := os.Open(src)
if err != nil {
return fmt.Errorf("failed to open source: %w", err)
}
defer srcFile.Close()
dstFile, err := os.Create(dst)
if err != nil {
return fmt.Errorf("failed to create destination: %w", err)
}
defer dstFile.Close()
if _, err := io.Copy(dstFile, srcFile); err != nil {
return fmt.Errorf("failed to copy content: %w", err)
}
srcInfo, err := srcFile.Stat()
if err != nil {
return fmt.Errorf("failed to get source stats: %w", err)
}
if err := dstFile.Chmod(srcInfo.Mode()); err != nil {
return fmt.Errorf("failed to set permissions: %w", err)
}
if err := os.Remove(src); err != nil {
return fmt.Errorf("failed to remove source: %w", err)
}
return nil
}
func moveFileWithErrorHandling(src, dst string) error {
err := moveWithSymlinkHandling(src, dst)
if err != nil {
if os.IsPermission(err) {
return fmt.Errorf("permission denied: %w", err)
}
if os.IsNotExist(err) {
return fmt.Errorf("source file does not exist: %w", err)
}
return fmt.Errorf("failed to move file: %w", err)
}
return nil
}
func applyFirejailIntegration(
vars *alrsh.Package,
dirs types.Directories,
contents []*files.Content,
) ([]*files.Content, error) {
slog.Info(gotext.Get("Applying FireJail integration"), "package", vars.Name)
if err := createFirejailedDirectory(dirs.PkgDir); err != nil {
return nil, fmt.Errorf("failed to create firejailed directory: %w", err)
}
newContents, err := processBinaryFiles(vars, contents, dirs)
if err != nil {
return nil, fmt.Errorf("failed to process binary files: %w", err)
}
return append(contents, newContents...), nil
}
func createFirejailedDirectory(pkgDir string) error {
firejailedPath := filepath.Join(pkgDir, firejailedDir)
return os.MkdirAll(firejailedPath, defaultDirMode)
}
func processBinaryFiles(pkg *alrsh.Package, contents []*files.Content, dirs types.Directories) ([]*files.Content, error) {
var newContents []*files.Content
for _, content := range contents {
if content.Type == "dir" {
continue
}
if !isBinaryFile(content.Destination) {
slog.Debug("content not binary file", "content", content)
continue
}
slog.Debug("process content", "content", content)
newContent, err := createFirejailedBinary(pkg, content, dirs)
if err != nil {
return nil, fmt.Errorf("failed to create firejailed binary for %s: %w", content.Destination, err)
}
if newContent != nil {
newContents = append(newContents, newContent...)
}
}
return newContents, nil
}
func isBinaryFile(destination string) bool {
for _, binDir := range binaryDirectories {
if strings.HasPrefix(destination, binDir) {
return true
}
}
return false
}
func createFirejailedBinary(
pkg *alrsh.Package,
content *files.Content,
dirs types.Directories,
) ([]*files.Content, error) {
origFilePath, err := generateFirejailedPath(content.Destination)
if err != nil {
return nil, err
}
profiles := pkg.FireJailProfiles.Resolved()
sourceProfilePath, ok := profiles[content.Destination]
if !ok {
sourceProfilePath, ok = profiles["default"]
if !ok {
return nil, errors.New("default profile is missing")
}
}
sourceProfilePath = filepath.Join(dirs.ScriptDir, sourceProfilePath)
dest, err := createFirejailProfilePath(content.Destination)
if err != nil {
return nil, err
}
err = createProfile(filepath.Join(dirs.PkgDir, dest), sourceProfilePath)
if err != nil {
return nil, err
}
if err := moveFileWithErrorHandling(filepath.Join(dirs.PkgDir, content.Destination), filepath.Join(dirs.PkgDir, origFilePath)); err != nil {
return nil, fmt.Errorf("failed to move original binary: %w", err)
}
content.Type = "file"
content.Source = filepath.Join(dirs.PkgDir, content.Destination)
// Create wrapper script
if err := createWrapperScript(filepath.Join(dirs.PkgDir, content.Destination), origFilePath, dest); err != nil {
return nil, fmt.Errorf("failed to create wrapper script: %w", err)
}
profile, err := getContentFromPath(dest, dirs.PkgDir)
if err != nil {
return nil, err
}
bin, err := getContentFromPath(origFilePath, dirs.PkgDir)
if err != nil {
return nil, err
}
return []*files.Content{
bin,
profile,
}, nil
}
func getContentFromPath(path, base string) (*files.Content, error) {
absPath := filepath.Join(base, path)
fi, err := os.Lstat(absPath)
if err != nil {
return nil, fmt.Errorf("failed to get file info: %w", err)
}
return &files.Content{
Source: absPath,
Destination: path,
FileInfo: &files.ContentFileInfo{
MTime: fi.ModTime(),
Mode: fi.Mode(),
Size: fi.Size(),
},
}, nil
}
func generateSafeName(destination string) (string, error) {
cleanPath := strings.TrimPrefix(destination, ".")
if cleanPath == "" {
return "", fmt.Errorf("invalid destination path: %s", destination)
}
return strings.ReplaceAll(cleanPath, "/", "_"), nil
}
func generateFirejailedPath(destination string) (string, error) {
safeName, err := generateSafeName(destination)
if err != nil {
return "", err
}
return filepath.Join(firejailedDir, safeName), nil
}
func createProfile(destProfilePath, profilePath string) error {
srcFile, err := os.Open(profilePath)
if err != nil {
return err
}
defer srcFile.Close()
destFile, err := os.Create(destProfilePath)
if err != nil {
return err
}
defer destFile.Close()
_, err = io.Copy(destFile, srcFile)
if err != nil {
return err
}
return destFile.Sync()
}
func createWrapperScript(scriptPath, origFilePath, profilePath string) error {
scriptContent := fmt.Sprintf("#!/bin/bash\nexec firejail --profile=%q %q \"$@\"\n", profilePath, origFilePath)
return os.WriteFile(scriptPath, []byte(scriptContent), defaultDirMode)
}
func createFirejailProfilePath(binaryPath string) (string, error) {
name, err := generateSafeName(binaryPath)
if err != nil {
return "", err
}
return filepath.Join(firejailedDir, fmt.Sprintf("%s.profile", name)), nil
}

View File

@ -0,0 +1,322 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package build
import (
"os"
"path/filepath"
"testing"
"github.com/goreleaser/nfpm/v2/files"
"github.com/stretchr/testify/assert"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
)
func TestIsBinaryFile(t *testing.T) {
tests := []struct {
name string
destination string
expected bool
}{
{"usr/bin binary", "/usr/bin/test", true},
{"bin binary", "/bin/test", true},
{"usr/local/bin binary", "/usr/local/bin/test", true},
{"lib file", "/usr/lib/test.so", false},
{"etc file", "/etc/config", false},
{"empty destination", "", false},
{"root level file", "./test", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := isBinaryFile(tt.destination)
assert.Equal(t, tt.expected, result)
})
}
}
func TestGenerateSafeName(t *testing.T) {
tests := []struct {
name string
destination string
expected string
expectError bool
}{
{"usr/bin path", "./usr/bin/test", "_usr_bin_test", false},
{"bin path", "./bin/test", "_bin_test", false},
{"nested path", "./usr/local/bin/app", "_usr_local_bin_app", false},
{"path with spaces", "./usr/bin/my app", "_usr_bin_my app", false},
{"empty after trim", ".", "", true},
{"empty string", "", "", true},
{"only dots", "..", ".", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := generateSafeName(tt.destination)
if tt.expectError {
assert.Error(t, err)
} else {
assert.NoError(t, err)
assert.Equal(t, tt.expected, result)
}
})
}
}
func TestCreateWrapperScript(t *testing.T) {
tests := []struct {
name string
origFilePath string
profilePath string
expectedContent string
}{
{
"basic wrapper",
"/usr/lib/alr/firejailed/_usr_bin_test",
"/usr/lib/alr/firejailed/_usr_bin_test.profile",
"#!/bin/bash\nexec firejail --profile=\"/usr/lib/alr/firejailed/_usr_bin_test.profile\" \"/usr/lib/alr/firejailed/_usr_bin_test\" \"$@\"\n",
},
{
"path with spaces",
"/usr/lib/alr/firejailed/_usr_bin_my_app",
"/usr/lib/alr/firejailed/_usr_bin_my_app.profile",
"#!/bin/bash\nexec firejail --profile=\"/usr/lib/alr/firejailed/_usr_bin_my_app.profile\" \"/usr/lib/alr/firejailed/_usr_bin_my_app\" \"$@\"\n",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tmpDir := t.TempDir()
scriptPath := filepath.Join(tmpDir, "wrapper.sh")
err := createWrapperScript(scriptPath, tt.origFilePath, tt.profilePath)
assert.NoError(t, err)
assert.FileExists(t, scriptPath)
content, err := os.ReadFile(scriptPath)
assert.NoError(t, err)
assert.Equal(t, tt.expectedContent, string(content))
// Check file permissions
info, err := os.Stat(scriptPath)
assert.NoError(t, err)
assert.Equal(t, os.FileMode(defaultDirMode), info.Mode())
})
}
}
func TestCreateFirejailedBinary(t *testing.T) {
tests := []struct {
name string
setupFunc func(string) (*alrsh.Package, *files.Content, types.Directories)
expectError bool
errorMsg string
}{
{
"successful creation with default profile",
func(tmpDir string) (*alrsh.Package, *files.Content, types.Directories) {
pkgDir := filepath.Join(tmpDir, "pkg")
scriptDir := filepath.Join(tmpDir, "scripts")
os.MkdirAll(pkgDir, 0o755)
os.MkdirAll(scriptDir, 0o755)
binDir := filepath.Join(pkgDir, "usr", "bin")
os.MkdirAll(binDir, 0o755)
srcBinary := filepath.Join(binDir, "test-binary")
os.WriteFile(srcBinary, []byte("#!/bin/bash\necho test"), 0o755)
defaultProfile := filepath.Join(scriptDir, "default.profile")
os.WriteFile(defaultProfile, []byte("include /etc/firejail/default.profile\nnet none"), 0o644)
pkg := &alrsh.Package{
Name: "test-pkg",
FireJailProfiles: alrsh.OverridableFromMap(map[string]map[string]string{
"": {"default": "default.profile"},
}),
}
alrsh.ResolvePackage(pkg, []string{""})
content := &files.Content{
Source: srcBinary,
Destination: "/usr/bin/test-binary",
Type: "file",
}
dirs := types.Directories{PkgDir: pkgDir, ScriptDir: scriptDir}
return pkg, content, dirs
},
false,
"",
},
{
"successful creation with specific profile",
func(tmpDir string) (*alrsh.Package, *files.Content, types.Directories) {
pkgDir := filepath.Join(tmpDir, "pkg")
scriptDir := filepath.Join(tmpDir, "scripts")
os.MkdirAll(pkgDir, 0o755)
os.MkdirAll(scriptDir, 0o755)
binDir := filepath.Join(pkgDir, "usr", "bin")
os.MkdirAll(binDir, 0o755)
srcBinary := filepath.Join(binDir, "special-binary")
os.WriteFile(srcBinary, []byte("#!/bin/bash\necho special"), 0o755)
defaultProfile := filepath.Join(scriptDir, "default.profile")
os.WriteFile(defaultProfile, []byte("include /etc/firejail/default.profile"), 0o644)
specialProfile := filepath.Join(scriptDir, "special.profile")
os.WriteFile(specialProfile, []byte("include /etc/firejail/default.profile\nnet none\nprivate-tmp"), 0o644)
pkg := &alrsh.Package{
Name: "test-pkg",
FireJailProfiles: alrsh.OverridableFromMap(map[string]map[string]string{
"": {"default": "default.profile", "/usr/bin/special-binary": "special.profile"},
}),
}
alrsh.ResolvePackage(pkg, []string{""})
content := &files.Content{
Source: srcBinary,
Destination: "/usr/bin/special-binary",
Type: "file",
}
dirs := types.Directories{PkgDir: pkgDir, ScriptDir: scriptDir}
return pkg, content, dirs
},
false,
"",
},
{
"missing default profile",
func(tmpDir string) (*alrsh.Package, *files.Content, types.Directories) {
pkgDir := filepath.Join(tmpDir, "pkg")
scriptDir := filepath.Join(tmpDir, "scripts")
os.MkdirAll(pkgDir, 0o755)
os.MkdirAll(scriptDir, 0o755)
srcBinary := filepath.Join(tmpDir, "test-binary")
os.WriteFile(srcBinary, []byte("#!/bin/bash\necho test"), 0o755)
pkg := &alrsh.Package{
Name: "test-pkg",
FireJailProfiles: alrsh.OverridableFromMap(map[string]map[string]string{"": {}}),
}
alrsh.ResolvePackage(pkg, []string{""})
content := &files.Content{Source: srcBinary, Destination: "./usr/bin/test-binary", Type: "file"}
dirs := types.Directories{PkgDir: pkgDir, ScriptDir: scriptDir}
return pkg, content, dirs
},
true,
"default profile is missing",
},
{
"profile file not found",
func(tmpDir string) (*alrsh.Package, *files.Content, types.Directories) {
pkgDir := filepath.Join(tmpDir, "pkg")
scriptDir := filepath.Join(tmpDir, "scripts")
os.MkdirAll(pkgDir, 0o755)
os.MkdirAll(scriptDir, 0o755)
srcBinary := filepath.Join(tmpDir, "test-binary")
os.WriteFile(srcBinary, []byte("#!/bin/bash\necho test"), 0o755)
pkg := &alrsh.Package{
Name: "test-pkg",
FireJailProfiles: alrsh.OverridableFromMap(map[string]map[string]string{"": {"default": "nonexistent.profile"}}),
}
alrsh.ResolvePackage(pkg, []string{""})
content := &files.Content{Source: srcBinary, Destination: "./usr/bin/test-binary", Type: "file"}
dirs := types.Directories{PkgDir: pkgDir, ScriptDir: scriptDir}
return pkg, content, dirs
},
true,
"",
},
{
"invalid destination path",
func(tmpDir string) (*alrsh.Package, *files.Content, types.Directories) {
pkgDir := filepath.Join(tmpDir, "pkg")
scriptDir := filepath.Join(tmpDir, "scripts")
os.MkdirAll(pkgDir, 0o755)
os.MkdirAll(scriptDir, 0o755)
srcBinary := filepath.Join(tmpDir, "test-binary")
os.WriteFile(srcBinary, []byte("#!/bin/bash\necho test"), 0o755)
defaultProfile := filepath.Join(scriptDir, "default.profile")
os.WriteFile(defaultProfile, []byte("include /etc/firejail/default.profile"), 0o644)
pkg := &alrsh.Package{Name: "test-pkg", FireJailProfiles: alrsh.OverridableFromMap(map[string]map[string]string{"": {"default": "default.profile"}})}
alrsh.ResolvePackage(pkg, []string{""})
content := &files.Content{Source: srcBinary, Destination: ".", Type: "file"}
dirs := types.Directories{PkgDir: pkgDir, ScriptDir: scriptDir}
return pkg, content, dirs
},
true,
"",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tmpDir := t.TempDir()
pkg, content, dirs := tt.setupFunc(tmpDir)
err := createFirejailedDirectory(dirs.PkgDir)
assert.NoError(t, err)
result, err := createFirejailedBinary(pkg, content, dirs)
if tt.expectError {
assert.Error(t, err)
if tt.errorMsg != "" {
assert.Contains(t, err.Error(), tt.errorMsg)
}
assert.Nil(t, result)
} else {
assert.NoError(t, err)
assert.Len(t, result, 2)
binContent := result[0]
assert.Contains(t, binContent.Destination, "usr/lib/alr/firejailed/")
assert.FileExists(t, binContent.Source)
profileContent := result[1]
assert.Contains(t, profileContent.Destination, "usr/lib/alr/firejailed/")
assert.Contains(t, profileContent.Destination, ".profile")
assert.FileExists(t, profileContent.Source)
assert.FileExists(t, content.Source)
wrapperBytes, err := os.ReadFile(content.Source)
assert.NoError(t, err)
wrapper := string(wrapperBytes)
assert.Contains(t, wrapper, "#!/bin/bash")
assert.Contains(t, wrapper, "firejail --profile=")
}
})
}
}

View File

@ -0,0 +1,54 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package build
import (
"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager"
)
func NewInstaller(mgr manager.Manager) *Installer {
return &Installer{
mgr: mgr,
}
}
type Installer struct{ mgr manager.Manager }
func (i *Installer) InstallLocal(paths []string, opts *manager.Opts) error {
return i.mgr.InstallLocal(opts, paths...)
}
func (i *Installer) Install(pkgs []string, opts *manager.Opts) error {
return i.mgr.Install(opts, pkgs...)
}
func (i *Installer) RemoveAlreadyInstalled(pkgs []string) ([]string, error) {
filteredPackages := []string{}
for _, dep := range pkgs {
installed, err := i.mgr.IsInstalled(dep)
if err != nil {
return nil, err
}
if installed {
continue
}
filteredPackages = append(filteredPackages, dep)
}
return filteredPackages, nil
}

View File

@ -0,0 +1,52 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package build
import (
"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager"
)
func NewMainBuilder(
cfg Config,
mgr manager.Manager,
repos PackageFinder,
scriptExecutor ScriptExecutor,
installerExecutor InstallerExecutor,
) (*Builder, error) {
builder := &Builder{
scriptExecutor: scriptExecutor,
cacheExecutor: &Cache{
cfg,
},
scriptResolver: &ScriptResolver{
cfg,
},
scriptViewerExecutor: &ScriptViewer{
config: cfg,
},
checkerExecutor: &Checker{
mgr,
},
installerExecutor: installerExecutor,
sourceExecutor: &SourceDownloader{
cfg,
},
repos: repos,
}
return builder, nil
}

View File

@ -0,0 +1,40 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package build
import (
"os"
"os/exec"
"strings"
)
func setCommonCmdEnv(cmd *exec.Cmd) {
cmd.Env = []string{
"HOME=/var/cache/alr",
"LOGNAME=alr",
"USER=alr",
"PATH=/usr/bin:/bin:/usr/local/bin",
}
for _, env := range os.Environ() {
if strings.HasPrefix(env, "LANG=") ||
strings.HasPrefix(env, "LANGUAGE=") ||
strings.HasPrefix(env, "LC_") ||
strings.HasPrefix(env, "ALR_LOG_LEVEL=") {
cmd.Env = append(cmd.Env, env)
}
}
}

View File

@ -0,0 +1,150 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package build
import (
"fmt"
"log/slog"
"net/rpc"
"os"
"os/exec"
"sync"
"syscall"
"github.com/hashicorp/go-plugin"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/logger"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager"
)
type InstallerPlugin struct {
Impl InstallerExecutor
}
type InstallerRPC struct {
client *rpc.Client
}
type InstallerRPCServer struct {
Impl InstallerExecutor
}
type InstallArgs struct {
PackagesOrPaths []string
Opts *manager.Opts
}
func (r *InstallerRPC) InstallLocal(paths []string, opts *manager.Opts) error {
return r.client.Call("Plugin.InstallLocal", &InstallArgs{
PackagesOrPaths: paths,
Opts: opts,
}, nil)
}
func (s *InstallerRPCServer) InstallLocal(args *InstallArgs, reply *struct{}) error {
return s.Impl.InstallLocal(args.PackagesOrPaths, args.Opts)
}
func (r *InstallerRPC) Install(pkgs []string, opts *manager.Opts) error {
return r.client.Call("Plugin.Install", &InstallArgs{
PackagesOrPaths: pkgs,
Opts: opts,
}, nil)
}
func (s *InstallerRPCServer) Install(args *InstallArgs, reply *struct{}) error {
return s.Impl.Install(args.PackagesOrPaths, args.Opts)
}
func (r *InstallerRPC) RemoveAlreadyInstalled(paths []string) ([]string, error) {
var val []string
err := r.client.Call("Plugin.RemoveAlreadyInstalled", paths, &val)
return val, err
}
func (s *InstallerRPCServer) RemoveAlreadyInstalled(pkgs []string, res *[]string) error {
vars, err := s.Impl.RemoveAlreadyInstalled(pkgs)
if err != nil {
return err
}
*res = vars
return nil
}
func (p *InstallerPlugin) Client(b *plugin.MuxBroker, c *rpc.Client) (interface{}, error) {
return &InstallerRPC{client: c}, nil
}
func (p *InstallerPlugin) Server(*plugin.MuxBroker) (interface{}, error) {
return &InstallerRPCServer{Impl: p.Impl}, nil
}
func GetSafeInstaller() (InstallerExecutor, func(), error) {
var err error
executable, err := os.Executable()
if err != nil {
return nil, nil, err
}
cmd := exec.Command(executable, "_internal-installer")
setCommonCmdEnv(cmd)
slog.Debug("safe installer setup", "uid", syscall.Getuid(), "gid", syscall.Getgid())
client := plugin.NewClient(&plugin.ClientConfig{
HandshakeConfig: HandshakeConfig,
Plugins: pluginMap,
Cmd: cmd,
Logger: logger.GetHCLoggerAdapter(),
SkipHostEnv: true,
UnixSocketConfig: &plugin.UnixSocketConfig{
Group: "alr",
},
SyncStderr: os.Stderr,
})
rpcClient, err := client.Client()
if err != nil {
return nil, nil, err
}
var cleanupOnce sync.Once
cleanup := func() {
cleanupOnce.Do(func() {
client.Kill()
})
}
defer func() {
if err != nil {
slog.Debug("close installer")
cleanup()
}
}()
raw, err := rpcClient.Dispense("installer")
if err != nil {
return nil, nil, err
}
executor, ok := raw.(InstallerExecutor)
if !ok {
err = fmt.Errorf("dispensed object is not a ScriptExecutor (got %T)", raw)
return nil, nil, err
}
return executor, cleanup, nil
}

View File

@ -0,0 +1,273 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package build
import (
"context"
"fmt"
"log/slog"
"net/rpc"
"os"
"os/exec"
"sync"
"github.com/hashicorp/go-plugin"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/logger"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
)
var HandshakeConfig = plugin.HandshakeConfig{
ProtocolVersion: 1,
MagicCookieKey: "ALR_PLUGIN",
MagicCookieValue: "-",
}
type ScriptExecutorPlugin struct {
Impl ScriptExecutor
}
type ScriptExecutorRPCServer struct {
Impl ScriptExecutor
}
// =============================
//
// ReadScript
//
func (s *ScriptExecutorRPC) ReadScript(ctx context.Context, scriptPath string) (*alrsh.ScriptFile, error) {
var resp *alrsh.ScriptFile
err := s.client.Call("Plugin.ReadScript", scriptPath, &resp)
return resp, err
}
func (s *ScriptExecutorRPCServer) ReadScript(scriptPath string, resp *alrsh.ScriptFile) error {
file, err := s.Impl.ReadScript(context.Background(), scriptPath)
if err != nil {
return err
}
*resp = *file
return nil
}
// =============================
//
// ExecuteFirstPass
//
type ExecuteFirstPassArgs struct {
Input *BuildInput
Sf *alrsh.ScriptFile
}
type ExecuteFirstPassResp struct {
BasePkg string
VarsOfPackages []*alrsh.Package
}
func (s *ScriptExecutorRPC) ExecuteFirstPass(ctx context.Context, input *BuildInput, sf *alrsh.ScriptFile) (string, []*alrsh.Package, error) {
var resp *ExecuteFirstPassResp
err := s.client.Call("Plugin.ExecuteFirstPass", &ExecuteFirstPassArgs{
Input: input,
Sf: sf,
}, &resp)
if err != nil {
return "", nil, err
}
return resp.BasePkg, resp.VarsOfPackages, nil
}
func (s *ScriptExecutorRPCServer) ExecuteFirstPass(args *ExecuteFirstPassArgs, resp *ExecuteFirstPassResp) error {
basePkg, varsOfPackages, err := s.Impl.ExecuteFirstPass(context.Background(), args.Input, args.Sf)
if err != nil {
return err
}
*resp = ExecuteFirstPassResp{
BasePkg: basePkg,
VarsOfPackages: varsOfPackages,
}
return nil
}
// =============================
//
// PrepareDirs
//
type PrepareDirsArgs struct {
Input *BuildInput
BasePkg string
}
func (s *ScriptExecutorRPC) PrepareDirs(
ctx context.Context,
input *BuildInput,
basePkg string,
) error {
err := s.client.Call("Plugin.PrepareDirs", &PrepareDirsArgs{
Input: input,
BasePkg: basePkg,
}, nil)
if err != nil {
return err
}
return err
}
func (s *ScriptExecutorRPCServer) PrepareDirs(args *PrepareDirsArgs, reply *struct{}) error {
err := s.Impl.PrepareDirs(
context.Background(),
args.Input,
args.BasePkg,
)
if err != nil {
return err
}
return err
}
// =============================
//
// ExecuteSecondPass
//
type ExecuteSecondPassArgs struct {
Input *BuildInput
Sf *alrsh.ScriptFile
VarsOfPackages []*alrsh.Package
RepoDeps []string
BuiltDeps []*BuiltDep
BasePkg string
}
func (s *ScriptExecutorRPC) ExecuteSecondPass(
ctx context.Context,
input *BuildInput,
sf *alrsh.ScriptFile,
varsOfPackages []*alrsh.Package,
repoDeps []string,
builtDeps []*BuiltDep,
basePkg string,
) ([]*BuiltDep, error) {
var resp []*BuiltDep
err := s.client.Call("Plugin.ExecuteSecondPass", &ExecuteSecondPassArgs{
Input: input,
Sf: sf,
VarsOfPackages: varsOfPackages,
RepoDeps: repoDeps,
BuiltDeps: builtDeps,
BasePkg: basePkg,
}, &resp)
if err != nil {
return nil, err
}
return resp, nil
}
func (s *ScriptExecutorRPCServer) ExecuteSecondPass(args *ExecuteSecondPassArgs, resp *[]*BuiltDep) error {
res, err := s.Impl.ExecuteSecondPass(
context.Background(),
args.Input,
args.Sf,
args.VarsOfPackages,
args.RepoDeps,
args.BuiltDeps,
args.BasePkg,
)
if err != nil {
return err
}
*resp = res
return err
}
//
// ============================
//
func (p *ScriptExecutorPlugin) Server(*plugin.MuxBroker) (interface{}, error) {
return &ScriptExecutorRPCServer{Impl: p.Impl}, nil
}
func (p *ScriptExecutorPlugin) Client(b *plugin.MuxBroker, c *rpc.Client) (interface{}, error) {
return &ScriptExecutorRPC{client: c}, nil
}
type ScriptExecutorRPC struct {
client *rpc.Client
}
var pluginMap = map[string]plugin.Plugin{
"script-executor": &ScriptExecutorPlugin{},
"installer": &InstallerPlugin{},
}
func GetSafeScriptExecutor() (ScriptExecutor, func(), error) {
var err error
executable, err := os.Executable()
if err != nil {
return nil, nil, err
}
cmd := exec.Command(executable, "_internal-safe-script-executor")
setCommonCmdEnv(cmd)
client := plugin.NewClient(&plugin.ClientConfig{
HandshakeConfig: HandshakeConfig,
Plugins: pluginMap,
Cmd: cmd,
Logger: logger.GetHCLoggerAdapter(),
SkipHostEnv: true,
UnixSocketConfig: &plugin.UnixSocketConfig{
Group: "alr",
},
SyncStderr: os.Stderr,
})
rpcClient, err := client.Client()
if err != nil {
return nil, nil, err
}
var cleanupOnce sync.Once
cleanup := func() {
cleanupOnce.Do(func() {
client.Kill()
})
}
defer func() {
if err != nil {
slog.Debug("close script-executor")
cleanup()
}
}()
raw, err := rpcClient.Dispense("script-executor")
if err != nil {
return nil, nil, err
}
executor, ok := raw.(ScriptExecutor)
if !ok {
err = fmt.Errorf("dispensed object is not a ScriptExecutor (got %T)", raw)
return nil, nil, err
}
return executor, cleanup, nil
}

View File

@ -0,0 +1,364 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package build
import (
"bytes"
"context"
"fmt"
"log/slog"
"os"
"path/filepath"
"slices"
"strconv"
"strings"
"time"
"github.com/google/shlex"
"github.com/goreleaser/nfpm/v2"
"github.com/leonelquinteros/gotext"
"mvdan.cc/sh/v3/expand"
"mvdan.cc/sh/v3/interp"
"mvdan.cc/sh/v3/syntax"
finddeps "gitea.plemya-x.ru/Plemya-x/ALR/internal/build/find_deps"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/decoder"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/handlers"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/shutils/helpers"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
)
type LocalScriptExecutor struct {
cfg Config
}
func NewLocalScriptExecutor(cfg Config) *LocalScriptExecutor {
return &LocalScriptExecutor{
cfg,
}
}
func (e *LocalScriptExecutor) ReadScript(ctx context.Context, scriptPath string) (*alrsh.ScriptFile, error) {
return alrsh.ReadFromLocal(scriptPath)
}
func (e *LocalScriptExecutor) ExecuteFirstPass(ctx context.Context, input *BuildInput, sf *alrsh.ScriptFile) (string, []*alrsh.Package, error) {
return sf.ParseBuildVars(ctx, input.info, input.packages)
}
func (e *LocalScriptExecutor) PrepareDirs(
ctx context.Context,
input *BuildInput,
basePkg string,
) error {
dirs, err := getDirs(
e.cfg,
input.script,
basePkg,
)
if err != nil {
return err
}
err = prepareDirs(dirs)
if err != nil {
return err
}
return nil
}
func (e *LocalScriptExecutor) ExecuteSecondPass(
ctx context.Context,
input *BuildInput,
sf *alrsh.ScriptFile,
varsOfPackages []*alrsh.Package,
repoDeps []string,
builtDeps []*BuiltDep,
basePkg string,
) ([]*BuiltDep, error) {
dirs, err := getDirs(e.cfg, sf.Path(), basePkg)
if err != nil {
return nil, err
}
env := createBuildEnvVars(input.info, dirs)
fakeroot := handlers.FakerootExecHandler(2 * time.Second)
runner, err := interp.New(
interp.Env(expand.ListEnviron(env...)), // Устанавливаем окружение
interp.StdIO(os.Stdin, os.Stderr, os.Stderr), // Устанавливаем стандартный ввод-вывод
interp.ExecHandlers(func(next interp.ExecHandlerFunc) interp.ExecHandlerFunc {
return helpers.Helpers.ExecHandler(fakeroot)
}), // Обрабатываем выполнение через fakeroot
)
if err != nil {
return nil, err
}
err = runner.Run(ctx, sf.File())
if err != nil {
return nil, err
}
dec := decoder.New(input.info, runner)
// var builtPaths []string
err = e.ExecuteFunctions(ctx, dirs, dec)
if err != nil {
return nil, err
}
for _, vars := range varsOfPackages {
packageName := ""
if vars.BasePkgName != "" {
packageName = vars.Name
}
pkgFormat := input.pkgFormat
funcOut, err := e.ExecutePackageFunctions(
ctx,
dec,
dirs,
packageName,
)
if err != nil {
return nil, err
}
slog.Info(gotext.Get("Building package metadata"), "name", basePkg)
pkgInfo, err := buildPkgMetadata(
ctx,
input,
vars,
dirs,
append(
repoDeps,
GetBuiltName(builtDeps)...,
),
funcOut.Contents,
)
if err != nil {
return nil, err
}
packager, err := nfpm.Get(pkgFormat) // Получаем упаковщик для формата пакета
if err != nil {
return nil, err
}
pkgName := packager.ConventionalFileName(pkgInfo) // Получаем имя файла пакета
pkgPath := filepath.Join(dirs.BaseDir, pkgName) // Определяем путь к пакету
pkgFile, err := os.Create(pkgPath)
if err != nil {
return nil, err
}
err = packager.Package(pkgInfo, pkgFile)
if err != nil {
return nil, err
}
builtDeps = append(builtDeps, &BuiltDep{
Name: vars.Name,
Path: pkgPath,
})
}
return builtDeps, nil
}
func buildPkgMetadata(
ctx context.Context,
input interface {
OsInfoProvider
BuildOptsProvider
PkgFormatProvider
RepositoryProvider
},
vars *alrsh.Package,
dirs types.Directories,
deps []string,
preferedContents *[]string,
) (*nfpm.Info, error) {
pkgInfo := getBasePkgInfo(vars, input)
pkgInfo.Description = vars.Description.Resolved()
pkgInfo.Platform = "linux"
pkgInfo.Homepage = vars.Homepage.Resolved()
pkgInfo.License = strings.Join(vars.Licenses, ", ")
pkgInfo.Maintainer = vars.Maintainer.Resolved()
pkgInfo.Overridables = nfpm.Overridables{
Conflicts: append(vars.Conflicts, vars.Name),
Replaces: vars.Replaces,
Provides: append(vars.Provides, vars.Name),
Depends: deps,
}
pkgInfo.Section = vars.Group.Resolved()
pkgFormat := input.PkgFormat()
info := input.OSRelease()
if pkgFormat == "apk" {
// Alpine отказывается устанавливать пакеты, которые предоставляют сами себя, поэтому удаляем такие элементы
pkgInfo.Overridables.Provides = slices.DeleteFunc(pkgInfo.Overridables.Provides, func(s string) bool {
return s == pkgInfo.Name
})
}
if pkgFormat == "rpm" {
pkgInfo.RPM.Group = vars.Group.Resolved()
if vars.Summary.Resolved() != "" {
pkgInfo.RPM.Summary = vars.Summary.Resolved()
} else {
lines := strings.SplitN(vars.Description.Resolved(), "\n", 2)
pkgInfo.RPM.Summary = lines[0]
}
}
if vars.Epoch != 0 {
pkgInfo.Epoch = strconv.FormatUint(uint64(vars.Epoch), 10)
}
setScripts(vars, pkgInfo, dirs.ScriptDir)
if slices.Contains(vars.Architectures, "all") {
pkgInfo.Arch = "all"
}
contents, err := buildContents(vars, dirs, preferedContents)
if err != nil {
return nil, err
}
normalizeContents(contents)
if vars.FireJailed.Resolved() {
contents, err = applyFirejailIntegration(vars, dirs, contents)
if err != nil {
return nil, err
}
}
pkgInfo.Overridables.Contents = contents
if len(vars.AutoProv.Resolved()) == 1 && decoder.IsTruthy(vars.AutoProv.Resolved()[0]) {
f := finddeps.New(info, pkgFormat)
err = f.FindProvides(ctx, pkgInfo, dirs, vars.AutoProvSkipList.Resolved())
if err != nil {
return nil, err
}
}
if len(vars.AutoReq.Resolved()) == 1 && decoder.IsTruthy(vars.AutoReq.Resolved()[0]) {
f := finddeps.New(info, pkgFormat)
err = f.FindRequires(ctx, pkgInfo, dirs, vars.AutoReqSkipList.Resolved())
if err != nil {
return nil, err
}
}
return pkgInfo, nil
}
func (e *LocalScriptExecutor) ExecuteFunctions(ctx context.Context, dirs types.Directories, dec *decoder.Decoder) error {
prepare, ok := dec.GetFunc("prepare")
if ok {
slog.Info(gotext.Get("Executing prepare()"))
err := prepare(ctx, interp.Dir(dirs.SrcDir))
if err != nil {
return err
}
}
build, ok := dec.GetFunc("build")
if ok {
slog.Info(gotext.Get("Executing build()"))
err := build(ctx, interp.Dir(dirs.SrcDir))
if err != nil {
return err
}
}
return nil
}
func (e *LocalScriptExecutor) ExecutePackageFunctions(
ctx context.Context,
dec *decoder.Decoder,
dirs types.Directories,
packageName string,
) (*FunctionsOutput, error) {
output := &FunctionsOutput{}
var packageFuncName string
var filesFuncName string
if packageName == "" {
packageFuncName = "package"
filesFuncName = "files"
} else {
packageFuncName = fmt.Sprintf("package_%s", packageName)
filesFuncName = fmt.Sprintf("files_%s", packageName)
}
packageFn, ok := dec.GetFunc(packageFuncName)
if ok {
slog.Info(gotext.Get("Executing %s()", packageFuncName))
err := packageFn(ctx, interp.Dir(dirs.SrcDir))
if err != nil {
return nil, err
}
}
files, ok := dec.GetFuncP(filesFuncName, func(ctx context.Context, s *interp.Runner) error {
// It should be done via interp.RunnerOption,
// but due to the issues below, it cannot be done.
// - https://github.com/mvdan/sh/issues/962
// - https://github.com/mvdan/sh/issues/1125
script, err := syntax.NewParser().Parse(strings.NewReader("cd $pkgdir && shopt -s globstar"), "")
if err != nil {
return err
}
return s.Run(ctx, script)
})
if ok {
slog.Info(gotext.Get("Executing %s()", filesFuncName))
buf := &bytes.Buffer{}
err := files(
ctx,
interp.Dir(dirs.PkgDir),
interp.StdIO(os.Stdin, buf, os.Stderr),
)
if err != nil {
return nil, err
}
contents, err := shlex.Split(buf.String())
if err != nil {
return nil, err
}
output.Contents = &contents
}
return output, nil
}

View File

@ -0,0 +1,65 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package build
import (
"context"
"os"
"path/filepath"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
)
type ScriptResolver struct {
cfg Config
}
type ScriptInfo struct {
Script string
Repository string
}
func (s *ScriptResolver) ResolveScript(
ctx context.Context,
pkg *alrsh.Package,
) *ScriptInfo {
var repository, script string
repodir := s.cfg.GetPaths().RepoDir
repository = pkg.Repository
// First, we check if there is a root alr.sh in the repository
rootScriptPath := filepath.Join(repodir, repository, "alr.sh")
if _, err := os.Stat(rootScriptPath); err == nil {
// A repository with a single alr.sh at the root
script = rootScriptPath
} else {
// Multi-package repository - we are looking for alr.sh in the subfolder
var scriptPath string
if pkg.BasePkgName != "" {
scriptPath = filepath.Join(repodir, repository, pkg.BasePkgName, "alr.sh")
} else {
scriptPath = filepath.Join(repodir, repository, pkg.Name, "alr.sh")
}
script = scriptPath
}
return &ScriptInfo{
Repository: repository,
Script: script,
}
}

View File

@ -0,0 +1,47 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package build
import (
"context"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
)
type ScriptViewerConfig interface {
PagerStyle() string
}
type ScriptViewer struct {
config ScriptViewerConfig
}
func (s *ScriptViewer) ViewScript(
ctx context.Context,
input *BuildInput,
a *alrsh.ScriptFile,
basePkg string,
) error {
return cliutils.PromptViewScript(
ctx,
a.Path(),
basePkg,
s.config.PagerStyle(),
input.opts.Interactive,
)
}

View File

@ -0,0 +1,86 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package build
import (
"context"
"encoding/hex"
"fmt"
"os"
"strings"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/dl"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/dlcache"
)
type SourceDownloader struct {
cfg Config
}
func NewSourceDownloader(cfg Config) *SourceDownloader {
return &SourceDownloader{
cfg,
}
}
func (s *SourceDownloader) DownloadSources(
ctx context.Context,
input *BuildInput,
basePkg string,
si SourcesInput,
) error {
for i, src := range si.Sources {
opts := dl.Options{
Name: fmt.Sprintf("[%d]", i),
URL: src,
Destination: getSrcDir(s.cfg, basePkg),
Progress: os.Stderr,
LocalDir: getScriptDir(input.script),
}
if !strings.EqualFold(si.Checksums[i], "SKIP") {
// Если контрольная сумма содержит двоеточие, используйте часть до двоеточия
// как алгоритм, а часть после как фактическую контрольную сумму.
// В противном случае используйте sha256 по умолчанию с целой строкой как контрольной суммой.
algo, hashData, ok := strings.Cut(si.Checksums[i], ":")
if ok {
checksum, err := hex.DecodeString(hashData)
if err != nil {
return err
}
opts.Hash = checksum
opts.HashAlgorithm = algo
} else {
checksum, err := hex.DecodeString(si.Checksums[i])
if err != nil {
return err
}
opts.Hash = checksum
}
}
opts.DlCache = dlcache.New(s.cfg)
err := dl.Download(ctx, opts)
if err != nil {
return err
}
}
return nil
}

314
internal/build/utils.go Normal file
View File

@ -0,0 +1,314 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package build
import (
"fmt"
"io"
"os"
"path/filepath"
"regexp"
"runtime"
"slices"
"strconv"
"strings"
// Импортируем пакеты для поддержки различных форматов пакетов (APK, DEB, RPM и ARCH).
_ "github.com/goreleaser/nfpm/v2/apk"
_ "github.com/goreleaser/nfpm/v2/arch"
_ "github.com/goreleaser/nfpm/v2/deb"
_ "github.com/goreleaser/nfpm/v2/rpm"
"github.com/goreleaser/nfpm/v2"
"github.com/goreleaser/nfpm/v2/files"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cpu"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/overrides"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
)
// Функция prepareDirs подготавливает директории для сборки.
func prepareDirs(dirs types.Directories) error {
err := os.RemoveAll(dirs.BaseDir) // Удаляем базовую директорию, если она существует
if err != nil {
return err
}
err = os.MkdirAll(dirs.SrcDir, 0o755) // Создаем директорию для источников
if err != nil {
return err
}
return os.MkdirAll(dirs.PkgDir, 0o755) // Создаем директорию для пакетов
}
// Функция buildContents создает секцию содержимого пакета, которая содержит файлы,
// которые будут включены в конечный пакет.
func buildContents(vars *alrsh.Package, dirs types.Directories, preferedContents *[]string) ([]*files.Content, error) {
contents := []*files.Content{}
processPath := func(path, trimmed string, prefered bool) error {
fi, err := os.Lstat(path)
if err != nil {
return err
}
if fi.IsDir() {
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close()
if !prefered {
_, err = f.Readdirnames(1)
if err != io.EOF {
return nil
}
}
contents = append(contents, &files.Content{
Source: path,
Destination: trimmed,
Type: "dir",
FileInfo: &files.ContentFileInfo{
MTime: fi.ModTime(),
},
})
return nil
}
if fi.Mode()&os.ModeSymlink != 0 {
link, err := os.Readlink(path)
if err != nil {
return err
}
link = strings.TrimPrefix(link, dirs.PkgDir)
contents = append(contents, &files.Content{
Source: link,
Destination: trimmed,
Type: "symlink",
FileInfo: &files.ContentFileInfo{
MTime: fi.ModTime(),
Mode: fi.Mode(),
},
})
return nil
}
fileContent := &files.Content{
Source: path,
Destination: trimmed,
FileInfo: &files.ContentFileInfo{
MTime: fi.ModTime(),
Mode: fi.Mode(),
Size: fi.Size(),
},
}
if slices.Contains(vars.Backup.Resolved(), trimmed) {
fileContent.Type = "config|noreplace"
}
contents = append(contents, fileContent)
return nil
}
if preferedContents != nil {
for _, trimmed := range *preferedContents {
path := filepath.Join(dirs.PkgDir, trimmed)
if err := processPath(path, trimmed, true); err != nil {
return nil, err
}
}
} else {
err := filepath.Walk(dirs.PkgDir, func(path string, fi os.FileInfo, err error) error {
if err != nil {
return err
}
trimmed := strings.TrimPrefix(path, dirs.PkgDir)
return processPath(path, trimmed, false)
})
if err != nil {
return nil, err
}
}
return contents, nil
}
func normalizeContents(contents []*files.Content) {
for _, content := range contents {
content.Destination = filepath.Join("/", content.Destination)
}
}
var RegexpALRPackageName = regexp.MustCompile(`^(?P<package>[^+]+)\+alr-(?P<repo>.+)$`)
func getBasePkgInfo(vars *alrsh.Package, input interface {
RepositoryProvider
OsInfoProvider
},
) *nfpm.Info {
return &nfpm.Info{
Name: fmt.Sprintf("%s+alr-%s", vars.Name, input.Repository()),
Arch: cpu.Arch(),
Version: vars.Version,
Release: overrides.ReleasePlatformSpecific(vars.Release, input.OSRelease()),
Epoch: strconv.FormatUint(uint64(vars.Epoch), 10),
}
}
// Функция getPkgFormat возвращает формат пакета из менеджера пакетов,
// или ALR_PKG_FORMAT, если он установлен.
func GetPkgFormat(mgr manager.Manager) string {
pkgFormat := mgr.Format()
if format, ok := os.LookupEnv("ALR_PKG_FORMAT"); ok {
pkgFormat = format
}
return pkgFormat
}
// Функция createBuildEnvVars создает переменные окружения, которые будут установлены
// в скрипте сборки при его выполнении.
func createBuildEnvVars(info *distro.OSRelease, dirs types.Directories) []string {
env := os.Environ()
env = append(
env,
"DISTRO_NAME="+info.Name,
"DISTRO_PRETTY_NAME="+info.PrettyName,
"DISTRO_ID="+info.ID,
"DISTRO_VERSION_ID="+info.VersionID,
"DISTRO_ID_LIKE="+strings.Join(info.Like, " "),
"ARCH="+cpu.Arch(),
"NCPU="+strconv.Itoa(runtime.NumCPU()),
)
if dirs.ScriptDir != "" {
env = append(env, "scriptdir="+dirs.ScriptDir)
}
if dirs.PkgDir != "" {
env = append(env, "pkgdir="+dirs.PkgDir)
}
if dirs.SrcDir != "" {
env = append(env, "srcdir="+dirs.SrcDir)
}
return env
}
// Функция setScripts добавляет скрипты-перехватчики к метаданным пакета.
func setScripts(vars *alrsh.Package, info *nfpm.Info, scriptDir string) {
if vars.Scripts.Resolved().PreInstall != "" {
info.Scripts.PreInstall = filepath.Join(scriptDir, vars.Scripts.Resolved().PreInstall)
}
if vars.Scripts.Resolved().PostInstall != "" {
info.Scripts.PostInstall = filepath.Join(scriptDir, vars.Scripts.Resolved().PostInstall)
}
if vars.Scripts.Resolved().PreRemove != "" {
info.Scripts.PreRemove = filepath.Join(scriptDir, vars.Scripts.Resolved().PreRemove)
}
if vars.Scripts.Resolved().PostRemove != "" {
info.Scripts.PostRemove = filepath.Join(scriptDir, vars.Scripts.Resolved().PostRemove)
}
if vars.Scripts.Resolved().PreUpgrade != "" {
info.ArchLinux.Scripts.PreUpgrade = filepath.Join(scriptDir, vars.Scripts.Resolved().PreUpgrade)
info.APK.Scripts.PreUpgrade = filepath.Join(scriptDir, vars.Scripts.Resolved().PreUpgrade)
}
if vars.Scripts.Resolved().PostUpgrade != "" {
info.ArchLinux.Scripts.PostUpgrade = filepath.Join(scriptDir, vars.Scripts.Resolved().PostUpgrade)
info.APK.Scripts.PostUpgrade = filepath.Join(scriptDir, vars.Scripts.Resolved().PostUpgrade)
}
if vars.Scripts.Resolved().PreTrans != "" {
info.RPM.Scripts.PreTrans = filepath.Join(scriptDir, vars.Scripts.Resolved().PreTrans)
}
if vars.Scripts.Resolved().PostTrans != "" {
info.RPM.Scripts.PostTrans = filepath.Join(scriptDir, vars.Scripts.Resolved().PostTrans)
}
}
/*
// Функция setVersion изменяет переменную версии в скрипте runner.
// Она используется для установки версии на вывод функции version().
func setVersion(ctx context.Context, r *interp.Runner, to string) error {
fl, err := syntax.NewParser().Parse(strings.NewReader("version='"+to+"'"), "")
if err != nil {
return err
}
return r.Run(ctx, fl)
}
*/
// Функция packageNames возвращает имена всех предоставленных пакетов.
/*
func packageNames(pkgs []db.Package) []string {
names := make([]string, len(pkgs))
for i, p := range pkgs {
names[i] = p.Name
}
return names
}
*/
// Функция removeDuplicates убирает любые дубликаты из предоставленного среза.
func removeDuplicates[T comparable](slice []T) []T {
seen := map[T]struct{}{}
result := []T{}
for _, item := range slice {
if _, ok := seen[item]; !ok {
seen[item] = struct{}{}
result = append(result, item)
}
}
return result
}
func removeDuplicatesSources(sources, checksums []string) ([]string, []string) {
seen := map[string]string{}
keys := make([]string, 0)
for i, s := range sources {
if val, ok := seen[s]; !ok || strings.EqualFold(val, "SKIP") {
if !ok {
keys = append(keys, s)
}
seen[s] = checksums[i]
}
}
newSources := make([]string, len(keys))
newChecksums := make([]string, len(keys))
for i, k := range keys {
newSources[i] = k
newChecksums[i] = seen[k]
}
return newSources, newChecksums
}

View File

@ -0,0 +1,47 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package build
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestRemoveDuplicatesSources(t *testing.T) {
type testCase struct {
Name string
Sources []string
Checksums []string
NewSources []string
NewChecksums []string
}
for _, tc := range []testCase{{
Name: "prefer non-skip values",
Sources: []string{"a", "b", "c", "a"},
Checksums: []string{"skip", "skip", "skip", "1"},
NewSources: []string{"a", "b", "c"},
NewChecksums: []string{"1", "skip", "skip"},
}} {
t.Run(tc.Name, func(t *testing.T) {
s, c := removeDuplicatesSources(tc.Sources, tc.Checksums)
assert.Equal(t, s, tc.NewSources)
assert.Equal(t, c, tc.NewChecksums)
})
}
}

View File

@ -0,0 +1,183 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package appbuilder
import (
"context"
"errors"
"log/slog"
"github.com/leonelquinteros/gotext"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cliutils"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/manager"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/repos"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
)
type AppDeps struct {
Cfg *config.ALRConfig
DB *db.Database
Repos *repos.Repos
Info *distro.OSRelease
Manager manager.Manager
}
func (d *AppDeps) Defer() {
if d.DB != nil {
if err := d.DB.Close(); err != nil {
slog.Warn("failed to close db", "err", err)
}
}
}
type AppBuilder struct {
deps AppDeps
err error
ctx context.Context
}
func New(ctx context.Context) *AppBuilder {
return &AppBuilder{ctx: ctx}
}
func (b *AppBuilder) UseConfig(cfg *config.ALRConfig) *AppBuilder {
if b.err != nil {
return b
}
b.deps.Cfg = cfg
return b
}
func (b *AppBuilder) WithConfig() *AppBuilder {
if b.err != nil {
return b
}
cfg := config.New()
if err := cfg.Load(); err != nil {
b.err = cliutils.FormatCliExit(gotext.Get("Error loading config"), err)
return b
}
b.deps.Cfg = cfg
return b
}
func (b *AppBuilder) WithDB() *AppBuilder {
if b.err != nil {
return b
}
cfg := b.deps.Cfg
if cfg == nil {
b.err = errors.New("config is required before initializing DB")
return b
}
db := db.New(cfg)
if err := db.Init(b.ctx); err != nil {
b.err = cliutils.FormatCliExit(gotext.Get("Error initialization database"), err)
return b
}
b.deps.DB = db
return b
}
func (b *AppBuilder) WithRepos() *AppBuilder {
b.withRepos(true, false)
return b
}
func (b *AppBuilder) WithReposForcePull() *AppBuilder {
b.withRepos(true, true)
return b
}
func (b *AppBuilder) WithReposNoPull() *AppBuilder {
b.withRepos(false, false)
return b
}
func (b *AppBuilder) withRepos(enablePull, forcePull bool) *AppBuilder {
if b.err != nil {
return b
}
cfg := b.deps.Cfg
db := b.deps.DB
info := b.deps.Info
if info == nil {
b.WithDistroInfo()
info = b.deps.Info
}
if cfg == nil || db == nil || info == nil {
b.err = errors.New("config, db and info are required before initializing repos")
return b
}
rs := repos.New(cfg, db)
if enablePull && (forcePull || cfg.AutoPull()) {
if err := rs.Pull(b.ctx, cfg.Repos()); err != nil {
b.err = cliutils.FormatCliExit(gotext.Get("Error pulling repositories"), err)
return b
}
}
b.deps.Repos = rs
return b
}
func (b *AppBuilder) WithDistroInfo() *AppBuilder {
if b.err != nil {
return b
}
b.deps.Info, b.err = distro.ParseOSRelease(b.ctx)
if b.err != nil {
b.err = cliutils.FormatCliExit(gotext.Get("Error parsing os release"), b.err)
}
return b
}
func (b *AppBuilder) WithManager() *AppBuilder {
if b.err != nil {
return b
}
b.deps.Manager = manager.Detect()
if b.deps.Manager == nil {
b.err = cliutils.FormatCliExit(gotext.Get("Unable to detect a supported package manager on the system"), nil)
}
return b
}
func (b *AppBuilder) Build() (*AppDeps, error) {
if b.err != nil {
return nil, b.err
}
return &b.deps, nil
}

View File

@ -1,34 +1,35 @@
/*
* ALR - Any Linux Repository
* Copyright (C) 2024 Евгений Храмов
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
// It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors.
//
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package cliutils
import (
"context"
"log/slog"
"os"
"strings"
"github.com/AlecAivazis/survey/v2"
"plemya-x.ru/alr/internal/config"
"plemya-x.ru/alr/internal/db"
"plemya-x.ru/alr/internal/pager"
"plemya-x.ru/alr/internal/translations"
"plemya-x.ru/alr/pkg/loggerctx"
"github.com/leonelquinteros/gotext"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/pager"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
)
// YesNoPrompt asks the user a yes or no question, using def as the default answer
@ -37,7 +38,7 @@ func YesNoPrompt(ctx context.Context, msg string, interactive, def bool) (bool,
var answer bool
err := survey.AskOne(
&survey.Confirm{
Message: translations.Translator(ctx).TranslateTo(msg, config.Language(ctx)),
Message: msg,
Default: def,
},
&answer,
@ -52,14 +53,11 @@ func YesNoPrompt(ctx context.Context, msg string, interactive, def bool) (bool,
// shows it if they answer yes, then asks if they'd still like to
// continue, and exits if they answer no.
func PromptViewScript(ctx context.Context, script, name, style string, interactive bool) error {
log := loggerctx.From(ctx)
if !interactive {
return nil
}
scriptPrompt := translations.Translator(ctx).TranslateTo("Would you like to view the build script for", config.Language(ctx)) + " " + name
view, err := YesNoPrompt(ctx, scriptPrompt, interactive, false)
view, err := YesNoPrompt(ctx, gotext.Get("Would you like to view the build script for %s", name), interactive, false)
if err != nil {
return err
}
@ -70,13 +68,14 @@ func PromptViewScript(ctx context.Context, script, name, style string, interacti
return err
}
cont, err := YesNoPrompt(ctx, "Would you still like to continue?", interactive, false)
cont, err := YesNoPrompt(ctx, gotext.Get("Would you still like to continue?"), interactive, false)
if err != nil {
return err
}
if !cont {
log.Fatal(translations.Translator(ctx).TranslateTo("User chose not to continue after reading script", config.Language(ctx))).Send()
slog.Error(gotext.Get("User chose not to continue after reading script"))
os.Exit(1)
}
}
@ -103,14 +102,14 @@ func ShowScript(path, name, style string) error {
// FlattenPkgs attempts to flatten the a map of slices of packages into a single slice
// of packages by prompting the user if multiple packages match.
func FlattenPkgs(ctx context.Context, found map[string][]db.Package, verb string, interactive bool) []db.Package {
log := loggerctx.From(ctx)
var outPkgs []db.Package
func FlattenPkgs(ctx context.Context, found map[string][]alrsh.Package, verb string, interactive bool) []alrsh.Package {
var outPkgs []alrsh.Package
for _, pkgs := range found {
if len(pkgs) > 1 && interactive {
choice, err := PkgPrompt(ctx, pkgs, verb, interactive)
if err != nil {
log.Fatal("Error prompting for choice of package").Send()
slog.Error(gotext.Get("Error prompting for choice of package"))
os.Exit(1)
}
outPkgs = append(outPkgs, choice)
} else if len(pkgs) == 1 || !interactive {
@ -121,7 +120,7 @@ func FlattenPkgs(ctx context.Context, found map[string][]db.Package, verb string
}
// PkgPrompt asks the user to choose between multiple packages.
func PkgPrompt(ctx context.Context, options []db.Package, verb string, interactive bool) (db.Package, error) {
func PkgPrompt(ctx context.Context, options []alrsh.Package, verb string, interactive bool) (alrsh.Package, error) {
if !interactive {
return options[0], nil
}
@ -133,13 +132,13 @@ func PkgPrompt(ctx context.Context, options []db.Package, verb string, interacti
prompt := &survey.Select{
Options: names,
Message: translations.Translator(ctx).TranslateTo("Choose which package to "+verb, config.Language(ctx)),
Message: gotext.Get("Choose which package to %s", verb),
}
var choice int
err := survey.AskOne(prompt, &choice)
if err != nil {
return db.Package{}, err
return alrsh.Package{}, err
}
return options[choice], nil
@ -154,7 +153,7 @@ func ChooseOptDepends(ctx context.Context, options []string, verb string, intera
prompt := &survey.MultiSelect{
Options: options,
Message: translations.Translator(ctx).TranslateTo("Choose which optional package(s) to install", config.Language(ctx)),
Message: gotext.Get("Choose which optional package(s) to install"),
}
var choices []int

View File

@ -0,0 +1,102 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package cliutils
import (
"fmt"
"github.com/leonelquinteros/gotext"
)
// Templates are based on https://github.com/urfave/cli/blob/3b17080d70a630feadadd23dd036cad121dd9a50/template.go
//nolint:unused
var (
helpNameTemplate = `{{$v := offset .HelpName 6}}{{wrap .HelpName 3}}{{if .Usage}} - {{wrap .Usage $v}}{{end}}`
descriptionTemplate = `{{wrap .Description 3}}`
authorsTemplate = `{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}:
{{range $index, $author := .Authors}}{{if $index}}
{{end}}{{$author}}{{end}}`
visibleCommandTemplate = `{{ $cv := offsetCommands .VisibleCommands 5}}{{range .VisibleCommands}}
{{$s := join .Names ", "}}{{$s}}{{ $sp := subtract $cv (offset $s 3) }}{{ indent $sp ""}}{{wrap .Usage $cv}}{{end}}`
visibleCommandCategoryTemplate = `{{range .VisibleCategories}}{{if .Name}}
{{.Name}}:{{range .VisibleCommands}}
{{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{template "visibleCommandTemplate" .}}{{end}}{{end}}`
visibleFlagCategoryTemplate = `{{range .VisibleFlagCategories}}
{{if .Name}}{{.Name}}
{{end}}{{$flglen := len .Flags}}{{range $i, $e := .Flags}}{{if eq (subtract $flglen $i) 1}}{{$e}}
{{else}}{{$e}}
{{end}}{{end}}{{end}}`
visibleFlagTemplate = `{{range $i, $e := .VisibleFlags}}
{{wrap $e.String 6}}{{end}}`
copyrightTemplate = `{{wrap .Copyright 3}}`
)
func GetAppCliTemplate() string {
return fmt.Sprintf(`%s:
{{template "helpNameTemplate" .}}
%s:
{{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[%s]{{end}}{{if .Commands}} %s [%s]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[%s...]{{end}}{{end}}{{if .Version}}{{if not .HideVersion}}
%s:
{{.Version}}{{end}}{{end}}{{if .Description}}
%s:
{{template "descriptionTemplate" .}}{{end}}
{{- if len .Authors}}
%s{{template "authorsTemplate" .}}{{end}}{{if .VisibleCommands}}
%s:{{template "visibleCommandCategoryTemplate" .}}{{end}}{{if .VisibleFlagCategories}}
%s:{{template "visibleFlagCategoryTemplate" .}}{{else if .VisibleFlags}}
%s:{{template "visibleFlagTemplate" .}}{{end}}{{if .Copyright}}
%s:
{{template "copyrightTemplate" .}}{{end}}
`, gotext.Get("NAME"), gotext.Get("USAGE"), gotext.Get("global options"), gotext.Get("command"), gotext.Get("command options"), gotext.Get("arguments"), gotext.Get("VERSION"), gotext.Get("DESCRIPTION"), gotext.Get("AUTHOR"), gotext.Get("COMMANDS"), gotext.Get("GLOBAL OPTIONS"), gotext.Get("GLOBAL OPTIONS"), gotext.Get("COPYRIGHT"))
}
func GetCommandHelpTemplate() string {
return fmt.Sprintf(`%s:
{{template "helpNameTemplate" .}}
%s:
{{if .UsageText}}{{wrap .UsageText 3}}{{else}}{{.HelpName}}{{if .VisibleFlags}} [%s]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[%s...]{{end}}{{end}}{{if .Category}}
%s:
{{.Category}}{{end}}{{if .Description}}
%s:
{{template "descriptionTemplate" .}}{{end}}{{if .VisibleFlagCategories}}
%s:{{template "visibleFlagCategoryTemplate" .}}{{else if .VisibleFlags}}
%s:{{template "visibleFlagTemplate" .}}{{end}}
`, gotext.Get("NAME"),
gotext.Get("USAGE"),
gotext.Get("command options"),
gotext.Get("arguments"),
gotext.Get("CATEGORY"),
gotext.Get("DESCRIPTION"),
gotext.Get("OPTIONS"),
gotext.Get("OPTIONS"),
)
}

View File

@ -0,0 +1,72 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package cliutils
import (
"errors"
"fmt"
"log/slog"
"github.com/leonelquinteros/gotext"
"github.com/urfave/cli/v2"
)
type BashCompleteWithErrorFunc func(c *cli.Context) error
func BashCompleteWithError(f BashCompleteWithErrorFunc) cli.BashCompleteFunc {
return func(c *cli.Context) { HandleExitCoder(f(c)) }
}
func HandleExitCoder(err error) {
if err == nil {
return
}
if exitErr, ok := err.(cli.ExitCoder); ok {
if err.Error() != "" {
if _, ok := exitErr.(cli.ErrorFormatter); ok {
slog.Error(fmt.Sprintf("%+v\n", err))
} else {
slog.Error(err.Error())
}
}
cli.OsExiter(exitErr.ExitCode())
return
}
slog.Error(err.Error())
cli.OsExiter(1)
}
func FormatCliExit(msg string, err error) cli.ExitCoder {
return FormatCliExitWithCode(msg, err, 1)
}
func FormatCliExitWithCode(msg string, err error, exitCode int) cli.ExitCoder {
if err == nil {
return cli.Exit(errors.New(msg), exitCode)
}
return cli.Exit(fmt.Errorf("%s: %w", msg, err), exitCode)
}
func WarnLegacyCommand(newSyntax string) {
slog.Warn(
gotext.Get(
"This command is deprecated and would be removed in the future, use \"%s\" instead!", newSyntax,
),
)
}

View File

@ -1,79 +1,161 @@
/*
* ALR - Any Linux Repository
* Copyright (C) 2024 Евгений Храмов
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
// It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors.
//
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package config
import (
"context"
"log/slog"
"os"
"sync"
"path/filepath"
"reflect"
"github.com/caarlos0/env"
"github.com/pelletier/go-toml/v2"
"plemya-x.ru/alr/internal/types"
"plemya-x.ru/alr/pkg/loggerctx"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/constants"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/types"
)
type ALRConfig struct {
cfg *types.Config
paths *Paths
}
var defaultConfig = &types.Config{
RootCmd: "sudo",
UseRootCmd: true,
PagerStyle: "native",
IgnorePkgUpdates: []string{},
Repos: []types.Repo{
{
Name: "default",
URL: "https://gitea.plemya-x.ru/xpamych/xpamych-alr-repo.git",
},
},
AutoPull: true,
Repos: []types.Repo{},
}
var (
configMtx sync.Mutex
config *types.Config
func New() *ALRConfig {
return &ALRConfig{}
}
func readConfig(path string) (*types.Config, error) {
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
config := types.Config{}
if err := toml.NewDecoder(file).Decode(&config); err != nil {
return nil, err
}
return &config, nil
}
func mergeStructs(dst, src interface{}) {
srcVal := reflect.ValueOf(src)
if srcVal.IsNil() {
return
}
srcVal = srcVal.Elem()
dstVal := reflect.ValueOf(dst).Elem()
for i := range srcVal.NumField() {
srcField := srcVal.Field(i)
srcFieldName := srcVal.Type().Field(i).Name
dstField := dstVal.FieldByName(srcFieldName)
if dstField.IsValid() && dstField.CanSet() {
dstField.Set(srcField)
}
}
}
func (c *ALRConfig) Load() error {
systemConfig, err := readConfig(
constants.SystemConfigPath,
)
// Config returns a ALR configuration struct.
// The first time it's called, it'll load the config from a file.
// Subsequent calls will just return the same value.
func Config(ctx context.Context) *types.Config {
configMtx.Lock()
defer configMtx.Unlock()
log := loggerctx.From(ctx)
if config == nil {
cfgFl, err := os.Open(GetPaths(ctx).ConfigPath)
if err != nil {
log.Warn("Error opening config file, using defaults").Err(err).Send()
return defaultConfig
slog.Debug("Cannot read system config", "err", err)
}
defer cfgFl.Close()
// Copy the default configuration into config
defCopy := *defaultConfig
config = &defCopy
config.Repos = nil
config := &types.Config{}
err = toml.NewDecoder(cfgFl).Decode(config)
mergeStructs(config, defaultConfig)
mergeStructs(config, systemConfig)
err = env.Parse(config)
if err != nil {
log.Warn("Error decoding config file, using defaults").Err(err).Send()
// Set config back to nil so that we try again next time
config = nil
return defaultConfig
}
return err
}
return config
c.cfg = config
c.paths = &Paths{}
c.paths.UserConfigPath = constants.SystemConfigPath
c.paths.CacheDir = constants.SystemCachePath
c.paths.RepoDir = filepath.Join(c.paths.CacheDir, "repo")
c.paths.PkgsDir = filepath.Join(c.paths.CacheDir, "pkgs")
c.paths.DBPath = filepath.Join(c.paths.CacheDir, "db")
// c.initPaths()
return nil
}
func (c *ALRConfig) RootCmd() string {
return c.cfg.RootCmd
}
func (c *ALRConfig) PagerStyle() string {
return c.cfg.PagerStyle
}
func (c *ALRConfig) AutoPull() bool {
return c.cfg.AutoPull
}
func (c *ALRConfig) Repos() []types.Repo {
return c.cfg.Repos
}
func (c *ALRConfig) SetRepos(repos []types.Repo) {
c.cfg.Repos = repos
}
func (c *ALRConfig) IgnorePkgUpdates() []string {
return c.cfg.IgnorePkgUpdates
}
func (c *ALRConfig) LogLevel() string {
return c.cfg.LogLevel
}
func (c *ALRConfig) UseRootCmd() bool {
return c.cfg.UseRootCmd
}
func (c *ALRConfig) GetPaths() *Paths {
return c.paths
}
func (c *ALRConfig) SaveUserConfig() error {
f, err := os.Create(c.paths.UserConfigPath)
if err != nil {
return err
}
return toml.NewEncoder(f).Encode(c.cfg)
}

View File

@ -1,67 +0,0 @@
/*
* ALR - Any Linux Repository
* Copyright (C) 2024 Евгений Храмов
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package config
import (
"context"
"os"
"strings"
"sync"
"plemya-x.ru/alr/pkg/loggerctx"
"golang.org/x/text/language"
)
var (
langMtx sync.Mutex
lang language.Tag
langSet bool
)
// Language returns the system language.
// The first time it's called, it'll detect the langauge based on
// the $LANG environment variable.
// Subsequent calls will just return the same value.
func Language(ctx context.Context) language.Tag {
langMtx.Lock()
defer langMtx.Unlock()
log := loggerctx.From(ctx)
if !langSet {
syslang := SystemLang()
tag, err := language.Parse(syslang)
if err != nil {
log.Fatal("Error parsing system language").Err(err).Send()
}
base, _ := tag.Base()
lang = language.Make(base.String())
langSet = true
}
return lang
}
// SystemLang returns the system language based on
// the $LANG environment variable.
func SystemLang() string {
lang := os.Getenv("LANG")
lang, _, _ = strings.Cut(lang, ".")
if lang == "" || lang == "C" {
lang = "en"
}
return lang
}

View File

@ -1,108 +1,29 @@
/*
* ALR - Any Linux Repository
* Copyright (C) 2024 Евгений Храмов
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
// It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors.
//
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package config
import (
"context"
"os"
"path/filepath"
"sync"
"github.com/pelletier/go-toml/v2"
"plemya-x.ru/alr/pkg/loggerctx"
)
// Paths contains various paths used by ALR
type Paths struct {
ConfigDir string
ConfigPath string
UserConfigPath string
CacheDir string
RepoDir string
PkgsDir string
DBPath string
}
var (
pathsMtx sync.Mutex
paths *Paths
)
// GetPaths returns a Paths struct.
// The first time it's called, it'll generate the struct
// using information from the system.
// Subsequent calls will return the same value.
func GetPaths(ctx context.Context) *Paths {
pathsMtx.Lock()
defer pathsMtx.Unlock()
log := loggerctx.From(ctx)
if paths == nil {
paths = &Paths{}
cfgDir, err := os.UserConfigDir()
if err != nil {
log.Fatal("Unable to detect user config directory").Err(err).Send()
}
paths.ConfigDir = filepath.Join(cfgDir, "alr")
err = os.MkdirAll(paths.ConfigDir, 0o755)
if err != nil {
log.Fatal("Unable to create ALR config directory").Err(err).Send()
}
paths.ConfigPath = filepath.Join(paths.ConfigDir, "alr.toml")
if _, err := os.Stat(paths.ConfigPath); err != nil {
cfgFl, err := os.Create(paths.ConfigPath)
if err != nil {
log.Fatal("Unable to create ALR config file").Err(err).Send()
}
err = toml.NewEncoder(cfgFl).Encode(&defaultConfig)
if err != nil {
log.Fatal("Error encoding default configuration").Err(err).Send()
}
cfgFl.Close()
}
cacheDir, err := os.UserCacheDir()
if err != nil {
log.Fatal("Unable to detect cache directory").Err(err).Send()
}
paths.CacheDir = filepath.Join(cacheDir, "alr")
paths.RepoDir = filepath.Join(paths.CacheDir, "repo")
paths.PkgsDir = filepath.Join(paths.CacheDir, "pkgs")
err = os.MkdirAll(paths.RepoDir, 0o755)
if err != nil {
log.Fatal("Unable to create repo cache directory").Err(err).Send()
}
err = os.MkdirAll(paths.PkgsDir, 0o755)
if err != nil {
log.Fatal("Unable to create package cache directory").Err(err).Send()
}
paths.DBPath = filepath.Join(paths.CacheDir, "db")
}
return paths
}

View File

@ -1,3 +1,22 @@
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
// It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors.
//
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package config
// Version contains the version of ALR. If the version

View File

@ -0,0 +1,24 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package constants
const (
SystemConfigPath = "/etc/alr/alr.toml"
SystemCachePath = "/var/cache/alr"
AlrRunDir = "/var/run/alr"
PrivilegedGroup = "wheel"
)

View File

@ -1,20 +1,21 @@
/*
* ALR - Any Linux Repository
* Copyright (C) 2024 Евгений Храмов
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
// It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors.
//
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package cpu
@ -37,11 +38,12 @@ func armVariant() string {
return armEnv
}
if cpu.ARM.HasVFPv3 {
switch {
case cpu.ARM.HasVFPv3:
return "arm7"
} else if cpu.ARM.HasVFP {
case cpu.ARM.HasVFP:
return "arm6"
} else {
default:
return "arm5"
}
}

View File

@ -1,346 +1,151 @@
/*
* ALR - Any Linux Repository
* Copyright (C) 2024 Евгений Храмов
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
// It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors.
//
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package db
import (
"context"
"database/sql"
"database/sql/driver"
"encoding/json"
"errors"
"fmt"
"sync"
"log/slog"
"github.com/jmoiron/sqlx"
"plemya-x.ru/alr/internal/config"
"plemya-x.ru/alr/pkg/loggerctx"
"golang.org/x/exp/slices"
"modernc.org/sqlite"
"github.com/leonelquinteros/gotext"
_ "modernc.org/sqlite"
"xorm.io/xorm"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
)
// CurrentVersion is the current version of the database.
// The database is reset if its version doesn't match this.
const CurrentVersion = 2
const CurrentVersion = 5
func init() {
sqlite.MustRegisterScalarFunction("json_array_contains", 2, jsonArrayContains)
type Version struct {
Version int `xorm:"'version'"`
}
// Package is a ALR package's database representation
type Package struct {
Name string `sh:"name,required" db:"name"`
Version string `sh:"version,required" db:"version"`
Release int `sh:"release,required" db:"release"`
Epoch uint `sh:"epoch" db:"epoch"`
Description JSON[map[string]string] `db:"description"`
Homepage JSON[map[string]string] `db:"homepage"`
Maintainer JSON[map[string]string] `db:"maintainer"`
Architectures JSON[[]string] `sh:"architectures" db:"architectures"`
Licenses JSON[[]string] `sh:"license" db:"licenses"`
Provides JSON[[]string] `sh:"provides" db:"provides"`
Conflicts JSON[[]string] `sh:"conflicts" db:"conflicts"`
Replaces JSON[[]string] `sh:"replaces" db:"replaces"`
Depends JSON[map[string][]string] `db:"depends"`
BuildDepends JSON[map[string][]string] `db:"builddepends"`
OptDepends JSON[map[string][]string] `db:"optdepends"`
Repository string `db:"repository"`
type Config interface {
GetPaths() *config.Paths
}
type version struct {
Version int `db:"version"`
type Database struct {
engine *xorm.Engine
config Config
}
var (
mu sync.Mutex
conn *sqlx.DB
closed = true
)
// DB returns the ALR database.
// The first time it's called, it opens the SQLite database file.
// Subsequent calls return the same connection.
func DB(ctx context.Context) *sqlx.DB {
log := loggerctx.From(ctx)
if conn != nil && !closed {
return getConn()
}
_, err := open(ctx, config.GetPaths(ctx).DBPath)
if err != nil {
log.Fatal("Error opening database").Err(err).Send()
}
return getConn()
}
func getConn() *sqlx.DB {
mu.Lock()
defer mu.Unlock()
return conn
}
func open(ctx context.Context, dsn string) (*sqlx.DB, error) {
db, err := sqlx.Open("sqlite", dsn)
if err != nil {
return nil, err
}
mu.Lock()
conn = db
closed = false
mu.Unlock()
err = initDB(ctx, dsn)
if err != nil {
return nil, err
}
return db, nil
}
// Close closes the database
func Close() error {
closed = true
if conn != nil {
return conn.Close()
} else {
return nil
func New(config Config) *Database {
return &Database{
config: config,
}
}
// initDB initializes the database
func initDB(ctx context.Context, dsn string) error {
log := loggerctx.From(ctx)
conn = conn.Unsafe()
_, err := conn.ExecContext(ctx, `
CREATE TABLE IF NOT EXISTS pkgs (
name TEXT NOT NULL,
repository TEXT NOT NULL,
version TEXT NOT NULL,
release INT NOT NULL,
epoch INT,
description TEXT CHECK(description = 'null' OR (JSON_VALID(description) AND JSON_TYPE(description) = 'object')),
homepage TEXT CHECK(homepage = 'null' OR (JSON_VALID(homepage) AND JSON_TYPE(homepage) = 'object')),
maintainer TEXT CHECK(maintainer = 'null' OR (JSON_VALID(maintainer) AND JSON_TYPE(maintainer) = 'object')),
architectures TEXT CHECK(architectures = 'null' OR (JSON_VALID(architectures) AND JSON_TYPE(architectures) = 'array')),
licenses TEXT CHECK(licenses = 'null' OR (JSON_VALID(licenses) AND JSON_TYPE(licenses) = 'array')),
provides TEXT CHECK(provides = 'null' OR (JSON_VALID(provides) AND JSON_TYPE(provides) = 'array')),
conflicts TEXT CHECK(conflicts = 'null' OR (JSON_VALID(conflicts) AND JSON_TYPE(conflicts) = 'array')),
replaces TEXT CHECK(replaces = 'null' OR (JSON_VALID(replaces) AND JSON_TYPE(replaces) = 'array')),
depends TEXT CHECK(depends = 'null' OR (JSON_VALID(depends) AND JSON_TYPE(depends) = 'object')),
builddepends TEXT CHECK(builddepends = 'null' OR (JSON_VALID(builddepends) AND JSON_TYPE(builddepends) = 'object')),
optdepends TEXT CHECK(optdepends = 'null' OR (JSON_VALID(optdepends) AND JSON_TYPE(optdepends) = 'object')),
UNIQUE(name, repository)
);
CREATE TABLE IF NOT EXISTS alr_db_version (
version INT NOT NULL
);
`)
func (d *Database) Connect() error {
dsn := d.config.GetPaths().DBPath
engine, err := xorm.NewEngine("sqlite", dsn)
// engine.SetLogLevel(log.LOG_DEBUG)
// engine.ShowSQL(true)
if err != nil {
return err
}
d.engine = engine
return nil
}
ver, ok := GetVersion(ctx)
func (d *Database) Init(ctx context.Context) error {
if err := d.Connect(); err != nil {
return err
}
if err := d.engine.Sync2(new(alrsh.Package), new(Version)); err != nil {
return err
}
ver, ok := d.GetVersion(ctx)
if ok && ver != CurrentVersion {
log.Warn("Database version mismatch; resetting").Int("version", ver).Int("expected", CurrentVersion).Send()
reset(ctx)
return initDB(ctx, dsn)
} else if !ok {
log.Warn("Database version does not exist. Run alr fix if something isn't working.").Send()
return addVersion(ctx, CurrentVersion)
slog.Warn(gotext.Get("Database version mismatch; resetting"), "version", ver, "expected", CurrentVersion)
if err := d.reset(); err != nil {
return err
}
return d.Init(ctx)
} else if !ok {
slog.Warn(gotext.Get("Database version does not exist. Run alr fix if something isn't working."))
return d.addVersion(CurrentVersion)
}
return nil
}
// reset drops all the database tables
func reset(ctx context.Context) error {
_, err := DB(ctx).ExecContext(ctx, "DROP TABLE IF EXISTS pkgs;")
if err != nil {
return err
}
_, err = DB(ctx).ExecContext(ctx, "DROP TABLE IF EXISTS alr_db_version;")
return err
}
// IsEmpty returns true if the database has no packages in it, otherwise it returns false.
func IsEmpty(ctx context.Context) bool {
var count int
err := DB(ctx).GetContext(ctx, &count, "SELECT count(1) FROM pkgs;")
if err != nil {
return true
}
return count == 0
}
// GetVersion returns the database version and a boolean indicating
// whether the database contained a version number
func GetVersion(ctx context.Context) (int, bool) {
var ver version
err := DB(ctx).GetContext(ctx, &ver, "SELECT * FROM alr_db_version LIMIT 1;")
if err != nil {
func (d *Database) GetVersion(ctx context.Context) (int, bool) {
var v Version
has, err := d.engine.Get(&v)
if err != nil || !has {
return 0, false
}
return ver.Version, true
return v.Version, true
}
func addVersion(ctx context.Context, ver int) error {
_, err := DB(ctx).ExecContext(ctx, `INSERT INTO alr_db_version(version) VALUES (?);`, ver)
func (d *Database) addVersion(ver int) error {
_, err := d.engine.Insert(&Version{Version: ver})
return err
}
// InsertPackage adds a package to the database
func InsertPackage(ctx context.Context, pkg Package) error {
_, err := DB(ctx).NamedExecContext(ctx, `
INSERT OR REPLACE INTO pkgs (
name,
repository,
version,
release,
epoch,
description,
homepage,
maintainer,
architectures,
licenses,
provides,
conflicts,
replaces,
depends,
builddepends,
optdepends
) VALUES (
:name,
:repository,
:version,
:release,
:epoch,
:description,
:homepage,
:maintainer,
:architectures,
:licenses,
:provides,
:conflicts,
:replaces,
:depends,
:builddepends,
:optdepends
);
`, pkg)
return err
func (d *Database) reset() error {
return d.engine.DropTables(new(alrsh.Package), new(Version))
}
// GetPkgs returns a result containing packages that match the where conditions
func GetPkgs(ctx context.Context, where string, args ...any) (*sqlx.Rows, error) {
stream, err := DB(ctx).QueryxContext(ctx, "SELECT * FROM pkgs WHERE "+where, args...)
if err != nil {
return nil, err
}
return stream, nil
}
func (d *Database) InsertPackage(ctx context.Context, pkg alrsh.Package) error {
session := d.engine.Context(ctx)
// GetPkg returns a single package that matches the where conditions
func GetPkg(ctx context.Context, where string, args ...any) (*Package, error) {
out := &Package{}
err := DB(ctx).GetContext(ctx, out, "SELECT * FROM pkgs WHERE "+where+" LIMIT 1", args...)
return out, err
}
// DeletePkgs deletes all packages matching the where conditions
func DeletePkgs(ctx context.Context, where string, args ...any) error {
_, err := DB(ctx).ExecContext(ctx, "DELETE FROM pkgs WHERE "+where, args...)
return err
}
// jsonArrayContains is an SQLite function that checks if a JSON array
// in the database contains a given value
func jsonArrayContains(ctx *sqlite.FunctionContext, args []driver.Value) (driver.Value, error) {
value, ok := args[0].(string)
if !ok {
return nil, errors.New("both arguments to json_array_contains must be strings")
}
item, ok := args[1].(string)
if !ok {
return nil, errors.New("both arguments to json_array_contains must be strings")
}
var array []string
err := json.Unmarshal([]byte(value), &array)
if err != nil {
return nil, err
}
return slices.Contains(array, item), nil
}
// JSON represents a JSON value in the database
type JSON[T any] struct {
Val T
}
// NewJSON creates a new database JSON value
func NewJSON[T any](v T) JSON[T] {
return JSON[T]{Val: v}
}
func (s *JSON[T]) Scan(val any) error {
if val == nil {
return nil
}
switch val := val.(type) {
case string:
err := json.Unmarshal([]byte(val), &s.Val)
affected, err := session.Where("name = ? AND repository = ?", pkg.Name, pkg.Repository).Update(&pkg)
if err != nil {
return err
}
case sql.NullString:
if val.Valid {
err := json.Unmarshal([]byte(val.String), &s.Val)
if affected == 0 {
_, err = session.Insert(&pkg)
if err != nil {
return err
}
}
default:
return errors.New("sqlite json types must be strings")
}
return nil
}
func (s JSON[T]) Value() (driver.Value, error) {
data, err := json.Marshal(s.Val)
if err != nil {
func (d *Database) GetPkgs(_ context.Context, where string, args ...any) ([]alrsh.Package, error) {
var pkgs []alrsh.Package
err := d.engine.Where(where, args...).Find(&pkgs)
return pkgs, err
}
func (d *Database) GetPkg(where string, args ...any) (*alrsh.Package, error) {
var pkg alrsh.Package
has, err := d.engine.Where(where, args...).Get(&pkg)
if err != nil || !has {
return nil, err
}
return string(data), nil
return &pkg, nil
}
func (s JSON[T]) MarshalYAML() (any, error) {
return s.Val, nil
func (d *Database) DeletePkgs(_ context.Context, where string, args ...any) error {
_, err := d.engine.Where(where, args...).Delete(&alrsh.Package{})
return err
}
func (s JSON[T]) String() string {
return fmt.Sprint(s.Val)
func (d *Database) IsEmpty() bool {
count, err := d.engine.Count(new(alrsh.Package))
return err != nil || count == 0
}
func (s JSON[T]) GoString() string {
return fmt.Sprintf("%#v", s.Val)
func (d *Database) Close() error {
return d.engine.Close()
}

View File

@ -1,76 +1,91 @@
/*
* ALR - Any Linux Repository
* Copyright (C) 2024 Евгений Храмов
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
// It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors.
//
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package db_test
import (
"context"
"reflect"
"strings"
"testing"
"github.com/jmoiron/sqlx"
"plemya-x.ru/alr/internal/db"
"github.com/stretchr/testify/assert"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/config"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/db"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/alrsh"
)
var testPkg = db.Package{
type TestALRConfig struct{}
func (c *TestALRConfig) GetPaths() *config.Paths {
return &config.Paths{
DBPath: ":memory:",
}
}
func prepareDb() *db.Database {
database := db.New(&TestALRConfig{})
database.Init(context.Background())
return database
}
var testPkg = alrsh.Package{
Name: "test",
Version: "0.0.1",
Release: 1,
Epoch: 2,
Description: db.NewJSON(map[string]string{
Description: alrsh.OverridableFromMap(map[string]string{
"en": "Test package",
"ru": "Проверочный пакет",
}),
Homepage: db.NewJSON(map[string]string{
Homepage: alrsh.OverridableFromMap(map[string]string{
"en": "https://gitea.plemya-x.ru/xpamych/ALR",
}),
Maintainer: db.NewJSON(map[string]string{
Maintainer: alrsh.OverridableFromMap(map[string]string{
"en": "Evgeniy Khramov <xpamych@yandex.ru>",
"ru": "Евгений Храмов <xpamych@yandex.ru>",
}),
Architectures: db.NewJSON([]string{"arm64", "amd64"}),
Licenses: db.NewJSON([]string{"GPL-3.0-or-later"}),
Provides: db.NewJSON([]string{"test"}),
Conflicts: db.NewJSON([]string{"test"}),
Replaces: db.NewJSON([]string{"test-old"}),
Depends: db.NewJSON(map[string][]string{
Architectures: []string{"arm64", "amd64"},
Licenses: []string{"GPL-3.0-or-later"},
Provides: []string{"test"},
Conflicts: []string{"test"},
Replaces: []string{"test-old"},
Depends: alrsh.OverridableFromMap(map[string][]string{
"": {"sudo"},
}),
BuildDepends: db.NewJSON(map[string][]string{
BuildDepends: alrsh.OverridableFromMap(map[string][]string{
"": {"golang"},
"arch": {"go"},
}),
Repository: "default",
Summary: alrsh.OverridableFromMap(map[string]string{}),
Group: alrsh.OverridableFromMap(map[string]string{}),
OptDepends: alrsh.OverridableFromMap(map[string][]string{}),
}
func TestInit(t *testing.T) {
_, err := db.Open(":memory:")
if err != nil {
t.Fatalf("Expected no error, got %s", err)
}
defer db.Close()
ctx := context.Background()
database := prepareDb()
defer database.Close()
_, err = db.DB().Exec("SELECT * FROM pkgs")
if err != nil {
t.Fatalf("Expected no error, got %s", err)
}
ver, ok := db.GetVersion()
ver, ok := database.GetVersion(ctx)
if !ok {
t.Errorf("Expected version to be present")
} else if ver != db.CurrentVersion {
@ -79,62 +94,53 @@ func TestInit(t *testing.T) {
}
func TestInsertPackage(t *testing.T) {
_, err := db.Open(":memory:")
if err != nil {
t.Fatalf("Expected no error, got %s", err)
}
defer db.Close()
ctx := context.Background()
database := prepareDb()
defer database.Close()
err = db.InsertPackage(testPkg)
err := database.InsertPackage(ctx, testPkg)
if err != nil {
t.Fatalf("Expected no error, got %s", err)
}
dbPkg := db.Package{}
err = sqlx.Get(db.DB(), &dbPkg, "SELECT * FROM pkgs WHERE name = 'test' AND repository = 'default'")
pkgs, err := database.GetPkgs(ctx, "name = 'test' AND repository = 'default'")
if err != nil {
t.Fatalf("Expected no error, got %s", err)
}
if !reflect.DeepEqual(testPkg, dbPkg) {
t.Errorf("Expected test package to be the same as database package")
if len(pkgs) != 1 {
t.Fatalf("Expected 1 package, got %d", len(pkgs))
}
assert.Equal(t, testPkg, pkgs[0])
}
func TestGetPkgs(t *testing.T) {
_, err := db.Open(":memory:")
if err != nil {
t.Fatalf("Expected no error, got %s", err)
}
defer db.Close()
ctx := context.Background()
database := prepareDb()
defer database.Close()
x1 := testPkg
x1.Name = "x1"
x2 := testPkg
x2.Name = "x2"
err = db.InsertPackage(x1)
err := database.InsertPackage(ctx, x1)
if err != nil {
t.Errorf("Expected no error, got %s", err)
}
err = db.InsertPackage(x2)
err = database.InsertPackage(ctx, x2)
if err != nil {
t.Errorf("Expected no error, got %s", err)
}
result, err := db.GetPkgs("name LIKE 'x%'")
pkgs, err := database.GetPkgs(ctx, "name LIKE 'x%'")
if err != nil {
t.Fatalf("Expected no error, got %s", err)
}
for result.Next() {
var dbPkg db.Package
err = result.StructScan(&dbPkg)
if err != nil {
t.Errorf("Expected no error, got %s", err)
}
for _, dbPkg := range pkgs {
if !strings.HasPrefix(dbPkg.Name, "x") {
t.Errorf("Expected package name to start with 'x', got %s", dbPkg.Name)
}
@ -142,28 +148,26 @@ func TestGetPkgs(t *testing.T) {
}
func TestGetPkg(t *testing.T) {
_, err := db.Open(":memory:")
if err != nil {
t.Fatalf("Expected no error, got %s", err)
}
defer db.Close()
ctx := context.Background()
database := prepareDb()
defer database.Close()
x1 := testPkg
x1.Name = "x1"
x2 := testPkg
x2.Name = "x2"
err = db.InsertPackage(x1)
err := database.InsertPackage(ctx, x1)
if err != nil {
t.Errorf("Expected no error, got %s", err)
}
err = db.InsertPackage(x2)
err = database.InsertPackage(ctx, x2)
if err != nil {
t.Errorf("Expected no error, got %s", err)
}
pkg, err := db.GetPkg("name LIKE 'x%' ORDER BY name")
pkg, err := database.GetPkg("name LIKE 'x%'")
if err != nil {
t.Fatalf("Expected no error, got %s", err)
}
@ -178,73 +182,70 @@ func TestGetPkg(t *testing.T) {
}
func TestDeletePkgs(t *testing.T) {
_, err := db.Open(":memory:")
if err != nil {
t.Fatalf("Expected no error, got %s", err)
}
defer db.Close()
ctx := context.Background()
database := prepareDb()
defer database.Close()
x1 := testPkg
x1.Name = "x1"
x2 := testPkg
x2.Name = "x2"
err = db.InsertPackage(x1)
err := database.InsertPackage(ctx, x1)
if err != nil {
t.Errorf("Expected no error, got %s", err)
}
err = db.InsertPackage(x2)
err = database.InsertPackage(ctx, x2)
if err != nil {
t.Errorf("Expected no error, got %s", err)
}
err = db.DeletePkgs("name = 'x1'")
err = database.DeletePkgs(ctx, "name = 'x1'")
if err != nil {
t.Errorf("Expected no error, got %s", err)
}
var dbPkg db.Package
err = db.DB().Get(&dbPkg, "SELECT * FROM pkgs WHERE name LIKE 'x%' ORDER BY name LIMIT 1;")
if err != nil {
t.Errorf("Expected no error, got %s", err)
}
if dbPkg.Name != "x2" {
t.Errorf("Expected x2 package, got %s", dbPkg.Name)
}
}
func TestJsonArrayContains(t *testing.T) {
_, err := db.Open(":memory:")
if err != nil {
t.Fatalf("Expected no error, got %s", err)
}
defer db.Close()
ctx := context.Background()
database := prepareDb()
defer database.Close()
x1 := testPkg
x1.Name = "x1"
x2 := testPkg
x2.Name = "x2"
x2.Provides.Val = append(x2.Provides.Val, "x")
x2.Provides = append(x2.Provides, "x")
err = db.InsertPackage(x1)
err := database.InsertPackage(ctx, x1)
if err != nil {
t.Errorf("Expected no error, got %s", err)
}
err = db.InsertPackage(x2)
err = database.InsertPackage(ctx, x2)
if err != nil {
t.Errorf("Expected no error, got %s", err)
}
var dbPkg db.Package
err = db.DB().Get(&dbPkg, "SELECT * FROM pkgs WHERE json_array_contains(provides, 'x');")
pkgs, err := database.GetPkgs(ctx, "name = 'x2'")
if err != nil {
t.Fatalf("Expected no error, got %s", err)
}
if dbPkg.Name != "x2" {
t.Errorf("Expected x2 package, got %s", dbPkg.Name)
if len(pkgs) != 1 || pkgs[0].Name != "x2" {
t.Errorf("Expected x2 package, got %v", pkgs)
}
// Verify the provides field contains 'x'
found := false
for _, p := range pkgs[0].Provides {
if p == "x" {
found = true
break
}
}
if !found {
t.Errorf("Expected provides to contain 'x'")
}
}

52
internal/db/utils.go Normal file
View File

@ -0,0 +1,52 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package db
import (
"database/sql/driver"
"encoding/json"
"errors"
"golang.org/x/exp/slices"
"modernc.org/sqlite"
)
func init() {
sqlite.MustRegisterScalarFunction("json_array_contains", 2, jsonArrayContains)
}
// jsonArrayContains is an SQLite function that checks if a JSON array
// in the database contains a given value
func jsonArrayContains(ctx *sqlite.FunctionContext, args []driver.Value) (driver.Value, error) {
value, ok := args[0].(string)
if !ok {
return nil, errors.New("both arguments to json_array_contains must be strings")
}
item, ok := args[1].(string)
if !ok {
return nil, errors.New("both arguments to json_array_contains must be strings")
}
var array []string
err := json.Unmarshal([]byte(value), &array)
if err != nil {
return nil, err
}
return slices.Contains(array, item), nil
}

View File

@ -1,93 +0,0 @@
/*
* ALR - Any Linux Repository
* Copyright (C) 2024 Евгений Храмов
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package dlcache
import (
"context"
"crypto/sha1"
"encoding/hex"
"io"
"os"
"path/filepath"
"plemya-x.ru/alr/internal/config"
)
// BasePath returns the base path of the download cache
func BasePath(ctx context.Context) string {
return filepath.Join(config.GetPaths(ctx).CacheDir, "dl")
}
// New creates a new directory with the given ID in the cache.
// If a directory with the same ID already exists,
// it will be deleted before creating a new one.
func New(ctx context.Context, id string) (string, error) {
h, err := hashID(id)
if err != nil {
return "", err
}
itemPath := filepath.Join(BasePath(ctx), h)
fi, err := os.Stat(itemPath)
if err == nil || (fi != nil && !fi.IsDir()) {
err = os.RemoveAll(itemPath)
if err != nil {
return "", err
}
}
err = os.MkdirAll(itemPath, 0o755)
if err != nil {
return "", err
}
return itemPath, nil
}
// Get checks if an entry with the given ID
// already exists in the cache, and if so,
// returns the directory and true. If it
// does not exist, it returns an empty string
// and false.
func Get(ctx context.Context, id string) (string, bool) {
h, err := hashID(id)
if err != nil {
return "", false
}
itemPath := filepath.Join(BasePath(ctx), h)
_, err = os.Stat(itemPath)
if err != nil {
return "", false
}
return itemPath, true
}
// hashID hashes the input ID with SHA1
// and returns the hex string of the hashed
// ID.
func hashID(id string) (string, error) {
h := sha1.New()
_, err := io.WriteString(h, id)
if err != nil {
return "", err
}
return hex.EncodeToString(h.Sum(nil)), nil
}

View File

@ -1,76 +0,0 @@
/*
* ALR - Any Linux Repository
* Copyright (C) 2024 Евгений Храмов
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package dlcache_test
import (
"context"
"crypto/sha1"
"encoding/hex"
"io"
"os"
"path/filepath"
"testing"
"plemya-x.ru/alr/internal/config"
"plemya-x.ru/alr/internal/dlcache"
)
func init() {
dir, err := os.MkdirTemp("/tmp", "alr-dlcache-test.*")
if err != nil {
panic(err)
}
config.GetPaths(context.Background()).RepoDir = dir
}
func TestNew(t *testing.T) {
const id = "https://example.com"
dir, err := dlcache.New(id)
if err != nil {
t.Errorf("Expected no error, got %s", err)
}
exp := filepath.Join(dlcache.BasePath(), sha1sum(id))
if dir != exp {
t.Errorf("Expected %s, got %s", exp, dir)
}
fi, err := os.Stat(dir)
if err != nil {
t.Errorf("stat: expected no error, got %s", err)
}
if !fi.IsDir() {
t.Errorf("Expected cache item to be a directory")
}
dir2, ok := dlcache.Get(id)
if !ok {
t.Errorf("Expected Get() to return valid value")
}
if dir2 != dir {
t.Errorf("Expected %s from Get(), got %s", dir, dir2)
}
}
func sha1sum(id string) string {
h := sha1.New()
_, _ = io.WriteString(h, id)
return hex.EncodeToString(h.Sum(nil))
}

39
internal/gen/funcs.go Normal file
View File

@ -0,0 +1,39 @@
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
// It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors.
//
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package gen
import (
"strings"
"text/template"
)
// Определяем переменную funcs типа template.FuncMap, которая будет использоваться для
// предоставления пользовательских функций в шаблонах
var funcs = template.FuncMap{
// Функция "tolower" использует strings.ToLower
// для преобразования строки в нижний регистр
"tolower": strings.ToLower,
// Функция "firstchar" — это лямбда-функция, которая берет строку
// и возвращает её первый символ
"firstchar": func(s string) string {
return s[:1]
},
}

118
internal/gen/pip.go Normal file
View File

@ -0,0 +1,118 @@
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
// It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors.
//
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package gen
import (
_ "embed" // Пакет для встраивания содержимого файлов в бинарники Go, использовав откладку //go:embed
"encoding/json" // Пакет для работы с JSON: декодирование и кодирование
"errors" // Пакет для создания и обработки ошибок
"fmt" // Пакет для форматированного ввода и вывода
"io" // Пакет для интерфейсов ввода и вывода
"net/http" // Пакет для HTTP-клиентов и серверов
"text/template" // Пакет для обработки текстовых шаблонов
)
// Используем директиву //go:embed для встраивания содержимого файла шаблона в строку pipTmpl
// Встраивание файла tmpls/pip.tmpl.sh
//
//go:embed tmpls/pip.tmpl.sh
var pipTmpl string
// PipOptions содержит параметры, которые будут переданы в шаблон
type PipOptions struct {
Name string // Имя пакета
Version string // Версия пакета
Description string // Описание пакета
}
// pypiAPIResponse представляет структуру ответа от API PyPI
type pypiAPIResponse struct {
Info pypiInfo `json:"info"` // Информация о пакете
URLs []pypiURL `json:"urls"` // Список URL-адресов для загрузки пакета
}
// Метод SourceURL ищет и возвращает URL исходного distribution для пакета, если он существует
func (res pypiAPIResponse) SourceURL() (pypiURL, error) {
for _, url := range res.URLs {
if url.PackageType == "sdist" {
return url, nil
}
}
return pypiURL{}, errors.New("package doesn't have a source distribution")
}
// pypiInfo содержит основную информацию о пакете, такую как имя, версия и пр.
type pypiInfo struct {
Name string `json:"name"`
Version string `json:"version"`
Summary string `json:"summary"`
Homepage string `json:"home_page"`
License string `json:"license"`
}
// pypiURL представляет информацию об одном из доступных для загрузки URL
type pypiURL struct {
Digests map[string]string `json:"digests"` // Контрольные суммы для файлов
Filename string `json:"filename"` // Имя файла
PackageType string `json:"packagetype"` // Тип пакета (например sdist)
}
// Функция Pip загружает информацию о пакете из PyPI и использует шаблон для вывода информации
func Pip(w io.Writer, opts PipOptions) error {
// Создаем новый шаблон с добавлением функций из FuncMap
tmpl, err := template.New("pip").
Funcs(funcs).
Parse(pipTmpl)
if err != nil {
return err
}
// Формируем URL для запроса к PyPI на основании имени и версии пакета
url := fmt.Sprintf(
"https://pypi.org/pypi/%s/%s/json",
opts.Name,
opts.Version,
)
// Выполняем HTTP GET запрос к PyPI
res, err := http.Get(url)
if err != nil {
return err
}
defer res.Body.Close() // Закрываем тело ответа после завершения работы
if res.StatusCode != 200 {
return fmt.Errorf("pypi: %s", res.Status)
}
// Раскодируем ответ JSON от PyPI в структуру pypiAPIResponse
var resp pypiAPIResponse
err = json.NewDecoder(res.Body).Decode(&resp)
if err != nil {
return err
}
// Если в opts указано описание, используем его вместо описания из PyPI
if opts.Description != "" {
resp.Info.Summary = opts.Description
}
// Выполняем шаблон с использованием данных из resp и записываем результат в w
return tmpl.Execute(w, resp)
}

View File

@ -0,0 +1,55 @@
# This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
# It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors.
#
# ALR - Any Linux Repository
# Copyright (C) 2025 The ALR Authors
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
name='python3-{{.Info.Name | tolower}}'
version='{{.Info.Version}}'
release='1'
desc='{{.Info.Summary}}'
homepage='{{.Info.Homepage}}'
maintainer='Example <user@example.com>'
architectures=('all')
license=('{{if .Info.License | ne ""}}{{.Info.License}}{{else}}custom:Unknown{{end}}')
provides=('{{.Info.Name | tolower}}')
conflicts=('{{.Info.Name | tolower}}')
deps=("python3")
deps_arch=("python")
deps_alpine=("python3")
build_deps=("python3" "python3-pip")
build_deps_arch=("python" "python-pip")
build_deps_alpine=("python3" "py3-pip")
sources=("https://files.pythonhosted.org/packages/source/{{.SourceURL.Filename | firstchar}}/{{.Info.Name}}/{{.SourceURL.Filename}}")
checksums=('blake2b-256:{{.SourceURL.Digests.blake2b_256}}')
build() {
cd "$srcdir/{{.Info.Name}}-${version}"
python -m build --wheel --no-isolation
}
package() {
cd "$srcdir/{{.Info.Name}}-${version}"
pip install --root="${pkgdir}/" . --no-deps --ignore-installed --disable-pip-version-check
}
files() {
printf '"%s" ' ./usr/local/lib/python3.*/site-packages/{{.Info.Name | tolower}}/*
printf '"%s" ' ./usr/local/lib/python3.*/site-packages/{{.Info.Name | tolower}}-${version}.dist-info/*
}

154
internal/logger/hclog.go Normal file
View File

@ -0,0 +1,154 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package logger
import (
"io"
"log"
"strings"
chLog "github.com/charmbracelet/log"
"github.com/hashicorp/go-hclog"
)
type HCLoggerAdapter struct {
logger *Logger
}
func hclogLevelTochLog(level hclog.Level) chLog.Level {
switch level {
case hclog.Debug:
return chLog.DebugLevel
case hclog.Info:
return chLog.InfoLevel
case hclog.Warn:
return chLog.WarnLevel
case hclog.Error:
return chLog.ErrorLevel
}
return chLog.FatalLevel
}
func (a *HCLoggerAdapter) Log(level hclog.Level, msg string, args ...interface{}) {
filteredArgs := make([]interface{}, 0, len(args))
for i := 0; i < len(args); i += 2 {
if i+1 >= len(args) {
filteredArgs = append(filteredArgs, args[i])
continue
}
key, ok := args[i].(string)
if !ok || key != "timestamp" {
filteredArgs = append(filteredArgs, args[i], args[i+1])
}
}
// Start ugly hacks
// Ignore exit messages
// - https://github.com/hashicorp/go-plugin/issues/331
// - https://github.com/hashicorp/go-plugin/issues/203
// - https://github.com/hashicorp/go-plugin/issues/192
var chLogLevel chLog.Level
if msg == "plugin process exited" ||
strings.HasPrefix(msg, "[ERR] plugin: stream copy 'stderr' error") ||
strings.HasPrefix(msg, "[WARN] error closing client during Kill") ||
strings.HasPrefix(msg, "[WARN] plugin failed to exit gracefully") ||
strings.HasPrefix(msg, "[DEBUG] plugin") {
chLogLevel = chLog.DebugLevel
} else {
chLogLevel = hclogLevelTochLog(level)
}
a.logger.l.Log(chLogLevel, msg, filteredArgs...)
}
func (a *HCLoggerAdapter) Trace(msg string, args ...interface{}) {
a.Log(hclog.Trace, msg, args...)
}
func (a *HCLoggerAdapter) Debug(msg string, args ...interface{}) {
a.Log(hclog.Debug, msg, args...)
}
func (a *HCLoggerAdapter) Info(msg string, args ...interface{}) {
a.Log(hclog.Info, msg, args...)
}
func (a *HCLoggerAdapter) Warn(msg string, args ...interface{}) {
a.Log(hclog.Warn, msg, args...)
}
func (a *HCLoggerAdapter) Error(msg string, args ...interface{}) {
a.Log(hclog.Error, msg, args...)
}
func (a *HCLoggerAdapter) IsTrace() bool {
return a.logger.l.GetLevel() <= chLog.DebugLevel
}
func (a *HCLoggerAdapter) IsDebug() bool {
return a.logger.l.GetLevel() <= chLog.DebugLevel
}
func (a *HCLoggerAdapter) IsInfo() bool {
return a.logger.l.GetLevel() <= chLog.InfoLevel
}
func (a *HCLoggerAdapter) IsWarn() bool {
return a.logger.l.GetLevel() <= chLog.WarnLevel
}
func (a *HCLoggerAdapter) IsError() bool {
return a.logger.l.GetLevel() <= chLog.ErrorLevel
}
func (a *HCLoggerAdapter) ImpliedArgs() []interface{} {
return nil
}
func (a *HCLoggerAdapter) With(args ...interface{}) hclog.Logger {
return a
}
func (a *HCLoggerAdapter) Name() string {
return ""
}
func (a *HCLoggerAdapter) Named(name string) hclog.Logger {
return a
}
func (a *HCLoggerAdapter) ResetNamed(name string) hclog.Logger {
return a
}
func (a *HCLoggerAdapter) SetLevel(level hclog.Level) {
}
func (a *HCLoggerAdapter) StandardLogger(opts *hclog.StandardLoggerOptions) *log.Logger {
return nil
}
func (a *HCLoggerAdapter) StandardWriter(opts *hclog.StandardLoggerOptions) io.Writer {
return nil
}
func GetHCLoggerAdapter() *HCLoggerAdapter {
return &HCLoggerAdapter{
logger: logger,
}
}

111
internal/logger/log.go Normal file
View File

@ -0,0 +1,111 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package logger
import (
"context"
"log/slog"
"os"
"github.com/charmbracelet/lipgloss"
chLog "github.com/charmbracelet/log"
"github.com/leonelquinteros/gotext"
)
type Logger struct {
l *chLog.Logger
}
func setupLogger() *chLog.Logger {
styles := chLog.DefaultStyles()
logger := chLog.New(os.Stderr)
styles.Levels[chLog.InfoLevel] = lipgloss.NewStyle().
SetString("-->").
Foreground(lipgloss.Color("35"))
styles.Levels[chLog.ErrorLevel] = lipgloss.NewStyle().
SetString(gotext.Get("ERROR")).
Padding(0, 1, 0, 1).
Background(lipgloss.Color("204")).
Foreground(lipgloss.Color("0"))
logger.SetStyles(styles)
return logger
}
func New() *Logger {
return &Logger{
l: setupLogger(),
}
}
func slogLevelToLog(level slog.Level) chLog.Level {
switch level {
case slog.LevelDebug:
return chLog.DebugLevel
case slog.LevelInfo:
return chLog.InfoLevel
case slog.LevelWarn:
return chLog.WarnLevel
case slog.LevelError:
return chLog.ErrorLevel
}
return chLog.FatalLevel
}
func (l *Logger) SetLevel(level slog.Level) {
l.l.SetLevel(slogLevelToLog(level))
}
func (l *Logger) Enabled(ctx context.Context, level slog.Level) bool {
return l.l.Enabled(ctx, level)
}
func (l *Logger) Handle(ctx context.Context, rec slog.Record) error {
return l.l.Handle(ctx, rec)
}
func (l *Logger) WithAttrs(attrs []slog.Attr) slog.Handler {
sl := *l
sl.l = l.l.WithAttrs(attrs).(*chLog.Logger)
return &sl
}
func (l *Logger) WithGroup(name string) slog.Handler {
sl := *l
sl.l = l.l.WithGroup(name).(*chLog.Logger)
return &sl
}
var logger *Logger
func SetupDefault() *Logger {
logger = New()
slogLogger := slog.New(logger)
slog.SetDefault(slogLogger)
return logger
}
func SetupForGoPlugin() {
logger.l.SetFormatter(chLog.JSONFormatter)
chLog.TimestampKey = "@timestamp"
chLog.MessageKey = "@message"
chLog.LevelKey = "@level"
}
func GetLogger() *Logger {
return logger
}

View File

@ -1,20 +1,21 @@
/*
* ALR - Any Linux Repository
* Copyright (C) 2024 Евгений Храмов
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
// It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors.
//
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package manager
@ -27,7 +28,15 @@ import (
// APK represents the APK package manager
type APK struct {
rootCmd string
CommonPackageManager
}
func NewAPK() *APK {
return &APK{
CommonPackageManager: CommonPackageManager{
noConfirmArg: "-i",
},
}
}
func (*APK) Exists() bool {
@ -43,10 +52,6 @@ func (*APK) Format() string {
return "apk"
}
func (a *APK) SetRootCmd(s string) {
a.rootCmd = s
}
func (a *APK) Sync(opts *Opts) error {
opts = ensureOpts(opts)
cmd := a.getCmd(opts, "apk", "update")
@ -148,19 +153,17 @@ func (a *APK) ListInstalled(opts *Opts) (map[string]string, error) {
return out, nil
}
func (a *APK) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd {
var cmd *exec.Cmd
if opts.AsRoot {
cmd = exec.Command(getRootCmd(a.rootCmd), mgrCmd)
cmd.Args = append(cmd.Args, opts.Args...)
cmd.Args = append(cmd.Args, args...)
} else {
cmd = exec.Command(mgrCmd, args...)
func (a *APK) IsInstalled(pkg string) (bool, error) {
cmd := exec.Command("apk", "info", "--installed", pkg)
output, err := cmd.CombinedOutput()
if err != nil {
if exitErr, ok := err.(*exec.ExitError); ok {
// Exit code 1 means the package is not installed
if exitErr.ExitCode() == 1 {
return false, nil
}
if !opts.NoConfirm {
cmd.Args = append(cmd.Args, "-i")
}
return cmd
return false, fmt.Errorf("apk: isinstalled: %w, output: %s", err, output)
}
return true, nil
}

View File

@ -1,20 +1,21 @@
/*
* ALR - Any Linux Repository
* Copyright (C) 2024 Евгений Храмов
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
// It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors.
//
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package manager
@ -27,7 +28,15 @@ import (
// APT represents the APT package manager
type APT struct {
rootCmd string
CommonPackageManager
}
func NewAPT() *APT {
return &APT{
CommonPackageManager: CommonPackageManager{
noConfirmArg: "-y",
},
}
}
func (*APT) Exists() bool {
@ -43,10 +52,6 @@ func (*APT) Format() string {
return "deb"
}
func (a *APT) SetRootCmd(s string) {
a.rootCmd = s
}
func (a *APT) Sync(opts *Opts) error {
opts = ensureOpts(opts)
cmd := a.getCmd(opts, "apt", "update")
@ -134,19 +139,17 @@ func (a *APT) ListInstalled(opts *Opts) (map[string]string, error) {
return out, nil
}
func (a *APT) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd {
var cmd *exec.Cmd
if opts.AsRoot {
cmd = exec.Command(getRootCmd(a.rootCmd), mgrCmd)
cmd.Args = append(cmd.Args, opts.Args...)
cmd.Args = append(cmd.Args, args...)
} else {
cmd = exec.Command(mgrCmd, args...)
func (a *APT) IsInstalled(pkg string) (bool, error) {
cmd := exec.Command("dpkg-query", "-l", pkg)
output, err := cmd.CombinedOutput()
if err != nil {
if exitErr, ok := err.(*exec.ExitError); ok {
// Exit code 1 means the package is not installed
if exitErr.ExitCode() == 1 {
return false, nil
}
if opts.NoConfirm {
cmd.Args = append(cmd.Args, "-y")
}
return cmd
return false, fmt.Errorf("apt: isinstalled: %w, output: %s", err, output)
}
return true, nil
}

112
internal/manager/apt_rpm.go Normal file
View File

@ -0,0 +1,112 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package manager
import (
"fmt"
"os/exec"
"strings"
)
// APTRpm represents the APT-RPM package manager
type APTRpm struct {
CommonPackageManager
CommonRPM
}
func NewAPTRpm() *APTRpm {
return &APTRpm{
CommonPackageManager: CommonPackageManager{
noConfirmArg: "-y",
},
}
}
func (*APTRpm) Name() string {
return "apt-rpm"
}
func (*APTRpm) Format() string {
return "rpm"
}
func (*APTRpm) Exists() bool {
cmd := exec.Command("apt-config", "dump")
output, err := cmd.Output()
if err != nil {
return false
}
return strings.Contains(string(output), "RPM")
}
func (a *APTRpm) Sync(opts *Opts) error {
opts = ensureOpts(opts)
cmd := a.getCmd(opts, "apt-get", "update")
setCmdEnv(cmd)
err := cmd.Run()
if err != nil {
return fmt.Errorf("apt-get: sync: %w", err)
}
return nil
}
func (a *APTRpm) Install(opts *Opts, pkgs ...string) error {
opts = ensureOpts(opts)
cmd := a.getCmd(opts, "apt-get", "install", "-o", "APT::Install::Virtual=true")
cmd.Args = append(cmd.Args, pkgs...)
setCmdEnv(cmd)
cmd.Stdout = cmd.Stderr
err := cmd.Run()
if err != nil {
return fmt.Errorf("apt-get: install: %w", err)
}
return nil
}
func (a *APTRpm) InstallLocal(opts *Opts, pkgs ...string) error {
opts = ensureOpts(opts)
return a.Install(opts, pkgs...)
}
func (a *APTRpm) Remove(opts *Opts, pkgs ...string) error {
opts = ensureOpts(opts)
cmd := a.getCmd(opts, "apt-get", "remove")
cmd.Args = append(cmd.Args, pkgs...)
setCmdEnv(cmd)
err := cmd.Run()
if err != nil {
return fmt.Errorf("apt-get: remove: %w", err)
}
return nil
}
func (a *APTRpm) Upgrade(opts *Opts, pkgs ...string) error {
opts = ensureOpts(opts)
return a.Install(opts, pkgs...)
}
func (a *APTRpm) UpgradeAll(opts *Opts) error {
opts = ensureOpts(opts)
cmd := a.getCmd(opts, "apt-get", "dist-upgrade")
setCmdEnv(cmd)
err := cmd.Run()
if err != nil {
return fmt.Errorf("apt-get: upgradeall: %w", err)
}
return nil
}

View File

@ -0,0 +1,35 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package manager
import "os/exec"
type CommonPackageManager struct {
noConfirmArg string
}
func (m *CommonPackageManager) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd {
cmd := exec.Command(mgrCmd)
cmd.Args = append(cmd.Args, opts.Args...)
cmd.Args = append(cmd.Args, args...)
if opts.NoConfirm {
cmd.Args = append(cmd.Args, m.noConfirmArg)
}
return cmd
}

View File

@ -0,0 +1,72 @@
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package manager
import (
"bufio"
"fmt"
"os/exec"
"strings"
)
type CommonRPM struct{}
func (c *CommonRPM) ListInstalled(opts *Opts) (map[string]string, error) {
out := map[string]string{}
cmd := exec.Command("rpm", "-qa", "--queryformat", "%{NAME}\u200b%|EPOCH?{%{EPOCH}:}:{}|%{VERSION}-%{RELEASE}\\n")
stdout, err := cmd.StdoutPipe()
if err != nil {
return nil, err
}
err = cmd.Start()
if err != nil {
return nil, err
}
scanner := bufio.NewScanner(stdout)
for scanner.Scan() {
name, version, ok := strings.Cut(scanner.Text(), "\u200b")
if !ok {
continue
}
version = strings.TrimPrefix(version, "0:")
out[name] = version
}
err = scanner.Err()
if err != nil {
return nil, err
}
return out, nil
}
func (a *CommonRPM) IsInstalled(pkg string) (bool, error) {
cmd := exec.Command("rpm", "-q", "--whatprovides", pkg)
output, err := cmd.CombinedOutput()
if err != nil {
if exitErr, ok := err.(*exec.ExitError); ok {
if exitErr.ExitCode() == 1 {
return false, nil
}
}
return false, fmt.Errorf("rpm: isinstalled: %w, output: %s", err, output)
}
return true, nil
}

120
internal/manager/dnf.go Normal file
View File

@ -0,0 +1,120 @@
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
// It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors.
//
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package manager
import (
"fmt"
"os/exec"
)
type DNF struct {
CommonPackageManager
CommonRPM
}
func NewDNF() *DNF {
return &DNF{
CommonPackageManager: CommonPackageManager{
noConfirmArg: "-y",
},
}
}
func (*DNF) Exists() bool {
_, err := exec.LookPath("dnf")
return err == nil
}
func (*DNF) Name() string {
return "dnf"
}
func (*DNF) Format() string {
return "rpm"
}
// Sync выполняет upgrade всех установленных пакетов, обновляя их до более новых версий
func (d *DNF) Sync(opts *Opts) error {
opts = ensureOpts(opts) // Гарантирует, что opts не равен nil и содержит допустимые значения
cmd := d.getCmd(opts, "dnf", "upgrade")
setCmdEnv(cmd) // Устанавливает переменные окружения для команды
err := cmd.Run() // Выполняет команду
if err != nil {
return fmt.Errorf("dnf: sync: %w", err)
}
return nil
}
// Install устанавливает указанные пакеты с помощью DNF
func (d *DNF) Install(opts *Opts, pkgs ...string) error {
opts = ensureOpts(opts)
cmd := d.getCmd(opts, "dnf", "install", "--allowerasing")
cmd.Args = append(cmd.Args, pkgs...) // Добавляем названия пакетов к команде
setCmdEnv(cmd)
err := cmd.Run()
if err != nil {
return fmt.Errorf("dnf: install: %w", err)
}
return nil
}
// InstallLocal расширяет метод Install для установки пакетов, расположенных локально
func (d *DNF) InstallLocal(opts *Opts, pkgs ...string) error {
opts = ensureOpts(opts)
return d.Install(opts, pkgs...)
}
// Remove удаляет указанные пакеты с помощью DNF
func (d *DNF) Remove(opts *Opts, pkgs ...string) error {
opts = ensureOpts(opts)
cmd := d.getCmd(opts, "dnf", "remove")
cmd.Args = append(cmd.Args, pkgs...)
setCmdEnv(cmd)
err := cmd.Run()
if err != nil {
return fmt.Errorf("dnf: remove: %w", err)
}
return nil
}
// Upgrade обновляет указанные пакеты до более новых версий
func (d *DNF) Upgrade(opts *Opts, pkgs ...string) error {
opts = ensureOpts(opts)
cmd := d.getCmd(opts, "dnf", "upgrade")
cmd.Args = append(cmd.Args, pkgs...)
setCmdEnv(cmd)
err := cmd.Run()
if err != nil {
return fmt.Errorf("dnf: upgrade: %w", err)
}
return nil
}
// UpgradeAll обновляет все установленные пакеты
func (d *DNF) UpgradeAll(opts *Opts) error {
opts = ensureOpts(opts)
cmd := d.getCmd(opts, "dnf", "upgrade")
setCmdEnv(cmd)
err := cmd.Run()
if err != nil {
return fmt.Errorf("dnf: upgradeall: %w", err)
}
return nil
}

View File

@ -1,20 +1,21 @@
/*
* ALR - Any Linux Repository
* Copyright (C) 2024 Евгений Храмов
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
// It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors.
//
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package manager
@ -26,26 +27,22 @@ import (
var Args []string
type Opts struct {
AsRoot bool
NoConfirm bool
Args []string
}
var DefaultOpts = &Opts{
AsRoot: true,
NoConfirm: false,
}
// DefaultRootCmd is the command used for privilege elevation by default
var DefaultRootCmd = "sudo"
var managers = []Manager{
&Pacman{},
&APT{},
&DNF{},
&YUM{},
&APK{},
&Zypper{},
NewPacman(),
NewAPT(),
NewDNF(),
NewYUM(),
NewAPK(),
NewZypper(),
NewAPTRpm(),
}
// Register registers a new package manager
@ -62,8 +59,7 @@ type Manager interface {
Format() string
// Returns true if the package manager exists on the system.
Exists() bool
// Sets the command used to elevate privileges. Defaults to DefaultRootCmd.
SetRootCmd(string)
// Sync fetches repositories without installing anything
Sync(*Opts) error
// Install installs packages
@ -78,6 +74,8 @@ type Manager interface {
UpgradeAll(*Opts) error
// ListInstalled returns all installed packages mapped to their versions
ListInstalled(*Opts) (map[string]string, error)
//
IsInstalled(string) (bool, error)
}
// Detect returns the package manager detected on the system
@ -100,18 +98,10 @@ func Get(name string) Manager {
return nil
}
// getRootCmd returns rootCmd if it's not empty, otherwise returns DefaultRootCmd
func getRootCmd(rootCmd string) string {
if rootCmd != "" {
return rootCmd
}
return DefaultRootCmd
}
func setCmdEnv(cmd *exec.Cmd) {
cmd.Env = os.Environ()
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stdout = os.Stderr
cmd.Stderr = os.Stderr
}

View File

@ -1,20 +1,21 @@
/*
* ALR - Any Linux Repository
* Copyright (C) 2024 Евгений Храмов
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
// It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors.
//
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package manager
@ -27,7 +28,15 @@ import (
// Pacman represents the Pacman package manager
type Pacman struct {
rootCmd string
CommonPackageManager
}
func NewPacman() *Pacman {
return &Pacman{
CommonPackageManager: CommonPackageManager{
noConfirmArg: "--noconfirm",
},
}
}
func (*Pacman) Exists() bool {
@ -43,10 +52,6 @@ func (*Pacman) Format() string {
return "archlinux"
}
func (p *Pacman) SetRootCmd(s string) {
p.rootCmd = s
}
func (p *Pacman) Sync(opts *Opts) error {
opts = ensureOpts(opts)
cmd := p.getCmd(opts, "pacman", "-Sy")
@ -141,19 +146,17 @@ func (p *Pacman) ListInstalled(opts *Opts) (map[string]string, error) {
return out, nil
}
func (p *Pacman) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd {
var cmd *exec.Cmd
if opts.AsRoot {
cmd = exec.Command(getRootCmd(p.rootCmd), mgrCmd)
cmd.Args = append(cmd.Args, opts.Args...)
cmd.Args = append(cmd.Args, args...)
} else {
cmd = exec.Command(mgrCmd, args...)
func (p *Pacman) IsInstalled(pkg string) (bool, error) {
cmd := exec.Command("pacman", "-Q", pkg)
output, err := cmd.CombinedOutput()
if err != nil {
// Pacman returns exit code 1 if the package is not found
if exitErr, ok := err.(*exec.ExitError); ok {
if exitErr.ExitCode() == 1 {
return false, nil
}
if opts.NoConfirm {
cmd.Args = append(cmd.Args, "--noconfirm")
}
return cmd
return false, fmt.Errorf("pacman: isinstalled: %w, output: %s", err, output)
}
return true, nil
}

115
internal/manager/yum.go Normal file
View File

@ -0,0 +1,115 @@
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
// It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors.
//
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package manager
import (
"fmt"
"os/exec"
)
// YUM represents the YUM package manager
type YUM struct {
CommonPackageManager
CommonRPM
}
func NewYUM() *YUM {
return &YUM{
CommonPackageManager: CommonPackageManager{
noConfirmArg: "-y",
},
}
}
func (*YUM) Exists() bool {
_, err := exec.LookPath("yum")
return err == nil
}
func (*YUM) Name() string {
return "yum"
}
func (*YUM) Format() string {
return "rpm"
}
func (y *YUM) Sync(opts *Opts) error {
opts = ensureOpts(opts)
cmd := y.getCmd(opts, "yum", "upgrade")
setCmdEnv(cmd)
err := cmd.Run()
if err != nil {
return fmt.Errorf("yum: sync: %w", err)
}
return nil
}
func (y *YUM) Install(opts *Opts, pkgs ...string) error {
opts = ensureOpts(opts)
cmd := y.getCmd(opts, "yum", "install", "--allowerasing")
cmd.Args = append(cmd.Args, pkgs...)
setCmdEnv(cmd)
err := cmd.Run()
if err != nil {
return fmt.Errorf("yum: install: %w", err)
}
return nil
}
func (y *YUM) InstallLocal(opts *Opts, pkgs ...string) error {
opts = ensureOpts(opts)
return y.Install(opts, pkgs...)
}
func (y *YUM) Remove(opts *Opts, pkgs ...string) error {
opts = ensureOpts(opts)
cmd := y.getCmd(opts, "yum", "remove")
cmd.Args = append(cmd.Args, pkgs...)
setCmdEnv(cmd)
err := cmd.Run()
if err != nil {
return fmt.Errorf("yum: remove: %w", err)
}
return nil
}
func (y *YUM) Upgrade(opts *Opts, pkgs ...string) error {
opts = ensureOpts(opts)
cmd := y.getCmd(opts, "yum", "upgrade")
cmd.Args = append(cmd.Args, pkgs...)
setCmdEnv(cmd)
err := cmd.Run()
if err != nil {
return fmt.Errorf("yum: upgrade: %w", err)
}
return nil
}
func (y *YUM) UpgradeAll(opts *Opts) error {
opts = ensureOpts(opts)
cmd := y.getCmd(opts, "yum", "upgrade")
setCmdEnv(cmd)
err := cmd.Run()
if err != nil {
return fmt.Errorf("yum: upgradeall: %w", err)
}
return nil
}

View File

@ -1,33 +1,41 @@
/*
* ALR - Any Linux Repository
* Copyright (C) 2024 Евгений Храмов
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
// It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors.
//
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package manager
import (
"bufio"
"fmt"
"os/exec"
"strings"
)
// Zypper represents the Zypper package manager
type Zypper struct {
rootCmd string
CommonPackageManager
CommonRPM
}
func NewZypper() *YUM {
return &YUM{
CommonPackageManager: CommonPackageManager{
noConfirmArg: "-y",
},
}
}
func (*Zypper) Exists() bool {
@ -43,10 +51,6 @@ func (*Zypper) Format() string {
return "rpm"
}
func (z *Zypper) SetRootCmd(s string) {
z.rootCmd = s
}
func (z *Zypper) Sync(opts *Opts) error {
opts = ensureOpts(opts)
cmd := z.getCmd(opts, "zypper", "refresh")
@ -109,52 +113,3 @@ func (z *Zypper) UpgradeAll(opts *Opts) error {
}
return nil
}
func (z *Zypper) ListInstalled(opts *Opts) (map[string]string, error) {
out := map[string]string{}
cmd := exec.Command("rpm", "-qa", "--queryformat", "%{NAME}\u200b%|EPOCH?{%{EPOCH}:}:{}|%{VERSION}-%{RELEASE}\\n")
stdout, err := cmd.StdoutPipe()
if err != nil {
return nil, err
}
err = cmd.Start()
if err != nil {
return nil, err
}
scanner := bufio.NewScanner(stdout)
for scanner.Scan() {
name, version, ok := strings.Cut(scanner.Text(), "\u200b")
if !ok {
continue
}
version = strings.TrimPrefix(version, "0:")
out[name] = version
}
err = scanner.Err()
if err != nil {
return nil, err
}
return out, nil
}
func (z *Zypper) getCmd(opts *Opts, mgrCmd string, args ...string) *exec.Cmd {
var cmd *exec.Cmd
if opts.AsRoot {
cmd = exec.Command(getRootCmd(z.rootCmd), mgrCmd)
cmd.Args = append(cmd.Args, opts.Args...)
cmd.Args = append(cmd.Args, args...)
} else {
cmd = exec.Command(mgrCmd, args...)
}
if opts.NoConfirm {
cmd.Args = append(cmd.Args, "-y")
}
return cmd
}

View File

@ -1,20 +1,21 @@
/*
* ALR - Any Linux Repository
* Copyright (C) 2024 Евгений Храмов
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
// It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors.
//
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package osutils
@ -54,12 +55,12 @@ func copyDirOrFile(sourcePath, destPath string) error {
return err
}
if sourceInfo.IsDir() {
switch {
case sourceInfo.IsDir():
return copyDir(sourcePath, destPath, sourceInfo)
} else if sourceInfo.Mode().IsRegular() {
case sourceInfo.Mode().IsRegular():
return copyFile(sourcePath, destPath, sourceInfo)
} else {
// ignore non-regular files
default:
return nil
}
}

View File

@ -1,32 +1,34 @@
/*
* ALR - Any Linux Repository
* Copyright (C) 2024 Евгений Храмов
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
// It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors.
//
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package overrides
import (
"reflect"
"fmt"
"regexp"
"strings"
"plemya-x.ru/alr/internal/cpu"
"plemya-x.ru/alr/internal/db"
"plemya-x.ru/alr/pkg/distro"
"golang.org/x/exp/slices"
"golang.org/x/text/language"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/cpu"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
)
type Opts struct {
@ -100,7 +102,7 @@ func Resolve(info *distro.OSRelease, opts *Opts) ([]string, error) {
out = append(out, opts.Name)
for index, item := range out {
out[index] = strings.TrimPrefix(strings.ReplaceAll(item, "-", "_"), "_")
out[index] = strings.TrimPrefix(item, "_")
}
return out, nil
@ -146,63 +148,6 @@ func (o *Opts) WithLanguageTags(langs []string) *Opts {
return out
}
// ResolvedPackage is a ALR package after its overrides
// have been resolved
type ResolvedPackage struct {
Name string `sh:"name"`
Version string `sh:"version"`
Release int `sh:"release"`
Epoch uint `sh:"epoch"`
Description string `db:"description"`
Homepage string `db:"homepage"`
Maintainer string `db:"maintainer"`
Architectures []string `sh:"architectures"`
Licenses []string `sh:"license"`
Provides []string `sh:"provides"`
Conflicts []string `sh:"conflicts"`
Replaces []string `sh:"replaces"`
Depends []string `sh:"deps"`
BuildDepends []string `sh:"build_deps"`
OptDepends []string `sh:"opt_deps"`
}
func ResolvePackage(pkg *db.Package, overrides []string) *ResolvedPackage {
out := &ResolvedPackage{}
outVal := reflect.ValueOf(out).Elem()
pkgVal := reflect.ValueOf(pkg).Elem()
for i := 0; i < outVal.NumField(); i++ {
fieldVal := outVal.Field(i)
fieldType := fieldVal.Type()
pkgFieldVal := pkgVal.FieldByName(outVal.Type().Field(i).Name)
pkgFieldType := pkgFieldVal.Type()
if strings.HasPrefix(pkgFieldType.String(), "db.JSON") {
pkgFieldVal = pkgFieldVal.FieldByName("Val")
pkgFieldType = pkgFieldVal.Type()
}
if pkgFieldType.AssignableTo(fieldType) {
fieldVal.Set(pkgFieldVal)
continue
}
if pkgFieldVal.Kind() == reflect.Map && pkgFieldType.Elem().AssignableTo(fieldType) {
for _, override := range overrides {
overrideVal := pkgFieldVal.MapIndex(reflect.ValueOf(override))
if !overrideVal.IsValid() {
continue
}
fieldVal.Set(overrideVal)
break
}
}
}
return out
}
func parseLangs(langs []string, tags []language.Tag) ([]string, error) {
out := make([]string, len(tags)+len(langs))
for i, tag := range tags {
@ -221,3 +166,19 @@ func parseLangs(langs []string, tags []language.Tag) ([]string, error) {
out = slices.Compact(out)
return out, nil
}
func ReleasePlatformSpecific(release int, info *distro.OSRelease) string {
if info.ID == "altlinux" {
return fmt.Sprintf("alt%d", release)
}
if info.ID == "fedora" || slices.Contains(info.Like, "fedora") {
re := regexp.MustCompile(`platform:(\S+)`)
match := re.FindStringSubmatch(info.PlatformID)
if len(match) > 1 {
return fmt.Sprintf("%d.%s", release, match[1])
}
}
return fmt.Sprintf("%d", release)
}

View File

@ -1,20 +1,21 @@
/*
* ALR - Any Linux Repository
* Copyright (C) 2024 Евгений Храмов
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
// It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors.
//
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package overrides_test
@ -23,9 +24,11 @@ import (
"reflect"
"testing"
"plemya-x.ru/alr/internal/overrides"
"plemya-x.ru/alr/pkg/distro"
"github.com/stretchr/testify/assert"
"golang.org/x/text/language"
"gitea.plemya-x.ru/Plemya-x/ALR/internal/overrides"
"gitea.plemya-x.ru/Plemya-x/ALR/pkg/distro"
)
var info = &distro.OSRelease{
@ -193,3 +196,42 @@ func TestResolveLangs(t *testing.T) {
t.Errorf("expected %v, got %v", expected, names)
}
}
func TestReleasePlatformSpecific(t *testing.T) {
type testCase struct {
info *distro.OSRelease
expected string
}
for _, tc := range []testCase{
{
info: &distro.OSRelease{
ID: "centos",
Like: []string{"rhel", "fedora"},
PlatformID: "platform:el8",
},
expected: "1.el8",
},
{
info: &distro.OSRelease{
ID: "fedora",
PlatformID: "platform:f42",
},
expected: "1.f42",
},
{
info: &distro.OSRelease{
ID: "altlinux",
},
expected: "alt1",
},
{
info: &distro.OSRelease{
ID: "ubuntu",
},
expected: "1",
},
} {
assert.Equal(t, tc.expected, overrides.ReleasePlatformSpecific(1, tc.info))
}
}

View File

@ -1,20 +1,21 @@
/*
* ALR - Any Linux Repository
* Copyright (C) 2024 Евгений Храмов
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// This file was originally part of the project "LURE - Linux User REpository", created by Elara Musayelyan.
// It has been modified as part of "ALR - Any Linux Repository" by the ALR Authors.
//
// ALR - Any Linux Repository
// Copyright (C) 2025 The ALR Authors
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package pager

Some files were not shown because too many files have changed in this diff Show More