Compare commits

..

142 Commits

Author SHA1 Message Date
SteveLauC
d21141fefe chore: release v12.0.1 (#510) 2023-07-23 20:06:31 +08:00
SteveLauC
0ec0e5a9dd chore: bump ci toolchain and MSRV (#506)
* chore: bump ci toolchain and MSRV

* fix clippy on macOS
2023-07-19 10:54:34 +08:00
SteveLauC
9415d7c61f fix(oh-my-zsh): fix remote oh-my-zsh issue (#496)
* fix(oh-my-zsh): fix remote oh-my-zsh issue
2023-07-18 13:59:55 +08:00
SteveLauC
42188af02b CI: release to PyPI (#500) 2023-07-18 08:11:36 +08:00
signed-log
e9581bcf15 feat: add assume-yes to more Linux managers (#501)
* Add assume-yes options to most Linux managers

Add `assume-yes` to :
- SUSE (Micro) - TW (`zypper`)
- PCLinux OS (`apt`)
- Solus (`eopkg`)
- `pacdef`
- Clear Linux (`swupd`)
2023-07-17 15:47:13 +08:00
SteveLauC
6afe4f51c6 test: unit test for Solus (#504) 2023-07-17 13:31:46 +08:00
signed-log
f623746d6c Fix clippy warning about non_minimal_cfg (#505)
Fix clippy::non_minimal_cfg warning
2023-07-17 13:30:55 +08:00
signed-log
1ce4d66e74 Ass assume-yes to DragonflyBSD (#502)
Add assume-yes to DragonflyBSD
2023-07-17 11:40:00 +08:00
har7an
3735d5c537 steps/toolbx: Don't self-update and don't send notifications (#503)
steps/toolbx: Don't send notification

after finishing execution in the toolbx step, and don't perform another
self-update (because the application will already have done that).
2023-07-17 09:08:44 +08:00
SteveLauC
f3b1d2dfb3 Merge pull request from GHSA-f2wx-xjfw-xjv6
chore: bump tempfile to ~3.6
2023-07-15 09:24:08 +08:00
Steve Lau
7f7d2633cd chore: bump tempfile to ~3.6 2023-07-15 09:17:47 +08:00
Harsh Shandilya
afd95e3d5c fix(generic): add alternate binary name for spicetify (#486) 2023-07-14 16:14:06 +00:00
SteveLauC
8f72545894 docs(config): document 4 missing sections in example config file (#485) 2023-07-14 16:13:44 +00:00
SteveLauC
d0d447deac fix: fix wrong path in oh-my-bash (#478) 2023-07-14 16:13:28 +00:00
SteveLauC
53a8683788 ci: separate code-coverage and test-config-creation (#488) 2023-07-14 16:12:53 +00:00
Janek
81491a8d03 docs: apply corrections in config.example.toml (#492)
* Fix Issues in config.example.toml

* Update config.example.toml
2023-07-14 16:12:32 +00:00
Janek
83504754ac docs: add Karma commit messages to CONTRIBUTING.md (#493)
Add Karma commit messages to CONTRIBUTING.md
2023-07-14 16:11:59 +00:00
Marcelo Duarte Trevisani
2068c2c169 Update only base conda env (#495) 2023-07-14 16:11:18 +00:00
SteveLauC
dbac121a90 refactor(config): move sudo_command to section misc (#484) 2023-07-01 13:58:39 +00:00
Thomas Schönauer
b974938a33 v12 Cargo files update (#441) 2023-06-27 10:02:27 +00:00
SteveLauC
06cb88a1a1 test: test for config file creation and default config file parsing (#459) 2023-06-23 09:04:05 +00:00
SteveLauC
a6195d284c feat: support Bob (#461) 2023-06-23 09:03:57 +00:00
SteveLauC
5b8850e8a3 chore: update bug report issue template (#474) 2023-06-23 09:03:29 +00:00
SteveLauC
57546a07fc fix(pip3): prefer python when available (#471) 2023-06-23 09:02:58 +00:00
slowsage
d7709490ce fix: Run AstroUpdate before Lazy sync (#473) 2023-06-23 09:01:55 +00:00
slowsage
3e6c6e513b fix: handle no topgrade.toml but files in topgrade.d (#460) 2023-06-13 14:17:27 +00:00
SteveLauC
30858780cf refactor: unify the behavior of the steps that require sudo (#467) 2023-06-13 14:15:57 +00:00
SteveLauC
a7ddf4575a fix: fix Mist (#466) 2023-06-05 06:38:14 +00:00
Thomas Schönauer
470231c9d1 Revert "fix: fix mist" (#465)
Revert "fix: fix mist (#464)"

This reverts commit 282e336ac4.
2023-06-03 21:22:23 +00:00
SteveLauC
282e336ac4 fix: fix mist (#464) 2023-06-03 21:20:57 +00:00
SteveLauC
658829e4ff refactor: make update fn take &ExectionContext & put update fn together (#457) 2023-06-02 20:20:42 +00:00
SteveLauC
a0ff565220 docs: update CONTRIBUTING.md & config.example.toml (#458) 2023-06-01 11:02:39 +00:00
SteveLauC
7e48c5dedc fix: warn user about bad pattern paths before skipping step git (#456) 2023-06-01 07:16:01 +00:00
slowsage
03436b7f8f fix: Handle '# [include]'. Update default config (#450) 2023-06-01 07:15:49 +00:00
SteveLauC
3f5eedb83d fix: run AM without sudo (#454) 2023-05-31 07:01:45 +00:00
SteveLauC
234ad4bdd7 docs: add config-related CONTRIBUTING doc (#452) 2023-05-30 10:03:22 +00:00
slowsage
c7923393be fix: Write to correct config path when none exists. (#449) 2023-05-30 07:07:02 +00:00
slowsage
d4548b2f9a feat: Add arguments to pipupgrade and fix enable_pipupgrade check (#448) 2023-05-30 07:04:23 +00:00
SteveLauC
f6e8af186c feat: support Vanilla Linux (#447) 2023-05-29 11:45:11 +00:00
SteveLauC
58153635da refactor: remove Anarchy and Antergos as they are discontinued (#446) 2023-05-28 12:44:49 +00:00
SteveLauC
5358509825 fix: fix panic during container update (#445) 2023-05-27 14:12:45 +00:00
SteveLauC
1ab0232d96 feat: support deepin OS (#444) 2023-05-27 09:41:51 +00:00
SteveLauC
66860f1848 refactor: remove unnecessary qualification (#443) 2023-05-27 09:41:42 +00:00
SteveLauC
625f823f46 refactor: rename update fn name & some cleanup (#442) 2023-05-27 09:37:51 +00:00
Thomas Schönauer
6263ab7e10 Allow apt-get update to continue to apt-get upgrade with error code 100 (#440)
Allow apt-get update to continue with error code 100
2023-05-26 19:57:05 +00:00
Kevin Gavrois
7db991db9d Merge code for desktop notification between MacOS and Linux (#438) 2023-05-26 10:07:14 +02:00
SteveLauC
d75782892e docs: CONTRIBUTING.md (#439) 2023-05-26 09:34:20 +02:00
PolpOnline
cb7adc8ced Added ability to include directories as an extension of the config file (#421) 2023-05-25 12:22:11 +02:00
SteveLauC
7c3ba80270 fix: fix .NET language issue (#437)
Co-authored-by: Thomas Schönauer <37108907+DottoDev@users.noreply.github.com>
2023-05-25 09:24:53 +02:00
SteveLauC
76c39edc8b refactor: make all step functions take &ExectutionContext (#436) 2023-05-25 09:09:23 +02:00
SteveLauC
c20a300eea fix: use --platform opt when pulling containers (#435) 2023-05-23 08:47:47 +02:00
SteveLauC
de3902a9c9 fix: use env ZSH to compose oh-my-zsh install dir (#434) 2023-05-22 14:06:19 +02:00
SteveLauC
8bca671e9f fix: run deb-get without sudo (#430) 2023-05-20 19:35:17 +02:00
MonstrousOgre
54301a6a17 Adding local pip-review (#433) 2023-05-20 19:33:59 +02:00
Cat Core
f06b7c0807 Differentiate NPM and PNPM steps in name (#431) 2023-05-20 11:33:41 +02:00
SteveLauC
43c02cf7a7 feat: support maza (#427) 2023-05-17 19:18:03 +02:00
SteveLauC
3a1568e884 feat: support oh-my-bash (#425) 2023-05-17 19:17:37 +02:00
SteveLauC
14753a14e7 feat: support AppMan (#423) 2023-05-09 08:03:06 +02:00
Sourajyoti Basak
227e8dcc8d feat(shell): add packer.nu (#414)
* feat(shell): add `packer.nu`

* dependency update (#413)

* fix(main): move `packer.nu` step before linux package managers

---------

Co-authored-by: Thomas Schönauer <37108907+DottoDev@users.noreply.github.com>
2023-05-05 11:01:24 +02:00
signed-log
97fd2b2718 Make zypper dist-upgrade opt-in on SLE/Leap (#417)
Make zypper dist-upgrade opt-in on SLE/Leap

- Create a `suse_dup` config option
- Create a new `Distribution::OpenSuseTumbleweed` object along with `upgrade_opensuse_tumbleweed()`
    * The purpose of it is to ignore the config option on Tumblweed as
      zypper `dup` is the only way to update a Tumbleweed
2023-05-05 10:24:01 +02:00
SteveLauC
f30e36d7bb feat: support stew (#422) 2023-05-05 10:17:42 +02:00
SteveLauC
d640bc66f5 docs: update README for alternative config path (#419) 2023-05-04 08:36:36 +00:00
PolpOnline
a2331a2575 Add the ability to have the config file in $XDG_CONFIG_HOME/topgrade/topgrade.toml (#418) 2023-05-03 19:53:52 +00:00
Thomas Schönauer
26a2c3c266 v11.0.2 version bump (#416)
* dependency update

* Cargo.toml version bump
2023-05-01 18:26:18 +00:00
Thomas Schönauer
ceafcba88f dependency update (#413) 2023-05-01 15:02:16 +00:00
Thomas Schönauer
d7182b5a6e v11.0.0 bump (#410) 2023-04-30 19:02:26 +00:00
Thomas Schönauer
93ec1172fe Update README.md 2023-04-30 18:58:22 +00:00
Thomas Schönauer
609477a373 Update README.md 2023-04-30 18:57:23 +00:00
Thomas Schönauer
1d49af10a7 Update README.md 2023-04-30 18:57:07 +00:00
Utkarsh Gupta
327ed837c2 Replace directories with home & etcetera (#407)
* Use global lazy HOME_DIR

* Remove unused base_dirs

* Use `etcetera` instead of `directories`

---------

Co-authored-by: Thomas Schönauer <37108907+DottoDev@users.noreply.github.com>
2023-04-30 18:32:13 +00:00
Thomas Schönauer
d406e2aeab Assume Fedora Silverblue based on os-release and not on existence of rpm-ostree (#393)
* Do not assume silverblue if rpm-ostree is available

* Fix typo

* Fix config error
2023-04-30 18:22:08 +00:00
dependabot[bot]
0991cc8a6f Bump enumflags2 from 0.7.5 to 0.7.7 (#408) 2023-04-24 20:37:00 +00:00
Brian Riccardi
ac6330fac8 Added support to 'mamba' (alternative to 'conda' with the exact same commands/interface) (#395) 2023-04-17 14:19:59 +00:00
dependabot[bot]
29f0d229d3 Bump h2 from 0.3.16 to 0.3.17 (#404) 2023-04-17 14:19:48 +00:00
Roey Darwish Dror
3dd11f7b52 No need to run self-update in Rustup (#403) 2023-04-05 12:42:47 +00:00
Roey Darwish Dror
ddb1a021bb Display the preamble in Linux only if notify-send is installed (#401) 2023-04-05 12:34:47 +00:00
PolpOnline
565aa405be Add no-self-update config and flag (#388) 2023-03-22 21:05:21 +00:00
Utkarsh Gupta
907465f891 run_custom_command: allow using interactive shell on unix (#383) 2023-03-17 16:28:58 +00:00
Trevor Sullivan
250485c826 Add Scoop manifest link for Windows installation (#384) 2023-03-15 07:40:31 +00:00
Thomas Schönauer
3a3f22b4e5 V10 3 2 bugfix + revert #347 (#382)
* Revert "run_custom_command: use interactive shell on unix (#347)"

This reverts commit d767ef31a5.

* v10-3-3 + revert of #347
2023-03-13 19:27:33 +00:00
Thomas de Queiroz Barros
a3628d0d49 Add sudo_command option (#379)
This allows the user to specify the preferred sudo command to be used
instead of the command chosen by Sudo::detect
2023-03-13 19:23:37 +00:00
Thomas Schönauer
462016e51e 10.3.2 patch (#378)
* 10.3.2 patch

* Clippy
2023-03-12 20:37:41 +00:00
Thomas Schönauer
199b81183b Update check-and-lint.yaml to use Rust version 1.68.0 2023-03-12 20:22:58 +00:00
Thomas Schönauer
342d7f7209 skip skip-notify warning on Win (#362) 2023-03-03 11:58:58 +00:00
Isaac Tay
9c2d121fc9 cargo: add cleanup step (using cargo-cache) (#371) 2023-03-03 11:58:15 +00:00
Roey Darwish Dror
7728819133 Support antidote (#368) 2023-02-26 21:45:43 +00:00
arctic-penguin
a5d5d987d2 pacdef: support new version 1.x (#364) 2023-02-23 22:01:53 +00:00
TGRCDev
fae5d80f0a pip3: Check for EXTERNALLY-MANAGED (PEP 668) (#367) 2023-02-23 22:01:26 +00:00
Thomas Schönauer
2369e371be apt: Recognise mist (#351) 2023-02-18 21:22:02 +00:00
Jason Stelzer
e3b71b647f Silence misleading warning on other platforms. (#353)

2023-02-07 17:21:15 +00:00
Guilherme Silva
e224ea38b3 CI: Update cross to v0.2.5 (#354) 2023-02-07 17:19:46 +00:00
Thomas Schönauer
8ec37bcd44 vim: Adds Astrovim support (#352) 2023-02-03 13:46:09 +00:00
Thomas Schönauer
6b7f6f4cc7 ruby_gems: Fixes asdf (#350) 2023-02-02 21:48:48 +00:00
Utkarsh Gupta
d767ef31a5 run_custom_command: use interactive shell on unix (#347) 2023-02-02 19:46:11 +00:00
Dan Sully
fcf776fe07 Add support for please (access elevation) (#310)
* Add support for please (access elevation)

Please is a sudo-like tool written in Rust.

https://gitlab.com/edneville/please

* Fixes code typo

---------

Co-authored-by: Thomas Schönauer <37108907+DottoDev@users.noreply.github.com>
Co-authored-by: Thomas Schönauer <t.schoenauer@hgs-wt.at>
2023-02-02 19:22:56 +00:00
edi
58060dda09 use documented way of updating (#344) 2023-01-31 22:19:01 +00:00
Thomas Schönauer
8cfc8d66be v10.3.1 patch (#342) 2023-01-30 21:24:06 +00:00
edi
9dcc8fdd0d (neo)vim: topgrade should only invoke plugin managers not plugins (#341)
* fix upgrade order of (n)vim plugins

* treesitter should use the synchronous cmd

* add lazy pkg manager for neovim

* fix lazy cmd

* change calls

* add autocmd, remove ts and coc

* fix vimscript err invalid range

---------

Co-authored-by: Thomas Schönauer <37108907+DottoDev@users.noreply.github.com>
2023-01-30 18:42:13 +00:00
Thomas Schönauer
828477b255 Update README.md 2023-01-30 18:41:48 +00:00
arctic-penguin
4eae1fedf7 fix ignored config display_preamble = false (#340)
Bug was introduced in f1e4009. Fixes #337.
2023-01-30 18:41:00 +00:00
Thomas Schönauer
1051e4cf47 AM fix + version bump (#335) 2023-01-29 21:53:26 +00:00
Thomas Schönauer
80a95cb404 Clippy (#331) 2023-01-29 19:31:37 +00:00
Thomas Schönauer
ab630cfbc6 v10.2.5 release (#330)
* Don't show desktop notification on error (if `skip_notify = true`) (#275)

* Use ─ (U+2500) to draw borders (#282)

* Adds Pclinuxos support (#283)

* Add Devkitpro Pacman support (#291)

* Added support for Neovim package manager lazy.nvim (#293)

* Added support for lazy.nvim

From https://github.com/folke/lazy.nvim
Authored-by: Jacob Lane Ledbetter <jledbetter460@gmail.com>

* Make garuda-update update AUR packages by default (#296)

* fix(#298): Don't throw error if no Helm repository found (#305)

* Skip .NET when `dotnet tool list` is not successful (#302)

* feat(pacstall): add `-y` flag variant (#312)

* Add openSUSE MicroOS support (#315)

* Adds notify-send timeout of 10s (#318)

* Don't run yum when rpm-ostree is available (#313)

* don't run yum when rpm-ostree is available

* Clippy fix

* rpm-ostree: set default value to true

* Fixes if loop error

* Fixes gem update --system requires sudo now (#317)

* Fixes gem update --system requires sudo now

* rubygem: Adds arg -EH to sudo

* Use fixed nala path instead of which(nala) (#314)

* Adds notify-send bug warning when topgrade is run (#324)

* Adds notify-send bug warning when topgrade is run

* fix typo + clippy

* notify-send warning respects skip_notify flag

* nix: Adds additional arguments support (#325)

* Adds pip-review and pipupgrade support (#316)

* Adds pip-review and pipupgrade support

* Python: fixes pip_review and pipupgrade

* v10.2.5 patch (#329)

* WSL: Adds new wsl --update flags (#327)

* wsl: Updates available flags

* Clippy fix

* Add WslUpdate runner

* wsl: Code Typo

* wsl: Code Typos

* wsl: Code Typos

* wsl: Code Typo

* Adds AM Package Manager (#328)

* Adds AM Package Manager

* Clippy fixes

* Cargo fmt

* Moves am to linux only in main file

---------

Co-authored-by: Guilherme Silva <626206+guihkx@users.noreply.github.com>
Co-authored-by: Gabriel Augendre <gabriel@augendre.info>
Co-authored-by: Cat Core <34719527+arthurbambou@users.noreply.github.com>
Co-authored-by: Hugo Haas <hugoh@hugoh.net>
Co-authored-by: Baptiste <32563450+BapRx@users.noreply.github.com>
Co-authored-by: bbx0 <39773919+bbx0@users.noreply.github.com>
Co-authored-by: Sourajyoti Basak <wiz28@protonmail.com>
2023-01-29 19:19:27 +00:00
edi
c13e14080c Add Lazy, a Neovim plugin manager (#326)
* fix upgrade order of (n)vim plugins

* treesitter should use the synchronous cmd

* add lazy pkg manager for neovim

* fix lazy cmd

---------

Co-authored-by: Thomas Schönauer <37108907+DottoDev@users.noreply.github.com>
2023-01-29 18:49:56 +00:00
edi
4abbee99cc fix upgrade order of (n)vim plugins (#322)
* fix upgrade order of (n)vim plugins

* treesitter should use the synchronous cmd

---------

Co-authored-by: Thomas Schönauer <37108907+DottoDev@users.noreply.github.com>
2023-01-27 21:41:48 +00:00
Giovanni Merlino
45d935eda3 Fix missing separator for Pkgin (pkgsrc) (#307)
* Fix missing separator for Pkgin (pkgsrc)

* Fix whitespace (giving issues with cargo fmt?)

---------

Co-authored-by: Thomas Schönauer <37108907+DottoDev@users.noreply.github.com>
2023-01-27 21:34:27 +00:00
pwygab
b4c5efde50 Fix small typo in warning when notify-send fails (#319)
Fix small typo
2023-01-27 20:52:20 +00:00
Roey Darwish Dror
cba9dc1c2c Fix windows build (#303) 2023-01-09 08:50:21 +01:00
dependabot[bot]
938647123c Bump tokio from 1.8.5 to 1.18.4 (#301) 2023-01-06 23:13:01 +00:00
Jacob Lane Ledbetter
9f24f6474e Add dnf config to config example (#292) 2022-12-30 22:52:04 +00:00
Thomas Schönauer
814e39644c fixes dotnet update + version bump 10.2.4 (#274) 2022-12-18 15:37:10 +00:00
Thomas Schönauer
eb51be0732 10.2.3 release (#272) 2022-12-18 14:20:00 +00:00
Thomas Schönauer
56a717dcc6 Fixes dotnet if dotnet tool is not available (#229)
* Fixes dotnet if dotnet tool si not available

* dotnet: multi-lang support

* Fixes dotnet for non-english terminals
2022-12-18 14:12:36 +00:00
Thomas Schönauer
ee353ccb66 adds fish_update_completions to fish step (#270) 2022-12-18 14:11:36 +00:00
Thomas Schönauer
33cea0e5b6 10.2.3 release (#271)
10.2.3
2022-12-18 14:07:31 +00:00
Thomas Schönauer
78a491a976 Fixes rubygems on .deb distros (#268)
Fixes rubygem on systems with the .deb ruby package
2022-12-15 20:27:27 +00:00
Thomas Schönauer
9553be04e4 Fixes vcpkg when installed as root (#266) 2022-12-15 11:39:25 +00:00
Thomas Schönauer
81928f55a2 Fix notify not available (#257) 2022-12-15 11:34:18 +00:00
Mark Nefedov
4e56bf07f3 Add options to disable Yarn and Pnpm individually. (#260)
* Add options to disable Yarn and Pnpm individualy.

* cargo fmt

Co-authored-by: Thomas Schönauer <37108907+DottoDev@users.noreply.github.com>
2022-12-14 21:42:48 +00:00
sandal
9f424f03c3 Added a step for fetching and building treesitter grammars for helix (#263) 2022-12-14 08:12:39 +00:00
Thomas Kosiewski
1e14b3bf28 Add helm (#255) (#256)
* Add `helm` (#255)

Signed-off-by: Thomas Kosiewski <thoma471@googlemail.com>

* Sorted steps alphabetically

Signed-off-by: Thomas Kosiewski <thoma471@googlemail.com>

Signed-off-by: Thomas Kosiewski <thoma471@googlemail.com>
2022-12-08 21:47:57 +00:00
Thomas Schönauer
51e7a31f48 10.2.2 (#253) 2022-12-06 19:48:46 +00:00
Thomas Schönauer
9847fd9d4d Merge branch 'master' into dev 2022-12-06 19:23:45 +00:00
Thomas Schönauer
24ef44291f 10.2.2 version bump (#252) 2022-12-06 19:22:06 +00:00
Julien ITARD
ebb0c5a6d8 Fix RubyGems step (#251)
* Bump tokio from 1.5.1 to 1.8.4 (#245)

Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.5.1 to 1.8.4.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.5.1...tokio-1.8.4)

---
updated-dependencies:
- dependency-name: tokio
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Fix RubyGems step

* Fix style

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-06 19:13:07 +00:00
Armel Soro
1dee462175 Fix misleading log message when detecting rbenv (#249) 2022-12-05 17:42:26 +00:00
Armel Soro
dc82b8b766 Fix issue with RubyGems update command (#248) 2022-12-05 13:16:03 +00:00
dependabot[bot]
73888e7669 Bump tokio from 1.5.1 to 1.8.4 (#245)
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.5.1 to 1.8.4.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.5.1...tokio-1.8.4)

---
updated-dependencies:
- dependency-name: tokio
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-30 21:56:52 +00:00
dependabot[bot]
46a010cc8f Bump tokio from 1.5.1 to 1.8.4 (#147)
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.5.1 to 1.8.4.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.5.1...tokio-1.8.4)

---
updated-dependencies:
- dependency-name: tokio
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Thomas Schönauer <37108907+DottoDev@users.noreply.github.com>
2022-11-30 21:29:39 +00:00
Thomas Schönauer
96e4de4594 10.2.1 release (#244) 2022-11-30 21:08:15 +00:00
Thomas Schönauer
70616b2ec5 Version bump (#237) 2022-11-29 15:17:20 +00:00
Thomas Schönauer
22ab07d88e Fixes paru/yay -Pw return code error (#228) 2022-11-29 06:49:26 +00:00
Guilherme Silva
70045fca29 CI: Force color support for Rustfmt (#230)
Co-authored-by: Thomas Schönauer <37108907+DottoDev@users.noreply.github.com>
2022-11-27 00:21:29 +01:00
Marcin Puc
f6ec6c76db Add shell completion and manpage generation (#233) 2022-11-26 19:42:35 +00:00
Jason Stelzer
37b900c56a Add garuda-update (#227) 2022-11-26 19:38:18 +00:00
Guilherme Silva
e26ec4d9e0 Fix clippy warning for DragonFly BSD (#231) 2022-11-26 17:52:14 +00:00
Rebecca Turner
b31778bdd8 Add Sudo type (#221)
* Create `Sudo` type and `SudoKind` enum

* Fix build

* reformat

* Fix choco on windows

* Fix linux

* Fix linux more

* more fix stuff hehe hoho hahaha

* more fix stuff hehe hoho hahaha

Co-authored-by: Thomas Schönauer <37108907+DottoDev@users.noreply.github.com>
2022-11-25 22:19:32 +00:00
Thomas Schönauer
526c4c9a58 Fixes typo 2022-11-25 18:44:36 +00:00
Julien ITARD
3c1dda0c39 Add RubyGems update (#217) 2022-11-24 19:21:03 +00:00
Ruben Molina
25c5057171 Add support for juliaup (#208)
* Add support for juliaup

* Update config.rs

* Change the position for Juliaup Step.

* Update generic.rs
2022-11-24 19:17:58 +00:00
Rebecca Turner
e456155562 Add pre_sudo option (#219)
* Add `pre_sudo` option
2022-11-24 19:15:43 +00:00
Guilherme Silva
f2c7e4848e CI: Install only necessary components (#218)
* CI: Install only necessary components
2022-11-24 19:15:13 +00:00
54 changed files with 3733 additions and 1800 deletions

View File

@@ -2,32 +2,79 @@
name: Bug report name: Bug report
about: Topgrade is misbehaving about: Topgrade is misbehaving
title: '' title: ''
labels: '' labels: 'bug'
assignees: '' assignees: ''
--- ---
<!-- If you're here to report about a "No asset found" error, please make sure that an hour has been passed since the last release was made. --> <!--
Thanks for taking the time to fill out this bug report!
Please make sure to
[search for existing issues](https://github.com/topgrade-rs/topgrade/issues)
before filing a new one!
## What did you expect to happen? Questions labeled with `Optional` can be skipped.
-->
<!--
If you're here to report about a "No asset found" error, please make sure that
an hour has been passed since the last release was made.
-->
## What actually happened? ## Erroneous Behavior
<!--
What actually happened?
-->
## Expected Behavior
<!--
Describe the expected behavior
-->
## Steps to reproduce
<!--
A minimal example to reproduce the issue
-->
## Possible Cause (Optional)
<!--
If you know the possible cause of the issue, please tell us.
-->
## Problem persists without calling from topgrade
<!--
Execute the erroneous command directly to see if the problem persists
-->
- [ ] Yes
## Configuration file (Optional)
<!--
Paste your configuration file inside the code block if you think this issue is
related to configuration.
-->
```toml
```
## Additional Details ## Additional Details
- Which operating system or Linux distribution are you using? - Operation System/Version
- How did you install Topgrade? <!-- For example, Fedora Linux 38 -->
- Which version are you running? <!-- Check with `topgrade -V` -->
<!-- - Installation
Run `topgrade --dry-run` to see which commands Topgrade is running. <!--
If the command seems wrong and you know why please tell us so. How did you install topgrade: build from repo / crates.io (cargo install topgrade)
If the command seems fine try to run it yourself and tell us if you got a different result from Topgrade. / package manager (which one) / other (describe)
-->
- Topgrade version (`topgrade -V`)
## Verbose Output (`topgrade -v`)
<!--
Paste the verbose output into the pre-tags
--> -->
<details> <details>
<!-- Paste the output of the problematic command with `-v` into the pre-tags -->
<pre> <pre>
</pre> </pre>

View File

@@ -1,6 +1,7 @@
## Standards checklist: ## Standards checklist:
- [ ] The PR title is descriptive. - [ ] The PR title is descriptive.
- [ ] I have read `CONTRIBUTING.md`
- [ ] The code compiles (`cargo build`) - [ ] The code compiles (`cargo build`)
- [ ] The code passes rustfmt (`cargo fmt`) - [ ] The code passes rustfmt (`cargo fmt`)
- [ ] The code passes clippy (`cargo clippy`) - [ ] The code passes clippy (`cargo clippy`)

View File

@@ -7,8 +7,8 @@ on:
name: CI name: CI
env: env:
RUST_VER: '1.60.0' RUST_VER: '1.71.0'
CROSS_VER: '0.2.4' CROSS_VER: '0.2.5'
CARGO_NET_RETRY: 3 CARGO_NET_RETRY: 3
jobs: jobs:
@@ -23,10 +23,11 @@ jobs:
uses: dtolnay/rust-toolchain@master uses: dtolnay/rust-toolchain@master
with: with:
toolchain: '${{ env.RUST_VER }}' toolchain: '${{ env.RUST_VER }}'
targets: ${{ matrix.target }} components: rustfmt
components: clippy, rustfmt
- name: Run cargo fmt - name: Run cargo fmt
env:
TERM: xterm-256color
run: | run: |
cargo fmt --all -- --check cargo fmt --all -- --check
@@ -72,8 +73,7 @@ jobs:
uses: dtolnay/rust-toolchain@master uses: dtolnay/rust-toolchain@master
with: with:
toolchain: '${{ env.RUST_VER }}' toolchain: '${{ env.RUST_VER }}'
targets: ${{ matrix.target }} components: clippy
components: clippy, rustfmt
- name: Setup Rust Cache - name: Setup Rust Cache
uses: Swatinem/rust-cache@v2 uses: Swatinem/rust-cache@v2

View File

@@ -4,7 +4,7 @@ on:
# types: # types:
# - completed # - completed
release: release:
types: [published, edited] types: [published]
name: Publish to crates.io on release name: Publish to crates.io on release

View File

@@ -0,0 +1,21 @@
name: Test Configuration File Creation
on:
pull_request:
env:
CARGO_TERM_COLOR: always
jobs:
TestConfig:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: |
CONFIG_PATH=~/.config/topgrade.toml;
if [ -f "$CONFIG_PATH" ]; then rm $CONFIG_PATH; fi
cargo build;
./target/debug/topgrade --dry-run --only system;
stat $CONFIG_PATH;

99
.github/workflows/update_pypi.yml vendored Normal file
View File

@@ -0,0 +1,99 @@
name: Update PyPi
on:
release:
types: [published]
permissions:
contents: read
jobs:
linux:
runs-on: ubuntu-latest
strategy:
matrix:
target: [x86_64, x86, aarch64]
steps:
- uses: actions/checkout@v3
- name: Build wheels
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.target }}
args: --release --out dist
sccache: 'true'
manylinux: auto
- name: Upload wheels
uses: actions/upload-artifact@v3
with:
name: wheels
path: dist
windows:
runs-on: windows-latest
strategy:
matrix:
target: [x64, x86]
steps:
- uses: actions/checkout@v3
- name: Build wheels
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.target }}
args: --release --out dist
sccache: 'true'
- name: Upload wheels
uses: actions/upload-artifact@v3
with:
name: wheels
path: dist
macos:
runs-on: macos-latest
strategy:
matrix:
target: [x86_64, aarch64]
steps:
- uses: actions/checkout@v3
- name: Build wheels
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.target }}
args: --release --out dist
sccache: 'true'
- name: Upload wheels
uses: actions/upload-artifact@v3
with:
name: wheels
path: dist
sdist:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build sdist
uses: PyO3/maturin-action@v1
with:
command: sdist
args: --out dist
- name: Upload sdist
uses: actions/upload-artifact@v3
with:
name: wheels
path: dist
release:
name: Release
runs-on: ubuntu-latest
if: "startsWith(github.ref, 'refs/tags/')"
needs: [linux, windows, macos, sdist]
steps:
- uses: actions/download-artifact@v3
with:
name: wheels
- name: Publish to PyPI
uses: PyO3/maturin-action@v1
env:
MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
with:
command: upload
args: --skip-existing *

137
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,137 @@
## Contributing to `topgrade`
Thank you for your interest in contributing to `topgrade`!
We welcome and encourage contributions of all kinds, such as:
1. Issue reports or feature requests
2. Documentation improvements
3. Code (PR or PR Review)
Please follow the [Karma Runner guidelines](http://karma-runner.github.io/6.2/dev/git-commit-msg.html)
for commit messages.
## Adding a new `step`
In `topgrade`'s term, package manager is called `step`.
To add a new `step` to `topgrade`:
1. Add a new variant to
[`enum Step`](https://github.com/topgrade-rs/topgrade/blob/cb7adc8ced8a77addf2cb051d18bba9f202ab866/src/config.rs#L100)
```rust
pub enum Step {
// Existed steps
// ...
// Your new step here!
// You may want it to be sorted alphabetically because that looks great:)
Xxx,
}
```
2. Implement the update function
You need to find the appropriate location where this update function goes, it should be
a file under [`src/steps`](https://github.com/topgrade-rs/topgrade/tree/master/src/steps),
the file names are self-explanatory, for example, `step`s related to `zsh` are
placed in [`steps/zsh.rs`](https://github.com/topgrade-rs/topgrade/blob/master/src/steps/zsh.rs).
Then you implement the update function, and put it in the file where it belongs.
```rust
pub fn run_xxx(ctx: &ExecutionContext) -> Result<()> {
// Check if this step is installed, if not, then this update will be skipped.
let xxx = require("xxx")?;
// Print the separator
print_separator("xxx");
// Invoke the new step to get things updated!
ctx.run_type()
.execute("xxx")
.arg(/* args required by this step */)
.status_checked()
}
```
Such a update function would be conventionally named `run_xxx()`, where `xxx`
is the name of the new step, and it should take a argument of type
`&ExecutionContext`, this is adequate for most cases unless some extra stuff is
needed (You can find some examples where extra arguments are needed
[here](https://github.com/topgrade-rs/topgrade/blob/7e48c5dedcfd5d0124bb9f39079a03e27ed23886/src/main.rs#L201-L219)).
Update function would usually do 3 things:
1. Check if the step is installed
2. Output the Separator
3. Invoke the step
Still, this is sufficient for most tools, but you may need some extra stuff
with complicated `step`.
3. Finally, invoke that update function in `main.rs`
```rust
runner.execute(Step::Xxx, "xxx", || ItsModule::run_xxx(&ctx))?;
```
We use [conditional compilation](https://doc.rust-lang.org/reference/conditional-compilation.html)
to separate the steps, for example, for steps that are Linux-only, it goes
like this:
```
#[cfg(target_os = "linux")]
{
// Xxx is Linux-only
runner.execute(Step::Xxx, "xxx", || ItsModule::run_xxx(&ctx))?;
}
```
Congrats, you just added a new `step`:)
## Modification to the configuration entries
If your PR has the configuration options
(in [`src/config.rs`](https://github.com/topgrade-rs/topgrade/blob/master/src/config.rs))
modified:
1. Adding new options
2. Changing the existing options
Be sure to apply your changes to
[`config.example.toml`](https://github.com/topgrade-rs/topgrade/blob/master/config.example.toml),
and have some basic documentations guiding user how to use these options.
## Before you submit your PR
Make sure your patch passes the following tests on your host:
```shell
$ cargo build
$ cargo fmt
$ cargo clippy
$ cargo test
```
Don't worry about other platforms, we have most of them covered in our CI.
## Some tips
1. Locale
Some `step` respects locale, which means their output can be in language other
than English, we should not do check on it.
For example, one may want to check if a tool works by doing this:
```rust
let output = Command::new("xxx").arg("--help").output().unwrap();
let stdout = from_utf8(output.stdout).expect("Assume it is UTF-8 encoded");
if stdout.contains("help") {
// xxx works
}
```
If `xxx` respects locale, then the above code should work on English system,
on a system that does not use English, e.g., it uses Chinese, that `"help"` may be
translated to `"帮助"`, and the above code won't work.

1365
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,9 +4,8 @@ description = "Upgrade all the things"
categories = ["os"] categories = ["os"]
keywords = ["upgrade", "update"] keywords = ["upgrade", "update"]
license = "GPL-3.0" license = "GPL-3.0"
# license-file = "LICENSE"
repository = "https://github.com/topgrade-rs/topgrade" repository = "https://github.com/topgrade-rs/topgrade"
version = "10.2.0" version = "12.0.1"
authors = ["Roey Darwish Dror <roey.ghost@gmail.com>", "Thomas Schönauer <t.schoenauer@hgs-wt.at>"] authors = ["Roey Darwish Dror <roey.ghost@gmail.com>", "Thomas Schönauer <t.schoenauer@hgs-wt.at>"]
exclude = ["doc/screenshot.gif"] exclude = ["doc/screenshot.gif"]
edition = "2021" edition = "2021"
@@ -22,12 +21,15 @@ path = "src/main.rs"
[dependencies] [dependencies]
home = "~0.5" home = "~0.5"
directories = "~4.0" etcetera = "~0.8"
once_cell = "~1.17"
serde = { version = "~1.0", features = ["derive"] } serde = { version = "~1.0", features = ["derive"] }
toml = "0.5" toml = "0.5"
which_crate = { version = "~4.1", package = "which" } which_crate = { version = "~4.1", package = "which" }
shellexpand = "~2.1" shellexpand = "~2.1"
clap = { version = "~3.1", features = ["cargo", "derive"] } clap = { version = "~3.1", features = ["cargo", "derive"] }
clap_complete = "~3.1"
clap_mangen = "~0.1"
walkdir = "~2.3" walkdir = "~2.3"
console = "~0.15" console = "~0.15"
lazy_static = "~1.4" lazy_static = "~1.4"
@@ -35,19 +37,19 @@ chrono = "~0.4"
glob = "~0.3" glob = "~0.3"
strum = { version = "~0.24", features = ["derive"] } strum = { version = "~0.24", features = ["derive"] }
thiserror = "~1.0" thiserror = "~1.0"
tempfile = "~3.2" tempfile = "~3.6"
cfg-if = "~1.0" cfg-if = "~1.0"
tokio = { version = "~1.5", features = ["process", "rt-multi-thread"] } tokio = { version = "~1.18", features = ["process", "rt-multi-thread"] }
futures = "~0.3" futures = "~0.3"
regex = "~1.5" regex = "~1.7"
semver = "~1.0" semver = "~1.0"
shell-words = "~1.1" shell-words = "~1.1"
color-eyre = "0.6.2" color-eyre = "~0.6"
tracing = { version = "0.1.37", features = ["attributes", "log"] } tracing = { version = "~0.1", features = ["attributes", "log"] }
tracing-subscriber = { version = "0.3.16", features = ["env-filter", "time"] } tracing-subscriber = { version = "~0.3", features = ["env-filter", "time"] }
merge = "~0.1"
[target.'cfg(target_os = "macos")'.dependencies] regex-split = "~0.1"
notify-rust = "~4.5" notify-rust = "~4.8"
[package.metadata.generate-rpm] [package.metadata.generate-rpm]
assets = [{source = "target/release/topgrade", dest="/usr/bin/topgrade"}] assets = [{source = "target/release/topgrade", dest="/usr/bin/topgrade"}]

View File

@@ -1,4 +0,0 @@
# Workaround for: https://github.com/cross-rs/cross/issues/1100
# TODO: Remove this file altogether once a new version of cross (after v0.2.4) is released.
[target.x86_64-unknown-freebsd.env]
passthrough = ["AR_x86_64_unknown_freebsd=x86_64-unknown-freebsd12-ar"]

View File

@@ -10,7 +10,12 @@
<img alt="Demo" src="doc/screenshot.gif" width="550px"> <img alt="Demo" src="doc/screenshot.gif" width="550px">
</div> </div>
## Maintainers Wanted
I currently have not enough time to maintain this project on the level required and which the project deserves. For this reason I'm asking the community to help supporting the project, to help and work on resolving issues and create new features. Thanks for all your help.
## Introduction ## Introduction
> **Note** > **Note**
@@ -28,11 +33,14 @@ To remedy this, **Topgrade** detects which tools you use and runs the appropriat
- NixOS: [Nixpkgs](https://search.nixos.org/packages?show=topgrade) - NixOS: [Nixpkgs](https://search.nixos.org/packages?show=topgrade)
- Void Linux: [XBPS](https://voidlinux.org/packages/?arch=x86_64&q=topgrade) - Void Linux: [XBPS](https://voidlinux.org/packages/?arch=x86_64&q=topgrade)
- macOS: [Homebrew](https://formulae.brew.sh/formula/topgrade) or [MacPorts](https://ports.macports.org/port/topgrade/) - macOS: [Homebrew](https://formulae.brew.sh/formula/topgrade) or [MacPorts](https://ports.macports.org/port/topgrade/)
- Windows: [Scoop](https://github.com/ScoopInstaller/Main/blob/master/bucket/topgrade.json)
- PyPi: [pip](https://pypi.org/project/topgrade/)
Other systems users can either use `cargo install` or the compiled binaries from the release page. Other systems users can either use `cargo install` or the compiled binaries from the release page.
The compiled binaries contain a self-upgrading feature. The compiled binaries contain a self-upgrading feature.
Topgrade requires Rust 1.60 or above. > Currently, Topgrade requires Rust 1.65 or above. In general, Topgrade tracks
> the latest stable toolchain.
## Usage ## Usage
@@ -43,16 +51,32 @@ Visit the documentation at [topgrade-rs.github.io](https://topgrade-rs.github.io
> **Warning** > **Warning**
> Work in Progress > Work in Progress
## Customization ## Configuration
See `config.example.toml` for an example configuration file. See `config.example.toml` for an example configuration file.
### Configuration Path ### Configuration Path
The configuration should be placed in the following paths depending on the operating system: #### `CONFIG_DIR` on each platform
- **Windows**: `%APPDATA%`
- **macOS** and **other Unix systems**: `${XDG_CONFIG_HOME:-~/.config}`
- **Windows** - `%APPDATA%/topgrade.toml` `topgrade` will look for the configuration file in the following places, in order of priority:
- **macOS** and **other Unix systems** - `${XDG_CONFIG_HOME:-~/.config}/topgrade.toml`
1. `CONFIG_DIR/topgrade.toml`
2. `CONFIG_DIR/topgrade/topgrade.toml`
If the file with higher priority is present, no matter it is valid or not, the other configuration files will be ignored.
On the first run(no configuration file exists), `topgrade` will create a configuration file at `CONFIG_DIR/topgrade.toml` for you.
### Custom Commands
Custom commands can be defined in the config file which can be run before, during, or after the inbuilt commands, as required.
By default, the custom commands are run using a new shell according to the `$SHELL` environment variable on unix (falls back to `sh`) or `pwsh` on windows (falls back to `powershell`).
On unix, if you want to run your command using an interactive shell, for example to source your shell's rc files, you can add `-i` at the start of your custom command.
But note that this requires the command to exit the shell correctly or else the shell will hang indefinitely.
## Remote Execution ## Remote Execution
@@ -78,9 +102,7 @@ Just fork the repository and start coding.
### Contribution Guidelines ### Contribution Guidelines
- Check if your code passes `cargo fmt` and `cargo clippy`. See [CONTRIBUTING.md](https://github.com/topgrade-rs/topgrade/blob/master/CONTRIBUTING.md)
- Check if your code is self explanatory, if not it should be documented by comments.
- Make a pull request to the `dev` branch for new features or to the `bug-fixes` branch for bug fixes.
## Roadmap ## Roadmap

View File

@@ -1,3 +1,10 @@
# Include any additional configuration file(s)
# [include] sections are processed in the order you write them
# Files in $CONFIG_DIR/topgrade.d/ are automatically included before this file
[include]
#paths = ["/etc/topgrade.toml"]
[misc]
# Don't ask for confirmations # Don't ask for confirmations
#assume_yes = true #assume_yes = true
@@ -13,13 +20,20 @@
# Do not ask to retry failed steps (default: false) # Do not ask to retry failed steps (default: false)
#no_retry = true #no_retry = true
# Sudo command to be used
#sudo_command = "sudo"
# Run `sudo -v` to cache credentials at the start of the run
# This avoids a blocking password prompt in the middle of an unattended run
#pre_sudo = false
# Run inside tmux # Run inside tmux
#run_in_tmux = true #run_in_tmux = true
# List of remote machines with Topgrade installed on them # List of remote machines with Topgrade installed on them
#remote_topgrades = ["toothless", "pi", "parnas"] #remote_topgrades = ["toothless", "pi", "parnas"]
# Arguments to pass SSH when upgrading remote systems # Arguments to pass to SSH when upgrading remote systems
#ssh_arguments = "-o ConnectTimeout=2" #ssh_arguments = "-o ConnectTimeout=2"
# Path to Topgrade executable on remote machines # Path to Topgrade executable on remote machines
@@ -40,6 +54,57 @@
# Skip sending a notification at the end of a run # Skip sending a notification at the end of a run
#skip_notify = true #skip_notify = true
# Whether to self update (this is ignored if the binary has been built without self update support, available also via setting the environment variable TOPGRADE_NO_SELF_UPGRADE)
#no_self_update = true
# Commands to run before anything
[pre_commands]
#"Emacs Snapshot" = "rm -rf ~/.emacs.d/elpa.bak && cp -rl ~/.emacs.d/elpa ~/.emacs.d/elpa.bak"
# Commands to run after anything
[post_commands]
#"Emacs Snapshot" = "rm -rf ~/.emacs.d/elpa.bak && cp -rl ~/.emacs.d/elpa ~/.emacs.d/elpa.bak"
# Custom commands
[commands]
#"Python Environment" = "~/dev/.env/bin/pip install -i https://pypi.python.org/simple -U --upgrade-strategy eager jupyter"
#"Custom command using interactive shell (unix)" = "-i vim_upgrade"
[python]
#enable_pip_review = true ###disabled by default
#enable_pip_review_local = true ###disabled by default
#enable_pipupgrade = true ###disabled by default
#pipupgrade_arguments = "-y -u --pip-path pip" ###disabled by default
[composer]
#self_update = true
[brew]
#greedy_cask = true
#autoremove = true
[linux]
# Arch Package Manager to use. Allowed values: autodetect, aura, garuda_update, pacman, pamac, paru, pikaur, trizen, yay.
#arch_package_manager = "pacman"
# Arguments to pass yay (or paru) when updating packages
#yay_arguments = "--nodevel"
# Arguments to pass dnf when updating packages
#dnf_arguments = "--refresh"
#aura_aur_arguments = "-kx"
#aura_pacman_arguments = ""
#garuda_update_arguments = ""
#show_arch_news = true
#trizen_arguments = "--devel"
#pikaur_arguments = ""
#pamac_arguments = "--no-devel"
#enable_tlmgr = true
#emerge_sync_flags = "-q"
#emerge_update_flags = "-uDNa --with-bdeps=y world"
#redhat_distro_sync = false
#suse_dup = false
#rpm_ostree = false
#nix_arguments = "--flake"
[git] [git]
#max_concurrency = 5 #max_concurrency = 5
# Additional git repositories to pull # Additional git repositories to pull
@@ -54,42 +119,12 @@
# Arguments to pass Git when pulling Repositories # Arguments to pass Git when pulling Repositories
#arguments = "--rebase --autostash" #arguments = "--rebase --autostash"
[composer]
#self_update = true
# Commands to run before anything
[pre_commands]
#"Emacs Snapshot" = "rm -rf ~/.emacs.d/elpa.bak && cp -rl ~/.emacs.d/elpa ~/.emacs.d/elpa.bak"
# Custom commands
[commands]
#"Python Environment" = "~/dev/.env/bin/pip install -i https://pypi.python.org/simple -U --upgrade-strategy eager jupyter"
[brew]
#greedy_cask = true
#autoremove = true
[linux]
# Arch Package Manager to use. Allowed values: autodetect, trizen, aura, paru, yay, pikaur, pacman, pamac.
#arch_package_manager = "pacman"
# Arguments to pass yay (or paru) when updating packages
#yay_arguments = "--nodevel"
#aura_aur_arguments = "-kx"
#aura_pacman_arguments = ""
#show_arch_news = true
#trizen_arguments = "--devel"
#pikaur_arguments = ""
#pamac_arguments = "--no-devel"
#enable_tlmgr = true
#emerge_sync_flags = "-q"
#emerge_update_flags = "-uDNa --with-bdeps=y world"
#redhat_distro_sync = false
#rpm_ostree = false
[windows] [windows]
# Manually select Windows updates # Manually select Windows updates
#accept_all_updates = false #accept_all_updates = false
#open_remotes_in_new_terminal = true #open_remotes_in_new_terminal = true
#wsl_update_pre_release = true
#wsl_update_use_web_download = true
# Causes Topgrade to rename itself during the run to allow package managers # Causes Topgrade to rename itself during the run to allow package managers
# to upgrade it. Use this only if you installed Topgrade by using a package # to upgrade it. Use this only if you installed Topgrade by using a package
@@ -100,10 +135,28 @@
# Use sudo if the NPM directory isn't owned by the current user # Use sudo if the NPM directory isn't owned by the current user
#use_sudo = true #use_sudo = true
[yarn]
# Run `yarn global upgrade` with `sudo`
#use_sudo = true
[vim]
# For `vim-plug`, execute `PlugUpdate!` instead of `PlugUpdate`
#force_plug_update = true
[firmware] [firmware]
# Offer to update firmware; if false just check for and display available updates # Offer to update firmware; if false just check for and display available updates
#upgrade = true #upgrade = true
[vagrant]
# Vagrant directories
#directories = []
# power on vagrant boxes if needed
#power_on = true
# Always suspend vagrant boxes instead of powering off
#always_suspend = true
[flatpak] [flatpak]
# Use sudo for updating the system-wide installation # Use sudo for updating the system-wide installation
#use_sudo = true #use_sudo = true

16
pyproject.toml Normal file
View File

@@ -0,0 +1,16 @@
[build-system]
requires = ["maturin>=1.0,<2.0"]
build-backend = "maturin"
[project]
name = "topgrade"
requires-python = ">=3.7"
classifiers = [
"Programming Language :: Rust",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
]
[tool.maturin]
bindings = "bin"

File diff suppressed because it is too large Load Diff

View File

@@ -10,10 +10,6 @@ pub enum TopgradeError {
#[error("`{0}` failed: {1}")] #[error("`{0}` failed: {1}")]
ProcessFailedWithOutput(String, ExitStatus, String), ProcessFailedWithOutput(String, ExitStatus, String),
#[error("Sudo is required for this step")]
#[allow(dead_code)]
SudoRequired,
#[error("Unknown Linux Distribution")] #[error("Unknown Linux Distribution")]
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
UnknownLinuxDistribution, UnknownLinuxDistribution,

View File

@@ -1,57 +1,43 @@
#![allow(dead_code)] #![allow(dead_code)]
use crate::executor::RunType; use crate::executor::RunType;
use crate::git::Git; use crate::git::Git;
use crate::utils::require_option; use crate::sudo::Sudo;
use crate::utils::{require_option, REQUIRE_SUDO};
use crate::{config::Config, executor::Executor}; use crate::{config::Config, executor::Executor};
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use directories::BaseDirs; use std::env::var;
use std::path::{Path, PathBuf}; use std::path::Path;
use std::sync::Mutex; use std::sync::Mutex;
pub struct ExecutionContext<'a> { pub struct ExecutionContext<'a> {
run_type: RunType, run_type: RunType,
sudo: &'a Option<PathBuf>, sudo: Option<Sudo>,
git: &'a Git, git: &'a Git,
config: &'a Config, config: &'a Config,
base_dirs: &'a BaseDirs,
/// Name of a tmux session to execute commands in, if any. /// Name of a tmux session to execute commands in, if any.
/// This is used in `./steps/remote/ssh.rs`, where we want to run `topgrade` in a new /// This is used in `./steps/remote/ssh.rs`, where we want to run `topgrade` in a new
/// tmux window for each remote. /// tmux window for each remote.
tmux_session: Mutex<Option<String>>, tmux_session: Mutex<Option<String>>,
/// True if topgrade is running under ssh.
under_ssh: bool,
} }
impl<'a> ExecutionContext<'a> { impl<'a> ExecutionContext<'a> {
pub fn new( pub fn new(run_type: RunType, sudo: Option<Sudo>, git: &'a Git, config: &'a Config) -> Self {
run_type: RunType, let under_ssh = var("SSH_CLIENT").is_ok() || var("SSH_TTY").is_ok();
sudo: &'a Option<PathBuf>,
git: &'a Git,
config: &'a Config,
base_dirs: &'a BaseDirs,
) -> Self {
Self { Self {
run_type, run_type,
sudo, sudo,
git, git,
config, config,
base_dirs,
tmux_session: Mutex::new(None), tmux_session: Mutex::new(None),
under_ssh,
} }
} }
pub fn execute_elevated(&self, command: &Path, interactive: bool) -> Result<Executor> { pub fn execute_elevated(&self, command: &Path, interactive: bool) -> Result<Executor> {
let sudo = require_option(self.sudo.clone(), "Sudo is required for this operation".into())?; let sudo = require_option(self.sudo.as_ref(), REQUIRE_SUDO.to_string())?;
let mut cmd = self.run_type.execute(&sudo); Ok(sudo.execute_elevated(self, command, interactive))
if sudo.ends_with("sudo") {
cmd.arg("--preserve-env=DIFFPROG");
}
if interactive {
cmd.arg("-i");
}
cmd.arg(command);
Ok(cmd)
} }
pub fn run_type(&self) -> RunType { pub fn run_type(&self) -> RunType {
@@ -62,16 +48,16 @@ impl<'a> ExecutionContext<'a> {
self.git self.git
} }
pub fn sudo(&self) -> &Option<PathBuf> { pub fn sudo(&self) -> &Option<Sudo> {
self.sudo &self.sudo
} }
pub fn config(&self) -> &Config { pub fn config(&self) -> &Config {
self.config self.config
} }
pub fn base_dirs(&self) -> &BaseDirs { pub fn under_ssh(&self) -> bool {
self.base_dirs self.under_ssh
} }
pub fn set_tmux_session(&self, session_name: String) { pub fn set_tmux_session(&self, session_name: String) {

View File

@@ -3,7 +3,6 @@ use std::ffi::{OsStr, OsString};
use std::path::Path; use std::path::Path;
use std::process::{Child, Command, ExitStatus, Output}; use std::process::{Child, Command, ExitStatus, Output};
use color_eyre::eyre;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use tracing::debug; use tracing::debug;
@@ -176,7 +175,7 @@ impl Executor {
/// An extension of `status_checked` that allows you to set a sequence of codes /// An extension of `status_checked` that allows you to set a sequence of codes
/// that can indicate success of a script /// that can indicate success of a script
#[cfg_attr(windows, allow(dead_code))] #[allow(dead_code)]
pub fn status_checked_with_codes(&mut self, codes: &[i32]) -> Result<()> { pub fn status_checked_with_codes(&mut self, codes: &[i32]) -> Result<()> {
match self { match self {
Executor::Wet(c) => c.status_checked_with(|status| { Executor::Wet(c) => c.status_checked_with(|status| {
@@ -238,7 +237,7 @@ impl CommandExt for Executor {
// TODO: It might be nice to make `output_checked_with` return something that has a // TODO: It might be nice to make `output_checked_with` return something that has a
// variant for wet/dry runs. // variant for wet/dry runs.
fn output_checked_with(&mut self, succeeded: impl Fn(&Output) -> Result<(), ()>) -> eyre::Result<Output> { fn output_checked_with(&mut self, succeeded: impl Fn(&Output) -> Result<(), ()>) -> Result<Output> {
match self { match self {
Executor::Wet(c) => c.output_checked_with(succeeded), Executor::Wet(c) => c.output_checked_with(succeeded),
Executor::Dry(c) => { Executor::Dry(c) => {
@@ -248,7 +247,7 @@ impl CommandExt for Executor {
} }
} }
fn status_checked_with(&mut self, succeeded: impl Fn(ExitStatus) -> Result<(), ()>) -> eyre::Result<()> { fn status_checked_with(&mut self, succeeded: impl Fn(ExitStatus) -> Result<(), ()>) -> Result<()> {
match self { match self {
Executor::Wet(c) => c.status_checked_with(succeeded), Executor::Wet(c) => c.status_checked_with(succeeded),
Executor::Dry(c) => { Executor::Dry(c) => {
@@ -258,7 +257,7 @@ impl CommandExt for Executor {
} }
} }
fn spawn_checked(&mut self) -> eyre::Result<Self::Child> { fn spawn_checked(&mut self) -> Result<Self::Child> {
self.spawn() self.spawn()
} }
} }

View File

@@ -2,12 +2,19 @@
use std::env; use std::env;
use std::io; use std::io;
use std::path::PathBuf;
use std::process::exit; use std::process::exit;
use std::time::Duration;
use clap::CommandFactory;
use clap::{crate_version, Parser}; use clap::{crate_version, Parser};
use color_eyre::eyre::Context; use color_eyre::eyre::Context;
use color_eyre::eyre::{eyre, Result}; use color_eyre::eyre::Result;
use console::Key; use console::Key;
#[cfg(windows)]
use etcetera::base_strategy::Windows;
use etcetera::base_strategy::{BaseStrategy, Xdg};
use once_cell::sync::Lazy;
use tracing::debug; use tracing::debug;
use self::config::{CommandLineArgs, Config, Step}; use self::config::{CommandLineArgs, Config, Step};
@@ -30,17 +37,33 @@ mod self_renamer;
#[cfg(feature = "self-update")] #[cfg(feature = "self-update")]
mod self_update; mod self_update;
mod steps; mod steps;
mod sudo;
mod terminal; mod terminal;
mod utils; mod utils;
pub static HOME_DIR: Lazy<PathBuf> = Lazy::new(|| home::home_dir().expect("No home directory"));
pub static XDG_DIRS: Lazy<Xdg> = Lazy::new(|| Xdg::new().expect("No home directory"));
#[cfg(windows)]
pub static WINDOWS_DIRS: Lazy<Windows> = Lazy::new(|| Windows::new().expect("No home directory"));
fn run() -> Result<()> { fn run() -> Result<()> {
color_eyre::install()?; color_eyre::install()?;
ctrlc::set_handler(); ctrlc::set_handler();
let base_dirs = directories::BaseDirs::new().ok_or_else(|| eyre!("No base directories"))?;
let opt = CommandLineArgs::parse(); let opt = CommandLineArgs::parse();
if let Some(shell) = opt.gen_completion {
let cmd = &mut CommandLineArgs::command();
clap_complete::generate(shell, cmd, clap::crate_name!(), &mut io::stdout());
return Ok(());
}
if opt.gen_manpage {
let man = clap_mangen::Man::new(CommandLineArgs::command());
man.render(&mut io::stdout())?;
return Ok(());
}
install_tracing(&opt.tracing_filter_directives())?; install_tracing(&opt.tracing_filter_directives())?;
for env in opt.env_variables() { for env in opt.env_variables() {
@@ -51,19 +74,19 @@ fn run() -> Result<()> {
} }
if opt.edit_config() { if opt.edit_config() {
Config::edit(&base_dirs)?; Config::edit()?;
return Ok(()); return Ok(());
}; };
if opt.show_config_reference() { if opt.show_config_reference() {
print!("{}", crate::config::EXAMPLE_CONFIG); print!("{}", config::EXAMPLE_CONFIG);
return Ok(()); return Ok(());
} }
let config = Config::load(&base_dirs, opt)?; let config = Config::load(opt)?;
terminal::set_title(config.set_title()); set_title(config.set_title());
terminal::display_time(config.display_time()); display_time(config.display_time());
terminal::set_desktop_notifications(config.notify_each_step()); set_desktop_notifications(config.notify_each_step());
debug!("Version: {}", crate_version!()); debug!("Version: {}", crate_version!());
debug!("OS: {}", env!("TARGET")); debug!("OS: {}", env!("TARGET"));
@@ -81,17 +104,22 @@ fn run() -> Result<()> {
let git = git::Git::new(); let git = git::Git::new();
let mut git_repos = git::Repositories::new(&git); let mut git_repos = git::Repositories::new(&git);
let powershell = powershell::Powershell::new();
let should_run_powershell = powershell.profile().is_some() && config.should_run(Step::Powershell);
let emacs = emacs::Emacs::new();
#[cfg(target_os = "linux")]
let distribution = linux::Distribution::detect();
let sudo = utils::sudo(); let sudo = config.sudo_command().map_or_else(sudo::Sudo::detect, sudo::Sudo::new);
let run_type = executor::RunType::new(config.dry_run()); let run_type = executor::RunType::new(config.dry_run());
let ctx = execution_context::ExecutionContext::new(run_type, sudo, &git, &config);
let ctx = execution_context::ExecutionContext::new(run_type, &sudo, &git, &config, &base_dirs);
let mut runner = runner::Runner::new(&ctx); let mut runner = runner::Runner::new(&ctx);
#[cfg(feature = "self-update")] #[cfg(feature = "self-update")]
{ {
if !run_type.dry() && env::var("TOPGRADE_NO_SELF_UPGRADE").is_err() { let config_self_upgrade = env::var("TOPGRADE_NO_SELF_UPGRADE").is_err() && !config.no_self_update();
if !run_type.dry() && config_self_upgrade {
let result = self_update::self_update(); let result = self_update::self_update();
if let Err(e) = &result { if let Err(e) = &result {
@@ -101,7 +129,7 @@ fn run() -> Result<()> {
return result; return result;
} }
} }
print_warning(format!("Self update error: {}", e)); print_warning(format!("Self update error: {e}"));
} }
} }
} }
@@ -119,31 +147,42 @@ fn run() -> Result<()> {
} }
} }
let powershell = powershell::Powershell::new(); if config.pre_sudo() {
let should_run_powershell = powershell.profile().is_some() && config.should_run(Step::Powershell); if let Some(sudo) = ctx.sudo() {
sudo.elevate(&ctx)?;
#[cfg(windows)] }
runner.execute(Step::Wsl, "WSL", || windows::run_wsl_topgrade(&ctx))?; }
if let Some(topgrades) = config.remote_topgrades() { if let Some(topgrades) = config.remote_topgrades() {
for remote_topgrade in topgrades.iter().filter(|t| config.should_execute_remote(t)) { for remote_topgrade in topgrades.iter().filter(|t| config.should_execute_remote(t)) {
runner.execute(Step::Remotes, format!("Remote ({})", remote_topgrade), || { runner.execute(Step::Remotes, format!("Remote ({remote_topgrade})"), || {
remote::ssh::ssh_step(&ctx, remote_topgrade) ssh::ssh_step(&ctx, remote_topgrade)
})?; })?;
} }
} }
#[cfg(target_os = "linux")] #[cfg(windows)]
let distribution = linux::Distribution::detect();
#[cfg(target_os = r#"linux"#)]
{ {
runner.execute(Step::Wsl, "WSL", || windows::run_wsl_topgrade(&ctx))?;
runner.execute(Step::WslUpdate, "WSL", || windows::update_wsl(&ctx))?;
runner.execute(Step::Chocolatey, "Chocolatey", || windows::run_chocolatey(&ctx))?;
runner.execute(Step::Scoop, "Scoop", || windows::run_scoop(&ctx))?;
runner.execute(Step::Winget, "Winget", || windows::run_winget(&ctx))?;
runner.execute(Step::System, "Windows update", || windows::windows_update(&ctx))?;
}
#[cfg(target_os = "linux")]
{
// NOTE: Due to breaking `nu` updates, `packer.nu` needs to be updated before `nu` get updated
// by other package managers.
runner.execute(Step::Shell, "packer.nu", || linux::run_packer_nu(&ctx))?;
match &distribution { match &distribution {
Ok(distribution) => { Ok(distribution) => {
runner.execute(Step::System, "System update", || distribution.upgrade(&ctx))?; runner.execute(Step::System, "System update", || distribution.upgrade(&ctx))?;
} }
Err(e) => { Err(e) => {
println!("Error detecting current distribution: {}", e); println!("Error detecting current distribution: {e}");
} }
} }
runner.execute(Step::ConfigUpdate, "config-update", || linux::run_config_update(&ctx))?; runner.execute(Step::ConfigUpdate, "config-update", || linux::run_config_update(&ctx))?;
@@ -151,13 +190,21 @@ fn run() -> Result<()> {
runner.execute(Step::BrewFormula, "Brew", || { runner.execute(Step::BrewFormula, "Brew", || {
unix::run_brew_formula(&ctx, unix::BrewVariant::Path) unix::run_brew_formula(&ctx, unix::BrewVariant::Path)
})?; })?;
}
#[cfg(windows)] runner.execute(Step::AM, "am", || linux::run_am(&ctx))?;
{ runner.execute(Step::AppMan, "appman", || linux::run_appman(&ctx))?;
runner.execute(Step::Chocolatey, "Chocolatey", || windows::run_chocolatey(&ctx))?; runner.execute(Step::DebGet, "deb-get", || linux::run_deb_get(&ctx))?;
runner.execute(Step::Scoop, "Scoop", || windows::run_scoop(config.cleanup(), run_type))?; runner.execute(Step::Toolbx, "toolbx", || toolbx::run_toolbx(&ctx))?;
runner.execute(Step::Winget, "Winget", || windows::run_winget(&ctx))?; runner.execute(Step::Flatpak, "Flatpak", || linux::run_flatpak(&ctx))?;
runner.execute(Step::Snap, "snap", || linux::run_snap(&ctx))?;
runner.execute(Step::Pacstall, "pacstall", || linux::run_pacstall(&ctx))?;
runner.execute(Step::Pacdef, "pacdef", || linux::run_pacdef(&ctx))?;
runner.execute(Step::Protonup, "protonup", || linux::run_protonup_update(&ctx))?;
runner.execute(Step::Distrobox, "distrobox", || linux::run_distrobox_update(&ctx))?;
runner.execute(Step::DkpPacman, "dkp-pacman", || linux::run_dkp_pacman_update(&ctx))?;
runner.execute(Step::System, "pihole", || linux::run_pihole_update(&ctx))?;
runner.execute(Step::Firmware, "Firmware upgrades", || linux::run_fwupdmgr(&ctx))?;
runner.execute(Step::Restarts, "Restarts", || linux::run_needrestart(&ctx))?;
} }
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
@@ -181,6 +228,35 @@ fn run() -> Result<()> {
unix::run_brew_cask(&ctx, unix::BrewVariant::Path) unix::run_brew_cask(&ctx, unix::BrewVariant::Path)
})?; })?;
runner.execute(Step::Macports, "MacPorts", || macos::run_macports(&ctx))?; runner.execute(Step::Macports, "MacPorts", || macos::run_macports(&ctx))?;
runner.execute(Step::Sparkle, "Sparkle", || macos::run_sparkle(&ctx))?;
runner.execute(Step::Mas, "App Store", || macos::run_mas(&ctx))?;
runner.execute(Step::System, "System upgrade", || macos::upgrade_macos(&ctx))?;
}
#[cfg(target_os = "dragonfly")]
{
runner.execute(Step::Pkg, "DragonFly BSD Packages", || {
dragonfly::upgrade_packages(&ctx)
})?;
dragonfly::audit_packages(&ctx)?;
}
#[cfg(target_os = "freebsd")]
{
runner.execute(Step::Pkg, "FreeBSD Packages", || freebsd::upgrade_packages(&ctx))?;
runner.execute(Step::System, "FreeBSD Upgrade", || freebsd::upgrade_freebsd(&ctx))?;
freebsd::audit_packages(&ctx)?;
}
#[cfg(target_os = "openbsd")]
{
runner.execute(Step::Pkg, "OpenBSD Packages", || openbsd::upgrade_packages(&ctx))?;
runner.execute(Step::System, "OpenBSD Upgrade", || openbsd::upgrade_openbsd(&ctx))?;
}
#[cfg(target_os = "android")]
{
runner.execute(Step::Pkg, "Termux Packages", || android::upgrade_packages(&ctx))?;
} }
#[cfg(unix)] #[cfg(unix)]
@@ -188,32 +264,107 @@ fn run() -> Result<()> {
runner.execute(Step::Yadm, "yadm", || unix::run_yadm(&ctx))?; runner.execute(Step::Yadm, "yadm", || unix::run_yadm(&ctx))?;
runner.execute(Step::Nix, "nix", || unix::run_nix(&ctx))?; runner.execute(Step::Nix, "nix", || unix::run_nix(&ctx))?;
runner.execute(Step::Guix, "guix", || unix::run_guix(&ctx))?; runner.execute(Step::Guix, "guix", || unix::run_guix(&ctx))?;
runner.execute(Step::HomeManager, "home-manager", || unix::run_home_manager(&ctx))?;
runner.execute(Step::HomeManager, "home-manager", || unix::run_home_manager(run_type))?; runner.execute(Step::Asdf, "asdf", || unix::run_asdf(&ctx))?;
runner.execute(Step::Asdf, "asdf", || unix::run_asdf(run_type))?;
runner.execute(Step::Pkgin, "pkgin", || unix::run_pkgin(&ctx))?; runner.execute(Step::Pkgin, "pkgin", || unix::run_pkgin(&ctx))?;
runner.execute(Step::Bun, "bun", || unix::run_bun(&ctx))?; runner.execute(Step::Bun, "bun", || unix::run_bun(&ctx))?;
runner.execute(Step::Shell, "zr", || zsh::run_zr(&ctx))?;
runner.execute(Step::Shell, "antibody", || zsh::run_antibody(&ctx))?;
runner.execute(Step::Shell, "antidote", || zsh::run_antidote(&ctx))?;
runner.execute(Step::Shell, "antigen", || zsh::run_antigen(&ctx))?;
runner.execute(Step::Shell, "zgenom", || zsh::run_zgenom(&ctx))?;
runner.execute(Step::Shell, "zplug", || zsh::run_zplug(&ctx))?;
runner.execute(Step::Shell, "zinit", || zsh::run_zinit(&ctx))?;
runner.execute(Step::Shell, "zi", || zsh::run_zi(&ctx))?;
runner.execute(Step::Shell, "zim", || zsh::run_zim(&ctx))?;
runner.execute(Step::Shell, "oh-my-zsh", || zsh::run_oh_my_zsh(&ctx))?;
runner.execute(Step::Shell, "oh-my-bash", || unix::run_oh_my_bash(&ctx))?;
runner.execute(Step::Shell, "fisher", || unix::run_fisher(&ctx))?;
runner.execute(Step::Shell, "bash-it", || unix::run_bashit(&ctx))?;
runner.execute(Step::Shell, "oh-my-fish", || unix::run_oh_my_fish(&ctx))?;
runner.execute(Step::Shell, "fish-plug", || unix::run_fish_plug(&ctx))?;
runner.execute(Step::Shell, "fundle", || unix::run_fundle(&ctx))?;
runner.execute(Step::Tmux, "tmux", || tmux::run_tpm(&ctx))?;
runner.execute(Step::Tldr, "TLDR", || unix::run_tldr(&ctx))?;
runner.execute(Step::Pearl, "pearl", || unix::run_pearl(&ctx))?;
#[cfg(not(any(target_os = "macos", target_os = "android")))]
runner.execute(Step::GnomeShellExtensions, "Gnome Shell Extensions", || {
unix::upgrade_gnome_extensions(&ctx)
})?;
runner.execute(Step::Sdkman, "SDKMAN!", || unix::run_sdkman(&ctx))?;
runner.execute(Step::Rcm, "rcm", || unix::run_rcm(&ctx))?;
runner.execute(Step::Maza, "maza", || unix::run_maza(&ctx))?;
} }
#[cfg(target_os = "dragonfly")] #[cfg(not(any(
runner.execute(Step::Pkg, "DragonFly BSD Packages", || { target_os = "freebsd",
dragonfly::upgrade_packages(sudo.as_ref(), run_type) target_os = "openbsd",
target_os = "netbsd",
target_os = "dragonfly"
)))]
{
runner.execute(Step::Atom, "apm", || generic::run_apm(&ctx))?;
}
// The following update function should be executed on all OSes.
runner.execute(Step::Fossil, "fossil", || generic::run_fossil(&ctx))?;
runner.execute(Step::Rustup, "rustup", || generic::run_rustup(&ctx))?;
runner.execute(Step::Juliaup, "juliaup", || generic::run_juliaup(&ctx))?;
runner.execute(Step::Dotnet, ".NET", || generic::run_dotnet_upgrade(&ctx))?;
runner.execute(Step::Choosenim, "choosenim", || generic::run_choosenim(&ctx))?;
runner.execute(Step::Cargo, "cargo", || generic::run_cargo_update(&ctx))?;
runner.execute(Step::Flutter, "Flutter", || generic::run_flutter_upgrade(&ctx))?;
runner.execute(Step::Go, "go-global-update", || go::run_go_global_update(&ctx))?;
runner.execute(Step::Go, "gup", || go::run_go_gup(&ctx))?;
runner.execute(Step::Emacs, "Emacs", || emacs.upgrade(&ctx))?;
runner.execute(Step::Opam, "opam", || generic::run_opam_update(&ctx))?;
runner.execute(Step::Vcpkg, "vcpkg", || generic::run_vcpkg_update(&ctx))?;
runner.execute(Step::Pipx, "pipx", || generic::run_pipx_update(&ctx))?;
runner.execute(Step::Conda, "conda", || generic::run_conda_update(&ctx))?;
runner.execute(Step::Mamba, "mamba", || generic::run_mamba_update(&ctx))?;
runner.execute(Step::Pip3, "pip3", || generic::run_pip3_update(&ctx))?;
runner.execute(Step::PipReview, "pip-review", || generic::run_pip_review_update(&ctx))?;
runner.execute(Step::PipReviewLocal, "pip-review (local)", || {
generic::run_pip_review_local_update(&ctx)
})?; })?;
runner.execute(Step::Pipupgrade, "pipupgrade", || generic::run_pipupgrade_update(&ctx))?;
#[cfg(target_os = "freebsd")] runner.execute(Step::Ghcup, "ghcup", || generic::run_ghcup_update(&ctx))?;
runner.execute(Step::Pkg, "FreeBSD Packages", || { runner.execute(Step::Stack, "stack", || generic::run_stack_update(&ctx))?;
freebsd::upgrade_packages(&ctx, sudo.as_ref(), run_type) runner.execute(Step::Tlmgr, "tlmgr", || generic::run_tlmgr_update(&ctx))?;
runner.execute(Step::Myrepos, "myrepos", || generic::run_myrepos_update(&ctx))?;
runner.execute(Step::Chezmoi, "chezmoi", || generic::run_chezmoi_update(&ctx))?;
runner.execute(Step::Jetpack, "jetpack", || generic::run_jetpack(&ctx))?;
runner.execute(Step::Vim, "vim", || vim::upgrade_vim(&ctx))?;
runner.execute(Step::Vim, "Neovim", || vim::upgrade_neovim(&ctx))?;
runner.execute(Step::Vim, "The Ultimate vimrc", || vim::upgrade_ultimate_vimrc(&ctx))?;
runner.execute(Step::Vim, "voom", || vim::run_voom(&ctx))?;
runner.execute(Step::Kakoune, "Kakoune", || kakoune::upgrade_kak_plug(&ctx))?;
runner.execute(Step::Helix, "helix", || generic::run_helix_grammars(&ctx))?;
runner.execute(Step::Node, "npm", || node::run_npm_upgrade(&ctx))?;
runner.execute(Step::Yarn, "yarn", || node::run_yarn_upgrade(&ctx))?;
runner.execute(Step::Pnpm, "pnpm", || node::run_pnpm_upgrade(&ctx))?;
runner.execute(Step::Containers, "Containers", || containers::run_containers(&ctx))?;
runner.execute(Step::Deno, "deno", || node::deno_upgrade(&ctx))?;
runner.execute(Step::Composer, "composer", || generic::run_composer_update(&ctx))?;
runner.execute(Step::Krew, "krew", || generic::run_krew_upgrade(&ctx))?;
runner.execute(Step::Helm, "helm", || generic::run_helm_repo_update(&ctx))?;
runner.execute(Step::Gem, "gem", || generic::run_gem(&ctx))?;
runner.execute(Step::RubyGems, "rubygems", || generic::run_rubygems(&ctx))?;
runner.execute(Step::Julia, "julia", || generic::update_julia_packages(&ctx))?;
runner.execute(Step::Haxelib, "haxelib", || generic::run_haxelib_update(&ctx))?;
runner.execute(Step::Sheldon, "sheldon", || generic::run_sheldon(&ctx))?;
runner.execute(Step::Stew, "stew", || generic::run_stew(&ctx))?;
runner.execute(Step::Rtcl, "rtcl", || generic::run_rtcl(&ctx))?;
runner.execute(Step::Bin, "bin", || generic::bin_update(&ctx))?;
runner.execute(Step::Gcloud, "gcloud", || generic::run_gcloud_components_update(&ctx))?;
runner.execute(Step::Micro, "micro", || generic::run_micro(&ctx))?;
runner.execute(Step::Raco, "raco", || generic::run_raco_update(&ctx))?;
runner.execute(Step::Spicetify, "spicetify", || generic::spicetify_upgrade(&ctx))?;
runner.execute(Step::GithubCliExtensions, "GitHub CLI Extensions", || {
generic::run_ghcli_extensions_upgrade(&ctx)
})?; })?;
runner.execute(Step::Bob, "Bob", || generic::run_bob(&ctx))?;
#[cfg(target_os = "openbsd")]
runner.execute(Step::Pkg, "OpenBSD Packages", || {
openbsd::upgrade_packages(sudo.as_ref(), run_type)
})?;
#[cfg(target_os = "android")]
runner.execute(Step::Pkg, "Termux Packages", || android::upgrade_packages(&ctx))?;
let emacs = emacs::Emacs::new(&base_dirs);
if config.use_predefined_git_repos() { if config.use_predefined_git_repos() {
if config.should_run(Step::Emacs) { if config.should_run(Step::Emacs) {
if !emacs.is_doom() { if !emacs.is_doom() {
@@ -221,43 +372,43 @@ fn run() -> Result<()> {
git_repos.insert_if_repo(directory); git_repos.insert_if_repo(directory);
} }
} }
git_repos.insert_if_repo(base_dirs.home_dir().join(".doom.d")); git_repos.insert_if_repo(HOME_DIR.join(".doom.d"));
} }
if config.should_run(Step::Vim) { if config.should_run(Step::Vim) {
git_repos.insert_if_repo(base_dirs.home_dir().join(".vim")); git_repos.insert_if_repo(HOME_DIR.join(".vim"));
git_repos.insert_if_repo(base_dirs.home_dir().join(".config/nvim")); git_repos.insert_if_repo(HOME_DIR.join(".config/nvim"));
} }
git_repos.insert_if_repo(base_dirs.home_dir().join(".ideavimrc")); git_repos.insert_if_repo(HOME_DIR.join(".ideavimrc"));
git_repos.insert_if_repo(base_dirs.home_dir().join(".intellimacs")); git_repos.insert_if_repo(HOME_DIR.join(".intellimacs"));
if config.should_run(Step::Rcm) { if config.should_run(Step::Rcm) {
git_repos.insert_if_repo(base_dirs.home_dir().join(".dotfiles")); git_repos.insert_if_repo(HOME_DIR.join(".dotfiles"));
} }
#[cfg(unix)] #[cfg(unix)]
{ {
git_repos.insert_if_repo(zsh::zshrc(&base_dirs)); git_repos.insert_if_repo(zsh::zshrc());
if config.should_run(Step::Tmux) { if config.should_run(Step::Tmux) {
git_repos.insert_if_repo(base_dirs.home_dir().join(".tmux")); git_repos.insert_if_repo(HOME_DIR.join(".tmux"));
} }
git_repos.insert_if_repo(base_dirs.home_dir().join(".config/fish")); git_repos.insert_if_repo(HOME_DIR.join(".config/fish"));
git_repos.insert_if_repo(base_dirs.config_dir().join("openbox")); git_repos.insert_if_repo(XDG_DIRS.config_dir().join("openbox"));
git_repos.insert_if_repo(base_dirs.config_dir().join("bspwm")); git_repos.insert_if_repo(XDG_DIRS.config_dir().join("bspwm"));
git_repos.insert_if_repo(base_dirs.config_dir().join("i3")); git_repos.insert_if_repo(XDG_DIRS.config_dir().join("i3"));
git_repos.insert_if_repo(base_dirs.config_dir().join("sway")); git_repos.insert_if_repo(XDG_DIRS.config_dir().join("sway"));
} }
#[cfg(windows)] #[cfg(windows)]
git_repos.insert_if_repo( git_repos.insert_if_repo(
base_dirs WINDOWS_DIRS
.data_local_dir() .cache_dir()
.join("Packages/Microsoft.WindowsTerminal_8wekyb3d8bbwe/LocalState"), .join("Packages/Microsoft.WindowsTerminal_8wekyb3d8bbwe/LocalState"),
); );
#[cfg(windows)] #[cfg(windows)]
windows::insert_startup_scripts(&ctx, &mut git_repos).ok(); windows::insert_startup_scripts(&mut git_repos).ok();
if let Some(profile) = powershell.profile() { if let Some(profile) = powershell.profile() {
git_repos.insert_if_repo(profile); git_repos.insert_if_repo(profile);
@@ -281,106 +432,6 @@ fn run() -> Result<()> {
})?; })?;
} }
#[cfg(unix)]
{
runner.execute(Step::Shell, "zr", || zsh::run_zr(&base_dirs, run_type))?;
runner.execute(Step::Shell, "antibody", || zsh::run_antibody(run_type))?;
runner.execute(Step::Shell, "antigen", || zsh::run_antigen(&base_dirs, run_type))?;
runner.execute(Step::Shell, "zgenom", || zsh::run_zgenom(&base_dirs, run_type))?;
runner.execute(Step::Shell, "zplug", || zsh::run_zplug(&base_dirs, run_type))?;
runner.execute(Step::Shell, "zinit", || zsh::run_zinit(&base_dirs, run_type))?;
runner.execute(Step::Shell, "zi", || zsh::run_zi(&base_dirs, run_type))?;
runner.execute(Step::Shell, "zim", || zsh::run_zim(&base_dirs, run_type))?;
runner.execute(Step::Shell, "oh-my-zsh", || zsh::run_oh_my_zsh(&ctx))?;
runner.execute(Step::Shell, "fisher", || unix::run_fisher(run_type))?;
runner.execute(Step::Shell, "bash-it", || unix::run_bashit(&ctx))?;
runner.execute(Step::Shell, "oh-my-fish", || unix::run_oh_my_fish(&ctx))?;
runner.execute(Step::Shell, "fish-plug", || unix::run_fish_plug(&ctx))?;
runner.execute(Step::Shell, "fundle", || unix::run_fundle(&ctx))?;
runner.execute(Step::Tmux, "tmux", || tmux::run_tpm(&base_dirs, run_type))?;
runner.execute(Step::Tldr, "TLDR", || unix::run_tldr(run_type))?;
runner.execute(Step::Pearl, "pearl", || unix::run_pearl(run_type))?;
#[cfg(not(any(target_os = "macos", target_os = "android")))]
runner.execute(Step::GnomeShellExtensions, "Gnome Shell Extensions", || {
unix::upgrade_gnome_extensions(&ctx)
})?;
runner.execute(Step::Sdkman, "SDKMAN!", || {
unix::run_sdkman(&base_dirs, config.cleanup(), run_type)
})?;
runner.execute(Step::Rcm, "rcm", || unix::run_rcm(&ctx))?;
}
#[cfg(not(any(
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "dragonfly"
)))]
runner.execute(Step::Atom, "apm", || generic::run_apm(run_type))?;
runner.execute(Step::Fossil, "fossil", || generic::run_fossil(run_type))?;
runner.execute(Step::Rustup, "rustup", || generic::run_rustup(&base_dirs, run_type))?;
runner.execute(Step::Dotnet, ".NET", || generic::run_dotnet_upgrade(&ctx))?;
runner.execute(Step::Choosenim, "choosenim", || generic::run_choosenim(&ctx))?;
runner.execute(Step::Cargo, "cargo", || generic::run_cargo_update(&ctx))?;
runner.execute(Step::Flutter, "Flutter", || generic::run_flutter_upgrade(run_type))?;
runner.execute(Step::Go, "go-global-update", || go::run_go_global_update(run_type))?;
runner.execute(Step::Go, "gup", || go::run_go_gup(run_type))?;
runner.execute(Step::Emacs, "Emacs", || emacs.upgrade(&ctx))?;
runner.execute(Step::Opam, "opam", || generic::run_opam_update(&ctx))?;
runner.execute(Step::Vcpkg, "vcpkg", || generic::run_vcpkg_update(run_type))?;
runner.execute(Step::Pipx, "pipx", || generic::run_pipx_update(run_type))?;
runner.execute(Step::Conda, "conda", || generic::run_conda_update(&ctx))?;
runner.execute(Step::Pip3, "pip3", || generic::run_pip3_update(run_type))?;
runner.execute(Step::Ghcup, "ghcup", || generic::run_ghcup_update(run_type))?;
runner.execute(Step::Stack, "stack", || generic::run_stack_update(run_type))?;
runner.execute(Step::Tlmgr, "tlmgr", || generic::run_tlmgr_update(&ctx))?;
runner.execute(Step::Myrepos, "myrepos", || {
generic::run_myrepos_update(&base_dirs, run_type)
})?;
runner.execute(Step::Chezmoi, "chezmoi", || {
generic::run_chezmoi_update(&base_dirs, run_type)
})?;
runner.execute(Step::Jetpack, "jetpack", || generic::run_jetpack(run_type))?;
runner.execute(Step::Vim, "vim", || vim::upgrade_vim(&base_dirs, &ctx))?;
runner.execute(Step::Vim, "Neovim", || vim::upgrade_neovim(&base_dirs, &ctx))?;
runner.execute(Step::Vim, "The Ultimate vimrc", || vim::upgrade_ultimate_vimrc(&ctx))?;
runner.execute(Step::Vim, "voom", || vim::run_voom(&base_dirs, run_type))?;
runner.execute(Step::Kakoune, "Kakoune", || kakoune::upgrade_kak_plug(&ctx))?;
runner.execute(Step::Node, "npm", || node::run_npm_upgrade(&ctx))?;
runner.execute(Step::Node, "yarn", || node::run_yarn_upgrade(&ctx))?;
runner.execute(Step::Node, "pnpm", || node::run_pnpm_upgrade(&ctx))?;
runner.execute(Step::Containers, "Containers", || containers::run_containers(&ctx))?;
runner.execute(Step::Deno, "deno", || node::deno_upgrade(&ctx))?;
runner.execute(Step::Composer, "composer", || generic::run_composer_update(&ctx))?;
runner.execute(Step::Krew, "krew", || generic::run_krew_upgrade(run_type))?;
runner.execute(Step::Gem, "gem", || generic::run_gem(&base_dirs, run_type))?;
runner.execute(Step::Julia, "julia", || generic::update_julia_packages(&ctx))?;
runner.execute(Step::Haxelib, "haxelib", || generic::run_haxelib_update(&ctx))?;
runner.execute(Step::Sheldon, "sheldon", || generic::run_sheldon(&ctx))?;
runner.execute(Step::Rtcl, "rtcl", || generic::run_rtcl(&ctx))?;
runner.execute(Step::Bin, "bin", || generic::bin_update(&ctx))?;
runner.execute(Step::Gcloud, "gcloud", || {
generic::run_gcloud_components_update(run_type)
})?;
runner.execute(Step::Micro, "micro", || generic::run_micro(run_type))?;
runner.execute(Step::Raco, "raco", || generic::run_raco_update(run_type))?;
runner.execute(Step::Spicetify, "spicetify", || generic::spicetify_upgrade(&ctx))?;
runner.execute(Step::GithubCliExtensions, "GitHub CLI Extensions", || {
generic::run_ghcli_extensions_upgrade(&ctx)
})?;
#[cfg(target_os = "linux")]
{
runner.execute(Step::DebGet, "deb-get", || linux::run_deb_get(&ctx))?;
runner.execute(Step::Toolbx, "toolbx", || toolbx::run_toolbx(&ctx))?;
runner.execute(Step::Flatpak, "Flatpak", || linux::flatpak_update(&ctx))?;
runner.execute(Step::Snap, "snap", || linux::run_snap(sudo.as_ref(), run_type))?;
runner.execute(Step::Pacstall, "pacstall", || linux::run_pacstall(&ctx))?;
runner.execute(Step::Pacdef, "pacdef", || linux::run_pacdef(&ctx))?;
runner.execute(Step::Protonup, "protonup", || linux::run_protonup_update(&ctx))?;
runner.execute(Step::Distrobox, "distrobox", || linux::run_distrobox_update(&ctx))?;
}
if let Some(commands) = config.commands() { if let Some(commands) = config.commands() {
for (name, command) in commands { for (name, command) in commands {
if config.should_run_custom_command(name) { if config.should_run_custom_command(name) {
@@ -391,37 +442,6 @@ fn run() -> Result<()> {
} }
} }
#[cfg(target_os = "linux")]
{
runner.execute(Step::System, "pihole", || {
linux::run_pihole_update(sudo.as_ref(), run_type)
})?;
runner.execute(Step::Firmware, "Firmware upgrades", || linux::run_fwupdmgr(&ctx))?;
runner.execute(Step::Restarts, "Restarts", || {
linux::run_needrestart(sudo.as_ref(), run_type)
})?;
}
#[cfg(target_os = "macos")]
{
runner.execute(Step::Sparkle, "Sparkle", || macos::run_sparkle(&ctx))?;
runner.execute(Step::Mas, "App Store", || macos::run_mas(run_type))?;
runner.execute(Step::System, "System upgrade", || macos::upgrade_macos(&ctx))?;
}
#[cfg(target_os = "freebsd")]
runner.execute(Step::System, "FreeBSD Upgrade", || {
freebsd::upgrade_freebsd(sudo.as_ref(), run_type)
})?;
#[cfg(target_os = "openbsd")]
runner.execute(Step::System, "OpenBSD Upgrade", || {
openbsd::upgrade_openbsd(sudo.as_ref(), run_type)
})?;
#[cfg(windows)]
runner.execute(Step::System, "Windows update", || windows::windows_update(&ctx))?;
if config.should_run(Step::Vagrant) { if config.should_run(Step::Vagrant) {
if let Ok(boxes) = vagrant::collect_boxes(&ctx) { if let Ok(boxes) = vagrant::collect_boxes(&ctx) {
for vagrant_box in boxes { for vagrant_box in boxes {
@@ -446,12 +466,6 @@ fn run() -> Result<()> {
distribution.show_summary(); distribution.show_summary();
} }
} }
#[cfg(target_os = "freebsd")]
freebsd::audit_packages(&sudo).ok();
#[cfg(target_os = "dragonfly")]
dragonfly::audit_packages(&sudo).ok();
} }
let mut post_command_failed = false; let mut post_command_failed = false;
@@ -485,13 +499,13 @@ fn run() -> Result<()> {
let failed = post_command_failed || runner.report().data().iter().any(|(_, result)| result.failed()); let failed = post_command_failed || runner.report().data().iter().any(|(_, result)| result.failed());
if !config.skip_notify() { if !config.skip_notify() {
terminal::notify_desktop( notify_desktop(
format!( format!(
"Topgrade finished {}", "Topgrade finished {}",
if failed { "with errors" } else { "successfully" } if failed { "with errors" } else { "successfully" }
), ),
None, Some(Duration::from_secs(10)),
); )
} }
if failed { if failed {
@@ -524,14 +538,14 @@ fn main() {
// The `Debug` implementation of `eyre::Result` prints a multi-line // The `Debug` implementation of `eyre::Result` prints a multi-line
// error message that includes all the 'causes' added with // error message that includes all the 'causes' added with
// `.with_context(...)` calls. // `.with_context(...)` calls.
println!("Error: {:?}", error); println!("Error: {error:?}");
} }
exit(1); exit(1);
} }
} }
} }
pub fn install_tracing(filter_directives: &str) -> Result<()> { fn install_tracing(filter_directives: &str) -> Result<()> {
use tracing_subscriber::fmt; use tracing_subscriber::fmt;
use tracing_subscriber::fmt::format::FmtSpan; use tracing_subscriber::fmt::format::FmtSpan;
use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::layer::SubscriberExt;

View File

@@ -34,7 +34,7 @@ impl<'a> Report<'a> {
if let Some((key, success)) = result { if let Some((key, success)) = result {
let key = key.into(); let key = key.into();
debug_assert!(!self.data.iter().any(|(k, _)| k == &key), "{} already reported", key); debug_assert!(!self.data.iter().any(|(k, _)| k == &key), "{key} already reported");
self.data.push((key, success)); self.data.push((key, success));
} }
} }

View File

@@ -31,7 +31,7 @@ pub fn self_update() -> Result<()> {
if let UpdateStatus::Updated(release) = &result { if let UpdateStatus::Updated(release) = &result {
println!("\nTopgrade upgraded to {}:\n", release.version); println!("\nTopgrade upgraded to {}:\n", release.version);
if let Some(body) = &release.body { if let Some(body) = &release.body {
println!("{}", body); println!("{body}");
} }
} else { } else {
println!("Topgrade is up-to-date"); println!("Topgrade is up-to-date");

View File

@@ -1,3 +1,4 @@
use std::fmt::{Display, Formatter};
use std::path::Path; use std::path::Path;
use std::process::Command; use std::process::Command;
@@ -18,15 +19,42 @@ use crate::{execution_context::ExecutionContext, utils::require};
// themselves or when using docker-compose. // themselves or when using docker-compose.
const NONEXISTENT_REPO: &str = "repository does not exist"; const NONEXISTENT_REPO: &str = "repository does not exist";
/// Uniquely identifies a `Container`.
#[derive(Debug)]
struct Container {
/// `Repository` and `Tag`
///
/// format: `Repository:Tag`, e.g., `nixos/nix:latest`.
repo_tag: String,
/// Platform
///
/// format: `OS/Architecture`, e.g., `linux/amd64`.
platform: String,
}
impl Container {
/// Construct a new `Container`.
fn new(repo_tag: String, platform: String) -> Self {
Self { repo_tag, platform }
}
}
impl Display for Container {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
// e.g., "`fedora:latest` for `linux/amd64`"
write!(f, "`{}` for `{}`", self.repo_tag, self.platform)
}
}
/// Returns a Vector of all containers, with Strings in the format /// Returns a Vector of all containers, with Strings in the format
/// "REGISTRY/[PATH/]CONTAINER_NAME:TAG" /// "REGISTRY/[PATH/]CONTAINER_NAME:TAG"
fn list_containers(crt: &Path) -> Result<Vec<String>> { fn list_containers(crt: &Path) -> Result<Vec<Container>> {
debug!( debug!(
"Querying '{} image ls --format \"{{{{.Repository}}}}:{{{{.Tag}}}}\"' for containers", "Querying '{} image ls --format \"{{{{.Repository}}}}:{{{{.Tag}}}}/{{{{.ID}}}}\"' for containers",
crt.display() crt.display()
); );
let output = Command::new(crt) let output = Command::new(crt)
.args(["image", "ls", "--format", "{{.Repository}}:{{.Tag}}"]) .args(["image", "ls", "--format", "{{.Repository}}:{{.Tag}} {{.ID}}"])
.output_checked_with_utf8(|_| Ok(()))?; .output_checked_with_utf8(|_| Ok(()))?;
let mut retval = vec![]; let mut retval = vec![];
@@ -49,7 +77,26 @@ fn list_containers(crt: &Path) -> Result<Vec<String>> {
} }
debug!("Using container '{}'", line); debug!("Using container '{}'", line);
retval.push(String::from(line));
// line is of format: `Repository:Tag ImageID`, e.g., `nixos/nix:latest d80fea9c32b4`
let split_res = line.split(' ').collect::<Vec<&str>>();
assert_eq!(split_res.len(), 2);
let (repo_tag, image_id) = (split_res[0], split_res[1]);
debug!(
"Querying '{} image inspect --format \"{{{{.Os}}}}/{{{{.Architecture}}}}\"' for container {}",
crt.display(),
image_id
);
let inspect_output = Command::new(crt)
.args(["image", "inspect", image_id, "--format", "{{.Os}}/{{.Architecture}}"])
.output_checked_with_utf8(|_| Ok(()))?;
let mut platform = inspect_output.stdout;
// truncate the tailing new line character
platform.truncate(platform.len() - 1);
assert!(platform.contains('/'));
retval.push(Container::new(repo_tag.to_string(), platform));
} }
Ok(retval) Ok(retval)
@@ -67,7 +114,12 @@ pub fn run_containers(ctx: &ExecutionContext) -> Result<()> {
for container in containers.iter() { for container in containers.iter() {
debug!("Pulling container '{}'", container); debug!("Pulling container '{}'", container);
let args = vec!["pull", &container[..]]; let args = vec![
"pull",
container.repo_tag.as_str(),
"--platform",
container.platform.as_str(),
];
let mut exec = ctx.run_type().execute(&crt); let mut exec = ctx.run_type().execute(&crt);
if let Err(e) = exec.args(&args).status_checked() { if let Err(e) = exec.args(&args).status_checked() {

View File

@@ -1,9 +1,9 @@
#[cfg(any(windows, target_os = "macos"))] #[cfg(windows)]
use std::env; use std::env;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use directories::BaseDirs; use etcetera::base_strategy::BaseStrategy;
use crate::command::CommandExt; use crate::command::CommandExt;
use crate::execution_context::ExecutionContext; use crate::execution_context::ExecutionContext;
@@ -23,20 +23,12 @@ pub struct Emacs {
} }
impl Emacs { impl Emacs {
fn directory_path(base_dirs: &BaseDirs) -> Option<PathBuf> { fn directory_path() -> Option<PathBuf> {
#[cfg(unix)] #[cfg(unix)]
cfg_if::cfg_if! { return {
if #[cfg(target_os = "macos")] { let emacs_xdg_dir = crate::XDG_DIRS.config_dir().join("emacs").if_exists();
let emacs_xdg_dir = env::var("XDG_CONFIG_HOME") crate::HOME_DIR.join(".emacs.d").if_exists().or(emacs_xdg_dir)
.ok() };
.and_then(|config| PathBuf::from(config).join("emacs").if_exists())
.or_else(|| base_dirs.home_dir().join(".config/emacs").if_exists());
} else {
let emacs_xdg_dir = base_dirs.config_dir().join("emacs").if_exists();
}
}
#[cfg(unix)]
return base_dirs.home_dir().join(".emacs.d").if_exists().or(emacs_xdg_dir);
#[cfg(windows)] #[cfg(windows)]
return env::var("HOME") return env::var("HOME")
@@ -47,11 +39,11 @@ impl Emacs {
.if_exists() .if_exists()
.or_else(|| PathBuf::from(&home).join(".config\\emacs").if_exists()) .or_else(|| PathBuf::from(&home).join(".config\\emacs").if_exists())
}) })
.or_else(|| base_dirs.data_dir().join(".emacs.d").if_exists()); .or_else(|| crate::WINDOWS_DIRS.data_dir().join(".emacs.d").if_exists());
} }
pub fn new(base_dirs: &BaseDirs) -> Self { pub fn new() -> Self {
let directory = Emacs::directory_path(base_dirs); let directory = Emacs::directory_path();
let doom = directory.as_ref().and_then(|d| d.join(DOOM_PATH).if_exists()); let doom = directory.as_ref().and_then(|d| d.join(DOOM_PATH).if_exists());
Self { directory, doom } Self { directory, doom }
} }

View File

@@ -8,26 +8,27 @@ use std::{fs, io::Write};
use color_eyre::eyre::eyre; use color_eyre::eyre::eyre;
use color_eyre::eyre::Context; use color_eyre::eyre::Context;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use directories::BaseDirs;
use tempfile::tempfile_in; use tempfile::tempfile_in;
use tracing::debug; use tracing::{debug, error};
use crate::command::{CommandExt, Utf8Output}; use crate::command::{CommandExt, Utf8Output};
use crate::execution_context::ExecutionContext; use crate::execution_context::ExecutionContext;
use crate::executor::{ExecutorOutput, RunType}; use crate::executor::ExecutorOutput;
use crate::terminal::{print_separator, shell}; use crate::terminal::{print_separator, shell};
use crate::utils::{self, require_option, PathExt}; use crate::utils::{self, check_is_python_2_or_shim, require, require_option, which, PathExt, REQUIRE_SUDO};
use crate::Step;
use crate::HOME_DIR;
use crate::{ use crate::{
error::{SkipStep, TopgradeError}, error::{SkipStep, StepFailed, TopgradeError},
terminal::print_warning, terminal::print_warning,
}; };
pub fn run_cargo_update(ctx: &ExecutionContext) -> Result<()> { pub fn run_cargo_update(ctx: &ExecutionContext) -> Result<()> {
let cargo_dir = env::var_os("CARGO_HOME") let cargo_dir = env::var_os("CARGO_HOME")
.map(PathBuf::from) .map(PathBuf::from)
.unwrap_or_else(|| ctx.base_dirs().home_dir().join(".cargo")) .unwrap_or_else(|| HOME_DIR.join(".cargo"))
.require()?; .require()?;
utils::require("cargo").or_else(|_| { require("cargo").or_else(|_| {
require_option( require_option(
cargo_dir.join("bin/cargo").if_exists(), cargo_dir.join("bin/cargo").if_exists(),
String::from("No cargo detected"), String::from("No cargo detected"),
@@ -41,7 +42,7 @@ pub fn run_cargo_update(ctx: &ExecutionContext) -> Result<()> {
} }
print_separator("Cargo"); print_separator("Cargo");
let cargo_update = utils::require("cargo-install-update") let cargo_update = require("cargo-install-update")
.ok() .ok()
.or_else(|| cargo_dir.join("bin/cargo-install-update").if_exists()); .or_else(|| cargo_dir.join("bin/cargo-install-update").if_exists());
let cargo_update = match cargo_update { let cargo_update = match cargo_update {
@@ -56,23 +57,40 @@ pub fn run_cargo_update(ctx: &ExecutionContext) -> Result<()> {
ctx.run_type() ctx.run_type()
.execute(cargo_update) .execute(cargo_update)
.args(["install-update", "--git", "--all"]) .args(["install-update", "--git", "--all"])
.status_checked() .status_checked()?;
if ctx.config().cleanup() {
let cargo_cache = require("cargo-cache")
.ok()
.or_else(|| cargo_dir.join("bin/cargo-cache").if_exists());
match cargo_cache {
Some(e) => {
ctx.run_type().execute(e).args(["-a"]).status_checked()?;
}
None => {
let message = String::from("cargo-cache isn't installed so Topgrade can't cleanup cargo packages.\nInstall cargo-cache by running `cargo install cargo-cache`");
print_warning(message);
}
}
}
Ok(())
} }
pub fn run_flutter_upgrade(run_type: RunType) -> Result<()> { pub fn run_flutter_upgrade(ctx: &ExecutionContext) -> Result<()> {
let flutter = utils::require("flutter")?; let flutter = require("flutter")?;
print_separator("Flutter"); print_separator("Flutter");
run_type.execute(flutter).arg("upgrade").status_checked() ctx.run_type().execute(flutter).arg("upgrade").status_checked()
} }
pub fn run_gem(base_dirs: &BaseDirs, run_type: RunType) -> Result<()> { pub fn run_gem(ctx: &ExecutionContext) -> Result<()> {
let gem = utils::require("gem")?; let gem = require("gem")?;
base_dirs.home_dir().join(".gem").require()?; HOME_DIR.join(".gem").require()?;
print_separator("RubyGems"); print_separator("Gems");
let mut command = run_type.execute(gem); let mut command = ctx.run_type().execute(gem);
command.arg("update"); command.arg("update");
if env::var_os("RBENV_SHELL").is_none() { if env::var_os("RBENV_SHELL").is_none() {
@@ -83,8 +101,34 @@ pub fn run_gem(base_dirs: &BaseDirs, run_type: RunType) -> Result<()> {
command.status_checked() command.status_checked()
} }
pub fn run_rubygems(ctx: &ExecutionContext) -> Result<()> {
HOME_DIR.join(".gem").require()?;
let gem = require("gem")?;
print_separator("RubyGems");
let gem_path_str = gem.as_os_str();
if gem_path_str.to_str().unwrap().contains("asdf") {
ctx.run_type()
.execute(gem)
.args(["update", "--system"])
.status_checked()?;
} else {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
if !Path::new("/usr/lib/ruby/vendor_ruby/rubygems/defaults/operating_system.rb").exists() {
ctx.run_type()
.execute(sudo)
.arg("-EH")
.arg(gem)
.args(["update", "--system"])
.status_checked()?;
}
}
Ok(())
}
pub fn run_haxelib_update(ctx: &ExecutionContext) -> Result<()> { pub fn run_haxelib_update(ctx: &ExecutionContext) -> Result<()> {
let haxelib = utils::require("haxelib")?; let haxelib = require("haxelib")?;
let haxelib_dir = let haxelib_dir =
PathBuf::from(std::str::from_utf8(&Command::new(&haxelib).arg("config").output_checked()?.stdout)?.trim()) PathBuf::from(std::str::from_utf8(&Command::new(&haxelib).arg("config").output_checked()?.stdout)?.trim())
@@ -98,9 +142,8 @@ pub fn run_haxelib_update(ctx: &ExecutionContext) -> Result<()> {
let mut command = if directory_writable { let mut command = if directory_writable {
ctx.run_type().execute(&haxelib) ctx.run_type().execute(&haxelib)
} else { } else {
let mut c = ctx let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
.run_type() let mut c = ctx.run_type().execute(sudo);
.execute(ctx.sudo().as_ref().ok_or(TopgradeError::SudoRequired)?);
c.arg(&haxelib); c.arg(&haxelib);
c c
}; };
@@ -109,7 +152,7 @@ pub fn run_haxelib_update(ctx: &ExecutionContext) -> Result<()> {
} }
pub fn run_sheldon(ctx: &ExecutionContext) -> Result<()> { pub fn run_sheldon(ctx: &ExecutionContext) -> Result<()> {
let sheldon = utils::require("sheldon")?; let sheldon = require("sheldon")?;
print_separator("Sheldon"); print_separator("Sheldon");
@@ -119,20 +162,21 @@ pub fn run_sheldon(ctx: &ExecutionContext) -> Result<()> {
.status_checked() .status_checked()
} }
pub fn run_fossil(run_type: RunType) -> Result<()> { pub fn run_fossil(ctx: &ExecutionContext) -> Result<()> {
let fossil = utils::require("fossil")?; let fossil = require("fossil")?;
print_separator("Fossil"); print_separator("Fossil");
run_type.execute(fossil).args(["all", "sync"]).status_checked() ctx.run_type().execute(fossil).args(["all", "sync"]).status_checked()
} }
pub fn run_micro(run_type: RunType) -> Result<()> { pub fn run_micro(ctx: &ExecutionContext) -> Result<()> {
let micro = utils::require("micro")?; let micro = require("micro")?;
print_separator("micro"); print_separator("micro");
let stdout = run_type let stdout = ctx
.run_type()
.execute(micro) .execute(micro)
.args(["-plugin", "update"]) .args(["-plugin", "update"])
.output_checked_utf8()? .output_checked_utf8()?
@@ -152,31 +196,41 @@ pub fn run_micro(run_type: RunType) -> Result<()> {
target_os = "netbsd", target_os = "netbsd",
target_os = "dragonfly" target_os = "dragonfly"
)))] )))]
pub fn run_apm(run_type: RunType) -> Result<()> { pub fn run_apm(ctx: &ExecutionContext) -> Result<()> {
let apm = utils::require("apm")?; let apm = require("apm")?;
print_separator("Atom Package Manager"); print_separator("Atom Package Manager");
run_type ctx.run_type()
.execute(apm) .execute(apm)
.args(["upgrade", "--confirm=false"]) .args(["upgrade", "--confirm=false"])
.status_checked() .status_checked()
} }
pub fn run_rustup(base_dirs: &BaseDirs, run_type: RunType) -> Result<()> { pub fn run_rustup(ctx: &ExecutionContext) -> Result<()> {
let rustup = utils::require("rustup")?; let rustup = require("rustup")?;
print_separator("rustup"); print_separator("rustup");
ctx.run_type().execute(rustup).arg("update").status_checked()
}
if rustup.canonicalize()?.is_descendant_of(base_dirs.home_dir()) { pub fn run_juliaup(ctx: &ExecutionContext) -> Result<()> {
run_type.execute(&rustup).args(["self", "update"]).status_checked()?; let juliaup = require("juliaup")?;
print_separator("juliaup");
if juliaup.canonicalize()?.is_descendant_of(&HOME_DIR) {
ctx.run_type()
.execute(&juliaup)
.args(["self", "update"])
.status_checked()?;
} }
run_type.execute(&rustup).arg("update").status_checked() ctx.run_type().execute(&juliaup).arg("update").status_checked()
} }
pub fn run_choosenim(ctx: &ExecutionContext) -> Result<()> { pub fn run_choosenim(ctx: &ExecutionContext) -> Result<()> {
let choosenim = utils::require("choosenim")?; let choosenim = require("choosenim")?;
print_separator("choosenim"); print_separator("choosenim");
let run_type = ctx.run_type(); let run_type = ctx.run_type();
@@ -185,39 +239,42 @@ pub fn run_choosenim(ctx: &ExecutionContext) -> Result<()> {
run_type.execute(&choosenim).args(["update", "stable"]).status_checked() run_type.execute(&choosenim).args(["update", "stable"]).status_checked()
} }
pub fn run_krew_upgrade(run_type: RunType) -> Result<()> { pub fn run_krew_upgrade(ctx: &ExecutionContext) -> Result<()> {
let krew = utils::require("kubectl-krew")?; let krew = require("kubectl-krew")?;
print_separator("Krew"); print_separator("Krew");
run_type.execute(krew).args(["upgrade"]).status_checked() ctx.run_type().execute(krew).args(["upgrade"]).status_checked()
} }
pub fn run_gcloud_components_update(run_type: RunType) -> Result<()> { pub fn run_gcloud_components_update(ctx: &ExecutionContext) -> Result<()> {
let gcloud = utils::require("gcloud")?; let gcloud = require("gcloud")?;
if gcloud.starts_with("/snap") { if gcloud.starts_with("/snap") {
Ok(()) Ok(())
} else { } else {
print_separator("gcloud"); print_separator("gcloud");
run_type ctx.run_type()
.execute(gcloud) .execute(gcloud)
.args(["components", "update", "--quiet"]) .args(["components", "update", "--quiet"])
.status_checked() .status_checked()
} }
} }
pub fn run_jetpack(run_type: RunType) -> Result<()> { pub fn run_jetpack(ctx: &ExecutionContext) -> Result<()> {
let jetpack = utils::require("jetpack")?; let jetpack = require("jetpack")?;
print_separator("Jetpack"); print_separator("Jetpack");
run_type.execute(jetpack).args(["global", "update"]).status_checked() ctx.run_type()
.execute(jetpack)
.args(["global", "update"])
.status_checked()
} }
pub fn run_rtcl(ctx: &ExecutionContext) -> Result<()> { pub fn run_rtcl(ctx: &ExecutionContext) -> Result<()> {
let rupdate = utils::require("rupdate")?; let rupdate = require("rupdate")?;
print_separator("rtcl"); print_separator("rtcl");
@@ -225,7 +282,7 @@ pub fn run_rtcl(ctx: &ExecutionContext) -> Result<()> {
} }
pub fn run_opam_update(ctx: &ExecutionContext) -> Result<()> { pub fn run_opam_update(ctx: &ExecutionContext) -> Result<()> {
let opam = utils::require("opam")?; let opam = require("opam")?;
print_separator("OCaml Package Manager"); print_separator("OCaml Package Manager");
@@ -239,25 +296,37 @@ pub fn run_opam_update(ctx: &ExecutionContext) -> Result<()> {
Ok(()) Ok(())
} }
pub fn run_vcpkg_update(run_type: RunType) -> Result<()> { pub fn run_vcpkg_update(ctx: &ExecutionContext) -> Result<()> {
let vcpkg = utils::require("vcpkg")?; let vcpkg = require("vcpkg")?;
print_separator("vcpkg"); print_separator("vcpkg");
run_type #[cfg(unix)]
.execute(vcpkg) let is_root_install = !&vcpkg.starts_with("/home");
.args(["upgrade", "--no-dry-run"])
.status_checked() #[cfg(not(unix))]
let is_root_install = false;
let mut command = if is_root_install {
ctx.run_type().execute(&vcpkg)
} else {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
let mut c = ctx.run_type().execute(sudo);
c.arg(&vcpkg);
c
};
command.args(["upgrade", "--no-dry-run"]).status_checked()
} }
pub fn run_pipx_update(run_type: RunType) -> Result<()> { pub fn run_pipx_update(ctx: &ExecutionContext) -> Result<()> {
let pipx = utils::require("pipx")?; let pipx = require("pipx")?;
print_separator("pipx"); print_separator("pipx");
run_type.execute(pipx).arg("upgrade-all").status_checked() ctx.run_type().execute(pipx).arg("upgrade-all").status_checked()
} }
pub fn run_conda_update(ctx: &ExecutionContext) -> Result<()> { pub fn run_conda_update(ctx: &ExecutionContext) -> Result<()> {
let conda = utils::require("conda")?; let conda = require("conda")?;
let output = Command::new("conda") let output = Command::new("conda")
.args(["config", "--show", "auto_activate_base"]) .args(["config", "--show", "auto_activate_base"])
@@ -269,50 +338,153 @@ pub fn run_conda_update(ctx: &ExecutionContext) -> Result<()> {
print_separator("Conda"); print_separator("Conda");
ctx.run_type() let mut command = ctx.run_type().execute(conda);
.execute(conda) command.args(["update", "--all", "-n", "base"]);
.args(["update", "--all", "-y"]) if ctx.config().yes(Step::Conda) {
.status_checked() command.arg("--yes");
}
command.status_checked()
} }
pub fn run_pip3_update(run_type: RunType) -> Result<()> { pub fn run_mamba_update(ctx: &ExecutionContext) -> Result<()> {
let python3 = utils::require("python3")?; let mamba = require("mamba")?;
let output = Command::new("mamba")
.args(["config", "--show", "auto_activate_base"])
.output_checked_utf8()?;
debug!("Mamba output: {}", output.stdout);
if output.stdout.contains("False") {
return Err(SkipStep("auto_activate_base is set to False".to_string()).into());
}
print_separator("Mamba");
let mut command = ctx.run_type().execute(mamba);
command.args(["update", "--all", "-n", "base"]);
if ctx.config().yes(Step::Mamba) {
command.arg("--yes");
}
command.status_checked()
}
pub fn run_pip3_update(ctx: &ExecutionContext) -> Result<()> {
let py = require("python").and_then(check_is_python_2_or_shim);
let py3 = require("python3").and_then(check_is_python_2_or_shim);
let python3 = match (py, py3) {
// prefer `python` if it is available and is a valid Python 3.
(Ok(py), _) => py,
(Err(_), Ok(py3)) => py3,
(Err(py_err), Err(py3_err)) => {
return Err(SkipStep(format!("Skip due to following reasons: {} {}", py_err, py3_err)).into());
}
};
Command::new(&python3) Command::new(&python3)
.args(["-m", "pip"]) .args(["-m", "pip"])
.output_checked_utf8() .output_checked_utf8()
.map_err(|_| SkipStep("pip does not exists".to_string()))?; .map_err(|_| SkipStep("pip does not exists".to_string()))?;
let check_externally_managed = "import sysconfig; from os import path; print('Y') if path.isfile(path.join(sysconfig.get_path('stdlib'), 'EXTERNALLY-MANAGED')) else print('N')";
Command::new(&python3)
.args(["-c", check_externally_managed])
.output_checked_utf8()
.map_err(|_| SkipStep("pip may be externally managed".to_string()))
.and_then(|output| match output.stdout.trim() {
"N" => Ok(()),
"Y" => Err(SkipStep("pip is externally managed".to_string())),
_ => {
print_warning("Unexpected output when checking EXTERNALLY-MANAGED");
print_warning(output.stdout.trim());
Err(SkipStep("pip may be externally managed".to_string()))
}
})?;
print_separator("pip3"); print_separator("pip3");
if std::env::var("VIRTUAL_ENV").is_ok() { if env::var("VIRTUAL_ENV").is_ok() {
print_warning("This step is will be skipped when running inside a virtual environment"); print_warning("This step is will be skipped when running inside a virtual environment");
return Err(SkipStep("Does not run inside a virtual environment".to_string()).into()); return Err(SkipStep("Does not run inside a virtual environment".to_string()).into());
} }
run_type ctx.run_type()
.execute(&python3) .execute(&python3)
.args(["-m", "pip", "install", "--upgrade", "--user", "pip"]) .args(["-m", "pip", "install", "--upgrade", "--user", "pip"])
.status_checked() .status_checked()
} }
pub fn run_stack_update(run_type: RunType) -> Result<()> { pub fn run_pip_review_update(ctx: &ExecutionContext) -> Result<()> {
if utils::require("ghcup").is_ok() { let pip_review = require("pip-review")?;
print_separator("pip-review");
if !ctx.config().enable_pip_review() {
print_warning(
"Pip-review is disabled by default. Enable it by setting enable_pip_review=true in the configuration.",
);
return Err(SkipStep(String::from("Pip-review is disabled by default")).into());
}
ctx.run_type()
.execute(pip_review)
.arg("--auto")
.status_checked_with_codes(&[1])?;
Ok(())
}
pub fn run_pip_review_local_update(ctx: &ExecutionContext) -> Result<()> {
let pip_review = require("pip-review")?;
print_separator("pip-review (local)");
if !ctx.config().enable_pip_review_local() {
print_warning(
"Pip-review (local) is disabled by default. Enable it by setting enable_pip_review_local=true in the configuration.",
);
return Err(SkipStep(String::from("Pip-review (local) is disabled by default")).into());
}
ctx.run_type()
.execute(pip_review)
.arg("--local")
.arg("--auto")
.status_checked_with_codes(&[1])?;
Ok(())
}
pub fn run_pipupgrade_update(ctx: &ExecutionContext) -> Result<()> {
let pipupgrade = require("pipupgrade")?;
print_separator("Pipupgrade");
if !ctx.config().enable_pipupgrade() {
print_warning(
"Pipupgrade is disabled by default. Enable it by setting enable_pipupgrade=true in the configuration.",
);
return Err(SkipStep(String::from("Pipupgrade is disabled by default")).into());
}
ctx.run_type()
.execute(pipupgrade)
.args(ctx.config().pipupgrade_arguments().split_whitespace())
.status_checked()?;
Ok(())
}
pub fn run_stack_update(ctx: &ExecutionContext) -> Result<()> {
if require("ghcup").is_ok() {
// `ghcup` is present and probably(?) being used to install `stack`. // `ghcup` is present and probably(?) being used to install `stack`.
// Don't upgrade `stack`, let `ghcup` handle it. Per `ghcup install stack`: // Don't upgrade `stack`, let `ghcup` handle it. Per `ghcup install stack`:
// !!! Additionally, you should upgrade stack only through ghcup and not use 'stack upgrade' !!! // !!! Additionally, you should upgrade stack only through ghcup and not use 'stack upgrade' !!!
return Ok(()); return Ok(());
} }
let stack = utils::require("stack")?; let stack = require("stack")?;
print_separator("stack"); print_separator("stack");
run_type.execute(stack).arg("upgrade").status_checked() ctx.run_type().execute(stack).arg("upgrade").status_checked()
} }
pub fn run_ghcup_update(run_type: RunType) -> Result<()> { pub fn run_ghcup_update(ctx: &ExecutionContext) -> Result<()> {
let ghcup = utils::require("ghcup")?; let ghcup = require("ghcup")?;
print_separator("ghcup"); print_separator("ghcup");
run_type.execute(ghcup).arg("upgrade").status_checked() ctx.run_type().execute(ghcup).arg("upgrade").status_checked()
} }
pub fn run_tlmgr_update(ctx: &ExecutionContext) -> Result<()> { pub fn run_tlmgr_update(ctx: &ExecutionContext) -> Result<()> {
@@ -324,8 +496,8 @@ pub fn run_tlmgr_update(ctx: &ExecutionContext) -> Result<()> {
} }
} }
let tlmgr = utils::require("tlmgr")?; let tlmgr = require("tlmgr")?;
let kpsewhich = utils::require("kpsewhich")?; let kpsewhich = require("kpsewhich")?;
let tlmgr_directory = { let tlmgr_directory = {
let mut d = PathBuf::from( let mut d = PathBuf::from(
&Command::new(kpsewhich) &Command::new(kpsewhich)
@@ -347,9 +519,8 @@ pub fn run_tlmgr_update(ctx: &ExecutionContext) -> Result<()> {
let mut command = if directory_writable { let mut command = if directory_writable {
ctx.run_type().execute(&tlmgr) ctx.run_type().execute(&tlmgr)
} else { } else {
let mut c = ctx let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
.run_type() let mut c = ctx.run_type().execute(sudo);
.execute(ctx.sudo().as_ref().ok_or(TopgradeError::SudoRequired)?);
c.arg(&tlmgr); c.arg(&tlmgr);
c c
}; };
@@ -358,50 +529,58 @@ pub fn run_tlmgr_update(ctx: &ExecutionContext) -> Result<()> {
command.status_checked() command.status_checked()
} }
pub fn run_chezmoi_update(base_dirs: &BaseDirs, run_type: RunType) -> Result<()> { pub fn run_chezmoi_update(ctx: &ExecutionContext) -> Result<()> {
let chezmoi = utils::require("chezmoi")?; let chezmoi = require("chezmoi")?;
base_dirs.home_dir().join(".local/share/chezmoi").require()?; HOME_DIR.join(".local/share/chezmoi").require()?;
print_separator("chezmoi"); print_separator("chezmoi");
run_type.execute(chezmoi).arg("update").status_checked() ctx.run_type().execute(chezmoi).arg("update").status_checked()
} }
pub fn run_myrepos_update(base_dirs: &BaseDirs, run_type: RunType) -> Result<()> { pub fn run_myrepos_update(ctx: &ExecutionContext) -> Result<()> {
let myrepos = utils::require("mr")?; let myrepos = require("mr")?;
base_dirs.home_dir().join(".mrconfig").require()?; HOME_DIR.join(".mrconfig").require()?;
print_separator("myrepos"); print_separator("myrepos");
run_type ctx.run_type()
.execute(&myrepos) .execute(&myrepos)
.arg("--directory") .arg("--directory")
.arg(base_dirs.home_dir()) .arg(&*HOME_DIR)
.arg("checkout") .arg("checkout")
.status_checked()?; .status_checked()?;
run_type ctx.run_type()
.execute(&myrepos) .execute(&myrepos)
.arg("--directory") .arg("--directory")
.arg(base_dirs.home_dir()) .arg(&*HOME_DIR)
.arg("update") .arg("update")
.status_checked() .status_checked()
} }
pub fn run_custom_command(name: &str, command: &str, ctx: &ExecutionContext) -> Result<()> { pub fn run_custom_command(name: &str, command: &str, ctx: &ExecutionContext) -> Result<()> {
print_separator(name); print_separator(name);
ctx.run_type().execute(shell()).arg("-c").arg(command).status_checked() let mut exec = ctx.run_type().execute(shell());
#[cfg(unix)]
let command = if let Some(command) = command.strip_prefix("-i ") {
exec.arg("-i");
command
} else {
command
};
exec.arg("-c").arg(command).status_checked()
} }
pub fn run_composer_update(ctx: &ExecutionContext) -> Result<()> { pub fn run_composer_update(ctx: &ExecutionContext) -> Result<()> {
let composer = utils::require("composer")?; let composer = require("composer")?;
let composer_home = Command::new(&composer) let composer_home = Command::new(&composer)
.args(["global", "config", "--absolute", "--quiet", "home"]) .args(["global", "config", "--absolute", "--quiet", "home"])
.output_checked_utf8() .output_checked_utf8()
.map_err(|e| (SkipStep(format!("Error getting the composer directory: {}", e)))) .map_err(|e| (SkipStep(format!("Error getting the composer directory: {e}"))))
.map(|s| PathBuf::from(s.stdout.trim()))? .map(|s| PathBuf::from(s.stdout.trim()))?
.require()?; .require()?;
if !composer_home.is_descendant_of(ctx.base_dirs().home_dir()) { if !composer_home.is_descendant_of(&HOME_DIR) {
return Err(SkipStep(format!( return Err(SkipStep(format!(
"Composer directory {} isn't a decandent of the user's home directory", "Composer directory {} isn't a decandent of the user's home directory",
composer_home.display() composer_home.display()
@@ -421,8 +600,9 @@ pub fn run_composer_update(ctx: &ExecutionContext) -> Result<()> {
}; };
if has_update { if has_update {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
ctx.run_type() ctx.run_type()
.execute(ctx.sudo().as_ref().unwrap()) .execute(sudo)
.arg(&composer) .arg(&composer)
.arg("self-update") .arg("self-update")
.status_checked()?; .status_checked()?;
@@ -438,7 +618,7 @@ pub fn run_composer_update(ctx: &ExecutionContext) -> Result<()> {
let output: Utf8Output = output.try_into()?; let output: Utf8Output = output.try_into()?;
print!("{}\n{}", output.stdout, output.stderr); print!("{}\n{}", output.stdout, output.stderr);
if output.stdout.contains("valet") || output.stderr.contains("valet") { if output.stdout.contains("valet") || output.stderr.contains("valet") {
if let Some(valet) = utils::which("valet") { if let Some(valet) = which("valet") {
ctx.run_type().execute(valet).arg("install").status_checked()?; ctx.run_type().execute(valet).arg("install").status_checked()?;
} }
} }
@@ -448,17 +628,39 @@ pub fn run_composer_update(ctx: &ExecutionContext) -> Result<()> {
} }
pub fn run_dotnet_upgrade(ctx: &ExecutionContext) -> Result<()> { pub fn run_dotnet_upgrade(ctx: &ExecutionContext) -> Result<()> {
let dotnet = utils::require("dotnet")?; let dotnet = require("dotnet")?;
let output = Command::new(dotnet) // Skip when the `dotnet tool list` subcommand fails.
// (This is expected when a dotnet runtime is installed but no SDK.)
let output = match ctx
.run_type()
.execute(&dotnet)
.args(["tool", "list", "--global"]) .args(["tool", "list", "--global"])
.output_checked_utf8()?; .output_checked_utf8()
{
Ok(output) => output,
Err(_) => {
return Err(SkipStep(String::from(
"Error running `dotnet tool list`. This is expected when a dotnet runtime is installed but no SDK.",
))
.into())
}
};
if !output.stdout.starts_with("Package Id") { let mut packages = output
return Err(SkipStep(String::from("dotnet did not output packages")).into()); .stdout
} .lines()
// Skip the header:
let mut packages = output.stdout.lines().skip(2).filter(|line| !line.is_empty()).peekable(); //
// Package Id Version Commands
// -------------------------------------
//
// One thing to note is that .NET SDK respect locale, which means this
// header can be printed in languages other than English, do NOT use it
// to do any check.
.skip(2)
.filter(|line| !line.is_empty())
.peekable();
if packages.peek().is_none() { if packages.peek().is_none() {
return Err(SkipStep(String::from("No dotnet global tools installed")).into()); return Err(SkipStep(String::from("No dotnet global tools installed")).into());
@@ -469,7 +671,7 @@ pub fn run_dotnet_upgrade(ctx: &ExecutionContext) -> Result<()> {
for package in packages { for package in packages {
let package_name = package.split_whitespace().next().unwrap(); let package_name = package.split_whitespace().next().unwrap();
ctx.run_type() ctx.run_type()
.execute("dotnet") .execute(&dotnet)
.args(["tool", "update", package_name, "--global"]) .args(["tool", "update", package_name, "--global"])
.status_checked() .status_checked()
.with_context(|| format!("Failed to update .NET package {package_name}"))?; .with_context(|| format!("Failed to update .NET package {package_name}"))?;
@@ -478,30 +680,55 @@ pub fn run_dotnet_upgrade(ctx: &ExecutionContext) -> Result<()> {
Ok(()) Ok(())
} }
pub fn run_raco_update(run_type: RunType) -> Result<()> { pub fn run_helix_grammars(ctx: &ExecutionContext) -> Result<()> {
let raco = utils::require("raco")?; require("helix")?;
print_separator("Helix");
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
ctx.run_type()
.execute(sudo)
.args(["helix", "--grammar", "fetch"])
.status_checked()
.with_context(|| "Failed to download helix grammars!")?;
ctx.run_type()
.execute(sudo)
.args(["helix", "--grammar", "build"])
.status_checked()
.with_context(|| "Failed to build helix grammars!")?;
Ok(())
}
pub fn run_raco_update(ctx: &ExecutionContext) -> Result<()> {
let raco = require("raco")?;
print_separator("Racket Package Manager"); print_separator("Racket Package Manager");
run_type.execute(raco).args(["pkg", "update", "--all"]).status_checked() ctx.run_type()
.execute(raco)
.args(["pkg", "update", "--all"])
.status_checked()
} }
pub fn bin_update(ctx: &ExecutionContext) -> Result<()> { pub fn bin_update(ctx: &ExecutionContext) -> Result<()> {
let bin = utils::require("bin")?; let bin = require("bin")?;
print_separator("Bin"); print_separator("Bin");
ctx.run_type().execute(bin).arg("update").status_checked() ctx.run_type().execute(bin).arg("update").status_checked()
} }
pub fn spicetify_upgrade(ctx: &ExecutionContext) -> Result<()> { pub fn spicetify_upgrade(ctx: &ExecutionContext) -> Result<()> {
let spicetify = utils::require("spicetify")?; // As of 04-07-2023 NixOS packages Spicetify with the `spicetify-cli` binary name
let spicetify = require("spicetify").or(require("spicetify-cli"))?;
print_separator("Spicetify"); print_separator("Spicetify");
ctx.run_type().execute(spicetify).arg("upgrade").status_checked() ctx.run_type().execute(spicetify).arg("upgrade").status_checked()
} }
pub fn run_ghcli_extensions_upgrade(ctx: &ExecutionContext) -> Result<()> { pub fn run_ghcli_extensions_upgrade(ctx: &ExecutionContext) -> Result<()> {
let gh = utils::require("gh")?; let gh = require("gh")?;
let result = Command::new(&gh).args(["extensions", "list"]).output_checked_utf8(); let result = Command::new(&gh).args(["extensions", "list"]).output_checked_utf8();
if result.is_err() { if result.is_err() {
debug!("GH result {:?}", result); debug!("GH result {:?}", result);
@@ -516,7 +743,7 @@ pub fn run_ghcli_extensions_upgrade(ctx: &ExecutionContext) -> Result<()> {
} }
pub fn update_julia_packages(ctx: &ExecutionContext) -> Result<()> { pub fn update_julia_packages(ctx: &ExecutionContext) -> Result<()> {
let julia = utils::require("julia")?; let julia = require("julia")?;
print_separator("Julia Packages"); print_separator("Julia Packages");
@@ -525,3 +752,44 @@ pub fn update_julia_packages(ctx: &ExecutionContext) -> Result<()> {
.args(["-e", "using Pkg; Pkg.update()"]) .args(["-e", "using Pkg; Pkg.update()"])
.status_checked() .status_checked()
} }
pub fn run_helm_repo_update(ctx: &ExecutionContext) -> Result<()> {
let helm = require("helm")?;
print_separator("Helm");
let no_repo = "no repositories found";
let mut success = true;
let mut exec = ctx.run_type().execute(helm);
if let Err(e) = exec.arg("repo").arg("update").status_checked() {
error!("Updating repositories failed: {}", e);
success = match exec.output_checked_utf8() {
Ok(s) => s.stdout.contains(no_repo) || s.stderr.contains(no_repo),
Err(e) => match e.downcast_ref::<TopgradeError>() {
Some(TopgradeError::ProcessFailedWithOutput(_, _, stderr)) => stderr.contains(no_repo),
_ => false,
},
};
}
if success {
Ok(())
} else {
Err(eyre!(StepFailed))
}
}
pub fn run_stew(ctx: &ExecutionContext) -> Result<()> {
let stew = require("stew")?;
print_separator("stew");
ctx.run_type().execute(stew).args(["upgrade", "--all"]).status_checked()
}
pub fn run_bob(ctx: &ExecutionContext) -> Result<()> {
let bob = require("bob")?;
print_separator("Bob");
ctx.run_type().execute(bob).args(["update", "--all"]).status_checked()
}

View File

@@ -14,7 +14,6 @@ use tracing::{debug, error};
use crate::command::CommandExt; use crate::command::CommandExt;
use crate::execution_context::ExecutionContext; use crate::execution_context::ExecutionContext;
use crate::executor::RunType;
use crate::terminal::print_separator; use crate::terminal::print_separator;
use crate::utils::{which, PathExt}; use crate::utils::{which, PathExt};
use crate::{error::SkipStep, terminal::print_warning}; use crate::{error::SkipStep, terminal::print_warning};
@@ -71,7 +70,7 @@ async fn pull_repository(repo: String, git: &Path, ctx: &ExecutionContext<'_>) -
if let Err(message) = &result { if let Err(message) = &result {
println!("{} pulling {}", style("Failed").red().bold(), &repo); println!("{} pulling {}", style("Failed").red().bold(), &repo);
print!("{}", message); print!("{message}");
} else { } else {
let after_revision = get_head_revision(git, &repo); let after_revision = get_head_revision(git, &repo);
@@ -87,7 +86,7 @@ async fn pull_repository(repo: String, git: &Path, ctx: &ExecutionContext<'_>) -
"log", "log",
"--no-decorate", "--no-decorate",
"--oneline", "--oneline",
&format!("{}..{}", before, after), &format!("{before}..{after}"),
]) ])
.status_checked()?; .status_checked()?;
println!(); println!();
@@ -179,22 +178,28 @@ impl Git {
None None
} }
pub fn multi_pull_step(&self, repositories: &Repositories, ctx: &ExecutionContext) -> Result<()> { pub fn multi_pull_step(&self, repositories: &Repositories, ctx: &ExecutionContext) -> Result<()> {
// Warn the user about the bad patterns.
//
// NOTE: this should be executed **before** skipping the Git step or the
// user won't receive this warning in the cases where all the paths configured
// are bad patterns.
repositories
.bad_patterns
.iter()
.for_each(|pattern| print_warning(format!("Path {pattern} did not contain any git repositories")));
if repositories.repositories.is_empty() { if repositories.repositories.is_empty() {
return Err(SkipStep(String::from("No repositories to pull")).into()); return Err(SkipStep(String::from("No repositories to pull")).into());
} }
print_separator("Git repositories"); print_separator("Git repositories");
repositories
.bad_patterns
.iter()
.for_each(|pattern| print_warning(format!("Path {} did not contain any git repositories", pattern)));
self.multi_pull(repositories, ctx) self.multi_pull(repositories, ctx)
} }
pub fn multi_pull(&self, repositories: &Repositories, ctx: &ExecutionContext) -> Result<()> { pub fn multi_pull(&self, repositories: &Repositories, ctx: &ExecutionContext) -> Result<()> {
let git = self.git.as_ref().unwrap(); let git = self.git.as_ref().unwrap();
if let RunType::Dry = ctx.run_type() { if ctx.run_type().dry() {
repositories repositories
.repositories .repositories
.iter() .iter()

View File

@@ -4,27 +4,27 @@ use std::process::Command;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use crate::command::CommandExt; use crate::command::CommandExt;
use crate::executor::RunType; use crate::execution_context::ExecutionContext;
use crate::terminal::print_separator; use crate::terminal::print_separator;
use crate::utils; use crate::utils;
use crate::utils::PathExt; use crate::utils::PathExt;
/// <https://github.com/Gelio/go-global-update> /// <https://github.com/Gelio/go-global-update>
pub fn run_go_global_update(run_type: RunType) -> Result<()> { pub fn run_go_global_update(ctx: &ExecutionContext) -> Result<()> {
let go_global_update = require_go_bin("go-global-update")?; let go_global_update = require_go_bin("go-global-update")?;
print_separator("go-global-update"); print_separator("go-global-update");
run_type.execute(go_global_update).status_checked() ctx.run_type().execute(go_global_update).status_checked()
} }
/// <https://github.com/nao1215/gup> /// <https://github.com/nao1215/gup>
pub fn run_go_gup(run_type: RunType) -> Result<()> { pub fn run_go_gup(ctx: &ExecutionContext) -> Result<()> {
let gup = require_go_bin("gup")?; let gup = require_go_bin("gup")?;
print_separator("gup"); print_separator("gup");
run_type.execute(gup).arg("update").status_checked() ctx.run_type().execute(gup).arg("update").status_checked()
} }
/// Get the path of a Go binary. /// Get the path of a Go binary.

View File

@@ -4,7 +4,8 @@ use std::os::unix::fs::MetadataExt;
use std::path::PathBuf; use std::path::PathBuf;
use std::process::Command; use std::process::Command;
use crate::utils::require_option; use crate::utils::{require_option, REQUIRE_SUDO};
use crate::HOME_DIR;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
use nix::unistd::Uid; use nix::unistd::Uid;
@@ -12,9 +13,7 @@ use semver::Version;
use tracing::debug; use tracing::debug;
use crate::command::CommandExt; use crate::command::CommandExt;
use crate::executor::RunType;
use crate::terminal::print_separator; use crate::terminal::print_separator;
use crate::utils::sudo;
use crate::utils::{require, PathExt}; use crate::utils::{require, PathExt};
use crate::{error::SkipStep, execution_context::ExecutionContext}; use crate::{error::SkipStep, execution_context::ExecutionContext};
@@ -90,14 +89,17 @@ impl NPM {
Version::parse(&version_str?).map_err(|err| err.into()) Version::parse(&version_str?).map_err(|err| err.into())
} }
fn upgrade(&self, run_type: RunType, use_sudo: bool) -> Result<()> { fn upgrade(&self, ctx: &ExecutionContext, use_sudo: bool) -> Result<()> {
let args = ["update", self.global_location_arg()]; let args = ["update", self.global_location_arg()];
if use_sudo { if use_sudo {
let sudo_option = sudo(); let sudo = require_option(ctx.sudo().clone(), REQUIRE_SUDO.to_string())?;
let sudo = require_option(sudo_option, String::from("sudo is not installed"))?; ctx.run_type()
run_type.execute(sudo).arg(&self.command).args(args).status_checked()?; .execute(sudo)
.arg(&self.command)
.args(args)
.status_checked()?;
} else { } else {
run_type.execute(&self.command).args(args).status_checked()?; ctx.run_type().execute(&self.command).args(args).status_checked()?;
} }
Ok(()) Ok(())
@@ -150,17 +152,18 @@ impl Yarn {
.map(|s| PathBuf::from(s.stdout.trim())) .map(|s| PathBuf::from(s.stdout.trim()))
} }
fn upgrade(&self, run_type: RunType, use_sudo: bool) -> Result<()> { fn upgrade(&self, ctx: &ExecutionContext, use_sudo: bool) -> Result<()> {
let args = ["global", "upgrade"]; let args = ["global", "upgrade"];
if use_sudo { if use_sudo {
run_type let sudo = require_option(ctx.sudo().clone(), REQUIRE_SUDO.to_string())?;
.execute("sudo") ctx.run_type()
.execute(sudo)
.arg(self.yarn.as_ref().unwrap_or(&self.command)) .arg(self.yarn.as_ref().unwrap_or(&self.command))
.args(args) .args(args)
.status_checked()?; .status_checked()?;
} else { } else {
run_type.execute(&self.command).args(args).status_checked()?; ctx.run_type().execute(&self.command).args(args).status_checked()?;
} }
Ok(()) Ok(())
@@ -215,28 +218,28 @@ pub fn run_npm_upgrade(ctx: &ExecutionContext) -> Result<()> {
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
{ {
npm.upgrade(ctx.run_type(), should_use_sudo(&npm, ctx)?) npm.upgrade(ctx, should_use_sudo(&npm, ctx)?)
} }
#[cfg(not(target_os = "linux"))] #[cfg(not(target_os = "linux"))]
{ {
npm.upgrade(ctx.run_type(), false) npm.upgrade(ctx, false)
} }
} }
pub fn run_pnpm_upgrade(ctx: &ExecutionContext) -> Result<()> { pub fn run_pnpm_upgrade(ctx: &ExecutionContext) -> Result<()> {
let pnpm = require("pnpm").map(|b| NPM::new(b, NPMVariant::Pnpm))?; let pnpm = require("pnpm").map(|b| NPM::new(b, NPMVariant::Pnpm))?;
print_separator("Node Package Manager"); print_separator("Performant Node Package Manager");
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
{ {
pnpm.upgrade(ctx.run_type(), should_use_sudo(&pnpm, ctx)?) pnpm.upgrade(ctx, should_use_sudo(&pnpm, ctx)?)
} }
#[cfg(not(target_os = "linux"))] #[cfg(not(target_os = "linux"))]
{ {
pnpm.upgrade(ctx.run_type(), false) pnpm.upgrade(ctx, false)
} }
} }
@@ -252,18 +255,18 @@ pub fn run_yarn_upgrade(ctx: &ExecutionContext) -> Result<()> {
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
{ {
yarn.upgrade(ctx.run_type(), should_use_sudo_yarn(&yarn, ctx)?) yarn.upgrade(ctx, should_use_sudo_yarn(&yarn, ctx)?)
} }
#[cfg(not(target_os = "linux"))] #[cfg(not(target_os = "linux"))]
{ {
yarn.upgrade(ctx.run_type(), false) yarn.upgrade(ctx, false)
} }
} }
pub fn deno_upgrade(ctx: &ExecutionContext) -> Result<()> { pub fn deno_upgrade(ctx: &ExecutionContext) -> Result<()> {
let deno = require("deno")?; let deno = require("deno")?;
let deno_dir = ctx.base_dirs().home_dir().join(".deno"); let deno_dir = HOME_DIR.join(".deno");
if !deno.canonicalize()?.is_descendant_of(&deno_dir) { if !deno.canonicalize()?.is_descendant_of(&deno_dir) {
let skip_reason = SkipStep("Deno installed outside of .deno directory".to_string()); let skip_reason = SkipStep("Deno installed outside of .deno directory".to_string());

View File

@@ -26,7 +26,7 @@ pub fn upgrade_packages(ctx: &ExecutionContext) -> Result<()> {
ctx.run_type().execute(&pkg).arg("clean").status_checked()?; ctx.run_type().execute(&pkg).arg("clean").status_checked()?;
let apt = require("apt")?; let apt = require("apt")?;
let mut command = ctx.run_type().execute(&apt); let mut command = ctx.run_type().execute(apt);
command.arg("autoremove"); command.arg("autoremove");
if ctx.config().yes(Step::System) { if ctx.config().yes(Step::System) {
command.arg("-y"); command.arg("-y");

View File

@@ -1,7 +1,6 @@
use std::env::var_os; use std::env::var_os;
use std::ffi::OsString; use std::ffi::OsString;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::process::Command;
use color_eyre::eyre; use color_eyre::eyre;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
@@ -10,6 +9,7 @@ use walkdir::WalkDir;
use crate::command::CommandExt; use crate::command::CommandExt;
use crate::error::TopgradeError; use crate::error::TopgradeError;
use crate::execution_context::ExecutionContext; use crate::execution_context::ExecutionContext;
use crate::sudo::Sudo;
use crate::utils::which; use crate::utils::which;
use crate::{config, Step}; use crate::{config, Step};
@@ -31,7 +31,10 @@ pub struct YayParu {
impl ArchPackageManager for YayParu { impl ArchPackageManager for YayParu {
fn upgrade(&self, ctx: &ExecutionContext) -> Result<()> { fn upgrade(&self, ctx: &ExecutionContext) -> Result<()> {
if ctx.config().show_arch_news() { if ctx.config().show_arch_news() {
Command::new(&self.executable).arg("-Pw").status_checked()?; ctx.run_type()
.execute(&self.executable)
.arg("-Pw")
.status_checked_with_codes(&[1, 0])?;
} }
let mut command = ctx.run_type().execute(&self.executable); let mut command = ctx.run_type().execute(&self.executable);
@@ -70,6 +73,37 @@ impl YayParu {
} }
} }
pub struct GarudaUpdate {
executable: PathBuf,
}
impl ArchPackageManager for GarudaUpdate {
fn upgrade(&self, ctx: &ExecutionContext) -> Result<()> {
let mut command = ctx.run_type().execute(&self.executable);
command
.env("PATH", get_execution_path())
.env("UPDATE_AUR", "1")
.env("SKIP_MIRRORLIST", "1");
if ctx.config().yes(Step::System) {
command.env("PACMAN_NOCONFIRM", "1");
}
command.args(ctx.config().garuda_update_arguments().split_whitespace());
command.status_checked()?;
Ok(())
}
}
impl GarudaUpdate {
fn get() -> Option<Self> {
Some(Self {
executable: which("garuda-update")?,
})
}
}
pub struct Trizen { pub struct Trizen {
executable: PathBuf, executable: PathBuf,
} }
@@ -110,7 +144,7 @@ impl Trizen {
} }
pub struct Pacman { pub struct Pacman {
sudo: PathBuf, sudo: Sudo,
executable: PathBuf, executable: PathBuf,
} }
@@ -229,7 +263,7 @@ impl ArchPackageManager for Pamac {
pub struct Aura { pub struct Aura {
executable: PathBuf, executable: PathBuf,
sudo: PathBuf, sudo: Sudo,
} }
impl Aura { impl Aura {
@@ -282,14 +316,16 @@ pub fn get_arch_package_manager(ctx: &ExecutionContext) -> Option<Box<dyn ArchPa
let pacman = which("powerpill").unwrap_or_else(|| PathBuf::from("pacman")); let pacman = which("powerpill").unwrap_or_else(|| PathBuf::from("pacman"));
match ctx.config().arch_package_manager() { match ctx.config().arch_package_manager() {
config::ArchPackageManager::Autodetect => YayParu::get("paru", &pacman) config::ArchPackageManager::Autodetect => GarudaUpdate::get()
.map(box_package_manager) .map(box_package_manager)
.or_else(|| YayParu::get("paru", &pacman).map(box_package_manager))
.or_else(|| YayParu::get("yay", &pacman).map(box_package_manager)) .or_else(|| YayParu::get("yay", &pacman).map(box_package_manager))
.or_else(|| Trizen::get().map(box_package_manager)) .or_else(|| Trizen::get().map(box_package_manager))
.or_else(|| Pikaur::get().map(box_package_manager)) .or_else(|| Pikaur::get().map(box_package_manager))
.or_else(|| Pamac::get().map(box_package_manager)) .or_else(|| Pamac::get().map(box_package_manager))
.or_else(|| Pacman::get(ctx).map(box_package_manager)) .or_else(|| Pacman::get(ctx).map(box_package_manager))
.or_else(|| Aura::get(ctx).map(box_package_manager)), .or_else(|| Aura::get(ctx).map(box_package_manager)),
config::ArchPackageManager::GarudaUpdate => GarudaUpdate::get().map(box_package_manager),
config::ArchPackageManager::Trizen => Trizen::get().map(box_package_manager), config::ArchPackageManager::Trizen => Trizen::get().map(box_package_manager),
config::ArchPackageManager::Paru => YayParu::get("paru", &pacman).map(box_package_manager), config::ArchPackageManager::Paru => YayParu::get("paru", &pacman).map(box_package_manager),
config::ArchPackageManager::Yay => YayParu::get("yay", &pacman).map(box_package_manager), config::ArchPackageManager::Yay => YayParu::get("yay", &pacman).map(box_package_manager),

View File

@@ -1,26 +1,24 @@
use crate::command::CommandExt; use crate::command::CommandExt;
use crate::executor::RunType; use crate::execution_context::ExecutionContext;
use crate::terminal::print_separator; use crate::terminal::print_separator;
use crate::utils::require_option; use crate::utils::{require_option, REQUIRE_SUDO};
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use std::path::PathBuf;
use std::process::Command; use std::process::Command;
pub fn upgrade_packages(sudo: Option<&PathBuf>, run_type: RunType) -> Result<()> { pub fn upgrade_packages(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(sudo, String::from("No sudo detected"))?; let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
print_separator("DragonFly BSD Packages"); print_separator("DragonFly BSD Packages");
run_type ctx.execute(sudo)
.execute(sudo)
.args(["/usr/local/sbin/pkg", "upgrade"]) .args(["/usr/local/sbin/pkg", "upgrade"])
.arg(if ctx.config().yes(Step::System) { "-y" } else { "" })
.status_checked() .status_checked()
} }
pub fn audit_packages(sudo: &Option<PathBuf>) -> Result<()> { pub fn audit_packages(ctx: &ExecutionContext) -> Result<()> {
if let Some(sudo) = sudo { let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
println!(); println!();
Command::new(sudo) Command::new(sudo)
.args(["/usr/local/sbin/pkg", "audit", "-Fr"]) .args(["/usr/local/sbin/pkg", "audit", "-Fr"])
.status_checked()?; .status_checked()?;
}
Ok(()) Ok(())
} }

View File

@@ -1,27 +1,25 @@
use crate::command::CommandExt; use crate::command::CommandExt;
use crate::execution_context::ExecutionContext; use crate::execution_context::ExecutionContext;
use crate::executor::RunType;
use crate::terminal::print_separator; use crate::terminal::print_separator;
use crate::utils::require_option; use crate::utils::{require_option, REQUIRE_SUDO};
use crate::Step; use crate::Step;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use std::path::PathBuf;
use std::process::Command; use std::process::Command;
pub fn upgrade_freebsd(sudo: Option<&PathBuf>, run_type: RunType) -> Result<()> { pub fn upgrade_freebsd(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(sudo, String::from("No sudo detected"))?; let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
print_separator("FreeBSD Update"); print_separator("FreeBSD Update");
run_type ctx.run_type()
.execute(sudo) .execute(sudo)
.args(["/usr/sbin/freebsd-update", "fetch", "install"]) .args(["/usr/sbin/freebsd-update", "fetch", "install"])
.status_checked() .status_checked()
} }
pub fn upgrade_packages(ctx: &ExecutionContext, sudo: Option<&PathBuf>, run_type: RunType) -> Result<()> { pub fn upgrade_packages(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(sudo, String::from("No sudo detected"))?; let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
print_separator("FreeBSD Packages"); print_separator("FreeBSD Packages");
let mut command = run_type.execute(sudo); let mut command = ctx.run_type().execute(sudo);
command.args(["/usr/sbin/pkg", "upgrade"]); command.args(["/usr/sbin/pkg", "upgrade"]);
if ctx.config().yes(Step::System) { if ctx.config().yes(Step::System) {
@@ -30,12 +28,11 @@ pub fn upgrade_packages(ctx: &ExecutionContext, sudo: Option<&PathBuf>, run_type
command.status_checked() command.status_checked()
} }
pub fn audit_packages(sudo: &Option<PathBuf>) -> Result<()> { pub fn audit_packages(ctx: &ExecutionContext) -> Result<()> {
if let Some(sudo) = sudo { let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
println!(); println!();
Command::new(sudo) Command::new(sudo)
.args(["/usr/sbin/pkg", "audit", "-Fr"]) .args(["/usr/sbin/pkg", "audit", "-Fr"])
.status_checked()?; .status_checked()?;
}
Ok(()) Ok(())
} }

View File

@@ -8,11 +8,10 @@ use tracing::{debug, warn};
use crate::command::CommandExt; use crate::command::CommandExt;
use crate::error::{SkipStep, TopgradeError}; use crate::error::{SkipStep, TopgradeError};
use crate::execution_context::ExecutionContext; use crate::execution_context::ExecutionContext;
use crate::executor::RunType;
use crate::steps::os::archlinux; use crate::steps::os::archlinux;
use crate::terminal::{print_separator, print_warning}; use crate::terminal::print_separator;
use crate::utils::{require, require_option, which, PathExt}; use crate::utils::{require, require_option, which, PathExt, REQUIRE_SUDO};
use crate::Step; use crate::{Step, HOME_DIR};
static OS_RELEASE_PATH: &str = "/etc/os-release"; static OS_RELEASE_PATH: &str = "/etc/os-release";
@@ -25,10 +24,15 @@ pub enum Distribution {
CentOS, CentOS,
ClearLinux, ClearLinux,
Fedora, Fedora,
FedoraSilverblue,
Debian, Debian,
Gentoo, Gentoo,
OpenMandriva, OpenMandriva,
OpenSuseTumbleweed,
PCLinuxOS,
Suse, Suse,
SuseMicro,
Vanilla,
Void, Void,
Solus, Solus,
Exherbo, Exherbo,
@@ -37,33 +41,58 @@ pub enum Distribution {
} }
impl Distribution { impl Distribution {
fn parse_os_release(os_release: &ini::Ini) -> Result<Self> { fn parse_os_release(os_release: &Ini) -> Result<Self> {
let section = os_release.general_section(); let section = os_release.general_section();
let id = section.get("ID"); let id = section.get("ID");
let name = section.get("NAME");
let variant: Option<Vec<&str>> = section.get("VARIANT").map(|s| s.split_whitespace().collect());
let id_like: Option<Vec<&str>> = section.get("ID_LIKE").map(|s| s.split_whitespace().collect()); let id_like: Option<Vec<&str>> = section.get("ID_LIKE").map(|s| s.split_whitespace().collect());
Ok(match id { Ok(match id {
Some("alpine") => Distribution::Alpine, Some("alpine") => Distribution::Alpine,
Some("centos") | Some("rhel") | Some("ol") => Distribution::CentOS, Some("centos") | Some("rhel") | Some("ol") => Distribution::CentOS,
Some("clear-linux-os") => Distribution::ClearLinux, Some("clear-linux-os") => Distribution::ClearLinux,
Some("fedora") | Some("nobara") => Distribution::Fedora, Some("fedora") | Some("nobara") => {
return if let Some(variant) = variant {
if variant.contains(&"Silverblue") {
Ok(Distribution::FedoraSilverblue)
} else {
Ok(Distribution::Fedora)
}
} else {
Ok(Distribution::Fedora)
}
}
Some("void") => Distribution::Void, Some("void") => Distribution::Void,
Some("debian") | Some("pureos") => Distribution::Debian, Some("debian") | Some("pureos") | Some("Deepin") => Distribution::Debian,
Some("arch") | Some("anarchy") | Some("manjaro-arm") | Some("garuda") | Some("artix") => Distribution::Arch, Some("arch") | Some("manjaro-arm") | Some("garuda") | Some("artix") => Distribution::Arch,
Some("solus") => Distribution::Solus, Some("solus") => Distribution::Solus,
Some("gentoo") => Distribution::Gentoo, Some("gentoo") => Distribution::Gentoo,
Some("exherbo") => Distribution::Exherbo, Some("exherbo") => Distribution::Exherbo,
Some("nixos") => Distribution::NixOS, Some("nixos") => Distribution::NixOS,
Some("opensuse-microos") => Distribution::SuseMicro,
Some("neon") => Distribution::KDENeon, Some("neon") => Distribution::KDENeon,
Some("openmandriva") => Distribution::OpenMandriva, Some("openmandriva") => Distribution::OpenMandriva,
Some("pclinuxos") => Distribution::PCLinuxOS,
_ => { _ => {
if let Some(name) = name {
if name.contains("Vanilla") {
return Ok(Distribution::Vanilla);
}
}
if let Some(id_like) = id_like { if let Some(id_like) = id_like {
if id_like.contains(&"debian") || id_like.contains(&"ubuntu") { if id_like.contains(&"debian") || id_like.contains(&"ubuntu") {
return Ok(Distribution::Debian); return Ok(Distribution::Debian);
} else if id_like.contains(&"centos") { } else if id_like.contains(&"centos") {
return Ok(Distribution::CentOS); return Ok(Distribution::CentOS);
} else if id_like.contains(&"suse") { } else if id_like.contains(&"suse") {
return Ok(Distribution::Suse); let id_variant = id.unwrap_or_default();
return if id_variant.contains("tumbleweed") {
Ok(Distribution::OpenSuseTumbleweed)
} else {
Ok(Distribution::Suse)
};
} else if id_like.contains(&"arch") || id_like.contains(&"archlinux") { } else if id_like.contains(&"arch") || id_like.contains(&"archlinux") {
return Ok(Distribution::Arch); return Ok(Distribution::Arch);
} else if id_like.contains(&"alpine") { } else if id_like.contains(&"alpine") {
@@ -98,10 +127,14 @@ impl Distribution {
Distribution::Alpine => upgrade_alpine_linux(ctx), Distribution::Alpine => upgrade_alpine_linux(ctx),
Distribution::Arch => archlinux::upgrade_arch_linux(ctx), Distribution::Arch => archlinux::upgrade_arch_linux(ctx),
Distribution::CentOS | Distribution::Fedora => upgrade_redhat(ctx), Distribution::CentOS | Distribution::Fedora => upgrade_redhat(ctx),
Distribution::FedoraSilverblue => upgrade_fedora_silverblue(ctx),
Distribution::ClearLinux => upgrade_clearlinux(ctx), Distribution::ClearLinux => upgrade_clearlinux(ctx),
Distribution::Debian => upgrade_debian(ctx), Distribution::Debian => upgrade_debian(ctx),
Distribution::Gentoo => upgrade_gentoo(ctx), Distribution::Gentoo => upgrade_gentoo(ctx),
Distribution::Suse => upgrade_suse(ctx), Distribution::Suse => upgrade_suse(ctx),
Distribution::SuseMicro => upgrade_suse_micro(ctx),
Distribution::OpenSuseTumbleweed => upgrade_opensuse_tumbleweed(ctx),
Distribution::Vanilla => upgrade_vanilla(ctx),
Distribution::Void => upgrade_void(ctx), Distribution::Void => upgrade_void(ctx),
Distribution::Solus => upgrade_solus(ctx), Distribution::Solus => upgrade_solus(ctx),
Distribution::Exherbo => upgrade_exherbo(ctx), Distribution::Exherbo => upgrade_exherbo(ctx),
@@ -109,6 +142,7 @@ impl Distribution {
Distribution::KDENeon => upgrade_neon(ctx), Distribution::KDENeon => upgrade_neon(ctx),
Distribution::Bedrock => update_bedrock(ctx), Distribution::Bedrock => update_bedrock(ctx),
Distribution::OpenMandriva => upgrade_openmandriva(ctx), Distribution::OpenMandriva => upgrade_openmandriva(ctx),
Distribution::PCLinuxOS => upgrade_pclinuxos(ctx),
} }
} }
@@ -124,7 +158,7 @@ impl Distribution {
} }
fn update_bedrock(ctx: &ExecutionContext) -> Result<()> { fn update_bedrock(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), String::from("Sudo required"))?; let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
ctx.run_type().execute(sudo).args(["brl", "update"]); ctx.run_type().execute(sudo).args(["brl", "update"]);
@@ -155,7 +189,7 @@ fn is_wsl() -> Result<bool> {
fn upgrade_alpine_linux(ctx: &ExecutionContext) -> Result<()> { fn upgrade_alpine_linux(ctx: &ExecutionContext) -> Result<()> {
let apk = require("apk")?; let apk = require("apk")?;
let sudo = ctx.sudo().as_ref().unwrap(); let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
ctx.run_type().execute(sudo).arg(&apk).arg("update").status_checked()?; ctx.run_type().execute(sudo).arg(&apk).arg("update").status_checked()?;
ctx.run_type().execute(sudo).arg(&apk).arg("upgrade").status_checked() ctx.run_type().execute(sudo).arg(&apk).arg("upgrade").status_checked()
@@ -170,100 +204,172 @@ fn upgrade_redhat(ctx: &ExecutionContext) -> Result<()> {
} }
}; };
if let Some(sudo) = &ctx.sudo() { let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
let mut command = ctx.run_type().execute(sudo); let mut command = ctx.run_type().execute(sudo);
command command
.arg(which("dnf").unwrap_or_else(|| Path::new("yum").to_path_buf())) .arg(which("dnf").unwrap_or_else(|| Path::new("yum").to_path_buf()))
.arg(if ctx.config().redhat_distro_sync() { .arg(if ctx.config().redhat_distro_sync() {
"distro-sync" "distro-sync"
} else { } else {
"upgrade" "upgrade"
}); });
if let Some(args) = ctx.config().dnf_arguments() { if let Some(args) = ctx.config().dnf_arguments() {
command.args(args.split_whitespace()); command.args(args.split_whitespace());
}
if ctx.config().yes(Step::System) {
command.arg("-y");
}
command.status_checked()?;
} else {
print_warning("No sudo detected. Skipping system upgrade");
} }
if ctx.config().yes(Step::System) {
command.arg("-y");
}
command.status_checked()?;
Ok(())
}
fn upgrade_fedora_silverblue(ctx: &ExecutionContext) -> Result<()> {
let ostree = require("rpm-ostree")?;
let mut command = ctx.run_type().execute(ostree);
command.arg("upgrade");
command.status_checked()?;
Ok(()) Ok(())
} }
fn upgrade_bedrock_strata(ctx: &ExecutionContext) -> Result<()> { fn upgrade_bedrock_strata(ctx: &ExecutionContext) -> Result<()> {
if let Some(sudo) = ctx.sudo() { let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
ctx.run_type().execute(sudo).args(["brl", "update"]).status_checked()?; ctx.run_type().execute(sudo).args(["brl", "update"]).status_checked()?;
} else {
print_warning("No sudo detected. Skipping system upgrade");
}
Ok(()) Ok(())
} }
fn upgrade_suse(ctx: &ExecutionContext) -> Result<()> { fn upgrade_suse(ctx: &ExecutionContext) -> Result<()> {
if let Some(sudo) = ctx.sudo() { let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
ctx.run_type() ctx.run_type()
.execute(sudo) .execute(sudo)
.args(["zypper", "refresh"]) .args(["zypper", "refresh"])
.status_checked()?; .status_checked()?;
ctx.run_type() ctx.run_type()
.execute(sudo) .execute(sudo)
.args(["zypper", "dist-upgrade"]) .arg("zypper")
.status_checked()?; .arg(if ctx.config().suse_dup() {
} else { "dist-upgrade"
print_warning("No sudo detected. Skipping system upgrade"); } else {
} "update"
})
.arg(if ctx.config().yes(Step::System) { "-y" } else { "" })
.status_checked()?;
Ok(())
}
fn upgrade_opensuse_tumbleweed(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
ctx.run_type()
.execute(sudo)
.args(["zypper", "refresh"])
.status_checked()?;
ctx.run_type()
.execute(sudo)
.arg("zypper")
.arg("dist-upgrade")
.arg(if ctx.config().yes(Step::System) { "-y" } else { "" })
.status_checked()?;
Ok(())
}
fn upgrade_suse_micro(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
ctx.run_type()
.execute(sudo)
.arg("transactional-update")
.arg(if ctx.config().yes(Step::System) { "-n" } else { "" })
.arg("dup")
.status_checked()?;
Ok(()) Ok(())
} }
fn upgrade_openmandriva(ctx: &ExecutionContext) -> Result<()> { fn upgrade_openmandriva(ctx: &ExecutionContext) -> Result<()> {
if let Some(sudo) = &ctx.sudo() { let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
let mut command = ctx.run_type().execute(sudo); let mut command = ctx.run_type().execute(sudo);
command.arg(&which("dnf").unwrap()).arg("upgrade"); command.arg(&which("dnf").unwrap()).arg("upgrade");
if let Some(args) = ctx.config().dnf_arguments() { if let Some(args) = ctx.config().dnf_arguments() {
command.args(args.split_whitespace()); command.args(args.split_whitespace());
}
if ctx.config().yes(Step::System) {
command.arg("-y");
}
command.status_checked()?;
} else {
print_warning("No sudo detected. Skipping system upgrade");
} }
if ctx.config().yes(Step::System) {
command.arg("-y");
}
command.status_checked()?;
Ok(())
}
fn upgrade_pclinuxos(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
let mut command_update = ctx.run_type().execute(sudo);
command_update.arg(&which("apt-get").unwrap()).arg("update");
if let Some(args) = ctx.config().dnf_arguments() {
command_update.args(args.split_whitespace());
}
if ctx.config().yes(Step::System) {
command_update.arg("-y");
}
command_update.status_checked()?;
ctx.run_type()
.execute(sudo)
.arg(&which("apt-get").unwrap())
.arg("dist-upgrade")
.arg(if ctx.config().yes(Step::System) { "-y" } else { "" })
.status_checked()?;
Ok(())
}
fn upgrade_vanilla(ctx: &ExecutionContext) -> Result<()> {
let apx = require("apx")?;
let mut update = ctx.run_type().execute(&apx);
update.args(["update", "--all"]);
if ctx.config().yes(Step::System) {
update.arg("-y");
}
update.status_checked()?;
let mut upgrade = ctx.run_type().execute(&apx);
update.args(["upgrade", "--all"]);
if ctx.config().yes(Step::System) {
upgrade.arg("-y");
}
upgrade.status_checked()?;
Ok(()) Ok(())
} }
fn upgrade_void(ctx: &ExecutionContext) -> Result<()> { fn upgrade_void(ctx: &ExecutionContext) -> Result<()> {
if let Some(sudo) = ctx.sudo() { let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
let mut command = ctx.run_type().execute(sudo); let mut command = ctx.run_type().execute(sudo);
command.args(["xbps-install", "-Su", "xbps"]); command.args(["xbps-install", "-Su", "xbps"]);
if ctx.config().yes(Step::System) { if ctx.config().yes(Step::System) {
command.arg("-y"); command.arg("-y");
}
command.status_checked()?;
let mut command = ctx.run_type().execute(sudo);
command.args(["xbps-install", "-u"]);
if ctx.config().yes(Step::System) {
command.arg("-y");
}
command.status_checked()?;
} else {
print_warning("No sudo detected. Skipping system upgrade");
} }
command.status_checked()?;
let mut command = ctx.run_type().execute(sudo);
command.args(["xbps-install", "-u"]);
if ctx.config().yes(Step::System) {
command.arg("-y");
}
command.status_checked()?;
Ok(()) Ok(())
} }
@@ -271,86 +377,109 @@ fn upgrade_void(ctx: &ExecutionContext) -> Result<()> {
fn upgrade_gentoo(ctx: &ExecutionContext) -> Result<()> { fn upgrade_gentoo(ctx: &ExecutionContext) -> Result<()> {
let run_type = ctx.run_type(); let run_type = ctx.run_type();
if let Some(sudo) = &ctx.sudo() { let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
if let Some(layman) = which("layman") { if let Some(layman) = which("layman") {
run_type
.execute(sudo)
.arg(layman)
.args(["-s", "ALL"])
.status_checked()?;
}
println!("Syncing portage");
run_type run_type
.execute(sudo) .execute(sudo)
.args(["emerge", "--sync"]) .arg(layman)
.args( .args(["-s", "ALL"])
ctx.config()
.emerge_sync_flags()
.map(|s| s.split_whitespace().collect())
.unwrap_or_else(|| vec!["-q"]),
)
.status_checked()?; .status_checked()?;
if let Some(eix_update) = which("eix-update") {
run_type.execute(sudo).arg(eix_update).status_checked()?;
}
run_type
.execute(sudo)
.arg("emerge")
.args(
ctx.config()
.emerge_update_flags()
.map(|s| s.split_whitespace().collect())
.unwrap_or_else(|| vec!["-uDNa", "--with-bdeps=y", "world"]),
)
.status_checked()?;
} else {
print_warning("No sudo detected. Skipping system upgrade");
} }
println!("Syncing portage");
run_type
.execute(sudo)
.args(["emerge", "--sync"])
.args(
ctx.config()
.emerge_sync_flags()
.map(|s| s.split_whitespace().collect())
.unwrap_or_else(|| vec!["-q"]),
)
.status_checked()?;
if let Some(eix_update) = which("eix-update") {
run_type.execute(sudo).arg(eix_update).status_checked()?;
}
run_type
.execute(sudo)
.arg("emerge")
.args(
ctx.config()
.emerge_update_flags()
.map(|s| s.split_whitespace().collect())
.unwrap_or_else(|| vec!["-uDNa", "--with-bdeps=y", "world"]),
)
.status_checked()?;
Ok(()) Ok(())
} }
fn upgrade_debian(ctx: &ExecutionContext) -> Result<()> { fn upgrade_debian(ctx: &ExecutionContext) -> Result<()> {
if let Some(sudo) = &ctx.sudo() { let apt = which("apt-fast")
let apt = which("apt-fast") .or_else(|| {
.or_else(|| which("nala")) if which("mist").is_some() {
.unwrap_or_else(|| PathBuf::from("apt-get")); Some(PathBuf::from("mist"))
} else {
None
}
})
.or_else(|| {
if Path::new("/usr/bin/nala").exists() {
Some(Path::new("/usr/bin/nala").to_path_buf())
} else {
None
}
})
.unwrap_or_else(|| PathBuf::from("apt-get"));
let is_nala = apt.ends_with("nala"); let is_mist = apt.ends_with("mist");
if !is_nala { let is_nala = apt.ends_with("nala");
ctx.run_type().execute(sudo).arg(&apt).arg("update").status_checked()?;
} // MIST does not require `sudo`
if is_mist {
ctx.run_type().execute(&apt).arg("update").status_checked()?;
ctx.run_type().execute(&apt).arg("upgrade").status_checked()?;
// Simply return as MIST does not have `clean` and `autoremove`
// subcommands, neither the `-y` option (for now maybe?).
return Ok(());
}
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
if !is_nala {
ctx.run_type()
.execute(sudo)
.arg(&apt)
.arg("update")
.status_checked_with_codes(&[0, 100])?;
}
let mut command = ctx.run_type().execute(sudo);
command.arg(&apt);
if is_nala {
command.arg("upgrade");
} else {
command.arg("dist-upgrade");
};
if ctx.config().yes(Step::System) {
command.arg("-y");
}
if let Some(args) = ctx.config().apt_arguments() {
command.args(args.split_whitespace());
}
command.status_checked()?;
if ctx.config().cleanup() {
ctx.run_type().execute(sudo).arg(&apt).arg("clean").status_checked()?;
let mut command = ctx.run_type().execute(sudo); let mut command = ctx.run_type().execute(sudo);
command.arg(&apt); command.arg(&apt).arg("autoremove");
if is_nala {
command.arg("upgrade");
} else {
command.arg("dist-upgrade");
};
if ctx.config().yes(Step::System) { if ctx.config().yes(Step::System) {
command.arg("-y"); command.arg("-y");
} }
if let Some(args) = ctx.config().apt_arguments() {
command.args(args.split_whitespace());
}
command.status_checked()?; command.status_checked()?;
if ctx.config().cleanup() {
ctx.run_type().execute(sudo).arg(&apt).arg("clean").status_checked()?;
let mut command = ctx.run_type().execute(sudo);
command.arg(&apt).arg("autoremove");
if ctx.config().yes(Step::System) {
command.arg("-y");
}
command.status_checked()?;
}
} else {
print_warning("No sudo detected. Skipping system upgrade");
} }
Ok(()) Ok(())
@@ -361,38 +490,92 @@ pub fn run_deb_get(ctx: &ExecutionContext) -> Result<()> {
print_separator("deb-get"); print_separator("deb-get");
ctx.execute_elevated(&deb_get, false)?.arg("update").status_checked()?; ctx.run_type().execute(&deb_get).arg("update").status_checked()?;
ctx.execute_elevated(&deb_get, false)?.arg("upgrade").status_checked()?; ctx.run_type().execute(&deb_get).arg("upgrade").status_checked()?;
if ctx.config().cleanup() { if ctx.config().cleanup() {
ctx.execute_elevated(&deb_get, false)?.arg("clean").status_checked()?; ctx.run_type().execute(&deb_get).arg("clean").status_checked()?;
} }
Ok(()) Ok(())
} }
fn upgrade_solus(ctx: &ExecutionContext) -> Result<()> { fn upgrade_solus(ctx: &ExecutionContext) -> Result<()> {
if let Some(sudo) = ctx.sudo() { let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
ctx.run_type() ctx.run_type()
.execute(sudo) .execute(sudo)
.args(["eopkg", "upgrade"]) .arg("eopkg")
.status_checked()?; .arg(if ctx.config().yes(Step::System) { "-y" } else { "" })
} else { .arg("upgrade")
print_warning("No sudo detected. Skipping system upgrade"); .status_checked()?;
}
Ok(()) Ok(())
} }
pub fn run_am(ctx: &ExecutionContext) -> Result<()> {
let am = require("am")?;
print_separator("AM");
let mut am = ctx.run_type().execute(am);
if ctx.config().yes(Step::AM) {
am.arg("-U");
} else {
am.arg("-u");
}
am.status_checked()
}
pub fn run_appman(ctx: &ExecutionContext) -> Result<()> {
let appman = require("appman")?;
print_separator("appman");
ctx.run_type().execute(appman).arg("-u").status_checked()
}
pub fn run_pacdef(ctx: &ExecutionContext) -> Result<()> { pub fn run_pacdef(ctx: &ExecutionContext) -> Result<()> {
let pacdef = require("pacdef")?; let pacdef = require("pacdef")?;
print_separator("pacdef"); print_separator("pacdef");
ctx.run_type().execute(&pacdef).arg("sync").status_checked()?; let output = ctx.run_type().execute(&pacdef).arg("version").output_checked()?;
let string = String::from_utf8(output.stdout)?;
let new_version = string.contains("version: 1");
println!(); if new_version {
ctx.run_type().execute(&pacdef).arg("review").status_checked() ctx.run_type()
.execute(&pacdef)
.args(["package", "sync"])
.arg(if ctx.config().yes(Step::System) {
"--noconfirm"
} else {
""
})
.status_checked()?;
println!();
ctx.run_type()
.execute(&pacdef)
.args(["package", "review"])
.status_checked()?;
} else {
ctx.run_type()
.execute(&pacdef)
.arg("sync")
.arg(if ctx.config().yes(Step::System) {
"--noconfirm"
} else {
""
})
.status_checked()?;
println!();
ctx.run_type().execute(&pacdef).arg("review").status_checked()?;
}
Ok(())
} }
pub fn run_pacstall(ctx: &ExecutionContext) -> Result<()> { pub fn run_pacstall(ctx: &ExecutionContext) -> Result<()> {
@@ -400,70 +583,96 @@ pub fn run_pacstall(ctx: &ExecutionContext) -> Result<()> {
print_separator("Pacstall"); print_separator("Pacstall");
ctx.run_type().execute(&pacstall).arg("-U").status_checked()?; let mut update_cmd = ctx.run_type().execute(&pacstall);
ctx.run_type().execute(pacstall).arg("-Up").status_checked() let mut upgrade_cmd = ctx.run_type().execute(pacstall);
if ctx.config().yes(Step::Pacstall) {
update_cmd.arg("-P");
upgrade_cmd.arg("-P");
}
update_cmd.arg("-U").status_checked()?;
upgrade_cmd.arg("-Up").status_checked()
}
pub fn run_packer_nu(ctx: &ExecutionContext) -> Result<()> {
let nu = require("nu")?;
let packer_home = HOME_DIR.join(".local/share/nushell/packer");
packer_home.clone().require()?;
print_separator("packer.nu");
ctx.run_type()
.execute(nu)
.env("PWD", "/")
.env("NU_PACKER_HOME", packer_home)
.args([
"-c",
"use ~/.local/share/nushell/packer/start/packer.nu/api_layer/packer.nu; packer update",
])
.status_checked()
} }
fn upgrade_clearlinux(ctx: &ExecutionContext) -> Result<()> { fn upgrade_clearlinux(ctx: &ExecutionContext) -> Result<()> {
if let Some(sudo) = &ctx.sudo() { let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
ctx.run_type() ctx.run_type()
.execute(sudo) .execute(sudo)
.args(["swupd", "update"]) .args(["swupd", "update"])
.status_checked()?; .arg(if ctx.config().yes(Step::System) {
} else { "--assume=yes"
print_warning("No sudo detected. Skipping system upgrade"); } else {
} ""
})
.status_checked()?;
Ok(()) Ok(())
} }
fn upgrade_exherbo(ctx: &ExecutionContext) -> Result<()> { fn upgrade_exherbo(ctx: &ExecutionContext) -> Result<()> {
if let Some(sudo) = ctx.sudo() { let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
ctx.run_type().execute(sudo).args(["cave", "sync"]).status_checked()?; ctx.run_type().execute(sudo).args(["cave", "sync"]).status_checked()?;
ctx.run_type()
.execute(sudo)
.args(["cave", "resolve", "world", "-c1", "-Cs", "-km", "-Km", "-x"])
.status_checked()?;
if ctx.config().cleanup() {
ctx.run_type() ctx.run_type()
.execute(sudo) .execute(sudo)
.args(["cave", "resolve", "world", "-c1", "-Cs", "-km", "-Km", "-x"]) .args(["cave", "purge", "-x"])
.status_checked()?; .status_checked()?;
if ctx.config().cleanup() {
ctx.run_type()
.execute(sudo)
.args(["cave", "purge", "-x"])
.status_checked()?;
}
ctx.run_type()
.execute(sudo)
.args(["cave", "fix-linkage", "-x", "--", "-Cs"])
.status_checked()?;
ctx.run_type()
.execute(sudo)
.args(["eclectic", "config", "interactive"])
.status_checked()?;
} else {
print_warning("No sudo detected. Skipping system upgrade");
} }
ctx.run_type()
.execute(sudo)
.args(["cave", "fix-linkage", "-x", "--", "-Cs"])
.status_checked()?;
ctx.run_type()
.execute(sudo)
.args(["eclectic", "config", "interactive"])
.status_checked()?;
Ok(()) Ok(())
} }
fn upgrade_nixos(ctx: &ExecutionContext) -> Result<()> { fn upgrade_nixos(ctx: &ExecutionContext) -> Result<()> {
if let Some(sudo) = ctx.sudo() { let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
let mut command = ctx.run_type().execute(sudo);
command.args(["/run/current-system/sw/bin/nixos-rebuild", "switch", "--upgrade"]);
if let Some(args) = ctx.config().nix_arguments() {
command.args(args.split_whitespace());
}
command.status_checked()?;
if ctx.config().cleanup() {
ctx.run_type() ctx.run_type()
.execute(sudo) .execute(sudo)
.args(["/run/current-system/sw/bin/nixos-rebuild", "switch", "--upgrade"]) .args(["/run/current-system/sw/bin/nix-collect-garbage", "-d"])
.status_checked()?; .status_checked()?;
if ctx.config().cleanup() {
ctx.run_type()
.execute(sudo)
.args(["/run/current-system/sw/bin/nix-collect-garbage", "-d"])
.status_checked()?;
}
} else {
print_warning("No sudo detected. Skipping system upgrade");
} }
Ok(()) Ok(())
@@ -475,31 +684,31 @@ fn upgrade_neon(ctx: &ExecutionContext) -> Result<()> {
// in theory rpm based distributions use pkcon as well, though that // in theory rpm based distributions use pkcon as well, though that
// seems rare // seems rare
// if that comes up we need to create a Distribution::PackageKit or some such // if that comes up we need to create a Distribution::PackageKit or some such
if let Some(sudo) = &ctx.sudo() {
let pkcon = which("pkcon").unwrap(); let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
// pkcon ignores update with update and refresh provided together let pkcon = which("pkcon").unwrap();
ctx.run_type() // pkcon ignores update with update and refresh provided together
.execute(sudo) ctx.run_type()
.arg(&pkcon) .execute(sudo)
.arg("refresh") .arg(&pkcon)
.status_checked()?; .arg("refresh")
let mut exe = ctx.run_type().execute(sudo); .status_checked()?;
let cmd = exe.arg(&pkcon).arg("update"); let mut exe = ctx.run_type().execute(sudo);
if ctx.config().yes(Step::System) { let cmd = exe.arg(&pkcon).arg("update");
cmd.arg("-y"); if ctx.config().yes(Step::System) {
} cmd.arg("-y");
if ctx.config().cleanup() {
cmd.arg("--autoremove");
}
// from pkcon man, exit code 5 is 'Nothing useful was done.'
cmd.status_checked_with_codes(&[5])?;
} }
if ctx.config().cleanup() {
cmd.arg("--autoremove");
}
// from pkcon man, exit code 5 is 'Nothing useful was done.'
cmd.status_checked_with_codes(&[5])?;
Ok(()) Ok(())
} }
pub fn run_needrestart(sudo: Option<&PathBuf>, run_type: RunType) -> Result<()> { pub fn run_needrestart(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(sudo, String::from("sudo is not installed"))?; let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
let needrestart = require("needrestart")?; let needrestart = require("needrestart")?;
let distribution = Distribution::detect()?; let distribution = Distribution::detect()?;
@@ -509,7 +718,7 @@ pub fn run_needrestart(sudo: Option<&PathBuf>, run_type: RunType) -> Result<()>
print_separator("Check for needed restarts"); print_separator("Check for needed restarts");
run_type.execute(sudo).arg(needrestart).status_checked()?; ctx.run_type().execute(sudo).arg(needrestart).status_checked()?;
Ok(()) Ok(())
} }
@@ -541,9 +750,9 @@ pub fn run_fwupdmgr(ctx: &ExecutionContext) -> Result<()> {
updmgr.status_checked_with_codes(&[2]) updmgr.status_checked_with_codes(&[2])
} }
pub fn flatpak_update(ctx: &ExecutionContext) -> Result<()> { pub fn run_flatpak(ctx: &ExecutionContext) -> Result<()> {
let flatpak = require("flatpak")?; let flatpak = require("flatpak")?;
let sudo = require_option(ctx.sudo().as_ref(), String::from("sudo is not installed"))?; let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
let cleanup = ctx.config().cleanup(); let cleanup = ctx.config().cleanup();
let yes = ctx.config().yes(Step::Flatpak); let yes = ctx.config().yes(Step::Flatpak);
let run_type = ctx.run_type(); let run_type = ctx.run_type();
@@ -603,8 +812,8 @@ pub fn flatpak_update(ctx: &ExecutionContext) -> Result<()> {
Ok(()) Ok(())
} }
pub fn run_snap(sudo: Option<&PathBuf>, run_type: RunType) -> Result<()> { pub fn run_snap(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(sudo, String::from("sudo is not installed"))?; let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
let snap = require("snap")?; let snap = require("snap")?;
if !PathBuf::from("/var/snapd.socket").exists() && !PathBuf::from("/run/snapd.socket").exists() { if !PathBuf::from("/var/snapd.socket").exists() && !PathBuf::from("/run/snapd.socket").exists() {
@@ -612,17 +821,17 @@ pub fn run_snap(sudo: Option<&PathBuf>, run_type: RunType) -> Result<()> {
} }
print_separator("snap"); print_separator("snap");
run_type.execute(sudo).arg(snap).arg("refresh").status_checked() ctx.run_type().execute(sudo).arg(snap).arg("refresh").status_checked()
} }
pub fn run_pihole_update(sudo: Option<&PathBuf>, run_type: RunType) -> Result<()> { pub fn run_pihole_update(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(sudo, String::from("sudo is not installed"))?; let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
let pihole = require("pihole")?; let pihole = require("pihole")?;
Path::new("/opt/pihole/update.sh").require()?; Path::new("/opt/pihole/update.sh").require()?;
print_separator("pihole"); print_separator("pihole");
run_type.execute(sudo).arg(pihole).arg("-up").status_checked() ctx.run_type().execute(sudo).arg(pihole).arg("-up").status_checked()
} }
pub fn run_protonup_update(ctx: &ExecutionContext) -> Result<()> { pub fn run_protonup_update(ctx: &ExecutionContext) -> Result<()> {
@@ -659,8 +868,31 @@ pub fn run_distrobox_update(ctx: &ExecutionContext) -> Result<()> {
.status_checked() .status_checked()
} }
pub fn run_dkp_pacman_update(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
let dkp_pacman = require("dkp-pacman")?;
print_separator("Devkitpro pacman");
ctx.run_type()
.execute(sudo)
.arg(&dkp_pacman)
.arg("-Syu")
.status_checked()?;
if ctx.config().cleanup() {
ctx.run_type()
.execute(sudo)
.arg(&dkp_pacman)
.arg("-Scc")
.status_checked()?;
}
Ok(())
}
pub fn run_config_update(ctx: &ExecutionContext) -> Result<()> { pub fn run_config_update(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), String::from("sudo is not installed"))?; let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
if ctx.config().yes(Step::ConfigUpdate) { if ctx.config().yes(Step::ConfigUpdate) {
return Err(SkipStep("Skipped in --yes".to_string()).into()); return Err(SkipStep("Skipped in --yes".to_string()).into());
} }
@@ -743,11 +975,6 @@ mod tests {
test_template(include_str!("os_release/fedora"), Distribution::Fedora); test_template(include_str!("os_release/fedora"), Distribution::Fedora);
} }
#[test]
fn test_antergos() {
test_template(include_str!("os_release/antergos"), Distribution::Arch);
}
#[test] #[test]
fn test_manjaro() { fn test_manjaro() {
test_template(include_str!("os_release/manjaro"), Distribution::Arch); test_template(include_str!("os_release/manjaro"), Distribution::Arch);
@@ -758,11 +985,6 @@ mod tests {
test_template(include_str!("os_release/manjaro-arm"), Distribution::Arch); test_template(include_str!("os_release/manjaro-arm"), Distribution::Arch);
} }
#[test]
fn test_anarchy() {
test_template(include_str!("os_release/anarchy"), Distribution::Arch);
}
#[test] #[test]
fn test_gentoo() { fn test_gentoo() {
test_template(include_str!("os_release/gentoo"), Distribution::Gentoo); test_template(include_str!("os_release/gentoo"), Distribution::Gentoo);
@@ -807,4 +1029,19 @@ mod tests {
fn test_pureos() { fn test_pureos() {
test_template(include_str!("os_release/pureos"), Distribution::Debian); test_template(include_str!("os_release/pureos"), Distribution::Debian);
} }
#[test]
fn test_deepin() {
test_template(include_str!("os_release/deepin"), Distribution::Debian);
}
#[test]
fn test_vanilla() {
test_template(include_str!("os_release/vanilla"), Distribution::Vanilla);
}
#[test]
fn test_solus() {
test_template(include_str!("os_release/solus"), Distribution::Solus);
}
} }

View File

@@ -1,7 +1,7 @@
use crate::command::CommandExt; use crate::command::CommandExt;
use crate::execution_context::ExecutionContext; use crate::execution_context::ExecutionContext;
use crate::executor::RunType;
use crate::terminal::{print_separator, prompt_yesno}; use crate::terminal::{print_separator, prompt_yesno};
use crate::utils::{require_option, REQUIRE_SUDO};
use crate::{utils::require, Step}; use crate::{utils::require, Step};
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use std::fs; use std::fs;
@@ -10,7 +10,8 @@ use tracing::debug;
pub fn run_macports(ctx: &ExecutionContext) -> Result<()> { pub fn run_macports(ctx: &ExecutionContext) -> Result<()> {
require("port")?; require("port")?;
let sudo = ctx.sudo().as_ref().unwrap(); let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
print_separator("MacPorts"); print_separator("MacPorts");
ctx.run_type() ctx.run_type()
.execute(sudo) .execute(sudo)
@@ -30,11 +31,11 @@ pub fn run_macports(ctx: &ExecutionContext) -> Result<()> {
Ok(()) Ok(())
} }
pub fn run_mas(run_type: RunType) -> Result<()> { pub fn run_mas(ctx: &ExecutionContext) -> Result<()> {
let mas = require("mas")?; let mas = require("mas")?;
print_separator("macOS App Store"); print_separator("macOS App Store");
run_type.execute(mas).arg("upgrade").status_checked() ctx.run_type().execute(mas).arg("upgrade").status_checked()
} }
pub fn upgrade_macos(ctx: &ExecutionContext) -> Result<()> { pub fn upgrade_macos(ctx: &ExecutionContext) -> Result<()> {

View File

@@ -1,22 +1,22 @@
use crate::executor::RunType; use crate::execution_context::ExecutionContext;
use crate::terminal::print_separator; use crate::terminal::print_separator;
use crate::utils::require_option; use crate::utils::{require_option, REQUIRE_SUDO};
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use std::path::PathBuf; use std::path::PathBuf;
pub fn upgrade_openbsd(sudo: Option<&PathBuf>, run_type: RunType) -> Result<()> { pub fn upgrade_openbsd(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(sudo, String::from("No sudo detected"))?; let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
print_separator("OpenBSD Update"); print_separator("OpenBSD Update");
run_type ctx.run_type()
.execute(sudo) .execute(sudo)
.args(&["/usr/sbin/sysupgrade", "-n"]) .args(&["/usr/sbin/sysupgrade", "-n"])
.status_checked() .status_checked()
} }
pub fn upgrade_packages(sudo: Option<&PathBuf>, run_type: RunType) -> Result<()> { pub fn upgrade_packages(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(sudo, String::from("No sudo detected"))?; let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
print_separator("OpenBSD Packages"); print_separator("OpenBSD Packages");
run_type ctx.run_type()
.execute(sudo) .execute(sudo)
.args(&["/usr/sbin/pkg_add", "-u"]) .args(&["/usr/sbin/pkg_add", "-u"])
.status_checked() .status_checked()

View File

@@ -1,6 +0,0 @@
NAME="Anarchy Linux"
PRETTY_NAME="Anarchy Linux"
ID=anarchy
ID_LIKE=anarchylinux
ANSI_COLOR="0;36"
HOME_URL="https://anarchylinux.org/"

View File

@@ -1,10 +0,0 @@
NAME="Antergos Linux"
VERSION="18.7-ISO-Rolling"
ID="antergos"
ID_LIKE="arch"
PRETTY_NAME="Antergos Linux"
CPE_NAME="cpe:/o:antergosproject:antergos:18.7"
ANSI_COLOR="1;34;40"
HOME_URL="antergos.com"
SUPPORT_URL="forum.antergos.com"
BUG_REPORT_URL="@antergos"

View File

@@ -0,0 +1,8 @@
PRETTY_NAME="Deepin 20.9"
NAME="Deepin"
VERSION_ID="20.9"
VERSION="20.9"
VERSION_CODENAME="apricot"
ID=Deepin
HOME_URL="https://www.deepin.org/"
BUG_REPORT_URL="https://bbs.deepin.org/"

View File

@@ -0,0 +1,9 @@
NAME="PCLinuxOS"
VERSION="2022"
ID=pclinuxos
VERSION_ID=2022
ID_LIKE="mandriva"
PRETTY_NAME="PCLinuxOS 2022"
ANSI_COLOR="1;37"
HOME_URL="http://www.pclinuxos.com/"
SUPPORT_URL="http://www.pclinuxos.com/"

View File

@@ -0,0 +1,11 @@
NAME="Solus"
VERSION="4.4"
ID="solus"
VERSION_CODENAME=harmony
VERSION_ID="4.4"
PRETTY_NAME="Solus 4.4 Harmony"
ANSI_COLOR="1;34"
HOME_URL="https://getsol.us"
SUPPORT_URL="https://help.getsol.us/docs/user/contributing/getting-involved"
BUG_REPORT_URL="https://dev.getsol.us/"
LOGO="distributor-logo-solus"

View File

@@ -0,0 +1,12 @@
PRETTY_NAME="VanillaOS 22.10 all"
NAME="VanillaOS"
VERSION_ID="22.10"
VERSION="22.10 all"
VERSION_CODENAME="kinetic"
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://github.com/vanilla-os"
SUPPORT_URL="https://github.com/vanilla-os"
BUG_REPORT_URL="https://github.com/vanilla-os"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME="kinetic"

View File

@@ -2,12 +2,11 @@ use std::fs;
use std::os::unix::fs::MetadataExt; use std::os::unix::fs::MetadataExt;
use std::path::PathBuf; use std::path::PathBuf;
use std::process::Command; use std::process::Command;
use std::{env, path::Path}; use std::{env::var, path::Path};
use crate::command::CommandExt; use crate::command::CommandExt;
use crate::Step; use crate::{Step, HOME_DIR};
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use directories::BaseDirs;
use home; use home;
use ini::Ini; use ini::Ini;
use tracing::debug; use tracing::debug;
@@ -16,11 +15,10 @@ use crate::error::SkipStep;
use crate::execution_context::ExecutionContext; use crate::execution_context::ExecutionContext;
#[cfg(any(target_os = "linux", target_os = "macos"))] #[cfg(any(target_os = "linux", target_os = "macos"))]
use crate::executor::Executor; use crate::executor::Executor;
#[cfg(any(target_os = "linux", target_os = "macos"))]
use crate::executor::RunType; use crate::executor::RunType;
use crate::terminal::print_separator; use crate::terminal::print_separator;
#[cfg(not(any(target_os = "android", target_os = "macos")))] use crate::utils::{require, require_option, PathExt, REQUIRE_SUDO};
use crate::utils::require_option;
use crate::utils::{require, PathExt};
#[cfg(any(target_os = "linux", target_os = "macos"))] #[cfg(any(target_os = "linux", target_os = "macos"))]
const INTEL_BREW: &str = "/usr/local/bin/brew"; const INTEL_BREW: &str = "/usr/local/bin/brew";
@@ -87,7 +85,7 @@ impl BrewVariant {
} }
} }
pub fn run_fisher(run_type: RunType) -> Result<()> { pub fn run_fisher(ctx: &ExecutionContext) -> Result<()> {
let fish = require("fish")?; let fish = require("fish")?;
Command::new(&fish) Command::new(&fish)
@@ -102,9 +100,16 @@ pub fn run_fisher(run_type: RunType) -> Result<()> {
.and_then(|output| Path::new(&output.stdout.trim()).require().map(|_| ())) .and_then(|output| Path::new(&output.stdout.trim()).require().map(|_| ()))
.map_err(|err| SkipStep(format!("`fish_plugins` path doesn't exist: {err}")))?; .map_err(|err| SkipStep(format!("`fish_plugins` path doesn't exist: {err}")))?;
Command::new(&fish)
.args(["-c", "fish_update_completions"])
.output_checked_utf8()
.map(|_| ())
.map_err(|_| SkipStep("`fish_update_completions` is not available".to_owned()))?;
print_separator("Fisher"); print_separator("Fisher");
let version_str = run_type let version_str = ctx
.run_type()
.execute(&fish) .execute(&fish)
.args(["-c", "fisher --version"]) .args(["-c", "fisher --version"])
.output_checked_utf8()? .output_checked_utf8()?
@@ -113,15 +118,18 @@ pub fn run_fisher(run_type: RunType) -> Result<()> {
if version_str.starts_with("fisher version 3.") { if version_str.starts_with("fisher version 3.") {
// v3 - see https://github.com/topgrade-rs/topgrade/pull/37#issuecomment-1283844506 // v3 - see https://github.com/topgrade-rs/topgrade/pull/37#issuecomment-1283844506
run_type.execute(&fish).args(["-c", "fisher"]).status_checked() ctx.run_type().execute(&fish).args(["-c", "fisher"]).status_checked()
} else { } else {
// v4 // v4
run_type.execute(&fish).args(["-c", "fisher update"]).status_checked() ctx.run_type()
.execute(&fish)
.args(["-c", "fisher update"])
.status_checked()
} }
} }
pub fn run_bashit(ctx: &ExecutionContext) -> Result<()> { pub fn run_bashit(ctx: &ExecutionContext) -> Result<()> {
ctx.base_dirs().home_dir().join(".bash_it").require()?; HOME_DIR.join(".bash_it").require()?;
print_separator("Bash-it"); print_separator("Bash-it");
@@ -131,12 +139,30 @@ pub fn run_bashit(ctx: &ExecutionContext) -> Result<()> {
.status_checked() .status_checked()
} }
pub fn run_oh_my_bash(ctx: &ExecutionContext) -> Result<()> {
require("bash")?;
let oh_my_bash = var("OSH")
// default to `~/.oh-my-bash`
.unwrap_or(
HOME_DIR
.join(".oh-my-bash")
.to_str()
.expect("should be UTF-8 encoded")
.to_string(),
)
.require()?;
print_separator("oh-my-bash");
let mut update_script = oh_my_bash;
update_script.push_str("/tools/upgrade.sh");
ctx.run_type().execute("bash").arg(update_script).status_checked()
}
pub fn run_oh_my_fish(ctx: &ExecutionContext) -> Result<()> { pub fn run_oh_my_fish(ctx: &ExecutionContext) -> Result<()> {
let fish = require("fish")?; let fish = require("fish")?;
ctx.base_dirs() HOME_DIR.join(".local/share/omf/pkg/omf/functions/omf.fish").require()?;
.home_dir()
.join(".local/share/omf/pkg/omf/functions/omf.fish")
.require()?;
print_separator("oh-my-fish"); print_separator("oh-my-fish");
@@ -145,15 +171,18 @@ pub fn run_oh_my_fish(ctx: &ExecutionContext) -> Result<()> {
pub fn run_pkgin(ctx: &ExecutionContext) -> Result<()> { pub fn run_pkgin(ctx: &ExecutionContext) -> Result<()> {
let pkgin = require("pkgin")?; let pkgin = require("pkgin")?;
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
let mut command = ctx.run_type().execute(ctx.sudo().as_ref().unwrap()); print_separator("Pkgin");
let mut command = ctx.run_type().execute(sudo);
command.arg(&pkgin).arg("update"); command.arg(&pkgin).arg("update");
if ctx.config().yes(Step::Pkgin) { if ctx.config().yes(Step::Pkgin) {
command.arg("-y"); command.arg("-y");
} }
command.status_checked()?; command.status_checked()?;
let mut command = ctx.run_type().execute(ctx.sudo().as_ref().unwrap()); let mut command = ctx.run_type().execute(sudo);
command.arg(&pkgin).arg("upgrade"); command.arg(&pkgin).arg("upgrade");
if ctx.config().yes(Step::Pkgin) { if ctx.config().yes(Step::Pkgin) {
command.arg("-y"); command.arg("-y");
@@ -163,8 +192,7 @@ pub fn run_pkgin(ctx: &ExecutionContext) -> Result<()> {
pub fn run_fish_plug(ctx: &ExecutionContext) -> Result<()> { pub fn run_fish_plug(ctx: &ExecutionContext) -> Result<()> {
let fish = require("fish")?; let fish = require("fish")?;
ctx.base_dirs() HOME_DIR
.home_dir()
.join(".local/share/fish/plug/kidonng/fish-plug/functions/plug.fish") .join(".local/share/fish/plug/kidonng/fish-plug/functions/plug.fish")
.require()?; .require()?;
@@ -183,7 +211,7 @@ pub fn run_fish_plug(ctx: &ExecutionContext) -> Result<()> {
/// See: <https://github.com/danhper/fundle> /// See: <https://github.com/danhper/fundle>
pub fn run_fundle(ctx: &ExecutionContext) -> Result<()> { pub fn run_fundle(ctx: &ExecutionContext) -> Result<()> {
let fish = require("fish")?; let fish = require("fish")?;
ctx.base_dirs().home_dir().join(".config/fish/fundle").require()?; HOME_DIR.join(".config/fish/fundle").require()?;
print_separator("fundle"); print_separator("fundle");
@@ -197,7 +225,7 @@ pub fn run_fundle(ctx: &ExecutionContext) -> Result<()> {
pub fn upgrade_gnome_extensions(ctx: &ExecutionContext) -> Result<()> { pub fn upgrade_gnome_extensions(ctx: &ExecutionContext) -> Result<()> {
let gdbus = require("gdbus")?; let gdbus = require("gdbus")?;
require_option( require_option(
env::var("XDG_CURRENT_DESKTOP").ok().filter(|p| p.contains("GNOME")), var("XDG_CURRENT_DESKTOP").ok().filter(|p| p.contains("GNOME")),
"Desktop doest not appear to be gnome".to_string(), "Desktop doest not appear to be gnome".to_string(),
)?; )?;
let output = Command::new("gdbus") let output = Command::new("gdbus")
@@ -327,6 +355,7 @@ pub fn run_nix(ctx: &ExecutionContext) -> Result<()> {
let nix = require("nix")?; let nix = require("nix")?;
let nix_channel = require("nix-channel")?; let nix_channel = require("nix-channel")?;
let nix_env = require("nix-env")?; let nix_env = require("nix-env")?;
// TODO: Is None possible here?
let profile_path = match home::home_dir() { let profile_path = match home::home_dir() {
Some(home) => Path::new(&home).join(".nix-profile"), Some(home) => Path::new(&home).join(".nix-profile"),
None => Path::new("/nix/var/nix/profiles/per-user/default").into(), None => Path::new("/nix/var/nix/profiles/per-user/default").into(),
@@ -354,7 +383,7 @@ pub fn run_nix(ctx: &ExecutionContext) -> Result<()> {
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
{ {
if let Ok(..) = require("darwin-rebuild") { if require("darwin-rebuild").is_ok() {
return Err(SkipStep(String::from( return Err(SkipStep(String::from(
"Nix-darwin on macOS must be upgraded via darwin-rebuild switch", "Nix-darwin on macOS must be upgraded via darwin-rebuild switch",
)) ))
@@ -374,7 +403,7 @@ pub fn run_nix(ctx: &ExecutionContext) -> Result<()> {
run_type.execute(nix_channel).arg("--update").status_checked()?; run_type.execute(nix_channel).arg("--update").status_checked()?;
if std::path::Path::new(&manifest_json_path).exists() { if Path::new(&manifest_json_path).exists() {
run_type run_type
.execute(&nix) .execute(&nix)
.arg("profile") .arg("profile")
@@ -394,45 +423,48 @@ pub fn run_yadm(ctx: &ExecutionContext) -> Result<()> {
ctx.run_type().execute(yadm).arg("pull").status_checked() ctx.run_type().execute(yadm).arg("pull").status_checked()
} }
pub fn run_asdf(run_type: RunType) -> Result<()> { pub fn run_asdf(ctx: &ExecutionContext) -> Result<()> {
let asdf = require("asdf")?; let asdf = require("asdf")?;
print_separator("asdf"); print_separator("asdf");
run_type.execute(&asdf).arg("update").status_checked_with_codes(&[42])?; ctx.run_type()
.execute(&asdf)
.arg("update")
.status_checked_with_codes(&[42])?;
run_type ctx.run_type()
.execute(&asdf) .execute(&asdf)
.args(["plugin", "update", "--all"]) .args(["plugin", "update", "--all"])
.status_checked() .status_checked()
} }
pub fn run_home_manager(run_type: RunType) -> Result<()> { pub fn run_home_manager(ctx: &ExecutionContext) -> Result<()> {
let home_manager = require("home-manager")?; let home_manager = require("home-manager")?;
print_separator("home-manager"); print_separator("home-manager");
run_type.execute(home_manager).arg("switch").status_checked() ctx.run_type().execute(home_manager).arg("switch").status_checked()
} }
pub fn run_tldr(run_type: RunType) -> Result<()> { pub fn run_tldr(ctx: &ExecutionContext) -> Result<()> {
let tldr = require("tldr")?; let tldr = require("tldr")?;
print_separator("TLDR"); print_separator("TLDR");
run_type.execute(tldr).arg("--update").status_checked() ctx.run_type().execute(tldr).arg("--update").status_checked()
} }
pub fn run_pearl(run_type: RunType) -> Result<()> { pub fn run_pearl(ctx: &ExecutionContext) -> Result<()> {
let pearl = require("pearl")?; let pearl = require("pearl")?;
print_separator("pearl"); print_separator("pearl");
run_type.execute(pearl).arg("update").status_checked() ctx.run_type().execute(pearl).arg("update").status_checked()
} }
pub fn run_sdkman(base_dirs: &BaseDirs, cleanup: bool, run_type: RunType) -> Result<()> { pub fn run_sdkman(ctx: &ExecutionContext) -> Result<()> {
let bash = require("bash")?; let bash = require("bash")?;
let sdkman_init_path = env::var("SDKMAN_DIR") let sdkman_init_path = var("SDKMAN_DIR")
.map(PathBuf::from) .map(PathBuf::from)
.unwrap_or_else(|_| base_dirs.home_dir().join(".sdkman")) .unwrap_or_else(|_| HOME_DIR.join(".sdkman"))
.join("bin") .join("bin")
.join("sdkman-init.sh") .join("sdkman-init.sh")
.require() .require()
@@ -440,9 +472,9 @@ pub fn run_sdkman(base_dirs: &BaseDirs, cleanup: bool, run_type: RunType) -> Res
print_separator("SDKMAN!"); print_separator("SDKMAN!");
let sdkman_config_path = env::var("SDKMAN_DIR") let sdkman_config_path = var("SDKMAN_DIR")
.map(PathBuf::from) .map(PathBuf::from)
.unwrap_or_else(|_| base_dirs.home_dir().join(".sdkman")) .unwrap_or_else(|_| HOME_DIR.join(".sdkman"))
.join("etc") .join("etc")
.join("config") .join("config")
.require()?; .require()?;
@@ -455,33 +487,33 @@ pub fn run_sdkman(base_dirs: &BaseDirs, cleanup: bool, run_type: RunType) -> Res
if selfupdate_enabled == "true" { if selfupdate_enabled == "true" {
let cmd_selfupdate = format!("source {} && sdk selfupdate", &sdkman_init_path); let cmd_selfupdate = format!("source {} && sdk selfupdate", &sdkman_init_path);
run_type ctx.run_type()
.execute(&bash) .execute(&bash)
.args(["-c", cmd_selfupdate.as_str()]) .args(["-c", cmd_selfupdate.as_str()])
.status_checked()?; .status_checked()?;
} }
let cmd_update = format!("source {} && sdk update", &sdkman_init_path); let cmd_update = format!("source {} && sdk update", &sdkman_init_path);
run_type ctx.run_type()
.execute(&bash) .execute(&bash)
.args(["-c", cmd_update.as_str()]) .args(["-c", cmd_update.as_str()])
.status_checked()?; .status_checked()?;
let cmd_upgrade = format!("source {} && sdk upgrade", &sdkman_init_path); let cmd_upgrade = format!("source {} && sdk upgrade", &sdkman_init_path);
run_type ctx.run_type()
.execute(&bash) .execute(&bash)
.args(["-c", cmd_upgrade.as_str()]) .args(["-c", cmd_upgrade.as_str()])
.status_checked()?; .status_checked()?;
if cleanup { if ctx.config().cleanup() {
let cmd_flush_archives = format!("source {} && sdk flush archives", &sdkman_init_path); let cmd_flush_archives = format!("source {} && sdk flush archives", &sdkman_init_path);
run_type ctx.run_type()
.execute(&bash) .execute(&bash)
.args(["-c", cmd_flush_archives.as_str()]) .args(["-c", cmd_flush_archives.as_str()])
.status_checked()?; .status_checked()?;
let cmd_flush_temp = format!("source {} && sdk flush temp", &sdkman_init_path); let cmd_flush_temp = format!("source {} && sdk flush temp", &sdkman_init_path);
run_type ctx.run_type()
.execute(&bash) .execute(&bash)
.args(["-c", cmd_flush_temp.as_str()]) .args(["-c", cmd_flush_temp.as_str()])
.status_checked()?; .status_checked()?;
@@ -508,6 +540,13 @@ pub fn run_rcm(ctx: &ExecutionContext) -> Result<()> {
ctx.run_type().execute(rcup).arg("-v").status_checked() ctx.run_type().execute(rcup).arg("-v").status_checked()
} }
pub fn run_maza(ctx: &ExecutionContext) -> Result<()> {
let maza = require("maza")?;
print_separator("maza");
ctx.run_type().execute(maza).arg("update").status_checked()
}
pub fn reboot() -> Result<()> { pub fn reboot() -> Result<()> {
print!("Rebooting..."); print!("Rebooting...");
Command::new("sudo").arg("reboot").status_checked() Command::new("sudo").arg("reboot").status_checked()

View File

@@ -3,11 +3,11 @@ use std::path::Path;
use std::{ffi::OsStr, process::Command}; use std::{ffi::OsStr, process::Command};
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use etcetera::base_strategy::BaseStrategy;
use tracing::debug; use tracing::debug;
use crate::command::CommandExt; use crate::command::CommandExt;
use crate::execution_context::ExecutionContext; use crate::execution_context::ExecutionContext;
use crate::executor::RunType;
use crate::terminal::{print_separator, print_warning}; use crate::terminal::{print_separator, print_warning};
use crate::utils::require; use crate::utils::require;
use crate::{error::SkipStep, steps::git::Repositories}; use crate::{error::SkipStep, steps::git::Repositories};
@@ -19,17 +19,16 @@ pub fn run_chocolatey(ctx: &ExecutionContext) -> Result<()> {
print_separator("Chocolatey"); print_separator("Chocolatey");
let mut cmd = &choco; let mut command = match ctx.sudo() {
let mut args = vec!["upgrade", "all"]; Some(sudo) => {
let mut command = ctx.run_type().execute(sudo);
command.arg(choco);
command
}
None => ctx.run_type().execute(choco),
};
if let Some(sudo) = ctx.sudo() { command.args(["upgrade", "all"]);
cmd = sudo;
args.insert(0, "choco");
}
let mut command = ctx.run_type().execute(cmd);
command.args(&args);
if yes { if yes {
command.arg("--yes"); command.arg("--yes");
@@ -49,26 +48,45 @@ pub fn run_winget(ctx: &ExecutionContext) -> Result<()> {
} }
ctx.run_type() ctx.run_type()
.execute(&winget) .execute(winget)
.args(["upgrade", "--all"]) .args(["upgrade", "--all"])
.status_checked() .status_checked()
} }
pub fn run_scoop(cleanup: bool, run_type: RunType) -> Result<()> { pub fn run_scoop(ctx: &ExecutionContext) -> Result<()> {
let scoop = require("scoop")?; let scoop = require("scoop")?;
print_separator("Scoop"); print_separator("Scoop");
run_type.execute(&scoop).args(["update"]).status_checked()?; ctx.run_type().execute(&scoop).args(["update"]).status_checked()?;
run_type.execute(&scoop).args(["update", "*"]).status_checked()?; ctx.run_type().execute(&scoop).args(["update", "*"]).status_checked()?;
if cleanup { if ctx.config().cleanup() {
run_type.execute(&scoop).args(["cleanup", "*"]).status_checked()?; ctx.run_type().execute(&scoop).args(["cleanup", "*"]).status_checked()?;
} }
Ok(()) Ok(())
} }
pub fn update_wsl(ctx: &ExecutionContext) -> Result<()> {
let wsl = require("wsl")?;
print_separator("Update WSL");
let mut wsl_command = ctx.run_type().execute(wsl);
wsl_command.args(["--update"]);
if ctx.config().wsl_update_pre_release() {
wsl_command.args(["--pre-release"]);
}
if ctx.config().wsl_update_use_web_download() {
wsl_command.args(["--web-download"]);
}
wsl_command.status_checked()?;
Ok(())
}
fn get_wsl_distributions(wsl: &Path) -> Result<Vec<String>> { fn get_wsl_distributions(wsl: &Path) -> Result<Vec<String>> {
let output = Command::new(wsl).args(["--list", "-q"]).output_checked_utf8()?.stdout; let output = Command::new(wsl).args(["--list", "-q"]).output_checked_utf8()?.stdout;
Ok(output Ok(output
@@ -87,7 +105,7 @@ fn upgrade_wsl_distribution(wsl: &Path, dist: &str, ctx: &ExecutionContext) -> R
let mut command = ctx.run_type().execute(wsl); let mut command = ctx.run_type().execute(wsl);
command command
.args(["-d", dist, "bash", "-c"]) .args(["-d", dist, "bash", "-c"])
.arg(format!("TOPGRADE_PREFIX={} exec {}", dist, topgrade)); .arg(format!("TOPGRADE_PREFIX={dist} exec {topgrade}"));
if ctx.config().yes(Step::Wsl) { if ctx.config().yes(Step::Wsl) {
command.arg("-y"); command.arg("-y");
@@ -146,9 +164,8 @@ pub fn reboot() -> Result<()> {
Command::new("shutdown").args(["/R", "/T", "0"]).status_checked() Command::new("shutdown").args(["/R", "/T", "0"]).status_checked()
} }
pub fn insert_startup_scripts(ctx: &ExecutionContext, git_repos: &mut Repositories) -> Result<()> { pub fn insert_startup_scripts(git_repos: &mut Repositories) -> Result<()> {
let startup_dir = ctx let startup_dir = crate::WINDOWS_DIRS
.base_dirs()
.data_dir() .data_dir()
.join("Microsoft\\Windows\\Start Menu\\Programs\\Startup"); .join("Microsoft\\Windows\\Start Menu\\Programs\\Startup");
for entry in std::fs::read_dir(&startup_dir)?.flatten() { for entry in std::fs::read_dir(&startup_dir)?.flatten() {

View File

@@ -50,7 +50,7 @@ impl Powershell {
.args([ .args([
"-NoProfile", "-NoProfile",
"-Command", "-Command",
&format!("Get-Module -ListAvailable {}", command), &format!("Get-Module -ListAvailable {command}"),
]) ])
.output_checked_utf8() .output_checked_utf8()
.map(|result| !result.stdout.is_empty()) .map(|result| !result.stdout.is_empty())

View File

@@ -19,7 +19,7 @@ pub fn ssh_step(ctx: &ExecutionContext, hostname: &str) -> Result<()> {
args.extend(ssh_arguments.split_whitespace()); args.extend(ssh_arguments.split_whitespace());
} }
let env = format!("TOPGRADE_PREFIX={}", hostname); let env = format!("TOPGRADE_PREFIX={hostname}");
args.extend(["env", &env, "$SHELL", "-lc", topgrade]); args.extend(["env", &env, "$SHELL", "-lc", topgrade]);
if ctx.config().run_in_tmux() && !ctx.run_type().dry() { if ctx.config().run_in_tmux() && !ctx.run_type().dry() {
@@ -43,11 +43,11 @@ pub fn ssh_step(ctx: &ExecutionContext, hostname: &str) -> Result<()> {
args.extend(ssh_arguments.split_whitespace()); args.extend(ssh_arguments.split_whitespace());
} }
let env = format!("TOPGRADE_PREFIX={}", hostname); let env = format!("TOPGRADE_PREFIX={hostname}");
args.extend(["env", &env, "$SHELL", "-lc", topgrade]); args.extend(["env", &env, "$SHELL", "-lc", topgrade]);
print_separator(format!("Remote ({})", hostname)); print_separator(format!("Remote ({hostname})"));
println!("Connecting to {}...", hostname); println!("Connecting to {hostname}...");
ctx.run_type().execute(ssh).args(&args).status_checked() ctx.run_type().execute(ssh).args(&args).status_checked()
} }

View File

@@ -183,7 +183,7 @@ pub fn topgrade_vagrant_box(ctx: &ExecutionContext, vagrant_box: &VagrantBox) ->
let mut _poweron = None; let mut _poweron = None;
if !vagrant_box.initial_status.powered_on() { if !vagrant_box.initial_status.powered_on() {
if !(ctx.config().vagrant_power_on().unwrap_or(true)) { if !(ctx.config().vagrant_power_on().unwrap_or(true)) {
return Err(SkipStep(format!("Skipping powered off box {}", vagrant_box)).into()); return Err(SkipStep(format!("Skipping powered off box {vagrant_box}")).into());
} else { } else {
print_separator(seperator); print_separator(seperator);
_poweron = Some(vagrant.temporary_power_on(vagrant_box, ctx)?); _poweron = Some(vagrant.temporary_power_on(vagrant_box, ctx)?);

View File

@@ -5,11 +5,10 @@ use std::process::Command;
use color_eyre::eyre::eyre; use color_eyre::eyre::eyre;
use color_eyre::eyre::Context; use color_eyre::eyre::Context;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use directories::BaseDirs;
use crate::command::CommandExt; use crate::command::CommandExt;
use crate::executor::RunType;
use crate::terminal::print_separator; use crate::terminal::print_separator;
use crate::HOME_DIR;
use crate::{ use crate::{
execution_context::ExecutionContext, execution_context::ExecutionContext,
utils::{which, PathExt}, utils::{which, PathExt},
@@ -18,15 +17,12 @@ use crate::{
#[cfg(unix)] #[cfg(unix)]
use std::os::unix::process::CommandExt as _; use std::os::unix::process::CommandExt as _;
pub fn run_tpm(base_dirs: &BaseDirs, run_type: RunType) -> Result<()> { pub fn run_tpm(ctx: &ExecutionContext) -> Result<()> {
let tpm = base_dirs let tpm = HOME_DIR.join(".tmux/plugins/tpm/bin/update_plugins").require()?;
.home_dir()
.join(".tmux/plugins/tpm/bin/update_plugins")
.require()?;
print_separator("tmux plugins"); print_separator("tmux plugins");
run_type.execute(tpm).arg("all").status_checked() ctx.run_type().execute(tpm).arg("all").status_checked()
} }
struct Tmux { struct Tmux {

View File

@@ -42,7 +42,7 @@ pub fn run_toolbx(ctx: &ExecutionContext) -> Result<()> {
let topgrade_path = topgrade_path.to_str().unwrap(); let topgrade_path = topgrade_path.to_str().unwrap();
for tb in toolboxes.iter() { for tb in toolboxes.iter() {
let topgrade_prefix = format!("TOPGRADE_PREFIX='Toolbx {}'", tb); let topgrade_prefix = format!("TOPGRADE_PREFIX='Toolbx {tb}'");
let mut args = vec![ let mut args = vec![
"run", "run",
"-c", "-c",
@@ -52,6 +52,8 @@ pub fn run_toolbx(ctx: &ExecutionContext) -> Result<()> {
topgrade_path, topgrade_path,
"--only", "--only",
"system", "system",
"--no-self-update",
"--skip-notify",
]; ];
if ctx.config().yes(Step::Toolbx) { if ctx.config().yes(Step::Toolbx) {
args.push("--yes"); args.push("--yes");

View File

@@ -1,3 +1,14 @@
" AstroUpdate calls a plugin manager - Lazy as of this writing. So we check for it before
" others. Add to init.lua:
" updater = {
" skip_prompts = true,
" },
if exists(":AstroUpdate")
echo "AstroUpdate"
AstroUpdate
quitall
endif
if exists(":NeoBundleUpdate") if exists(":NeoBundleUpdate")
echo "NeoBundle" echo "NeoBundle"
NeoBundleUpdate NeoBundleUpdate
@@ -33,23 +44,15 @@ if exists(":PaqUpdate")
PaqUpdate PaqUpdate
endif endif
if exists(":CocUpdateSync") if exists(":Lazy")
echo "CocUpdateSync" echo "Lazy Update"
CocUpdateSync Lazy! sync | qa
endif
" TODO: Should this be after `PackerSync`?
" Not sure how to sequence this after Packer without doing something weird
" with that `PackerComplete` autocommand.
if exists(":TSUpdate")
echo "TreeSitter Update"
TSUpdate
endif endif
if exists(':PackerSync') if exists(':PackerSync')
echo "Packer" echo "Packer"
autocmd User PackerComplete quitall autocmd User PackerComplete quitall
PackerSync PackerSync
else else
quitall quitall
endif endif

View File

@@ -1,14 +1,15 @@
use crate::command::CommandExt; use crate::command::CommandExt;
use crate::error::{SkipStep, TopgradeError}; use crate::error::{SkipStep, TopgradeError};
use crate::HOME_DIR;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use etcetera::base_strategy::BaseStrategy;
use crate::executor::{Executor, ExecutorOutput, RunType}; use crate::executor::{Executor, ExecutorOutput};
use crate::terminal::print_separator; use crate::terminal::print_separator;
use crate::{ use crate::{
execution_context::ExecutionContext, execution_context::ExecutionContext,
utils::{require, PathExt}, utils::{require, PathExt},
}; };
use directories::BaseDirs;
use std::path::PathBuf; use std::path::PathBuf;
use std::{ use std::{
io::{self, Write}, io::{self, Write},
@@ -18,22 +19,19 @@ use tracing::debug;
const UPGRADE_VIM: &str = include_str!("upgrade.vim"); const UPGRADE_VIM: &str = include_str!("upgrade.vim");
pub fn vimrc(base_dirs: &BaseDirs) -> Result<PathBuf> { pub fn vimrc() -> Result<PathBuf> {
base_dirs HOME_DIR
.home_dir()
.join(".vimrc") .join(".vimrc")
.require() .require()
.or_else(|_| base_dirs.home_dir().join(".vim/vimrc").require()) .or_else(|_| HOME_DIR.join(".vim/vimrc").require())
} }
fn nvimrc(base_dirs: &BaseDirs) -> Result<PathBuf> { fn nvimrc() -> Result<PathBuf> {
#[cfg(unix)] #[cfg(unix)]
let base_dir = let base_dir = crate::XDG_DIRS.config_dir();
// Bypass directories crate as nvim doesn't use the macOS-specific directories.
std::env::var_os("XDG_CONFIG_HOME").map_or_else(|| base_dirs.home_dir().join(".config"), PathBuf::from);
#[cfg(windows)] #[cfg(windows)]
let base_dir = base_dirs.cache_dir(); let base_dir = crate::WINDOWS_DIRS.cache_dir();
base_dir base_dir
.join("nvim/init.vim") .join("nvim/init.vim")
@@ -74,7 +72,7 @@ fn upgrade(command: &mut Executor, ctx: &ExecutionContext) -> Result<()> {
} }
pub fn upgrade_ultimate_vimrc(ctx: &ExecutionContext) -> Result<()> { pub fn upgrade_ultimate_vimrc(ctx: &ExecutionContext) -> Result<()> {
let config_dir = ctx.base_dirs().home_dir().join(".vim_runtime").require()?; let config_dir = HOME_DIR.join(".vim_runtime").require()?;
let git = require("git")?; let git = require("git")?;
let python = require("python3")?; let python = require("python3")?;
let update_plugins = config_dir.join("update_plugins.py").require()?; let update_plugins = config_dir.join("update_plugins.py").require()?;
@@ -105,7 +103,7 @@ pub fn upgrade_ultimate_vimrc(ctx: &ExecutionContext) -> Result<()> {
Ok(()) Ok(())
} }
pub fn upgrade_vim(base_dirs: &BaseDirs, ctx: &ExecutionContext) -> Result<()> { pub fn upgrade_vim(ctx: &ExecutionContext) -> Result<()> {
let vim = require("vim")?; let vim = require("vim")?;
let output = Command::new(&vim).arg("--version").output_checked_utf8()?; let output = Command::new(&vim).arg("--version").output_checked_utf8()?;
@@ -113,7 +111,7 @@ pub fn upgrade_vim(base_dirs: &BaseDirs, ctx: &ExecutionContext) -> Result<()> {
return Err(SkipStep(String::from("vim binary might be actually nvim")).into()); return Err(SkipStep(String::from("vim binary might be actually nvim")).into());
} }
let vimrc = vimrc(base_dirs)?; let vimrc = vimrc()?;
print_separator("Vim"); print_separator("Vim");
upgrade( upgrade(
@@ -127,9 +125,9 @@ pub fn upgrade_vim(base_dirs: &BaseDirs, ctx: &ExecutionContext) -> Result<()> {
) )
} }
pub fn upgrade_neovim(base_dirs: &BaseDirs, ctx: &ExecutionContext) -> Result<()> { pub fn upgrade_neovim(ctx: &ExecutionContext) -> Result<()> {
let nvim = require("nvim")?; let nvim = require("nvim")?;
let nvimrc = nvimrc(base_dirs)?; let nvimrc = nvimrc()?;
print_separator("Neovim"); print_separator("Neovim");
upgrade( upgrade(
@@ -143,10 +141,10 @@ pub fn upgrade_neovim(base_dirs: &BaseDirs, ctx: &ExecutionContext) -> Result<()
) )
} }
pub fn run_voom(_base_dirs: &BaseDirs, run_type: RunType) -> Result<()> { pub fn run_voom(ctx: &ExecutionContext) -> Result<()> {
let voom = require("voom")?; let voom = require("voom")?;
print_separator("voom"); print_separator("voom");
run_type.execute(voom).arg("update").status_checked() ctx.run_type().execute(voom).arg("update").status_checked()
} }

View File

@@ -1,118 +1,147 @@
use std::env; use std::env;
use std::path::{Path, PathBuf}; use std::path::PathBuf;
use std::process::Command; use std::process::Command;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use directories::BaseDirs;
use tracing::debug; use tracing::debug;
use walkdir::WalkDir; use walkdir::WalkDir;
use crate::command::CommandExt; use crate::command::CommandExt;
use crate::execution_context::ExecutionContext; use crate::execution_context::ExecutionContext;
use crate::executor::RunType;
use crate::git::Repositories; use crate::git::Repositories;
use crate::terminal::print_separator; use crate::terminal::print_separator;
use crate::utils::{require, PathExt}; use crate::utils::{require, PathExt};
use crate::HOME_DIR;
pub fn run_zr(base_dirs: &BaseDirs, run_type: RunType) -> Result<()> { pub fn run_zr(ctx: &ExecutionContext) -> Result<()> {
let zsh = require("zsh")?; let zsh = require("zsh")?;
require("zr")?; require("zr")?;
print_separator("zr"); print_separator("zr");
let cmd = format!("source {} && zr --update", zshrc(base_dirs).display()); let cmd = format!("source {} && zr --update", zshrc().display());
run_type.execute(zsh).args(["-l", "-c", cmd.as_str()]).status_checked() ctx.run_type()
.execute(zsh)
.args(["-l", "-c", cmd.as_str()])
.status_checked()
} }
pub fn zshrc(base_dirs: &BaseDirs) -> PathBuf { fn zdotdir() -> PathBuf {
env::var("ZDOTDIR") env::var("ZDOTDIR")
.map(|p| Path::new(&p).join(".zshrc")) .map(PathBuf::from)
.unwrap_or_else(|_| base_dirs.home_dir().join(".zshrc")) .unwrap_or_else(|_| HOME_DIR.clone())
} }
pub fn run_antibody(run_type: RunType) -> Result<()> { pub fn zshrc() -> PathBuf {
zdotdir().join(".zshrc")
}
pub fn run_antidote(ctx: &ExecutionContext) -> Result<()> {
let zsh = require("zsh")?;
let mut antidote = zdotdir().join(".antidote").require()?;
antidote.push("antidote.zsh");
print_separator("antidote");
ctx.run_type()
.execute(zsh)
.arg("-c")
.arg(format!("source {} && antidote update", antidote.display()))
.status_checked()
}
pub fn run_antibody(ctx: &ExecutionContext) -> Result<()> {
require("zsh")?; require("zsh")?;
let antibody = require("antibody")?; let antibody = require("antibody")?;
print_separator("antibody"); print_separator("antibody");
run_type.execute(antibody).arg("update").status_checked() ctx.run_type().execute(antibody).arg("update").status_checked()
} }
pub fn run_antigen(base_dirs: &BaseDirs, run_type: RunType) -> Result<()> { pub fn run_antigen(ctx: &ExecutionContext) -> Result<()> {
let zsh = require("zsh")?; let zsh = require("zsh")?;
let zshrc = zshrc(base_dirs).require()?; let zshrc = zshrc().require()?;
env::var("ADOTDIR") env::var("ADOTDIR")
.map(PathBuf::from) .map(PathBuf::from)
.unwrap_or_else(|_| base_dirs.home_dir().join("antigen.zsh")) .unwrap_or_else(|_| HOME_DIR.join("antigen.zsh"))
.require()?; .require()?;
print_separator("antigen"); print_separator("antigen");
let cmd = format!("source {} && (antigen selfupdate ; antigen update)", zshrc.display()); let cmd = format!("source {} && (antigen selfupdate ; antigen update)", zshrc.display());
run_type.execute(zsh).args(["-l", "-c", cmd.as_str()]).status_checked() ctx.run_type()
.execute(zsh)
.args(["-l", "-c", cmd.as_str()])
.status_checked()
} }
pub fn run_zgenom(base_dirs: &BaseDirs, run_type: RunType) -> Result<()> { pub fn run_zgenom(ctx: &ExecutionContext) -> Result<()> {
let zsh = require("zsh")?; let zsh = require("zsh")?;
let zshrc = zshrc(base_dirs).require()?; let zshrc = zshrc().require()?;
env::var("ZGEN_SOURCE") env::var("ZGEN_SOURCE")
.map(PathBuf::from) .map(PathBuf::from)
.unwrap_or_else(|_| base_dirs.home_dir().join(".zgenom")) .unwrap_or_else(|_| HOME_DIR.join(".zgenom"))
.require()?; .require()?;
print_separator("zgenom"); print_separator("zgenom");
let cmd = format!("source {} && zgenom selfupdate && zgenom update", zshrc.display()); let cmd = format!("source {} && zgenom selfupdate && zgenom update", zshrc.display());
run_type.execute(zsh).args(["-l", "-c", cmd.as_str()]).status_checked() ctx.run_type()
.execute(zsh)
.args(["-l", "-c", cmd.as_str()])
.status_checked()
} }
pub fn run_zplug(base_dirs: &BaseDirs, run_type: RunType) -> Result<()> { pub fn run_zplug(ctx: &ExecutionContext) -> Result<()> {
let zsh = require("zsh")?; let zsh = require("zsh")?;
zshrc(base_dirs).require()?; zshrc().require()?;
env::var("ZPLUG_HOME") env::var("ZPLUG_HOME")
.map(PathBuf::from) .map(PathBuf::from)
.unwrap_or_else(|_| base_dirs.home_dir().join(".zplug")) .unwrap_or_else(|_| HOME_DIR.join(".zplug"))
.require()?; .require()?;
print_separator("zplug"); print_separator("zplug");
run_type ctx.run_type()
.execute(zsh) .execute(zsh)
.args(["-i", "-c", "zplug update"]) .args(["-i", "-c", "zplug update"])
.status_checked() .status_checked()
} }
pub fn run_zinit(base_dirs: &BaseDirs, run_type: RunType) -> Result<()> { pub fn run_zinit(ctx: &ExecutionContext) -> Result<()> {
let zsh = require("zsh")?; let zsh = require("zsh")?;
let zshrc = zshrc(base_dirs).require()?; let zshrc = zshrc().require()?;
env::var("ZINIT_HOME") env::var("ZINIT_HOME")
.map(PathBuf::from) .map(PathBuf::from)
.unwrap_or_else(|_| base_dirs.home_dir().join(".zinit")) .unwrap_or_else(|_| HOME_DIR.join(".zinit"))
.require()?; .require()?;
print_separator("zinit"); print_separator("zinit");
let cmd = format!("source {} && zinit self-update && zinit update --all", zshrc.display(),); let cmd = format!("source {} && zinit self-update && zinit update --all", zshrc.display(),);
run_type.execute(zsh).args(["-i", "-c", cmd.as_str()]).status_checked() ctx.run_type()
.execute(zsh)
.args(["-i", "-c", cmd.as_str()])
.status_checked()
} }
pub fn run_zi(base_dirs: &BaseDirs, run_type: RunType) -> Result<()> { pub fn run_zi(ctx: &ExecutionContext) -> Result<()> {
let zsh = require("zsh")?; let zsh = require("zsh")?;
let zshrc = zshrc(base_dirs).require()?; let zshrc = zshrc().require()?;
base_dirs.home_dir().join(".zi").require()?; HOME_DIR.join(".zi").require()?;
print_separator("zi"); print_separator("zi");
let cmd = format!("source {} && zi self-update && zi update --all", zshrc.display(),); let cmd = format!("source {} && zi self-update && zi update --all", zshrc.display(),);
run_type.execute(zsh).args(["-i", "-c", &cmd]).status_checked() ctx.run_type().execute(zsh).args(["-i", "-c", &cmd]).status_checked()
} }
pub fn run_zim(base_dirs: &BaseDirs, run_type: RunType) -> Result<()> { pub fn run_zim(ctx: &ExecutionContext) -> Result<()> {
let zsh = require("zsh")?; let zsh = require("zsh")?;
env::var("ZIM_HOME") env::var("ZIM_HOME")
.or_else(|_| { .or_else(|_| {
@@ -123,12 +152,12 @@ pub fn run_zim(base_dirs: &BaseDirs, run_type: RunType) -> Result<()> {
.map(|o| o.stdout) .map(|o| o.stdout)
}) })
.map(PathBuf::from) .map(PathBuf::from)
.unwrap_or_else(|_| base_dirs.home_dir().join(".zim")) .unwrap_or_else(|_| HOME_DIR.join(".zim"))
.require()?; .require()?;
print_separator("zim"); print_separator("zim");
run_type ctx.run_type()
.execute(zsh) .execute(zsh)
.args(["-i", "-c", "zimfw upgrade && zimfw update"]) .args(["-i", "-c", "zimfw upgrade && zimfw update"])
.status_checked() .status_checked()
@@ -136,7 +165,33 @@ pub fn run_zim(base_dirs: &BaseDirs, run_type: RunType) -> Result<()> {
pub fn run_oh_my_zsh(ctx: &ExecutionContext) -> Result<()> { pub fn run_oh_my_zsh(ctx: &ExecutionContext) -> Result<()> {
require("zsh")?; require("zsh")?;
let oh_my_zsh = ctx.base_dirs().home_dir().join(".oh-my-zsh").require()?;
// When updating `oh-my-zsh` on a remote machine through topgrade, the
// following processes will be created:
//
// SSH -> ZSH -> ZSH ($SHELL) -> topgrade -> ZSH
//
// The first ZSH process, won't source zshrc (as it is a login shell),
// and thus it won't have the ZSH environment variable, as a result, the
// children processes won't get it either, so we source the zshrc and set
// the ZSH variable for topgrade here.
if ctx.under_ssh() {
let zshrc_path = zshrc().require()?;
let output = Command::new("zsh")
.args([
"-c",
// ` > /dev/null` is used in case the user's zshrc will have some stdout output.
format!("source {} > /dev/null && echo $ZSH", zshrc_path.display()).as_str(),
])
.output_checked_utf8()?;
env::set_var("ZSH", output.stdout.trim());
}
let oh_my_zsh = env::var("ZSH")
.map(PathBuf::from)
// default to `~/.oh-my-zsh`
.unwrap_or(HOME_DIR.join(".oh-my-zsh"))
.require()?;
print_separator("oh-my-zsh"); print_separator("oh-my-zsh");
@@ -176,7 +231,10 @@ pub fn run_oh_my_zsh(ctx: &ExecutionContext) -> Result<()> {
ctx.run_type() ctx.run_type()
.execute("zsh") .execute("zsh")
.env("ZSH", &oh_my_zsh)
.arg(&oh_my_zsh.join("tools/upgrade.sh")) .arg(&oh_my_zsh.join("tools/upgrade.sh"))
// oh-my-zsh returns 80 when it is already updated and no changes pulled
// in this update.
// See this comment: https://github.com/r-darwish/topgrade/issues/569#issuecomment-736756731
// for more information.
.status_checked_with_codes(&[80]) .status_checked_with_codes(&[80])
} }

125
src/sudo.rs Normal file
View File

@@ -0,0 +1,125 @@
use std::ffi::OsStr;
use std::path::Path;
use std::path::PathBuf;
use color_eyre::eyre::Context;
use color_eyre::eyre::Result;
use serde::Deserialize;
use strum::AsRefStr;
use crate::command::CommandExt;
use crate::execution_context::ExecutionContext;
use crate::executor::Executor;
use crate::terminal::print_separator;
use crate::utils::which;
#[derive(Clone, Debug)]
pub struct Sudo {
/// The path to the `sudo` binary.
path: PathBuf,
/// The type of program being used as `sudo`.
kind: SudoKind,
}
impl Sudo {
/// Get the `sudo` binary for this platform.
pub fn detect() -> Option<Self> {
which("doas")
.map(|p| (p, SudoKind::Doas))
.or_else(|| which("please").map(|p| (p, SudoKind::Please)))
.or_else(|| which("sudo").map(|p| (p, SudoKind::Sudo)))
.or_else(|| which("gsudo").map(|p| (p, SudoKind::Gsudo)))
.or_else(|| which("pkexec").map(|p| (p, SudoKind::Pkexec)))
.map(|(path, kind)| Self { path, kind })
}
/// Create Sudo from SudoKind, if found in the system
pub fn new(kind: SudoKind) -> Option<Self> {
which(kind.as_ref()).map(|path| Self { path, kind })
}
/// Elevate permissions with `sudo`.
///
/// This helps prevent blocking `sudo` prompts from stopping the run in the middle of a
/// step.
///
/// See: https://github.com/topgrade-rs/topgrade/issues/205
pub fn elevate(&self, ctx: &ExecutionContext) -> Result<()> {
print_separator("Sudo");
let mut cmd = ctx.run_type().execute(self);
match self.kind {
SudoKind::Doas => {
// `doas` doesn't have anything like `sudo -v` to cache credentials,
// so we just execute a dummy `echo` command so we have something
// unobtrusive to run.
// See: https://man.openbsd.org/doas
cmd.arg("echo");
}
SudoKind::Please => {
// From `man please`
// -w, --warm
// Warm the access token and exit.
cmd.arg("-w");
}
SudoKind::Sudo => {
// From `man sudo` on macOS:
// -v, --validate
// Update the user's cached credentials, authenticating the user
// if necessary. For the sudoers plugin, this extends the sudo
// timeout for another 5 minutes by default, but does not run a
// command. Not all security policies support cached credentials.
cmd.arg("-v");
}
SudoKind::Gsudo => {
// Shows current user, cache and console status.
// See: https://gerardog.github.io/gsudo/docs/usage
cmd.arg("status");
}
SudoKind::Pkexec => {
// I don't think this does anything; `pkexec` usually asks for
// authentication every time, although it can be configured
// differently.
//
// See the note for `doas` above.
//
// See: https://linux.die.net/man/1/pkexec
cmd.arg("echo");
}
}
cmd.status_checked().wrap_err("Failed to elevate permissions")
}
/// Execute a command with `sudo`.
pub fn execute_elevated(&self, ctx: &ExecutionContext, command: &Path, interactive: bool) -> Executor {
let mut cmd = ctx.run_type().execute(self);
if let SudoKind::Sudo = self.kind {
cmd.arg("--preserve-env=DIFFPROG");
}
if interactive {
cmd.arg("-i");
}
cmd.arg(command);
cmd
}
}
#[derive(Clone, Copy, Debug, Deserialize, AsRefStr)]
#[serde(rename_all = "lowercase")]
#[strum(serialize_all = "lowercase")]
pub enum SudoKind {
Doas,
Please,
Sudo,
Gsudo,
Pkexec,
}
impl AsRef<OsStr> for Sudo {
fn as_ref(&self) -> &OsStr {
self.path.as_ref()
}
}

View File

@@ -1,8 +1,6 @@
use std::cmp::{max, min}; use std::cmp::{max, min};
use std::env; use std::env;
use std::io::{self, Write}; use std::io::{self, Write};
#[cfg(target_os = "linux")]
use std::path::PathBuf;
use std::process::Command; use std::process::Command;
use std::sync::Mutex; use std::sync::Mutex;
use std::time::Duration; use std::time::Duration;
@@ -12,7 +10,6 @@ use color_eyre::eyre;
use color_eyre::eyre::Context; use color_eyre::eyre::Context;
use console::{style, Key, Term}; use console::{style, Key, Term};
use lazy_static::lazy_static; use lazy_static::lazy_static;
#[cfg(target_os = "macos")]
use notify_rust::{Notification, Timeout}; use notify_rust::{Notification, Timeout};
use tracing::{debug, error}; use tracing::{debug, error};
#[cfg(windows)] #[cfg(windows)]
@@ -20,8 +17,6 @@ use which_crate::which;
use crate::command::CommandExt; use crate::command::CommandExt;
use crate::report::StepResult; use crate::report::StepResult;
#[cfg(target_os = "linux")]
use crate::utils::which;
lazy_static! { lazy_static! {
static ref TERMINAL: Mutex<Terminal> = Mutex::new(Terminal::new()); static ref TERMINAL: Mutex<Terminal> = Mutex::new(Terminal::new());
@@ -48,8 +43,6 @@ struct Terminal {
set_title: bool, set_title: bool,
display_time: bool, display_time: bool,
desktop_notification: bool, desktop_notification: bool,
#[cfg(target_os = "linux")]
notify_send: Option<PathBuf>,
} }
impl Terminal { impl Terminal {
@@ -59,13 +52,11 @@ impl Terminal {
width: term.size_checked().map(|(_, w)| w), width: term.size_checked().map(|(_, w)| w),
term, term,
prefix: env::var("TOPGRADE_PREFIX") prefix: env::var("TOPGRADE_PREFIX")
.map(|prefix| format!("({}) ", prefix)) .map(|prefix| format!("({prefix}) "))
.unwrap_or_else(|_| String::new()), .unwrap_or_else(|_| String::new()),
set_title: true, set_title: true,
display_time: true, display_time: true,
desktop_notification: false, desktop_notification: false,
#[cfg(target_os = "linux")]
notify_send: which("notify-send"),
} }
} }
@@ -81,35 +72,18 @@ impl Terminal {
self.display_time = display_time self.display_time = display_time
} }
#[allow(unused_variables)]
fn notify_desktop<P: AsRef<str>>(&self, message: P, timeout: Option<Duration>) { fn notify_desktop<P: AsRef<str>>(&self, message: P, timeout: Option<Duration>) {
debug!("Desktop notification: {}", message.as_ref()); debug!("Desktop notification: {}", message.as_ref());
cfg_if::cfg_if! { let mut notification = Notification::new();
if #[cfg(target_os = "macos")] { notification
let mut notification = Notification::new(); .summary("Topgrade")
notification.summary("Topgrade") .body(message.as_ref())
.body(message.as_ref()) .appname("topgrade");
.appname("topgrade");
if let Some(timeout) = timeout { if let Some(timeout) = timeout {
notification.timeout(Timeout::Milliseconds(timeout.as_millis() as u32)); notification.timeout(Timeout::Milliseconds(timeout.as_millis() as u32));
}
notification.show().ok();
} else if #[cfg(target_os = "linux")] {
if let Some(ns) = self.notify_send.as_ref() {
let mut command = Command::new(ns);
if let Some(timeout) = timeout {
command.arg("-t");
command.arg(format!("{}", timeout.as_millis()));
}
command.args(["-a", "Topgrade", "Topgrade"]);
command.arg(message.as_ref());
if let Err(err) = command.output_checked() {
tracing::error!("{err:?}");
}
}
}
} }
notification.show().ok();
} }
fn print_separator<P: AsRef<str>>(&mut self, message: P) { fn print_separator<P: AsRef<str>>(&mut self, message: P) {
@@ -142,7 +116,7 @@ impl Terminal {
.write_fmt(format_args!( .write_fmt(format_args!(
"{}\n", "{}\n",
style(format_args!( style(format_args!(
"\n―― {} {:^border$}", "\n── {} {:^border$}",
message, message,
"", "",
border = max( border = max(
@@ -158,7 +132,7 @@ impl Terminal {
.ok(); .ok();
} }
None => { None => {
self.term.write_fmt(format_args!("―― {} ――\n", message)).ok(); self.term.write_fmt(format_args!("―― {message} ――\n")).ok();
} }
} }
} }
@@ -170,7 +144,7 @@ impl Terminal {
self.term self.term
.write_fmt(format_args!( .write_fmt(format_args!(
"{} {}", "{} {}",
style(format!("{} failed:", key)).red().bold(), style(format!("{key} failed:")).red().bold(),
message message
)) ))
.ok(); .ok();
@@ -214,7 +188,7 @@ impl Terminal {
self.term self.term
.write_fmt(format_args!( .write_fmt(format_args!(
"{}", "{}",
style(format!("{} (y)es/(N)o", question,)).yellow().bold() style(format!("{question} (y)es/(N)o",)).yellow().bold()
)) ))
.ok(); .ok();
@@ -236,13 +210,15 @@ impl Terminal {
self.term.set_title("Topgrade - Awaiting user"); self.term.set_title("Topgrade - Awaiting user");
} }
self.notify_desktop(format!("{} failed", step_name), None); if self.desktop_notification {
self.notify_desktop(format!("{step_name} failed"), None);
}
let prompt_inner = style(format!("{}Retry? (y)es/(N)o/(s)hell/(q)uit", self.prefix)) let prompt_inner = style(format!("{}Retry? (y)es/(N)o/(s)hell/(q)uit", self.prefix))
.yellow() .yellow()
.bold(); .bold();
self.term.write_fmt(format_args!("\n{}", prompt_inner)).ok(); self.term.write_fmt(format_args!("\n{prompt_inner}")).ok();
let answer = loop { let answer = loop {
match self.term.read_key() { match self.term.read_key() {
@@ -250,7 +226,7 @@ impl Terminal {
Ok(Key::Char('s')) | Ok(Key::Char('S')) => { Ok(Key::Char('s')) | Ok(Key::Char('S')) => {
println!("\n\nDropping you to shell. Fix what you need and then exit the shell.\n"); println!("\n\nDropping you to shell. Fix what you need and then exit the shell.\n");
if let Err(err) = run_shell().context("Failed to run shell") { if let Err(err) = run_shell().context("Failed to run shell") {
self.term.write_fmt(format_args!("{err:?}\n{}", prompt_inner)).ok(); self.term.write_fmt(format_args!("{err:?}\n{prompt_inner}")).ok();
} else { } else {
break Ok(true); break Ok(true);
} }

View File

@@ -1,12 +1,15 @@
use crate::error::SkipStep;
use color_eyre::eyre::Result;
use std::env; use std::env;
use std::ffi::OsStr; use std::ffi::OsStr;
use std::fmt::Debug; use std::fmt::Debug;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::process::Command;
use color_eyre::eyre::Result;
use tracing::{debug, error}; use tracing::{debug, error};
use crate::command::CommandExt;
use crate::error::SkipStep;
pub trait PathExt pub trait PathExt
where where
Self: Sized, Self: Sized,
@@ -67,13 +70,6 @@ pub fn which<T: AsRef<OsStr> + Debug>(binary_name: T) -> Option<PathBuf> {
} }
} }
pub fn sudo() -> Option<PathBuf> {
which("doas")
.or_else(|| which("sudo"))
.or_else(|| which("gsudo"))
.or_else(|| which("pkexec"))
}
pub fn editor() -> Vec<String> { pub fn editor() -> Vec<String> {
env::var("EDITOR") env::var("EDITOR")
.unwrap_or_else(|_| String::from(if cfg!(windows) { "notepad" } else { "vi" })) .unwrap_or_else(|_| String::from(if cfg!(windows) { "notepad" } else { "vi" }))
@@ -108,6 +104,13 @@ pub fn require_option<T>(option: Option<T>, cause: String) -> Result<T> {
} }
} }
pub fn string_prepend_str(string: &mut String, s: &str) {
let mut new_string = String::with_capacity(string.len() + s.len());
new_string.push_str(s);
new_string.push_str(string);
*string = new_string;
}
/* sys-info-rs /* sys-info-rs
* *
* Copyright (c) 2015 Siyu Wang * Copyright (c) 2015 Siyu Wang
@@ -151,11 +154,100 @@ pub fn hostname() -> Result<String> {
#[cfg(target_family = "windows")] #[cfg(target_family = "windows")]
pub fn hostname() -> Result<String> { pub fn hostname() -> Result<String> {
use crate::command::CommandExt;
use std::process::Command;
Command::new("hostname") Command::new("hostname")
.output_checked_utf8() .output_checked_utf8()
.map_err(|err| SkipStep(format!("Failed to get hostname: {}", err)).into()) .map_err(|err| SkipStep(format!("Failed to get hostname: {err}")).into())
.map(|output| output.stdout.trim().to_owned()) .map(|output| output.stdout.trim().to_owned())
} }
pub mod merge_strategies {
use merge::Merge;
use crate::config::Commands;
/// Prepends right to left (both Option<Vec<T>>)
pub fn vec_prepend_opt<T>(left: &mut Option<Vec<T>>, right: Option<Vec<T>>) {
if let Some(left_vec) = left {
if let Some(mut right_vec) = right {
right_vec.append(left_vec);
let _ = std::mem::replace(left, Some(right_vec));
}
} else {
*left = right;
}
}
/// Appends an Option<String> to another Option<String>
pub fn string_append_opt(left: &mut Option<String>, right: Option<String>) {
if let Some(left_str) = left {
if let Some(right_str) = right {
left_str.push(' ');
left_str.push_str(&right_str);
}
} else {
*left = right;
}
}
pub fn inner_merge_opt<T>(left: &mut Option<T>, right: Option<T>)
where
T: Merge,
{
if let Some(ref mut left_inner) = left {
if let Some(right_inner) = right {
left_inner.merge(right_inner);
}
} else {
*left = right;
}
}
pub fn commands_merge_opt(left: &mut Option<Commands>, right: Option<Commands>) {
if let Some(ref mut left_inner) = left {
if let Some(right_inner) = right {
left_inner.extend(right_inner);
}
} else {
*left = right;
}
}
}
// Skip causes
// TODO: Put them in a better place when we have more of them
pub const REQUIRE_SUDO: &str = "Require sudo or counterpart but not found, skip";
/// Return `Err(SkipStep)` if `python` is a Python 2 or shim.
///
/// # Shim
/// On Windows, if you install `python` through `winget`, an actual `python`
/// is installed as well as a `python3` shim. Shim is invokable, but when you
/// execute it, the Microsoft App Store will be launched instead of a Python
/// shell.
///
/// We do this check through `python -V`, a shim will just give `Python` with
/// no version number.
pub fn check_is_python_2_or_shim(python: PathBuf) -> Result<PathBuf> {
let output = Command::new(&python).arg("-V").output_checked_utf8()?;
// "Python x.x.x\n"
let stdout = output.stdout;
// ["Python"] or ["Python", "x.x.x"], the newline char is trimmed.
let mut split = stdout.split_whitespace();
if let Some(version) = split.nth(1) {
let major_version = version
.split('.')
.next()
.expect("Should have a major version number")
.parse::<u32>()
.expect("Major version should be a valid number");
if major_version == 2 {
return Err(SkipStep(format!("{} is a Python 2, skip.", python.display())).into());
}
} else {
// No version number, is a shim
return Err(SkipStep(format!("{} is a Python shim, skip.", python.display())).into());
}
Ok(python)
}

View File

@@ -1,80 +0,0 @@
.hy
.TH "topgrade" "8"
.SH NAME
.PP
Topgrade \- Upgrade everything
.SH SYNOPSIS
.PP
topgrade [\fIoptions\f[]]
.SH DESCRIPTION
.PP
Keeping your system up to date usually involves invoking multiple package managers.
This results in big, non-portable shell one-liners saved in your shell.
To remedy this, \fBTopgrade\fR detects which tools you use and runs the appropriate commands to update them.
.SH OPTIONS
.TP
.B \-\-only <only>
Run only specific steps
.RS
.RE
.TP
.B \-\-disable <disable>
Disable specific steps
.RS
.RE
.TP
.B \-c, \-\-cleanup
Cleanup temporary or old files
.RS
.RE
.TP
.B \-n, \-\-dry\-run
List the commands that would be run
.RS
.RE
.TP
.B \-\-edit\-config
Edit the configuration file
.RS
.RE
.TP
.B \-h, \-\-help
Print help information
.RS
.RE
.TP
.B \-k, \-\-keep
Prompt for a key before exiting
.RS
.RE
.TP
.B \-\-no\-retry
Do not ask to retry failed steps
.RS
.RE
.TP
.B \-t, \-\-tmux
Run inside tmux
.RS
.RE
.TP
.B \-V, \-\-version
Print version information
.RS
.RE
.TP
.B \-v, \-\-verbose
Output logs
.RS
.RE
.B \-y, \-\-yes
Skip package manager's prompts (experimental)
.SH ARGUMENT FORMAT
Options can be given in any order.
A list of steps must be provided as a list of separate arguments, i.e. 'topgrade --only system shell'.
.SH BUGS
For a list of bugs see <\fIhttps://github.com/r-darwish/topgrade/issues\fR>.
.SH AUTHOR
\fBTopgrade\fR is maintained by Roey Dror (\[aq]r\-darwish\[aq]) and many other contributors.
You can view the full list at
<\fIhttps://github.com/r-darwish/topgrade/graphs/contributors\fR>