Compare commits

...

141 Commits

Author SHA1 Message Date
SteveLauC
96efcc6c0d chore: release v14.0.0 (#652) 2024-01-22 11:13:33 +08:00
SteveLauC
bf72d7bb5a fix: oh-my-zsh step issue #646 (#647) 2024-01-22 09:18:27 +08:00
dependabot[bot]
dadffb1081 chore(deps): bump h2 from 0.3.22 to 0.3.24 (#645)
Bumps [h2](https://github.com/hyperium/h2) from 0.3.22 to 0.3.24.
- [Release notes](https://github.com/hyperium/h2/releases)
- [Changelog](https://github.com/hyperium/h2/blob/v0.3.24/CHANGELOG.md)
- [Commits](https://github.com/hyperium/h2/compare/v0.3.22...v0.3.24)

---
updated-dependencies:
- dependency-name: h2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-20 12:13:19 +08:00
Ned Wolpert
78dc567226 Added an Audit step for FreeBSD and DragonFly packagees. (#640)
* Added an Audit step for FreeBSD and DragonFly.

Allows for auditing the packages to be disabled since they are breaking steps.
Current behaivor is the default, where if the audit fails topgrade stops. Can
be disabled in the [misc] section independenly from other sections
2024-01-08 09:40:01 +08:00
Mike Wood
362ce4f4f9 fix(os) consider Fedora Kinoite and other immutable distros to be the FedoraImmutable (#638)
* fix(os) consider Fedora Kinoite to be the Fedora Silverblue distribution

* fix(os) support additional Fedora immutable variants

Rename FedoraSilverblue Distribution to FedoraImmutable.  Add test cases for Onyx, Sericea and Silverblue.  Rename upgrade method to match distribution.

Fixes #637
2024-01-08 08:48:48 +08:00
Carrol Cox
ab35cd7b10 feat(pipx-update): add quiet flag for pipx upgrade-all on version 1.4.0+ (#635)
This commit introduces conditional logic to the `run_pipx_update` function that checks the installed version of pipx. If the version is 1.4.0 or higher, the `--quiet` argument is added to the `pipx upgrade-all` command to suppress non-critical output during the upgrade process, adhering to the new feature introduced in pipx 1.4.0 as per the documentation (https://pipx.pypa.io/stable/docs/#pipx-upgrade-all). This change aims to make the upgrade process less verbose and more manageable in automated scripts or CI/CD pipelines where log brevity is beneficial.
2023-12-31 11:38:39 +08:00
SteveLauC
15f4ad7cd1 refactor: update pip if extern managed and global.break-system-packages is true (#634)
refactor: update pip if extern managed and global.break-system-packages is true
2023-12-30 18:23:33 +08:00
Rebecca Turner
cbfb92041f Skip nix upgrade-nix when Nix is installed in a nix profile (#622)
Make `nix upgrade-nix` a separate step

Also check that Nix can be upgraded before running `nix upgrade-nix` to
work around a bug.

See: <https://github.com/NixOS/nix/issues/5473>
2023-12-21 08:55:32 +08:00
SteveLauC
a506c67cac fix: remove deprecated brew option '--ignore-pinned' (#629) 2023-12-19 17:09:32 +08:00
SteveLauC
788e0412f6 feat: inform users of breaking changes on first run (#619) 2023-12-03 09:52:35 +08:00
Nils
18b37ce3e3 Update config.example.toml (#621)
Added WinGet setting:
enable_winget = true
2023-11-26 08:06:17 +08:00
Jakob Fels
a15e6748c7 Add option to ignore containers to pull (#613) 2023-11-24 16:44:52 +08:00
SteveLauC
c6d0539fd2 chore(deps): bump all deps (#618) 2023-11-24 07:50:41 +08:00
LeSnake
3eb3867944 Bun packages fixes (#617)
* fix running with --only

* fix error when no packages installed
2023-11-23 06:36:00 +08:00
DomGlusk
810315b0e2 Make zinit and zi use parallel updates (#614)
* Update zsh.rs to make zinit and zi use parallel

* run cargo fmt

---------

Co-authored-by: Dominic Gluskin <rhinoarmyleader@gmail.com>
2023-11-22 11:18:41 +08:00
SteveLauC
b461fc2536 refactor: cleanup for #615 (#616) 2023-11-22 09:34:21 +08:00
Sam Vente
7e63977ba0 revert git pushing functionalities (#615) 2023-11-22 09:04:19 +08:00
SteveLauC
78dec892cf docs: migration and breaking changes (#606) 2023-11-12 11:43:58 +08:00
pacjo
9ea6628b5c docs: fix typo in config.example.toml (#603)
docs(config): fix typo (dfault -> default)
2023-11-10 10:32:15 +08:00
LeSnake
465df2e9be feat: add Bun packages step (#599) 2023-11-05 10:34:21 +08:00
SteveLauC
61ef926849 chore: update issue template label (#596) 2023-11-01 08:57:57 +08:00
SteveLauC
7fa38c593e fix: omz remote execution if ZSH is not present (#592) 2023-10-29 18:05:20 +08:00
SteveLauC
41c6d1cd9a chore: release v13.0.0 (#579) 2023-10-20 08:07:11 +08:00
dependabot[bot]
cf3893dc49 chore(deps): bump rustix from 0.37.20 to 0.37.25 (#586) 2023-10-19 08:38:28 +08:00
SteveLauC
a2fbe92a25 refactor: make SelfUpdate a step (#585) 2023-10-18 12:19:53 +08:00
SteveLauC
e1754707d8 refactor: remove legacy deprecated macros (#583) 2023-10-18 11:13:14 +08:00
SteveLauC
cd380a53b3 docs: new demo video (#584) 2023-10-18 09:33:37 +08:00
SteveLauC
a8c29fd1a2 fix: make logger work while loading config file (#581) 2023-10-17 11:19:47 +08:00
Sam Vente
6b871e7949 switch git push and pull order (#578) 2023-10-15 17:06:40 +08:00
SteveLauC
1b5fdb6645 fix: shellexpand git.pull_only_repos & git.push_only_repos (#576) 2023-10-13 18:54:42 +08:00
Sam Vente
fe9d877cdf Add support for pushing custom git repositories (#574) 2023-10-13 17:01:35 +08:00
SteveLauC
60e7aa8f03 fix: disable dotnet greeting msg with DOTNET_NOLOGO=true (#573) 2023-10-12 14:37:52 +08:00
SteveLauC
18e2d3e59c chore: always use the latest stable toolchain for CI (#571) 2023-10-11 09:46:36 +08:00
Mylloon
d68fcb08b2 fix: Support yes option for opam upgrade (#570) 2023-10-10 08:08:46 +08:00
Zach Crownover
1f6baefdc3 Fix builds and runs on DragonFly BSD (#569) 2023-10-08 08:13:26 +08:00
SteveLauC
71efce32c1 chore: bump CI toolchain to 1.73.0 (#567) 2023-10-06 12:05:44 +08:00
dependabot[bot]
3626c9cdc8 chore(deps): bump webpki from 0.22.1 to 0.22.2 (#564)
Bumps [webpki](https://github.com/briansmith/webpki) from 0.22.1 to 0.22.2.
- [Commits](https://github.com/briansmith/webpki/commits)

---
updated-dependencies:
- dependency-name: webpki
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-03 08:21:00 +08:00
SteveLauC
a23b761304 fix: --yes option for protonup (#560) 2023-09-30 16:44:20 +08:00
SteveLauC
3fd27e4913 chore: add the check for the --yes opt in PR template (#561) 2023-09-30 12:46:01 +08:00
Sohum
b3f152b716 feat(wsl): pass verbose to topgrade-in-wsl (#556)
Closes #521
2023-09-26 11:11:19 +08:00
PabloMarcendo
df381f3a79 feat: add option for nix-env arguments (#555) 2023-09-21 09:05:03 +08:00
dependabot[bot]
2dec9db310 chore(deps): bump webpki from 0.22.0 to 0.22.1 (#554)
Bumps [webpki](https://github.com/briansmith/webpki) from 0.22.0 to 0.22.1.
- [Commits](https://github.com/briansmith/webpki/commits)

---
updated-dependencies:
- dependency-name: webpki
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-20 16:04:27 +08:00
SteveLauC
d50dc4c9f6 chore: bump CI toolchain (#553) 2023-09-20 15:57:35 +08:00
SteveLauC
ed8b563f20 fix: remote oh-my-zsh env var export issue (#528)
* fix: fix remove oh-my-zsh env export issue
2023-09-19 09:15:34 +08:00
Rebecca Turner
2a73aa731d Make error messages nicer (#551)
* Remove unhelpful information from errors

Before:

```
Git repositories failed:
   0: error: cannot pull with rebase: You have unstaged changes.
      error: Please commit or stash them.
   0:

Location:
   src/steps/git.rs:39

Backtrace omitted. Run with RUST_BACKTRACE=1 environment variable to display it.
Run with RUST_BACKTRACE=full to include source snippets.
```

After:

```
Git repositories failed:
   0: Failed to pull /Users/wiggles/.dotfiles
   1: error: cannot pull with rebase: You have unstaged changes.
      error: Please commit or stash them.

Location:
   src/steps/git.rs:39
```

* Improve git_repos errors

This removes the extra blank "0:" line at the end of the error, doesn't
print the error message twice, and provides the repo path in the error
message.
2023-09-19 09:09:58 +08:00
Rebecca Turner
4dd1c13bd8 fix: fix "Nix" step to use nix upgrade-nix in more situations (#550)
`nix upgrade-nix` can be used on any platform except NixOS where `nix`
is available.

Also use `nix profile upgrade --verbose` because the non-verbose mode
doesn't print anything on stdout.
2023-09-17 15:40:04 +08:00
Rebecca Turner
c1c9fe22df feat: allow setting misc.log_filters in config.toml (#552)
Allow setting `log_filters` in `config.toml`

This allows setting a list of `log_filters` in the `[misc]` section in
the `config.toml`. These filters are prepended to any filters listed
with `--log-filters`. Finally, `--verbose` can now be used with
`--log-filters`, and it will append `debug` to the list of filters
rather than replacing it entirely.
2023-09-17 15:04:46 +08:00
SteveLauC
06a6b7a2eb fix: skip needrestart when using nala on debian-based distro (#548) 2023-09-14 18:15:45 +08:00
SteveLauC
b814dd824f chore: bump ci toolchain (#544) 2023-09-01 14:42:12 +08:00
dependabot[bot]
ce234bdb59 chore(deps): bump rustls-webpki from 0.100.1 to 0.100.2 (#542)
Bumps [rustls-webpki](https://github.com/rustls/webpki) from 0.100.1 to 0.100.2.
- [Release notes](https://github.com/rustls/webpki/releases)
- [Commits](https://github.com/rustls/webpki/compare/v/0.100.1...v/0.100.2)

---
updated-dependencies:
- dependency-name: rustls-webpki
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-08-23 09:29:05 +08:00
SteveLauC
13a46a44a8 refactor: deprioritize please-sudo (#541)
refactor: deprioritze please-sudo
2023-08-22 09:14:29 +08:00
SteveLauC
dc78b00c3c feat: support LURE (#537) 2023-08-15 08:36:20 +08:00
samhanic
48ae4bf813 feat: support Vscode packages updates (#536)
feat: support vscode extensions update
2023-08-14 09:22:26 +08:00
SteveLauC
a50040e2d5 chore: add check for dry-run opt in PR template (#538)
chore: add test for dry-run opt in PR template
2023-08-13 10:24:39 +08:00
samhanic
2c9a56a8df feat: support miktex packages update (#535) 2023-08-13 10:05:07 +08:00
Sujay R
021320b292 Prioritize sudo steps to prevent sudo timeout (#532) 2023-08-06 11:32:20 +08:00
SteveLauC
9d3662c3ea chore: add ssh-related questions in issue template (#523) 2023-07-29 09:20:39 +08:00
SteveLauC
8e580457a5 chore: release v12.0.2 (#518) 2023-07-25 14:22:14 +08:00
SteveLauC
5350658dab fix: WSL detection (#508)
* fix: wSL detection
2023-07-25 14:02:13 +08:00
SteveLauC
1ec0ac50a5 fix: fix Linux and DragonFlyBSD yes option (#513) 2023-07-25 08:37:03 +08:00
SteveLauC
635bfce198 feat: extra arguments for Home Manager (#507)
* feat: extra arguments for Home Manager
2023-07-24 13:07:55 +08:00
6543
1307d2d7e8 feat: better error message on wrong os-release file (#511)
* enhancement: better error message when os-release parsing fails
2023-07-24 08:27:13 +08:00
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
59 changed files with 4443 additions and 2146 deletions

View File

@@ -2,32 +2,91 @@
name: Bug report
about: Topgrade is misbehaving
title: ''
labels: ''
labels: 'C-bug'
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?
## What actually happened?
## Additional Details
- Which operating system or Linux distribution are you using?
- How did you install Topgrade?
- Which version are you running? <!-- Check with `topgrade -V` -->
Questions labeled with `Optional` can be skipped.
-->
<!--
Run `topgrade --dry-run` to see which commands Topgrade is running.
If the command seems wrong and you know why please tell us so.
If the command seems fine try to run it yourself and tell us if you got a different result from Topgrade.
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.
-->
## 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
- [ ] No
## Did you run topgrade through `Remote Execution`
- [ ] Yes
- [ ] No
If yes, does the issue still occur when you run topgrade directlly in your
remote host
- [ ] Yes
- [ ] No
## Configuration file (Optional)
<!--
Paste your configuration file inside the code block if you think this issue is
related to configuration.
-->
```toml
```
## Additional Details
- Operation System/Version
<!-- For example, Fedora Linux 38 -->
- Installation
<!--
How did you install topgrade: build from repo / crates.io (cargo install topgrade)
/ package manager (which one) / other (describe)
-->
- Topgrade version (`topgrade -V`)
## Verbose Output (`topgrade -v`)
<!--
Paste the verbose output into the pre-tags
-->
<details>
<!-- Paste the output of the problematic command with `-v` into the pre-tags -->
<pre>
</pre>

View File

@@ -2,7 +2,7 @@
name: Feature request
about: Can you please support...?
title: ''
labels: ''
labels: 'C-feature request'
assignees: ''
---

View File

@@ -1,12 +1,18 @@
## Standards checklist:
- [ ] The PR title is descriptive.
- [ ] I have read `CONTRIBUTING.md`
- [ ] The code compiles (`cargo build`)
- [ ] The code passes rustfmt (`cargo fmt`)
- [ ] The code passes clippy (`cargo clippy`)
- [ ] The code passes tests (`cargo test`)
- [ ] *Optional:* I have tested the code myself
- [ ] I also tested that Topgrade skips the step where needed
## For new steps
- [ ] *Optional:* Topgrade skips this step where needed
- [ ] *Optional:* The `--dry-run` option works with this step
- [ ] *Optional:* The `--yes` option works with this step if it is supported by
the underlying command
If you developed a feature or a bug fix for someone else and you do not have the
means to test it, please tag this person here.

View File

@@ -7,7 +7,7 @@ on:
name: CI
env:
RUST_VER: '1.68.0'
RUST_VER: 'stable'
CROSS_VER: '0.2.5'
CARGO_NET_RETRY: 3

View File

@@ -4,7 +4,7 @@ on:
# types:
# - completed
release:
types: [published, edited]
types: [published]
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 *

12
BREAKINGCHANGES.md Normal file
View File

@@ -0,0 +1,12 @@
1. In 13.0.0, we introduced a new feature, pushing git repos, now this feature
has been removed as some users are not satisfied with it.
For configuration entries, the following ones are gone:
```toml
[git]
pull_only_repos = []
push_only_repos = []
pull_arguments = ""
push_arguments = ""
```

0
BREAKINGCHNAGES_dev.md Normal file
View File

150
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,150 @@
## 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.
## Breaking changes
If your PR introduces a breaking change, document it in `BREAKINGCHANGE_dev.md`,
it should be written in Markdown and wrapped in 80, for example:
```md
1. The configuration location has been updated to x.
2. The step x has been removed.
3. ...
```
## 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.

1817
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,9 +5,9 @@ categories = ["os"]
keywords = ["upgrade", "update"]
license = "GPL-3.0"
repository = "https://github.com/topgrade-rs/topgrade"
version = "10.3.3"
version = "14.0.0"
authors = ["Roey Darwish Dror <roey.ghost@gmail.com>", "Thomas Schönauer <t.schoenauer@hgs-wt.at>"]
exclude = ["doc/screenshot.gif"]
exclude = ["doc/screenshot.gif", "BREAKINGCHNAGES_dev.md"]
edition = "2021"
readme = "README.md"
@@ -21,37 +21,38 @@ path = "src/main.rs"
[dependencies]
home = "~0.5"
directories = "~4.0"
etcetera = "~0.8"
once_cell = "~1.18"
serde = { version = "~1.0", features = ["derive"] }
toml = "0.5"
toml = "0.8"
which_crate = { version = "~4.1", package = "which" }
shellexpand = "~2.1"
clap = { version = "~3.1", features = ["cargo", "derive"] }
clap_complete = "~3.1"
clap_mangen = "~0.1"
walkdir = "~2.3"
shellexpand = "~3.1"
clap = { version = "~4.4", features = ["cargo", "derive"] }
clap_complete = "~4.4"
clap_mangen = "~0.2"
walkdir = "~2.4"
console = "~0.15"
lazy_static = "~1.4"
chrono = "~0.4"
glob = "~0.3"
strum = { version = "~0.24", features = ["derive"] }
thiserror = "~1.0"
tempfile = "~3.2"
tempfile = "~3.8"
cfg-if = "~1.0"
tokio = { version = "~1.18", features = ["process", "rt-multi-thread"] }
tokio = { version = "~1.34", features = ["process", "rt-multi-thread"] }
futures = "~0.3"
regex = "~1.5"
regex = "~1.10"
semver = "~1.0"
shell-words = "~1.1"
color-eyre = "~0.6"
tracing = { version = "~0.1", features = ["attributes", "log"] }
tracing-subscriber = { version = "~0.3", features = ["env-filter", "time"] }
[target.'cfg(target_os = "macos")'.dependencies]
notify-rust = "~4.5"
merge = "~0.1"
regex-split = "~0.1"
notify-rust = "~4.10"
[package.metadata.generate-rpm]
assets = [{source = "target/release/topgrade", dest="/usr/bin/topgrade"}]
assets = [{ source = "target/release/topgrade", dest = "/usr/bin/topgrade" }]
[package.metadata.generate-rpm.requires]
git = "*"
@@ -60,9 +61,8 @@ git = "*"
depends = "$auto,git"
[target.'cfg(unix)'.dependencies]
libc = "~0.2"
nix = "~0.24"
rust-ini = "~0.18"
nix = { version = "~0.27", features = ["hostname", "signal", "user"] }
rust-ini = "~0.19"
self_update_crate = { version = "~0.30", default-features = false, optional = true, package = "self_update", features = ["archive-tar", "compression-flate2", "rustls"] }
[target.'cfg(windows)'.dependencies]

View File

@@ -8,9 +8,10 @@
<a href="https://aur.archlinux.org/packages/topgrade"><img alt="AUR" src="https://img.shields.io/aur/version/topgrade.svg"></a>
<a href="https://formulae.brew.sh/formula/topgrade"><img alt="Homebrew" src="https://img.shields.io/homebrew/v/topgrade.svg"></a>
<img alt="Demo" src="doc/screenshot.gif" width="550px">
<img alt="Demo" src="doc/topgrade_demo.gif">
</div>
## Introduction
> **Note**
@@ -28,31 +29,53 @@ To remedy this, **Topgrade** detects which tools you use and runs the appropriat
- NixOS: [Nixpkgs](https://search.nixos.org/packages?show=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/)
- 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.
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
Just run `topgrade`.
Visit the documentation at [topgrade-rs.github.io](https://topgrade-rs.github.io/) for more information.
> **Warning**
> Work in Progress
## Customization
## Configuration
See `config.example.toml` for an example configuration file.
## Migration and Breaking Changes
Whenever there is a **breaking change**, the major version number will be bumped,
and we will document these changes in the release note, please take a look at
it when updated to a major release.
> Got a question? Feel free to open an issue or discussion!
### 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`
- **macOS** and **other Unix systems** - `${XDG_CONFIG_HOME:-~/.config}/topgrade.toml`
`topgrade` will look for the configuration file in the following places, in order of priority:
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
@@ -78,8 +101,8 @@ Just fork the repository and start coding.
### Contribution Guidelines
- Check if your code passes `cargo fmt` and `cargo clippy`.
- Check if your code is self explanatory, if not it should be documented by comments.
See [CONTRIBUTING.md](https://github.com/topgrade-rs/topgrade/blob/master/CONTRIBUTING.md)
## Roadmap
- [ ] Add a proper testing framework to the code base.

View File

@@ -1,133 +1,230 @@
# Don't ask for confirmations
#assume_yes = true
# 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"]
# Disable specific steps - same options as the command line flag
#disable = ["system", "emacs"]
# Ignore failures for these steps
#ignore_failures = ["powershell"]
# Run specific steps - same options as the command line flag
#only = ["system", "emacs"]
# Do not ask to retry failed steps (default: false)
#no_retry = true
[misc]
# 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
# (default: false)
# pre_sudo = false
# Sudo command to be used
#sudo_command = "sudo"
# 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 a possibly-unattended run.
#pre_sudo = false
# Disable specific steps - same options as the command line flag
# disable = ["system", "emacs"]
# Run inside tmux
#run_in_tmux = true
# Ignore failures for these steps
# ignore_failures = ["powershell"]
# List of remote machines with Topgrade installed on them
#remote_topgrades = ["toothless", "pi", "parnas"]
# Arguments to pass SSH when upgrading remote systems
#ssh_arguments = "-o ConnectTimeout=2"
# remote_topgrades = ["toothless", "pi", "parnas"]
# Path to Topgrade executable on remote machines
#remote_topgrade_path = ".cargo/bin/topgrade"
# remote_topgrade_path = ".cargo/bin/topgrade"
# Arguments to pass to SSH when upgrading remote systems
# ssh_arguments = "-o ConnectTimeout=2"
# Arguments to pass tmux when pulling Repositories
#tmux_arguments = "-S /var/tmux.sock"
# tmux_arguments = "-S /var/tmux.sock"
# Do not set the terminal title
#set_title = false
# Do not set the terminal title (default: true)
# set_title = true
# Display the time in step titles
# Display the time in step titles (default: true)
# display_time = true
# Cleanup temporary or old files
#cleanup = true
# Don't ask for confirmations (no default value)
# assume_yes = true
# Skip sending a notification at the end of a run
#skip_notify = true
# Do not ask to retry failed steps (default: false)
# no_retry = true
# Skip the preamble displayed when topgrade is run
#display_preamble = false
# Run inside tmux (default: false)
# run_in_tmux = true
[git]
#max_concurrency = 5
# Additional git repositories to pull
#repos = [
# "~/src/*/",
# "~/.config/something"
#]
# Cleanup temporary or old files (default: false)
# cleanup = true
# Don't pull the predefined git repos
#pull_predefined = false
# Send a notification for every step (default: false)
# notify_each_step = false
# Arguments to pass Git when pulling Repositories
#arguments = "--rebase --autostash"
# Skip sending a notification at the end of a run (default: false)
# skip_notify = true
# The Bash-it branch to update (default: "stable")
# bashit_branch = "stable"
# Run specific steps - same options as the command line flag
# only = ["system", "emacs"]
# Whether to self update
#
# this will be ignored if the binary is built without self update support
#
# available also via setting the environment variable TOPGRADE_NO_SELF_UPGRADE)
# no_self_update = true
# Extra tracing filter directives
# These are prepended to the `--log-filter` argument
# See: https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives
# log_filters = ["topgrade::command=debug", "warn"]
[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"
# "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"
# "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"
[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
#rpm_ostree = false
#nix_arguments = "--flake"
[python]
#enable_pip_review = true ###disabled by default
#enable_pipupgrade = true ###disabled by default
# 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"
# nix_env_arguments = "--prebuilt-only"
# Extra Home Manager arguments
# home_manager_arguments = ["--flake", "file"]
[git]
# max_concurrency = 5
# Additional git repositories to pull
# repos = [
# "~/src/*/",
# "~/.config/something"
# ]
# Don't pull the predefined git repos
# pull_predefined = false
# Arguments to pass Git when pulling Repositories
# arguments = "--rebase --autostash"
[windows]
# Manually select Windows updates
#accept_all_updates = false
#open_remotes_in_new_terminal = true
#wsl_update_pre_release = true
#wsl_update_use_web_download = true
# accept_all_updates = false
# 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
# to upgrade it. Use this only if you installed Topgrade by using a package
# manager such as Scoop or Cargo
#self_rename = true
# self_rename = true
# Enable WinGet upgrade
# enable_winget = true
[npm]
# 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]
# 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]
# Use sudo for updating the system-wide installation
#use_sudo = true
# use_sudo = true
[distrobox]
#use_root = false
#containers = ["archlinux-latest"]
# use_root = false
# containers = ["archlinux-latest"]
[containers]
# ignored_containers = ["ghcr.io/rancher-sandbox/rancher-desktop/rdx-proxy:latest"]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 718 KiB

BIN
doc/topgrade_demo.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 MiB

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"

156
src/breaking_changes.rs Normal file
View File

@@ -0,0 +1,156 @@
//! Inform the users of the breaking changes introduced in this major release.
//!
//! Print the breaking changes and possibly a migration guide when:
//! 1. The Topgrade being executed is a new major release
//! 2. This is the first launch of that major release
use crate::terminal::print_separator;
#[cfg(windows)]
use crate::WINDOWS_DIRS;
#[cfg(unix)]
use crate::XDG_DIRS;
use color_eyre::eyre::Result;
use etcetera::base_strategy::BaseStrategy;
use std::{
fs::{read_to_string, OpenOptions},
io::Write,
path::PathBuf,
str::FromStr,
};
/// Version string x.y.z
static VERSION_STR: &str = env!("CARGO_PKG_VERSION");
/// Version info
#[derive(Debug)]
pub(crate) struct Version {
_major: u64,
minor: u64,
patch: u64,
}
impl FromStr for Version {
type Err = std::convert::Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
const NOT_SEMVER: &str = "Topgrade version is not semantic";
const NOT_NUMBER: &str = "Topgrade version is not dot-separated numbers";
let mut iter = s.split('.').take(3);
let major = iter.next().expect(NOT_SEMVER).parse().expect(NOT_NUMBER);
let minor = iter.next().expect(NOT_SEMVER).parse().expect(NOT_NUMBER);
let patch = iter.next().expect(NOT_SEMVER).parse().expect(NOT_NUMBER);
// They cannot be all 0s
assert!(
!(major == 0 && minor == 0 && patch == 0),
"Version numbers can not be all 0s"
);
Ok(Self {
_major: major,
minor,
patch,
})
}
}
impl Version {
/// True if this version is a new major release.
pub(crate) fn is_new_major_release(&self) -> bool {
// We have already checked that they cannot all be zeros, so `self.major`
// is guaranteed to be non-zero.
self.minor == 0 && self.patch == 0
}
}
/// Topgrade's breaking changes
///
/// We store them in the compiled binary.
pub(crate) static BREAKINGCHANGES: &str = include_str!("../BREAKINGCHANGES.md");
/// Return platform's data directory.
fn data_dir() -> PathBuf {
#[cfg(unix)]
return XDG_DIRS.data_dir();
#[cfg(windows)]
return WINDOWS_DIRS.data_dir();
}
/// Return Topgrade's keep file path.
///
/// keep file is a file under the data directory containing a major version
/// number, it will be created on first run and is used to check if an execution
/// of Topgrade is the first run of a major release, for more details, see
/// `first_run_of_major_release()`.
fn keep_file_path() -> PathBuf {
let keep_file = "topgrade_keep";
data_dir().join(keep_file)
}
/// True if this is the first execution of a major release.
pub(crate) fn first_run_of_major_release() -> Result<bool> {
let version = VERSION_STR.parse::<Version>().expect("should be a valid version");
let keep_file = keep_file_path();
// disable this lint here as the current code has better readability
#[allow(clippy::collapsible_if)]
if version.is_new_major_release() {
if !keep_file.exists() || read_to_string(&keep_file)? != VERSION_STR {
return Ok(true);
}
}
Ok(false)
}
/// Print breaking changes to the user.
pub(crate) fn print_breaking_changes() {
let header = format!("Topgrade {VERSION_STR} Breaking Changes");
print_separator(header);
let contents = if BREAKINGCHANGES.is_empty() {
"No Breaking changes"
} else {
BREAKINGCHANGES
};
println!("{contents}\n");
}
/// This function will be ONLY executed when the user has confirmed the breaking
/// changes, once confirmed, we write the keep file, which means the first run
/// of this major release is finished.
pub(crate) fn write_keep_file() -> Result<()> {
std::fs::create_dir_all(data_dir())?;
let keep_file = keep_file_path();
let mut file = OpenOptions::new()
.create(true)
.write(true)
.truncate(true)
.open(keep_file)?;
let _ = file.write(VERSION_STR.as_bytes())?;
Ok(())
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn is_new_major_release_works() {
let first_major_release: Version = "1.0.0".parse().unwrap();
let under_dev: Version = "0.1.0".parse().unwrap();
assert!(first_major_release.is_new_major_release());
assert!(!under_dev.is_new_major_release());
}
#[test]
#[should_panic(expected = "Version numbers can not be all 0s")]
fn invalid_version() {
let all_0 = "0.0.0";
all_0.parse::<Version>().unwrap();
}
}

View File

@@ -10,6 +10,8 @@ use color_eyre::eyre::Context;
use crate::error::TopgradeError;
use tracing::debug;
/// Like [`Output`], but UTF-8 decoded.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Utf8Output {
@@ -183,7 +185,7 @@ impl CommandExt for Command {
let err = TopgradeError::ProcessFailedWithOutput(program, output.status, stderr.into_owned());
let ret = Err(err).with_context(|| message);
tracing::debug!("Command failed: {ret:?}");
debug!("Command failed: {ret:?}");
ret
}
}
@@ -203,7 +205,7 @@ impl CommandExt for Command {
let (program, _) = get_program_and_args(self);
let err = TopgradeError::ProcessFailed(program, status);
let ret = Err(err).with_context(|| format!("Command failed: `{command}`"));
tracing::debug!("Command failed: {ret:?}");
debug!("Command failed: {ret:?}");
ret
}
}
@@ -239,6 +241,6 @@ fn format_program_and_args(cmd: &Command) -> String {
fn log(cmd: &Command) -> String {
let command = format_program_and_args(cmd);
tracing::debug!("Executing command `{command}`");
debug!("Executing command `{command}`");
command
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
//! SIGINT handling in Unix systems.
use crate::ctrlc::interrupted::set_interrupted;
use nix::sys::signal;
use nix::sys::signal::{sigaction, SaFlags, SigAction, SigHandler, SigSet, Signal};
/// Handle SIGINT. Set the interruption flag.
extern "C" fn handle_sigint(_: i32) {
@@ -10,12 +10,8 @@ extern "C" fn handle_sigint(_: i32) {
/// Set the necessary signal handlers.
/// The function panics on failure.
pub fn set_handler() {
let sig_action = signal::SigAction::new(
signal::SigHandler::Handler(handle_sigint),
signal::SaFlags::empty(),
signal::SigSet::empty(),
);
let sig_action = SigAction::new(SigHandler::Handler(handle_sigint), SaFlags::empty(), SigSet::empty());
unsafe {
signal::sigaction(signal::SIGINT, &sig_action).unwrap();
sigaction(Signal::SIGINT, &sig_action).unwrap();
}
}

View File

@@ -1,5 +1,6 @@
//! A stub for Ctrl + C handling.
use crate::ctrlc::interrupted::set_interrupted;
use tracing::error;
use winapi::shared::minwindef::{BOOL, DWORD, FALSE, TRUE};
use winapi::um::consoleapi::SetConsoleCtrlHandler;
use winapi::um::wincon::CTRL_C_EVENT;
@@ -16,6 +17,6 @@ extern "system" fn handler(ctrl_type: DWORD) -> BOOL {
pub fn set_handler() {
if 0 == unsafe { SetConsoleCtrlHandler(Some(handler), TRUE) } {
tracing::error!("Cannot set a control C handler")
error!("Cannot set a control C handler")
}
}

View File

@@ -10,14 +10,14 @@ pub enum TopgradeError {
#[error("`{0}` failed: {1}")]
ProcessFailedWithOutput(String, ExitStatus, String),
#[error("Sudo is required for this step")]
#[allow(dead_code)]
SudoRequired,
#[error("Unknown Linux Distribution")]
#[cfg(target_os = "linux")]
UnknownLinuxDistribution,
#[error("File \"/etc/os-release\" does not exist or is empty")]
#[cfg(target_os = "linux")]
EmptyOSReleaseFile,
#[error("Failed getting the system package manager")]
#[cfg(target_os = "linux")]
FailedGettingPackageManager,

View File

@@ -2,10 +2,10 @@
use crate::executor::RunType;
use crate::git::Git;
use crate::sudo::Sudo;
use crate::utils::require_option;
use crate::utils::{require_option, REQUIRE_SUDO};
use crate::{config::Config, executor::Executor};
use color_eyre::eyre::Result;
use directories::BaseDirs;
use std::env::var;
use std::path::Path;
use std::sync::Mutex;
@@ -14,33 +14,29 @@ pub struct ExecutionContext<'a> {
sudo: Option<Sudo>,
git: &'a Git,
config: &'a Config,
base_dirs: &'a BaseDirs,
/// 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
/// tmux window for each remote.
tmux_session: Mutex<Option<String>>,
/// True if topgrade is running under ssh.
under_ssh: bool,
}
impl<'a> ExecutionContext<'a> {
pub fn new(
run_type: RunType,
sudo: Option<Sudo>,
git: &'a Git,
config: &'a Config,
base_dirs: &'a BaseDirs,
) -> Self {
pub fn new(run_type: RunType, sudo: Option<Sudo>, git: &'a Git, config: &'a Config) -> Self {
let under_ssh = var("SSH_CLIENT").is_ok() || var("SSH_TTY").is_ok();
Self {
run_type,
sudo,
git,
config,
base_dirs,
tmux_session: Mutex::new(None),
under_ssh,
}
}
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())?;
Ok(sudo.execute_elevated(self, command, interactive))
}
@@ -60,8 +56,8 @@ impl<'a> ExecutionContext<'a> {
self.config
}
pub fn base_dirs(&self) -> &BaseDirs {
self.base_dirs
pub fn under_ssh(&self) -> bool {
self.under_ssh
}
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::process::{Child, Command, ExitStatus, Output};
use color_eyre::eyre;
use color_eyre::eyre::Result;
use tracing::debug;
@@ -238,7 +237,7 @@ impl CommandExt for Executor {
// TODO: It might be nice to make `output_checked_with` return something that has a
// 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 {
Executor::Wet(c) => c.output_checked_with(succeeded),
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 {
Executor::Wet(c) => c.status_checked_with(succeeded),
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()
}
}

View File

@@ -2,14 +2,22 @@
use std::env;
use std::io;
use std::path::PathBuf;
use std::process::exit;
use std::time::Duration;
use crate::breaking_changes::{first_run_of_major_release, print_breaking_changes, write_keep_file};
use clap::CommandFactory;
use clap::{crate_version, Parser};
use color_eyre::eyre::Context;
use color_eyre::eyre::{eyre, Result};
use color_eyre::eyre::Result;
use console::Key;
use etcetera::base_strategy::BaseStrategy;
#[cfg(windows)]
use etcetera::base_strategy::Windows;
#[cfg(unix)]
use etcetera::base_strategy::Xdg;
use once_cell::sync::Lazy;
use tracing::debug;
use self::config::{CommandLineArgs, Config, Step};
@@ -19,6 +27,9 @@ use self::error::Upgraded;
use self::steps::{remote::*, *};
use self::terminal::*;
use self::utils::{install_color_eyre, install_tracing, update_tracing};
mod breaking_changes;
mod command;
mod config;
mod ctrlc;
@@ -36,28 +47,42 @@ mod sudo;
mod terminal;
mod utils;
pub(crate) static HOME_DIR: Lazy<PathBuf> = Lazy::new(|| home::home_dir().expect("No home directory"));
#[cfg(unix)]
pub(crate) static XDG_DIRS: Lazy<Xdg> = Lazy::new(|| Xdg::new().expect("No home directory"));
#[cfg(windows)]
pub(crate) static WINDOWS_DIRS: Lazy<Windows> = Lazy::new(|| Windows::new().expect("No home directory"));
fn run() -> Result<()> {
color_eyre::install()?;
install_color_eyre()?;
ctrlc::set_handler();
let base_dirs = directories::BaseDirs::new().ok_or_else(|| eyre!("No base directories"))?;
let opt = CommandLineArgs::parse();
// Set up the logger with the filter directives from:
// 1. CLI option `--log-filter`
// 2. `debug` if the `--verbose` option is present
// We do this because we need our logger to work while loading the
// configuration file.
//
// When the configuration file is loaded, update the logger with the full
// filter directives.
//
// For more info, see the comments in `CommandLineArgs::tracing_filter_directives()`
// and `Config::tracing_filter_directives()`.
let reload_handle = install_tracing(&opt.tracing_filter_directives())?;
if let Some(shell) = opt.gen_completion {
let cmd = &mut CommandLineArgs::command();
clap_complete::generate(shell, cmd, clap::crate_name!(), &mut std::io::stdout());
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 std::io::stdout())?;
man.render(&mut io::stdout())?;
return Ok(());
}
install_tracing(&opt.tracing_filter_directives())?;
for env in opt.env_variables() {
let mut splitted = env.split('=');
let var = splitted.next().unwrap();
@@ -66,35 +91,28 @@ fn run() -> Result<()> {
}
if opt.edit_config() {
Config::edit(&base_dirs)?;
Config::edit()?;
return Ok(());
};
if opt.show_config_reference() {
print!("{}", crate::config::EXAMPLE_CONFIG);
print!("{}", config::EXAMPLE_CONFIG);
return Ok(());
}
let config = Config::load(&base_dirs, opt)?;
terminal::set_title(config.set_title());
terminal::display_time(config.display_time());
terminal::set_desktop_notifications(config.notify_each_step());
let config = Config::load(opt)?;
// Update the logger with the full filter directives.
update_tracing(&reload_handle, &config.tracing_filter_directives())?;
set_title(config.set_title());
display_time(config.display_time());
set_desktop_notifications(config.notify_each_step());
debug!("Version: {}", crate_version!());
debug!("OS: {}", env!("TARGET"));
debug!("{:?}", std::env::args());
debug!("Binary path: {:?}", std::env::current_exe());
debug!("Self Update: {:?}", cfg!(feature = "self-update"));
#[cfg(target_os = "linux")]
{
if config.display_preamble() && !config.skip_notify() {
print_warning("Due to a design issue with notify-send it could be that topgrade hangs when it's finished.
If this is the case on your system add the --skip-notify flag to the topgrade command or set skip_notify = true in the config file.
If you don't want this message to appear any longer set display_preamble = false in the config file.
For more information about this issue see https://askubuntu.com/questions/110969/notify-send-ignores-timeout and https://github.com/topgrade-rs/topgrade/issues/288.");
}
}
debug!("self-update Feature Enabled: {:?}", cfg!(feature = "self-update"));
debug!("Configuration: {:?}", config);
if config.run_in_tmux() && env::var("TOPGRADE_INSIDE_TMUX").is_err() {
#[cfg(unix)]
@@ -106,28 +124,38 @@ For more information about this issue see https://askubuntu.com/questions/110969
let git = git::Git::new();
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 = config.sudo_command().map_or_else(sudo::Sudo::detect, sudo::Sudo::new);
let run_type = executor::RunType::new(config.dry_run());
let ctx = execution_context::ExecutionContext::new(run_type, sudo, &git, &config, &base_dirs);
let ctx = execution_context::ExecutionContext::new(run_type, sudo, &git, &config);
let mut runner = runner::Runner::new(&ctx);
// If this is the first execution of a major release, inform user of breaking
// changes
if first_run_of_major_release()? {
print_breaking_changes();
if prompt_yesno("Confirmed?")? {
write_keep_file()?;
} else {
exit(1);
}
}
// Self-Update step, this will execute only if:
// 1. the `self-update` feature is enabled
// 2. it is not disabled from configuration (env var/CLI opt/file)
#[cfg(feature = "self-update")]
{
if !run_type.dry() && env::var("TOPGRADE_NO_SELF_UPGRADE").is_err() {
let result = self_update::self_update();
let should_self_update = env::var("TOPGRADE_NO_SELF_UPGRADE").is_err() && !config.no_self_update();
if let Err(e) = &result {
#[cfg(windows)]
{
if e.downcast_ref::<Upgraded>().is_some() {
return result;
}
}
print_warning(format!("Self update error: {e}"));
}
if should_self_update {
runner.execute(Step::SelfUpdate, "Self Update", || self_update::self_update(&ctx))?;
}
}
@@ -150,28 +178,30 @@ For more information about this issue see https://askubuntu.com/questions/110969
}
}
let powershell = powershell::Powershell::new();
let should_run_powershell = powershell.profile().is_some() && config.should_run(Step::Powershell);
#[cfg(windows)]
runner.execute(Step::Wsl, "WSL", || windows::run_wsl_topgrade(&ctx))?;
#[cfg(windows)]
runner.execute(Step::WslUpdate, "WSL", || windows::update_wsl(&ctx))?;
if let Some(topgrades) = config.remote_topgrades() {
for remote_topgrade in topgrades.iter().filter(|t| config.should_execute_remote(t)) {
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")]
let distribution = linux::Distribution::detect();
#[cfg(target_os = r#"linux"#)]
#[cfg(windows)]
{
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 {
Ok(distribution) => {
runner.execute(Step::System, "System update", || distribution.upgrade(&ctx))?;
@@ -182,16 +212,25 @@ For more information about this issue see https://askubuntu.com/questions/110969
}
runner.execute(Step::ConfigUpdate, "config-update", || linux::run_config_update(&ctx))?;
runner.execute(Step::AM, "am", || linux::run_am(&ctx))?;
runner.execute(Step::AppMan, "appman", || linux::run_appman(&ctx))?;
runner.execute(Step::DebGet, "deb-get", || linux::run_deb_get(&ctx))?;
runner.execute(Step::Toolbx, "toolbx", || toolbx::run_toolbx(&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))?;
runner.execute(Step::Flatpak, "Flatpak", || linux::run_flatpak(&ctx))?;
runner.execute(Step::BrewFormula, "Brew", || {
unix::run_brew_formula(&ctx, unix::BrewVariant::Path)
})?;
}
#[cfg(windows)]
{
runner.execute(Step::Chocolatey, "Chocolatey", || windows::run_chocolatey(&ctx))?;
runner.execute(Step::Scoop, "Scoop", || windows::run_scoop(config.cleanup(), run_type))?;
runner.execute(Step::Winget, "Winget", || windows::run_winget(&ctx))?;
runner.execute(Step::Lure, "LURE", || linux::run_lure_update(&ctx))?;
}
#[cfg(target_os = "macos")]
@@ -215,39 +254,149 @@ For more information about this issue see https://askubuntu.com/questions/110969
unix::run_brew_cask(&ctx, unix::BrewVariant::Path)
})?;
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)
})?;
runner.execute(Step::Audit, "DragonFly Audit", || 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))?;
runner.execute(Step::Audit, "FreeBSD Audit", || 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)]
{
runner.execute(Step::Yadm, "yadm", || unix::run_yadm(&ctx))?;
runner.execute(Step::Nix, "nix", || unix::run_nix(&ctx))?;
runner.execute(Step::Nix, "nix upgrade-nix", || unix::run_nix_self_upgrade(&ctx))?;
runner.execute(Step::Guix, "guix", || unix::run_guix(&ctx))?;
runner.execute(Step::HomeManager, "home-manager", || unix::run_home_manager(run_type))?;
runner.execute(Step::Asdf, "asdf", || unix::run_asdf(run_type))?;
runner.execute(Step::HomeManager, "home-manager", || unix::run_home_manager(&ctx))?;
runner.execute(Step::Asdf, "asdf", || unix::run_asdf(&ctx))?;
runner.execute(Step::Pkgin, "pkgin", || unix::run_pkgin(&ctx))?;
runner.execute(Step::Bun, "bun", || unix::run_bun(&ctx))?;
runner.execute(Step::BunPackages, "bun-packages", || unix::run_bun_packages(&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")]
runner.execute(Step::Pkg, "DragonFly BSD Packages", || {
dragonfly::upgrade_packages(ctx.sudo().as_ref(), run_type)
#[cfg(not(any(
target_os = "freebsd",
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::Vscode, "Visual Studio Code extensions", || {
generic::run_vscode_extensions_upgrade(&ctx)
})?;
#[cfg(target_os = "freebsd")]
runner.execute(Step::Pkg, "FreeBSD Packages", || {
freebsd::upgrade_packages(&ctx, ctx.sudo().as_ref(), run_type)
runner.execute(Step::Conda, "conda", || generic::run_conda_update(&ctx))?;
runner.execute(Step::Mamba, "mamba", || generic::run_mamba_update(&ctx))?;
runner.execute(Step::Miktex, "miktex", || generic::run_miktex_packages_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)
})?;
#[cfg(target_os = "openbsd")]
runner.execute(Step::Pkg, "OpenBSD Packages", || {
openbsd::upgrade_packages(ctx.sudo().as_ref(), run_type)
runner.execute(Step::Pipupgrade, "pipupgrade", || generic::run_pipupgrade_update(&ctx))?;
runner.execute(Step::Ghcup, "ghcup", || generic::run_ghcup_update(&ctx))?;
runner.execute(Step::Stack, "stack", || generic::run_stack_update(&ctx))?;
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 = "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.should_run(Step::Emacs) {
if !emacs.is_doom() {
@@ -255,43 +404,43 @@ For more information about this issue see https://askubuntu.com/questions/110969
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) {
git_repos.insert_if_repo(base_dirs.home_dir().join(".vim"));
git_repos.insert_if_repo(base_dirs.home_dir().join(".config/nvim"));
git_repos.insert_if_repo(HOME_DIR.join(".vim"));
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(base_dirs.home_dir().join(".intellimacs"));
git_repos.insert_if_repo(HOME_DIR.join(".ideavimrc"));
git_repos.insert_if_repo(HOME_DIR.join(".intellimacs"));
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)]
{
git_repos.insert_if_repo(zsh::zshrc(&base_dirs));
git_repos.insert_if_repo(zsh::zshrc());
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(base_dirs.config_dir().join("openbox"));
git_repos.insert_if_repo(base_dirs.config_dir().join("bspwm"));
git_repos.insert_if_repo(base_dirs.config_dir().join("i3"));
git_repos.insert_if_repo(base_dirs.config_dir().join("sway"));
git_repos.insert_if_repo(HOME_DIR.join(".config/fish"));
git_repos.insert_if_repo(XDG_DIRS.config_dir().join("openbox"));
git_repos.insert_if_repo(XDG_DIRS.config_dir().join("bspwm"));
git_repos.insert_if_repo(XDG_DIRS.config_dir().join("i3"));
git_repos.insert_if_repo(XDG_DIRS.config_dir().join("sway"));
}
#[cfg(windows)]
git_repos.insert_if_repo(
base_dirs
.data_local_dir()
WINDOWS_DIRS
.cache_dir()
.join("Packages/Microsoft.WindowsTerminal_8wekyb3d8bbwe/LocalState"),
);
#[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() {
git_repos.insert_if_repo(profile);
@@ -315,115 +464,6 @@ For more information about this issue see https://askubuntu.com/questions/110969
})?;
}
#[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, "antidote", || zsh::run_antidote(&ctx))?;
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::Juliaup, "juliaup", || generic::run_juliaup(&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(&ctx))?;
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::PipReview, "pip-review", || generic::run_pip_review_update(&ctx))?;
runner.execute(Step::Pipupgrade, "pipupgrade", || generic::run_pipupgrade_update(&ctx))?;
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::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(run_type))?;
runner.execute(Step::Helm, "helm", || generic::run_helm_repo_update(run_type))?;
runner.execute(Step::Gem, "gem", || generic::run_gem(&base_dirs, run_type))?;
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::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::AM, "am", || linux::update_am(&ctx))?;
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(ctx.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))?;
runner.execute(Step::DkpPacman, "dkp-pacman", || linux::run_dkp_pacman_update(&ctx))?;
}
if let Some(commands) = config.commands() {
for (name, command) in commands {
if config.should_run_custom_command(name) {
@@ -434,37 +474,6 @@ For more information about this issue see https://askubuntu.com/questions/110969
}
}
#[cfg(target_os = "linux")]
{
runner.execute(Step::System, "pihole", || {
linux::run_pihole_update(ctx.sudo().as_ref(), run_type)
})?;
runner.execute(Step::Firmware, "Firmware upgrades", || linux::run_fwupdmgr(&ctx))?;
runner.execute(Step::Restarts, "Restarts", || {
linux::run_needrestart(ctx.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(ctx.sudo().as_ref(), run_type)
})?;
#[cfg(target_os = "openbsd")]
runner.execute(Step::System, "OpenBSD Upgrade", || {
openbsd::upgrade_openbsd(ctx.sudo().as_ref(), run_type)
})?;
#[cfg(windows)]
runner.execute(Step::System, "Windows update", || windows::windows_update(&ctx))?;
if config.should_run(Step::Vagrant) {
if let Ok(boxes) = vagrant::collect_boxes(&ctx) {
for vagrant_box in boxes {
@@ -489,12 +498,6 @@ For more information about this issue see https://askubuntu.com/questions/110969
distribution.show_summary();
}
}
#[cfg(target_os = "freebsd")]
freebsd::audit_packages(ctx.sudo().as_ref()).ok();
#[cfg(target_os = "dragonfly")]
dragonfly::audit_packages(ctx.sudo().as_ref()).ok();
}
let mut post_command_failed = false;
@@ -528,7 +531,7 @@ For more information about this issue see https://askubuntu.com/questions/110969
let failed = post_command_failed || runner.report().data().iter().any(|(_, result)| result.failed());
if !config.skip_notify() {
terminal::notify_desktop(
notify_desktop(
format!(
"Topgrade finished {}",
if failed { "with errors" } else { "successfully" }
@@ -573,26 +576,3 @@ fn main() {
}
}
}
pub fn install_tracing(filter_directives: &str) -> Result<()> {
use tracing_subscriber::fmt;
use tracing_subscriber::fmt::format::FmtSpan;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
use tracing_subscriber::EnvFilter;
let env_filter = EnvFilter::try_new(filter_directives)
.or_else(|_| EnvFilter::try_from_default_env())
.or_else(|_| EnvFilter::try_new("info"))?;
let fmt_layer = fmt::layer()
.with_target(false)
.with_span_events(FmtSpan::NEW | FmtSpan::CLOSE)
.without_time();
let registry = tracing_subscriber::registry();
registry.with(env_filter).with(fmt_layer).init();
Ok(())
}

View File

@@ -3,6 +3,7 @@ use std::env;
use std::os::unix::process::CommandExt as _;
use std::process::Command;
use crate::config::Step;
use color_eyre::eyre::{bail, Result};
use self_update_crate::backends::github::Update;
use self_update_crate::update::UpdateStatus;
@@ -11,8 +12,16 @@ use super::terminal::*;
#[cfg(windows)]
use crate::error::Upgraded;
pub fn self_update() -> Result<()> {
use crate::execution_context::ExecutionContext;
pub fn self_update(ctx: &ExecutionContext) -> Result<()> {
print_separator("Self update");
if ctx.run_type().dry() {
println!("Would self-update");
Ok(())
} else {
let assume_yes = ctx.config().yes(Step::SelfUpdate);
let current_exe = env::current_exe();
let target = self_update_crate::get_target();
@@ -21,10 +30,10 @@ pub fn self_update() -> Result<()> {
.repo_name("topgrade")
.target(target)
.bin_name(if cfg!(windows) { "topgrade.exe" } else { "topgrade" })
.show_output(false)
.show_output(true)
.show_download_progress(true)
.current_version(self_update_crate::cargo_crate_version!())
.no_confirm(true)
.no_confirm(assume_yes)
.build()?
.update_extended()?;
@@ -39,7 +48,7 @@ pub fn self_update() -> Result<()> {
{
if result.updated() {
print_warning("Respawning...");
print_info("Respawning...");
let mut command = Command::new(current_exe?);
command.args(env::args().skip(1)).env("TOPGRADE_NO_SELF_UPGRADE", "");
@@ -59,4 +68,5 @@ pub fn self_update() -> Result<()> {
}
Ok(())
}
}

View File

@@ -1,3 +1,4 @@
use std::fmt::{Display, Formatter};
use std::path::Path;
use std::process::Command;
@@ -18,15 +19,44 @@ use crate::{execution_context::ExecutionContext, utils::require};
// themselves or when using docker-compose.
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
/// "REGISTRY/[PATH/]CONTAINER_NAME:TAG"
fn list_containers(crt: &Path) -> Result<Vec<String>> {
///
/// Containers specified in `ignored_containers` will be filtered out.
fn list_containers(crt: &Path, ignored_containers: Option<&Vec<String>>) -> Result<Vec<Container>> {
debug!(
"Querying '{} image ls --format \"{{{{.Repository}}}}:{{{{.Tag}}}}\"' for containers",
"Querying '{} image ls --format \"{{{{.Repository}}}}:{{{{.Tag}}}}/{{{{.ID}}}}\"' for containers",
crt.display()
);
let output = Command::new(crt)
.args(["image", "ls", "--format", "{{.Repository}}:{{.Tag}}"])
.args(["image", "ls", "--format", "{{.Repository}}:{{.Tag}} {{.ID}}"])
.output_checked_with_utf8(|_| Ok(()))?;
let mut retval = vec![];
@@ -49,7 +79,36 @@ fn list_containers(crt: &Path) -> Result<Vec<String>> {
}
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]);
if let Some(ignored_containers) = ignored_containers {
if ignored_containers
.iter()
.any(|ignored_container| repo_tag.eq(ignored_container))
{
debug!("Skipping ignored container '{}'", line);
continue;
}
}
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)
@@ -62,12 +121,18 @@ pub fn run_containers(ctx: &ExecutionContext) -> Result<()> {
print_separator("Containers");
let mut success = true;
let containers = list_containers(&crt).context("Failed to list Docker containers")?;
let containers =
list_containers(&crt, ctx.config().containers_ignored_tags()).context("Failed to list Docker containers")?;
debug!("Containers to inspect: {:?}", containers);
for container in containers.iter() {
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);
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::path::{Path, PathBuf};
use color_eyre::eyre::Result;
use directories::BaseDirs;
use etcetera::base_strategy::BaseStrategy;
use crate::command::CommandExt;
use crate::execution_context::ExecutionContext;
@@ -23,20 +23,12 @@ pub struct Emacs {
}
impl Emacs {
fn directory_path(base_dirs: &BaseDirs) -> Option<PathBuf> {
fn directory_path() -> Option<PathBuf> {
#[cfg(unix)]
cfg_if::cfg_if! {
if #[cfg(target_os = "macos")] {
let emacs_xdg_dir = env::var("XDG_CONFIG_HOME")
.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);
return {
let emacs_xdg_dir = crate::XDG_DIRS.config_dir().join("emacs").if_exists();
crate::HOME_DIR.join(".emacs.d").if_exists().or(emacs_xdg_dir)
};
#[cfg(windows)]
return env::var("HOME")
@@ -47,11 +39,11 @@ impl 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 {
let directory = Emacs::directory_path(base_dirs);
pub fn new() -> Self {
let directory = Emacs::directory_path();
let doom = directory.as_ref().and_then(|d| d.join(DOOM_PATH).if_exists());
Self { directory, doom }
}

View File

@@ -8,15 +8,17 @@ use std::{fs, io::Write};
use color_eyre::eyre::eyre;
use color_eyre::eyre::Context;
use color_eyre::eyre::Result;
use directories::BaseDirs;
use semver::Version;
use tempfile::tempfile_in;
use tracing::{debug, error};
use crate::command::{CommandExt, Utf8Output};
use crate::execution_context::ExecutionContext;
use crate::executor::{ExecutorOutput, RunType};
use crate::executor::ExecutorOutput;
use crate::terminal::{print_separator, shell};
use crate::utils::{self, require, require_option, which, 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::{
error::{SkipStep, StepFailed, TopgradeError},
terminal::print_warning,
@@ -25,9 +27,9 @@ use crate::{
pub fn run_cargo_update(ctx: &ExecutionContext) -> Result<()> {
let cargo_dir = env::var_os("CARGO_HOME")
.map(PathBuf::from)
.unwrap_or_else(|| ctx.base_dirs().home_dir().join(".cargo"))
.unwrap_or_else(|| HOME_DIR.join(".cargo"))
.require()?;
utils::require("cargo").or_else(|_| {
require("cargo").or_else(|_| {
require_option(
cargo_dir.join("bin/cargo").if_exists(),
String::from("No cargo detected"),
@@ -41,7 +43,7 @@ pub fn run_cargo_update(ctx: &ExecutionContext) -> Result<()> {
}
print_separator("Cargo");
let cargo_update = utils::require("cargo-install-update")
let cargo_update = require("cargo-install-update")
.ok()
.or_else(|| cargo_dir.join("bin/cargo-install-update").if_exists());
let cargo_update = match cargo_update {
@@ -59,7 +61,7 @@ pub fn run_cargo_update(ctx: &ExecutionContext) -> Result<()> {
.status_checked()?;
if ctx.config().cleanup() {
let cargo_cache = utils::require("cargo-cache")
let cargo_cache = require("cargo-cache")
.ok()
.or_else(|| cargo_dir.join("bin/cargo-cache").if_exists());
match cargo_cache {
@@ -76,20 +78,20 @@ pub fn run_cargo_update(ctx: &ExecutionContext) -> Result<()> {
Ok(())
}
pub fn run_flutter_upgrade(run_type: RunType) -> Result<()> {
let flutter = utils::require("flutter")?;
pub fn run_flutter_upgrade(ctx: &ExecutionContext) -> Result<()> {
let flutter = require("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<()> {
let gem = utils::require("gem")?;
base_dirs.home_dir().join(".gem").require()?;
pub fn run_gem(ctx: &ExecutionContext) -> Result<()> {
let gem = require("gem")?;
HOME_DIR.join(".gem").require()?;
print_separator("Gems");
let mut command = run_type.execute(gem);
let mut command = ctx.run_type().execute(gem);
command.arg("update");
if env::var_os("RBENV_SHELL").is_none() {
@@ -101,7 +103,7 @@ pub fn run_gem(base_dirs: &BaseDirs, run_type: RunType) -> Result<()> {
}
pub fn run_rubygems(ctx: &ExecutionContext) -> Result<()> {
ctx.base_dirs().home_dir().join(".gem").require()?;
HOME_DIR.join(".gem").require()?;
let gem = require("gem")?;
print_separator("RubyGems");
@@ -111,8 +113,9 @@ pub fn run_rubygems(ctx: &ExecutionContext) -> Result<()> {
.execute(gem)
.args(["update", "--system"])
.status_checked()?;
} else if let Some(sudo) = &ctx.sudo() {
if !std::path::Path::new("/usr/lib/ruby/vendor_ruby/rubygems/defaults/operating_system.rb").exists() {
} 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")
@@ -120,14 +123,13 @@ pub fn run_rubygems(ctx: &ExecutionContext) -> Result<()> {
.args(["update", "--system"])
.status_checked()?;
}
} else {
print_warning("No sudo detected. Skipping system upgrade");
}
Ok(())
}
pub fn run_haxelib_update(ctx: &ExecutionContext) -> Result<()> {
let haxelib = utils::require("haxelib")?;
let haxelib = require("haxelib")?;
let haxelib_dir =
PathBuf::from(std::str::from_utf8(&Command::new(&haxelib).arg("config").output_checked()?.stdout)?.trim())
@@ -141,9 +143,8 @@ pub fn run_haxelib_update(ctx: &ExecutionContext) -> Result<()> {
let mut command = if directory_writable {
ctx.run_type().execute(&haxelib)
} else {
let mut c = ctx
.run_type()
.execute(ctx.sudo().as_ref().ok_or(TopgradeError::SudoRequired)?);
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
let mut c = ctx.run_type().execute(sudo);
c.arg(&haxelib);
c
};
@@ -152,7 +153,7 @@ pub fn run_haxelib_update(ctx: &ExecutionContext) -> Result<()> {
}
pub fn run_sheldon(ctx: &ExecutionContext) -> Result<()> {
let sheldon = utils::require("sheldon")?;
let sheldon = require("sheldon")?;
print_separator("Sheldon");
@@ -162,20 +163,21 @@ pub fn run_sheldon(ctx: &ExecutionContext) -> Result<()> {
.status_checked()
}
pub fn run_fossil(run_type: RunType) -> Result<()> {
let fossil = utils::require("fossil")?;
pub fn run_fossil(ctx: &ExecutionContext) -> Result<()> {
let fossil = require("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<()> {
let micro = utils::require("micro")?;
pub fn run_micro(ctx: &ExecutionContext) -> Result<()> {
let micro = require("micro")?;
print_separator("micro");
let stdout = run_type
let stdout = ctx
.run_type()
.execute(micro)
.args(["-plugin", "update"])
.output_checked_utf8()?
@@ -195,43 +197,41 @@ pub fn run_micro(run_type: RunType) -> Result<()> {
target_os = "netbsd",
target_os = "dragonfly"
)))]
pub fn run_apm(run_type: RunType) -> Result<()> {
let apm = utils::require("apm")?;
pub fn run_apm(ctx: &ExecutionContext) -> Result<()> {
let apm = require("apm")?;
print_separator("Atom Package Manager");
run_type
ctx.run_type()
.execute(apm)
.args(["upgrade", "--confirm=false"])
.status_checked()
}
pub fn run_rustup(base_dirs: &BaseDirs, run_type: RunType) -> Result<()> {
let rustup = utils::require("rustup")?;
pub fn run_rustup(ctx: &ExecutionContext) -> Result<()> {
let rustup = require("rustup")?;
print_separator("rustup");
if rustup.canonicalize()?.is_descendant_of(base_dirs.home_dir()) {
run_type.execute(&rustup).args(["self", "update"]).status_checked()?;
}
run_type.execute(&rustup).arg("update").status_checked()
ctx.run_type().execute(rustup).arg("update").status_checked()
}
pub fn run_juliaup(base_dirs: &BaseDirs, run_type: RunType) -> Result<()> {
let juliaup = utils::require("juliaup")?;
pub fn run_juliaup(ctx: &ExecutionContext) -> Result<()> {
let juliaup = require("juliaup")?;
print_separator("juliaup");
if juliaup.canonicalize()?.is_descendant_of(base_dirs.home_dir()) {
run_type.execute(&juliaup).args(["self", "update"]).status_checked()?;
if juliaup.canonicalize()?.is_descendant_of(&HOME_DIR) {
ctx.run_type()
.execute(&juliaup)
.args(["self", "update"])
.status_checked()?;
}
run_type.execute(&juliaup).arg("update").status_checked()
ctx.run_type().execute(&juliaup).arg("update").status_checked()
}
pub fn run_choosenim(ctx: &ExecutionContext) -> Result<()> {
let choosenim = utils::require("choosenim")?;
let choosenim = require("choosenim")?;
print_separator("choosenim");
let run_type = ctx.run_type();
@@ -240,39 +240,42 @@ pub fn run_choosenim(ctx: &ExecutionContext) -> Result<()> {
run_type.execute(&choosenim).args(["update", "stable"]).status_checked()
}
pub fn run_krew_upgrade(run_type: RunType) -> Result<()> {
let krew = utils::require("kubectl-krew")?;
pub fn run_krew_upgrade(ctx: &ExecutionContext) -> Result<()> {
let krew = require("kubectl-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<()> {
let gcloud = utils::require("gcloud")?;
pub fn run_gcloud_components_update(ctx: &ExecutionContext) -> Result<()> {
let gcloud = require("gcloud")?;
if gcloud.starts_with("/snap") {
Ok(())
} else {
print_separator("gcloud");
run_type
ctx.run_type()
.execute(gcloud)
.args(["components", "update", "--quiet"])
.status_checked()
}
}
pub fn run_jetpack(run_type: RunType) -> Result<()> {
let jetpack = utils::require("jetpack")?;
pub fn run_jetpack(ctx: &ExecutionContext) -> Result<()> {
let jetpack = require("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<()> {
let rupdate = utils::require("rupdate")?;
let rupdate = require("rupdate")?;
print_separator("rtcl");
@@ -280,12 +283,18 @@ pub fn run_rtcl(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");
ctx.run_type().execute(&opam).arg("update").status_checked()?;
ctx.run_type().execute(&opam).arg("upgrade").status_checked()?;
let mut command = ctx.run_type().execute(&opam);
command.arg("upgrade");
if ctx.config().yes(Step::Opam) {
command.arg("--yes");
}
command.status_checked()?;
if ctx.config().cleanup() {
ctx.run_type().execute(&opam).arg("clean").status_checked()?;
@@ -295,7 +304,7 @@ pub fn run_opam_update(ctx: &ExecutionContext) -> Result<()> {
}
pub fn run_vcpkg_update(ctx: &ExecutionContext) -> Result<()> {
let vcpkg = utils::require("vcpkg")?;
let vcpkg = require("vcpkg")?;
print_separator("vcpkg");
#[cfg(unix)]
@@ -307,9 +316,8 @@ pub fn run_vcpkg_update(ctx: &ExecutionContext) -> Result<()> {
let mut command = if is_root_install {
ctx.run_type().execute(&vcpkg)
} else {
let mut c = ctx
.run_type()
.execute(ctx.sudo().as_ref().ok_or(TopgradeError::SudoRequired)?);
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
let mut c = ctx.run_type().execute(sudo);
c.arg(&vcpkg);
c
};
@@ -317,15 +325,52 @@ pub fn run_vcpkg_update(ctx: &ExecutionContext) -> Result<()> {
command.args(["upgrade", "--no-dry-run"]).status_checked()
}
pub fn run_pipx_update(run_type: RunType) -> Result<()> {
let pipx = utils::require("pipx")?;
pub fn run_vscode_extensions_upgrade(ctx: &ExecutionContext) -> Result<()> {
let vscode = require("code")?;
print_separator("Visual Studio Code extensions");
// Vscode does not have CLI command to upgrade all extensions (see https://github.com/microsoft/vscode/issues/56578)
// Instead we get the list of installed extensions with `code --list-extensions` command (obtain a line-return separated list of installed extensions)
let extensions = Command::new(&vscode)
.arg("--list-extensions")
.output_checked_utf8()?
.stdout;
// Then we construct the upgrade command: `code --force --install-extension [ext0] --install-extension [ext1] ... --install-extension [extN]`
if !extensions.is_empty() {
let mut command_args = vec!["--force"];
for extension in extensions.split_whitespace() {
command_args.extend(["--install-extension", extension]);
}
ctx.run_type().execute(&vscode).args(command_args).status_checked()?;
}
Ok(())
}
pub fn run_pipx_update(ctx: &ExecutionContext) -> Result<()> {
let pipx = require("pipx")?;
print_separator("pipx");
run_type.execute(pipx).arg("upgrade-all").status_checked()
let mut command_args = vec!["upgrade-all"];
// pipx version 1.4.0 introduced a new command argument `pipx upgrade-all --quiet`
// (see https://pipx.pypa.io/stable/docs/#pipx-upgrade-all)
let version_str = Command::new("pipx")
.args(["--version"])
.output_checked_utf8()
.map(|s| s.stdout.trim().to_owned());
let version = Version::parse(&version_str?);
if matches!(version, Ok(version) if version >= Version::new(1, 4, 0)) {
command_args.push("--quiet")
}
ctx.run_type().execute(pipx).args(command_args).status_checked()
}
pub fn run_conda_update(ctx: &ExecutionContext) -> Result<()> {
let conda = utils::require("conda")?;
let conda = require("conda")?;
let output = Command::new("conda")
.args(["config", "--show", "auto_activate_base"])
@@ -337,41 +382,118 @@ pub fn run_conda_update(ctx: &ExecutionContext) -> Result<()> {
print_separator("Conda");
let mut command = ctx.run_type().execute(conda);
command.args(["update", "--all", "-n", "base"]);
if ctx.config().yes(Step::Conda) {
command.arg("--yes");
}
command.status_checked()
}
pub fn run_mamba_update(ctx: &ExecutionContext) -> Result<()> {
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_miktex_packages_update(ctx: &ExecutionContext) -> Result<()> {
let miktex = require("miktex")?;
print_separator("miktex");
ctx.run_type()
.execute(conda)
.args(["update", "--all", "-y"])
.execute(miktex)
.args(["packages", "update"])
.status_checked()
}
pub fn run_pip3_update(run_type: RunType) -> Result<()> {
let python3 = utils::require("python3")?;
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)
.args(["-m", "pip"])
.output_checked_utf8()
.map_err(|_| SkipStep("pip does not exists".to_string()))?;
.map_err(|_| SkipStep("pip does not exist".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])
let check_extern_managed_script = "import sysconfig; from os import path; print('Y') if path.isfile(path.join(sysconfig.get_path('stdlib'), 'EXTERNALLY-MANAGED')) else print('N')";
let output = Command::new(&python3)
.args(["-c", check_extern_managed_script])
.output_checked_utf8()?;
let stdout = output.stdout.trim();
let extern_managed = match stdout {
"N" => false,
"Y" => true,
_ => unreachable!("unexpected output from `check_extern_managed_script`"),
};
let allow_break_sys_pkg = match Command::new(&python3)
.args(["-m", "pip", "config", "get", "global.break-system-packages"])
.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()))
{
Ok(output) => {
let stdout = output.stdout.trim();
stdout
.parse::<bool>()
.expect("unexpected output that is not `true` or `false`")
}
// it can fail because this key may not be set
//
// ```sh
// $ pip --version
// pip 23.0.1 from /usr/lib/python3/dist-packages/pip (python 3.11)
//
// $ pip config get global.break-system-packages
// ERROR: No such key - global.break-system-packages
//
// $ echo $?
// 1
// ```
Err(_) => false,
};
debug!("pip3 externally managed: {} ", extern_managed);
debug!("pip3 global.break-system-packages: {}", allow_break_sys_pkg);
// Even though pip3 is externally managed, we should still update it if
// `global.break-system-packages` is true.
if extern_managed && !allow_break_sys_pkg {
return Err(SkipStep(
"Skip pip3 update as it is externally managed and global.break-system-packages is not true".to_string(),
)
.into());
}
})?;
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");
return Err(SkipStep("Does not run inside a virtual environment".to_string()).into());
}
run_type
ctx.run_type()
.execute(&python3)
.args(["-m", "pip", "install", "--upgrade", "--user", "pip"])
.status_checked()
@@ -395,40 +517,61 @@ pub fn run_pip_review_update(ctx: &ExecutionContext) -> Result<()> {
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_pip_review() {
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).status_checked()?;
ctx.run_type()
.execute(pipupgrade)
.args(ctx.config().pipupgrade_arguments().split_whitespace())
.status_checked()?;
Ok(())
}
pub fn run_stack_update(run_type: RunType) -> Result<()> {
if utils::require("ghcup").is_ok() {
pub fn run_stack_update(ctx: &ExecutionContext) -> Result<()> {
if require("ghcup").is_ok() {
// `ghcup` is present and probably(?) being used to 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' !!!
return Ok(());
}
let stack = utils::require("stack")?;
let stack = require("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<()> {
let ghcup = utils::require("ghcup")?;
pub fn run_ghcup_update(ctx: &ExecutionContext) -> Result<()> {
let ghcup = require("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<()> {
@@ -440,8 +583,8 @@ pub fn run_tlmgr_update(ctx: &ExecutionContext) -> Result<()> {
}
}
let tlmgr = utils::require("tlmgr")?;
let kpsewhich = utils::require("kpsewhich")?;
let tlmgr = require("tlmgr")?;
let kpsewhich = require("kpsewhich")?;
let tlmgr_directory = {
let mut d = PathBuf::from(
&Command::new(kpsewhich)
@@ -463,9 +606,8 @@ pub fn run_tlmgr_update(ctx: &ExecutionContext) -> Result<()> {
let mut command = if directory_writable {
ctx.run_type().execute(&tlmgr)
} else {
let mut c = ctx
.run_type()
.execute(ctx.sudo().as_ref().ok_or(TopgradeError::SudoRequired)?);
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
let mut c = ctx.run_type().execute(sudo);
c.arg(&tlmgr);
c
};
@@ -474,42 +616,50 @@ pub fn run_tlmgr_update(ctx: &ExecutionContext) -> Result<()> {
command.status_checked()
}
pub fn run_chezmoi_update(base_dirs: &BaseDirs, run_type: RunType) -> Result<()> {
let chezmoi = utils::require("chezmoi")?;
base_dirs.home_dir().join(".local/share/chezmoi").require()?;
pub fn run_chezmoi_update(ctx: &ExecutionContext) -> Result<()> {
let chezmoi = require("chezmoi")?;
HOME_DIR.join(".local/share/chezmoi").require()?;
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<()> {
let myrepos = utils::require("mr")?;
base_dirs.home_dir().join(".mrconfig").require()?;
pub fn run_myrepos_update(ctx: &ExecutionContext) -> Result<()> {
let myrepos = require("mr")?;
HOME_DIR.join(".mrconfig").require()?;
print_separator("myrepos");
run_type
ctx.run_type()
.execute(&myrepos)
.arg("--directory")
.arg(base_dirs.home_dir())
.arg(&*HOME_DIR)
.arg("checkout")
.status_checked()?;
run_type
ctx.run_type()
.execute(&myrepos)
.arg("--directory")
.arg(base_dirs.home_dir())
.arg(&*HOME_DIR)
.arg("update")
.status_checked()
}
pub fn run_custom_command(name: &str, command: &str, ctx: &ExecutionContext) -> Result<()> {
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<()> {
let composer = utils::require("composer")?;
let composer = require("composer")?;
let composer_home = Command::new(&composer)
.args(["global", "config", "--absolute", "--quiet", "home"])
.output_checked_utf8()
@@ -517,7 +667,7 @@ pub fn run_composer_update(ctx: &ExecutionContext) -> Result<()> {
.map(|s| PathBuf::from(s.stdout.trim()))?
.require()?;
if !composer_home.is_descendant_of(ctx.base_dirs().home_dir()) {
if !composer_home.is_descendant_of(&HOME_DIR) {
return Err(SkipStep(format!(
"Composer directory {} isn't a decandent of the user's home directory",
composer_home.display()
@@ -537,8 +687,9 @@ pub fn run_composer_update(ctx: &ExecutionContext) -> Result<()> {
};
if has_update {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
ctx.run_type()
.execute(ctx.sudo().as_ref().unwrap())
.execute(sudo)
.arg(&composer)
.arg("self-update")
.status_checked()?;
@@ -554,7 +705,7 @@ pub fn run_composer_update(ctx: &ExecutionContext) -> Result<()> {
let output: Utf8Output = output.try_into()?;
print!("{}\n{}", output.stdout, output.stderr);
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()?;
}
}
@@ -564,13 +715,18 @@ pub fn run_composer_update(ctx: &ExecutionContext) -> Result<()> {
}
pub fn run_dotnet_upgrade(ctx: &ExecutionContext) -> Result<()> {
let dotnet = utils::require("dotnet")?;
let dotnet = require("dotnet")?;
//Skip when the `dotnet tool list` subcommand fails. (This is expected when a dotnet runtime is installed but no SDK.)
// 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"])
// dotnet will print a greeting message on its first run, from this question:
// https://stackoverflow.com/q/70493706/14092446
// Setting `DOTNET_NOLOGO` to `true` should disable it
.env("DOTNET_NOLOGO", "true")
.output_checked_utf8()
{
Ok(output) => output,
@@ -582,11 +738,20 @@ pub fn run_dotnet_upgrade(ctx: &ExecutionContext) -> Result<()> {
}
};
if !output.stdout.starts_with("Package Id") {
return Err(SkipStep(String::from("dotnet did not output packages")).into());
}
let mut packages = output.stdout.lines().skip(2).filter(|line| !line.is_empty()).peekable();
let mut packages = output
.stdout
.lines()
// Skip the header:
//
// 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() {
return Err(SkipStep(String::from("No dotnet global tools installed")).into());
@@ -607,18 +772,19 @@ pub fn run_dotnet_upgrade(ctx: &ExecutionContext) -> Result<()> {
}
pub fn run_helix_grammars(ctx: &ExecutionContext) -> Result<()> {
utils::require("helix")?;
require("helix")?;
print_separator("Helix");
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
ctx.run_type()
.execute(ctx.sudo().as_ref().ok_or(TopgradeError::SudoRequired)?)
.execute(sudo)
.args(["helix", "--grammar", "fetch"])
.status_checked()
.with_context(|| "Failed to download helix grammars!")?;
ctx.run_type()
.execute(ctx.sudo().as_ref().ok_or(TopgradeError::SudoRequired)?)
.execute(sudo)
.args(["helix", "--grammar", "build"])
.status_checked()
.with_context(|| "Failed to build helix grammars!")?;
@@ -626,30 +792,34 @@ pub fn run_helix_grammars(ctx: &ExecutionContext) -> Result<()> {
Ok(())
}
pub fn run_raco_update(run_type: RunType) -> Result<()> {
let raco = utils::require("raco")?;
pub fn run_raco_update(ctx: &ExecutionContext) -> Result<()> {
let raco = require("raco")?;
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<()> {
let bin = utils::require("bin")?;
let bin = require("bin")?;
print_separator("Bin");
ctx.run_type().execute(bin).arg("update").status_checked()
}
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");
ctx.run_type().execute(spicetify).arg("upgrade").status_checked()
}
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();
if result.is_err() {
debug!("GH result {:?}", result);
@@ -664,7 +834,7 @@ pub fn run_ghcli_extensions_upgrade(ctx: &ExecutionContext) -> Result<()> {
}
pub fn update_julia_packages(ctx: &ExecutionContext) -> Result<()> {
let julia = utils::require("julia")?;
let julia = require("julia")?;
print_separator("Julia Packages");
@@ -674,14 +844,14 @@ pub fn update_julia_packages(ctx: &ExecutionContext) -> Result<()> {
.status_checked()
}
pub fn run_helm_repo_update(run_type: RunType) -> Result<()> {
let helm = utils::require("helm")?;
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 = run_type.execute(helm);
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() {
@@ -699,3 +869,18 @@ pub fn run_helm_repo_update(run_type: RunType) -> Result<()> {
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

@@ -3,6 +3,7 @@ use std::io;
use std::path::{Path, PathBuf};
use std::process::{Command, Output, Stdio};
use color_eyre::eyre::Context;
use color_eyre::eyre::{eyre, Result};
use console::style;
use futures::stream::{iter, FuturesUnordered};
@@ -14,7 +15,6 @@ use tracing::{debug, error};
use crate::command::CommandExt;
use crate::execution_context::ExecutionContext;
use crate::executor::RunType;
use crate::terminal::print_separator;
use crate::utils::{which, PathExt};
use crate::{error::SkipStep, terminal::print_warning};
@@ -34,10 +34,12 @@ pub struct Repositories<'a> {
bad_patterns: Vec<String>,
}
#[track_caller]
fn output_checked_utf8(output: Output) -> Result<()> {
if !(output.status.success()) {
let stderr = String::from_utf8(output.stderr).unwrap();
Err(eyre!(stderr))
let stderr = String::from_utf8_lossy(&output.stderr);
let stderr = stderr.trim();
Err(eyre!("{stderr}"))
} else {
Ok(())
}
@@ -67,11 +69,12 @@ async fn pull_repository(repo: String, git: &Path, ctx: &ExecutionContext<'_>) -
.stdin(Stdio::null())
.output()
.await?;
let result = output_checked_utf8(pull_output).and_then(|_| output_checked_utf8(submodule_output));
let result = output_checked_utf8(pull_output)
.and_then(|_| output_checked_utf8(submodule_output))
.wrap_err_with(|| format!("Failed to pull {repo}"));
if let Err(message) = &result {
if result.is_err() {
println!("{} pulling {}", style("Failed").red().bold(), &repo);
print!("{message}");
} else {
let after_revision = get_head_revision(git, &repo);
@@ -171,7 +174,7 @@ impl Git {
}
}
Err(e) => match e.kind() {
io::ErrorKind::NotFound => debug!("{} does not exists", path.as_ref().display()),
io::ErrorKind::NotFound => debug!("{} does not exist", path.as_ref().display()),
_ => error!("Error looking for {}: {}", path.as_ref().display(), e),
},
}
@@ -179,22 +182,28 @@ impl Git {
None
}
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() {
return Err(SkipStep(String::from("No repositories to pull")).into());
}
print_separator("Git repositories");
repositories
.bad_patterns
.iter()
.for_each(|pattern| print_warning(format!("Path {pattern} did not contain any git repositories")));
self.multi_pull(repositories, ctx)
}
pub fn multi_pull(&self, repositories: &Repositories, ctx: &ExecutionContext) -> Result<()> {
let git = self.git.as_ref().unwrap();
if let RunType::Dry = ctx.run_type() {
if ctx.run_type().dry() {
repositories
.repositories
.iter()
@@ -297,6 +306,8 @@ impl<'a> Repositories<'a> {
self.repositories.is_empty()
}
// The following 2 functions are `#[cfg(unix)]` because they are only used in
// the `oh-my-zsh` step, which is UNIX-only.
#[cfg(unix)]
pub fn remove(&mut self, path: &str) {
let _removed = self.repositories.remove(path);

View File

@@ -4,27 +4,27 @@ use std::process::Command;
use color_eyre::eyre::Result;
use crate::command::CommandExt;
use crate::executor::RunType;
use crate::execution_context::ExecutionContext;
use crate::terminal::print_separator;
use crate::utils;
use crate::utils::PathExt;
/// <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")?;
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>
pub fn run_go_gup(run_type: RunType) -> Result<()> {
pub fn run_go_gup(ctx: &ExecutionContext) -> Result<()> {
let gup = require_go_bin("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.

View File

@@ -4,7 +4,8 @@ use std::os::unix::fs::MetadataExt;
use std::path::PathBuf;
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;
#[cfg(target_os = "linux")]
use nix::unistd::Uid;
@@ -91,7 +92,7 @@ impl NPM {
fn upgrade(&self, ctx: &ExecutionContext, use_sudo: bool) -> Result<()> {
let args = ["update", self.global_location_arg()];
if use_sudo {
let sudo = require_option(ctx.sudo().clone(), String::from("sudo is not installed"))?;
let sudo = require_option(ctx.sudo().clone(), REQUIRE_SUDO.to_string())?;
ctx.run_type()
.execute(sudo)
.arg(&self.command)
@@ -155,7 +156,7 @@ impl Yarn {
let args = ["global", "upgrade"];
if use_sudo {
let sudo = require_option(ctx.sudo().clone(), String::from("sudo is not installed"))?;
let sudo = require_option(ctx.sudo().clone(), REQUIRE_SUDO.to_string())?;
ctx.run_type()
.execute(sudo)
.arg(self.yarn.as_ref().unwrap_or(&self.command))
@@ -229,7 +230,7 @@ pub fn run_npm_upgrade(ctx: &ExecutionContext) -> Result<()> {
pub fn run_pnpm_upgrade(ctx: &ExecutionContext) -> Result<()> {
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")]
{
@@ -265,7 +266,7 @@ pub fn run_yarn_upgrade(ctx: &ExecutionContext) -> Result<()> {
pub fn deno_upgrade(ctx: &ExecutionContext) -> Result<()> {
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) {
let skip_reason = SkipStep("Deno installed outside of .deno directory".to_string());

View File

@@ -277,7 +277,7 @@ impl Aura {
impl ArchPackageManager for Aura {
fn upgrade(&self, ctx: &ExecutionContext) -> Result<()> {
let sudo = which("sudo").unwrap_or_else(PathBuf::new);
let sudo = which("sudo").unwrap_or_default();
let mut aur_update = ctx.run_type().execute(&sudo);
if sudo.ends_with("sudo") {

View File

@@ -1,26 +1,34 @@
use crate::command::CommandExt;
use crate::executor::RunType;
use crate::sudo::Sudo;
use crate::execution_context::ExecutionContext;
use crate::terminal::print_separator;
use crate::utils::require_option;
use crate::utils::{require_option, REQUIRE_SUDO};
use crate::Step;
use color_eyre::eyre::Result;
use std::process::Command;
pub fn upgrade_packages(sudo: Option<&Sudo>, run_type: RunType) -> Result<()> {
let sudo = require_option(sudo, String::from("No sudo detected"))?;
pub fn upgrade_packages(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
print_separator("DragonFly BSD Packages");
run_type
.execute(sudo)
.args(["/usr/local/sbin/pkg", "upgrade"])
.status_checked()
let mut cmd = ctx.run_type().execute(sudo);
cmd.args(["/usr/local/sbin/pkg", "upgrade"]);
if ctx.config().yes(Step::System) {
cmd.arg("-y");
}
cmd.status_checked()
}
pub fn audit_packages(sudo: Option<&Sudo>) -> Result<()> {
if let Some(sudo) = sudo {
println!();
Command::new(sudo)
pub fn audit_packages(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
print_separator("DragonFly BSD Audit");
#[allow(clippy::disallowed_methods)]
if !Command::new(sudo)
.args(["/usr/local/sbin/pkg", "audit", "-Fr"])
.status_checked()?;
.status()?
.success()
{
println!("The package audit was successful, but vulnerable packages still remain on the system");
}
Ok(())
}

View File

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

View File

@@ -8,12 +8,10 @@ use tracing::{debug, warn};
use crate::command::CommandExt;
use crate::error::{SkipStep, TopgradeError};
use crate::execution_context::ExecutionContext;
use crate::executor::RunType;
use crate::steps::os::archlinux;
use crate::sudo::Sudo;
use crate::terminal::{print_separator, print_warning};
use crate::utils::{require, require_option, which, PathExt};
use crate::Step;
use crate::terminal::print_separator;
use crate::utils::{require, require_option, which, PathExt, REQUIRE_SUDO};
use crate::{Step, HOME_DIR};
static OS_RELEASE_PATH: &str = "/etc/os-release";
@@ -26,12 +24,15 @@ pub enum Distribution {
CentOS,
ClearLinux,
Fedora,
FedoraImmutable,
Debian,
Gentoo,
OpenMandriva,
OpenSuseTumbleweed,
PCLinuxOS,
Suse,
SuseMicro,
Vanilla,
Void,
Solus,
Exherbo,
@@ -40,19 +41,36 @@ pub enum 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 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());
Ok(match id {
Some("alpine") => Distribution::Alpine,
Some("centos") | Some("rhel") | Some("ol") => Distribution::CentOS,
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")
|| variant.contains(&"Kinoite")
|| variant.contains(&"Sericea")
|| variant.contains(&"Onyx")
{
Ok(Distribution::FedoraImmutable)
} else {
Ok(Distribution::Fedora)
}
} else {
Ok(Distribution::Fedora)
};
}
Some("void") => Distribution::Void,
Some("debian") | Some("pureos") => Distribution::Debian,
Some("arch") | Some("anarchy") | Some("manjaro-arm") | Some("garuda") | Some("artix") => Distribution::Arch,
Some("debian") | Some("pureos") | Some("Deepin") => Distribution::Debian,
Some("arch") | Some("manjaro-arm") | Some("garuda") | Some("artix") => Distribution::Arch,
Some("solus") => Distribution::Solus,
Some("gentoo") => Distribution::Gentoo,
Some("exherbo") => Distribution::Exherbo,
@@ -62,13 +80,23 @@ impl Distribution {
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 id_like.contains(&"debian") || id_like.contains(&"ubuntu") {
return Ok(Distribution::Debian);
} else if id_like.contains(&"centos") {
return Ok(Distribution::CentOS);
} 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") {
return Ok(Distribution::Arch);
} else if id_like.contains(&"alpine") {
@@ -90,10 +118,14 @@ impl Distribution {
if PathBuf::from(OS_RELEASE_PATH).exists() {
let os_release = Ini::load_from_file(OS_RELEASE_PATH)?;
if os_release.general_section().is_empty() {
return Err(TopgradeError::EmptyOSReleaseFile.into());
}
return Self::parse_os_release(&os_release);
}
Err(TopgradeError::UnknownLinuxDistribution.into())
Err(TopgradeError::EmptyOSReleaseFile.into())
}
pub fn upgrade(self, ctx: &ExecutionContext) -> Result<()> {
@@ -103,11 +135,14 @@ impl Distribution {
Distribution::Alpine => upgrade_alpine_linux(ctx),
Distribution::Arch => archlinux::upgrade_arch_linux(ctx),
Distribution::CentOS | Distribution::Fedora => upgrade_redhat(ctx),
Distribution::FedoraImmutable => upgrade_fedora_immutable(ctx),
Distribution::ClearLinux => upgrade_clearlinux(ctx),
Distribution::Debian => upgrade_debian(ctx),
Distribution::Gentoo => upgrade_gentoo(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::Solus => upgrade_solus(ctx),
Distribution::Exherbo => upgrade_exherbo(ctx),
@@ -131,7 +166,7 @@ impl Distribution {
}
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"]);
@@ -162,7 +197,7 @@ fn is_wsl() -> Result<bool> {
fn upgrade_alpine_linux(ctx: &ExecutionContext) -> Result<()> {
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("upgrade").status_checked()
@@ -177,7 +212,7 @@ 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);
command
.arg(which("dnf").unwrap_or_else(|| Path::new("yum").to_path_buf()))
@@ -196,54 +231,80 @@ fn upgrade_redhat(ctx: &ExecutionContext) -> Result<()> {
}
command.status_checked()?;
} else {
print_warning("No sudo detected. Skipping system upgrade");
}
Ok(())
}
fn upgrade_fedora_immutable(ctx: &ExecutionContext) -> Result<()> {
let ostree = require("rpm-ostree")?;
let mut command = ctx.run_type().execute(ostree);
command.arg("upgrade");
command.status_checked()?;
Ok(())
}
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()?;
} else {
print_warning("No sudo detected. Skipping system upgrade");
}
Ok(())
}
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()
.execute(sudo)
.args(["zypper", "refresh"])
.status_checked()?;
ctx.run_type()
.execute(sudo)
.args(["zypper", "dist-upgrade"])
.status_checked()?;
let mut cmd = ctx.run_type().execute(sudo);
cmd.arg("zypper");
cmd.arg(if ctx.config().suse_dup() {
"dist-upgrade"
} else {
print_warning("No sudo detected. Skipping system upgrade");
"update"
});
if ctx.config().yes(Step::System) {
cmd.arg("-y");
}
cmd.status_checked()?;
Ok(())
}
fn upgrade_suse_micro(ctx: &ExecutionContext) -> Result<()> {
if let Some(sudo) = ctx.sudo() {
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(["transactional-update", "dup"])
.args(["zypper", "refresh"])
.status_checked()?;
} else {
print_warning("No sudo detected. Skipping system upgrade");
let mut cmd = ctx.run_type().execute(sudo);
cmd.args(["zypper", "dist-upgrade"]);
if ctx.config().yes(Step::System) {
cmd.arg("-y");
}
cmd.status_checked()?;
Ok(())
}
fn upgrade_suse_micro(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
let mut cmd = ctx.run_type().execute(sudo);
cmd.arg("transactional-update");
if ctx.config().yes(Step::System) {
cmd.arg("-n");
}
cmd.arg("dup").status_checked()?;
Ok(())
}
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);
command.arg(&which("dnf").unwrap()).arg("upgrade");
@@ -257,14 +318,12 @@ fn upgrade_openmandriva(ctx: &ExecutionContext) -> Result<()> {
}
command.status_checked()?;
} else {
print_warning("No sudo detected. Skipping system upgrade");
}
Ok(())
}
fn upgrade_pclinuxos(ctx: &ExecutionContext) -> Result<()> {
if let Some(sudo) = &ctx.sudo() {
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");
@@ -279,20 +338,39 @@ fn upgrade_pclinuxos(ctx: &ExecutionContext) -> Result<()> {
command_update.status_checked()?;
ctx.run_type()
.execute(sudo)
.arg(&which("apt-get").unwrap())
.arg("dist-upgrade")
.status_checked()?;
} else {
print_warning("No sudo detected. Skipping system upgrade");
let mut cmd = ctx.run_type().execute(sudo);
cmd.arg(&which("apt-get").unwrap());
cmd.arg("dist-upgrade");
if ctx.config().yes(Step::System) {
cmd.arg("-y");
}
cmd.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(())
}
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);
command.args(["xbps-install", "-Su", "xbps"]);
if ctx.config().yes(Step::System) {
@@ -306,9 +384,6 @@ fn upgrade_void(ctx: &ExecutionContext) -> Result<()> {
command.arg("-y");
}
command.status_checked()?;
} else {
print_warning("No sudo detected. Skipping system upgrade");
}
Ok(())
}
@@ -316,7 +391,7 @@ fn upgrade_void(ctx: &ExecutionContext) -> Result<()> {
fn upgrade_gentoo(ctx: &ExecutionContext) -> Result<()> {
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") {
run_type
.execute(sudo)
@@ -351,15 +426,11 @@ fn upgrade_gentoo(ctx: &ExecutionContext) -> Result<()> {
.unwrap_or_else(|| vec!["-uDNa", "--with-bdeps=y", "world"]),
)
.status_checked()?;
} else {
print_warning("No sudo detected. Skipping system upgrade");
}
Ok(())
}
fn upgrade_debian(ctx: &ExecutionContext) -> Result<()> {
if let Some(sudo) = &ctx.sudo() {
let apt = which("apt-fast")
.or_else(|| {
if which("mist").is_some() {
@@ -377,9 +448,26 @@ fn upgrade_debian(ctx: &ExecutionContext) -> Result<()> {
})
.unwrap_or_else(|| PathBuf::from("apt-get"));
let is_mist = apt.ends_with("mist");
let is_nala = apt.ends_with("nala");
// 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()?;
ctx.run_type()
.execute(sudo)
.arg(&apt)
.arg("update")
.status_checked_with_codes(&[0, 100])?;
}
let mut command = ctx.run_type().execute(sudo);
@@ -407,9 +495,6 @@ fn upgrade_debian(ctx: &ExecutionContext) -> Result<()> {
}
command.status_checked()?;
}
} else {
print_warning("No sudo detected. Skipping system upgrade");
}
Ok(())
}
@@ -419,38 +504,50 @@ pub fn run_deb_get(ctx: &ExecutionContext) -> Result<()> {
print_separator("deb-get");
ctx.execute_elevated(&deb_get, false)?.arg("update").status_checked()?;
ctx.execute_elevated(&deb_get, false)?.arg("upgrade").status_checked()?;
ctx.run_type().execute(&deb_get).arg("update").status_checked()?;
ctx.run_type().execute(&deb_get).arg("upgrade").status_checked()?;
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(())
}
fn upgrade_solus(ctx: &ExecutionContext) -> Result<()> {
if let Some(sudo) = ctx.sudo() {
ctx.run_type()
.execute(sudo)
.args(["eopkg", "upgrade"])
.status_checked()?;
} else {
print_warning("No sudo detected. Skipping system upgrade");
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
let mut cmd = ctx.run_type().execute(sudo);
cmd.arg("eopkg");
if ctx.config().yes(Step::System) {
cmd.arg("-y");
}
cmd.arg("upgrade").status_checked()?;
Ok(())
}
pub fn update_am(ctx: &ExecutionContext) -> Result<()> {
pub fn run_am(ctx: &ExecutionContext) -> Result<()> {
let am = require("am")?;
if let Some(sudo) = ctx.sudo() {
ctx.run_type().execute(sudo).arg(am).arg("-u").status_checked()?;
print_separator("AM");
let mut am = ctx.run_type().execute(am);
if ctx.config().yes(Step::AM) {
am.arg("-U");
} else {
print_warning("No sudo detected. Skipping AM Step");
am.arg("-u");
}
Ok(())
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<()> {
@@ -463,10 +560,12 @@ pub fn run_pacdef(ctx: &ExecutionContext) -> Result<()> {
let new_version = string.contains("version: 1");
if new_version {
ctx.run_type()
.execute(&pacdef)
.args(["package", "sync"])
.status_checked()?;
let mut cmd = ctx.run_type().execute(&pacdef);
cmd.args(["package", "sync"]);
if ctx.config().yes(Step::System) {
cmd.arg("--noconfirm");
}
cmd.status_checked()?;
println!();
ctx.run_type()
@@ -474,7 +573,13 @@ pub fn run_pacdef(ctx: &ExecutionContext) -> Result<()> {
.args(["package", "review"])
.status_checked()?;
} else {
ctx.run_type().execute(&pacdef).arg("sync").status_checked()?;
let mut cmd = ctx.run_type().execute(&pacdef);
cmd.arg("sync");
if ctx.config().yes(Step::System) {
cmd.arg("--noconfirm");
}
cmd.status_checked()?;
println!();
ctx.run_type().execute(&pacdef).arg("review").status_checked()?;
@@ -499,21 +604,39 @@ pub fn run_pacstall(ctx: &ExecutionContext) -> Result<()> {
upgrade_cmd.arg("-Up").status_checked()
}
fn upgrade_clearlinux(ctx: &ExecutionContext) -> Result<()> {
if let Some(sudo) = &ctx.sudo() {
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(sudo)
.args(["swupd", "update"])
.status_checked()?;
} else {
print_warning("No sudo detected. Skipping system upgrade");
.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<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
let mut cmd = ctx.run_type().execute(sudo);
cmd.args(["swupd", "update"]);
if ctx.config().yes(Step::System) {
cmd.arg("--assume=yes");
}
cmd.status_checked()?;
Ok(())
}
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()
@@ -537,15 +660,12 @@ fn upgrade_exherbo(ctx: &ExecutionContext) -> Result<()> {
.execute(sudo)
.args(["eclectic", "config", "interactive"])
.status_checked()?;
} else {
print_warning("No sudo detected. Skipping system upgrade");
}
Ok(())
}
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"]);
@@ -560,9 +680,6 @@ fn upgrade_nixos(ctx: &ExecutionContext) -> Result<()> {
.args(["/run/current-system/sw/bin/nix-collect-garbage", "-d"])
.status_checked()?;
}
} else {
print_warning("No sudo detected. Skipping system upgrade");
}
Ok(())
}
@@ -573,7 +690,8 @@ fn upgrade_neon(ctx: &ExecutionContext) -> Result<()> {
// in theory rpm based distributions use pkcon as well, though that
// seems rare
// if that comes up we need to create a Distribution::PackageKit or some such
if let Some(sudo) = &ctx.sudo() {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
let pkcon = which("pkcon").unwrap();
// pkcon ignores update with update and refresh provided together
ctx.run_type()
@@ -591,23 +709,60 @@ fn upgrade_neon(ctx: &ExecutionContext) -> Result<()> {
}
// from pkcon man, exit code 5 is 'Nothing useful was done.'
cmd.status_checked_with_codes(&[5])?;
Ok(())
}
/// `needrestart` should be skipped if:
///
/// 1. This is a redhat-based distribution
/// 2. This is a debian-based distribution and it is using `nala` as the `apt`
/// alternative
fn should_skip_needrestart() -> Result<()> {
let distribution = Distribution::detect()?;
let msg = "needrestart will be ran by the package manager";
if distribution.redhat_based() {
return Err(SkipStep(String::from(msg)).into());
}
if matches!(distribution, Distribution::Debian) {
let apt = which("apt-fast")
.or_else(|| {
if which("mist").is_some() {
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");
if is_nala {
return Err(SkipStep(String::from(msg)).into());
}
}
Ok(())
}
pub fn run_needrestart(sudo: Option<&Sudo>, run_type: RunType) -> Result<()> {
let sudo = require_option(sudo, String::from("sudo is not installed"))?;
pub fn run_needrestart(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
let needrestart = require("needrestart")?;
let distribution = Distribution::detect()?;
if distribution.redhat_based() {
return Err(SkipStep(String::from("needrestart will be ran by the package manager")).into());
}
should_skip_needrestart()?;
print_separator("Check for needed restarts");
run_type.execute(sudo).arg(needrestart).status_checked()?;
ctx.run_type().execute(sudo).arg(needrestart).status_checked()?;
Ok(())
}
@@ -639,9 +794,9 @@ pub fn run_fwupdmgr(ctx: &ExecutionContext) -> Result<()> {
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 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 yes = ctx.config().yes(Step::Flatpak);
let run_type = ctx.run_type();
@@ -701,8 +856,8 @@ pub fn flatpak_update(ctx: &ExecutionContext) -> Result<()> {
Ok(())
}
pub fn run_snap(sudo: Option<&Sudo>, run_type: RunType) -> Result<()> {
let sudo = require_option(sudo, String::from("sudo is not installed"))?;
pub fn run_snap(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
let snap = require("snap")?;
if !PathBuf::from("/var/snapd.socket").exists() && !PathBuf::from("/run/snapd.socket").exists() {
@@ -710,17 +865,17 @@ pub fn run_snap(sudo: Option<&Sudo>, run_type: RunType) -> Result<()> {
}
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<&Sudo>, run_type: RunType) -> Result<()> {
let sudo = require_option(sudo, String::from("sudo is not installed"))?;
pub fn run_pihole_update(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
let pihole = require("pihole")?;
Path::new("/opt/pihole/update.sh").require()?;
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<()> {
@@ -728,7 +883,12 @@ pub fn run_protonup_update(ctx: &ExecutionContext) -> Result<()> {
print_separator("protonup");
ctx.run_type().execute(protonup).status_checked()?;
let mut cmd = ctx.run_type().execute(protonup);
if ctx.config().yes(Step::Protonup) {
cmd.arg("--yes");
}
cmd.status_checked()?;
Ok(())
}
@@ -758,7 +918,7 @@ pub fn run_distrobox_update(ctx: &ExecutionContext) -> Result<()> {
}
pub fn run_dkp_pacman_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())?;
let dkp_pacman = require("dkp-pacman")?;
print_separator("Devkitpro pacman");
@@ -781,7 +941,7 @@ pub fn run_dkp_pacman_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) {
return Err(SkipStep("Skipped in --yes".to_string()).into());
}
@@ -801,6 +961,22 @@ pub fn run_config_update(ctx: &ExecutionContext) -> Result<()> {
Ok(())
}
pub fn run_lure_update(ctx: &ExecutionContext) -> Result<()> {
let lure = require("lure")?;
print_separator("LURE");
let mut exe = ctx.run_type().execute(lure);
if ctx.config().yes(Step::Lure) {
exe.args(["-i=false", "up"]);
} else {
exe.arg("up");
}
exe.status_checked()
}
#[cfg(test)]
mod tests {
use super::*;
@@ -865,8 +1041,14 @@ mod tests {
}
#[test]
fn test_antergos() {
test_template(include_str!("os_release/antergos"), Distribution::Arch);
fn test_fedora_immutable() {
test_template(
include_str!("os_release/fedorasilverblue"),
Distribution::FedoraImmutable,
);
test_template(include_str!("os_release/fedorakinoite"), Distribution::FedoraImmutable);
test_template(include_str!("os_release/fedoraonyx"), Distribution::FedoraImmutable);
test_template(include_str!("os_release/fedorasericea"), Distribution::FedoraImmutable);
}
#[test]
@@ -879,11 +1061,6 @@ mod tests {
test_template(include_str!("os_release/manjaro-arm"), Distribution::Arch);
}
#[test]
fn test_anarchy() {
test_template(include_str!("os_release/anarchy"), Distribution::Arch);
}
#[test]
fn test_gentoo() {
test_template(include_str!("os_release/gentoo"), Distribution::Gentoo);
@@ -928,4 +1105,19 @@ mod tests {
fn test_pureos() {
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::execution_context::ExecutionContext;
use crate::executor::RunType;
use crate::terminal::{print_separator, prompt_yesno};
use crate::utils::{require_option, REQUIRE_SUDO};
use crate::{utils::require, Step};
use color_eyre::eyre::Result;
use std::fs;
@@ -10,7 +10,8 @@ use tracing::debug;
pub fn run_macports(ctx: &ExecutionContext) -> Result<()> {
require("port")?;
let sudo = ctx.sudo().as_ref().unwrap();
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
print_separator("MacPorts");
ctx.run_type()
.execute(sudo)
@@ -30,11 +31,11 @@ pub fn run_macports(ctx: &ExecutionContext) -> Result<()> {
Ok(())
}
pub fn run_mas(run_type: RunType) -> Result<()> {
pub fn run_mas(ctx: &ExecutionContext) -> Result<()> {
let mas = require("mas")?;
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<()> {

View File

@@ -1,22 +1,22 @@
use crate::executor::RunType;
use crate::execution_context::ExecutionContext;
use crate::terminal::print_separator;
use crate::utils::require_option;
use crate::utils::{require_option, REQUIRE_SUDO};
use color_eyre::eyre::Result;
use std::path::PathBuf;
pub fn upgrade_openbsd(sudo: Option<&PathBuf>, run_type: RunType) -> Result<()> {
let sudo = require_option(sudo, String::from("No sudo detected"))?;
pub fn upgrade_openbsd(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
print_separator("OpenBSD Update");
run_type
ctx.run_type()
.execute(sudo)
.args(&["/usr/sbin/sysupgrade", "-n"])
.status_checked()
}
pub fn upgrade_packages(sudo: Option<&PathBuf>, run_type: RunType) -> Result<()> {
let sudo = require_option(sudo, String::from("No sudo detected"))?;
pub fn upgrade_packages(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
print_separator("OpenBSD Packages");
run_type
ctx.run_type()
.execute(sudo)
.args(&["/usr/sbin/pkg_add", "-u"])
.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,23 @@
NAME="Fedora Linux"
VERSION="39.20240105.0 (Kinoite)"
ID=fedora
VERSION_ID=39
VERSION_CODENAME=""
PLATFORM_ID="platform:f39"
PRETTY_NAME="Fedora Linux 39.20240105.0 (Kinoite)"
ANSI_COLOR="0;38;2;60;110;180"
LOGO=fedora-logo-icon
CPE_NAME="cpe:/o:fedoraproject:fedora:39"
DEFAULT_HOSTNAME="fedora"
HOME_URL="https://kinoite.fedoraproject.org"
DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora-kinoite/"
SUPPORT_URL="https://ask.fedoraproject.org/"
BUG_REPORT_URL="https://pagure.io/fedora-kde/SIG/issues"
REDHAT_BUGZILLA_PRODUCT="Fedora"
REDHAT_BUGZILLA_PRODUCT_VERSION=39
REDHAT_SUPPORT_PRODUCT="Fedora"
REDHAT_SUPPORT_PRODUCT_VERSION=39
SUPPORT_END=2024-11-12
VARIANT="Kinoite"
VARIANT_ID=kinoite
OSTREE_VERSION='39.20240105.0'

View File

@@ -0,0 +1,22 @@
NAME="Fedora Linux"
VERSION="39 (Onyx)"
ID=fedora
VERSION_ID=39
VERSION_CODENAME=""
PLATFORM_ID="platform:f39"
PRETTY_NAME="Fedora Linux 39 (Onyx)"
ANSI_COLOR="0;38;2;60;110;180"
LOGO=fedora-logo-icon
CPE_NAME="cpe:/o:fedoraproject:fedora:39"
DEFAULT_HOSTNAME="fedora"
HOME_URL="https://fedoraproject.org/onyx/"
DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora-onyx/"
SUPPORT_URL="https://ask.fedoraproject.org/"
BUG_REPORT_URL="https://bugzilla.redhat.com/"
REDHAT_BUGZILLA_PRODUCT="Fedora"
REDHAT_BUGZILLA_PRODUCT_VERSION=39
REDHAT_SUPPORT_PRODUCT="Fedora"
REDHAT_SUPPORT_PRODUCT_VERSION=39
SUPPORT_END=2024-05-14
VARIANT="Onyx"
VARIANT_ID=onyx

View File

@@ -0,0 +1,22 @@
NAME="Fedora Linux"
VERSION="39 (Sericea)"
ID=fedora
VERSION_ID=39
VERSION_CODENAME=""
PLATFORM_ID="platform:f39"
PRETTY_NAME="Fedora Linux 39 (Sericea)"
ANSI_COLOR="0;38;2;60;110;180"
LOGO=fedora-logo-icon
CPE_NAME="cpe:/o:fedoraproject:fedora:39"
DEFAULT_HOSTNAME="fedora"
HOME_URL="https://fedoraproject.org/sericea/"
DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora-sericea/"
SUPPORT_URL="https://ask.fedoraproject.org/"
BUG_REPORT_URL="https://gitlab.com/fedora/sigs/sway/SIG/-/issues"
REDHAT_BUGZILLA_PRODUCT="Fedora"
REDHAT_BUGZILLA_PRODUCT_VERSION=39
REDHAT_SUPPORT_PRODUCT="Fedora"
REDHAT_SUPPORT_PRODUCT_VERSION=39
SUPPORT_END=2024-05-14
VARIANT="Sericea"
VARIANT_ID=sericea

View File

@@ -0,0 +1,22 @@
NAME="Fedora Linux"
VERSION="39 (Silverblue)"
ID=fedora
VERSION_ID=39
VERSION_CODENAME=""
PLATFORM_ID="platform:f39"
PRETTY_NAME="Fedora Linux 39 (Silverblue)"
ANSI_COLOR="0;38;2;60;110;180"
LOGO=fedora-logo-icon
CPE_NAME="cpe:/o:fedoraproject:fedora:39"
DEFAULT_HOSTNAME="fedora"
HOME_URL="https://silverblue.fedoraproject.org"
DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora-silverblue/"
SUPPORT_URL="https://ask.fedoraproject.org/"
BUG_REPORT_URL="https://github.com/fedora-silverblue/issue-tracker/issues"
REDHAT_BUGZILLA_PRODUCT="Fedora"
REDHAT_BUGZILLA_PRODUCT_VERSION=39
REDHAT_SUPPORT_PRODUCT="Fedora"
REDHAT_SUPPORT_PRODUCT_VERSION=39
SUPPORT_END=2024-05-14
VARIANT="Silverblue"
VARIANT_ID=silverblue

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

@@ -1,26 +1,30 @@
use std::ffi::OsStr;
use std::fs;
use std::os::unix::fs::MetadataExt;
use std::path::Component;
use std::path::PathBuf;
use std::process::Command;
use std::{env, path::Path};
use std::{env::var, path::Path};
use crate::command::CommandExt;
use crate::Step;
use crate::{Step, HOME_DIR};
use color_eyre::eyre::eyre;
use color_eyre::eyre::Context;
use color_eyre::eyre::Result;
use directories::BaseDirs;
use home;
use ini::Ini;
use tracing::debug;
#[cfg(target_os = "linux")]
use super::linux::Distribution;
use crate::error::SkipStep;
use crate::execution_context::ExecutionContext;
#[cfg(any(target_os = "linux", target_os = "macos"))]
use crate::executor::Executor;
#[cfg(any(target_os = "linux", target_os = "macos"))]
use crate::executor::RunType;
use crate::terminal::print_separator;
#[cfg(not(any(target_os = "android", target_os = "macos")))]
use crate::utils::require_option;
use crate::utils::{require, PathExt};
use crate::utils::{require, require_option, PathExt, REQUIRE_SUDO};
#[cfg(any(target_os = "linux", target_os = "macos"))]
const INTEL_BREW: &str = "/usr/local/bin/brew";
@@ -87,7 +91,7 @@ impl BrewVariant {
}
}
pub fn run_fisher(run_type: RunType) -> Result<()> {
pub fn run_fisher(ctx: &ExecutionContext) -> Result<()> {
let fish = require("fish")?;
Command::new(&fish)
@@ -110,7 +114,8 @@ pub fn run_fisher(run_type: RunType) -> Result<()> {
print_separator("Fisher");
let version_str = run_type
let version_str = ctx
.run_type()
.execute(&fish)
.args(["-c", "fisher --version"])
.output_checked_utf8()?
@@ -119,15 +124,18 @@ pub fn run_fisher(run_type: RunType) -> Result<()> {
if version_str.starts_with("fisher version 3.") {
// 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 {
// 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<()> {
ctx.base_dirs().home_dir().join(".bash_it").require()?;
HOME_DIR.join(".bash_it").require()?;
print_separator("Bash-it");
@@ -137,12 +145,30 @@ pub fn run_bashit(ctx: &ExecutionContext) -> Result<()> {
.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<()> {
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");
@@ -151,17 +177,18 @@ pub fn run_oh_my_fish(ctx: &ExecutionContext) -> Result<()> {
pub fn run_pkgin(ctx: &ExecutionContext) -> Result<()> {
let pkgin = require("pkgin")?;
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
print_separator("Pkgin");
let mut command = ctx.run_type().execute(ctx.sudo().as_ref().unwrap());
let mut command = ctx.run_type().execute(sudo);
command.arg(&pkgin).arg("update");
if ctx.config().yes(Step::Pkgin) {
command.arg("-y");
}
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");
if ctx.config().yes(Step::Pkgin) {
command.arg("-y");
@@ -171,8 +198,7 @@ pub fn run_pkgin(ctx: &ExecutionContext) -> Result<()> {
pub fn run_fish_plug(ctx: &ExecutionContext) -> Result<()> {
let fish = require("fish")?;
ctx.base_dirs()
.home_dir()
HOME_DIR
.join(".local/share/fish/plug/kidonng/fish-plug/functions/plug.fish")
.require()?;
@@ -191,7 +217,7 @@ pub fn run_fish_plug(ctx: &ExecutionContext) -> Result<()> {
/// See: <https://github.com/danhper/fundle>
pub fn run_fundle(ctx: &ExecutionContext) -> Result<()> {
let fish = require("fish")?;
ctx.base_dirs().home_dir().join(".config/fish/fundle").require()?;
HOME_DIR.join(".config/fish/fundle").require()?;
print_separator("fundle");
@@ -205,7 +231,7 @@ pub fn run_fundle(ctx: &ExecutionContext) -> Result<()> {
pub fn upgrade_gnome_extensions(ctx: &ExecutionContext) -> Result<()> {
let gdbus = require("gdbus")?;
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(),
)?;
let output = Command::new("gdbus")
@@ -261,7 +287,7 @@ pub fn run_brew_formula(ctx: &ExecutionContext, variant: BrewVariant) -> Result<
variant.execute(run_type).arg("update").status_checked()?;
variant
.execute(run_type)
.args(["upgrade", "--ignore-pinned", "--formula"])
.args(["upgrade", "--formula"])
.status_checked()?;
if ctx.config().cleanup() {
@@ -335,6 +361,7 @@ pub fn run_nix(ctx: &ExecutionContext) -> Result<()> {
let nix = require("nix")?;
let nix_channel = require("nix-channel")?;
let nix_env = require("nix-env")?;
// TODO: Is None possible here?
let profile_path = match home::home_dir() {
Some(home) => Path::new(&home).join(".nix-profile"),
None => Path::new("/nix/var/nix/profiles/per-user/default").into(),
@@ -342,27 +369,11 @@ pub fn run_nix(ctx: &ExecutionContext) -> Result<()> {
debug!("nix profile: {:?}", profile_path);
let manifest_json_path = profile_path.join("manifest.json");
let output = Command::new(&nix_env).args(["--query", "nix"]).output_checked_utf8();
debug!("nix-env output: {:?}", output);
let should_self_upgrade = output.is_ok();
print_separator("Nix");
let multi_user = fs::metadata(&nix)?.uid() == 0;
debug!("Multi user nix: {}", multi_user);
#[cfg(target_os = "linux")]
{
use super::linux::Distribution;
if let Ok(Distribution::NixOS) = Distribution::detect() {
return Err(SkipStep(String::from("Nix on NixOS must be upgraded via nixos-rebuild switch")).into());
}
}
#[cfg(target_os = "macos")]
{
if let Ok(..) = require("darwin-rebuild") {
if require("darwin-rebuild").is_ok() {
return Err(SkipStep(String::from(
"Nix-darwin on macOS must be upgraded via darwin-rebuild switch",
))
@@ -371,29 +382,144 @@ pub fn run_nix(ctx: &ExecutionContext) -> Result<()> {
}
let run_type = ctx.run_type();
if should_self_upgrade {
if multi_user {
ctx.execute_elevated(&nix, true)?.arg("upgrade-nix").status_checked()?;
} else {
run_type.execute(&nix).arg("upgrade-nix").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
.execute(&nix)
.execute(nix)
.args(nix_args())
.arg("profile")
.arg("upgrade")
.arg(".*")
.arg("--verbose")
.status_checked()
} else {
run_type.execute(&nix_env).arg("--upgrade").status_checked()
let mut command = run_type.execute(nix_env);
command.arg("--upgrade");
if let Some(args) = ctx.config().nix_env_arguments() {
command.args(args.split_whitespace());
};
command.status_checked()
}
}
pub fn run_nix_self_upgrade(ctx: &ExecutionContext) -> Result<()> {
let nix = require("nix")?;
// Should we attempt to upgrade Nix with `nix upgrade-nix`?
#[allow(unused_mut)]
let mut should_self_upgrade = cfg!(target_os = "macos");
#[cfg(target_os = "linux")]
{
// We can't use `nix upgrade-nix` on NixOS.
if let Ok(Distribution::NixOS) = Distribution::detect() {
should_self_upgrade = false;
}
}
if !should_self_upgrade {
return Err(SkipStep(String::from(
"`nix upgrade-nix` can only be used on macOS or non-NixOS Linux",
))
.into());
}
if nix_profile_dir(&nix)?.is_none() {
return Err(SkipStep(String::from(
"`nix upgrade-nix` cannot be run when Nix is installed in a profile",
))
.into());
}
print_separator("Nix (self-upgrade)");
let multi_user = fs::metadata(&nix)?.uid() == 0;
debug!("Multi user nix: {}", multi_user);
let nix_args = nix_args();
if multi_user {
ctx.execute_elevated(&nix, true)?
.args(nix_args)
.arg("upgrade-nix")
.status_checked()
} else {
ctx.run_type()
.execute(&nix)
.args(nix_args)
.arg("upgrade-nix")
.status_checked()
}
}
/// If we try to `nix upgrade-nix` but Nix is installed with `nix profile`, we'll get a `does not
/// appear to be part of a Nix profile` error.
///
/// We duplicate some of the `nix` logic here to avoid this.
/// See: <https://github.com/NixOS/nix/blob/f0180487a0e4c0091b46cb1469c44144f5400240/src/nix/upgrade-nix.cc#L102-L139>
///
/// See: <https://github.com/NixOS/nix/issues/5473>
fn nix_profile_dir(nix: &Path) -> Result<Option<PathBuf>> {
// NOTE: `nix` uses the location of the `nix-env` binary for this but we're using the `nix`
// binary; should be the same.
let nix_bin_dir = nix.parent();
if nix_bin_dir.and_then(|p| p.file_name()) != Some(OsStr::new("bin")) {
debug!("Nix is not installed in a `bin` directory: {nix_bin_dir:?}");
return Ok(None);
}
let nix_dir = nix_bin_dir
.and_then(|bin_dir| bin_dir.parent())
.ok_or_else(|| eyre!("Unable to find Nix install directory from Nix binary {nix:?}"))?;
debug!("Found Nix in {nix_dir:?}");
let mut profile_dir = nix_dir.to_path_buf();
while profile_dir.is_symlink() {
profile_dir = profile_dir
.parent()
.ok_or_else(|| eyre!("Path has no parent: {profile_dir:?}"))?
.join(
profile_dir
.read_link()
.wrap_err_with(|| format!("Failed to read symlink {profile_dir:?}"))?,
);
// NOTE: `nix` uses a hand-rolled canonicalize function, Rust just uses `realpath`.
if profile_dir
.canonicalize()
.wrap_err_with(|| format!("Failed to canonicalize {profile_dir:?}"))?
.components()
.any(|component| component == Component::Normal(OsStr::new("profiles")))
{
break;
}
}
debug!("Found Nix profile {profile_dir:?}");
let user_env = profile_dir
.canonicalize()
.wrap_err_with(|| format!("Failed to canonicalize {profile_dir:?}"))?;
Ok(
if user_env
.file_name()
.and_then(|name| name.to_str())
.map(|name| name.ends_with("user-environment"))
.unwrap_or(false)
{
Some(profile_dir)
} else {
None
},
)
}
fn nix_args() -> [&'static str; 2] {
["--extra-experimental-features", "nix-command"]
}
pub fn run_yadm(ctx: &ExecutionContext) -> Result<()> {
let yadm = require("yadm")?;
@@ -402,45 +528,56 @@ pub fn run_yadm(ctx: &ExecutionContext) -> Result<()> {
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")?;
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)
.args(["plugin", "update", "--all"])
.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")?;
print_separator("home-manager");
run_type.execute(home_manager).arg("switch").status_checked()
let mut cmd = ctx.run_type().execute(home_manager);
cmd.arg("switch");
if let Some(extra_args) = ctx.config().home_manager() {
cmd.args(extra_args);
}
cmd.status_checked()
}
pub fn run_tldr(run_type: RunType) -> Result<()> {
pub fn run_tldr(ctx: &ExecutionContext) -> Result<()> {
let tldr = require("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")?;
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 sdkman_init_path = env::var("SDKMAN_DIR")
let sdkman_init_path = var("SDKMAN_DIR")
.map(PathBuf::from)
.unwrap_or_else(|_| base_dirs.home_dir().join(".sdkman"))
.unwrap_or_else(|_| HOME_DIR.join(".sdkman"))
.join("bin")
.join("sdkman-init.sh")
.require()
@@ -448,9 +585,9 @@ pub fn run_sdkman(base_dirs: &BaseDirs, cleanup: bool, run_type: RunType) -> Res
print_separator("SDKMAN!");
let sdkman_config_path = env::var("SDKMAN_DIR")
let sdkman_config_path = var("SDKMAN_DIR")
.map(PathBuf::from)
.unwrap_or_else(|_| base_dirs.home_dir().join(".sdkman"))
.unwrap_or_else(|_| HOME_DIR.join(".sdkman"))
.join("etc")
.join("config")
.require()?;
@@ -463,33 +600,33 @@ pub fn run_sdkman(base_dirs: &BaseDirs, cleanup: bool, run_type: RunType) -> Res
if selfupdate_enabled == "true" {
let cmd_selfupdate = format!("source {} && sdk selfupdate", &sdkman_init_path);
run_type
ctx.run_type()
.execute(&bash)
.args(["-c", cmd_selfupdate.as_str()])
.status_checked()?;
}
let cmd_update = format!("source {} && sdk update", &sdkman_init_path);
run_type
ctx.run_type()
.execute(&bash)
.args(["-c", cmd_update.as_str()])
.status_checked()?;
let cmd_upgrade = format!("source {} && sdk upgrade", &sdkman_init_path);
run_type
ctx.run_type()
.execute(&bash)
.args(["-c", cmd_upgrade.as_str()])
.status_checked()?;
if cleanup {
if ctx.config().cleanup() {
let cmd_flush_archives = format!("source {} && sdk flush archives", &sdkman_init_path);
run_type
ctx.run_type()
.execute(&bash)
.args(["-c", cmd_flush_archives.as_str()])
.status_checked()?;
let cmd_flush_temp = format!("source {} && sdk flush temp", &sdkman_init_path);
run_type
ctx.run_type()
.execute(&bash)
.args(["-c", cmd_flush_temp.as_str()])
.status_checked()?;
@@ -506,6 +643,19 @@ pub fn run_bun(ctx: &ExecutionContext) -> Result<()> {
ctx.run_type().execute(bun).arg("upgrade").status_checked()
}
pub fn run_bun_packages(ctx: &ExecutionContext) -> Result<()> {
let bun = require("bun")?;
print_separator("Bun Packages");
if !HOME_DIR.join(".bun/install/global/package.json").exists() {
println!("No global packages installed");
return Ok(());
}
ctx.run_type().execute(bun).args(["-g", "update"]).status_checked()
}
/// Update dotfiles with `rcm(7)`.
///
/// See: <https://github.com/thoughtbot/rcm>
@@ -516,6 +666,13 @@ pub fn run_rcm(ctx: &ExecutionContext) -> Result<()> {
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<()> {
print!("Rebooting...");
Command::new("sudo").arg("reboot").status_checked()

View File

@@ -3,13 +3,13 @@ use std::path::Path;
use std::{ffi::OsStr, process::Command};
use color_eyre::eyre::Result;
use etcetera::base_strategy::BaseStrategy;
use tracing::debug;
use crate::command::CommandExt;
use crate::execution_context::ExecutionContext;
use crate::executor::RunType;
use crate::terminal::{print_separator, print_warning};
use crate::utils::require;
use crate::utils::{require, which};
use crate::{error::SkipStep, steps::git::Repositories};
use crate::{powershell, Step};
@@ -53,22 +53,26 @@ pub fn run_winget(ctx: &ExecutionContext) -> Result<()> {
.status_checked()
}
pub fn run_scoop(cleanup: bool, run_type: RunType) -> Result<()> {
pub fn run_scoop(ctx: &ExecutionContext) -> Result<()> {
let scoop = require("scoop")?;
print_separator("Scoop");
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()?;
ctx.run_type().execute(&scoop).args(["update", "*"]).status_checked()?;
if cleanup {
run_type.execute(&scoop).args(["cleanup", "*"]).status_checked()?;
if ctx.config().cleanup() {
ctx.run_type().execute(&scoop).args(["cleanup", "*"]).status_checked()?;
}
Ok(())
}
pub fn update_wsl(ctx: &ExecutionContext) -> Result<()> {
if !is_wsl_installed()? {
return Err(SkipStep("WSL not installed".to_string()).into());
}
let wsl = require("wsl")?;
print_separator("Update WSL");
@@ -87,6 +91,30 @@ pub fn update_wsl(ctx: &ExecutionContext) -> Result<()> {
Ok(())
}
/// Detect if WSL is installed or not.
///
/// For WSL, we cannot simply check if command `wsl` is installed as on newer
/// versions of Windows (since windows 10 version 2004), this commmand is
/// installed by default.
///
/// If the command is installed and the user hasn't installed any Linux distros
/// on it, command `wsl -l` would print a help message and exit with failure, we
/// use this to check whether WSL is install or not.
fn is_wsl_installed() -> Result<bool> {
if let Some(wsl) = which("wsl") {
// Don't use `output_checked` as an execution failure log is not wanted
#[allow(clippy::disallowed_methods)]
let output = Command::new(wsl).arg("-l").output()?;
let status = output.status;
if status.success() {
return Ok(true);
}
}
Ok(false)
}
fn get_wsl_distributions(wsl: &Path) -> Result<Vec<String>> {
let output = Command::new(wsl).args(["--list", "-q"]).output_checked_utf8()?.stdout;
Ok(output
@@ -100,12 +128,45 @@ fn upgrade_wsl_distribution(wsl: &Path, dist: &str, ctx: &ExecutionContext) -> R
let topgrade = Command::new(wsl)
.args(["-d", dist, "bash", "-lc", "which topgrade"])
.output_checked_utf8()
.map_err(|_| SkipStep(String::from("Could not find Topgrade installed in WSL")))?;
.map_err(|_| SkipStep(String::from("Could not find Topgrade installed in WSL")))?
.stdout // The normal output from `which topgrade` appends a newline, so we trim it here.
.trim_end()
.to_owned();
let mut command = ctx.run_type().execute(wsl);
// The `arg` method automatically quotes its arguments.
// This means we can't append additional arguments to `topgrade` in WSL
// by calling `arg` successively.
//
// For example:
//
// ```rust
// command
// .args(["-d", dist, "bash", "-c"])
// .arg(format!("TOPGRADE_PREFIX={dist} exec {topgrade}"));
// ```
//
// creates a command string like:
// > `C:\WINDOWS\system32\wsl.EXE -d Ubuntu bash -c 'TOPGRADE_PREFIX=Ubuntu exec /bin/topgrade'`
//
// Adding the following:
//
// ```rust
// command.arg("-v");
// ```
//
// appends the next argument like so:
// > `C:\WINDOWS\system32\wsl.EXE -d Ubuntu bash -c 'TOPGRADE_PREFIX=Ubuntu exec /bin/topgrade' -v`
// which means `-v` isn't passed to `topgrade`.
let mut args = String::new();
if ctx.config().verbose() {
args.push_str("-v");
}
command
.args(["-d", dist, "bash", "-c"])
.arg(format!("TOPGRADE_PREFIX={dist} exec {topgrade}"));
.arg(format!("TOPGRADE_PREFIX={dist} exec {topgrade} {args}"));
if ctx.config().yes(Step::Wsl) {
command.arg("-y");
@@ -115,6 +176,10 @@ fn upgrade_wsl_distribution(wsl: &Path, dist: &str, ctx: &ExecutionContext) -> R
}
pub fn run_wsl_topgrade(ctx: &ExecutionContext) -> Result<()> {
if !is_wsl_installed()? {
return Err(SkipStep("WSL not installed".to_string()).into());
}
let wsl = require("wsl")?;
let wsl_distributions = get_wsl_distributions(&wsl)?;
let mut ran = false;
@@ -164,9 +229,8 @@ pub fn reboot() -> Result<()> {
Command::new("shutdown").args(["/R", "/T", "0"]).status_checked()
}
pub fn insert_startup_scripts(ctx: &ExecutionContext, git_repos: &mut Repositories) -> Result<()> {
let startup_dir = ctx
.base_dirs()
pub fn insert_startup_scripts(git_repos: &mut Repositories) -> Result<()> {
let startup_dir = crate::WINDOWS_DIRS
.data_dir()
.join("Microsoft\\Windows\\Start Menu\\Programs\\Startup");
for entry in std::fs::read_dir(&startup_dir)?.flatten() {

View File

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

View File

@@ -52,6 +52,8 @@ pub fn run_toolbx(ctx: &ExecutionContext) -> Result<()> {
topgrade_path,
"--only",
"system",
"--no-self-update",
"--skip-notify",
];
if ctx.config().yes(Step::Toolbx) {
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")
echo "NeoBundle"
NeoBundleUpdate
@@ -38,11 +49,6 @@ if exists(":Lazy")
Lazy! sync | qa
endif
if exists(":AstroUpdate")
echo "AstroUpdate"
AstroUpdate
endif
if exists(':PackerSync')
echo "Packer"
autocmd User PackerComplete quitall

View File

@@ -1,14 +1,15 @@
use crate::command::CommandExt;
use crate::error::{SkipStep, TopgradeError};
use crate::HOME_DIR;
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::{
execution_context::ExecutionContext,
utils::{require, PathExt},
};
use directories::BaseDirs;
use std::path::PathBuf;
use std::{
io::{self, Write},
@@ -18,22 +19,19 @@ use tracing::debug;
const UPGRADE_VIM: &str = include_str!("upgrade.vim");
pub fn vimrc(base_dirs: &BaseDirs) -> Result<PathBuf> {
base_dirs
.home_dir()
pub fn vimrc() -> Result<PathBuf> {
HOME_DIR
.join(".vimrc")
.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)]
let base_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);
let base_dir = crate::XDG_DIRS.config_dir();
#[cfg(windows)]
let base_dir = base_dirs.cache_dir();
let base_dir = crate::WINDOWS_DIRS.cache_dir();
base_dir
.join("nvim/init.vim")
@@ -74,7 +72,7 @@ fn upgrade(command: &mut Executor, 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 python = require("python3")?;
let update_plugins = config_dir.join("update_plugins.py").require()?;
@@ -105,7 +103,7 @@ pub fn upgrade_ultimate_vimrc(ctx: &ExecutionContext) -> Result<()> {
Ok(())
}
pub fn upgrade_vim(base_dirs: &BaseDirs, ctx: &ExecutionContext) -> Result<()> {
pub fn upgrade_vim(ctx: &ExecutionContext) -> Result<()> {
let vim = require("vim")?;
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());
}
let vimrc = vimrc(base_dirs)?;
let vimrc = vimrc()?;
print_separator("Vim");
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 nvimrc = nvimrc(base_dirs)?;
let nvimrc = nvimrc()?;
print_separator("Neovim");
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")?;
print_separator("voom");
run_type.execute(voom).arg("update").status_checked()
ctx.run_type().execute(voom).arg("update").status_checked()
}

View File

@@ -3,41 +3,43 @@ use std::path::PathBuf;
use std::process::Command;
use color_eyre::eyre::Result;
use directories::BaseDirs;
use tracing::debug;
use walkdir::WalkDir;
use crate::command::CommandExt;
use crate::execution_context::ExecutionContext;
use crate::executor::RunType;
use crate::git::Repositories;
use crate::terminal::print_separator;
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")?;
require("zr")?;
print_separator("zr");
let cmd = format!("source {} && zr --update", zshrc(base_dirs).display());
run_type.execute(zsh).args(["-l", "-c", cmd.as_str()]).status_checked()
let cmd = format!("source {} && zr --update", zshrc().display());
ctx.run_type()
.execute(zsh)
.args(["-l", "-c", cmd.as_str()])
.status_checked()
}
fn zdotdir(base_dirs: &BaseDirs) -> PathBuf {
fn zdotdir() -> PathBuf {
env::var("ZDOTDIR")
.map(PathBuf::from)
.unwrap_or_else(|_| base_dirs.home_dir().to_path_buf())
.unwrap_or_else(|_| HOME_DIR.clone())
}
pub fn zshrc(base_dirs: &BaseDirs) -> PathBuf {
zdotdir(base_dirs).join(".zshrc")
pub fn zshrc() -> PathBuf {
zdotdir().join(".zshrc")
}
pub fn run_antidote(ctx: &ExecutionContext) -> Result<()> {
let zsh = require("zsh")?;
let mut antidote = zdotdir(ctx.base_dirs()).join(".antidote").require()?;
let mut antidote = zdotdir().join(".antidote").require()?;
antidote.push("antidote.zsh");
print_separator("antidote");
@@ -49,88 +51,100 @@ pub fn run_antidote(ctx: &ExecutionContext) -> Result<()> {
.status_checked()
}
pub fn run_antibody(run_type: RunType) -> Result<()> {
pub fn run_antibody(ctx: &ExecutionContext) -> Result<()> {
require("zsh")?;
let antibody = require("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 zshrc = zshrc(base_dirs).require()?;
let zshrc = zshrc().require()?;
env::var("ADOTDIR")
.map(PathBuf::from)
.unwrap_or_else(|_| base_dirs.home_dir().join("antigen.zsh"))
.unwrap_or_else(|_| HOME_DIR.join("antigen.zsh"))
.require()?;
print_separator("antigen");
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 zshrc = zshrc(base_dirs).require()?;
let zshrc = zshrc().require()?;
env::var("ZGEN_SOURCE")
.map(PathBuf::from)
.unwrap_or_else(|_| base_dirs.home_dir().join(".zgenom"))
.unwrap_or_else(|_| HOME_DIR.join(".zgenom"))
.require()?;
print_separator("zgenom");
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")?;
zshrc(base_dirs).require()?;
zshrc().require()?;
env::var("ZPLUG_HOME")
.map(PathBuf::from)
.unwrap_or_else(|_| base_dirs.home_dir().join(".zplug"))
.unwrap_or_else(|_| HOME_DIR.join(".zplug"))
.require()?;
print_separator("zplug");
run_type
ctx.run_type()
.execute(zsh)
.args(["-i", "-c", "zplug update"])
.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 zshrc = zshrc(base_dirs).require()?;
let zshrc = zshrc().require()?;
env::var("ZINIT_HOME")
.map(PathBuf::from)
.unwrap_or_else(|_| base_dirs.home_dir().join(".zinit"))
.unwrap_or_else(|_| HOME_DIR.join(".zinit"))
.require()?;
print_separator("zinit");
let cmd = format!("source {} && zinit self-update && zinit update --all", zshrc.display(),);
run_type.execute(zsh).args(["-i", "-c", cmd.as_str()]).status_checked()
let cmd = format!(
"source {} && zinit self-update && zinit update --all -p",
zshrc.display(),
);
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 zshrc = zshrc(base_dirs).require()?;
let zshrc = zshrc().require()?;
base_dirs.home_dir().join(".zi").require()?;
HOME_DIR.join(".zi").require()?;
print_separator("zi");
let cmd = format!("source {} && zi self-update && zi update --all", zshrc.display(),);
run_type.execute(zsh).args(["-i", "-c", &cmd]).status_checked()
let cmd = format!("source {} && zi self-update && zi update --all -p", zshrc.display(),);
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")?;
env::var("ZIM_HOME")
.or_else(|_| {
@@ -141,12 +155,12 @@ pub fn run_zim(base_dirs: &BaseDirs, run_type: RunType) -> Result<()> {
.map(|o| o.stdout)
})
.map(PathBuf::from)
.unwrap_or_else(|_| base_dirs.home_dir().join(".zim"))
.unwrap_or_else(|_| HOME_DIR.join(".zim"))
.require()?;
print_separator("zim");
run_type
ctx.run_type()
.execute(zsh)
.args(["-i", "-c", "zimfw upgrade && zimfw update"])
.status_checked()
@@ -154,7 +168,34 @@ pub fn run_zim(base_dirs: &BaseDirs, run_type: RunType) -> Result<()> {
pub fn run_oh_my_zsh(ctx: &ExecutionContext) -> Result<()> {
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 res_env_zsh = Command::new("zsh")
.args(["-ic", "print -rn -- ${ZSH:?}"])
.output_checked_utf8();
// this command will fail if `ZSH` is not set
if let Ok(output) = res_env_zsh {
let env_zsh = output.stdout;
debug!("Oh-my-zsh: under SSH, setting ZSH={}", env_zsh);
env::set_var("ZSH", env_zsh);
}
}
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");
@@ -194,7 +235,10 @@ pub fn run_oh_my_zsh(ctx: &ExecutionContext) -> Result<()> {
ctx.run_type()
.execute("zsh")
.env("ZSH", &oh_my_zsh)
.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])
}

View File

@@ -26,10 +26,10 @@ impl Sudo {
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)))
.or_else(|| which("please").map(|p| (p, SudoKind::Please)))
.map(|(path, kind)| Self { path, kind })
}
@@ -55,12 +55,6 @@ impl Sudo {
// 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
@@ -85,6 +79,12 @@ impl Sudo {
// See: https://linux.die.net/man/1/pkexec
cmd.arg("echo");
}
SudoKind::Please => {
// From `man please`
// -w, --warm
// Warm the access token and exit.
cmd.arg("-w");
}
}
cmd.status_checked().wrap_err("Failed to elevate permissions")
}
@@ -112,10 +112,10 @@ impl Sudo {
#[strum(serialize_all = "lowercase")]
pub enum SudoKind {
Doas,
Please,
Sudo,
Gsudo,
Pkexec,
Please,
}
impl AsRef<OsStr> for Sudo {

View File

@@ -1,8 +1,6 @@
use std::cmp::{max, min};
use std::env;
use std::io::{self, Write};
#[cfg(target_os = "linux")]
use std::path::PathBuf;
use std::process::Command;
use std::sync::Mutex;
use std::time::Duration;
@@ -12,7 +10,6 @@ use color_eyre::eyre;
use color_eyre::eyre::Context;
use console::{style, Key, Term};
use lazy_static::lazy_static;
#[cfg(target_os = "macos")]
use notify_rust::{Notification, Timeout};
use tracing::{debug, error};
#[cfg(windows)]
@@ -20,10 +17,7 @@ use which_crate::which;
use crate::command::CommandExt;
use crate::report::StepResult;
#[cfg(target_os = "linux")]
use crate::terminal;
#[cfg(target_os = "linux")]
use crate::utils::which;
lazy_static! {
static ref TERMINAL: Mutex<Terminal> = Mutex::new(Terminal::new());
}
@@ -49,8 +43,6 @@ struct Terminal {
set_title: bool,
display_time: bool,
desktop_notification: bool,
#[cfg(target_os = "linux")]
notify_send: Option<PathBuf>,
}
impl Terminal {
@@ -65,8 +57,6 @@ impl Terminal {
set_title: true,
display_time: true,
desktop_notification: false,
#[cfg(target_os = "linux")]
notify_send: which("notify-send"),
}
}
@@ -82,13 +72,11 @@ impl Terminal {
self.display_time = display_time
}
#[allow(unused_variables)]
fn notify_desktop<P: AsRef<str>>(&self, message: P, timeout: Option<Duration>) {
debug!("Desktop notification: {}", message.as_ref());
cfg_if::cfg_if! {
if #[cfg(target_os = "macos")] {
let mut notification = Notification::new();
notification.summary("Topgrade")
notification
.summary("Topgrade")
.body(message.as_ref())
.appname("topgrade");
@@ -96,21 +84,6 @@ impl Terminal {
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() {
terminal::print_warning("Sending notification failed with {err:?}");
}
}
}
}
}
fn print_separator<P: AsRef<str>>(&mut self, message: P) {

View File

@@ -1,11 +1,22 @@
use crate::error::SkipStep;
use color_eyre::eyre::Result;
use std::env;
use std::ffi::OsStr;
use std::fmt::Debug;
use std::path::{Path, PathBuf};
use std::process::Command;
use color_eyre::eyre::Result;
use tracing::{debug, error};
use tracing_subscriber::fmt::format::FmtSpan;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::reload::{Handle, Layer};
use tracing_subscriber::util::SubscriberInitExt;
use tracing_subscriber::{fmt, Registry};
use tracing_subscriber::{registry, EnvFilter};
use crate::command::CommandExt;
use crate::config::DEFAULT_LOG_LEVEL;
use crate::error::SkipStep;
pub trait PathExt
where
@@ -101,54 +112,165 @@ pub fn require_option<T>(option: Option<T>, cause: String) -> Result<T> {
}
}
/* sys-info-rs
*
* Copyright (c) 2015 Siyu Wang
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
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;
}
#[cfg(target_family = "unix")]
pub fn hostname() -> Result<String> {
use std::ffi;
extern crate libc;
unsafe {
let buf_size = libc::sysconf(libc::_SC_HOST_NAME_MAX) as usize;
let mut buf = Vec::<u8>::with_capacity(buf_size + 1);
if libc::gethostname(buf.as_mut_ptr() as *mut libc::c_char, buf_size) < 0 {
return Err(SkipStep(format!("Failed to get hostname: {}", std::io::Error::last_os_error())).into());
}
let hostname_len = libc::strnlen(buf.as_ptr() as *const libc::c_char, buf_size);
buf.set_len(hostname_len);
Ok(ffi::CString::new(buf).unwrap().into_string().unwrap())
match nix::unistd::gethostname() {
Ok(os_str) => Ok(os_str
.into_string()
.map_err(|_| SkipStep("Failed to get a UTF-8 encoded hostname".into()))?),
Err(e) => Err(e.into()),
}
}
#[cfg(target_family = "windows")]
pub fn hostname() -> Result<String> {
use crate::command::CommandExt;
use std::process::Command;
Command::new("hostname")
.output_checked_utf8()
.map_err(|err| SkipStep(format!("Failed to get hostname: {err}")).into())
.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)
}
/// Set up the tracing logger
///
/// # Return value
/// A reload handle will be returned so that we can change the log level at
/// runtime.
pub fn install_tracing(filter_directives: &str) -> Result<Handle<EnvFilter, Registry>> {
let env_filter = EnvFilter::try_new(filter_directives)
.or_else(|_| EnvFilter::try_from_default_env())
.or_else(|_| EnvFilter::try_new(DEFAULT_LOG_LEVEL))?;
let fmt_layer = fmt::layer()
.with_target(false)
.with_span_events(FmtSpan::NEW | FmtSpan::CLOSE)
.without_time();
let (filter, reload_handle) = Layer::new(env_filter);
registry().with(filter).with(fmt_layer).init();
Ok(reload_handle)
}
/// Update the tracing logger with new `filter_directives`.
pub fn update_tracing(reload_handle: &Handle<EnvFilter, Registry>, filter_directives: &str) -> Result<()> {
let new = EnvFilter::try_new(filter_directives)
.or_else(|_| EnvFilter::try_from_default_env())
.or_else(|_| EnvFilter::try_new(DEFAULT_LOG_LEVEL))?;
reload_handle.modify(|old| *old = new)?;
Ok(())
}
/// Set up the error handler crate
pub fn install_color_eyre() -> Result<()> {
color_eyre::config::HookBuilder::new()
// Don't display the backtrace reminder by default:
// Backtrace omitted. Run with RUST_BACKTRACE=1 environment variable to display it.
// Run with RUST_BACKTRACE=full to include source snippets.
.display_env_section(false)
// Display location information by default:
// Location:
// src/steps.rs:92
.display_location_section(true)
.install()
}