Compare commits

...

45 Commits

Author SHA1 Message Date
SteveLauC
0854f9c559 revert: PR 866 (#927) 2024-10-06 21:39:47 +08:00
SteveLauC
e4a068d808 chore: release v16.0.0 (#925)
* chore: release v16.0.0

* chore: it should be containers.runtime
2024-10-06 21:23:00 +08:00
SteveLauC
4c793b0df8 ci: correct checker binary name (#926)
* ci: correct checker binary name

* ci: correct checker binary name
2024-10-06 21:13:25 +08:00
SteveLauC
a021441135 fix: use single quotes for locale keys with new line char (#920) 2024-10-04 12:33:19 +08:00
Florian Nagel
29c555c394 Add i18n by using rust i18n (#807)
* feat: initial i18n setup

* style: fmt

* feat: i18n support for new steps

* fix: build on Linux

* fix: build on Linux

* refactor: rm unused translation keys

---------

Co-authored-by: Steve Lau <stevelauc@outlook.com>
2024-10-03 18:47:35 +08:00
SteveLauC
c33d396489 docs: --only is no longer experimental (#919) 2024-09-29 09:03:26 +08:00
⑆ Neveda ⑈
f6d2ba4dae feat(brew): Add greedy-auto-updates option to Brew (#914) 2024-09-26 18:29:11 +08:00
SteveLauC
a88574204d docs: don't call execute("bin_name_str") (#916) 2024-09-26 15:05:45 +08:00
Marcelo Duarte Trevisani
9435bc4b7d Add Pixi to topgrade (#915)
* Add Pixi

* make linter happy

* Fix args
2024-09-26 14:19:32 +08:00
tomaszn
27245cbd7b feat(brew): use sudo if Homebrew owned by another user on Linux (#904)
feat(brew): use sudo if Homebrew owned by another user

On Linux, run "brew update" with sudo if the Homebrew installation directory
is owned by a different user. This is typically the user who installed
Homebrew, but can also be a dedicated user account. This change ensures that
Homebrew updates can proceed smoothly even when its directory ownership does
not match the current user's UID. Proper sudo configuration is assumed for
this to work properly.
2024-09-22 21:00:52 +08:00
wetfloo
21751aa8a5 feat: tmux session attach mode (#901)
* feat: tmux session attach mode

* feat: example config update

* feat: move the comment down to be relevant

* feat: fix tmux not attaching from non-tmux env when using create_and_switch_client

* feat: make matching on tmux modes as described in suggestions

* feat: make tmux_session_attach_mode private

* feat: remove tmux mode cli option

* feat: wrap default value in quotation marks for tmux session mode

* feat: renames for tmux session management options

* feat: try to make tmux session mode description better
2024-09-17 21:06:39 +08:00
Marcel Coetzee
ad41948450 Remove check for whether conda config contains auto_activate_base (#905)
Signed-off-by: Marcel Coetzee <marcel@mooncoon.com>
2024-09-17 09:14:52 +08:00
SteveLauC
e32246f172 feat: clean scoop cache if cleanup is enabled (#909) 2024-09-16 15:27:01 +08:00
SteveLauC
25d3a816b4 fix: aura since v4.0.6 does not need sudo (#908)
* fix: aura since v4.0.6 does not need sudo

* fix: remove 'aura ' from version str
2024-09-16 13:01:05 +08:00
SteveLauC
05b1a565e0 chore: pin toolchain to MSRV(1.76) (#900)
* chore: pin toolchain to MSRV(1.76)

* chore: remove more toolchain action & update readme
2024-09-04 21:40:09 +08:00
Kreeblah
7b2623ea3c Add Debian-based system builds (#898)
* Add Debian-based system builds

* Address feedback

* Remove git as a listed dependency for Debian package
2024-09-04 11:50:39 +08:00
SteveLauC
983c5243ba fix: a panic introduced by improper unwrap() (#899)
fix: an panic introduced by improper unwrap()
2024-09-03 15:26:41 +08:00
Lucas Parzianello
1958fe1e5b Containers step: new runtime option to configuration (#896)
* pyenv: fixes #849

* feat: adds `uv` python manager step

* moved new uv step from unix to generic

* containers step: added container runtime option to config

* documented breaking change

---------

Co-authored-by: Lucas Parzianello <lucaspar@users.noreply.github.com>
2024-09-01 15:35:23 +08:00
SteveLauC
ca8558d9b4 feat: support step Bun on Windows (#893) 2024-08-26 22:21:17 +08:00
Lucas Parzianello
1b534800a9 Adds uv step (#890)
* pyenv: fixes #849

* feat: adds `uv` python manager step

* moved new uv step from unix to generic

---------

Co-authored-by: Lucas Parzianello <lucaspar@users.noreply.github.com>
2024-08-25 10:22:27 +08:00
Boris Smidt
e91c00c9c0 Add aqua tool installer cli (#889)
* Add aqua cli

* Move aqua cli to generic.rs

* Add a dry-run support to aqua

* style: format code

---------

Co-authored-by: Steve Lau <stevelauc@outlook.com>
2024-08-20 09:18:27 +08:00
Nils
a2375b4820 chore: update winget-releaser to use main branch (#888)
Update the winget-releaser action in the release_to_winget.yml workflow to use the main branch instead of v2. This ensures that the latest version of the action is being used for publishing.
2024-08-18 10:29:17 +08:00
Patrick J. Roddy
2e0c8e9e17 Fix RubyGems issues for mise regarding sudo (#887) 2024-08-18 10:28:22 +08:00
Nils
dc0ddcf9f0 Update README.md (#882)
* Update README.md

Added Chocolatey

* chore: fix broken Chocolatey link in README.md
2024-08-18 10:22:23 +08:00
Diogo Ribeiro
a1f3c86a39 feat: add volta packages (#883)
add print_info when no packages found

apply review feedback
2024-08-01 18:26:22 +08:00
Daniel Horecki
55f672eff7 Allow Nix unfree packages to be upgraded (#881)
Allow unfree packages to be upgraded

Fixes #611.

Co-authored-by: Daniel Horecki <morr@morr.pl>
2024-08-01 09:52:03 +08:00
Nils
8ece0346d8 chore: improve Windows Update step and add PSWindowsUpdate Module (#842)
* chore: improve Windows Update step and add PSWindowsUpdate Module

Refactor the `windows_update` function in `windows.rs` to improve the Windows Update step. Added a prompt for administrator privileges and updated the warning message. Also, added support for installing the PSWindowsUpdate Module as an alternative to using USOClient for Windows Update.

still see warning:
The installer will request to run as administrator, expect a prompt.
Start-Process : A parameter cannot be found that matches parameter name 'Command'.
At line:1 char:74
+ ... ath powershell -Verb runAs -ArgumentList  -NoProfile -Command Import- ...
+                                                          ~~~~~~~~
    + CategoryInfo          : InvalidArgument: (:) [Start-Process], ParameterBindingException
    + FullyQualifiedErrorId : NamedParameterNotFound,Microsoft.PowerShell.Commands.StartProcessCommand

VERBOSE: MSI-THIN-GF36 (6/30/2024 4:48:48 PM): Connecting to Microsoft Update server. Please wait...
VERBOSE: Found [0] Updates in pre search criteria

but as the verbose shows it works

* trying

* fix
2024-08-01 09:50:48 +08:00
Nicolas Lorin
b1fe1d201a ci: fix release_to_aur.yml (#879) 2024-07-29 16:13:33 +08:00
Nils
5010abdc22 Update SECURITY.md (#878) 2024-07-29 10:01:46 +08:00
SteveLauC
e4441d5021 refactor: fix Windows clippy (#880)
Refactor: fix Windows clippy
2024-07-29 09:01:04 +08:00
dashmoho
5af0c6a7e5 Fix nix upgrades (#874)
Nix version 2.21 changed how packages are upgraded.
Fixes #782.

Co-authored-by: Daniel Horecki <morr@morr.pl>
2024-07-24 07:37:22 +08:00
SteveLauC
b8da17106a feat: support ZVM (#777) 2024-07-23 07:26:08 +08:00
Tommaso Melacarne
fdf40dbf43 Fix nightly clippy warnings (#872) 2024-07-22 07:33:42 +08:00
Ryan Zoeller
f3b6530969 feat(os): support NI Linux Real-Time's opkg package manager (#870)
NI Linux Real-Time is a Yocto Linux-based distribution used with
NI's embedded and real-time controllers.

Related links:
- https://www.ni.com/en/shop/linux/introduction-to-ni-linux-real-time.html
- https://github.com/ni/nilrt
- https://github.com/ni/nilrt-docs
2024-07-21 09:09:36 +08:00
Lazerbeak12345
cbc5fc94f9 feat(linux.rs): Add support for Funtoo (#868)
* feat(linus.rs): Add support for Funtoo

* style(linux.rs): fix clippy reccomendations

* test(funtoo support): add funtoo test
2024-07-20 11:04:26 +08:00
SteveLauC
dceb697355 feat: don't run reboot with sudo on Linux with systemd (#866) 2024-07-20 10:13:14 +08:00
Lucas Parzianello
07118fa0d2 Fix pyenv: no such command 'update' (#860)
pyenv: fixes #849

Co-authored-by: Lucas Parzianello <lucaspar@users.noreply.github.com>
2024-07-11 07:52:09 +08:00
dependabot[bot]
16e6db0def chore(deps): bump zerovec-derive from 0.10.2 to 0.10.3 (#858)
Bumps [zerovec-derive](https://github.com/unicode-org/icu4x) from 0.10.2 to 0.10.3.
- [Release notes](https://github.com/unicode-org/icu4x/releases)
- [Changelog](https://github.com/unicode-org/icu4x/blob/main/CHANGELOG.md)
- [Commits](https://github.com/unicode-org/icu4x/commits/ind/zerovec-derive@0.10.3)

---
updated-dependencies:
- dependency-name: zerovec-derive
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-10 07:44:46 +08:00
dependabot[bot]
64d8f6d632 chore(deps): bump zerovec from 0.10.2 to 0.10.4 (#856)
Bumps [zerovec](https://github.com/unicode-org/icu4x) from 0.10.2 to 0.10.4.
- [Release notes](https://github.com/unicode-org/icu4x/releases)
- [Changelog](https://github.com/unicode-org/icu4x/blob/main/CHANGELOG.md)
- [Commits](https://github.com/unicode-org/icu4x/commits/ind/zerovec@0.10.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-09 07:59:04 +08:00
SteveLauC
180b5cba58 docs: document that maintenance continues (#855) 2024-07-08 09:26:57 +08:00
Steve Lau
bac416e907 docs: document that it is currently unmaintained 2024-07-07 16:23:23 +08:00
NAKASHIMA, Makoto
cb674a1572 fix: traverse symbolic links under $CONIG_DIR/topgrade.d (#852) (#853) 2024-07-07 13:47:53 +08:00
SteveLauC
960b14fa20 feat: support Poetry (#790) 2024-07-07 10:37:07 +08:00
tranzystorekk
a9f57d4205 Small clap adjustments (#846)
* style(cli): use new clap keywords

* fix(cli): use lowercase command name
2024-07-01 17:06:04 +08:00
SteveLauC
13330b6950 docs: update release procedure (#845) 2024-07-01 10:21:35 +08:00
47 changed files with 1760 additions and 670 deletions

22
.github/workflows/check_i18n.yml vendored Normal file
View File

@@ -0,0 +1,22 @@
on:
pull_request:
push:
branches:
- main
name: Check i18n
jobs:
check_locale:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install checker
# Build it with the dev profile as this is faster and the checker still works
run: |
cargo install --git https://github.com/topgrade-rs/topgrade_i18n_locale_checker --profile dev
- name: Run the checker
run: topgrade_i18n_locale_checker --locale-file ./locales/app.yml --rust-src-to-check ./src

View File

@@ -7,7 +7,6 @@ on:
name: CI name: CI
env: env:
RUST_VER: 'stable'
CROSS_VER: '0.2.5' CROSS_VER: '0.2.5'
CARGO_NET_RETRY: 3 CARGO_NET_RETRY: 3
@@ -19,12 +18,6 @@ jobs:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Setup Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: '${{ env.RUST_VER }}'
components: rustfmt
- name: Run cargo fmt - name: Run cargo fmt
env: env:
TERM: xterm-256color TERM: xterm-256color
@@ -73,12 +66,6 @@ jobs:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Setup Rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: '${{ env.RUST_VER }}'
components: clippy
- name: Setup Rust Cache - name: Setup Rust Cache
uses: Swatinem/rust-cache@v2 uses: Swatinem/rust-cache@v2
with: with:

View File

@@ -18,10 +18,10 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: setup Rust - name: Install cargo-deb
uses: dtolnay/rust-toolchain@stable run: cargo install cargo-deb
with: if: ${{ matrix.platform == 'ubuntu-latest' }}
components: rustfmt, clippy shell: bash
- name: Check format - name: Check format
run: cargo fmt --all -- --check run: cargo fmt --all -- --check
@@ -41,7 +41,7 @@ jobs:
- name: Rename Release (Unix) - name: Rename Release (Unix)
run: | run: |
cargo install default-target cargo install default-target
mkdir assets mkdir -p assets
FILENAME=topgrade-${{github.event.release.tag_name}}-$(default-target) FILENAME=topgrade-${{github.event.release.tag_name}}-$(default-target)
mv target/release/topgrade assets mv target/release/topgrade assets
cd assets cd assets
@@ -51,6 +51,24 @@ jobs:
if: ${{ matrix.platform != 'windows-latest' }} if: ${{ matrix.platform != 'windows-latest' }}
shell: bash shell: bash
- name: Build Debian-based system binary and create package
# First remove the binary built by previous steps
# because we don't want the auto-update feature,
# then build the new binary without auto-updating.
run: |
rm -rf target/release
cargo build --release
cargo deb --no-build --no-strip
if: ${{ matrix.platform == 'ubuntu-latest' }}
shell: bash
- name: Move Debian-based system package
run: |
mkdir -p assets
mv target/debian/*.deb assets
if: ${{ matrix.platform == 'ubuntu-latest' }}
shell: bash
- name: Rename Release (Windows) - name: Rename Release (Windows)
run: | run: |
cargo install default-target cargo install default-target

View File

@@ -24,10 +24,15 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: setup Rust - name: Install cargo-deb cross compilation dependencies
uses: dtolnay/rust-toolchain@stable run: sudo apt-get install libc6-arm64-cross libgcc-s1-arm64-cross
with: if: ${{ matrix.target == 'aarch64-unknown-linux-gnu' }}
components: rustfmt, clippy shell: bash
- name: Install cargo-deb
run: cargo install cargo-deb
if: ${{ matrix.target == 'aarch64-unknown-linux-gnu' }}
shell: bash
- name: install targets - name: install targets
run: rustup target add ${{ matrix.target }} run: rustup target add ${{ matrix.target }}
@@ -54,7 +59,7 @@ jobs:
- name: Rename Release - name: Rename Release
run: | run: |
mkdir assets mkdir -p assets
FILENAME=topgrade-${{github.event.release.tag_name}}-${{matrix.target}} FILENAME=topgrade-${{github.event.release.tag_name}}-${{matrix.target}}
mv target/${{matrix.target}}/release/topgrade assets mv target/${{matrix.target}}/release/topgrade assets
cd assets cd assets
@@ -62,6 +67,24 @@ jobs:
rm topgrade rm topgrade
ls . ls .
- name: Build Debian-based system package without autoupdate feature
# First remove the binary built by previous steps
# because we don't want the auto-update feature,
# then build the new binary without auto-updating.
run: |
rm -rf target/${{matrix.target}}
cross build --release --target ${{matrix.target}}
cargo deb --target=${{matrix.target}} --no-build --no-strip
if: ${{ matrix.target == 'aarch64-unknown-linux-gnu' }}
shell: bash
- name: Move Debian-based system package
run: |
mkdir -p assets
mv target/${{matrix.target}}/debian/*.deb assets
if: ${{ matrix.target == 'aarch64-unknown-linux-gnu' }}
shell: bash
- name: Release - name: Release
uses: softprops/action-gh-release@v2 uses: softprops/action-gh-release@v2
with: with:

View File

@@ -14,8 +14,9 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Publish AUR package - name: Publish AUR package
uses: ATiltedTree/create-aur-release@v1 uses: aksh1618/update-aur-package@v1.0.5
with: with:
tag_version_prefix: v
package_name: topgrade package_name: topgrade
commit_username: "Thomas Schönauer" commit_username: "Thomas Schönauer"
commit_email: t.schoenauer@hgs-wt.at commit_email: t.schoenauer@hgs-wt.at

View File

@@ -6,7 +6,7 @@ jobs:
publish: publish:
runs-on: windows-latest runs-on: windows-latest
steps: steps:
- uses: vedantmgoyal2009/winget-releaser@v2 - uses: vedantmgoyal2009/winget-releaser@main
with: with:
identifier: topgrade-rs.topgrade identifier: topgrade-rs.topgrade
max-versions-to-keep: 5 # keep only latest 5 versions max-versions-to-keep: 5 # keep only latest 5 versions

View File

@@ -1,9 +1,6 @@
# Git: Pull Repos # Containers step
1. The output of "Pulling <repository path>" has been moved behind the * New default behavior: In the previous versions, if you have both Docker and
--verbose flag / [misc] configuration block. Podman installed, Podman will be used by Topgrade. Now the default option
has been changed to Docker. This can be overridden by setting the
# Configuration `containers.runtime` option in the configuration TOML to "podman".
1. The `enable_winget` configuration entry in the `windows` section has been
removed because it will not cause any issues and will be enabled by default.

View File

@@ -48,7 +48,7 @@ To add a new `step` to `topgrade`:
// Invoke the new step to get things updated! // Invoke the new step to get things updated!
ctx.run_type() ctx.run_type()
.execute("xxx") .execute(xxx)
.arg(/* args required by this step */) .arg(/* args required by this step */)
.status_checked() .status_checked()
} }

245
Cargo.lock generated
View File

@@ -90,6 +90,12 @@ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
[[package]]
name = "arc-swap"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457"
[[package]] [[package]]
name = "async-broadcast" name = "async-broadcast"
version = "0.7.1" version = "0.7.1"
@@ -319,6 +325,16 @@ dependencies = [
"piper", "piper",
] ]
[[package]]
name = "bstr"
version = "1.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706"
dependencies = [
"memchr",
"serde",
]
[[package]] [[package]]
name = "bumpalo" name = "bumpalo"
version = "3.16.0" version = "3.16.0"
@@ -539,6 +555,25 @@ dependencies = [
"cfg-if", "cfg-if",
] ]
[[package]]
name = "crossbeam-deque"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
dependencies = [
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
dependencies = [
"crossbeam-utils",
]
[[package]] [[package]]
name = "crossbeam-utils" name = "crossbeam-utils"
version = "0.8.20" version = "0.8.20"
@@ -994,6 +1029,36 @@ version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]]
name = "globset"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1"
dependencies = [
"aho-corasick",
"bstr",
"log",
"regex-automata 0.4.7",
"regex-syntax 0.8.4",
]
[[package]]
name = "globwalk"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc"
dependencies = [
"bitflags 1.3.2",
"ignore",
"walkdir",
]
[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.14.5" version = "0.14.5"
@@ -1276,12 +1341,38 @@ dependencies = [
"utf8_iter", "utf8_iter",
] ]
[[package]]
name = "ignore"
version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b46810df39e66e925525d6e38ce1e7f6e1d208f72dc39757880fcb66e2c58af1"
dependencies = [
"crossbeam-deque",
"globset",
"log",
"memchr",
"regex-automata 0.4.7",
"same-file",
"walkdir",
"winapi-util",
]
[[package]] [[package]]
name = "indenter" name = "indenter"
version = "0.3.3" version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
[[package]]
name = "indexmap"
version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
dependencies = [
"autocfg",
"hashbrown 0.12.3",
]
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "2.2.6" version = "2.2.6"
@@ -1289,7 +1380,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
dependencies = [ dependencies = [
"equivalent", "equivalent",
"hashbrown", "hashbrown 0.14.5",
] ]
[[package]] [[package]]
@@ -1363,6 +1454,12 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "linked-hash-map"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
[[package]] [[package]]
name = "linux-raw-sys" name = "linux-raw-sys"
version = "0.4.14" version = "0.4.14"
@@ -1500,6 +1597,15 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "normpath"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5831952a9476f2fed74b77d74182fa5ddc4d21c72ec45a333b250e3ed0272804"
dependencies = [
"windows-sys 0.52.0",
]
[[package]] [[package]]
name = "notify-rust" name = "notify-rust"
version = "4.11.0" version = "4.11.0"
@@ -1611,7 +1717,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79" checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79"
dependencies = [ dependencies = [
"dlv-list", "dlv-list",
"hashbrown", "hashbrown 0.14.5",
] ]
[[package]] [[package]]
@@ -1982,6 +2088,57 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316" checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316"
[[package]]
name = "rust-i18n"
version = "3.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dcd94370631e5658a0a23635f7f47e43d06a00ad948e0bb5de79b00d85b880c"
dependencies = [
"globwalk",
"once_cell",
"regex",
"rust-i18n-macro",
"rust-i18n-support",
"smallvec",
]
[[package]]
name = "rust-i18n-macro"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "355763801dcf287e777e42def7c578410783477b804b1107852119e0b2518396"
dependencies = [
"glob",
"once_cell",
"proc-macro2",
"quote",
"rust-i18n-support",
"serde",
"serde_json",
"serde_yaml",
"syn 2.0.66",
]
[[package]]
name = "rust-i18n-support"
version = "3.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "399801f4d955abf1c3ce3ce2215dc76bd40beb4ae39e3a84936b21a79ce2caa5"
dependencies = [
"arc-swap",
"globwalk",
"lazy_static",
"normpath",
"once_cell",
"proc-macro2",
"regex",
"serde",
"serde_json",
"serde_yaml",
"toml 0.7.8",
"triomphe",
]
[[package]] [[package]]
name = "rust-ini" name = "rust-ini"
version = "0.21.0" version = "0.21.0"
@@ -2187,6 +2344,18 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "serde_yaml"
version = "0.8.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578a7433b776b56a35785ed5ce9a7e777ac0598aac5a6dd1b4b18a307c7fc71b"
dependencies = [
"indexmap 1.9.3",
"ryu",
"serde",
"yaml-rust",
]
[[package]] [[package]]
name = "sha1" name = "sha1"
version = "0.10.6" version = "0.10.6"
@@ -2378,6 +2547,15 @@ dependencies = [
"syn 2.0.66", "syn 2.0.66",
] ]
[[package]]
name = "sys-locale"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e801cf239ecd6ccd71f03d270d67dd53d13e90aab208bf4b8fe4ad957ea949b0"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "tar" name = "tar"
version = "0.4.41" version = "0.4.41"
@@ -2520,6 +2698,18 @@ dependencies = [
"tokio", "tokio",
] ]
[[package]]
name = "toml"
version = "0.7.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit 0.19.15",
]
[[package]] [[package]]
name = "toml" name = "toml"
version = "0.8.14" version = "0.8.14"
@@ -2541,13 +2731,26 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "toml_edit"
version = "0.19.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
dependencies = [
"indexmap 2.2.6",
"serde",
"serde_spanned",
"toml_datetime",
"winnow 0.5.40",
]
[[package]] [[package]]
name = "toml_edit" name = "toml_edit"
version = "0.21.1" version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1"
dependencies = [ dependencies = [
"indexmap", "indexmap 2.2.6",
"toml_datetime", "toml_datetime",
"winnow 0.5.40", "winnow 0.5.40",
] ]
@@ -2558,7 +2761,7 @@ version = "0.22.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38" checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38"
dependencies = [ dependencies = [
"indexmap", "indexmap 2.2.6",
"serde", "serde",
"serde_spanned", "serde_spanned",
"toml_datetime", "toml_datetime",
@@ -2567,7 +2770,7 @@ dependencies = [
[[package]] [[package]]
name = "topgrade" name = "topgrade"
version = "15.0.0" version = "16.0.0"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"chrono", "chrono",
@@ -2588,6 +2791,7 @@ dependencies = [
"parselnk", "parselnk",
"regex", "regex",
"regex-split", "regex-split",
"rust-i18n",
"rust-ini", "rust-ini",
"self_update", "self_update",
"semver", "semver",
@@ -2595,10 +2799,11 @@ dependencies = [
"shell-words", "shell-words",
"shellexpand", "shellexpand",
"strum", "strum",
"sys-locale",
"tempfile", "tempfile",
"thiserror", "thiserror",
"tokio", "tokio",
"toml", "toml 0.8.14",
"tracing", "tracing",
"tracing-subscriber", "tracing-subscriber",
"walkdir", "walkdir",
@@ -2713,6 +2918,17 @@ version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "343e926fc669bc8cde4fa3129ab681c63671bae288b1f1081ceee6d9d37904fc" checksum = "343e926fc669bc8cde4fa3129ab681c63671bae288b1f1081ceee6d9d37904fc"
[[package]]
name = "triomphe"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6631e42e10b40c0690bf92f404ebcfe6e1fdb480391d15f17cc8e96eeed5369"
dependencies = [
"arc-swap",
"serde",
"stable_deref_trait",
]
[[package]] [[package]]
name = "try-lock" name = "try-lock"
version = "0.2.5" version = "0.2.5"
@@ -3243,6 +3459,15 @@ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
[[package]]
name = "yaml-rust"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
dependencies = [
"linked-hash-map",
]
[[package]] [[package]]
name = "yoke" name = "yoke"
version = "0.7.4" version = "0.7.4"
@@ -3358,9 +3583,9 @@ checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
[[package]] [[package]]
name = "zerovec" name = "zerovec"
version = "0.10.2" version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb2cc8827d6c0994478a15c53f374f46fbd41bea663d809b14744bc42e6b109c" checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079"
dependencies = [ dependencies = [
"yoke", "yoke",
"zerofrom", "zerofrom",
@@ -3369,9 +3594,9 @@ dependencies = [
[[package]] [[package]]
name = "zerovec-derive" name = "zerovec-derive"
version = "0.10.2" version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97cf56601ee5052b4417d90c8755c6683473c926039908196cf35d99f893ebe7" checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",

View File

@@ -5,7 +5,8 @@ categories = ["os"]
keywords = ["upgrade", "update"] keywords = ["upgrade", "update"]
license = "GPL-3.0" license = "GPL-3.0"
repository = "https://github.com/topgrade-rs/topgrade" repository = "https://github.com/topgrade-rs/topgrade"
version = "15.0.0" rust-version = "1.76.0"
version = "16.0.0"
authors = ["Roey Darwish Dror <roey.ghost@gmail.com>", "Thomas Schönauer <t.schoenauer@hgs-wt.at>"] authors = ["Roey Darwish Dror <roey.ghost@gmail.com>", "Thomas Schönauer <t.schoenauer@hgs-wt.at>"]
exclude = ["doc/screenshot.gif", "BREAKINGCHANGES_dev.md"] exclude = ["doc/screenshot.gif", "BREAKINGCHANGES_dev.md"]
edition = "2021" edition = "2021"
@@ -51,6 +52,8 @@ merge = "~0.1"
regex-split = "~0.1" regex-split = "~0.1"
notify-rust = "~4.11" notify-rust = "~4.11"
wildmatch = "2.3.0" wildmatch = "2.3.0"
rust-i18n = "3.0.1"
sys-locale = "0.3.1"
[package.metadata.generate-rpm] [package.metadata.generate-rpm]
assets = [{ source = "target/release/topgrade", dest = "/usr/bin/topgrade" }] assets = [{ source = "target/release/topgrade", dest = "/usr/bin/topgrade" }]
@@ -59,7 +62,15 @@ assets = [{ source = "target/release/topgrade", dest = "/usr/bin/topgrade" }]
git = "*" git = "*"
[package.metadata.deb] [package.metadata.deb]
depends = "$auto,git" name = "topgrade"
maintainer = "Chris Gelatt <kreeblah@gmail.com>"
copyright = "2024, Topgrade Team"
license-file = ["LICENSE", "0"]
depends = "$auto"
extended-description = "Keeping your system up to date usually involves invoking multiple package managers. This results in big, non-portable shell one-liners saved in your shell. To remedy this, Topgrade detects which tools you use and runs the appropriate commands to update them."
section = "utils"
priority = "optional"
default-features = true
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
nix = { version = "~0.29", features = ["hostname", "signal", "user"] } nix = { version = "~0.29", features = ["hostname", "signal", "user"] }

View File

@@ -29,18 +29,16 @@ To remedy this, **Topgrade** detects which tools you use and runs the appropriat
- NixOS: [Nixpkgs](https://search.nixos.org/packages?show=topgrade) - NixOS: [Nixpkgs](https://search.nixos.org/packages?show=topgrade)
- Void Linux: [XBPS](https://voidlinux.org/packages/?arch=x86_64&q=topgrade) - Void Linux: [XBPS](https://voidlinux.org/packages/?arch=x86_64&q=topgrade)
- macOS: [Homebrew](https://formulae.brew.sh/formula/topgrade) or [MacPorts](https://ports.macports.org/port/topgrade/) - macOS: [Homebrew](https://formulae.brew.sh/formula/topgrade) or [MacPorts](https://ports.macports.org/port/topgrade/)
- Windows: [Scoop][scoop] or [Winget][winget] - Windows: [Chocolatey][choco], [Scoop][scoop] or [Winget][winget]
- PyPi: [pip](https://pypi.org/project/topgrade/) - PyPi: [pip](https://pypi.org/project/topgrade/)
[choco]: https://community.chocolatey.org/packages/topgrade
[scoop]: https://scoop.sh/#/apps?q=topgrade [scoop]: https://scoop.sh/#/apps?q=topgrade
[winget]: https://winstall.app/apps/topgrade-rs.topgrade [winget]: https://winstall.app/apps/topgrade-rs.topgrade
Other systems users can either use `cargo install` or the compiled binaries from the release page. Other systems users can either use `cargo install` or the compiled binaries from the release page.
The compiled binaries contain a self-upgrading feature. The compiled binaries contain a self-upgrading feature.
> Currently, Topgrade requires Rust 1.65 or above. In general, Topgrade tracks
> the latest stable toolchain.
## Usage ## Usage
Just run `topgrade`. Just run `topgrade`.

View File

@@ -14,7 +14,7 @@
```sh' ```sh'
$ cd topgrade $ cd topgrade
$ cp BREAKINGCHANGES_dev.md BREAKINGCHANGES.md $ mv BREAKINGCHANGES_dev.md BREAKINGCHANGES.md
$ touch BREAKINGCHANGES_dev.md $ touch BREAKINGCHANGES_dev.md
``` ```

View File

@@ -6,6 +6,6 @@ We only support the latest major version and each subversion.
| Version | Supported | | Version | Supported |
| -------- | ------------------ | | -------- | ------------------ |
| 10.0.x | :white_check_mark: | | 15.0.x | :white_check_mark: |
| < 10.0 | :x: | | < 15.0 | :x: |

View File

@@ -47,6 +47,12 @@
# Run inside tmux (default: false) # Run inside tmux (default: false)
# run_in_tmux = true # run_in_tmux = true
# Changes the way topgrade interacts with
# the tmux session, creating the session
# and only attaching to it if not inside tmux
# (default: "attach_if_not_in_session", allowed values: "attach_if_not_in_session", "attach_always")
# tmux_session_mode = "attach_if_not_in_session"
# Cleanup temporary or old files (default: false) # Cleanup temporary or old files (default: false)
# cleanup = true # cleanup = true
@@ -114,6 +120,12 @@
# both of them, they won't clash with each other. # both of them, they won't clash with each other.
# greedy_latest = true # greedy_latest = true
# For the BrewCask step
# If `Repo Cask Upgrade` does not exist, then use the `--greedy_auto_updates` option.
# NOTE: the above entry `greedy_cask` contains this entry, though you can enable
# both of them, they won't clash with each other.
# greedy_auto_updates = true
# For the BrewFormula step # For the BrewFormula step
# Execute `brew autoremove` after the step. # Execute `brew autoremove` after the step.
# autoremove = true # autoremove = true
@@ -244,6 +256,8 @@
[containers] [containers]
# Specify the containers to ignore while updating (Wildcard supported) # Specify the containers to ignore while updating (Wildcard supported)
# ignored_containers = ["ghcr.io/rancher-sandbox/rancher-desktop/rdx-proxy:latest", "docker.io*"] # ignored_containers = ["ghcr.io/rancher-sandbox/rancher-desktop/rdx-proxy:latest", "docker.io*"]
# Specify the runtime to use for containers (default: "docker", allowed values: "docker", "podman")
# runtime = "podman"
[lensfun] [lensfun]
# If disabled, Topgrade invokes `lensfunupdatedata` without root priviledge, # If disabled, Topgrade invokes `lensfunupdatedata` without root priviledge,

307
locales/app.yml Normal file
View File

@@ -0,0 +1,307 @@
_version: 2
"Current system locale is {system_locale}":
en: "Current system locale is %{system_locale}"
"Dry running: {program_name} {arguments}":
en: "Dry running: %{program_name} %{arguments}"
"in {directory}":
en: "in %{directory}"
"Rebooting...":
en: "Rebooting..."
"Plugins upgraded":
en: "Plugins upgraded"
"Would self-update":
en: "Would self-update"
"Pulling":
en: "Pulling"
"No Breaking changes":
en: "No Breaking changes"
"Dropping you to shell. Fix what you need and then exit the shell.":
en: "Dropping you to shell. Fix what you need and then exit the shell."
"Topgrade launched in a new tmux session":
en: "Topgrade launched in a new tmux session"
'Topgrade upgraded to {version}:\n':
en: 'Topgrade upgraded to %{version}:\n'
"Topgrade is up-to-date":
en: "Topgrade is up-to-date"
"Updating modules...":
en: "Updating modules..."
"Powershell Modules Update":
en: "Powershell Modules Update"
"Powershell is not installed":
en: "Powershell is not installed"
"Error detecting current distribution: {error}":
en: "Error detecting current distribution: %{error}"
"Error: {error}":
en: "Error: %{error}"
"Failed":
en: "Failed"
"pulling":
en: "pulling"
"Changed":
en: "Changed"
"Up-to-date":
en: "Up-to-date"
"Self update":
en: "Self update"
# The following 2 strings are used in the same sentence
"Only":
en: "Only"
"updated repositories will be shown...":
en: "updated repositories will be shown..."
"because it has no remotes":
en: "because it has no remotes"
"Skipping":
en: "Skipping"
"Aura(<0.4.6) requires sudo installed to work with AUR packages":
en: "Aura(<0.4.6) requires sudo installed to work with AUR packages"
"Pacman backup configuration files found:":
en: "Pacman backup configuration files found:"
"The package audit was successful, but vulnerable packages still remain on the system":
en: "The package audit was successful, but vulnerable packages still remain on the system"
"Syncing portage":
en: "Syncing portage"
"Finding available software":
en: "Finding available software"
"A system update is available. Do you wish to install it?":
en: "A system update is available. Do you wish to install it?"
"No new software available.":
en: "No new software available."
"No Xcode releases installed.":
en: "No Xcode releases installed."
"Would you like to move the former Xcode release to the trash?":
en: "Would you like to move the former Xcode release to the trash?"
"New Xcode release detected:":
en: "New Xcode release detected:"
"Would you like to install it?":
en: "Would you like to install it?"
"No global packages installed":
en: "No global packages installed"
"Remote Topgrade launched in Tmux":
en: "Remote Topgrade launched in Tmux"
"Remote Topgrade launched in an external terminal":
en: "Remote Topgrade launched in an external terminal"
"Collecting Vagrant boxes":
en: "Collecting Vagrant boxes"
"No Vagrant directories were specified in the configuration file":
en: "No Vagrant directories were specified in the configuration file"
"Vagrant boxes":
en: "Vagrant boxes"
"No outdated boxes":
en: "No outdated boxes"
"Summary":
en: "Summary"
"Topgrade finished with errors":
en: "Topgrade finished with errors"
"Topgrade finished successfully":
en: "Topgrade finished successfully"
"Topgrade {version_str} Breaking Changes":
en: "Topgrade %{version_str} Breaking Changes"
"Path {path} expanded to {expanded}":
en: "Path %{path} expanded to %{expanded}"
"Path {path} doesn't exist":
en: "Path %{path} doesn't exist"
"Cannot find {binary_name} in PATH":
en: "Cannot find %{binary_name} in PATH"
"Failed to get a UTF-8 encoded hostname":
en: "Failed to get a UTF-8 encoded hostname"
"Failed to get hostname: {err}":
en: "Failed to get hostname: %{err}"
"{python} is a Python 2, skip.":
en: "%{python} is a Python 2, skip."
"{python} is a Python shim, skip.":
en: "%{python} is a Python shim, skip."
"{key} failed:":
en: "%{key} failed:"
"{step_name} failed":
en: "%{step_name} failed"
"DragonFly BSD Packages":
en: "DragonFly BSD Packages"
"DragonFly BSD Audit":
en: "DragonFly BSD Audit"
"FreeBSD Update":
en: "FreeBSD Update"
"FreeBSD Packages":
en: "FreeBSD Packages"
"FreeBSD Audit":
en: "FreeBSD Audit"
"System update":
en: "System update"
"needrestart will be ran by the package manager":
en: "needrestart will be ran by the package manager"
"Check for needed restarts":
en: "Check for needed restarts"
"Should not run in WSL":
en: "Should not run in WSL"
"Firmware upgrades":
en: "Firmware upgrades"
"Flatpak System Packages":
en: "Flatpak System Packages"
"Snapd socket does not exist":
en: "Snapd socket does not exist"
"You need to specify at least one container":
en: "You need to specify at least one container"
"Skipped in --yes":
en: "Skipped in --yes"
"Configuration update":
en: "Configuration update"
"Going to execute `waydroid upgrade`, which would STOP the running container, is this ok?":
en: "Going to execute `waydroid upgrade`, which would STOP the running container, is this ok?"
"Skip the Waydroid step because the user don't want to proceed":
en: "Skip the Waydroid step because the user don't want to proceed"
"macOS App Store":
en: "macOS App Store"
"macOS system update":
en: "macOS system update"
"OpenBSD Update":
en: "OpenBSD Update"
"OpenBSD Packages":
en: "OpenBSD Packages"
"`fisher` is not defined in `fish`":
en: "`fisher` is not defined in `fish`"
"`fish_plugins` path doesn't exist: {err}":
en: "`fish_plugins` path doesn't exist: %{err}"
"`fish_update_completions` is not available":
en: "`fish_update_completions` is not available"
"Desktop doest not appear to be gnome":
en: "Desktop doest not appear to be gnome"
"Gnome shell extensions are unregistered in DBus":
en: "Gnome shell extensions are unregistered in DBus"
"Gnome Shell extensions":
en: "Gnome Shell extensions"
"Not a custom brew for macOS":
en: "Not a custom brew for macOS"
"Guix Pull Failed, Skipping":
en: "Guix Pull Failed, Skipping"
"Nix-darwin on macOS must be upgraded via darwin-rebuild switch":
en: "Nix-darwin on macOS must be upgraded via darwin-rebuild switch"
"`nix upgrade-nix` can only be used on macOS or non-NixOS Linux":
en: "`nix upgrade-nix` can only be used on macOS or non-NixOS Linux"
"`nix upgrade-nix` cannot be run when Nix is installed in a profile":
en: "`nix upgrade-nix` cannot be run when Nix is installed in a profile"
"Nix (self-upgrade)":
en: "Nix (self-upgrade)"
"Pyenv is installed, but $PYENV_ROOT is not set correctly":
en: "Pyenv is installed, but $PYENV_ROOT is not set correctly"
"pyenv is not a git repository":
en: "pyenv is not a git repository"
"Bun Packages":
en: "Bun Packages"
"WSL not installed":
en: "WSL not installed"
"Update WSL":
en: "Update WSL"
"Could not find Topgrade installed in WSL":
en: "Could not find Topgrade installed in WSL"
"Consider installing PSWindowsUpdate as the use of Windows Update via USOClient is not supported.":
en: "Consider installing PSWindowsUpdate as the use of Windows Update via USOClient is not supported."
"USOClient not supported.":
en: "USOClient not supported."
"Connecting to {hostname}...":
en: "Connecting to %{hostname}..."
"Skipping powered off box {vagrant_box}":
en: "Skipping powered off box %{vagrant_box}"
"`{repo_tag}` for `{platform}`":
en: "`%{repo_tag}` for `%{platform}`"
"Containers":
en: "Containers"
"Emacs directory does not exist":
en: "Emacs directory does not exist"
"Error getting the composer directory: {error}":
en: "Error getting the composer directory: %{error}"
"Composer directory {composer_home} isn't a descendant of the user's home directory":
en: "Composer directory %{composer_home} isn't a descendant of the user's home directory"
"Composer":
en: "Composer"
"Error running `dotnet tool list`. This is expected when a dotnet runtime is installed but no SDK.":
en: "Error running `dotnet tool list`. This is expected when a dotnet runtime is installed but no SDK."
"No dotnet global tools installed":
en: "No dotnet global tools installed"
"Racket Package Manager":
en: "Racket Package Manager"
"GH failed":
en: "GH failed"
"GitHub CLI Extensions":
en: "GitHub CLI Extensions"
"Julia Packages":
en: "Julia Packages"
"Update ClamAV Database(FreshClam)":
en: "Update ClamAV Database(FreshClam)"
"Path {pattern} did not contain any git repositories":
en: "Path %{pattern} did not contain any git repositories"
"No repositories to pull":
en: "No repositories to pull"
"Git repositories":
en: "Git repositories"
"Would pull {repo}":
en: "Would pull %{repo}"
"Node Package Manager":
en: "Node Package Manager"
"Performant Node Package Manager":
en: "Performant Node Package Manager"
"Yarn Package Manager":
en: "Yarn Package Manager"
"Deno installed outside of .deno directory":
en: "Deno installed outside of .deno directory"
"The Ultimate vimrc":
en: "The Ultimate vimrc"
"vim binary might be actually nvim":
en: "vim binary might be actually nvim"
"`{process}` failed: {exit_satus}":
en: "`%{process}` failed: %{exit_satus}"
"`{process}` failed: {exit_satus} with {output}":
en: "`%{process}` failed: %{exit_satus} with %{output}"
"Unknown Linux Distribution":
en: "Unknown Linux Distribution"
'File "/etc/os-release" does not exist or is empty':
en: 'File "/etc/os-release" does not exist or is empty'
"Failed getting the system package manager":
en: "Failed getting the system package manager"
"A step failed":
en: "A step failed"
"Dry running":
en: "Dry running"
"Topgrade Upgraded":
en: "Topgrade Upgraded"
"OK":
en: "OK"
"FAILED":
en: "FAILED"
"IGNORED":
en: "IGNORED"
"SKIPPED":
en: "SKIPPED"
# 'Y' and 'N' have to stay the same characters. Eg for German the translation
# would look sth like "(Y) Ja / (N) Nein"
"(Y)es/(N)o":
en: "(Y)es/(N)o"
# 'y', 'N', 's', 'q' have to stay the same throughout all translations.
# Eg German would look like "(y) Wiederholen / (N) Nein / (s) Konsole / (q) Beenden"
"Retry? (y)es/(N)o/(s)hell/(q)uit":
en: "Retry? (y)es/(N)o/(s)hell/(q)uit"
# 'R', 'S', 'Q' have to stay the same throughout all translations. Eg German would look like "\n(R) Neustarten\n(S) Konsole\n(Q) Beenden"
'\n(R)eboot\n(S)hell\n(Q)uit':
en: '\n(R)eboot\n(S)hell\n(Q)uit'
"Require sudo or counterpart but not found, skip":
en: "Require sudo or counterpart but not found, skip"
"sudo as user '{user}'":
en: "sudo as user '%{user}'"
"Updating aqua ...":
en: "Updating aqua ..."
"Updating aqua installed cli tools ...":
en: "Updating aqua installed cli tools ..."
"Updating Volta packages...":
en: "Updating Volta packages..."
"No packages installed with Volta":
en: "No packages installed with Volta"
"pyenv-update plugin is not installed":
en: "pyenv-update plugin is not installed"
"Respawning...":
en: "Respawning..."
"Could not find Topgrade in any WSL disribution":
en: "Could not find Topgrade in any WSL disribution"
"Windows Update":
en: "Windows Update"

2
rust-toolchain.toml Normal file
View File

@@ -0,0 +1,2 @@
[toolchain]
channel = "1.76.0"

View File

@@ -11,6 +11,7 @@ use crate::WINDOWS_DIRS;
use crate::XDG_DIRS; use crate::XDG_DIRS;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use etcetera::base_strategy::BaseStrategy; use etcetera::base_strategy::BaseStrategy;
use rust_i18n::t;
use std::{ use std::{
env::var, env::var,
fs::{read_to_string, OpenOptions}, fs::{read_to_string, OpenOptions},
@@ -45,7 +46,7 @@ impl FromStr for Version {
// They cannot be all 0s // They cannot be all 0s
assert!( assert!(
!(major == 0 && minor == 0 && patch == 0), !(major == 0 && minor == 0 && patch == 0),
"Version numbers can not be all 0s" "Version numbers cannot be all 0s"
); );
Ok(Self { Ok(Self {
@@ -118,12 +119,15 @@ pub(crate) fn first_run_of_major_release() -> Result<bool> {
/// Print breaking changes to the user. /// Print breaking changes to the user.
pub(crate) fn print_breaking_changes() { pub(crate) fn print_breaking_changes() {
let header = format!("Topgrade {VERSION_STR} Breaking Changes"); let header = format!(
"{}",
t!("Topgrade {version_str} Breaking Changes", version_str = VERSION_STR)
);
print_separator(header); print_separator(header);
let contents = if BREAKINGCHANGES.is_empty() { let contents = if BREAKINGCHANGES.is_empty() {
"No Breaking changes" t!("No Breaking changes").to_string()
} else { } else {
BREAKINGCHANGES BREAKINGCHANGES.to_string()
}; };
println!("{contents}\n"); println!("{contents}\n");
} }
@@ -159,7 +163,7 @@ mod test {
} }
#[test] #[test]
#[should_panic(expected = "Version numbers can not be all 0s")] #[should_panic(expected = "Version numbers cannot be all 0s")]
fn invalid_version() { fn invalid_version() {
let all_0 = "0.0.0"; let all_0 = "0.0.0";
all_0.parse::<Version>().unwrap(); all_0.parse::<Version>().unwrap();

View File

@@ -5,7 +5,7 @@ use std::fs::{write, File};
use std::io::Write; use std::io::Write;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::process::Command; use std::process::Command;
use std::{env, fs}; use std::{env, fmt, fs};
use clap::{Parser, ValueEnum}; use clap::{Parser, ValueEnum};
use clap_complete::Shell; use clap_complete::Shell;
@@ -15,6 +15,7 @@ use etcetera::base_strategy::BaseStrategy;
use merge::Merge; use merge::Merge;
use regex::Regex; use regex::Regex;
use regex_split::RegexSplit; use regex_split::RegexSplit;
use rust_i18n::t;
use serde::Deserialize; use serde::Deserialize;
use strum::{EnumIter, EnumString, IntoEnumIterator, VariantNames}; use strum::{EnumIter, EnumString, IntoEnumIterator, VariantNames};
use which_crate::which; use which_crate::which;
@@ -25,6 +26,7 @@ use crate::sudo::SudoKind;
use crate::utils::string_prepend_str; use crate::utils::string_prepend_str;
use tracing::{debug, error}; use tracing::{debug, error};
// TODO: Add i18n to this. Tracking issue: https://github.com/topgrade-rs/topgrade/issues/859
pub static EXAMPLE_CONFIG: &str = include_str!("../config.example.toml"); pub static EXAMPLE_CONFIG: &str = include_str!("../config.example.toml");
/// Topgrade's default log level. /// Topgrade's default log level.
@@ -53,6 +55,7 @@ pub enum Step {
AppMan, AppMan,
Asdf, Asdf,
Atom, Atom,
Aqua,
Audit, Audit,
AutoCpufreq, AutoCpufreq,
Bin, Bin,
@@ -121,10 +124,12 @@ pub enum Step {
PipReviewLocal, PipReviewLocal,
Pipupgrade, Pipupgrade,
Pipx, Pipx,
Pixi,
Pkg, Pkg,
Pkgin, Pkgin,
PlatformioCore, PlatformioCore,
Pnpm, Pnpm,
Poetry,
Powershell, Powershell,
Protonup, Protonup,
Pyenv, Pyenv,
@@ -151,9 +156,11 @@ pub enum Step {
Tlmgr, Tlmgr,
Tmux, Tmux,
Toolbx, Toolbx,
Uv,
Vagrant, Vagrant,
Vcpkg, Vcpkg,
Vim, Vim,
VoltaPackages,
Vscode, Vscode,
Waydroid, Waydroid,
Winget, Winget,
@@ -162,6 +169,7 @@ pub enum Step {
Xcodes, Xcodes,
Yadm, Yadm,
Yarn, Yarn,
Zvm,
} }
#[derive(Deserialize, Default, Debug, Merge)] #[derive(Deserialize, Default, Debug, Merge)]
@@ -176,6 +184,7 @@ pub struct Include {
pub struct Containers { pub struct Containers {
#[merge(strategy = crate::utils::merge_strategies::vec_prepend_opt)] #[merge(strategy = crate::utils::merge_strategies::vec_prepend_opt)]
ignored_containers: Option<Vec<String>>, ignored_containers: Option<Vec<String>>,
runtime: Option<ContainerRuntime>,
} }
#[derive(Deserialize, Default, Debug, Merge)] #[derive(Deserialize, Default, Debug, Merge)]
@@ -264,6 +273,7 @@ pub struct Flatpak {
pub struct Brew { pub struct Brew {
greedy_cask: Option<bool>, greedy_cask: Option<bool>,
greedy_latest: Option<bool>, greedy_latest: Option<bool>,
greedy_auto_updates: Option<bool>,
autoremove: Option<bool>, autoremove: Option<bool>,
fetch_head: Option<bool>, fetch_head: Option<bool>,
} }
@@ -282,6 +292,22 @@ pub enum ArchPackageManager {
Yay, Yay,
} }
#[derive(Clone, Copy, Debug, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ContainerRuntime {
Docker,
Podman,
}
impl fmt::Display for ContainerRuntime {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ContainerRuntime::Docker => write!(f, "docker"),
ContainerRuntime::Podman => write!(f, "podman"),
}
}
}
#[derive(Deserialize, Default, Debug, Merge)] #[derive(Deserialize, Default, Debug, Merge)]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
pub struct Linux { pub struct Linux {
@@ -381,6 +407,8 @@ pub struct Misc {
run_in_tmux: Option<bool>, run_in_tmux: Option<bool>,
tmux_session_mode: Option<TmuxSessionMode>,
cleanup: Option<bool>, cleanup: Option<bool>,
notify_each_step: Option<bool>, notify_each_step: Option<bool>,
@@ -397,6 +425,19 @@ pub struct Misc {
log_filters: Option<Vec<String>>, log_filters: Option<Vec<String>>,
} }
#[derive(Clone, Copy, Debug, Deserialize, ValueEnum)]
#[clap(rename_all = "snake_case")]
#[serde(rename_all = "snake_case")]
pub enum TmuxSessionMode {
AttachIfNotInSession,
AttachAlways,
}
pub struct TmuxConfig {
pub args: Vec<String>,
pub session_mode: TmuxSessionMode,
}
#[derive(Deserialize, Default, Debug, Merge)] #[derive(Deserialize, Default, Debug, Merge)]
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
pub struct Lensfun { pub struct Lensfun {
@@ -532,7 +573,9 @@ impl ConfigFile {
if dir_to_search.exists() { if dir_to_search.exists() {
for entry in fs::read_dir(dir_to_search)? { for entry in fs::read_dir(dir_to_search)? {
let entry = entry?; let entry = entry?;
if entry.file_type()?.is_file() { // Use `Path::is_file()` here to traverse symbolic links.
// `DirEntry::file_type()` and `FileType::is_file()` will not traverse symbolic links.
if entry.path().is_file() {
debug!( debug!(
"Found additional (directory) configuration file at {}", "Found additional (directory) configuration file at {}",
entry.path().display() entry.path().display()
@@ -565,13 +608,11 @@ impl ConfigFile {
to read the include directory before returning the main config path to read the include directory before returning the main config path
*/ */
for include in dir_include { for include in dir_include {
let include_contents = fs::read_to_string(&include).map_err(|e| { let include_contents = fs::read_to_string(&include).inspect_err(|_| {
error!("Unable to read {}", include.display()); error!("Unable to read {}", include.display());
e
})?; })?;
let include_contents_parsed = toml::from_str(include_contents.as_str()).map_err(|e| { let include_contents_parsed = toml::from_str(include_contents.as_str()).inspect_err(|_| {
error!("Failed to deserialize {}", include.display()); error!("Failed to deserialize {}", include.display());
e
})?; })?;
result.merge(include_contents_parsed); result.merge(include_contents_parsed);
@@ -586,9 +627,8 @@ impl ConfigFile {
return Ok(result); return Ok(result);
} }
let mut contents_non_split = fs::read_to_string(&config_path).map_err(|e| { let mut contents_non_split = fs::read_to_string(&config_path).inspect_err(|_| {
error!("Unable to read {}", config_path.display()); error!("Unable to read {}", config_path.display());
e
})?; })?;
Self::ensure_misc_is_present(&mut contents_non_split, &config_path); Self::ensure_misc_is_present(&mut contents_non_split, &config_path);
@@ -599,9 +639,8 @@ impl ConfigFile {
let contents_split = regex_match_include.split_inclusive_left(contents_non_split.as_str()); let contents_split = regex_match_include.split_inclusive_left(contents_non_split.as_str());
for contents in contents_split { for contents in contents_split {
let config_file_include_only: ConfigFileIncludeOnly = toml::from_str(contents).map_err(|e| { let config_file_include_only: ConfigFileIncludeOnly = toml::from_str(contents).inspect_err(|_| {
error!("Failed to deserialize an include section of {}", config_path.display()); error!("Failed to deserialize an include section of {}", config_path.display());
e
})?; })?;
if let Some(includes) = &config_file_include_only.include { if let Some(includes) = &config_file_include_only.include {
@@ -613,14 +652,14 @@ impl ConfigFile {
let include_contents = match fs::read_to_string(&include_path) { let include_contents = match fs::read_to_string(&include_path) {
Ok(c) => c, Ok(c) => c,
Err(e) => { Err(e) => {
error!("Unable to read {}: {}", include_path.display(), e); error!("Unable to read {}: {e}", include_path.display(),);
continue; continue;
} }
}; };
match toml::from_str::<Self>(&include_contents) { match toml::from_str::<Self>(&include_contents) {
Ok(include_parsed) => result.merge(include_parsed), Ok(include_parsed) => result.merge(include_parsed),
Err(e) => { Err(e) => {
error!("Failed to deserialize {}: {}", include_path.display(), e); error!("Failed to deserialize {}: {e}", include_path.display(),);
continue; continue;
} }
}; };
@@ -630,14 +669,17 @@ impl ConfigFile {
match toml::from_str::<Self>(contents) { match toml::from_str::<Self>(contents) {
Ok(contents) => result.merge(contents), Ok(contents) => result.merge(contents),
Err(e) => error!("Failed to deserialize {}: {}", config_path.display(), e), Err(e) => error!("Failed to deserialize {}: {e}", config_path.display(),),
} }
} }
if let Some(paths) = result.git.as_mut().and_then(|git| git.repos.as_mut()) { if let Some(paths) = result.git.as_mut().and_then(|git| git.repos.as_mut()) {
for path in paths.iter_mut() { for path in paths.iter_mut() {
let expanded = shellexpand::tilde::<&str>(&path.as_ref()).into_owned(); let expanded = shellexpand::tilde::<&str>(&path.as_ref()).into_owned();
debug!("Path {} expanded to {}", path, expanded); debug!(
"{}",
t!("Path {path} expanded to {expanded}", path = path, expanded = expanded)
);
*path = expanded; *path = expanded;
} }
} }
@@ -675,63 +717,65 @@ impl ConfigFile {
} }
// Command line arguments // Command line arguments
// TODO: i18n of clap currently not easily possible. Waiting for https://github.com/clap-rs/clap/issues/380
// Tracking issue for i18n: https://github.com/topgrade-rs/topgrade/issues/859
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[clap(name = "Topgrade", version)] #[command(name = "topgrade", version)]
pub struct CommandLineArgs { pub struct CommandLineArgs {
/// Edit the configuration file /// Edit the configuration file
#[clap(long = "edit-config")] #[arg(long = "edit-config")]
edit_config: bool, edit_config: bool,
/// Show config reference /// Show config reference
#[clap(long = "config-reference")] #[arg(long = "config-reference")]
show_config_reference: bool, show_config_reference: bool,
/// Run inside tmux /// Run inside tmux
#[clap(short = 't', long = "tmux")] #[arg(short = 't', long = "tmux")]
run_in_tmux: bool, run_in_tmux: bool,
/// Cleanup temporary or old files /// Cleanup temporary or old files
#[clap(short = 'c', long = "cleanup")] #[arg(short = 'c', long = "cleanup")]
cleanup: bool, cleanup: bool,
/// Print what would be done /// Print what would be done
#[clap(short = 'n', long = "dry-run")] #[arg(short = 'n', long = "dry-run")]
dry_run: bool, dry_run: bool,
/// Do not ask to retry failed steps /// Do not ask to retry failed steps
#[clap(long = "no-retry")] #[arg(long = "no-retry")]
no_retry: bool, no_retry: bool,
/// Do not perform upgrades for the given steps /// Do not perform upgrades for the given steps
#[clap(long = "disable", value_name = "STEP", value_enum, num_args = 1..)] #[arg(long = "disable", value_name = "STEP", value_enum, num_args = 1..)]
disable: Vec<Step>, disable: Vec<Step>,
/// Perform only the specified steps (experimental) /// Perform only the specified steps
#[clap(long = "only", value_name = "STEP", value_enum, num_args = 1..)] #[arg(long = "only", value_name = "STEP", value_enum, num_args = 1..)]
only: Vec<Step>, only: Vec<Step>,
/// Run only specific custom commands /// Run only specific custom commands
#[clap(long = "custom-commands", value_name = "NAME", num_args = 1..)] #[arg(long = "custom-commands", value_name = "NAME", num_args = 1..)]
custom_commands: Vec<String>, custom_commands: Vec<String>,
/// Set environment variables /// Set environment variables
#[clap(long = "env", value_name = "NAME=VALUE", num_args = 1..)] #[arg(long = "env", value_name = "NAME=VALUE", num_args = 1..)]
env: Vec<String>, env: Vec<String>,
/// Output debug logs. Alias for `--log-filter debug`. /// Output debug logs. Alias for `--log-filter debug`.
#[clap(short = 'v', long = "verbose")] #[arg(short = 'v', long = "verbose")]
pub verbose: bool, pub verbose: bool,
/// Prompt for a key before exiting /// Prompt for a key before exiting
#[clap(short = 'k', long = "keep")] #[arg(short = 'k', long = "keep")]
keep_at_end: bool, keep_at_end: bool,
/// Skip sending a notification at the end of a run /// Skip sending a notification at the end of a run
#[clap(long = "skip-notify")] #[arg(long = "skip-notify")]
skip_notify: bool, skip_notify: bool,
/// Say yes to package manager's prompt /// Say yes to package manager's prompt
#[clap( #[arg(
short = 'y', short = 'y',
long = "yes", long = "yes",
value_name = "STEP", value_name = "STEP",
@@ -741,37 +785,37 @@ pub struct CommandLineArgs {
yes: Option<Vec<Step>>, yes: Option<Vec<Step>>,
/// Don't pull the predefined git repos /// Don't pull the predefined git repos
#[clap(long = "disable-predefined-git-repos")] #[arg(long = "disable-predefined-git-repos")]
disable_predefined_git_repos: bool, disable_predefined_git_repos: bool,
/// Alternative configuration file /// Alternative configuration file
#[clap(long = "config", value_name = "PATH")] #[arg(long = "config", value_name = "PATH")]
config: Option<PathBuf>, config: Option<PathBuf>,
/// A regular expression for restricting remote host execution /// A regular expression for restricting remote host execution
#[clap(long = "remote-host-limit", value_name = "REGEX")] #[arg(long = "remote-host-limit", value_name = "REGEX")]
remote_host_limit: Option<Regex>, remote_host_limit: Option<Regex>,
/// Show the reason for skipped steps /// Show the reason for skipped steps
#[clap(long = "show-skipped")] #[arg(long = "show-skipped")]
show_skipped: bool, show_skipped: bool,
/// Tracing filter directives. /// Tracing filter directives.
/// ///
/// See: https://docs.rs/tracing-subscriber/latest/tracing_subscriber/struct.EnvFilter.html /// See: https://docs.rs/tracing-subscriber/latest/tracing_subscriber/struct.EnvFilter.html
#[clap(long, default_value = DEFAULT_LOG_LEVEL)] #[arg(long, default_value = DEFAULT_LOG_LEVEL)]
pub log_filter: String, pub log_filter: String,
/// Print completion script for the given shell and exit /// Print completion script for the given shell and exit
#[clap(long, value_enum, hide = true)] #[arg(long, value_enum, hide = true)]
pub gen_completion: Option<Shell>, pub gen_completion: Option<Shell>,
/// Print roff manpage and exit /// Print roff manpage and exit
#[clap(long, hide = true)] #[arg(long, hide = true)]
pub gen_manpage: bool, pub gen_manpage: bool,
/// Don't update Topgrade /// Don't update Topgrade
#[clap(long = "no-self-update")] #[arg(long = "no-self-update")]
pub no_self_update: bool, pub no_self_update: bool,
} }
@@ -832,7 +876,7 @@ impl Config {
ConfigFile::read(opt.config.clone()).unwrap_or_else(|e| { ConfigFile::read(opt.config.clone()).unwrap_or_else(|e| {
// Inform the user about errors when loading the configuration, // Inform the user about errors when loading the configuration,
// but fallback to the default config to at least attempt to do something // but fallback to the default config to at least attempt to do something
error!("failed to load configuration: {}", e); error!("failed to load configuration: {e}");
ConfigFile::default() ConfigFile::default()
}) })
} else { } else {
@@ -882,6 +926,15 @@ impl Config {
.and_then(|containers| containers.ignored_containers.as_ref()) .and_then(|containers| containers.ignored_containers.as_ref())
} }
/// The preferred runtime for container updates (podman / docker).
pub fn containers_runtime(&self) -> ContainerRuntime {
self.config_file
.containers
.as_ref()
.and_then(|containers| containers.runtime)
.unwrap_or(ContainerRuntime::Docker) // defaults to a popular choice
}
/// Tell whether the specified step should run. /// Tell whether the specified step should run.
/// ///
/// If the step appears either in the `--disable` command line argument /// If the step appears either in the `--disable` command line argument
@@ -938,6 +991,15 @@ impl Config {
.unwrap_or(false) .unwrap_or(false)
} }
/// The preferred way to run the new tmux session.
fn tmux_session_mode(&self) -> TmuxSessionMode {
self.config_file
.misc
.as_ref()
.and_then(|misc| misc.tmux_session_mode)
.unwrap_or(TmuxSessionMode::AttachIfNotInSession)
}
/// Tell whether we should perform cleanup steps. /// Tell whether we should perform cleanup steps.
pub fn cleanup(&self) -> bool { pub fn cleanup(&self) -> bool {
self.opt.cleanup self.opt.cleanup
@@ -995,8 +1057,16 @@ impl Config {
self.config_file.git.as_ref().and_then(|git| git.arguments.as_ref()) self.config_file.git.as_ref().and_then(|git| git.arguments.as_ref())
} }
pub fn tmux_config(&self) -> Result<TmuxConfig> {
let args = self.tmux_arguments()?;
Ok(TmuxConfig {
args,
session_mode: self.tmux_session_mode(),
})
}
/// Extra Tmux arguments /// Extra Tmux arguments
pub fn tmux_arguments(&self) -> Result<Vec<String>> { fn tmux_arguments(&self) -> Result<Vec<String>> {
let args = &self let args = &self
.config_file .config_file
.misc .misc
@@ -1117,6 +1187,15 @@ impl Config {
.unwrap_or(false) .unwrap_or(false)
} }
/// Whether Brew cask should be auto_updates
pub fn brew_greedy_auto_updates(&self) -> bool {
self.config_file
.brew
.as_ref()
.and_then(|c| c.greedy_auto_updates)
.unwrap_or(false)
}
/// Whether Brew should autoremove /// Whether Brew should autoremove
pub fn brew_autoremove(&self) -> bool { pub fn brew_autoremove(&self) -> bool {
self.config_file self.config_file

View File

@@ -1,41 +1,98 @@
use std::process::ExitStatus; use std::{fmt::Display, process::ExitStatus};
use rust_i18n::t;
use thiserror::Error; use thiserror::Error;
#[derive(Error, Debug, PartialEq, Eq)] #[derive(Error, Debug, PartialEq, Eq)]
pub enum TopgradeError { pub enum TopgradeError {
#[error("`{0}` failed: {1}")]
ProcessFailed(String, ExitStatus), ProcessFailed(String, ExitStatus),
#[error("`{0}` failed: {1}")]
ProcessFailedWithOutput(String, ExitStatus, String), ProcessFailedWithOutput(String, ExitStatus, String),
#[error("Unknown Linux Distribution")]
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
UnknownLinuxDistribution, UnknownLinuxDistribution,
#[error("File \"/etc/os-release\" does not exist or is empty")]
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
EmptyOSReleaseFile, EmptyOSReleaseFile,
#[error("Failed getting the system package manager")]
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
FailedGettingPackageManager, FailedGettingPackageManager,
} }
impl Display for TopgradeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TopgradeError::ProcessFailed(process, exit_status) => {
write!(
f,
"{}",
t!(
"`{process}` failed: {exit_satus}",
process = process,
exit_status = exit_status
)
)
}
TopgradeError::ProcessFailedWithOutput(process, exit_status, output) => {
write!(
f,
"{}",
t!(
"`{process}` failed: {exit_satus} with {output}",
process = process,
exit_status = exit_status,
output = output
)
)
}
#[cfg(target_os = "linux")]
TopgradeError::UnknownLinuxDistribution => write!(f, "{}", t!("Unknown Linux Distribution")),
#[cfg(target_os = "linux")]
TopgradeError::EmptyOSReleaseFile => {
write!(f, "{}", t!("File \"/etc/os-release\" does not exist or is empty"))
}
#[cfg(target_os = "linux")]
TopgradeError::FailedGettingPackageManager => {
write!(f, "{}", t!("Failed getting the system package manager"))
}
}
}
}
#[derive(Error, Debug)] #[derive(Error, Debug)]
#[error("A step failed")]
pub struct StepFailed; pub struct StepFailed;
#[derive(Error, Debug)] impl Display for StepFailed {
#[error("Dry running")] fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
pub struct DryRun(); write!(f, "{}", t!("A step failed"))
}
}
#[derive(Error, Debug)]
pub struct DryRun();
impl Display for DryRun {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", t!("Dry running"))
}
}
#[derive(Error, Debug)] #[derive(Error, Debug)]
#[error("{0}")]
pub struct SkipStep(pub String); pub struct SkipStep(pub String);
impl Display for SkipStep {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
#[cfg(all(windows, feature = "self-update"))] #[cfg(all(windows, feature = "self-update"))]
#[derive(Error, Debug)] #[derive(Error, Debug)]
#[error("Topgrade Upgraded")]
pub struct Upgraded(pub ExitStatus); pub struct Upgraded(pub ExitStatus);
#[cfg(all(windows, feature = "self-update"))]
impl Display for Upgraded {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", t!("Topgrade Upgraded"))
}
}

View File

@@ -1,7 +1,7 @@
#![allow(dead_code)] #![allow(dead_code)]
use crate::executor::RunType; use crate::executor::RunType;
use crate::sudo::Sudo; use crate::sudo::Sudo;
use crate::utils::{require_option, REQUIRE_SUDO}; use crate::utils::{get_require_sudo_string, require_option};
use crate::{config::Config, executor::Executor}; use crate::{config::Config, executor::Executor};
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use std::env::var; use std::env::var;
@@ -33,7 +33,7 @@ impl<'a> ExecutionContext<'a> {
} }
pub fn execute_elevated(&self, command: &Path, interactive: bool) -> Result<Executor> { pub fn execute_elevated(&self, command: &Path, interactive: bool) -> Result<Executor> {
let sudo = require_option(self.sudo.as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(self.sudo.as_ref(), get_require_sudo_string())?;
Ok(sudo.execute_elevated(self, command, interactive)) Ok(sudo.execute_elevated(self, command, interactive))
} }

View File

@@ -4,6 +4,7 @@ use std::path::Path;
use std::process::{Child, Command, ExitStatus, Output}; use std::process::{Child, Command, ExitStatus, Output};
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use rust_i18n::t;
use tracing::debug; use tracing::debug;
use crate::command::CommandExt; use crate::command::CommandExt;
@@ -209,17 +210,20 @@ pub struct DryCommand {
impl DryCommand { impl DryCommand {
fn dry_run(&self) { fn dry_run(&self) {
print!( print!(
"Dry running: {} {}", "{}",
self.program.to_string_lossy(), t!(
shell_words::join( "Dry running: {program_name} {arguments}",
program_name = self.program.to_string_lossy(),
arguments = shell_words::join(
self.args self.args
.iter() .iter()
.map(|a| String::from(a.to_string_lossy())) .map(|a| String::from(a.to_string_lossy()))
.collect::<Vec<String>>() .collect::<Vec<String>>()
) )
)
); );
match &self.directory { match &self.directory {
Some(dir) => println!(" in {}", dir.to_string_lossy()), Some(dir) => println!(" {}", t!("in {directory}", directory = dir.to_string_lossy())),
None => println!(), None => println!(),
}; };
} }

View File

@@ -18,6 +18,7 @@ use etcetera::base_strategy::Windows;
#[cfg(unix)] #[cfg(unix)]
use etcetera::base_strategy::Xdg; use etcetera::base_strategy::Xdg;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use rust_i18n::{i18n, t};
use tracing::debug; use tracing::debug;
use self::config::{CommandLineArgs, Config, Step}; use self::config::{CommandLineArgs, Config, Step};
@@ -54,6 +55,9 @@ pub(crate) static XDG_DIRS: Lazy<Xdg> = Lazy::new(|| Xdg::new().expect("No home
#[cfg(windows)] #[cfg(windows)]
pub(crate) static WINDOWS_DIRS: Lazy<Windows> = Lazy::new(|| Windows::new().expect("No home directory")); pub(crate) static WINDOWS_DIRS: Lazy<Windows> = Lazy::new(|| Windows::new().expect("No home directory"));
// Init and load the i18n files
i18n!("locales", fallback = "en");
fn run() -> Result<()> { fn run() -> Result<()> {
install_color_eyre()?; install_color_eyre()?;
ctrlc::set_handler(); ctrlc::set_handler();
@@ -72,6 +76,11 @@ fn run() -> Result<()> {
// and `Config::tracing_filter_directives()`. // and `Config::tracing_filter_directives()`.
let reload_handle = install_tracing(&opt.tracing_filter_directives())?; let reload_handle = install_tracing(&opt.tracing_filter_directives())?;
// Get current system locale and set it as the default locale
let system_locale = sys_locale::get_locale().unwrap_or("en".to_string());
rust_i18n::set_locale(&system_locale);
debug!("Current system locale is {system_locale}");
if let Some(shell) = opt.gen_completion { if let Some(shell) = opt.gen_completion {
let cmd = &mut CommandLineArgs::command(); let cmd = &mut CommandLineArgs::command();
clap_complete::generate(shell, cmd, clap::crate_name!(), &mut io::stdout()); clap_complete::generate(shell, cmd, clap::crate_name!(), &mut io::stdout());
@@ -118,7 +127,7 @@ fn run() -> Result<()> {
if config.run_in_tmux() && env::var("TOPGRADE_INSIDE_TMUX").is_err() { if config.run_in_tmux() && env::var("TOPGRADE_INSIDE_TMUX").is_err() {
#[cfg(unix)] #[cfg(unix)]
{ {
tmux::run_in_tmux(config.tmux_arguments()?)?; tmux::run_in_tmux(config.tmux_config()?)?;
return Ok(()); return Ok(());
} }
} }
@@ -210,7 +219,7 @@ fn run() -> Result<()> {
runner.execute(Step::System, "System update", || distribution.upgrade(&ctx))?; runner.execute(Step::System, "System update", || distribution.upgrade(&ctx))?;
} }
Err(e) => { Err(e) => {
println!("Error detecting current distribution: {e}"); println!("{}", t!("Error detecting current distribution: {error}", error = e));
} }
} }
runner.execute(Step::ConfigUpdate, "config-update", || linux::run_config_update(&ctx))?; runner.execute(Step::ConfigUpdate, "config-update", || linux::run_config_update(&ctx))?;
@@ -301,7 +310,6 @@ fn run() -> Result<()> {
runner.execute(Step::Asdf, "asdf", || unix::run_asdf(&ctx))?; runner.execute(Step::Asdf, "asdf", || unix::run_asdf(&ctx))?;
runner.execute(Step::Mise, "mise", || unix::run_mise(&ctx))?; runner.execute(Step::Mise, "mise", || unix::run_mise(&ctx))?;
runner.execute(Step::Pkgin, "pkgin", || unix::run_pkgin(&ctx))?; runner.execute(Step::Pkgin, "pkgin", || unix::run_pkgin(&ctx))?;
runner.execute(Step::Bun, "bun", || unix::run_bun(&ctx))?;
runner.execute(Step::BunPackages, "bun-packages", || unix::run_bun_packages(&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, "zr", || zsh::run_zr(&ctx))?;
runner.execute(Step::Shell, "antibody", || zsh::run_antibody(&ctx))?; runner.execute(Step::Shell, "antibody", || zsh::run_antibody(&ctx))?;
@@ -363,6 +371,7 @@ fn run() -> Result<()> {
})?; })?;
runner.execute(Step::Conda, "conda", || generic::run_conda_update(&ctx))?; runner.execute(Step::Conda, "conda", || generic::run_conda_update(&ctx))?;
runner.execute(Step::Mamba, "mamba", || generic::run_mamba_update(&ctx))?; runner.execute(Step::Mamba, "mamba", || generic::run_mamba_update(&ctx))?;
runner.execute(Step::Pixi, "pixi", || generic::run_pixi_update(&ctx))?;
runner.execute(Step::Miktex, "miktex", || generic::run_miktex_packages_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::Pip3, "pip3", || generic::run_pip3_update(&ctx))?;
runner.execute(Step::PipReview, "pip-review", || generic::run_pip_review_update(&ctx))?; runner.execute(Step::PipReview, "pip-review", || generic::run_pip_review_update(&ctx))?;
@@ -385,6 +394,9 @@ fn run() -> Result<()> {
runner.execute(Step::Node, "npm", || node::run_npm_upgrade(&ctx))?; runner.execute(Step::Node, "npm", || node::run_npm_upgrade(&ctx))?;
runner.execute(Step::Yarn, "yarn", || node::run_yarn_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::Pnpm, "pnpm", || node::run_pnpm_upgrade(&ctx))?;
runner.execute(Step::VoltaPackages, "volta packages", || {
node::run_volta_packages_upgrade(&ctx)
})?;
runner.execute(Step::Containers, "Containers", || containers::run_containers(&ctx))?; runner.execute(Step::Containers, "Containers", || containers::run_containers(&ctx))?;
runner.execute(Step::Deno, "deno", || node::deno_upgrade(&ctx))?; runner.execute(Step::Deno, "deno", || node::deno_upgrade(&ctx))?;
runner.execute(Step::Composer, "composer", || generic::run_composer_update(&ctx))?; runner.execute(Step::Composer, "composer", || generic::run_composer_update(&ctx))?;
@@ -415,6 +427,11 @@ fn run() -> Result<()> {
runner.execute(Step::Lensfun, "Lensfun's database update", || { runner.execute(Step::Lensfun, "Lensfun's database update", || {
generic::run_lensfun_update_data(&ctx) generic::run_lensfun_update_data(&ctx)
})?; })?;
runner.execute(Step::Poetry, "Poetry", || generic::run_poetry(&ctx))?;
runner.execute(Step::Uv, "uv", || generic::run_uv(&ctx))?;
runner.execute(Step::Zvm, "ZVM", || generic::run_zvm(&ctx))?;
runner.execute(Step::Aqua, "aqua", || generic::run_aqua(&ctx))?;
runner.execute(Step::Bun, "bun", || generic::run_bun(&ctx))?;
if should_run_powershell { if should_run_powershell {
runner.execute(Step::Powershell, "Powershell Modules Update", || { runner.execute(Step::Powershell, "Powershell Modules Update", || {
@@ -444,7 +461,7 @@ fn run() -> Result<()> {
runner.execute(Step::Vagrant, "Vagrant boxes", || vagrant::upgrade_vagrant_boxes(&ctx))?; runner.execute(Step::Vagrant, "Vagrant boxes", || vagrant::upgrade_vagrant_boxes(&ctx))?;
if !runner.report().data().is_empty() { if !runner.report().data().is_empty() {
print_separator("Summary"); print_separator(t!("Summary"));
for (key, result) in runner.report().data() { for (key, result) in runner.report().data() {
print_result(key, result); print_result(key, result);
@@ -468,7 +485,7 @@ fn run() -> Result<()> {
} }
if config.keep_at_end() { if config.keep_at_end() {
print_info("\n(R)eboot\n(S)hell\n(Q)uit"); print_info(t!("\n(R)eboot\n(S)hell\n(Q)uit"));
loop { loop {
match get_key() { match get_key() {
Ok(Key::Char('s')) | Ok(Key::Char('S')) => { Ok(Key::Char('s')) | Ok(Key::Char('S')) => {
@@ -490,10 +507,11 @@ fn run() -> Result<()> {
if !config.skip_notify() { if !config.skip_notify() {
notify_desktop( notify_desktop(
format!( if failed {
"Topgrade finished {}", t!("Topgrade finished with errors")
if failed { "with errors" } else { "successfully" } } else {
), t!("Topgrade finished successfully")
},
Some(Duration::from_secs(10)), Some(Duration::from_secs(10)),
) )
} }
@@ -528,7 +546,7 @@ fn main() {
// The `Debug` implementation of `eyre::Result` prints a multi-line // The `Debug` implementation of `eyre::Result` prints a multi-line
// error message that includes all the 'causes' added with // error message that includes all the 'causes' added with
// `.with_context(...)` calls. // `.with_context(...)` calls.
println!("Error: {error:?}"); println!("{}", t!("Error: {error}", error = format!("{:?}", error)));
} }
exit(1); exit(1);
} }

View File

@@ -5,6 +5,7 @@ use std::process::Command;
use crate::config::Step; use crate::config::Step;
use color_eyre::eyre::{bail, Result}; use color_eyre::eyre::{bail, Result};
use rust_i18n::t;
use self_update_crate::backends::github::Update; use self_update_crate::backends::github::Update;
use self_update_crate::update::UpdateStatus; use self_update_crate::update::UpdateStatus;
@@ -15,10 +16,10 @@ use crate::error::Upgraded;
use crate::execution_context::ExecutionContext; use crate::execution_context::ExecutionContext;
pub fn self_update(ctx: &ExecutionContext) -> Result<()> { pub fn self_update(ctx: &ExecutionContext) -> Result<()> {
print_separator("Self update"); print_separator(t!("Self update"));
if ctx.run_type().dry() { if ctx.run_type().dry() {
println!("Would self-update"); println!("{}", t!("Would self-update"));
Ok(()) Ok(())
} else { } else {
let assume_yes = ctx.config().yes(Step::SelfUpdate); let assume_yes = ctx.config().yes(Step::SelfUpdate);
@@ -38,17 +39,17 @@ pub fn self_update(ctx: &ExecutionContext) -> Result<()> {
.update_extended()?; .update_extended()?;
if let UpdateStatus::Updated(release) = &result { if let UpdateStatus::Updated(release) = &result {
println!("\nTopgrade upgraded to {}:\n", release.version); println!("{}", t!("Topgrade upgraded to {version}:\n", version = release.version));
if let Some(body) = &release.body { if let Some(body) = &release.body {
println!("{body}"); println!("{body}");
} }
} else { } else {
println!("Topgrade is up-to-date"); println!("{}", t!("Topgrade is up-to-date"));
} }
{ {
if result.updated() { if result.updated() {
print_info("Respawning..."); print_info(t!("Respawning..."));
let mut command = Command::new(current_exe?); let mut command = Command::new(current_exe?);
command.args(env::args().skip(1)).env("TOPGRADE_NO_SELF_UPGRADE", ""); command.args(env::args().skip(1)).env("TOPGRADE_NO_SELF_UPGRADE", "");

View File

@@ -12,6 +12,7 @@ use crate::command::CommandExt;
use crate::error::{self, TopgradeError}; use crate::error::{self, TopgradeError};
use crate::terminal::print_separator; use crate::terminal::print_separator;
use crate::{execution_context::ExecutionContext, utils::require}; use crate::{execution_context::ExecutionContext, utils::require};
use rust_i18n::t;
// A string found in the output of docker for containers that weren't found in // A string found in the output of docker for containers that weren't found in
// the docker registry. We use this to gracefully handle and skip containers // the docker registry. We use this to gracefully handle and skip containers
@@ -43,7 +44,15 @@ impl Container {
impl Display for Container { impl Display for Container {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
// e.g., "`fedora:latest` for `linux/amd64`" // e.g., "`fedora:latest` for `linux/amd64`"
write!(f, "`{}` for `{}`", self.repo_tag, self.platform) write!(
f,
"{}",
t!(
"`{repo_tag}` for `{platform}`",
repo_tag = self.repo_tag,
platform = self.platform
)
)
} }
} }
@@ -120,11 +129,12 @@ fn list_containers(crt: &Path, ignored_containers: Option<&Vec<String>>) -> Resu
} }
pub fn run_containers(ctx: &ExecutionContext) -> Result<()> { pub fn run_containers(ctx: &ExecutionContext) -> Result<()> {
// Prefer podman, fall back to docker if not present // Check what runtime is specified in the config
let crt = require("podman").or_else(|_| require("docker"))?; let container_runtime = ctx.config().containers_runtime().to_string();
let crt = require(container_runtime)?;
debug!("Using container runtime '{}'", crt.display()); debug!("Using container runtime '{}'", crt.display());
print_separator("Containers"); print_separator(t!("Containers"));
let mut success = true; let mut success = true;
let containers = let containers =
list_containers(&crt, ctx.config().containers_ignored_tags()).context("Failed to list Docker containers")?; list_containers(&crt, ctx.config().containers_ignored_tags()).context("Failed to list Docker containers")?;

View File

@@ -4,6 +4,7 @@ use std::path::{Path, PathBuf};
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use etcetera::base_strategy::BaseStrategy; use etcetera::base_strategy::BaseStrategy;
use rust_i18n::t;
use crate::command::CommandExt; use crate::command::CommandExt;
use crate::execution_context::ExecutionContext; use crate::execution_context::ExecutionContext;
@@ -74,7 +75,10 @@ impl Emacs {
if let Some(doom) = &self.doom { if let Some(doom) = &self.doom {
Emacs::update_doom(doom, ctx)?; Emacs::update_doom(doom, ctx)?;
} }
let init_file = require_option(self.directory.as_ref(), String::from("Emacs directory does not exist"))? let init_file = require_option(
self.directory.as_ref(),
t!("Emacs directory does not exist").to_string(),
)?
.join("init.el") .join("init.el")
.require()?; .require()?;

View File

@@ -8,6 +8,7 @@ use std::{fs, io::Write};
use color_eyre::eyre::eyre; use color_eyre::eyre::eyre;
use color_eyre::eyre::Context; use color_eyre::eyre::Context;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use rust_i18n::t;
use semver::Version; use semver::Version;
use tempfile::tempfile_in; use tempfile::tempfile_in;
use tracing::{debug, error}; use tracing::{debug, error};
@@ -16,7 +17,7 @@ use crate::command::{CommandExt, Utf8Output};
use crate::execution_context::ExecutionContext; use crate::execution_context::ExecutionContext;
use crate::executor::ExecutorOutput; use crate::executor::ExecutorOutput;
use crate::terminal::{print_separator, shell}; use crate::terminal::{print_separator, shell};
use crate::utils::{self, check_is_python_2_or_shim, require, require_option, which, PathExt, REQUIRE_SUDO}; use crate::utils::{self, check_is_python_2_or_shim, get_require_sudo_string, require, require_option, which, PathExt};
use crate::Step; use crate::Step;
use crate::HOME_DIR; use crate::HOME_DIR;
use crate::{ use crate::{
@@ -121,6 +122,7 @@ pub fn run_rubygems(ctx: &ExecutionContext) -> Result<()> {
print_separator("RubyGems"); print_separator("RubyGems");
let gem_path_str = gem.as_os_str(); let gem_path_str = gem.as_os_str();
if gem_path_str.to_str().unwrap().contains("asdf") if gem_path_str.to_str().unwrap().contains("asdf")
|| gem_path_str.to_str().unwrap().contains("mise")
|| gem_path_str.to_str().unwrap().contains(".rbenv") || gem_path_str.to_str().unwrap().contains(".rbenv")
|| gem_path_str.to_str().unwrap().contains(".rvm") || gem_path_str.to_str().unwrap().contains(".rvm")
{ {
@@ -129,7 +131,7 @@ pub fn run_rubygems(ctx: &ExecutionContext) -> Result<()> {
.args(["update", "--system"]) .args(["update", "--system"])
.status_checked()?; .status_checked()?;
} else { } else {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
if !Path::new("/usr/lib/ruby/vendor_ruby/rubygems/defaults/operating_system.rb").exists() { if !Path::new("/usr/lib/ruby/vendor_ruby/rubygems/defaults/operating_system.rb").exists() {
ctx.run_type() ctx.run_type()
.execute(sudo) .execute(sudo)
@@ -158,7 +160,7 @@ pub fn run_haxelib_update(ctx: &ExecutionContext) -> Result<()> {
let mut command = if directory_writable { let mut command = if directory_writable {
ctx.run_type().execute(&haxelib) ctx.run_type().execute(&haxelib)
} else { } else {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
let mut c = ctx.run_type().execute(sudo); let mut c = ctx.run_type().execute(sudo);
c.arg(&haxelib); c.arg(&haxelib);
c c
@@ -223,6 +225,20 @@ pub fn run_apm(ctx: &ExecutionContext) -> Result<()> {
.status_checked() .status_checked()
} }
pub fn run_aqua(ctx: &ExecutionContext) -> Result<()> {
let aqua = require("aqua")?;
print_separator("Aqua");
if ctx.run_type().dry() {
println!("{}", t!("Updating aqua ..."));
println!("{}", t!("Updating aqua installed cli tools ..."));
Ok(())
} else {
ctx.run_type().execute(&aqua).arg("update-aqua").status_checked()?;
ctx.run_type().execute(&aqua).arg("update").status_checked()
}
}
pub fn run_rustup(ctx: &ExecutionContext) -> Result<()> { pub fn run_rustup(ctx: &ExecutionContext) -> Result<()> {
let rustup = require("rustup")?; let rustup = require("rustup")?;
@@ -349,7 +365,7 @@ pub fn run_vcpkg_update(ctx: &ExecutionContext) -> Result<()> {
let mut command = if is_root_install { let mut command = if is_root_install {
ctx.run_type().execute(&vcpkg) ctx.run_type().execute(&vcpkg)
} else { } else {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
let mut c = ctx.run_type().execute(sudo); let mut c = ctx.run_type().execute(sudo);
c.arg(&vcpkg); c.arg(&vcpkg);
c c
@@ -432,17 +448,16 @@ pub fn run_conda_update(ctx: &ExecutionContext) -> Result<()> {
command.status_checked() command.status_checked()
} }
pub fn run_pixi_update(ctx: &ExecutionContext) -> Result<()> {
let pixi = require("pixi")?;
print_separator("Pixi");
ctx.run_type().execute(pixi).args(["self-update"]).status_checked()
}
pub fn run_mamba_update(ctx: &ExecutionContext) -> Result<()> { pub fn run_mamba_update(ctx: &ExecutionContext) -> Result<()> {
let mamba = require("mamba")?; 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"); print_separator("Mamba");
let mut command = ctx.run_type().execute(mamba); let mut command = ctx.run_type().execute(mamba);
@@ -651,7 +666,7 @@ pub fn run_tlmgr_update(ctx: &ExecutionContext) -> Result<()> {
let mut command = if directory_writable { let mut command = if directory_writable {
ctx.run_type().execute(&tlmgr) ctx.run_type().execute(&tlmgr)
} else { } else {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
let mut c = ctx.run_type().execute(sudo); let mut c = ctx.run_type().execute(sudo);
c.arg(&tlmgr); c.arg(&tlmgr);
c c
@@ -708,19 +723,22 @@ pub fn run_composer_update(ctx: &ExecutionContext) -> Result<()> {
let composer_home = Command::new(&composer) let composer_home = Command::new(&composer)
.args(["global", "config", "--absolute", "--quiet", "home"]) .args(["global", "config", "--absolute", "--quiet", "home"])
.output_checked_utf8() .output_checked_utf8()
.map_err(|e| (SkipStep(format!("Error getting the composer directory: {e}")))) .map_err(|e| (SkipStep(t!("Error getting the composer directory: {error}", error = e).to_string())))
.map(|s| PathBuf::from(s.stdout.trim()))? .map(|s| PathBuf::from(s.stdout.trim()))?
.require()?; .require()?;
if !composer_home.is_descendant_of(&HOME_DIR) { if !composer_home.is_descendant_of(&HOME_DIR) {
return Err(SkipStep(format!( return Err(SkipStep(
"Composer directory {} isn't a descendant of the user's home directory", t!(
composer_home.display() "Composer directory {composer_home} isn't a descendant of the user's home directory",
)) composer_home = composer_home.display()
)
.to_string(),
)
.into()); .into());
} }
print_separator("Composer"); print_separator(t!("Composer"));
if ctx.config().composer_self_update() { if ctx.config().composer_self_update() {
cfg_if::cfg_if! { cfg_if::cfg_if! {
@@ -732,7 +750,7 @@ pub fn run_composer_update(ctx: &ExecutionContext) -> Result<()> {
}; };
if has_update { if has_update {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
ctx.run_type() ctx.run_type()
.execute(sudo) .execute(sudo)
.arg(&composer) .arg(&composer)
@@ -776,9 +794,10 @@ pub fn run_dotnet_upgrade(ctx: &ExecutionContext) -> Result<()> {
{ {
Ok(output) => output, Ok(output) => output,
Err(_) => { Err(_) => {
return Err(SkipStep(String::from( return Err(SkipStep(
"Error running `dotnet tool list`. This is expected when a dotnet runtime is installed but no SDK.", t!("Error running `dotnet tool list`. This is expected when a dotnet runtime is installed but no SDK.")
)) .to_string(),
)
.into()); .into());
} }
}; };
@@ -806,7 +825,7 @@ pub fn run_dotnet_upgrade(ctx: &ExecutionContext) -> Result<()> {
.peekable(); .peekable();
if packages.peek().is_none() { if packages.peek().is_none() {
return Err(SkipStep(String::from("No dotnet global tools installed")).into()); return Err(SkipStep(t!("No dotnet global tools installed").to_string()).into());
} }
print_separator(".NET"); print_separator(".NET");
@@ -817,7 +836,7 @@ pub fn run_dotnet_upgrade(ctx: &ExecutionContext) -> Result<()> {
.execute(&dotnet) .execute(&dotnet)
.args(["tool", "update", package_name, "--global"]) .args(["tool", "update", package_name, "--global"])
.status_checked() .status_checked()
.with_context(|| format!("Failed to update .NET package {package_name}"))?; .with_context(|| format!("Failed to update .NET package {:?}", package_name))?;
} }
Ok(()) Ok(())
@@ -846,7 +865,7 @@ pub fn run_helix_grammars(ctx: &ExecutionContext) -> Result<()> {
pub fn run_raco_update(ctx: &ExecutionContext) -> Result<()> { pub fn run_raco_update(ctx: &ExecutionContext) -> Result<()> {
let raco = require("raco")?; let raco = require("raco")?;
print_separator("Racket Package Manager"); print_separator(t!("Racket Package Manager"));
ctx.run_type() ctx.run_type()
.execute(raco) .execute(raco)
@@ -874,10 +893,10 @@ pub fn run_ghcli_extensions_upgrade(ctx: &ExecutionContext) -> Result<()> {
let result = Command::new(&gh).args(["extensions", "list"]).output_checked_utf8(); let result = Command::new(&gh).args(["extensions", "list"]).output_checked_utf8();
if result.is_err() { if result.is_err() {
debug!("GH result {:?}", result); debug!("GH result {:?}", result);
return Err(SkipStep(String::from("GH failed")).into()); return Err(SkipStep(t!("GH failed").to_string()).into());
} }
print_separator("GitHub CLI Extensions"); print_separator(t!("GitHub CLI Extensions"));
ctx.run_type() ctx.run_type()
.execute(&gh) .execute(&gh)
.args(["extension", "upgrade", "--all"]) .args(["extension", "upgrade", "--all"])
@@ -887,7 +906,7 @@ pub fn run_ghcli_extensions_upgrade(ctx: &ExecutionContext) -> Result<()> {
pub fn update_julia_packages(ctx: &ExecutionContext) -> Result<()> { pub fn update_julia_packages(ctx: &ExecutionContext) -> Result<()> {
let julia = require("julia")?; let julia = require("julia")?;
print_separator("Julia Packages"); print_separator(t!("Julia Packages"));
ctx.run_type() ctx.run_type()
.execute(julia) .execute(julia)
@@ -904,7 +923,7 @@ pub fn run_helm_repo_update(ctx: &ExecutionContext) -> Result<()> {
let mut success = true; let mut success = true;
let mut exec = ctx.run_type().execute(helm); let mut exec = ctx.run_type().execute(helm);
if let Err(e) = exec.arg("repo").arg("update").status_checked() { if let Err(e) = exec.arg("repo").arg("update").status_checked() {
error!("Updating repositories failed: {}", e); error!("Updating repositories failed: {e}");
success = match exec.output_checked_utf8() { success = match exec.output_checked_utf8() {
Ok(s) => s.stdout.contains(no_repo) || s.stderr.contains(no_repo), Ok(s) => s.stdout.contains(no_repo) || s.stderr.contains(no_repo),
Err(e) => match e.downcast_ref::<TopgradeError>() { Err(e) => match e.downcast_ref::<TopgradeError>() {
@@ -937,7 +956,7 @@ pub fn run_bob(ctx: &ExecutionContext) -> Result<()> {
} }
pub fn run_certbot(ctx: &ExecutionContext) -> Result<()> { pub fn run_certbot(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
let certbot = require("certbot")?; let certbot = require("certbot")?;
print_separator("Certbot"); print_separator("Certbot");
@@ -954,7 +973,7 @@ pub fn run_certbot(ctx: &ExecutionContext) -> Result<()> {
/// doc: https://docs.clamav.net/manual/Usage/SignatureManagement.html#freshclam /// doc: https://docs.clamav.net/manual/Usage/SignatureManagement.html#freshclam
pub fn run_freshclam(ctx: &ExecutionContext) -> Result<()> { pub fn run_freshclam(ctx: &ExecutionContext) -> Result<()> {
let freshclam = require("freshclam")?; let freshclam = require("freshclam")?;
print_separator("Update ClamAV Database(FreshClam)"); print_separator(t!("Update ClamAV Database(FreshClam)"));
ctx.run_type().execute(freshclam).status_checked() ctx.run_type().execute(freshclam).status_checked()
} }
@@ -987,7 +1006,7 @@ pub fn run_lensfun_update_data(ctx: &ExecutionContext) -> Result<()> {
const EXIT_CODE_WHEN_NO_UPDATE: i32 = 1; const EXIT_CODE_WHEN_NO_UPDATE: i32 = 1;
if ctx.config().lensfun_use_sudo() { if ctx.config().lensfun_use_sudo() {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
print_separator(SEPARATOR); print_separator(SEPARATOR);
ctx.run_type() ctx.run_type()
.execute(sudo) .execute(sudo)
@@ -1002,3 +1021,46 @@ pub fn run_lensfun_update_data(ctx: &ExecutionContext) -> Result<()> {
.status_checked_with_codes(&[EXIT_CODE_WHEN_NO_UPDATE]) .status_checked_with_codes(&[EXIT_CODE_WHEN_NO_UPDATE])
} }
} }
pub fn run_poetry(ctx: &ExecutionContext) -> Result<()> {
let poetry = require("poetry")?;
print_separator("Poetry");
ctx.run_type().execute(poetry).args(["self", "update"]).status_checked()
}
pub fn run_uv(ctx: &ExecutionContext) -> Result<()> {
let uv_exec = require("uv")?;
print_separator("uv");
ctx.run_type()
.execute(&uv_exec)
.args(["self", "update"])
.status_checked()
.ok();
// ignoring self-update errors, because they are likely due to uv's
// installation being managed by another package manager, in which
// case another step will handle the update.
ctx.run_type()
.execute(&uv_exec)
.args(["tool", "upgrade", "--all"])
.status_checked()
}
/// Involve `zvm upgrade` to update ZVM
pub fn run_zvm(ctx: &ExecutionContext) -> Result<()> {
let zvm = require("zvm")?;
print_separator("ZVM");
ctx.run_type().execute(zvm).arg("upgrade").status_checked()
}
pub fn run_bun(ctx: &ExecutionContext) -> Result<()> {
let bun = require("bun")?;
print_separator("Bun");
ctx.run_type().execute(bun).arg("upgrade").status_checked()
}

View File

@@ -20,6 +20,7 @@ use crate::terminal::print_separator;
use crate::utils::{require, PathExt}; use crate::utils::{require, PathExt};
use crate::{error::SkipStep, terminal::print_warning, HOME_DIR}; use crate::{error::SkipStep, terminal::print_warning, HOME_DIR};
use etcetera::base_strategy::BaseStrategy; use etcetera::base_strategy::BaseStrategy;
use rust_i18n::t;
#[cfg(unix)] #[cfg(unix)]
use crate::XDG_DIRS; use crate::XDG_DIRS;
@@ -100,16 +101,18 @@ pub fn run_git_pull(ctx: &ExecutionContext) -> Result<()> {
// NOTE: this should be executed **before** skipping the Git step or the // 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 // user won't receive this warning in the cases where all the paths configured
// are bad patterns. // are bad patterns.
repos repos.bad_patterns.iter().for_each(|pattern| {
.bad_patterns print_warning(t!(
.iter() "Path {pattern} did not contain any git repositories",
.for_each(|pattern| print_warning(format!("Path {pattern} did not contain any git repositories"))); pattern = pattern
))
});
if repos.is_repos_empty() { if repos.is_repos_empty() {
return Err(SkipStep(String::from("No repositories to pull")).into()); return Err(SkipStep(t!("No repositories to pull").to_string()).into());
} }
print_separator("Git repositories"); print_separator(t!("Git repositories"));
repos.pull_repos(ctx) repos.pull_repos(ctx)
} }
@@ -143,7 +146,7 @@ fn get_head_revision<P: AsRef<Path>>(git: &Path, repo: P) -> Option<String> {
.output_checked_utf8() .output_checked_utf8()
.map(|output| output.stdout.trim().to_string()) .map(|output| output.stdout.trim().to_string())
.map_err(|e| { .map_err(|e| {
error!("Error getting revision for {}: {}", repo.as_ref().display(), e); error!("Error getting revision for {}: {e}", repo.as_ref().display(),);
e e
}) })
@@ -206,7 +209,7 @@ impl RepoStep {
} }
Err(e) => match e.kind() { Err(e) => match e.kind() {
io::ErrorKind::NotFound => debug!("{} does not exist", path.as_ref().display()), io::ErrorKind::NotFound => debug!("{} does not exist", path.as_ref().display()),
_ => error!("Error looking for {}: {}", path.as_ref().display(), e), _ => error!("Error looking for {}: {e}", path.as_ref().display(),),
}, },
} }
@@ -236,7 +239,7 @@ impl RepoStep {
res.map(|output| output.stdout.lines().count() > 0) res.map(|output| output.stdout.lines().count() > 0)
.map_err(|e| { .map_err(|e| {
error!("Error getting remotes for {}: {}", repo.as_ref().display(), e); error!("Error getting remotes for {}: {e}", repo.as_ref().display());
e e
}) })
.ok() .ok()
@@ -264,7 +267,7 @@ impl RepoStep {
} }
} }
Err(e) => { Err(e) => {
error!("Error in path {}", e); error!("Error in path {e}");
} }
} }
} }
@@ -273,7 +276,7 @@ impl RepoStep {
self.bad_patterns.push(String::from(pattern)); self.bad_patterns.push(String::from(pattern));
} }
} else { } else {
error!("Bad glob pattern: {}", pattern); error!("Bad glob pattern: {pattern}");
} }
} }
@@ -296,7 +299,7 @@ impl RepoStep {
let before_revision = get_head_revision(&self.git, &repo); let before_revision = get_head_revision(&self.git, &repo);
if ctx.config().verbose() { if ctx.config().verbose() {
println!("{} {}", style("Pulling").cyan().bold(), repo.as_ref().display()); println!("{} {}", style(t!("Pulling")).cyan().bold(), repo.as_ref().display());
} }
let mut command = AsyncCommand::new(&self.git); let mut command = AsyncCommand::new(&self.git);
@@ -322,13 +325,18 @@ impl RepoStep {
.wrap_err_with(|| format!("Failed to pull {}", repo.as_ref().display())); .wrap_err_with(|| format!("Failed to pull {}", repo.as_ref().display()));
if result.is_err() { if result.is_err() {
println!("{} pulling {}", style("Failed").red().bold(), repo.as_ref().display()); println!(
"{} {} {}",
style(t!("Failed")).red().bold(),
t!("pulling"),
repo.as_ref().display()
);
} else { } else {
let after_revision = get_head_revision(&self.git, repo.as_ref()); let after_revision = get_head_revision(&self.git, repo.as_ref());
match (&before_revision, &after_revision) { match (&before_revision, &after_revision) {
(Some(before), Some(after)) if before != after => { (Some(before), Some(after)) if before != after => {
println!("{} {}", style("Changed").yellow().bold(), repo.as_ref().display()); println!("{} {}", style(t!("Changed")).yellow().bold(), repo.as_ref().display());
Command::new(&self.git) Command::new(&self.git)
.stdin(Stdio::null()) .stdin(Stdio::null())
@@ -345,7 +353,7 @@ impl RepoStep {
} }
_ => { _ => {
if ctx.config().verbose() { if ctx.config().verbose() {
println!("{} {}", style("Up-to-date").green().bold(), repo.as_ref().display()); println!("{} {}", style(t!("Up-to-date")).green().bold(), repo.as_ref().display());
} }
} }
} }
@@ -363,15 +371,16 @@ impl RepoStep {
if ctx.run_type().dry() { if ctx.run_type().dry() {
self.repos self.repos
.iter() .iter()
.for_each(|repo| println!("Would pull {}", repo.display())); .for_each(|repo| println!("{}", t!("Would pull {repo}", repo = repo.display())));
return Ok(()); return Ok(());
} }
if !ctx.config().verbose() { if !ctx.config().verbose() {
println!( println!(
"\n{} updated repositories will be shown...\n", "\n{} {}\n",
style("Only").green().bold() style(t!("Only")).green().bold(),
t!("updated repositories will be shown...")
); );
} }
@@ -381,9 +390,10 @@ impl RepoStep {
.filter(|repo| match self.has_remotes(repo) { .filter(|repo| match self.has_remotes(repo) {
Some(false) => { Some(false) => {
println!( println!(
"{} {} because it has no remotes", "{} {} {}",
style("Skipping").yellow().bold(), style(t!("Skipping")).yellow().bold(),
repo.display() repo.display(),
t!("because it has no remotes")
); );
false false
} }

View File

@@ -1,6 +1,7 @@
use crate::terminal::print_separator; use crate::terminal::print_separator;
use crate::utils::require; use crate::utils::require;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use rust_i18n::t;
use crate::execution_context::ExecutionContext; use crate::execution_context::ExecutionContext;
@@ -17,7 +18,7 @@ pub fn upgrade_kak_plug(ctx: &ExecutionContext) -> Result<()> {
.args(["-ui", "dummy", "-e", UPGRADE_KAK]) .args(["-ui", "dummy", "-e", UPGRADE_KAK])
.output()?; .output()?;
println!("Plugins upgraded"); println!("{}", t!("Plugins upgraded"));
Ok(()) Ok(())
} }

View File

@@ -4,16 +4,17 @@ use std::os::unix::fs::MetadataExt;
use std::path::PathBuf; use std::path::PathBuf;
use std::process::Command; use std::process::Command;
use crate::utils::{require_option, REQUIRE_SUDO}; use crate::utils::{get_require_sudo_string, require_option};
use crate::HOME_DIR; use crate::HOME_DIR;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
use nix::unistd::Uid; use nix::unistd::Uid;
use rust_i18n::t;
use semver::Version; use semver::Version;
use tracing::debug; use tracing::debug;
use crate::command::CommandExt; use crate::command::CommandExt;
use crate::terminal::print_separator; use crate::terminal::{print_info, print_separator};
use crate::utils::{require, PathExt}; use crate::utils::{require, PathExt};
use crate::{error::SkipStep, execution_context::ExecutionContext}; use crate::{error::SkipStep, execution_context::ExecutionContext};
@@ -92,7 +93,7 @@ impl NPM {
fn upgrade(&self, ctx: &ExecutionContext, use_sudo: bool) -> Result<()> { fn upgrade(&self, ctx: &ExecutionContext, use_sudo: bool) -> Result<()> {
let args = ["update", self.global_location_arg()]; let args = ["update", self.global_location_arg()];
if use_sudo { if use_sudo {
let sudo = require_option(ctx.sudo().clone(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().clone(), get_require_sudo_string())?;
ctx.run_type() ctx.run_type()
.execute(sudo) .execute(sudo)
.arg(&self.command) .arg(&self.command)
@@ -156,7 +157,7 @@ impl Yarn {
let args = ["global", "upgrade"]; let args = ["global", "upgrade"];
if use_sudo { if use_sudo {
let sudo = require_option(ctx.sudo().clone(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().clone(), get_require_sudo_string())?;
ctx.run_type() ctx.run_type()
.execute(sudo) .execute(sudo)
.arg(self.yarn.as_ref().unwrap_or(&self.command)) .arg(self.yarn.as_ref().unwrap_or(&self.command))
@@ -214,7 +215,7 @@ fn should_use_sudo_yarn(yarn: &Yarn, ctx: &ExecutionContext) -> Result<bool> {
pub fn run_npm_upgrade(ctx: &ExecutionContext) -> Result<()> { pub fn run_npm_upgrade(ctx: &ExecutionContext) -> Result<()> {
let npm = require("npm").map(|b| NPM::new(b, NPMVariant::Npm))?; let npm = require("npm").map(|b| NPM::new(b, NPMVariant::Npm))?;
print_separator("Node Package Manager"); print_separator(t!("Node Package Manager"));
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
{ {
@@ -230,7 +231,7 @@ pub fn run_npm_upgrade(ctx: &ExecutionContext) -> Result<()> {
pub fn run_pnpm_upgrade(ctx: &ExecutionContext) -> Result<()> { pub fn run_pnpm_upgrade(ctx: &ExecutionContext) -> Result<()> {
let pnpm = require("pnpm").map(|b| NPM::new(b, NPMVariant::Pnpm))?; let pnpm = require("pnpm").map(|b| NPM::new(b, NPMVariant::Pnpm))?;
print_separator("Performant Node Package Manager"); print_separator(t!("Performant Node Package Manager"));
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
{ {
@@ -251,7 +252,7 @@ pub fn run_yarn_upgrade(ctx: &ExecutionContext) -> Result<()> {
return Ok(()); return Ok(());
} }
print_separator("Yarn Package Manager"); print_separator(t!("Yarn Package Manager"));
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
{ {
@@ -269,10 +270,55 @@ pub fn deno_upgrade(ctx: &ExecutionContext) -> Result<()> {
let deno_dir = HOME_DIR.join(".deno"); let deno_dir = HOME_DIR.join(".deno");
if !deno.canonicalize()?.is_descendant_of(&deno_dir) { if !deno.canonicalize()?.is_descendant_of(&deno_dir) {
let skip_reason = SkipStep("Deno installed outside of .deno directory".to_string()); let skip_reason = SkipStep(t!("Deno installed outside of .deno directory").to_string());
return Err(skip_reason.into()); return Err(skip_reason.into());
} }
print_separator("Deno"); print_separator("Deno");
ctx.run_type().execute(&deno).arg("upgrade").status_checked() ctx.run_type().execute(&deno).arg("upgrade").status_checked()
} }
/// There is no `volta upgrade` command, so we need to upgrade each package
pub fn run_volta_packages_upgrade(ctx: &ExecutionContext) -> Result<()> {
let volta = require("volta")?;
print_separator("Volta");
if ctx.run_type().dry() {
print_info(t!("Updating Volta packages..."));
return Ok(());
}
let list_output = ctx
.run_type()
.execute(&volta)
.args(["list", "--format=plain"])
.output_checked_utf8()?
.stdout;
let installed_packages: Vec<&str> = list_output
.lines()
.filter_map(|line| {
// format is 'kind package@version ...'
let mut parts = line.split_whitespace();
parts.next();
let package_part = parts.next()?;
let version_index = package_part.rfind('@').unwrap_or(package_part.len());
Some(package_part[..version_index].trim())
})
.collect();
if installed_packages.is_empty() {
print_info(t!("No packages installed with Volta"));
return Ok(());
}
for package in installed_packages.iter() {
ctx.run_type()
.execute(&volta)
.args(["install", package])
.status_checked()?;
}
Ok(())
}

View File

@@ -4,12 +4,13 @@ use std::path::{Path, PathBuf};
use color_eyre::eyre; use color_eyre::eyre;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use rust_i18n::t;
use walkdir::WalkDir; use walkdir::WalkDir;
use crate::command::CommandExt; use crate::command::CommandExt;
use crate::error::TopgradeError; use crate::error::TopgradeError;
use crate::execution_context::ExecutionContext; use crate::execution_context::ExecutionContext;
use crate::sudo::Sudo; use crate::utils::require_option;
use crate::utils::which; use crate::utils::which;
use crate::{config, Step}; use crate::{config, Step};
@@ -144,13 +145,13 @@ impl Trizen {
} }
pub struct Pacman { pub struct Pacman {
sudo: Sudo,
executable: PathBuf, executable: PathBuf,
} }
impl ArchPackageManager for Pacman { impl ArchPackageManager for Pacman {
fn upgrade(&self, ctx: &ExecutionContext) -> Result<()> { fn upgrade(&self, ctx: &ExecutionContext) -> Result<()> {
let mut command = ctx.run_type().execute(&self.sudo); let sudo = require_option(ctx.sudo().as_ref(), "sudo is required to run pacman".into())?;
let mut command = ctx.run_type().execute(sudo);
command command
.arg(&self.executable) .arg(&self.executable)
.arg("-Syu") .arg("-Syu")
@@ -161,7 +162,7 @@ impl ArchPackageManager for Pacman {
command.status_checked()?; command.status_checked()?;
if ctx.config().cleanup() { if ctx.config().cleanup() {
let mut command = ctx.run_type().execute(&self.sudo); let mut command = ctx.run_type().execute(sudo);
command.arg(&self.executable).arg("-Scc"); command.arg(&self.executable).arg("-Scc");
if ctx.config().yes(Step::System) { if ctx.config().yes(Step::System) {
command.arg("--noconfirm"); command.arg("--noconfirm");
@@ -174,10 +175,9 @@ impl ArchPackageManager for Pacman {
} }
impl Pacman { impl Pacman {
pub fn get(ctx: &ExecutionContext) -> Option<Self> { pub fn get() -> Option<Self> {
Some(Self { Some(Self {
executable: which("powerpill").unwrap_or_else(|| PathBuf::from("pacman")), executable: which("powerpill").unwrap_or_else(|| PathBuf::from("pacman")),
sudo: ctx.sudo().to_owned()?,
}) })
} }
} }
@@ -263,46 +263,75 @@ impl ArchPackageManager for Pamac {
pub struct Aura { pub struct Aura {
executable: PathBuf, executable: PathBuf,
sudo: Sudo,
} }
impl Aura { impl Aura {
fn get(ctx: &ExecutionContext) -> Option<Self> { fn get() -> Option<Self> {
Some(Self { Some(Self {
executable: which("aura")?, executable: which("aura")?,
sudo: ctx.sudo().to_owned()?,
}) })
} }
} }
impl ArchPackageManager for Aura { impl ArchPackageManager for Aura {
fn upgrade(&self, ctx: &ExecutionContext) -> Result<()> { fn upgrade(&self, ctx: &ExecutionContext) -> Result<()> {
let sudo = which("sudo").unwrap_or_default(); use semver::Version;
let mut aur_update = ctx.run_type().execute(&sudo);
if sudo.ends_with("sudo") { let version_cmd_output = ctx
aur_update .run_type()
.arg(&self.executable) .execute(&self.executable)
.arg("--version")
.output_checked_utf8()?;
// Output will be something like: "aura x.x.x\n"
let version_cmd_stdout = version_cmd_output.stdout;
let version_str = version_cmd_stdout.trim_start_matches("aura ").trim_end();
let version = Version::parse(version_str).expect("invalid version");
// Aura, since version 4.0.6, no longer needs sudo.
//
// https://github.com/fosskers/aura/releases/tag/v4.0.6
let version_no_sudo = Version::new(4, 0, 6);
if version >= version_no_sudo {
let mut cmd = ctx.run_type().execute(&self.executable);
cmd.arg("-Au")
.args(ctx.config().aura_aur_arguments().split_whitespace());
if ctx.config().yes(Step::System) {
cmd.arg("--noconfirm");
}
cmd.status_checked()?;
let mut cmd = ctx.run_type().execute(&self.executable);
cmd.arg("-Syu")
.args(ctx.config().aura_pacman_arguments().split_whitespace());
if ctx.config().yes(Step::System) {
cmd.arg("--noconfirm");
}
cmd.status_checked()?;
} else {
let sudo = crate::utils::require_option(
ctx.sudo().as_ref(),
t!("Aura(<0.4.6) requires sudo installed to work with AUR packages").to_string(),
)?;
let mut cmd = ctx.run_type().execute(sudo);
cmd.arg(&self.executable)
.arg("-Au") .arg("-Au")
.args(ctx.config().aura_aur_arguments().split_whitespace()); .args(ctx.config().aura_aur_arguments().split_whitespace());
if ctx.config().yes(Step::System) { if ctx.config().yes(Step::System) {
aur_update.arg("--noconfirm"); cmd.arg("--noconfirm");
} }
cmd.status_checked()?;
aur_update.status_checked()?; let mut cmd = ctx.run_type().execute(sudo);
} else { cmd.arg(&self.executable)
println!("Aura requires sudo installed to work with AUR packages")
}
let mut pacman_update = ctx.run_type().execute(&self.sudo);
pacman_update
.arg(&self.executable)
.arg("-Syu") .arg("-Syu")
.args(ctx.config().aura_pacman_arguments().split_whitespace()); .args(ctx.config().aura_pacman_arguments().split_whitespace());
if ctx.config().yes(Step::System) { if ctx.config().yes(Step::System) {
pacman_update.arg("--noconfirm"); cmd.arg("--noconfirm");
}
cmd.status_checked()?;
} }
pacman_update.status_checked()?;
Ok(()) Ok(())
} }
@@ -323,16 +352,16 @@ pub fn get_arch_package_manager(ctx: &ExecutionContext) -> Option<Box<dyn ArchPa
.or_else(|| Trizen::get().map(box_package_manager)) .or_else(|| Trizen::get().map(box_package_manager))
.or_else(|| Pikaur::get().map(box_package_manager)) .or_else(|| Pikaur::get().map(box_package_manager))
.or_else(|| Pamac::get().map(box_package_manager)) .or_else(|| Pamac::get().map(box_package_manager))
.or_else(|| Pacman::get(ctx).map(box_package_manager)) .or_else(|| Pacman::get().map(box_package_manager))
.or_else(|| Aura::get(ctx).map(box_package_manager)), .or_else(|| Aura::get().map(box_package_manager)),
config::ArchPackageManager::GarudaUpdate => GarudaUpdate::get().map(box_package_manager), config::ArchPackageManager::GarudaUpdate => GarudaUpdate::get().map(box_package_manager),
config::ArchPackageManager::Trizen => Trizen::get().map(box_package_manager), config::ArchPackageManager::Trizen => Trizen::get().map(box_package_manager),
config::ArchPackageManager::Paru => YayParu::get("paru", &pacman).map(box_package_manager), config::ArchPackageManager::Paru => YayParu::get("paru", &pacman).map(box_package_manager),
config::ArchPackageManager::Yay => YayParu::get("yay", &pacman).map(box_package_manager), config::ArchPackageManager::Yay => YayParu::get("yay", &pacman).map(box_package_manager),
config::ArchPackageManager::Pacman => Pacman::get(ctx).map(box_package_manager), config::ArchPackageManager::Pacman => Pacman::get().map(box_package_manager),
config::ArchPackageManager::Pikaur => Pikaur::get().map(box_package_manager), config::ArchPackageManager::Pikaur => Pikaur::get().map(box_package_manager),
config::ArchPackageManager::Pamac => Pamac::get().map(box_package_manager), config::ArchPackageManager::Pamac => Pamac::get().map(box_package_manager),
config::ArchPackageManager::Aura => Aura::get(ctx).map(box_package_manager), config::ArchPackageManager::Aura => Aura::get().map(box_package_manager),
} }
} }
@@ -355,7 +384,7 @@ pub fn show_pacnew() {
.peekable(); .peekable();
if iter.peek().is_some() { if iter.peek().is_some() {
println!("\nPacman backup configuration files found:"); println!("\n{}", t!("Pacman backup configuration files found:"));
for entry in iter { for entry in iter {
println!("{}", entry.path().display()); println!("{}", entry.path().display());

View File

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

View File

@@ -1,14 +1,15 @@
use crate::command::CommandExt; use crate::command::CommandExt;
use crate::execution_context::ExecutionContext; use crate::execution_context::ExecutionContext;
use crate::terminal::print_separator; use crate::terminal::print_separator;
use crate::utils::{require_option, REQUIRE_SUDO}; use crate::utils::{get_require_sudo_string, require_option};
use crate::Step; use crate::Step;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use rust_i18n::t;
use std::process::Command; use std::process::Command;
pub fn upgrade_freebsd(ctx: &ExecutionContext) -> Result<()> { pub fn upgrade_freebsd(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
print_separator("FreeBSD Update"); print_separator(t!("FreeBSD Update"));
ctx.run_type() ctx.run_type()
.execute(sudo) .execute(sudo)
.args(["/usr/sbin/freebsd-update", "fetch", "install"]) .args(["/usr/sbin/freebsd-update", "fetch", "install"])
@@ -16,8 +17,8 @@ pub fn upgrade_freebsd(ctx: &ExecutionContext) -> Result<()> {
} }
pub fn upgrade_packages(ctx: &ExecutionContext) -> Result<()> { pub fn upgrade_packages(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
print_separator("FreeBSD Packages"); print_separator(t!("FreeBSD Packages"));
let mut command = ctx.run_type().execute(sudo); let mut command = ctx.run_type().execute(sudo);
@@ -29,9 +30,9 @@ pub fn upgrade_packages(ctx: &ExecutionContext) -> Result<()> {
} }
pub fn audit_packages(ctx: &ExecutionContext) -> Result<()> { pub fn audit_packages(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
print_separator("FreeBSD Audit"); print_separator(t!("FreeBSD Audit"));
Command::new(sudo) Command::new(sudo)
.args(["/usr/sbin/pkg", "audit", "-Fr"]) .args(["/usr/sbin/pkg", "audit", "-Fr"])

View File

@@ -3,6 +3,7 @@ use std::process::Command;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use ini::Ini; use ini::Ini;
use rust_i18n::t;
use tracing::{debug, warn}; use tracing::{debug, warn};
use crate::command::CommandExt; use crate::command::CommandExt;
@@ -11,7 +12,7 @@ use crate::execution_context::ExecutionContext;
use crate::steps::generic::is_wsl; use crate::steps::generic::is_wsl;
use crate::steps::os::archlinux; use crate::steps::os::archlinux;
use crate::terminal::{print_separator, prompt_yesno}; use crate::terminal::{print_separator, prompt_yesno};
use crate::utils::{require, require_option, which, PathExt, REQUIRE_SUDO}; use crate::utils::{get_require_sudo_string, require, require_option, which, PathExt};
use crate::{Step, HOME_DIR}; use crate::{Step, HOME_DIR};
static OS_RELEASE_PATH: &str = "/etc/os-release"; static OS_RELEASE_PATH: &str = "/etc/os-release";
@@ -30,6 +31,7 @@ pub enum Distribution {
FedoraImmutable, FedoraImmutable,
Debian, Debian,
Gentoo, Gentoo,
NILRT,
OpenMandriva, OpenMandriva,
OpenSuseTumbleweed, OpenSuseTumbleweed,
PCLinuxOS, PCLinuxOS,
@@ -71,12 +73,13 @@ impl Distribution {
}; };
} }
Some("nilrt") => Distribution::NILRT,
Some("nobara") => Distribution::Nobara, Some("nobara") => Distribution::Nobara,
Some("void") => Distribution::Void, Some("void") => Distribution::Void,
Some("debian") | Some("pureos") | Some("Deepin") | Some("linuxmint") => Distribution::Debian, Some("debian") | Some("pureos") | Some("Deepin") | Some("linuxmint") => Distribution::Debian,
Some("arch") | Some("manjaro-arm") | Some("garuda") | Some("artix") => Distribution::Arch, Some("arch") | Some("manjaro-arm") | Some("garuda") | Some("artix") => Distribution::Arch,
Some("solus") => Distribution::Solus, Some("solus") => Distribution::Solus,
Some("gentoo") => Distribution::Gentoo, Some("gentoo") | Some("funtoo") => Distribution::Gentoo,
Some("exherbo") => Distribution::Exherbo, Some("exherbo") => Distribution::Exherbo,
Some("nixos") => Distribution::NixOS, Some("nixos") => Distribution::NixOS,
Some("opensuse-microos") => Distribution::SuseMicro, Some("opensuse-microos") => Distribution::SuseMicro,
@@ -133,7 +136,7 @@ impl Distribution {
} }
pub fn upgrade(self, ctx: &ExecutionContext) -> Result<()> { pub fn upgrade(self, ctx: &ExecutionContext) -> Result<()> {
print_separator("System update"); print_separator(t!("System update"));
match self { match self {
Distribution::Alpine => upgrade_alpine_linux(ctx), Distribution::Alpine => upgrade_alpine_linux(ctx),
@@ -158,6 +161,7 @@ impl Distribution {
Distribution::OpenMandriva => upgrade_openmandriva(ctx), Distribution::OpenMandriva => upgrade_openmandriva(ctx),
Distribution::PCLinuxOS => upgrade_pclinuxos(ctx), Distribution::PCLinuxOS => upgrade_pclinuxos(ctx),
Distribution::Nobara => upgrade_nobara(ctx), Distribution::Nobara => upgrade_nobara(ctx),
Distribution::NILRT => upgrade_nilrt(ctx),
} }
} }
@@ -173,7 +177,7 @@ impl Distribution {
} }
fn update_bedrock(ctx: &ExecutionContext) -> Result<()> { fn update_bedrock(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
ctx.run_type().execute(sudo).args(["brl", "update"]); ctx.run_type().execute(sudo).args(["brl", "update"]);
@@ -198,7 +202,7 @@ fn update_bedrock(ctx: &ExecutionContext) -> Result<()> {
fn upgrade_alpine_linux(ctx: &ExecutionContext) -> Result<()> { fn upgrade_alpine_linux(ctx: &ExecutionContext) -> Result<()> {
let apk = require("apk")?; let apk = require("apk")?;
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
ctx.run_type().execute(sudo).arg(&apk).arg("update").status_checked()?; ctx.run_type().execute(sudo).arg(&apk).arg("update").status_checked()?;
ctx.run_type().execute(sudo).arg(&apk).arg("upgrade").status_checked() ctx.run_type().execute(sudo).arg(&apk).arg("upgrade").status_checked()
@@ -206,7 +210,7 @@ fn upgrade_alpine_linux(ctx: &ExecutionContext) -> Result<()> {
fn upgrade_chimera_linux(ctx: &ExecutionContext) -> Result<()> { fn upgrade_chimera_linux(ctx: &ExecutionContext) -> Result<()> {
let apk = require("apk")?; let apk = require("apk")?;
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
ctx.run_type().execute(sudo).arg(&apk).arg("update").status_checked()?; ctx.run_type().execute(sudo).arg(&apk).arg("update").status_checked()?;
ctx.run_type().execute(sudo).arg(&apk).arg("upgrade").status_checked() ctx.run_type().execute(sudo).arg(&apk).arg("upgrade").status_checked()
@@ -214,7 +218,7 @@ fn upgrade_chimera_linux(ctx: &ExecutionContext) -> Result<()> {
fn upgrade_wolfi_linux(ctx: &ExecutionContext) -> Result<()> { fn upgrade_wolfi_linux(ctx: &ExecutionContext) -> Result<()> {
let apk = require("apk")?; let apk = require("apk")?;
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
ctx.run_type().execute(sudo).arg(&apk).arg("update").status_checked()?; ctx.run_type().execute(sudo).arg(&apk).arg("update").status_checked()?;
ctx.run_type().execute(sudo).arg(&apk).arg("upgrade").status_checked() ctx.run_type().execute(sudo).arg(&apk).arg("upgrade").status_checked()
@@ -229,7 +233,7 @@ fn upgrade_redhat(ctx: &ExecutionContext) -> Result<()> {
} }
}; };
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
let mut command = ctx.run_type().execute(sudo); let mut command = ctx.run_type().execute(sudo);
command command
.arg(which("dnf").unwrap_or_else(|| Path::new("yum").to_path_buf())) .arg(which("dnf").unwrap_or_else(|| Path::new("yum").to_path_buf()))
@@ -252,7 +256,7 @@ fn upgrade_redhat(ctx: &ExecutionContext) -> Result<()> {
} }
fn upgrade_nobara(ctx: &ExecutionContext) -> Result<()> { fn upgrade_nobara(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
let pkg_manager = require("dnf")?; let pkg_manager = require("dnf")?;
let mut update_command = ctx.run_type().execute(sudo); let mut update_command = ctx.run_type().execute(sudo);
@@ -285,6 +289,14 @@ fn upgrade_nobara(ctx: &ExecutionContext) -> Result<()> {
Ok(()) Ok(())
} }
fn upgrade_nilrt(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
let opkg = require("opkg")?;
ctx.run_type().execute(sudo).arg(&opkg).arg("update").status_checked()?;
ctx.run_type().execute(sudo).arg(&opkg).arg("upgrade").status_checked()
}
fn upgrade_fedora_immutable(ctx: &ExecutionContext) -> Result<()> { fn upgrade_fedora_immutable(ctx: &ExecutionContext) -> Result<()> {
let ostree = require("rpm-ostree")?; let ostree = require("rpm-ostree")?;
let mut command = ctx.run_type().execute(ostree); let mut command = ctx.run_type().execute(ostree);
@@ -294,14 +306,14 @@ fn upgrade_fedora_immutable(ctx: &ExecutionContext) -> Result<()> {
} }
fn upgrade_bedrock_strata(ctx: &ExecutionContext) -> Result<()> { fn upgrade_bedrock_strata(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
ctx.run_type().execute(sudo).args(["brl", "update"]).status_checked()?; ctx.run_type().execute(sudo).args(["brl", "update"]).status_checked()?;
Ok(()) Ok(())
} }
fn upgrade_suse(ctx: &ExecutionContext) -> Result<()> { fn upgrade_suse(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
ctx.run_type() ctx.run_type()
.execute(sudo) .execute(sudo)
.args(["zypper", "refresh"]) .args(["zypper", "refresh"])
@@ -324,7 +336,7 @@ fn upgrade_suse(ctx: &ExecutionContext) -> Result<()> {
} }
fn upgrade_opensuse_tumbleweed(ctx: &ExecutionContext) -> Result<()> { fn upgrade_opensuse_tumbleweed(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
ctx.run_type() ctx.run_type()
.execute(sudo) .execute(sudo)
.args(["zypper", "refresh"]) .args(["zypper", "refresh"])
@@ -342,7 +354,7 @@ fn upgrade_opensuse_tumbleweed(ctx: &ExecutionContext) -> Result<()> {
} }
fn upgrade_suse_micro(ctx: &ExecutionContext) -> Result<()> { fn upgrade_suse_micro(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
let mut cmd = ctx.run_type().execute(sudo); let mut cmd = ctx.run_type().execute(sudo);
cmd.arg("transactional-update"); cmd.arg("transactional-update");
if ctx.config().yes(Step::System) { if ctx.config().yes(Step::System) {
@@ -355,10 +367,10 @@ fn upgrade_suse_micro(ctx: &ExecutionContext) -> Result<()> {
} }
fn upgrade_openmandriva(ctx: &ExecutionContext) -> Result<()> { fn upgrade_openmandriva(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
let mut command = ctx.run_type().execute(sudo); let mut command = ctx.run_type().execute(sudo);
command.arg(&which("dnf").unwrap()).arg("upgrade"); command.arg(which("dnf").unwrap()).arg("upgrade");
if let Some(args) = ctx.config().dnf_arguments() { if let Some(args) = ctx.config().dnf_arguments() {
command.args(args.split_whitespace()); command.args(args.split_whitespace());
@@ -374,10 +386,10 @@ fn upgrade_openmandriva(ctx: &ExecutionContext) -> Result<()> {
} }
fn upgrade_pclinuxos(ctx: &ExecutionContext) -> Result<()> { fn upgrade_pclinuxos(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
let mut command_update = ctx.run_type().execute(sudo); let mut command_update = ctx.run_type().execute(sudo);
command_update.arg(&which("apt-get").unwrap()).arg("update"); command_update.arg(which("apt-get").unwrap()).arg("update");
if let Some(args) = ctx.config().dnf_arguments() { if let Some(args) = ctx.config().dnf_arguments() {
command_update.args(args.split_whitespace()); command_update.args(args.split_whitespace());
@@ -390,7 +402,7 @@ fn upgrade_pclinuxos(ctx: &ExecutionContext) -> Result<()> {
command_update.status_checked()?; command_update.status_checked()?;
let mut cmd = ctx.run_type().execute(sudo); let mut cmd = ctx.run_type().execute(sudo);
cmd.arg(&which("apt-get").unwrap()); cmd.arg(which("apt-get").unwrap());
cmd.arg("dist-upgrade"); cmd.arg("dist-upgrade");
if ctx.config().yes(Step::System) { if ctx.config().yes(Step::System) {
cmd.arg("-y"); cmd.arg("-y");
@@ -421,7 +433,7 @@ fn upgrade_vanilla(ctx: &ExecutionContext) -> Result<()> {
} }
fn upgrade_void(ctx: &ExecutionContext) -> Result<()> { fn upgrade_void(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
let mut command = ctx.run_type().execute(sudo); let mut command = ctx.run_type().execute(sudo);
command.args(["xbps-install", "-Su", "xbps"]); command.args(["xbps-install", "-Su", "xbps"]);
if ctx.config().yes(Step::System) { if ctx.config().yes(Step::System) {
@@ -442,7 +454,7 @@ fn upgrade_void(ctx: &ExecutionContext) -> Result<()> {
fn upgrade_gentoo(ctx: &ExecutionContext) -> Result<()> { fn upgrade_gentoo(ctx: &ExecutionContext) -> Result<()> {
let run_type = ctx.run_type(); let run_type = ctx.run_type();
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
if let Some(layman) = which("layman") { if let Some(layman) = which("layman") {
run_type run_type
.execute(sudo) .execute(sudo)
@@ -451,7 +463,11 @@ fn upgrade_gentoo(ctx: &ExecutionContext) -> Result<()> {
.status_checked()?; .status_checked()?;
} }
println!("Syncing portage"); println!("{}", t!("Syncing portage"));
if let Some(ego) = which("ego") {
// The Funtoo team doesn't reccomend running both ego sync and emerge --sync
run_type.execute(sudo).arg(ego).arg("sync").status_checked()?;
} else {
run_type run_type
.execute(sudo) .execute(sudo)
.args(["emerge", "--sync"]) .args(["emerge", "--sync"])
@@ -462,6 +478,7 @@ fn upgrade_gentoo(ctx: &ExecutionContext) -> Result<()> {
.unwrap_or_else(|| vec!["-q"]), .unwrap_or_else(|| vec!["-q"]),
) )
.status_checked()?; .status_checked()?;
}
if let Some(eix_update) = which("eix-update") { if let Some(eix_update) = which("eix-update") {
run_type.execute(sudo).arg(eix_update).status_checked()?; run_type.execute(sudo).arg(eix_update).status_checked()?;
@@ -512,7 +529,7 @@ fn upgrade_debian(ctx: &ExecutionContext) -> Result<()> {
return Ok(()); return Ok(());
} }
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
if !is_nala { if !is_nala {
ctx.run_type() ctx.run_type()
.execute(sudo) .execute(sudo)
@@ -566,7 +583,7 @@ pub fn run_deb_get(ctx: &ExecutionContext) -> Result<()> {
} }
fn upgrade_solus(ctx: &ExecutionContext) -> Result<()> { fn upgrade_solus(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
let mut cmd = ctx.run_type().execute(sudo); let mut cmd = ctx.run_type().execute(sudo);
cmd.arg("eopkg"); cmd.arg("eopkg");
if ctx.config().yes(Step::System) { if ctx.config().yes(Step::System) {
@@ -675,7 +692,7 @@ pub fn run_packer_nu(ctx: &ExecutionContext) -> Result<()> {
} }
fn upgrade_clearlinux(ctx: &ExecutionContext) -> Result<()> { fn upgrade_clearlinux(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
let mut cmd = ctx.run_type().execute(sudo); let mut cmd = ctx.run_type().execute(sudo);
cmd.args(["swupd", "update"]); cmd.args(["swupd", "update"]);
if ctx.config().yes(Step::System) { if ctx.config().yes(Step::System) {
@@ -687,7 +704,7 @@ fn upgrade_clearlinux(ctx: &ExecutionContext) -> Result<()> {
} }
fn upgrade_exherbo(ctx: &ExecutionContext) -> Result<()> { fn upgrade_exherbo(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
ctx.run_type().execute(sudo).args(["cave", "sync"]).status_checked()?; ctx.run_type().execute(sudo).args(["cave", "sync"]).status_checked()?;
ctx.run_type() ctx.run_type()
@@ -716,7 +733,7 @@ fn upgrade_exherbo(ctx: &ExecutionContext) -> Result<()> {
} }
fn upgrade_nixos(ctx: &ExecutionContext) -> Result<()> { fn upgrade_nixos(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
let mut command = ctx.run_type().execute(sudo); let mut command = ctx.run_type().execute(sudo);
command.args(["/run/current-system/sw/bin/nixos-rebuild", "switch", "--upgrade"]); command.args(["/run/current-system/sw/bin/nixos-rebuild", "switch", "--upgrade"]);
@@ -742,7 +759,7 @@ fn upgrade_neon(ctx: &ExecutionContext) -> Result<()> {
// seems rare // seems rare
// if that comes up we need to create a Distribution::PackageKit or some such // if that comes up we need to create a Distribution::PackageKit or some such
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
let pkcon = which("pkcon").unwrap(); let pkcon = which("pkcon").unwrap();
// pkcon ignores update with update and refresh provided together // pkcon ignores update with update and refresh provided together
ctx.run_type() ctx.run_type()
@@ -771,7 +788,7 @@ fn upgrade_neon(ctx: &ExecutionContext) -> Result<()> {
/// alternative /// alternative
fn should_skip_needrestart() -> Result<()> { fn should_skip_needrestart() -> Result<()> {
let distribution = Distribution::detect()?; let distribution = Distribution::detect()?;
let msg = "needrestart will be ran by the package manager"; let msg = t!("needrestart will be ran by the package manager");
if distribution.redhat_based() { if distribution.redhat_based() {
return Err(SkipStep(String::from(msg)).into()); return Err(SkipStep(String::from(msg)).into());
@@ -806,12 +823,12 @@ fn should_skip_needrestart() -> Result<()> {
} }
pub fn run_needrestart(ctx: &ExecutionContext) -> Result<()> { pub fn run_needrestart(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
let needrestart = require("needrestart")?; let needrestart = require("needrestart")?;
should_skip_needrestart()?; should_skip_needrestart()?;
print_separator("Check for needed restarts"); print_separator(t!("Check for needed restarts"));
ctx.run_type().execute(sudo).arg(needrestart).status_checked()?; ctx.run_type().execute(sudo).arg(needrestart).status_checked()?;
@@ -822,10 +839,10 @@ pub fn run_fwupdmgr(ctx: &ExecutionContext) -> Result<()> {
let fwupdmgr = require("fwupdmgr")?; let fwupdmgr = require("fwupdmgr")?;
if is_wsl()? { if is_wsl()? {
return Err(SkipStep(String::from("Should not run in WSL")).into()); return Err(SkipStep(t!("Should not run in WSL").to_string()).into());
} }
print_separator("Firmware upgrades"); print_separator(t!("Firmware upgrades"));
ctx.run_type() ctx.run_type()
.execute(&fwupdmgr) .execute(&fwupdmgr)
@@ -847,7 +864,7 @@ pub fn run_fwupdmgr(ctx: &ExecutionContext) -> Result<()> {
pub fn run_flatpak(ctx: &ExecutionContext) -> Result<()> { pub fn run_flatpak(ctx: &ExecutionContext) -> Result<()> {
let flatpak = require("flatpak")?; let flatpak = require("flatpak")?;
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
let cleanup = ctx.config().cleanup(); let cleanup = ctx.config().cleanup();
let yes = ctx.config().yes(Step::Flatpak); let yes = ctx.config().yes(Step::Flatpak);
let run_type = ctx.run_type(); let run_type = ctx.run_type();
@@ -867,7 +884,7 @@ pub fn run_flatpak(ctx: &ExecutionContext) -> Result<()> {
run_type.execute(&flatpak).args(&cleanup_args).status_checked()?; run_type.execute(&flatpak).args(&cleanup_args).status_checked()?;
} }
print_separator("Flatpak System Packages"); print_separator(t!("Flatpak System Packages"));
if ctx.config().flatpak_use_sudo() || std::env::var("SSH_CLIENT").is_ok() { if ctx.config().flatpak_use_sudo() || std::env::var("SSH_CLIENT").is_ok() {
let mut update_args = vec!["update", "--system"]; let mut update_args = vec!["update", "--system"];
if yes { if yes {
@@ -908,11 +925,11 @@ pub fn run_flatpak(ctx: &ExecutionContext) -> Result<()> {
} }
pub fn run_snap(ctx: &ExecutionContext) -> Result<()> { pub fn run_snap(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
let snap = require("snap")?; let snap = require("snap")?;
if !PathBuf::from("/var/snapd.socket").exists() && !PathBuf::from("/run/snapd.socket").exists() { if !PathBuf::from("/var/snapd.socket").exists() && !PathBuf::from("/run/snapd.socket").exists() {
return Err(SkipStep(String::from("Snapd socket does not exist")).into()); return Err(SkipStep(t!("Snapd socket does not exist").to_string()).into());
} }
print_separator("snap"); print_separator("snap");
@@ -920,7 +937,7 @@ pub fn run_snap(ctx: &ExecutionContext) -> Result<()> {
} }
pub fn run_pihole_update(ctx: &ExecutionContext) -> Result<()> { pub fn run_pihole_update(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
let pihole = require("pihole")?; let pihole = require("pihole")?;
Path::new("/opt/pihole/update.sh").require()?; Path::new("/opt/pihole/update.sh").require()?;
@@ -954,7 +971,7 @@ pub fn run_distrobox_update(ctx: &ExecutionContext) -> Result<()> {
) { ) {
(r, Some(c)) => { (r, Some(c)) => {
if c.is_empty() { if c.is_empty() {
return Err(SkipStep("You need to specify at least one container".to_string()).into()); return Err(SkipStep(t!("You need to specify at least one container").to_string()).into());
} }
r.args(c) r.args(c)
} }
@@ -969,7 +986,7 @@ pub fn run_distrobox_update(ctx: &ExecutionContext) -> Result<()> {
} }
pub fn run_dkp_pacman_update(ctx: &ExecutionContext) -> Result<()> { pub fn run_dkp_pacman_update(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
let dkp_pacman = require("dkp-pacman")?; let dkp_pacman = require("dkp-pacman")?;
print_separator("Devkitpro pacman"); print_separator("Devkitpro pacman");
@@ -992,20 +1009,20 @@ pub fn run_dkp_pacman_update(ctx: &ExecutionContext) -> Result<()> {
} }
pub fn run_config_update(ctx: &ExecutionContext) -> Result<()> { pub fn run_config_update(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
if ctx.config().yes(Step::ConfigUpdate) { if ctx.config().yes(Step::ConfigUpdate) {
return Err(SkipStep("Skipped in --yes".to_string()).into()); return Err(SkipStep(t!("Skipped in --yes").to_string()).into());
} }
if let Ok(etc_update) = require("etc-update") { if let Ok(etc_update) = require("etc-update") {
print_separator("Configuration update"); print_separator(t!("Configuration update"));
ctx.run_type().execute(sudo).arg(etc_update).status_checked()?; ctx.run_type().execute(sudo).arg(etc_update).status_checked()?;
} else if let Ok(pacdiff) = require("pacdiff") { } else if let Ok(pacdiff) = require("pacdiff") {
if std::env::var("DIFFPROG").is_err() { if std::env::var("DIFFPROG").is_err() {
require("vim")?; require("vim")?;
} }
print_separator("Configuration update"); print_separator(t!("Configuration update"));
ctx.execute_elevated(&pacdiff, false)?.status_checked()?; ctx.execute_elevated(&pacdiff, false)?.status_checked()?;
} }
@@ -1029,7 +1046,7 @@ pub fn run_lure_update(ctx: &ExecutionContext) -> Result<()> {
} }
pub fn run_waydroid(ctx: &ExecutionContext) -> Result<()> { pub fn run_waydroid(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
let waydroid = require("waydroid")?; let waydroid = require("waydroid")?;
let status = ctx.run_type().execute(&waydroid).arg("status").output_checked_utf8()?; let status = ctx.run_type().execute(&waydroid).arg("status").output_checked_utf8()?;
// example output of `waydroid status`: // example output of `waydroid status`:
@@ -1053,17 +1070,20 @@ pub fn run_waydroid(ctx: &ExecutionContext) -> Result<()> {
.stdout .stdout
.lines() .lines()
.find(|line| line.contains("Session:")) .find(|line| line.contains("Session:"))
.expect("the output of `waydroid status` should contain `Session:`"); .unwrap_or_else(|| panic!("the output of `waydroid status` should contain `Session:`"));
let is_container_running = session.contains("RUNNING"); let is_container_running = session.contains("RUNNING");
let assume_yes = ctx.config().yes(Step::Waydroid); let assume_yes = ctx.config().yes(Step::Waydroid);
print_separator("Waydroid"); print_separator("Waydroid");
if is_container_running && !assume_yes { if is_container_running && !assume_yes {
let update_allowed = let update_allowed = prompt_yesno(&t!(
prompt_yesno("Going to execute `waydroid upgrade`, which would STOP the running container, is this ok?")?; "Going to execute `waydroid upgrade`, which would STOP the running container, is this ok?"
))?;
if !update_allowed { if !update_allowed {
return Err(SkipStep("Skip the Waydroid step because the user don't want to proceed".to_string()).into()); return Err(
SkipStep(t!("Skip the Waydroid step because the user don't want to proceed").to_string()).into(),
);
} }
} }
ctx.run_type() ctx.run_type()
@@ -1074,7 +1094,7 @@ pub fn run_waydroid(ctx: &ExecutionContext) -> Result<()> {
} }
pub fn run_auto_cpufreq(ctx: &ExecutionContext) -> Result<()> { pub fn run_auto_cpufreq(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
let auto_cpu_freq = require("auto-cpufreq")?; let auto_cpu_freq = require("auto-cpufreq")?;
print_separator("auto-cpufreq"); print_separator("auto-cpufreq");
@@ -1185,6 +1205,11 @@ mod tests {
test_template(include_str!("os_release/gentoo"), Distribution::Gentoo); test_template(include_str!("os_release/gentoo"), Distribution::Gentoo);
} }
#[test]
fn test_funtoo() {
test_template(include_str!("os_release/funtoo"), Distribution::Gentoo);
}
#[test] #[test]
fn test_exherbo() { fn test_exherbo() {
test_template(include_str!("os_release/exherbo"), Distribution::Exherbo); test_template(include_str!("os_release/exherbo"), Distribution::Exherbo);
@@ -1244,4 +1269,9 @@ mod tests {
fn test_nobara() { fn test_nobara() {
test_template(include_str!("os_release/nobara"), Distribution::Nobara); test_template(include_str!("os_release/nobara"), Distribution::Nobara);
} }
#[test]
fn test_nilrt() {
test_template(include_str!("os_release/nilrt"), Distribution::NILRT);
}
} }

View File

@@ -1,9 +1,10 @@
use crate::command::CommandExt; use crate::command::CommandExt;
use crate::execution_context::ExecutionContext; use crate::execution_context::ExecutionContext;
use crate::terminal::{print_separator, prompt_yesno}; use crate::terminal::{print_separator, prompt_yesno};
use crate::utils::{require_option, REQUIRE_SUDO}; use crate::utils::{get_require_sudo_string, require_option};
use crate::{utils::require, Step}; use crate::{utils::require, Step};
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use rust_i18n::t;
use std::collections::HashSet; use std::collections::HashSet;
use std::fs; use std::fs;
use std::process::Command; use std::process::Command;
@@ -11,7 +12,7 @@ use tracing::debug;
pub fn run_macports(ctx: &ExecutionContext) -> Result<()> { pub fn run_macports(ctx: &ExecutionContext) -> Result<()> {
require("port")?; require("port")?;
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
print_separator("MacPorts"); print_separator("MacPorts");
ctx.run_type() ctx.run_type()
@@ -34,25 +35,25 @@ pub fn run_macports(ctx: &ExecutionContext) -> Result<()> {
pub fn run_mas(ctx: &ExecutionContext) -> Result<()> { pub fn run_mas(ctx: &ExecutionContext) -> Result<()> {
let mas = require("mas")?; let mas = require("mas")?;
print_separator("macOS App Store"); print_separator(t!("macOS App Store"));
ctx.run_type().execute(mas).arg("upgrade").status_checked() ctx.run_type().execute(mas).arg("upgrade").status_checked()
} }
pub fn upgrade_macos(ctx: &ExecutionContext) -> Result<()> { pub fn upgrade_macos(ctx: &ExecutionContext) -> Result<()> {
print_separator("macOS system update"); print_separator(t!("macOS system update"));
let should_ask = !(ctx.config().yes(Step::System) || ctx.config().dry_run()); let should_ask = !(ctx.config().yes(Step::System) || ctx.config().dry_run());
if should_ask { if should_ask {
println!("Finding available software"); println!("{}", t!("Finding available software"));
if system_update_available()? { if system_update_available()? {
let answer = prompt_yesno("A system update is available. Do you wish to install it?")?; let answer = prompt_yesno(t!("A system update is available. Do you wish to install it?").as_ref())?;
if !answer { if !answer {
return Ok(()); return Ok(());
} }
println!(); println!();
} else { } else {
println!("No new software available."); println!("{}", t!("No new software available."));
return Ok(()); return Ok(());
} }
} }
@@ -115,7 +116,7 @@ pub fn update_xcodes(ctx: &ExecutionContext) -> Result<()> {
.collect(); .collect();
if releases_installed.is_empty() { if releases_installed.is_empty() {
println!("No Xcode releases installed."); println!("{}", t!("No Xcode releases installed."));
return Ok(()); return Ok(());
} }
@@ -194,7 +195,8 @@ pub fn update_xcodes(ctx: &ExecutionContext) -> Result<()> {
releases_regular_new_installed, releases_regular_new_installed,
] { ] {
if should_ask && releases_new_installed.len() == 2 { if should_ask && releases_new_installed.len() == 2 {
let answer_uninstall = prompt_yesno("Would you like to move the former Xcode release to the trash?")?; let answer_uninstall =
prompt_yesno(t!("Would you like to move the former Xcode release to the trash?").as_ref())?;
if answer_uninstall { if answer_uninstall {
let _ = ctx let _ = ctx
.run_type() .run_type()
@@ -221,11 +223,12 @@ pub fn process_xcodes_releases(releases_filtered: Vec<String>, should_ask: bool,
&& !releases_filtered.is_empty() && !releases_filtered.is_empty()
{ {
println!( println!(
"New Xcode release detected: {}", "{} {}",
t!("New Xcode release detected:"),
releases_filtered.last().cloned().unwrap_or_default() releases_filtered.last().cloned().unwrap_or_default()
); );
if should_ask { if should_ask {
let answer_install = prompt_yesno("Would you like to install it?")?; let answer_install = prompt_yesno(t!("Would you like to install it?").as_ref())?;
if answer_install { if answer_install {
let _ = ctx let _ = ctx
.run_type() .run_type()

View File

@@ -1,12 +1,12 @@
use crate::command::CommandExt; use crate::command::CommandExt;
use crate::execution_context::ExecutionContext; use crate::execution_context::ExecutionContext;
use crate::terminal::print_separator; use crate::terminal::print_separator;
use crate::utils::{require_option, REQUIRE_SUDO}; use crate::utils::{get_require_sudo_string, require_option};
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
pub fn upgrade_openbsd(ctx: &ExecutionContext) -> Result<()> { pub fn upgrade_openbsd(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
print_separator("OpenBSD Update"); print_separator(t!("OpenBSD Update"));
ctx.run_type() ctx.run_type()
.execute(sudo) .execute(sudo)
.args(["/usr/sbin/sysupgrade", "-n"]) .args(["/usr/sbin/sysupgrade", "-n"])
@@ -14,8 +14,8 @@ pub fn upgrade_openbsd(ctx: &ExecutionContext) -> Result<()> {
} }
pub fn upgrade_packages(ctx: &ExecutionContext) -> Result<()> { pub fn upgrade_packages(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
print_separator("OpenBSD Packages"); print_separator(t!("OpenBSD Packages"));
if ctx.config().cleanup() { if ctx.config().cleanup() {
ctx.run_type() ctx.run_type()

View File

@@ -0,0 +1,6 @@
ID="funtoo"
NAME="Funtoo"
PRETTY_NAME="Funtoo Linux"
ANSI_COLOR="0;34"
HOME_URL="https://www.funtoo.org"
BUG_REPORT_URL="https://bugs.funtoo.org"

View File

@@ -0,0 +1,8 @@
ID=nilrt
NAME="NI Linux Real-Time"
VERSION="10.0 (kirkstone)"
VERSION_ID=10.0
PRETTY_NAME="NI Linux Real-Time 10.0 (kirkstone)"
DISTRO_CODENAME="kirkstone"
BUILD_ID="23.8.0f153-x64"
VERSION_CODENAME="kirkstone"

View File

@@ -13,6 +13,10 @@ use color_eyre::eyre::Context;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use home; use home;
use ini::Ini; use ini::Ini;
#[cfg(target_os = "linux")]
use nix::unistd::Uid;
use rust_i18n::t;
use semver::Version;
use tracing::debug; use tracing::debug;
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
@@ -24,7 +28,7 @@ use crate::executor::Executor;
#[cfg(any(target_os = "linux", target_os = "macos"))] #[cfg(any(target_os = "linux", target_os = "macos"))]
use crate::executor::RunType; use crate::executor::RunType;
use crate::terminal::print_separator; use crate::terminal::print_separator;
use crate::utils::{require, require_option, PathExt, REQUIRE_SUDO}; use crate::utils::{get_require_sudo_string, require, require_option, PathExt};
#[cfg(any(target_os = "linux", target_os = "macos"))] #[cfg(any(target_os = "linux", target_os = "macos"))]
const INTEL_BREW: &str = "/usr/local/bin/brew"; const INTEL_BREW: &str = "/usr/local/bin/brew";
@@ -98,19 +102,19 @@ pub fn run_fisher(ctx: &ExecutionContext) -> Result<()> {
.args(["-c", "type -t fisher"]) .args(["-c", "type -t fisher"])
.output_checked_utf8() .output_checked_utf8()
.map(|_| ()) .map(|_| ())
.map_err(|_| SkipStep("`fisher` is not defined in `fish`".to_owned()))?; .map_err(|_| SkipStep(t!("`fisher` is not defined in `fish`").to_string()))?;
Command::new(&fish) Command::new(&fish)
.args(["-c", "echo \"$__fish_config_dir/fish_plugins\""]) .args(["-c", "echo \"$__fish_config_dir/fish_plugins\""])
.output_checked_utf8() .output_checked_utf8()
.and_then(|output| Path::new(&output.stdout.trim()).require().map(|_| ())) .and_then(|output| Path::new(&output.stdout.trim()).require().map(|_| ()))
.map_err(|err| SkipStep(format!("`fish_plugins` path doesn't exist: {err}")))?; .map_err(|err| SkipStep(t!("`fish_plugins` path doesn't exist: {err}", err = err).to_string()))?;
Command::new(&fish) Command::new(&fish)
.args(["-c", "fish_update_completions"]) .args(["-c", "fish_update_completions"])
.output_checked_utf8() .output_checked_utf8()
.map(|_| ()) .map(|_| ())
.map_err(|_| SkipStep("`fish_update_completions` is not available".to_owned()))?; .map_err(|_| SkipStep(t!("`fish_update_completions` is not available").to_string()))?;
print_separator("Fisher"); print_separator("Fisher");
@@ -177,7 +181,7 @@ pub fn run_oh_my_fish(ctx: &ExecutionContext) -> Result<()> {
pub fn run_pkgin(ctx: &ExecutionContext) -> Result<()> { pub fn run_pkgin(ctx: &ExecutionContext) -> Result<()> {
let pkgin = require("pkgin")?; let pkgin = require("pkgin")?;
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?; let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
print_separator("Pkgin"); print_separator("Pkgin");
@@ -232,7 +236,7 @@ pub fn upgrade_gnome_extensions(ctx: &ExecutionContext) -> Result<()> {
let gdbus = require("gdbus")?; let gdbus = require("gdbus")?;
require_option( require_option(
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(), t!("Desktop doest not appear to be gnome").to_string(),
)?; )?;
let output = Command::new("gdbus") let output = Command::new("gdbus")
.args([ .args([
@@ -249,10 +253,10 @@ pub fn upgrade_gnome_extensions(ctx: &ExecutionContext) -> Result<()> {
debug!("Checking for gnome extensions: {}", output); debug!("Checking for gnome extensions: {}", output);
if !output.stdout.contains("org.gnome.Shell.Extensions") { if !output.stdout.contains("org.gnome.Shell.Extensions") {
return Err(SkipStep(String::from("Gnome shell extensions are unregistered in DBus")).into()); return Err(SkipStep(t!("Gnome shell extensions are unregistered in DBus").to_string()).into());
} }
print_separator("Gnome Shell extensions"); print_separator(t!("Gnome Shell extensions"));
ctx.run_type() ctx.run_type()
.execute(gdbus) .execute(gdbus)
@@ -269,6 +273,23 @@ pub fn upgrade_gnome_extensions(ctx: &ExecutionContext) -> Result<()> {
.status_checked() .status_checked()
} }
#[cfg(target_os = "linux")]
pub fn brew_linux_sudo_uid() -> Option<u32> {
let linuxbrew_directory = "/home/linuxbrew/.linuxbrew";
if let Ok(metadata) = std::fs::metadata(linuxbrew_directory) {
let owner_id = metadata.uid();
let current_id = Uid::effective();
// print debug these two values
debug!("linuxbrew_directory owner_id: {}, current_id: {}", owner_id, current_id);
return if owner_id == current_id.as_raw() {
None // no need for sudo if linuxbrew is owned by the current user
} else {
Some(owner_id) // otherwise use sudo to run brew as the owner
};
}
None
}
#[cfg(any(target_os = "linux", target_os = "macos"))] #[cfg(any(target_os = "linux", target_os = "macos"))]
pub fn run_brew_formula(ctx: &ExecutionContext, variant: BrewVariant) -> Result<()> { pub fn run_brew_formula(ctx: &ExecutionContext, variant: BrewVariant) -> Result<()> {
#[allow(unused_variables)] #[allow(unused_variables)]
@@ -277,10 +298,37 @@ pub fn run_brew_formula(ctx: &ExecutionContext, variant: BrewVariant) -> Result<
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
{ {
if variant.is_path() && !BrewVariant::is_macos_custom(binary_name) { if variant.is_path() && !BrewVariant::is_macos_custom(binary_name) {
return Err(SkipStep("Not a custom brew for macOS".to_string()).into()); return Err(SkipStep(t!("Not a custom brew for macOS").to_string()).into());
} }
} }
#[cfg(target_os = "linux")]
{
let sudo_uid = brew_linux_sudo_uid();
// if brew is owned by another user, execute "sudo -Hu <uid> brew update"
if let Some(user_id) = sudo_uid {
let uid = nix::unistd::Uid::from_raw(user_id);
let user = nix::unistd::User::from_uid(uid)
.expect("failed to call getpwuid()")
.expect("this user should exist");
let sudo_as_user = t!("sudo as user '{user}'", user = user.name);
print_separator(format!("{} ({})", variant.step_title(), sudo_as_user));
let sudo = crate::utils::require_option(ctx.sudo().as_ref(), crate::utils::get_require_sudo_string())?;
ctx.run_type()
.execute(sudo)
.current_dir("/tmp") // brew needs a writable current directory
.args([
"--set-home",
&format!("--user={}", user.name),
&format!("{}", binary_name.to_string_lossy()),
"update",
])
.status_checked()?;
return Ok(());
}
}
print_separator(variant.step_title()); print_separator(variant.step_title());
let run_type = ctx.run_type(); let run_type = ctx.run_type();
@@ -310,7 +358,7 @@ pub fn run_brew_formula(ctx: &ExecutionContext, variant: BrewVariant) -> Result<
pub fn run_brew_cask(ctx: &ExecutionContext, variant: BrewVariant) -> Result<()> { pub fn run_brew_cask(ctx: &ExecutionContext, variant: BrewVariant) -> Result<()> {
let binary_name = require(variant.binary_name())?; let binary_name = require(variant.binary_name())?;
if variant.is_path() && !BrewVariant::is_macos_custom(binary_name) { if variant.is_path() && !BrewVariant::is_macos_custom(binary_name) {
return Err(SkipStep("Not a custom brew for macOS".to_string()).into()); return Err(SkipStep(t!("Not a custom brew for macOS").to_string()).into());
} }
print_separator(format!("{} - Cask", variant.step_title())); print_separator(format!("{} - Cask", variant.step_title()));
let run_type = ctx.run_type(); let run_type = ctx.run_type();
@@ -336,6 +384,9 @@ pub fn run_brew_cask(ctx: &ExecutionContext, variant: BrewVariant) -> Result<()>
if ctx.config().brew_greedy_latest() { if ctx.config().brew_greedy_latest() {
brew_args.push("--greedy-latest"); brew_args.push("--greedy-latest");
} }
if ctx.config().brew_greedy_auto_updates() {
brew_args.push("--greedy-auto-updates");
}
} }
variant.execute(run_type).args(&brew_args).status_checked()?; variant.execute(run_type).args(&brew_args).status_checked()?;
@@ -362,7 +413,7 @@ pub fn run_guix(ctx: &ExecutionContext) -> Result<()> {
if should_upgrade { if should_upgrade {
return run_type.execute(&guix).args(["package", "-u"]).status_checked(); return run_type.execute(&guix).args(["package", "-u"]).status_checked();
} }
Err(SkipStep(String::from("Guix Pull Failed, Skipping")).into()) Err(SkipStep(t!("Guix Pull Failed, Skipping").to_string()).into())
} }
pub fn run_nix(ctx: &ExecutionContext) -> Result<()> { pub fn run_nix(ctx: &ExecutionContext) -> Result<()> {
@@ -382,23 +433,46 @@ pub fn run_nix(ctx: &ExecutionContext) -> Result<()> {
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
{ {
if require("darwin-rebuild").is_ok() { if require("darwin-rebuild").is_ok() {
return Err(SkipStep(String::from( return Err(
"Nix-darwin on macOS must be upgraded via darwin-rebuild switch", SkipStep(t!("Nix-darwin on macOS must be upgraded via darwin-rebuild switch").to_string()).into(),
)) );
.into());
} }
} }
let run_type = ctx.run_type(); let run_type = ctx.run_type();
run_type.execute(nix_channel).arg("--update").status_checked()?; run_type.execute(nix_channel).arg("--update").status_checked()?;
let mut get_version_cmd = ctx.run_type().execute(&nix);
get_version_cmd.arg("--version");
let get_version_cmd_output = get_version_cmd.output_checked_utf8()?;
let get_version_cmd_first_line_stdout = get_version_cmd_output
.stdout
.lines()
.next()
.expect("nix --version gives an empty output");
let splitted: Vec<&str> = get_version_cmd_first_line_stdout.split_whitespace().collect();
let version = if splitted.len() >= 3 {
Version::parse(splitted[2]).expect("invalid version")
} else {
panic!("nix --version output format changed, file an issue to Topgrade!")
};
debug!("Nix version: {:?}", version);
// Nix since 2.21.0 uses `--all --impure` rather than `.*` to upgrade all packages
let packages = if version >= Version::new(2, 21, 0) {
vec!["--all", "--impure"]
} else {
vec![".*"]
};
if Path::new(&manifest_json_path).exists() { if Path::new(&manifest_json_path).exists() {
run_type run_type
.execute(nix) .execute(nix)
.args(nix_args()) .args(nix_args())
.arg("profile") .arg("profile")
.arg("upgrade") .arg("upgrade")
.arg(".*") .args(&packages)
.arg("--verbose") .arg("--verbose")
.status_checked() .status_checked()
} else { } else {
@@ -427,20 +501,16 @@ pub fn run_nix_self_upgrade(ctx: &ExecutionContext) -> Result<()> {
} }
if !should_self_upgrade { if !should_self_upgrade {
return Err(SkipStep(String::from( return Err(SkipStep(t!("`nix upgrade-nix` can only be used on macOS or non-NixOS Linux").to_string()).into());
"`nix upgrade-nix` can only be used on macOS or non-NixOS Linux",
))
.into());
} }
if nix_profile_dir(&nix)?.is_none() { if nix_profile_dir(&nix)?.is_none() {
return Err(SkipStep(String::from( return Err(
"`nix upgrade-nix` cannot be run when Nix is installed in a profile", SkipStep(t!("`nix upgrade-nix` cannot be run when Nix is installed in a profile").to_string()).into(),
)) );
.into());
} }
print_separator("Nix (self-upgrade)"); print_separator(t!("Nix (self-upgrade)"));
let multi_user = fs::metadata(&nix)?.uid() == 0; let multi_user = fs::metadata(&nix)?.uid() == 0;
debug!("Multi user nix: {}", multi_user); debug!("Multi user nix: {}", multi_user);
@@ -505,7 +575,6 @@ fn nix_profile_dir(nix: &Path) -> Result<Option<PathBuf>> {
} }
debug!("Found Nix profile {profile_dir:?}"); debug!("Found Nix profile {profile_dir:?}");
let user_env = profile_dir let user_env = profile_dir
.canonicalize() .canonicalize()
.wrap_err_with(|| format!("Failed to canonicalize {profile_dir:?}"))?; .wrap_err_with(|| format!("Failed to canonicalize {profile_dir:?}"))?;
@@ -602,11 +671,15 @@ pub fn run_pyenv(ctx: &ExecutionContext) -> Result<()> {
.unwrap_or_else(|_| HOME_DIR.join(".pyenv")); .unwrap_or_else(|_| HOME_DIR.join(".pyenv"));
if !pyenv_dir.exists() { if !pyenv_dir.exists() {
return Err(SkipStep("Pyenv is installed, but $PYENV_ROOT is not set correctly".to_string()).into()); return Err(SkipStep(t!("Pyenv is installed, but $PYENV_ROOT is not set correctly").to_string()).into());
} }
if !pyenv_dir.join(".git").exists() { if !pyenv_dir.join(".git").exists() {
return Err(SkipStep("pyenv is not a git repository".to_string()).into()); return Err(SkipStep(t!("pyenv is not a git repository").to_string()).into());
}
if !pyenv_dir.join("plugins").join("pyenv-update").exists() {
return Err(SkipStep(t!("pyenv-update plugin is not installed").to_string()).into());
} }
ctx.run_type().execute(pyenv).arg("update").status_checked() ctx.run_type().execute(pyenv).arg("update").status_checked()
@@ -675,18 +748,10 @@ pub fn run_sdkman(ctx: &ExecutionContext) -> Result<()> {
Ok(()) Ok(())
} }
pub fn run_bun(ctx: &ExecutionContext) -> Result<()> {
let bun = require("bun")?;
print_separator("Bun");
ctx.run_type().execute(bun).arg("upgrade").status_checked()
}
pub fn run_bun_packages(ctx: &ExecutionContext) -> Result<()> { pub fn run_bun_packages(ctx: &ExecutionContext) -> Result<()> {
let bun = require("bun")?; let bun = require("bun")?;
print_separator("Bun Packages"); print_separator(t!("Bun Packages"));
let mut package_json: PathBuf = var("BUN_INSTALL") let mut package_json: PathBuf = var("BUN_INSTALL")
.map(PathBuf::from) .map(PathBuf::from)
@@ -694,7 +759,7 @@ pub fn run_bun_packages(ctx: &ExecutionContext) -> Result<()> {
package_json.push("install/global/package.json"); package_json.push("install/global/package.json");
if !package_json.exists() { if !package_json.exists() {
println!("No global packages installed"); println!("{}", t!("No global packages installed"));
return Ok(()); return Ok(());
} }
@@ -719,6 +784,7 @@ pub fn run_maza(ctx: &ExecutionContext) -> Result<()> {
} }
pub fn reboot() -> Result<()> { pub fn reboot() -> Result<()> {
print!("Rebooting..."); print!("{}", t!("Rebooting..."));
Command::new("sudo").arg("reboot").status_checked() Command::new("sudo").arg("reboot").status_checked()
} }

View File

@@ -11,6 +11,7 @@ use crate::terminal::{print_separator, print_warning};
use crate::utils::{require, which}; use crate::utils::{require, which};
use crate::{error::SkipStep, steps::git::RepoStep}; use crate::{error::SkipStep, steps::git::RepoStep};
use crate::{powershell, Step}; use crate::{powershell, Step};
use rust_i18n::t;
pub fn run_chocolatey(ctx: &ExecutionContext) -> Result<()> { pub fn run_chocolatey(ctx: &ExecutionContext) -> Result<()> {
let choco = require("choco")?; let choco = require("choco")?;
@@ -57,6 +58,10 @@ pub fn run_scoop(ctx: &ExecutionContext) -> Result<()> {
if ctx.config().cleanup() { if ctx.config().cleanup() {
ctx.run_type().execute(&scoop).args(["cleanup", "*"]).status_checked()?; ctx.run_type().execute(&scoop).args(["cleanup", "*"]).status_checked()?;
ctx.run_type()
.execute(&scoop)
.args(["cache", "rm", "-a"])
.status_checked()?
} }
Ok(()) Ok(())
@@ -64,12 +69,12 @@ pub fn run_scoop(ctx: &ExecutionContext) -> Result<()> {
pub fn update_wsl(ctx: &ExecutionContext) -> Result<()> { pub fn update_wsl(ctx: &ExecutionContext) -> Result<()> {
if !is_wsl_installed()? { if !is_wsl_installed()? {
return Err(SkipStep("WSL not installed".to_string()).into()); return Err(SkipStep(t!("WSL not installed").to_string()).into());
} }
let wsl = require("wsl")?; let wsl = require("wsl")?;
print_separator("Update WSL"); print_separator(t!("Update WSL"));
let mut wsl_command = ctx.run_type().execute(wsl); let mut wsl_command = ctx.run_type().execute(wsl);
wsl_command.args(["--update"]); wsl_command.args(["--update"]);
@@ -122,7 +127,7 @@ fn upgrade_wsl_distribution(wsl: &Path, dist: &str, ctx: &ExecutionContext) -> R
let topgrade = Command::new(wsl) let topgrade = Command::new(wsl)
.args(["-d", dist, "bash", "-lc", "which topgrade"]) .args(["-d", dist, "bash", "-lc", "which topgrade"])
.output_checked_utf8() .output_checked_utf8()
.map_err(|_| SkipStep(String::from("Could not find Topgrade installed in WSL")))? .map_err(|_| SkipStep(t!("Could not find Topgrade installed in WSL").to_string()))?
.stdout // The normal output from `which topgrade` appends a newline, so we trim it here. .stdout // The normal output from `which topgrade` appends a newline, so we trim it here.
.trim_end() .trim_end()
.to_owned(); .to_owned();
@@ -171,7 +176,7 @@ fn upgrade_wsl_distribution(wsl: &Path, dist: &str, ctx: &ExecutionContext) -> R
pub fn run_wsl_topgrade(ctx: &ExecutionContext) -> Result<()> { pub fn run_wsl_topgrade(ctx: &ExecutionContext) -> Result<()> {
if !is_wsl_installed()? { if !is_wsl_installed()? {
return Err(SkipStep("WSL not installed".to_string()).into()); return Err(SkipStep(t!("WSL not installed").to_string()).into());
} }
let wsl = require("wsl")?; let wsl = require("wsl")?;
@@ -194,23 +199,25 @@ pub fn run_wsl_topgrade(ctx: &ExecutionContext) -> Result<()> {
if ran { if ran {
Ok(()) Ok(())
} else { } else {
Err(SkipStep(String::from("Could not find Topgrade in any WSL disribution")).into()) Err(SkipStep(t!("Could not find Topgrade in any WSL disribution").to_string()).into())
} }
} }
pub fn windows_update(ctx: &ExecutionContext) -> Result<()> { pub fn windows_update(ctx: &ExecutionContext) -> Result<()> {
let powershell = powershell::Powershell::windows_powershell(); let powershell = powershell::Powershell::windows_powershell();
print_separator("Windows Update"); print_separator(t!("Windows Update"));
if powershell.supports_windows_update() { if powershell.supports_windows_update() {
println!("The installer will request to run as administrator, expect a prompt.");
powershell.windows_update(ctx) powershell.windows_update(ctx)
} else { } else {
print_warning( print_warning(t!(
"Consider installing PSWindowsUpdate as the use of Windows Update via USOClient is not supported.", "Consider installing PSWindowsUpdate as the use of Windows Update via USOClient is not supported."
); ));
Err(SkipStep("USOClient not supported.".to_string()).into()) Err(SkipStep(t!("USOClient not supported.").to_string()).into())
} }
} }
@@ -230,7 +237,7 @@ pub fn insert_startup_scripts(git_repos: &mut RepoStep) -> Result<()> {
if let Ok(lnk) = parselnk::Lnk::try_from(Path::new(&path)) { if let Ok(lnk) = parselnk::Lnk::try_from(Path::new(&path)) {
debug!("Startup link: {:?}", lnk); debug!("Startup link: {:?}", lnk);
if let Some(path) = lnk.relative_path() { if let Some(path) = lnk.relative_path() {
git_repos.insert_if_repo(&startup_dir.join(path)); git_repos.insert_if_repo(startup_dir.join(path));
} }
} }
} }

View File

@@ -4,6 +4,7 @@ use std::path::PathBuf;
use std::process::Command; use std::process::Command;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use rust_i18n::t;
use crate::command::CommandExt; use crate::command::CommandExt;
use crate::execution_context::ExecutionContext; use crate::execution_context::ExecutionContext;
@@ -62,9 +63,9 @@ impl Powershell {
} }
pub fn update_modules(&self, ctx: &ExecutionContext) -> Result<()> { pub fn update_modules(&self, ctx: &ExecutionContext) -> Result<()> {
let powershell = require_option(self.path.as_ref(), String::from("Powershell is not installed"))?; let powershell = require_option(self.path.as_ref(), t!("Powershell is not installed").to_string())?;
print_separator("Powershell Modules Update"); print_separator(t!("Powershell Modules Update"));
let mut cmd = vec!["Update-Module"]; let mut cmd = vec!["Update-Module"];
@@ -76,7 +77,7 @@ impl Powershell {
cmd.push("-Force") cmd.push("-Force")
} }
println!("Updating modules..."); println!("{}", t!("Updating modules..."));
ctx.run_type() ctx.run_type()
.execute(powershell) .execute(powershell)
// This probably doesn't need `shell_words::join`. // This probably doesn't need `shell_words::join`.
@@ -94,10 +95,18 @@ impl Powershell {
#[cfg(windows)] #[cfg(windows)]
pub fn windows_update(&self, ctx: &ExecutionContext) -> Result<()> { pub fn windows_update(&self, ctx: &ExecutionContext) -> Result<()> {
let powershell = require_option(self.path.as_ref(), String::from("Powershell is not installed"))?; let powershell = require_option(self.path.as_ref(), t!("Powershell is not installed").to_string())?;
debug_assert!(self.supports_windows_update()); debug_assert!(self.supports_windows_update());
let accept_all = if ctx.config().accept_all_windows_updates() {
"-AcceptAll"
} else {
""
};
let install_windowsupdate_verbose = "Install-WindowsUpdate -Verbose".to_string();
let mut command = if let Some(sudo) = ctx.sudo() { let mut command = if let Some(sudo) = ctx.sudo() {
let mut command = ctx.run_type().execute(sudo); let mut command = ctx.run_type().execute(sudo);
command.arg(powershell); command.arg(powershell);
@@ -107,18 +116,7 @@ impl Powershell {
}; };
command command
.args([ .args(["-NoProfile", &install_windowsupdate_verbose, accept_all])
"-NoProfile",
"-Command",
&format!(
"Start-Process powershell -Verb runAs -ArgumentList 'Import-Module PSWindowsUpdate; Install-WindowsUpdate -MicrosoftUpdate {} -Verbose'",
if ctx.config().accept_all_windows_updates() {
"-AcceptAll"
} else {
""
}
),
])
.status_checked() .status_checked()
} }
} }

View File

@@ -1,4 +1,5 @@
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use rust_i18n::t;
use crate::{ use crate::{
command::CommandExt, error::SkipStep, execution_context::ExecutionContext, terminal::print_separator, utils, command::CommandExt, error::SkipStep, execution_context::ExecutionContext, terminal::print_separator, utils,
@@ -27,7 +28,7 @@ pub fn ssh_step(ctx: &ExecutionContext, hostname: &str) -> Result<()> {
{ {
prepare_async_ssh_command(&mut args); prepare_async_ssh_command(&mut args);
crate::tmux::run_command(ctx, hostname, &shell_words::join(args))?; crate::tmux::run_command(ctx, hostname, &shell_words::join(args))?;
Err(SkipStep(String::from("Remote Topgrade launched in Tmux")).into()) Err(SkipStep(String::from(t!("Remote Topgrade launched in Tmux"))).into())
} }
#[cfg(not(unix))] #[cfg(not(unix))]
@@ -35,7 +36,7 @@ pub fn ssh_step(ctx: &ExecutionContext, hostname: &str) -> Result<()> {
} else if ctx.config().open_remotes_in_new_terminal() && !ctx.run_type().dry() && cfg!(windows) { } else if ctx.config().open_remotes_in_new_terminal() && !ctx.run_type().dry() && cfg!(windows) {
prepare_async_ssh_command(&mut args); prepare_async_ssh_command(&mut args);
ctx.run_type().execute("wt").args(&args).spawn()?; ctx.run_type().execute("wt").args(&args).spawn()?;
Err(SkipStep(String::from("Remote Topgrade launched in an external terminal")).into()) Err(SkipStep(String::from(t!("Remote Topgrade launched in an external terminal"))).into())
} else { } else {
let mut args = vec!["-t", hostname]; let mut args = vec!["-t", hostname];
@@ -47,7 +48,7 @@ pub fn ssh_step(ctx: &ExecutionContext, hostname: &str) -> Result<()> {
args.extend(["env", &env, "$SHELL", "-lc", topgrade]); args.extend(["env", &env, "$SHELL", "-lc", topgrade]);
print_separator(format!("Remote ({hostname})")); print_separator(format!("Remote ({hostname})"));
println!("Connecting to {hostname}..."); println!("{}", t!("Connecting to {hostname}...", hostname = hostname));
ctx.run_type().execute(ssh).args(&args).status_checked() ctx.run_type().execute(ssh).args(&args).status_checked()
} }

View File

@@ -4,6 +4,7 @@ use std::{fmt::Display, rc::Rc, str::FromStr};
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use regex::Regex; use regex::Regex;
use rust_i18n::t;
use strum::EnumString; use strum::EnumString;
use tracing::{debug, error}; use tracing::{debug, error};
@@ -151,14 +152,14 @@ impl<'a> Drop for TemporaryPowerOn<'a> {
pub fn collect_boxes(ctx: &ExecutionContext) -> Result<Vec<VagrantBox>> { pub fn collect_boxes(ctx: &ExecutionContext) -> Result<Vec<VagrantBox>> {
let directories = utils::require_option( let directories = utils::require_option(
ctx.config().vagrant_directories(), ctx.config().vagrant_directories(),
String::from("No Vagrant directories were specified in the configuration file"), String::from(t!("No Vagrant directories were specified in the configuration file")),
)?; )?;
let vagrant = Vagrant { let vagrant = Vagrant {
path: utils::require("vagrant")?, path: utils::require("vagrant")?,
}; };
print_separator("Vagrant"); print_separator("Vagrant");
println!("Collecting Vagrant boxes"); println!("{}", t!("Collecting Vagrant boxes"));
let mut result = Vec::new(); let mut result = Vec::new();
@@ -183,7 +184,11 @@ pub fn topgrade_vagrant_box(ctx: &ExecutionContext, vagrant_box: &VagrantBox) ->
let mut _poweron = None; let mut _poweron = None;
if !vagrant_box.initial_status.powered_on() { if !vagrant_box.initial_status.powered_on() {
if !(ctx.config().vagrant_power_on().unwrap_or(true)) { if !(ctx.config().vagrant_power_on().unwrap_or(true)) {
return Err(SkipStep(format!("Skipping powered off box {vagrant_box}")).into()); return Err(SkipStep(format!(
"{}",
t!("Skipping powered off box {vagrant_box}", vagrant_box = vagrant_box)
))
.into());
} else { } else {
print_separator(seperator); print_separator(seperator);
_poweron = Some(vagrant.temporary_power_on(vagrant_box, ctx)?); _poweron = Some(vagrant.temporary_power_on(vagrant_box, ctx)?);
@@ -205,7 +210,7 @@ pub fn topgrade_vagrant_box(ctx: &ExecutionContext, vagrant_box: &VagrantBox) ->
pub fn upgrade_vagrant_boxes(ctx: &ExecutionContext) -> Result<()> { pub fn upgrade_vagrant_boxes(ctx: &ExecutionContext) -> Result<()> {
let vagrant = utils::require("vagrant")?; let vagrant = utils::require("vagrant")?;
print_separator("Vagrant boxes"); print_separator(t!("Vagrant boxes"));
let outdated = Command::new(&vagrant) let outdated = Command::new(&vagrant)
.args(["box", "outdated", "--global"]) .args(["box", "outdated", "--global"])
@@ -227,7 +232,7 @@ pub fn upgrade_vagrant_boxes(ctx: &ExecutionContext) -> Result<()> {
} }
if !found { if !found {
println!("No outdated boxes") println!("{}", t!("No outdated boxes"))
} else { } else {
ctx.run_type() ctx.run_type()
.execute(&vagrant) .execute(&vagrant)

View File

@@ -7,6 +7,8 @@ use color_eyre::eyre::Context;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use crate::command::CommandExt; use crate::command::CommandExt;
use crate::config::TmuxConfig;
use crate::config::TmuxSessionMode;
use crate::terminal::print_separator; use crate::terminal::print_separator;
use crate::HOME_DIR; use crate::HOME_DIR;
use crate::{ use crate::{
@@ -14,6 +16,7 @@ use crate::{
utils::{which, PathExt}, utils::{which, PathExt},
}; };
use rust_i18n::t;
#[cfg(unix)] #[cfg(unix)]
use std::os::unix::process::CommandExt as _; use std::os::unix::process::CommandExt as _;
@@ -131,7 +134,7 @@ impl Tmux {
} }
} }
pub fn run_in_tmux(args: Vec<String>) -> Result<()> { pub fn run_in_tmux(config: TmuxConfig) -> Result<()> {
let command = { let command = {
let mut command = vec![ let mut command = vec![
String::from("env"), String::from("env"),
@@ -144,25 +147,39 @@ pub fn run_in_tmux(args: Vec<String>) -> Result<()> {
shell_words::join(command) shell_words::join(command)
}; };
let tmux = Tmux::new(args); let tmux = Tmux::new(config.args);
// Find an unused session and run `topgrade` in it with the current command's arguments. // Find an unused session and run `topgrade` in it with the current command's arguments.
let session_name = "topgrade"; let session_name = "topgrade";
let window_name = "topgrade"; let window_name = "topgrade";
let session = tmux.new_unique_session(session_name, window_name, &command)?; let session = tmux.new_unique_session(session_name, window_name, &command)?;
let is_inside_tmux = env::var("TMUX").is_ok();
let err = match config.session_mode {
TmuxSessionMode::AttachIfNotInSession => {
if is_inside_tmux {
// Only attach to the newly-created session if we're not currently in a tmux session. // Only attach to the newly-created session if we're not currently in a tmux session.
if env::var("TMUX").is_err() { println!("{}", t!("Topgrade launched in a new tmux session"));
let err = tmux.build().args(["attach-session", "-t", &session]).exec(); return Ok(());
Err(eyre!("{err}")).context("Failed to `execvp(3)` tmux")
} else { } else {
println!("Topgrade launched in a new tmux session"); tmux.build().args(["attach-client", "-t", &session]).exec()
Ok(())
} }
}
TmuxSessionMode::AttachAlways => {
if is_inside_tmux {
tmux.build().args(["switch-client", "-t", &session]).exec()
} else {
tmux.build().args(["attach-client", "-t", &session]).exec()
}
}
};
Err(eyre!("{err}")).context("Failed to `execvp(3)` tmux")
} }
pub fn run_command(ctx: &ExecutionContext, window_name: &str, command: &str) -> Result<()> { pub fn run_command(ctx: &ExecutionContext, window_name: &str, command: &str) -> Result<()> {
let tmux = Tmux::new(ctx.config().tmux_arguments()?); let tmux = Tmux::new(ctx.config().tmux_config()?.args);
match ctx.get_tmux_session() { match ctx.get_tmux_session() {
Some(session_name) => { Some(session_name) => {

View File

@@ -10,6 +10,7 @@ use crate::{
execution_context::ExecutionContext, execution_context::ExecutionContext,
utils::{require, PathExt}, utils::{require, PathExt},
}; };
use rust_i18n::t;
use std::path::PathBuf; use std::path::PathBuf;
use std::{ use std::{
io::{self, Write}, io::{self, Write},
@@ -64,7 +65,7 @@ fn upgrade(command: &mut Executor, ctx: &ExecutionContext) -> Result<()> {
if !status.success() { if !status.success() {
return Err(TopgradeError::ProcessFailed(command.get_program(), status).into()); return Err(TopgradeError::ProcessFailed(command.get_program(), status).into());
} else { } else {
println!("Plugins upgraded") println!("{}", t!("Plugins upgraded"))
} }
} }
@@ -77,7 +78,7 @@ pub fn upgrade_ultimate_vimrc(ctx: &ExecutionContext) -> Result<()> {
let python = require("python3")?; let python = require("python3")?;
let update_plugins = config_dir.join("update_plugins.py").require()?; let update_plugins = config_dir.join("update_plugins.py").require()?;
print_separator("The Ultimate vimrc"); print_separator(t!("The Ultimate vimrc"));
ctx.run_type() ctx.run_type()
.execute(&git) .execute(&git)
@@ -108,7 +109,7 @@ pub fn upgrade_vim(ctx: &ExecutionContext) -> Result<()> {
let output = Command::new(&vim).arg("--version").output_checked_utf8()?; let output = Command::new(&vim).arg("--version").output_checked_utf8()?;
if !output.stdout.starts_with("VIM") { if !output.stdout.starts_with("VIM") {
return Err(SkipStep(String::from("vim binary might be actually nvim")).into()); return Err(SkipStep(t!("vim binary might be actually nvim").to_string()).into());
} }
let vimrc = vimrc()?; let vimrc = vimrc()?;

View File

@@ -210,8 +210,7 @@ pub fn run_oh_my_zsh(ctx: &ExecutionContext) -> Result<()> {
.unwrap_or_else(|e| { .unwrap_or_else(|e| {
let default_path = oh_my_zsh.join("custom"); let default_path = oh_my_zsh.join("custom");
debug!( debug!(
"Running zsh returned {}. Using default path: {}", "Running zsh returned {e}. Using default path: {}",
e,
default_path.display() default_path.display()
); );
default_path default_path
@@ -229,7 +228,7 @@ pub fn run_oh_my_zsh(ctx: &ExecutionContext) -> Result<()> {
custom_repos.remove(&oh_my_zsh); custom_repos.remove(&oh_my_zsh);
ctx.run_type() ctx.run_type()
.execute("zsh") .execute("zsh")
.arg(&oh_my_zsh.join("tools/upgrade.sh")) .arg(oh_my_zsh.join("tools/upgrade.sh"))
// oh-my-zsh returns 80 when it is already updated and no changes pulled // oh-my-zsh returns 80 when it is already updated and no changes pulled
// in this update. // in this update.
// See this comment: https://github.com/r-darwish/topgrade/issues/569#issuecomment-736756731 // See this comment: https://github.com/r-darwish/topgrade/issues/569#issuecomment-736756731

View File

@@ -11,6 +11,7 @@ use color_eyre::eyre::Context;
use console::{style, Key, Term}; use console::{style, Key, Term};
use lazy_static::lazy_static; use lazy_static::lazy_static;
use notify_rust::{Notification, Timeout}; use notify_rust::{Notification, Timeout};
use rust_i18n::t;
use tracing::{debug, error}; use tracing::{debug, error};
#[cfg(windows)] #[cfg(windows)]
use which_crate::which; use which_crate::which;
@@ -144,7 +145,7 @@ impl Terminal {
self.term self.term
.write_fmt(format_args!( .write_fmt(format_args!(
"{} {}", "{} {}",
style(format!("{key} failed:")).red().bold(), style(format!("{}", t!("{key} failed:", key = key))).red().bold(),
message message
)) ))
.ok(); .ok();
@@ -174,10 +175,10 @@ impl Terminal {
"{}: {}\n", "{}: {}\n",
key, key,
match result { match result {
StepResult::Success => format!("{}", style("OK").bold().green()), StepResult::Success => format!("{}", style(t!("OK")).bold().green()),
StepResult::Failure => format!("{}", style("FAILED").bold().red()), StepResult::Failure => format!("{}", style(t!("FAILED")).bold().red()),
StepResult::Ignored => format!("{}", style("IGNORED").bold().yellow()), StepResult::Ignored => format!("{}", style(t!("IGNORED")).bold().yellow()),
StepResult::Skipped(reason) => format!("{}: {}", style("SKIPPED").bold().blue(), reason), StepResult::Skipped(reason) => format!("{}: {}", style(t!("SKIPPED")).bold().blue(), reason),
} }
)) ))
.ok(); .ok();
@@ -188,7 +189,7 @@ impl Terminal {
self.term self.term
.write_fmt(format_args!( .write_fmt(format_args!(
"{}", "{}",
style(format!("{question} (y)es/(N)o",)).yellow().bold() style(format!("{question} {}", t!("(Y)es/(N)o"))).yellow().bold()
)) ))
.ok(); .ok();
@@ -207,14 +208,14 @@ impl Terminal {
} }
if self.set_title { if self.set_title {
self.term.set_title("Topgrade - Awaiting user"); self.term.set_title(format!("Topgrade - {}", t!("Awaiting user")));
} }
if self.desktop_notification { if self.desktop_notification {
self.notify_desktop(format!("{step_name} failed"), None); self.notify_desktop(format!("{}", t!("{step_name} failed", step_name = step_name)), None);
} }
let prompt_inner = style(format!("{}Retry? (y)es/(N)o/(s)hell/(q)uit", self.prefix)) let prompt_inner = style(format!("{}{}", self.prefix, t!("Retry? (y)es/(N)o/(s)hell/(q)uit")))
.yellow() .yellow()
.bold(); .bold();
@@ -224,7 +225,10 @@ impl Terminal {
match self.term.read_key() { match self.term.read_key() {
Ok(Key::Char('y')) | Ok(Key::Char('Y')) => break Ok(true), Ok(Key::Char('y')) | Ok(Key::Char('Y')) => break Ok(true),
Ok(Key::Char('s')) | Ok(Key::Char('S')) => { Ok(Key::Char('s')) | Ok(Key::Char('S')) => {
println!("\n\nDropping you to shell. Fix what you need and then exit the shell.\n"); println!(
"\n\n{}\n",
t!("Dropping you to shell. Fix what you need and then exit the shell.")
);
if let Err(err) = run_shell().context("Failed to run shell") { if let Err(err) = run_shell().context("Failed to run shell") {
self.term.write_fmt(format_args!("{err:?}\n{prompt_inner}")).ok(); self.term.write_fmt(format_args!("{err:?}\n{prompt_inner}")).ok();
} else { } else {

View File

@@ -5,6 +5,7 @@ use std::path::{Path, PathBuf};
use std::process::Command; use std::process::Command;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use rust_i18n::t;
use tracing::{debug, error}; use tracing::{debug, error};
use tracing_subscriber::layer::SubscriberExt; use tracing_subscriber::layer::SubscriberExt;
@@ -51,7 +52,11 @@ where
debug!("Path {:?} exists", self.as_ref()); debug!("Path {:?} exists", self.as_ref());
Ok(self) Ok(self)
} else { } else {
Err(SkipStep(format!("Path {:?} doesn't exist", self.as_ref())).into()) Err(SkipStep(format!(
"{}",
t!("Path {path} doesn't exist", path = format!("{:?}", self.as_ref()))
))
.into())
} }
} }
} }
@@ -92,9 +97,14 @@ pub fn require<T: AsRef<OsStr> + Debug>(binary_name: T) -> Result<PathBuf> {
Ok(path) Ok(path)
} }
Err(e) => match e { Err(e) => match e {
which_crate::Error::CannotFindBinaryPath => { which_crate::Error::CannotFindBinaryPath => Err(SkipStep(format!(
Err(SkipStep(format!("Cannot find {:?} in PATH", &binary_name)).into()) "{}",
} t!(
"Cannot find {binary_name} in PATH",
binary_name = format!("{:?}", &binary_name)
)
))
.into()),
_ => { _ => {
panic!("Detecting {:?} failed: {}", &binary_name, e); panic!("Detecting {:?} failed: {}", &binary_name, e);
} }
@@ -123,7 +133,7 @@ pub fn hostname() -> Result<String> {
match nix::unistd::gethostname() { match nix::unistd::gethostname() {
Ok(os_str) => Ok(os_str Ok(os_str) => Ok(os_str
.into_string() .into_string()
.map_err(|_| SkipStep("Failed to get a UTF-8 encoded hostname".into()))?), .map_err(|_| SkipStep(t!("Failed to get a UTF-8 encoded hostname").into()))?),
Err(e) => Err(e.into()), Err(e) => Err(e.into()),
} }
} }
@@ -132,7 +142,7 @@ pub fn hostname() -> Result<String> {
pub fn hostname() -> Result<String> { pub fn hostname() -> Result<String> {
Command::new("hostname") Command::new("hostname")
.output_checked_utf8() .output_checked_utf8()
.map_err(|err| SkipStep(format!("Failed to get hostname: {err}")).into()) .map_err(|err| SkipStep(t!("Failed to get hostname: {err}", err = err).to_string()).into())
.map(|output| output.stdout.trim().to_owned()) .map(|output| output.stdout.trim().to_owned())
} }
@@ -191,7 +201,9 @@ pub mod merge_strategies {
// Skip causes // Skip causes
// TODO: Put them in a better place when we have more of them // 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"; pub fn get_require_sudo_string() -> String {
t!("Require sudo or counterpart but not found, skip").to_string()
}
/// Return `Err(SkipStep)` if `python` is a Python 2 or shim. /// Return `Err(SkipStep)` if `python` is a Python 2 or shim.
/// ///
@@ -218,11 +230,11 @@ pub fn check_is_python_2_or_shim(python: PathBuf) -> Result<PathBuf> {
.parse::<u32>() .parse::<u32>()
.expect("Major version should be a valid number"); .expect("Major version should be a valid number");
if major_version == 2 { if major_version == 2 {
return Err(SkipStep(format!("{} is a Python 2, skip.", python.display())).into()); return Err(SkipStep(t!("{python} is a Python 2, skip.", python = python.display()).to_string()).into());
} }
} else { } else {
// No version number, is a shim // No version number, is a shim
return Err(SkipStep(format!("{} is a Python shim, skip.", python.display())).into()); return Err(SkipStep(t!("{python} is a Python shim, skip.", python = python.display()).to_string()).into());
} }
Ok(python) Ok(python)