Compare commits
65 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea13c51b7d | ||
|
|
3ed763b884 | ||
|
|
10e1e170b7 | ||
|
|
ffa62afc66 | ||
|
|
f794329913 | ||
|
|
f9a35c7661 | ||
|
|
ed496f3462 | ||
|
|
6accdae232 | ||
|
|
96efcc6c0d | ||
|
|
bf72d7bb5a | ||
|
|
dadffb1081 | ||
|
|
78dc567226 | ||
|
|
362ce4f4f9 | ||
|
|
ab35cd7b10 | ||
|
|
15f4ad7cd1 | ||
|
|
cbfb92041f | ||
|
|
a506c67cac | ||
|
|
788e0412f6 | ||
|
|
18b37ce3e3 | ||
|
|
a15e6748c7 | ||
|
|
c6d0539fd2 | ||
|
|
3eb3867944 | ||
|
|
810315b0e2 | ||
|
|
b461fc2536 | ||
|
|
7e63977ba0 | ||
|
|
78dec892cf | ||
|
|
9ea6628b5c | ||
|
|
465df2e9be | ||
|
|
61ef926849 | ||
|
|
7fa38c593e | ||
|
|
41c6d1cd9a | ||
|
|
cf3893dc49 | ||
|
|
a2fbe92a25 | ||
|
|
e1754707d8 | ||
|
|
cd380a53b3 | ||
|
|
a8c29fd1a2 | ||
|
|
6b871e7949 | ||
|
|
1b5fdb6645 | ||
|
|
fe9d877cdf | ||
|
|
60e7aa8f03 | ||
|
|
18e2d3e59c | ||
|
|
d68fcb08b2 | ||
|
|
1f6baefdc3 | ||
|
|
71efce32c1 | ||
|
|
3626c9cdc8 | ||
|
|
a23b761304 | ||
|
|
3fd27e4913 | ||
|
|
b3f152b716 | ||
|
|
df381f3a79 | ||
|
|
2dec9db310 | ||
|
|
d50dc4c9f6 | ||
|
|
ed8b563f20 | ||
|
|
2a73aa731d | ||
|
|
4dd1c13bd8 | ||
|
|
c1c9fe22df | ||
|
|
06a6b7a2eb | ||
|
|
b814dd824f | ||
|
|
ce234bdb59 | ||
|
|
13a46a44a8 | ||
|
|
dc78b00c3c | ||
|
|
48ae4bf813 | ||
|
|
a50040e2d5 | ||
|
|
2c9a56a8df | ||
|
|
021320b292 | ||
|
|
9d3662c3ea |
14
.github/ISSUE_TEMPLATE/bug_report.md
vendored
14
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -2,7 +2,7 @@
|
|||||||
name: Bug report
|
name: Bug report
|
||||||
about: Topgrade is misbehaving
|
about: Topgrade is misbehaving
|
||||||
title: ''
|
title: ''
|
||||||
labels: 'bug'
|
labels: 'C-bug'
|
||||||
assignees: ''
|
assignees: ''
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -46,6 +46,18 @@ If you know the possible cause of the issue, please tell us.
|
|||||||
Execute the erroneous command directly to see if the problem persists
|
Execute the erroneous command directly to see if the problem persists
|
||||||
-->
|
-->
|
||||||
- [ ] Yes
|
- [ ] Yes
|
||||||
|
- [ ] No
|
||||||
|
|
||||||
|
## Did you run topgrade through `Remote Execution`
|
||||||
|
|
||||||
|
- [ ] Yes
|
||||||
|
- [ ] No
|
||||||
|
|
||||||
|
If yes, does the issue still occur when you run topgrade directlly in your
|
||||||
|
remote host
|
||||||
|
|
||||||
|
- [ ] Yes
|
||||||
|
- [ ] No
|
||||||
|
|
||||||
## Configuration file (Optional)
|
## Configuration file (Optional)
|
||||||
<!--
|
<!--
|
||||||
|
|||||||
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -2,7 +2,7 @@
|
|||||||
name: Feature request
|
name: Feature request
|
||||||
about: Can you please support...?
|
about: Can you please support...?
|
||||||
title: ''
|
title: ''
|
||||||
labels: ''
|
labels: 'C-feature request'
|
||||||
assignees: ''
|
assignees: ''
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
7
.github/PULL_REQUEST_TEMPLATE.md
vendored
7
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -7,7 +7,12 @@
|
|||||||
- [ ] The code passes clippy (`cargo clippy`)
|
- [ ] The code passes clippy (`cargo clippy`)
|
||||||
- [ ] The code passes tests (`cargo test`)
|
- [ ] The code passes tests (`cargo test`)
|
||||||
- [ ] *Optional:* I have tested the code myself
|
- [ ] *Optional:* I have tested the code myself
|
||||||
- [ ] I also tested that Topgrade skips the step where needed
|
|
||||||
|
## For new steps
|
||||||
|
- [ ] *Optional:* Topgrade skips this step where needed
|
||||||
|
- [ ] *Optional:* The `--dry-run` option works with this step
|
||||||
|
- [ ] *Optional:* The `--yes` option works with this step if it is supported by
|
||||||
|
the underlying command
|
||||||
|
|
||||||
If you developed a feature or a bug fix for someone else and you do not have the
|
If you developed a feature or a bug fix for someone else and you do not have the
|
||||||
means to test it, please tag this person here.
|
means to test it, please tag this person here.
|
||||||
|
|||||||
2
.github/workflows/check-and-lint.yaml
vendored
2
.github/workflows/check-and-lint.yaml
vendored
@@ -7,7 +7,7 @@ on:
|
|||||||
name: CI
|
name: CI
|
||||||
|
|
||||||
env:
|
env:
|
||||||
RUST_VER: '1.71.0'
|
RUST_VER: 'stable'
|
||||||
CROSS_VER: '0.2.5'
|
CROSS_VER: '0.2.5'
|
||||||
CARGO_NET_RETRY: 3
|
CARGO_NET_RETRY: 3
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/test-config-creation.yml
vendored
2
.github/workflows/test-config-creation.yml
vendored
@@ -17,5 +17,5 @@ jobs:
|
|||||||
CONFIG_PATH=~/.config/topgrade.toml;
|
CONFIG_PATH=~/.config/topgrade.toml;
|
||||||
if [ -f "$CONFIG_PATH" ]; then rm $CONFIG_PATH; fi
|
if [ -f "$CONFIG_PATH" ]; then rm $CONFIG_PATH; fi
|
||||||
cargo build;
|
cargo build;
|
||||||
./target/debug/topgrade --dry-run --only system;
|
TOPGRADE_SKIP_BRKC_NOTIFY=true ./target/debug/topgrade --dry-run --only system;
|
||||||
stat $CONFIG_PATH;
|
stat $CONFIG_PATH;
|
||||||
|
|||||||
3
.github/workflows/update_homebrew.yml
vendored
3
.github/workflows/update_homebrew.yml
vendored
@@ -29,7 +29,8 @@ jobs:
|
|||||||
if: steps.cache.outputs.cache-hit != 'true'
|
if: steps.cache.outputs.cache-hit != 'true'
|
||||||
run: brew install-bundler-gems
|
run: brew install-bundler-gems
|
||||||
- name: Bump formulae
|
- name: Bump formulae
|
||||||
uses: Homebrew/actions/bump-formulae@master
|
uses: Homebrew/actions/bump-packages@master
|
||||||
|
continue-on-error: true
|
||||||
with:
|
with:
|
||||||
# Custom GitHub access token with only the 'public_repo' scope enabled
|
# Custom GitHub access token with only the 'public_repo' scope enabled
|
||||||
token: ${{secrets.HOMEBREW_ACCESS_TOKEN}}
|
token: ${{secrets.HOMEBREW_ACCESS_TOKEN}}
|
||||||
|
|||||||
12
BREAKINGCHANGES.md
Normal file
12
BREAKINGCHANGES.md
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
1. In 13.0.0, we introduced a new feature, pushing git repos, now this feature
|
||||||
|
has been removed as some users are not satisfied with it.
|
||||||
|
|
||||||
|
For configuration entries, the following ones are gone:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[git]
|
||||||
|
pull_only_repos = []
|
||||||
|
push_only_repos = []
|
||||||
|
pull_arguments = ""
|
||||||
|
push_arguments = ""
|
||||||
|
```
|
||||||
0
BREAKINGCHANGES_dev.md
Normal file
0
BREAKINGCHANGES_dev.md
Normal file
@@ -101,6 +101,21 @@ Be sure to apply your changes to
|
|||||||
[`config.example.toml`](https://github.com/topgrade-rs/topgrade/blob/master/config.example.toml),
|
[`config.example.toml`](https://github.com/topgrade-rs/topgrade/blob/master/config.example.toml),
|
||||||
and have some basic documentations guiding user how to use these options.
|
and have some basic documentations guiding user how to use these options.
|
||||||
|
|
||||||
|
## Breaking changes
|
||||||
|
|
||||||
|
If your PR introduces a breaking change, document it in [`BREAKINGCHANGES_dev.md`][bc_dev],
|
||||||
|
it should be written in Markdown and wrapped in 80, for example:
|
||||||
|
|
||||||
|
```md
|
||||||
|
1. The configuration location has been updated to x.
|
||||||
|
|
||||||
|
2. The step x has been removed.
|
||||||
|
|
||||||
|
3. ...
|
||||||
|
```
|
||||||
|
|
||||||
|
[bc_dev]: https://github.com/topgrade-rs/topgrade/blob/main/BREAKINGCHANGES_dev.md
|
||||||
|
|
||||||
## Before you submit your PR
|
## Before you submit your PR
|
||||||
|
|
||||||
Make sure your patch passes the following tests on your host:
|
Make sure your patch passes the following tests on your host:
|
||||||
|
|||||||
1400
Cargo.lock
generated
1400
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
31
Cargo.toml
31
Cargo.toml
@@ -5,9 +5,9 @@ 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 = "12.0.2"
|
version = "14.0.1"
|
||||||
authors = ["Roey Darwish Dror <roey.ghost@gmail.com>", "Thomas Schönauer <t.schoenauer@hgs-wt.at>"]
|
authors = ["Roey Darwish Dror <roey.ghost@gmail.com>", "Thomas Schönauer <t.schoenauer@hgs-wt.at>"]
|
||||||
exclude = ["doc/screenshot.gif"]
|
exclude = ["doc/screenshot.gif", "BREAKINGCHANGES_dev.md"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
@@ -22,26 +22,26 @@ path = "src/main.rs"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
home = "~0.5"
|
home = "~0.5"
|
||||||
etcetera = "~0.8"
|
etcetera = "~0.8"
|
||||||
once_cell = "~1.17"
|
once_cell = "~1.18"
|
||||||
serde = { version = "~1.0", features = ["derive"] }
|
serde = { version = "~1.0", features = ["derive"] }
|
||||||
toml = "0.5"
|
toml = "0.8"
|
||||||
which_crate = { version = "~4.1", package = "which" }
|
which_crate = { version = "~4.1", package = "which" }
|
||||||
shellexpand = "~2.1"
|
shellexpand = "~3.1"
|
||||||
clap = { version = "~3.1", features = ["cargo", "derive"] }
|
clap = { version = "~4.4", features = ["cargo", "derive"] }
|
||||||
clap_complete = "~3.1"
|
clap_complete = "~4.4"
|
||||||
clap_mangen = "~0.1"
|
clap_mangen = "~0.2"
|
||||||
walkdir = "~2.3"
|
walkdir = "~2.4"
|
||||||
console = "~0.15"
|
console = "~0.15"
|
||||||
lazy_static = "~1.4"
|
lazy_static = "~1.4"
|
||||||
chrono = "~0.4"
|
chrono = "~0.4"
|
||||||
glob = "~0.3"
|
glob = "~0.3"
|
||||||
strum = { version = "~0.24", features = ["derive"] }
|
strum = { version = "~0.24", features = ["derive"] }
|
||||||
thiserror = "~1.0"
|
thiserror = "~1.0"
|
||||||
tempfile = "~3.6"
|
tempfile = "~3.8"
|
||||||
cfg-if = "~1.0"
|
cfg-if = "~1.0"
|
||||||
tokio = { version = "~1.18", features = ["process", "rt-multi-thread"] }
|
tokio = { version = "~1.34", features = ["process", "rt-multi-thread"] }
|
||||||
futures = "~0.3"
|
futures = "~0.3"
|
||||||
regex = "~1.7"
|
regex = "~1.10"
|
||||||
semver = "~1.0"
|
semver = "~1.0"
|
||||||
shell-words = "~1.1"
|
shell-words = "~1.1"
|
||||||
color-eyre = "~0.6"
|
color-eyre = "~0.6"
|
||||||
@@ -49,10 +49,10 @@ tracing = { version = "~0.1", features = ["attributes", "log"] }
|
|||||||
tracing-subscriber = { version = "~0.3", features = ["env-filter", "time"] }
|
tracing-subscriber = { version = "~0.3", features = ["env-filter", "time"] }
|
||||||
merge = "~0.1"
|
merge = "~0.1"
|
||||||
regex-split = "~0.1"
|
regex-split = "~0.1"
|
||||||
notify-rust = "~4.8"
|
notify-rust = "~4.10"
|
||||||
|
|
||||||
[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" }]
|
||||||
|
|
||||||
[package.metadata.generate-rpm.requires]
|
[package.metadata.generate-rpm.requires]
|
||||||
git = "*"
|
git = "*"
|
||||||
@@ -61,8 +61,7 @@ git = "*"
|
|||||||
depends = "$auto,git"
|
depends = "$auto,git"
|
||||||
|
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
libc = "~0.2"
|
nix = { version = "~0.27", features = ["hostname", "signal", "user"] }
|
||||||
nix = "~0.24"
|
|
||||||
rust-ini = "~0.19"
|
rust-ini = "~0.19"
|
||||||
self_update_crate = { version = "~0.30", default-features = false, optional = true, package = "self_update", features = ["archive-tar", "compression-flate2", "rustls"] }
|
self_update_crate = { version = "~0.30", default-features = false, optional = true, package = "self_update", features = ["archive-tar", "compression-flate2", "rustls"] }
|
||||||
|
|
||||||
|
|||||||
19
README.md
19
README.md
@@ -8,13 +8,9 @@
|
|||||||
<a href="https://aur.archlinux.org/packages/topgrade"><img alt="AUR" src="https://img.shields.io/aur/version/topgrade.svg"></a>
|
<a href="https://aur.archlinux.org/packages/topgrade"><img alt="AUR" src="https://img.shields.io/aur/version/topgrade.svg"></a>
|
||||||
<a href="https://formulae.brew.sh/formula/topgrade"><img alt="Homebrew" src="https://img.shields.io/homebrew/v/topgrade.svg"></a>
|
<a href="https://formulae.brew.sh/formula/topgrade"><img alt="Homebrew" src="https://img.shields.io/homebrew/v/topgrade.svg"></a>
|
||||||
|
|
||||||
<img alt="Demo" src="doc/screenshot.gif" width="550px">
|
<img alt="Demo" src="doc/topgrade_demo.gif">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
## Maintainers Wanted
|
|
||||||
|
|
||||||
I currently have not enough time to maintain this project on the level required and which the project deserves. For this reason I'm asking the community to help supporting the project, to help and work on resolving issues and create new features. Thanks for all your help.
|
|
||||||
|
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
@@ -46,15 +42,18 @@ The compiled binaries contain a self-upgrading feature.
|
|||||||
|
|
||||||
Just run `topgrade`.
|
Just run `topgrade`.
|
||||||
|
|
||||||
Visit the documentation at [topgrade-rs.github.io](https://topgrade-rs.github.io/) for more information.
|
|
||||||
|
|
||||||
> **Warning**
|
|
||||||
> Work in Progress
|
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
See `config.example.toml` for an example configuration file.
|
See `config.example.toml` for an example configuration file.
|
||||||
|
|
||||||
|
## Migration and Breaking Changes
|
||||||
|
|
||||||
|
Whenever there is a **breaking change**, the major version number will be bumped,
|
||||||
|
and we will document these changes in the release note, please take a look at
|
||||||
|
it when updated to a major release.
|
||||||
|
|
||||||
|
> Got a question? Feel free to open an issue or discussion!
|
||||||
|
|
||||||
### Configuration Path
|
### Configuration Path
|
||||||
|
|
||||||
#### `CONFIG_DIR` on each platform
|
#### `CONFIG_DIR` on each platform
|
||||||
|
|||||||
64
RELEASE_PROCEDURE.md
Normal file
64
RELEASE_PROCEDURE.md
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
> This document lists the steps that lead to a successful release of Topgrade.
|
||||||
|
|
||||||
|
1. Open a PR that:
|
||||||
|
|
||||||
|
> Here is an [Example PR](https://github.com/topgrade-rs/topgrade/pull/652)
|
||||||
|
> that you can refer to.
|
||||||
|
|
||||||
|
1. bumps the version number.
|
||||||
|
|
||||||
|
> If there are breaking changes, the major version number should be increased.
|
||||||
|
|
||||||
|
2. Overwrite [`BREAKINGCHANGES`][breaking_changes] with
|
||||||
|
[`BREAKINGCHANGES_dev`][breaking_changes_dev], and create a new dev file:
|
||||||
|
|
||||||
|
```sh'
|
||||||
|
$ cd topgrade
|
||||||
|
$ cp BREAKINGCHANGES_dev.md BREAKINGCHANGES.md
|
||||||
|
$ touch BREAKINGCHANGES_dev.md
|
||||||
|
```
|
||||||
|
|
||||||
|
[breaking_changes_dev]: https://github.com/topgrade-rs/topgrade/blob/main/BREAKINGCHANGES_dev.md
|
||||||
|
[breaking_changes]: https://github.com/topgrade-rs/topgrade/blob/main/BREAKINGCHANGES.md
|
||||||
|
|
||||||
|
2. Check and merge that PR.
|
||||||
|
|
||||||
|
3. Go to the [release](https://github.com/topgrade-rs/topgrade/releases) page
|
||||||
|
and click the [Draft a new release button](https://github.com/topgrade-rs/topgrade/releases/new)
|
||||||
|
|
||||||
|
4. Write the release notes
|
||||||
|
|
||||||
|
We usually use GitHub's [Automatically generated release notes][auto_gen_release_notes]
|
||||||
|
functionality to generate release notes, but you write your own one instead.
|
||||||
|
|
||||||
|
[auto_gen_release_notes]: https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes
|
||||||
|
|
||||||
|
5. Attaching binaries
|
||||||
|
|
||||||
|
You don't need to do this as our CI will automatically do it for you,
|
||||||
|
binaries for Linux, macOS and Windows will be created and attached.
|
||||||
|
|
||||||
|
And the CI will publish the new binary to:
|
||||||
|
|
||||||
|
1. AUR
|
||||||
|
2. PyPi
|
||||||
|
3. Homebrew (seems that this is not working correctly)
|
||||||
|
|
||||||
|
6. Manually release it to Crates.io
|
||||||
|
|
||||||
|
> Yeah, this is unfortunate, our CI won't do this for us. We should probably add one.
|
||||||
|
|
||||||
|
1. `cd` to the Topgrade directory, make sure that it is the latest version
|
||||||
|
(i.e., including the PR that bumps the version number).
|
||||||
|
2. Set up your token with `cargo login`.
|
||||||
|
3. Dry-run the publish `cargo publish --dry-run`.
|
||||||
|
4. If step 3 works, then do the final release `cargo publish`.
|
||||||
|
|
||||||
|
> You can also take a look at the official tutorial [Publishing on crates.io][doc]
|
||||||
|
>
|
||||||
|
> [doc]: https://doc.rust-lang.org/cargo/reference/publishing.html
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -2,168 +2,231 @@
|
|||||||
# [include] sections are processed in the order you write them
|
# [include] sections are processed in the order you write them
|
||||||
# Files in $CONFIG_DIR/topgrade.d/ are automatically included before this file
|
# Files in $CONFIG_DIR/topgrade.d/ are automatically included before this file
|
||||||
[include]
|
[include]
|
||||||
#paths = ["/etc/topgrade.toml"]
|
# paths = ["/etc/topgrade.toml"]
|
||||||
|
|
||||||
|
|
||||||
[misc]
|
[misc]
|
||||||
# Don't ask for confirmations
|
|
||||||
#assume_yes = true
|
|
||||||
|
|
||||||
# Disable specific steps - same options as the command line flag
|
|
||||||
#disable = ["system", "emacs"]
|
|
||||||
|
|
||||||
# Ignore failures for these steps
|
|
||||||
#ignore_failures = ["powershell"]
|
|
||||||
|
|
||||||
# Run specific steps - same options as the command line flag
|
|
||||||
#only = ["system", "emacs"]
|
|
||||||
|
|
||||||
# Do not ask to retry failed steps (default: false)
|
|
||||||
#no_retry = true
|
|
||||||
|
|
||||||
# Sudo command to be used
|
|
||||||
#sudo_command = "sudo"
|
|
||||||
|
|
||||||
# Run `sudo -v` to cache credentials at the start of the run
|
# Run `sudo -v` to cache credentials at the start of the run
|
||||||
# This avoids a blocking password prompt in the middle of an unattended run
|
# This avoids a blocking password prompt in the middle of an unattended run
|
||||||
#pre_sudo = false
|
# (default: false)
|
||||||
|
# pre_sudo = false
|
||||||
|
|
||||||
# Run inside tmux
|
# Sudo command to be used
|
||||||
#run_in_tmux = true
|
# sudo_command = "sudo"
|
||||||
|
|
||||||
|
# Disable specific steps - same options as the command line flag
|
||||||
|
# disable = ["system", "emacs"]
|
||||||
|
|
||||||
|
# Ignore failures for these steps
|
||||||
|
# ignore_failures = ["powershell"]
|
||||||
|
|
||||||
# List of remote machines with Topgrade installed on them
|
# List of remote machines with Topgrade installed on them
|
||||||
#remote_topgrades = ["toothless", "pi", "parnas"]
|
# remote_topgrades = ["toothless", "pi", "parnas"]
|
||||||
|
|
||||||
# Arguments to pass to SSH when upgrading remote systems
|
|
||||||
#ssh_arguments = "-o ConnectTimeout=2"
|
|
||||||
|
|
||||||
# Path to Topgrade executable on remote machines
|
# Path to Topgrade executable on remote machines
|
||||||
#remote_topgrade_path = ".cargo/bin/topgrade"
|
# remote_topgrade_path = ".cargo/bin/topgrade"
|
||||||
|
|
||||||
|
# Arguments to pass to SSH when upgrading remote systems
|
||||||
|
# ssh_arguments = "-o ConnectTimeout=2"
|
||||||
|
|
||||||
# Arguments to pass tmux when pulling Repositories
|
# Arguments to pass tmux when pulling Repositories
|
||||||
#tmux_arguments = "-S /var/tmux.sock"
|
# tmux_arguments = "-S /var/tmux.sock"
|
||||||
|
|
||||||
# Do not set the terminal title
|
# Do not set the terminal title (default: true)
|
||||||
#set_title = false
|
# set_title = true
|
||||||
|
|
||||||
# Display the time in step titles
|
# Display the time in step titles (default: true)
|
||||||
# display_time = true
|
# display_time = true
|
||||||
|
|
||||||
# Cleanup temporary or old files
|
# Don't ask for confirmations (no default value)
|
||||||
#cleanup = true
|
# assume_yes = true
|
||||||
|
|
||||||
# Skip sending a notification at the end of a run
|
# Do not ask to retry failed steps (default: false)
|
||||||
#skip_notify = true
|
# no_retry = true
|
||||||
|
|
||||||
# Whether to self update (this is ignored if the binary has been built without self update support, available also via setting the environment variable TOPGRADE_NO_SELF_UPGRADE)
|
# Run inside tmux (default: false)
|
||||||
#no_self_update = true
|
# run_in_tmux = true
|
||||||
|
|
||||||
|
# Cleanup temporary or old files (default: false)
|
||||||
|
# cleanup = true
|
||||||
|
|
||||||
|
# Send a notification for every step (default: false)
|
||||||
|
# notify_each_step = false
|
||||||
|
|
||||||
|
# Skip sending a notification at the end of a run (default: false)
|
||||||
|
# skip_notify = true
|
||||||
|
|
||||||
|
# The Bash-it branch to update (default: "stable")
|
||||||
|
# bashit_branch = "stable"
|
||||||
|
|
||||||
|
# Run specific steps - same options as the command line flag
|
||||||
|
# only = ["system", "emacs"]
|
||||||
|
|
||||||
|
# Whether to self update
|
||||||
|
#
|
||||||
|
# this will be ignored if the binary is built without self update support
|
||||||
|
#
|
||||||
|
# available also via setting the environment variable TOPGRADE_NO_SELF_UPGRADE)
|
||||||
|
# no_self_update = true
|
||||||
|
|
||||||
|
# Extra tracing filter directives
|
||||||
|
# These are prepended to the `--log-filter` argument
|
||||||
|
# See: https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives
|
||||||
|
# log_filters = ["topgrade::command=debug", "warn"]
|
||||||
|
|
||||||
# Extra Home Manager arguments
|
|
||||||
#home_manager_arguments = ["--flake", "file"]
|
|
||||||
|
|
||||||
# Commands to run before anything
|
# Commands to run before anything
|
||||||
[pre_commands]
|
[pre_commands]
|
||||||
#"Emacs Snapshot" = "rm -rf ~/.emacs.d/elpa.bak && cp -rl ~/.emacs.d/elpa ~/.emacs.d/elpa.bak"
|
# "Emacs Snapshot" = "rm -rf ~/.emacs.d/elpa.bak && cp -rl ~/.emacs.d/elpa ~/.emacs.d/elpa.bak"
|
||||||
|
|
||||||
|
|
||||||
# Commands to run after anything
|
# Commands to run after anything
|
||||||
[post_commands]
|
[post_commands]
|
||||||
#"Emacs Snapshot" = "rm -rf ~/.emacs.d/elpa.bak && cp -rl ~/.emacs.d/elpa ~/.emacs.d/elpa.bak"
|
# "Emacs Snapshot" = "rm -rf ~/.emacs.d/elpa.bak && cp -rl ~/.emacs.d/elpa ~/.emacs.d/elpa.bak"
|
||||||
|
|
||||||
|
|
||||||
# Custom commands
|
# Custom commands
|
||||||
[commands]
|
[commands]
|
||||||
#"Python Environment" = "~/dev/.env/bin/pip install -i https://pypi.python.org/simple -U --upgrade-strategy eager jupyter"
|
# "Python Environment" = "~/dev/.env/bin/pip install -i https://pypi.python.org/simple -U --upgrade-strategy eager jupyter"
|
||||||
#"Custom command using interactive shell (unix)" = "-i vim_upgrade"
|
# "Custom command using interactive shell (unix)" = "-i vim_upgrade"
|
||||||
|
|
||||||
|
|
||||||
[python]
|
[python]
|
||||||
#enable_pip_review = true ###disabled by default
|
# enable_pip_review = true ###disabled by default
|
||||||
#enable_pip_review_local = true ###disabled by default
|
# enable_pip_review_local = true ###disabled by default
|
||||||
#enable_pipupgrade = true ###disabled by default
|
# enable_pipupgrade = true ###disabled by default
|
||||||
#pipupgrade_arguments = "-y -u --pip-path pip" ###disabled by default
|
# pipupgrade_arguments = "-y -u --pip-path pip" ###disabled by default
|
||||||
|
|
||||||
|
|
||||||
[composer]
|
[composer]
|
||||||
#self_update = true
|
# self_update = true
|
||||||
|
|
||||||
|
|
||||||
[brew]
|
[brew]
|
||||||
#greedy_cask = true
|
# greedy_cask = true
|
||||||
#autoremove = true
|
# autoremove = true
|
||||||
|
|
||||||
|
|
||||||
[linux]
|
[linux]
|
||||||
# Arch Package Manager to use. Allowed values: autodetect, aura, garuda_update, pacman, pamac, paru, pikaur, trizen, yay.
|
# Arch Package Manager to use.
|
||||||
#arch_package_manager = "pacman"
|
# Allowed values:
|
||||||
|
# autodetect, aura, garuda_update, pacman, pamac, paru, pikaur, trizen, yay
|
||||||
|
# arch_package_manager = "pacman"
|
||||||
|
|
||||||
# Arguments to pass yay (or paru) when updating packages
|
# Arguments to pass yay (or paru) when updating packages
|
||||||
#yay_arguments = "--nodevel"
|
# yay_arguments = "--nodevel"
|
||||||
|
|
||||||
# Arguments to pass dnf when updating packages
|
# Arguments to pass dnf when updating packages
|
||||||
#dnf_arguments = "--refresh"
|
# dnf_arguments = "--refresh"
|
||||||
#aura_aur_arguments = "-kx"
|
|
||||||
#aura_pacman_arguments = ""
|
# aura_aur_arguments = "-kx"
|
||||||
#garuda_update_arguments = ""
|
|
||||||
#show_arch_news = true
|
# aura_pacman_arguments = ""
|
||||||
#trizen_arguments = "--devel"
|
# garuda_update_arguments = ""
|
||||||
#pikaur_arguments = ""
|
|
||||||
#pamac_arguments = "--no-devel"
|
# show_arch_news = true
|
||||||
#enable_tlmgr = true
|
|
||||||
#emerge_sync_flags = "-q"
|
# trizen_arguments = "--devel"
|
||||||
#emerge_update_flags = "-uDNa --with-bdeps=y world"
|
|
||||||
#redhat_distro_sync = false
|
# pikaur_arguments = ""
|
||||||
#suse_dup = false
|
|
||||||
#rpm_ostree = false
|
# pamac_arguments = "--no-devel"
|
||||||
#nix_arguments = "--flake"
|
|
||||||
|
# enable_tlmgr = true
|
||||||
|
|
||||||
|
# emerge_sync_flags = "-q"
|
||||||
|
|
||||||
|
# emerge_update_flags = "-uDNa --with-bdeps=y world"
|
||||||
|
|
||||||
|
# redhat_distro_sync = false
|
||||||
|
|
||||||
|
# suse_dup = false
|
||||||
|
|
||||||
|
# rpm_ostree = false
|
||||||
|
|
||||||
|
# nix_arguments = "--flake"
|
||||||
|
|
||||||
|
# nix_env_arguments = "--prebuilt-only"
|
||||||
|
|
||||||
|
# Extra Home Manager arguments
|
||||||
|
# home_manager_arguments = ["--flake", "file"]
|
||||||
|
|
||||||
|
|
||||||
[git]
|
[git]
|
||||||
#max_concurrency = 5
|
# How many repos to pull at max in parallel
|
||||||
|
# max_concurrency = 5
|
||||||
|
|
||||||
# Additional git repositories to pull
|
# Additional git repositories to pull
|
||||||
#repos = [
|
# repos = [
|
||||||
# "~/src/*/",
|
# "~/src/*/",
|
||||||
# "~/.config/something"
|
# "~/.config/something"
|
||||||
#]
|
# ]
|
||||||
|
|
||||||
# Don't pull the predefined git repos
|
# Don't pull the predefined git repos
|
||||||
#pull_predefined = false
|
# pull_predefined = false
|
||||||
|
|
||||||
# Arguments to pass Git when pulling Repositories
|
# Arguments to pass Git when pulling Repositories
|
||||||
#arguments = "--rebase --autostash"
|
# arguments = "--rebase --autostash"
|
||||||
|
|
||||||
|
|
||||||
[windows]
|
[windows]
|
||||||
# Manually select Windows updates
|
# Manually select Windows updates
|
||||||
#accept_all_updates = false
|
# accept_all_updates = false
|
||||||
#open_remotes_in_new_terminal = true
|
|
||||||
#wsl_update_pre_release = true
|
# open_remotes_in_new_terminal = true
|
||||||
#wsl_update_use_web_download = true
|
|
||||||
|
# wsl_update_pre_release = true
|
||||||
|
|
||||||
|
# wsl_update_use_web_download = true
|
||||||
|
|
||||||
# Causes Topgrade to rename itself during the run to allow package managers
|
# Causes Topgrade to rename itself during the run to allow package managers
|
||||||
# to upgrade it. Use this only if you installed Topgrade by using a package
|
# to upgrade it. Use this only if you installed Topgrade by using a package
|
||||||
# manager such as Scoop or Cargo
|
# manager such as Scoop or Cargo
|
||||||
#self_rename = true
|
# self_rename = true
|
||||||
|
|
||||||
|
# Enable WinGet upgrade
|
||||||
|
# enable_winget = true
|
||||||
|
|
||||||
|
|
||||||
[npm]
|
[npm]
|
||||||
# Use sudo if the NPM directory isn't owned by the current user
|
# Use sudo if the NPM directory isn't owned by the current user
|
||||||
#use_sudo = true
|
# use_sudo = true
|
||||||
|
|
||||||
|
|
||||||
[yarn]
|
[yarn]
|
||||||
# Run `yarn global upgrade` with `sudo`
|
# Run `yarn global upgrade` with `sudo`
|
||||||
#use_sudo = true
|
# use_sudo = true
|
||||||
|
|
||||||
|
|
||||||
[vim]
|
[vim]
|
||||||
# For `vim-plug`, execute `PlugUpdate!` instead of `PlugUpdate`
|
# For `vim-plug`, execute `PlugUpdate!` instead of `PlugUpdate`
|
||||||
#force_plug_update = true
|
# force_plug_update = true
|
||||||
|
|
||||||
|
|
||||||
[firmware]
|
[firmware]
|
||||||
# Offer to update firmware; if false just check for and display available updates
|
# Offer to update firmware; if false just check for and display available updates
|
||||||
#upgrade = true
|
# upgrade = true
|
||||||
|
|
||||||
|
|
||||||
[vagrant]
|
[vagrant]
|
||||||
# Vagrant directories
|
# Vagrant directories
|
||||||
#directories = []
|
# directories = []
|
||||||
|
|
||||||
# power on vagrant boxes if needed
|
# power on vagrant boxes if needed
|
||||||
#power_on = true
|
# power_on = true
|
||||||
|
|
||||||
# Always suspend vagrant boxes instead of powering off
|
# Always suspend vagrant boxes instead of powering off
|
||||||
#always_suspend = true
|
# always_suspend = true
|
||||||
|
|
||||||
|
|
||||||
[flatpak]
|
[flatpak]
|
||||||
# Use sudo for updating the system-wide installation
|
# Use sudo for updating the system-wide installation
|
||||||
#use_sudo = true
|
# use_sudo = true
|
||||||
|
|
||||||
|
|
||||||
[distrobox]
|
[distrobox]
|
||||||
#use_root = false
|
# use_root = false
|
||||||
#containers = ["archlinux-latest"]
|
|
||||||
|
# containers = ["archlinux-latest"]
|
||||||
|
[containers]
|
||||||
|
# ignored_containers = ["ghcr.io/rancher-sandbox/rancher-desktop/rdx-proxy:latest"]
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 718 KiB |
BIN
doc/topgrade_demo.gif
Normal file
BIN
doc/topgrade_demo.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.1 MiB |
167
src/breaking_changes.rs
Normal file
167
src/breaking_changes.rs
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
//! Inform the users of the breaking changes introduced in this major release.
|
||||||
|
//!
|
||||||
|
//! Print the breaking changes and possibly a migration guide when:
|
||||||
|
//! 1. The Topgrade being executed is a new major release
|
||||||
|
//! 2. This is the first launch of that major release
|
||||||
|
|
||||||
|
use crate::terminal::print_separator;
|
||||||
|
#[cfg(windows)]
|
||||||
|
use crate::WINDOWS_DIRS;
|
||||||
|
#[cfg(unix)]
|
||||||
|
use crate::XDG_DIRS;
|
||||||
|
use color_eyre::eyre::Result;
|
||||||
|
use etcetera::base_strategy::BaseStrategy;
|
||||||
|
use std::{
|
||||||
|
env::var,
|
||||||
|
fs::{read_to_string, OpenOptions},
|
||||||
|
io::Write,
|
||||||
|
path::PathBuf,
|
||||||
|
str::FromStr,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Version string x.y.z
|
||||||
|
static VERSION_STR: &str = env!("CARGO_PKG_VERSION");
|
||||||
|
|
||||||
|
/// Version info
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct Version {
|
||||||
|
_major: u64,
|
||||||
|
minor: u64,
|
||||||
|
patch: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Version {
|
||||||
|
type Err = std::convert::Infallible;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
const NOT_SEMVER: &str = "Topgrade version is not semantic";
|
||||||
|
const NOT_NUMBER: &str = "Topgrade version is not dot-separated numbers";
|
||||||
|
|
||||||
|
let mut iter = s.split('.').take(3);
|
||||||
|
let major = iter.next().expect(NOT_SEMVER).parse().expect(NOT_NUMBER);
|
||||||
|
let minor = iter.next().expect(NOT_SEMVER).parse().expect(NOT_NUMBER);
|
||||||
|
let patch = iter.next().expect(NOT_SEMVER).parse().expect(NOT_NUMBER);
|
||||||
|
|
||||||
|
// They cannot be all 0s
|
||||||
|
assert!(
|
||||||
|
!(major == 0 && minor == 0 && patch == 0),
|
||||||
|
"Version numbers can not be all 0s"
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
_major: major,
|
||||||
|
minor,
|
||||||
|
patch,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Version {
|
||||||
|
/// True if this version is a new major release.
|
||||||
|
pub(crate) fn is_new_major_release(&self) -> bool {
|
||||||
|
// We have already checked that they cannot all be zeros, so `self.major`
|
||||||
|
// is guaranteed to be non-zero.
|
||||||
|
self.minor == 0 && self.patch == 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Topgrade's breaking changes
|
||||||
|
///
|
||||||
|
/// We store them in the compiled binary.
|
||||||
|
pub(crate) static BREAKINGCHANGES: &str = include_str!("../BREAKINGCHANGES.md");
|
||||||
|
|
||||||
|
/// Return platform's data directory.
|
||||||
|
fn data_dir() -> PathBuf {
|
||||||
|
#[cfg(unix)]
|
||||||
|
return XDG_DIRS.data_dir();
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
return WINDOWS_DIRS.data_dir();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return Topgrade's keep file path.
|
||||||
|
///
|
||||||
|
/// keep file is a file under the data directory containing a major version
|
||||||
|
/// number, it will be created on first run and is used to check if an execution
|
||||||
|
/// of Topgrade is the first run of a major release, for more details, see
|
||||||
|
/// `first_run_of_major_release()`.
|
||||||
|
fn keep_file_path() -> PathBuf {
|
||||||
|
let keep_file = "topgrade_keep";
|
||||||
|
data_dir().join(keep_file)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If environment variable `TOPGRADE_SKIP_BRKC_NOTIFY` is set to `true`, then
|
||||||
|
/// we won't notify the user of the breaking changes.
|
||||||
|
pub(crate) fn should_skip() -> bool {
|
||||||
|
if let Ok(var) = var("TOPGRADE_SKIP_BRKC_NOTIFY") {
|
||||||
|
return var.as_str() == "true";
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
/// True if this is the first execution of a major release.
|
||||||
|
pub(crate) fn first_run_of_major_release() -> Result<bool> {
|
||||||
|
let version = VERSION_STR.parse::<Version>().expect("should be a valid version");
|
||||||
|
let keep_file = keep_file_path();
|
||||||
|
|
||||||
|
// disable this lint here as the current code has better readability
|
||||||
|
#[allow(clippy::collapsible_if)]
|
||||||
|
if version.is_new_major_release() {
|
||||||
|
if !keep_file.exists() || read_to_string(&keep_file)? != VERSION_STR {
|
||||||
|
return Ok(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Print breaking changes to the user.
|
||||||
|
pub(crate) fn print_breaking_changes() {
|
||||||
|
let header = format!("Topgrade {VERSION_STR} Breaking Changes");
|
||||||
|
print_separator(header);
|
||||||
|
let contents = if BREAKINGCHANGES.is_empty() {
|
||||||
|
"No Breaking changes"
|
||||||
|
} else {
|
||||||
|
BREAKINGCHANGES
|
||||||
|
};
|
||||||
|
println!("{contents}\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This function will be ONLY executed when the user has confirmed the breaking
|
||||||
|
/// changes, once confirmed, we write the keep file, which means the first run
|
||||||
|
/// of this major release is finished.
|
||||||
|
pub(crate) fn write_keep_file() -> Result<()> {
|
||||||
|
std::fs::create_dir_all(data_dir())?;
|
||||||
|
let keep_file = keep_file_path();
|
||||||
|
|
||||||
|
let mut file = OpenOptions::new()
|
||||||
|
.create(true)
|
||||||
|
.write(true)
|
||||||
|
.truncate(true)
|
||||||
|
.open(keep_file)?;
|
||||||
|
let _ = file.write(VERSION_STR.as_bytes())?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn is_new_major_release_works() {
|
||||||
|
let first_major_release: Version = "1.0.0".parse().unwrap();
|
||||||
|
let under_dev: Version = "0.1.0".parse().unwrap();
|
||||||
|
|
||||||
|
assert!(first_major_release.is_new_major_release());
|
||||||
|
assert!(!under_dev.is_new_major_release());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic(expected = "Version numbers can not be all 0s")]
|
||||||
|
fn invalid_version() {
|
||||||
|
let all_0 = "0.0.0";
|
||||||
|
all_0.parse::<Version>().unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,8 @@ use color_eyre::eyre::Context;
|
|||||||
|
|
||||||
use crate::error::TopgradeError;
|
use crate::error::TopgradeError;
|
||||||
|
|
||||||
|
use tracing::debug;
|
||||||
|
|
||||||
/// Like [`Output`], but UTF-8 decoded.
|
/// Like [`Output`], but UTF-8 decoded.
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct Utf8Output {
|
pub struct Utf8Output {
|
||||||
@@ -183,7 +185,7 @@ impl CommandExt for Command {
|
|||||||
let err = TopgradeError::ProcessFailedWithOutput(program, output.status, stderr.into_owned());
|
let err = TopgradeError::ProcessFailedWithOutput(program, output.status, stderr.into_owned());
|
||||||
|
|
||||||
let ret = Err(err).with_context(|| message);
|
let ret = Err(err).with_context(|| message);
|
||||||
tracing::debug!("Command failed: {ret:?}");
|
debug!("Command failed: {ret:?}");
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -203,7 +205,7 @@ impl CommandExt for Command {
|
|||||||
let (program, _) = get_program_and_args(self);
|
let (program, _) = get_program_and_args(self);
|
||||||
let err = TopgradeError::ProcessFailed(program, status);
|
let err = TopgradeError::ProcessFailed(program, status);
|
||||||
let ret = Err(err).with_context(|| format!("Command failed: `{command}`"));
|
let ret = Err(err).with_context(|| format!("Command failed: `{command}`"));
|
||||||
tracing::debug!("Command failed: {ret:?}");
|
debug!("Command failed: {ret:?}");
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -239,6 +241,6 @@ fn format_program_and_args(cmd: &Command) -> String {
|
|||||||
|
|
||||||
fn log(cmd: &Command) -> String {
|
fn log(cmd: &Command) -> String {
|
||||||
let command = format_program_and_args(cmd);
|
let command = format_program_and_args(cmd);
|
||||||
tracing::debug!("Executing command `{command}`");
|
debug!("Executing command `{command}`");
|
||||||
command
|
command
|
||||||
}
|
}
|
||||||
|
|||||||
251
src/config.rs
251
src/config.rs
@@ -7,7 +7,7 @@ use std::path::{Path, PathBuf};
|
|||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use std::{env, fs};
|
use std::{env, fs};
|
||||||
|
|
||||||
use clap::{ArgEnum, Parser};
|
use clap::{Parser, ValueEnum};
|
||||||
use clap_complete::Shell;
|
use clap_complete::Shell;
|
||||||
use color_eyre::eyre::Context;
|
use color_eyre::eyre::Context;
|
||||||
use color_eyre::eyre::Result;
|
use color_eyre::eyre::Result;
|
||||||
@@ -17,17 +17,19 @@ use regex::Regex;
|
|||||||
use regex_split::RegexSplit;
|
use regex_split::RegexSplit;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use strum::{EnumIter, EnumString, EnumVariantNames, IntoEnumIterator};
|
use strum::{EnumIter, EnumString, EnumVariantNames, IntoEnumIterator};
|
||||||
use tracing::debug;
|
|
||||||
use which_crate::which;
|
use which_crate::which;
|
||||||
|
|
||||||
|
use super::utils::editor;
|
||||||
use crate::command::CommandExt;
|
use crate::command::CommandExt;
|
||||||
use crate::sudo::SudoKind;
|
use crate::sudo::SudoKind;
|
||||||
use crate::utils::string_prepend_str;
|
use crate::utils::{hostname, string_prepend_str};
|
||||||
|
use tracing::{debug, error};
|
||||||
use super::utils::{editor, hostname};
|
|
||||||
|
|
||||||
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.
|
||||||
|
pub const DEFAULT_LOG_LEVEL: &str = "warn";
|
||||||
|
|
||||||
#[allow(unused_macros)]
|
#[allow(unused_macros)]
|
||||||
macro_rules! str_value {
|
macro_rules! str_value {
|
||||||
($section:ident, $value:ident) => {
|
($section:ident, $value:ident) => {
|
||||||
@@ -40,60 +42,9 @@ macro_rules! str_value {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! check_deprecated {
|
|
||||||
($config:expr, $old:ident, $section:ident, $new:ident) => {
|
|
||||||
if $config.$old.is_some() {
|
|
||||||
println!(concat!(
|
|
||||||
"'",
|
|
||||||
stringify!($old),
|
|
||||||
"' configuration option is deprecated. Rename it to '",
|
|
||||||
stringify!($new),
|
|
||||||
"' and put it under the section [",
|
|
||||||
stringify!($section),
|
|
||||||
"]",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a deprecated option moved from a section to another
|
|
||||||
macro_rules! get_deprecated_moved_opt {
|
|
||||||
($old_section:expr, $old:ident, $new_section:expr, $new:ident) => {{
|
|
||||||
if let Some(old_section) = &$old_section {
|
|
||||||
if old_section.$old.is_some() {
|
|
||||||
return &old_section.$old;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(new_section) = &$new_section {
|
|
||||||
return &new_section.$new;
|
|
||||||
}
|
|
||||||
|
|
||||||
return &None;
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! get_deprecated_moved_or_default_to {
|
|
||||||
($old_section:expr, $old:ident, $new_section:expr, $new:ident, $default_ret:ident) => {{
|
|
||||||
if let Some(old_section) = &$old_section {
|
|
||||||
if let Some(old) = old_section.$old {
|
|
||||||
return old;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(new_section) = &$new_section {
|
|
||||||
if let Some(new) = new_section.$new {
|
|
||||||
return new;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $default_ret;
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type Commands = BTreeMap<String, String>;
|
pub type Commands = BTreeMap<String, String>;
|
||||||
|
|
||||||
#[derive(ArgEnum, EnumString, EnumVariantNames, Debug, Clone, PartialEq, Eq, Deserialize, EnumIter, Copy)]
|
#[derive(ValueEnum, EnumString, EnumVariantNames, Debug, Clone, PartialEq, Eq, Deserialize, EnumIter, Copy)]
|
||||||
#[clap(rename_all = "snake_case")]
|
#[clap(rename_all = "snake_case")]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
#[strum(serialize_all = "snake_case")]
|
#[strum(serialize_all = "snake_case")]
|
||||||
@@ -102,11 +53,13 @@ pub enum Step {
|
|||||||
AppMan,
|
AppMan,
|
||||||
Asdf,
|
Asdf,
|
||||||
Atom,
|
Atom,
|
||||||
|
Audit,
|
||||||
Bin,
|
Bin,
|
||||||
Bob,
|
Bob,
|
||||||
BrewCask,
|
BrewCask,
|
||||||
BrewFormula,
|
BrewFormula,
|
||||||
Bun,
|
Bun,
|
||||||
|
BunPackages,
|
||||||
Cargo,
|
Cargo,
|
||||||
Chezmoi,
|
Chezmoi,
|
||||||
Chocolatey,
|
Chocolatey,
|
||||||
@@ -143,8 +96,10 @@ pub enum Step {
|
|||||||
Kakoune,
|
Kakoune,
|
||||||
Helix,
|
Helix,
|
||||||
Krew,
|
Krew,
|
||||||
|
Lure,
|
||||||
Macports,
|
Macports,
|
||||||
Mamba,
|
Mamba,
|
||||||
|
Miktex,
|
||||||
Mas,
|
Mas,
|
||||||
Maza,
|
Maza,
|
||||||
Micro,
|
Micro,
|
||||||
@@ -174,6 +129,7 @@ pub enum Step {
|
|||||||
Rustup,
|
Rustup,
|
||||||
Scoop,
|
Scoop,
|
||||||
Sdkman,
|
Sdkman,
|
||||||
|
SelfUpdate,
|
||||||
Sheldon,
|
Sheldon,
|
||||||
Shell,
|
Shell,
|
||||||
Snap,
|
Snap,
|
||||||
@@ -189,6 +145,7 @@ pub enum Step {
|
|||||||
Vagrant,
|
Vagrant,
|
||||||
Vcpkg,
|
Vcpkg,
|
||||||
Vim,
|
Vim,
|
||||||
|
Vscode,
|
||||||
Winget,
|
Winget,
|
||||||
Wsl,
|
Wsl,
|
||||||
WslUpdate,
|
WslUpdate,
|
||||||
@@ -203,6 +160,13 @@ pub struct Include {
|
|||||||
paths: Option<Vec<String>>,
|
paths: Option<Vec<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Default, Debug, Merge)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
pub struct Containers {
|
||||||
|
#[merge(strategy = crate::utils::merge_strategies::vec_prepend_opt)]
|
||||||
|
ignored_containers: Option<Vec<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Default, Debug, Merge)]
|
#[derive(Deserialize, Default, Debug, Merge)]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct Git {
|
pub struct Git {
|
||||||
@@ -338,6 +302,9 @@ pub struct Linux {
|
|||||||
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
|
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
|
||||||
nix_arguments: Option<String>,
|
nix_arguments: Option<String>,
|
||||||
|
|
||||||
|
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
|
||||||
|
nix_env_arguments: Option<String>,
|
||||||
|
|
||||||
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
|
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
|
||||||
apt_arguments: Option<String>,
|
apt_arguments: Option<String>,
|
||||||
|
|
||||||
@@ -375,11 +342,6 @@ pub struct Misc {
|
|||||||
|
|
||||||
sudo_command: Option<SudoKind>,
|
sudo_command: Option<SudoKind>,
|
||||||
|
|
||||||
#[merge(strategy = crate::utils::merge_strategies::vec_prepend_opt)]
|
|
||||||
git_repos: Option<Vec<String>>,
|
|
||||||
|
|
||||||
predefined_git_repos: Option<bool>,
|
|
||||||
|
|
||||||
#[merge(strategy = crate::utils::merge_strategies::vec_prepend_opt)]
|
#[merge(strategy = crate::utils::merge_strategies::vec_prepend_opt)]
|
||||||
disable: Option<Vec<Step>>,
|
disable: Option<Vec<Step>>,
|
||||||
|
|
||||||
@@ -394,9 +356,6 @@ pub struct Misc {
|
|||||||
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
|
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
|
||||||
ssh_arguments: Option<String>,
|
ssh_arguments: Option<String>,
|
||||||
|
|
||||||
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
|
|
||||||
git_arguments: Option<String>,
|
|
||||||
|
|
||||||
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
|
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
|
||||||
tmux_arguments: Option<String>,
|
tmux_arguments: Option<String>,
|
||||||
|
|
||||||
@@ -406,15 +365,6 @@ pub struct Misc {
|
|||||||
|
|
||||||
assume_yes: Option<bool>,
|
assume_yes: Option<bool>,
|
||||||
|
|
||||||
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
|
|
||||||
yay_arguments: Option<String>,
|
|
||||||
|
|
||||||
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
|
|
||||||
aura_aur_arguments: Option<String>,
|
|
||||||
|
|
||||||
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
|
|
||||||
aura_pacman_arguments: Option<String>,
|
|
||||||
|
|
||||||
no_retry: Option<bool>,
|
no_retry: Option<bool>,
|
||||||
|
|
||||||
run_in_tmux: Option<bool>,
|
run_in_tmux: Option<bool>,
|
||||||
@@ -423,8 +373,6 @@ pub struct Misc {
|
|||||||
|
|
||||||
notify_each_step: Option<bool>,
|
notify_each_step: Option<bool>,
|
||||||
|
|
||||||
accept_all_windows_updates: Option<bool>,
|
|
||||||
|
|
||||||
skip_notify: Option<bool>,
|
skip_notify: Option<bool>,
|
||||||
|
|
||||||
bashit_branch: Option<String>,
|
bashit_branch: Option<String>,
|
||||||
@@ -433,6 +381,8 @@ pub struct Misc {
|
|||||||
only: Option<Vec<Step>>,
|
only: Option<Vec<Step>>,
|
||||||
|
|
||||||
no_self_update: Option<bool>,
|
no_self_update: Option<bool>,
|
||||||
|
|
||||||
|
log_filters: Option<Vec<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Default, Debug, Merge)]
|
#[derive(Deserialize, Default, Debug, Merge)]
|
||||||
@@ -469,6 +419,9 @@ pub struct ConfigFile {
|
|||||||
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
||||||
git: Option<Git>,
|
git: Option<Git>,
|
||||||
|
|
||||||
|
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
||||||
|
containers: Option<Containers>,
|
||||||
|
|
||||||
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
||||||
windows: Option<Windows>,
|
windows: Option<Windows>,
|
||||||
|
|
||||||
@@ -592,11 +545,11 @@ impl ConfigFile {
|
|||||||
*/
|
*/
|
||||||
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).map_err(|e| {
|
||||||
tracing::error!("Unable to read {}", include.display());
|
error!("Unable to read {}", include.display());
|
||||||
e
|
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()).map_err(|e| {
|
||||||
tracing::error!("Failed to deserialize {}", include.display());
|
error!("Failed to deserialize {}", include.display());
|
||||||
e
|
e
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
@@ -613,7 +566,7 @@ impl ConfigFile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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).map_err(|e| {
|
||||||
tracing::error!("Unable to read {}", config_path.display());
|
error!("Unable to read {}", config_path.display());
|
||||||
e
|
e
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
@@ -626,7 +579,7 @@ impl ConfigFile {
|
|||||||
|
|
||||||
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).map_err(|e| {
|
||||||
tracing::error!("Failed to deserialize an include section of {}", config_path.display());
|
error!("Failed to deserialize an include section of {}", config_path.display());
|
||||||
e
|
e
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
@@ -639,38 +592,24 @@ 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) => {
|
||||||
tracing::error!("Unable to read {}: {}", include_path.display(), e);
|
error!("Unable to read {}: {}", include_path.display(), e);
|
||||||
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) => {
|
||||||
tracing::error!("Failed to deserialize {}: {}", include_path.display(), e);
|
error!("Failed to deserialize {}: {}", include_path.display(), e);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
debug!("Configuration include found: {}", include_path.display());
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
debug!("No include paths found in {}", config_path.display());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match toml::from_str::<Self>(contents) {
|
match toml::from_str::<Self>(contents) {
|
||||||
Ok(contents) => result.merge(contents),
|
Ok(contents) => result.merge(contents),
|
||||||
Err(e) => tracing::error!("Failed to deserialize {}: {}", config_path.display(), e),
|
Err(e) => error!("Failed to deserialize {}: {}", config_path.display(), e),
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(misc) = &mut result.misc {
|
|
||||||
if let Some(ref mut paths) = &mut misc.git_repos {
|
|
||||||
for path in paths.iter_mut() {
|
|
||||||
let expanded = shellexpand::tilde::<&str>(&path.as_ref()).into_owned();
|
|
||||||
debug!("Path {} expanded to {}", path, expanded);
|
|
||||||
*path = expanded;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -683,7 +622,6 @@ impl ConfigFile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
debug!("Loaded configuration: {:?}", result);
|
debug!("Loaded configuration: {:?}", result);
|
||||||
|
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -744,19 +682,19 @@ pub struct CommandLineArgs {
|
|||||||
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", arg_enum, multiple_values = true)]
|
#[clap(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 (experimental)
|
||||||
#[clap(long = "only", value_name = "STEP", arg_enum, multiple_values = true)]
|
#[clap(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", multiple_values = true)]
|
#[clap(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", multiple_values = true)]
|
#[clap(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`.
|
||||||
@@ -776,9 +714,8 @@ pub struct CommandLineArgs {
|
|||||||
short = 'y',
|
short = 'y',
|
||||||
long = "yes",
|
long = "yes",
|
||||||
value_name = "STEP",
|
value_name = "STEP",
|
||||||
arg_enum,
|
value_enum,
|
||||||
multiple_values = true,
|
num_args = 0..,
|
||||||
min_values = 0
|
|
||||||
)]
|
)]
|
||||||
yes: Option<Vec<Step>>,
|
yes: Option<Vec<Step>>,
|
||||||
|
|
||||||
@@ -801,11 +738,11 @@ pub struct CommandLineArgs {
|
|||||||
/// 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 = "warn")]
|
#[clap(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, arg_enum, hide = true)]
|
#[clap(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
|
||||||
@@ -830,12 +767,25 @@ impl CommandLineArgs {
|
|||||||
&self.env
|
&self.env
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// In Topgrade, filter directives come from 3 places:
|
||||||
|
/// 1. CLI option `--log-filter`
|
||||||
|
/// 2. Config file
|
||||||
|
/// 3. `debug` if the `--verbose` option is present
|
||||||
|
///
|
||||||
|
/// Before loading the configuration file, we need our logger to work, so this
|
||||||
|
/// function will return directives coming from part 1 and 2.
|
||||||
|
///
|
||||||
|
///
|
||||||
|
/// When the configuration file is loaded, `Config::tracing_filter_directives()`
|
||||||
|
/// will return all the 3 parts.
|
||||||
pub fn tracing_filter_directives(&self) -> String {
|
pub fn tracing_filter_directives(&self) -> String {
|
||||||
|
let mut ret = self.log_filter.clone();
|
||||||
if self.verbose {
|
if self.verbose {
|
||||||
"debug".into()
|
ret.push(',');
|
||||||
} else {
|
ret.push_str("debug");
|
||||||
self.log_filter.clone()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ret
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -844,6 +794,7 @@ impl CommandLineArgs {
|
|||||||
/// The struct holds the loaded configuration file, as well as the arguments parsed from the command line.
|
/// The struct holds the loaded configuration file, as well as the arguments parsed from the command line.
|
||||||
/// Its provided methods decide the appropriate options based on combining the configuration file and the
|
/// Its provided methods decide the appropriate options based on combining the configuration file and the
|
||||||
/// command line arguments.
|
/// command line arguments.
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
opt: CommandLineArgs,
|
opt: CommandLineArgs,
|
||||||
config_file: ConfigFile,
|
config_file: ConfigFile,
|
||||||
@@ -860,7 +811,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
|
||||||
tracing::error!("failed to load configuration: {}", e);
|
error!("failed to load configuration: {}", e);
|
||||||
ConfigFile::default()
|
ConfigFile::default()
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@@ -868,14 +819,6 @@ impl Config {
|
|||||||
ConfigFile::default()
|
ConfigFile::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(misc) = &config_file.misc {
|
|
||||||
check_deprecated!(misc, git_arguments, git, arguments);
|
|
||||||
check_deprecated!(misc, git_repos, git, repos);
|
|
||||||
check_deprecated!(misc, predefined_git_repos, git, pull_predefined);
|
|
||||||
check_deprecated!(misc, yay_arguments, linux, yay_arguments);
|
|
||||||
check_deprecated!(misc, accept_all_windows_updates, windows, accept_all_updates);
|
|
||||||
}
|
|
||||||
|
|
||||||
let allowed_steps = Self::allowed_steps(&opt, &config_file);
|
let allowed_steps = Self::allowed_steps(&opt, &config_file);
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
@@ -906,8 +849,16 @@ impl Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The list of additional git repositories to pull.
|
/// The list of additional git repositories to pull.
|
||||||
pub fn git_repos(&self) -> &Option<Vec<String>> {
|
pub fn git_repos(&self) -> Option<&Vec<String>> {
|
||||||
get_deprecated_moved_opt!(&self.config_file.misc, git_repos, &self.config_file.git, repos)
|
self.config_file.git.as_ref().and_then(|git| git.repos.as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The list of docker/podman containers to ignore.
|
||||||
|
pub fn containers_ignored_tags(&self) -> Option<&Vec<String>> {
|
||||||
|
self.config_file
|
||||||
|
.containers
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|containers| containers.ignored_containers.as_ref())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tell whether the specified step should run.
|
/// Tell whether the specified step should run.
|
||||||
@@ -1019,8 +970,8 @@ impl Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Extra Git arguments
|
/// Extra Git arguments
|
||||||
pub fn git_arguments(&self) -> &Option<String> {
|
pub fn git_arguments(&self) -> Option<&String> {
|
||||||
get_deprecated_moved_opt!(&self.config_file.misc, git_arguments, &self.config_file.git, arguments)
|
self.config_file.git.as_ref().and_then(|git| git.arguments.as_ref())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extra Tmux arguments
|
/// Extra Tmux arguments
|
||||||
@@ -1093,13 +1044,11 @@ impl Config {
|
|||||||
|
|
||||||
/// Whether to accept all Windows updates
|
/// Whether to accept all Windows updates
|
||||||
pub fn accept_all_windows_updates(&self) -> bool {
|
pub fn accept_all_windows_updates(&self) -> bool {
|
||||||
get_deprecated_moved_or_default_to!(
|
self.config_file
|
||||||
&self.config_file.misc,
|
.windows
|
||||||
accept_all_windows_updates,
|
.as_ref()
|
||||||
&self.config_file.windows,
|
.and_then(|windows| windows.accept_all_updates)
|
||||||
accept_all_updates,
|
.unwrap_or(true)
|
||||||
true
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether to self rename the Topgrade executable during the run
|
/// Whether to self rename the Topgrade executable during the run
|
||||||
@@ -1278,6 +1227,14 @@ impl Config {
|
|||||||
.and_then(|linux| linux.nix_arguments.as_deref())
|
.and_then(|linux| linux.nix_arguments.as_deref())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Extra nix-env arguments
|
||||||
|
pub fn nix_env_arguments(&self) -> Option<&str> {
|
||||||
|
self.config_file
|
||||||
|
.linux
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|linux| linux.nix_env_arguments.as_deref())
|
||||||
|
}
|
||||||
|
|
||||||
/// Extra Home Manager arguments
|
/// Extra Home Manager arguments
|
||||||
pub fn home_manager(&self) -> Option<&Vec<String>> {
|
pub fn home_manager(&self) -> Option<&Vec<String>> {
|
||||||
self.config_file
|
self.config_file
|
||||||
@@ -1374,19 +1331,39 @@ impl Config {
|
|||||||
|
|
||||||
pub fn use_predefined_git_repos(&self) -> bool {
|
pub fn use_predefined_git_repos(&self) -> bool {
|
||||||
!self.opt.disable_predefined_git_repos
|
!self.opt.disable_predefined_git_repos
|
||||||
&& get_deprecated_moved_or_default_to!(
|
&& self
|
||||||
&self.config_file.misc,
|
.config_file
|
||||||
predefined_git_repos,
|
.git
|
||||||
&self.config_file.git,
|
.as_ref()
|
||||||
pull_predefined,
|
.and_then(|git| git.pull_predefined)
|
||||||
true
|
.unwrap_or(true)
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn verbose(&self) -> bool {
|
pub fn verbose(&self) -> bool {
|
||||||
self.opt.verbose
|
self.opt.verbose
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// After loading the config file, filter directives consist of 3 parts:
|
||||||
|
///
|
||||||
|
/// 1. directives from the configuration file
|
||||||
|
/// 2. directives from the CLI options `--log-filter`
|
||||||
|
/// 3. `debug`, which would be enabled if the `--verbose` option is present
|
||||||
|
///
|
||||||
|
/// Previous directive will be overwritten if a directive with the same target
|
||||||
|
/// appear later.
|
||||||
|
pub fn tracing_filter_directives(&self) -> String {
|
||||||
|
let mut ret = String::new();
|
||||||
|
if let Some(directives) = self.config_file.misc.as_ref().and_then(|m| m.log_filters.as_ref()) {
|
||||||
|
ret.push_str(&directives.join(","));
|
||||||
|
}
|
||||||
|
ret.push(',');
|
||||||
|
ret.push_str(&self.opt.log_filter);
|
||||||
|
if self.verbose() {
|
||||||
|
ret.push_str(",debug");
|
||||||
|
}
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
pub fn show_skipped(&self) -> bool {
|
pub fn show_skipped(&self) -> bool {
|
||||||
self.opt.show_skipped
|
self.opt.show_skipped
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
//! SIGINT handling in Unix systems.
|
//! SIGINT handling in Unix systems.
|
||||||
use crate::ctrlc::interrupted::set_interrupted;
|
use crate::ctrlc::interrupted::set_interrupted;
|
||||||
use nix::sys::signal;
|
use nix::sys::signal::{sigaction, SaFlags, SigAction, SigHandler, SigSet, Signal};
|
||||||
|
|
||||||
/// Handle SIGINT. Set the interruption flag.
|
/// Handle SIGINT. Set the interruption flag.
|
||||||
extern "C" fn handle_sigint(_: i32) {
|
extern "C" fn handle_sigint(_: i32) {
|
||||||
@@ -10,12 +10,8 @@ extern "C" fn handle_sigint(_: i32) {
|
|||||||
/// Set the necessary signal handlers.
|
/// Set the necessary signal handlers.
|
||||||
/// The function panics on failure.
|
/// The function panics on failure.
|
||||||
pub fn set_handler() {
|
pub fn set_handler() {
|
||||||
let sig_action = signal::SigAction::new(
|
let sig_action = SigAction::new(SigHandler::Handler(handle_sigint), SaFlags::empty(), SigSet::empty());
|
||||||
signal::SigHandler::Handler(handle_sigint),
|
|
||||||
signal::SaFlags::empty(),
|
|
||||||
signal::SigSet::empty(),
|
|
||||||
);
|
|
||||||
unsafe {
|
unsafe {
|
||||||
signal::sigaction(signal::SIGINT, &sig_action).unwrap();
|
sigaction(Signal::SIGINT, &sig_action).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
//! A stub for Ctrl + C handling.
|
//! A stub for Ctrl + C handling.
|
||||||
use crate::ctrlc::interrupted::set_interrupted;
|
use crate::ctrlc::interrupted::set_interrupted;
|
||||||
|
use tracing::error;
|
||||||
use winapi::shared::minwindef::{BOOL, DWORD, FALSE, TRUE};
|
use winapi::shared::minwindef::{BOOL, DWORD, FALSE, TRUE};
|
||||||
use winapi::um::consoleapi::SetConsoleCtrlHandler;
|
use winapi::um::consoleapi::SetConsoleCtrlHandler;
|
||||||
use winapi::um::wincon::CTRL_C_EVENT;
|
use winapi::um::wincon::CTRL_C_EVENT;
|
||||||
@@ -16,6 +17,6 @@ extern "system" fn handler(ctrl_type: DWORD) -> BOOL {
|
|||||||
|
|
||||||
pub fn set_handler() {
|
pub fn set_handler() {
|
||||||
if 0 == unsafe { SetConsoleCtrlHandler(Some(handler), TRUE) } {
|
if 0 == unsafe { SetConsoleCtrlHandler(Some(handler), TRUE) } {
|
||||||
tracing::error!("Cannot set a control C handler")
|
error!("Cannot set a control C handler")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
115
src/main.rs
115
src/main.rs
@@ -6,14 +6,17 @@ use std::path::PathBuf;
|
|||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use crate::breaking_changes::{first_run_of_major_release, print_breaking_changes, should_skip, write_keep_file};
|
||||||
use clap::CommandFactory;
|
use clap::CommandFactory;
|
||||||
use clap::{crate_version, Parser};
|
use clap::{crate_version, Parser};
|
||||||
use color_eyre::eyre::Context;
|
use color_eyre::eyre::Context;
|
||||||
use color_eyre::eyre::Result;
|
use color_eyre::eyre::Result;
|
||||||
use console::Key;
|
use console::Key;
|
||||||
|
use etcetera::base_strategy::BaseStrategy;
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use etcetera::base_strategy::Windows;
|
use etcetera::base_strategy::Windows;
|
||||||
use etcetera::base_strategy::{BaseStrategy, Xdg};
|
#[cfg(unix)]
|
||||||
|
use etcetera::base_strategy::Xdg;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
@@ -24,6 +27,9 @@ use self::error::Upgraded;
|
|||||||
use self::steps::{remote::*, *};
|
use self::steps::{remote::*, *};
|
||||||
use self::terminal::*;
|
use self::terminal::*;
|
||||||
|
|
||||||
|
use self::utils::{install_color_eyre, install_tracing, update_tracing};
|
||||||
|
|
||||||
|
mod breaking_changes;
|
||||||
mod command;
|
mod command;
|
||||||
mod config;
|
mod config;
|
||||||
mod ctrlc;
|
mod ctrlc;
|
||||||
@@ -41,16 +47,29 @@ mod sudo;
|
|||||||
mod terminal;
|
mod terminal;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
pub static HOME_DIR: Lazy<PathBuf> = Lazy::new(|| home::home_dir().expect("No home directory"));
|
pub(crate) static HOME_DIR: Lazy<PathBuf> = Lazy::new(|| home::home_dir().expect("No home directory"));
|
||||||
pub static XDG_DIRS: Lazy<Xdg> = Lazy::new(|| Xdg::new().expect("No home directory"));
|
#[cfg(unix)]
|
||||||
|
pub(crate) static XDG_DIRS: Lazy<Xdg> = Lazy::new(|| Xdg::new().expect("No home directory"));
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
pub 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"));
|
||||||
|
|
||||||
fn run() -> Result<()> {
|
fn run() -> Result<()> {
|
||||||
color_eyre::install()?;
|
install_color_eyre()?;
|
||||||
ctrlc::set_handler();
|
ctrlc::set_handler();
|
||||||
|
|
||||||
let opt = CommandLineArgs::parse();
|
let opt = CommandLineArgs::parse();
|
||||||
|
// Set up the logger with the filter directives from:
|
||||||
|
// 1. CLI option `--log-filter`
|
||||||
|
// 2. `debug` if the `--verbose` option is present
|
||||||
|
// We do this because we need our logger to work while loading the
|
||||||
|
// configuration file.
|
||||||
|
//
|
||||||
|
// When the configuration file is loaded, update the logger with the full
|
||||||
|
// filter directives.
|
||||||
|
//
|
||||||
|
// For more info, see the comments in `CommandLineArgs::tracing_filter_directives()`
|
||||||
|
// and `Config::tracing_filter_directives()`.
|
||||||
|
let reload_handle = install_tracing(&opt.tracing_filter_directives())?;
|
||||||
|
|
||||||
if let Some(shell) = opt.gen_completion {
|
if let Some(shell) = opt.gen_completion {
|
||||||
let cmd = &mut CommandLineArgs::command();
|
let cmd = &mut CommandLineArgs::command();
|
||||||
@@ -64,8 +83,6 @@ fn run() -> Result<()> {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
install_tracing(&opt.tracing_filter_directives())?;
|
|
||||||
|
|
||||||
for env in opt.env_variables() {
|
for env in opt.env_variables() {
|
||||||
let mut splitted = env.split('=');
|
let mut splitted = env.split('=');
|
||||||
let var = splitted.next().unwrap();
|
let var = splitted.next().unwrap();
|
||||||
@@ -84,6 +101,8 @@ fn run() -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let config = Config::load(opt)?;
|
let config = Config::load(opt)?;
|
||||||
|
// Update the logger with the full filter directives.
|
||||||
|
update_tracing(&reload_handle, &config.tracing_filter_directives())?;
|
||||||
set_title(config.set_title());
|
set_title(config.set_title());
|
||||||
display_time(config.display_time());
|
display_time(config.display_time());
|
||||||
set_desktop_notifications(config.notify_each_step());
|
set_desktop_notifications(config.notify_each_step());
|
||||||
@@ -92,7 +111,8 @@ fn run() -> Result<()> {
|
|||||||
debug!("OS: {}", env!("TARGET"));
|
debug!("OS: {}", env!("TARGET"));
|
||||||
debug!("{:?}", std::env::args());
|
debug!("{:?}", std::env::args());
|
||||||
debug!("Binary path: {:?}", std::env::current_exe());
|
debug!("Binary path: {:?}", std::env::current_exe());
|
||||||
debug!("Self Update: {:?}", cfg!(feature = "self-update"));
|
debug!("self-update Feature Enabled: {:?}", cfg!(feature = "self-update"));
|
||||||
|
debug!("Configuration: {:?}", config);
|
||||||
|
|
||||||
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)]
|
||||||
@@ -115,22 +135,31 @@ fn run() -> Result<()> {
|
|||||||
let ctx = execution_context::ExecutionContext::new(run_type, sudo, &git, &config);
|
let ctx = execution_context::ExecutionContext::new(run_type, sudo, &git, &config);
|
||||||
let mut runner = runner::Runner::new(&ctx);
|
let mut runner = runner::Runner::new(&ctx);
|
||||||
|
|
||||||
|
// If
|
||||||
|
//
|
||||||
|
// 1. the breaking changes notification shouldnot be skipped
|
||||||
|
// 2. this is the first execution of a major release
|
||||||
|
//
|
||||||
|
// inform user of breaking changes
|
||||||
|
if !should_skip() && first_run_of_major_release()? {
|
||||||
|
print_breaking_changes();
|
||||||
|
|
||||||
|
if prompt_yesno("Confirmed?")? {
|
||||||
|
write_keep_file()?;
|
||||||
|
} else {
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Self-Update step, this will execute only if:
|
||||||
|
// 1. the `self-update` feature is enabled
|
||||||
|
// 2. it is not disabled from configuration (env var/CLI opt/file)
|
||||||
#[cfg(feature = "self-update")]
|
#[cfg(feature = "self-update")]
|
||||||
{
|
{
|
||||||
let config_self_upgrade = env::var("TOPGRADE_NO_SELF_UPGRADE").is_err() && !config.no_self_update();
|
let should_self_update = env::var("TOPGRADE_NO_SELF_UPGRADE").is_err() && !config.no_self_update();
|
||||||
|
|
||||||
if !run_type.dry() && config_self_upgrade {
|
if should_self_update {
|
||||||
let result = self_update::self_update();
|
runner.execute(Step::SelfUpdate, "Self Update", || self_update::self_update(&ctx))?;
|
||||||
|
|
||||||
if let Err(e) = &result {
|
|
||||||
#[cfg(windows)]
|
|
||||||
{
|
|
||||||
if e.downcast_ref::<Upgraded>().is_some() {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
print_warning(format!("Self update error: {e}"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,15 +216,10 @@ fn run() -> Result<()> {
|
|||||||
}
|
}
|
||||||
runner.execute(Step::ConfigUpdate, "config-update", || linux::run_config_update(&ctx))?;
|
runner.execute(Step::ConfigUpdate, "config-update", || linux::run_config_update(&ctx))?;
|
||||||
|
|
||||||
runner.execute(Step::BrewFormula, "Brew", || {
|
|
||||||
unix::run_brew_formula(&ctx, unix::BrewVariant::Path)
|
|
||||||
})?;
|
|
||||||
|
|
||||||
runner.execute(Step::AM, "am", || linux::run_am(&ctx))?;
|
runner.execute(Step::AM, "am", || linux::run_am(&ctx))?;
|
||||||
runner.execute(Step::AppMan, "appman", || linux::run_appman(&ctx))?;
|
runner.execute(Step::AppMan, "appman", || linux::run_appman(&ctx))?;
|
||||||
runner.execute(Step::DebGet, "deb-get", || linux::run_deb_get(&ctx))?;
|
runner.execute(Step::DebGet, "deb-get", || linux::run_deb_get(&ctx))?;
|
||||||
runner.execute(Step::Toolbx, "toolbx", || toolbx::run_toolbx(&ctx))?;
|
runner.execute(Step::Toolbx, "toolbx", || toolbx::run_toolbx(&ctx))?;
|
||||||
runner.execute(Step::Flatpak, "Flatpak", || linux::run_flatpak(&ctx))?;
|
|
||||||
runner.execute(Step::Snap, "snap", || linux::run_snap(&ctx))?;
|
runner.execute(Step::Snap, "snap", || linux::run_snap(&ctx))?;
|
||||||
runner.execute(Step::Pacstall, "pacstall", || linux::run_pacstall(&ctx))?;
|
runner.execute(Step::Pacstall, "pacstall", || linux::run_pacstall(&ctx))?;
|
||||||
runner.execute(Step::Pacdef, "pacdef", || linux::run_pacdef(&ctx))?;
|
runner.execute(Step::Pacdef, "pacdef", || linux::run_pacdef(&ctx))?;
|
||||||
@@ -205,6 +229,12 @@ fn run() -> Result<()> {
|
|||||||
runner.execute(Step::System, "pihole", || linux::run_pihole_update(&ctx))?;
|
runner.execute(Step::System, "pihole", || linux::run_pihole_update(&ctx))?;
|
||||||
runner.execute(Step::Firmware, "Firmware upgrades", || linux::run_fwupdmgr(&ctx))?;
|
runner.execute(Step::Firmware, "Firmware upgrades", || linux::run_fwupdmgr(&ctx))?;
|
||||||
runner.execute(Step::Restarts, "Restarts", || linux::run_needrestart(&ctx))?;
|
runner.execute(Step::Restarts, "Restarts", || linux::run_needrestart(&ctx))?;
|
||||||
|
|
||||||
|
runner.execute(Step::Flatpak, "Flatpak", || linux::run_flatpak(&ctx))?;
|
||||||
|
runner.execute(Step::BrewFormula, "Brew", || {
|
||||||
|
unix::run_brew_formula(&ctx, unix::BrewVariant::Path)
|
||||||
|
})?;
|
||||||
|
runner.execute(Step::Lure, "LURE", || linux::run_lure_update(&ctx))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
@@ -238,14 +268,14 @@ fn run() -> Result<()> {
|
|||||||
runner.execute(Step::Pkg, "DragonFly BSD Packages", || {
|
runner.execute(Step::Pkg, "DragonFly BSD Packages", || {
|
||||||
dragonfly::upgrade_packages(&ctx)
|
dragonfly::upgrade_packages(&ctx)
|
||||||
})?;
|
})?;
|
||||||
dragonfly::audit_packages(&ctx)?;
|
runner.execute(Step::Audit, "DragonFly Audit", || dragonfly::audit_packages(&ctx))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "freebsd")]
|
#[cfg(target_os = "freebsd")]
|
||||||
{
|
{
|
||||||
runner.execute(Step::Pkg, "FreeBSD Packages", || freebsd::upgrade_packages(&ctx))?;
|
runner.execute(Step::Pkg, "FreeBSD Packages", || freebsd::upgrade_packages(&ctx))?;
|
||||||
runner.execute(Step::System, "FreeBSD Upgrade", || freebsd::upgrade_freebsd(&ctx))?;
|
runner.execute(Step::System, "FreeBSD Upgrade", || freebsd::upgrade_freebsd(&ctx))?;
|
||||||
freebsd::audit_packages(&ctx)?;
|
runner.execute(Step::Audit, "FreeBSD Audit", || freebsd::audit_packages(&ctx))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "openbsd")]
|
#[cfg(target_os = "openbsd")]
|
||||||
@@ -263,11 +293,13 @@ fn run() -> Result<()> {
|
|||||||
{
|
{
|
||||||
runner.execute(Step::Yadm, "yadm", || unix::run_yadm(&ctx))?;
|
runner.execute(Step::Yadm, "yadm", || unix::run_yadm(&ctx))?;
|
||||||
runner.execute(Step::Nix, "nix", || unix::run_nix(&ctx))?;
|
runner.execute(Step::Nix, "nix", || unix::run_nix(&ctx))?;
|
||||||
|
runner.execute(Step::Nix, "nix upgrade-nix", || unix::run_nix_self_upgrade(&ctx))?;
|
||||||
runner.execute(Step::Guix, "guix", || unix::run_guix(&ctx))?;
|
runner.execute(Step::Guix, "guix", || unix::run_guix(&ctx))?;
|
||||||
runner.execute(Step::HomeManager, "home-manager", || unix::run_home_manager(&ctx))?;
|
runner.execute(Step::HomeManager, "home-manager", || unix::run_home_manager(&ctx))?;
|
||||||
runner.execute(Step::Asdf, "asdf", || unix::run_asdf(&ctx))?;
|
runner.execute(Step::Asdf, "asdf", || unix::run_asdf(&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::Bun, "bun", || unix::run_bun(&ctx))?;
|
||||||
|
runner.execute(Step::BunPackages, "bun-packages", || unix::run_bun_packages(&ctx))?;
|
||||||
runner.execute(Step::Shell, "zr", || zsh::run_zr(&ctx))?;
|
runner.execute(Step::Shell, "zr", || zsh::run_zr(&ctx))?;
|
||||||
runner.execute(Step::Shell, "antibody", || zsh::run_antibody(&ctx))?;
|
runner.execute(Step::Shell, "antibody", || zsh::run_antibody(&ctx))?;
|
||||||
runner.execute(Step::Shell, "antidote", || zsh::run_antidote(&ctx))?;
|
runner.execute(Step::Shell, "antidote", || zsh::run_antidote(&ctx))?;
|
||||||
@@ -320,8 +352,12 @@ fn run() -> Result<()> {
|
|||||||
runner.execute(Step::Opam, "opam", || generic::run_opam_update(&ctx))?;
|
runner.execute(Step::Opam, "opam", || generic::run_opam_update(&ctx))?;
|
||||||
runner.execute(Step::Vcpkg, "vcpkg", || generic::run_vcpkg_update(&ctx))?;
|
runner.execute(Step::Vcpkg, "vcpkg", || generic::run_vcpkg_update(&ctx))?;
|
||||||
runner.execute(Step::Pipx, "pipx", || generic::run_pipx_update(&ctx))?;
|
runner.execute(Step::Pipx, "pipx", || generic::run_pipx_update(&ctx))?;
|
||||||
|
runner.execute(Step::Vscode, "Visual Studio Code extensions", || {
|
||||||
|
generic::run_vscode_extensions_update(&ctx)
|
||||||
|
})?;
|
||||||
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::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))?;
|
||||||
runner.execute(Step::PipReviewLocal, "pip-review (local)", || {
|
runner.execute(Step::PipReviewLocal, "pip-review (local)", || {
|
||||||
@@ -544,26 +580,3 @@ fn main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn install_tracing(filter_directives: &str) -> Result<()> {
|
|
||||||
use tracing_subscriber::fmt;
|
|
||||||
use tracing_subscriber::fmt::format::FmtSpan;
|
|
||||||
use tracing_subscriber::layer::SubscriberExt;
|
|
||||||
use tracing_subscriber::util::SubscriberInitExt;
|
|
||||||
use tracing_subscriber::EnvFilter;
|
|
||||||
|
|
||||||
let env_filter = EnvFilter::try_new(filter_directives)
|
|
||||||
.or_else(|_| EnvFilter::try_from_default_env())
|
|
||||||
.or_else(|_| EnvFilter::try_new("info"))?;
|
|
||||||
|
|
||||||
let fmt_layer = fmt::layer()
|
|
||||||
.with_target(false)
|
|
||||||
.with_span_events(FmtSpan::NEW | FmtSpan::CLOSE)
|
|
||||||
.without_time();
|
|
||||||
|
|
||||||
let registry = tracing_subscriber::registry();
|
|
||||||
|
|
||||||
registry.with(env_filter).with(fmt_layer).init();
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use std::env;
|
|||||||
use std::os::unix::process::CommandExt as _;
|
use std::os::unix::process::CommandExt as _;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
|
use crate::config::Step;
|
||||||
use color_eyre::eyre::{bail, Result};
|
use color_eyre::eyre::{bail, Result};
|
||||||
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;
|
||||||
@@ -11,52 +12,61 @@ use super::terminal::*;
|
|||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
use crate::error::Upgraded;
|
use crate::error::Upgraded;
|
||||||
|
|
||||||
pub fn self_update() -> Result<()> {
|
use crate::execution_context::ExecutionContext;
|
||||||
|
|
||||||
|
pub fn self_update(ctx: &ExecutionContext) -> Result<()> {
|
||||||
print_separator("Self update");
|
print_separator("Self update");
|
||||||
let current_exe = env::current_exe();
|
|
||||||
|
|
||||||
let target = self_update_crate::get_target();
|
if ctx.run_type().dry() {
|
||||||
let result = Update::configure()
|
println!("Would self-update");
|
||||||
.repo_owner("topgrade-rs")
|
Ok(())
|
||||||
.repo_name("topgrade")
|
|
||||||
.target(target)
|
|
||||||
.bin_name(if cfg!(windows) { "topgrade.exe" } else { "topgrade" })
|
|
||||||
.show_output(false)
|
|
||||||
.show_download_progress(true)
|
|
||||||
.current_version(self_update_crate::cargo_crate_version!())
|
|
||||||
.no_confirm(true)
|
|
||||||
.build()?
|
|
||||||
.update_extended()?;
|
|
||||||
|
|
||||||
if let UpdateStatus::Updated(release) = &result {
|
|
||||||
println!("\nTopgrade upgraded to {}:\n", release.version);
|
|
||||||
if let Some(body) = &release.body {
|
|
||||||
println!("{body}");
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
println!("Topgrade is up-to-date");
|
let assume_yes = ctx.config().yes(Step::SelfUpdate);
|
||||||
}
|
let current_exe = env::current_exe();
|
||||||
|
|
||||||
{
|
let target = self_update_crate::get_target();
|
||||||
if result.updated() {
|
let result = Update::configure()
|
||||||
print_warning("Respawning...");
|
.repo_owner("topgrade-rs")
|
||||||
let mut command = Command::new(current_exe?);
|
.repo_name("topgrade")
|
||||||
command.args(env::args().skip(1)).env("TOPGRADE_NO_SELF_UPGRADE", "");
|
.target(target)
|
||||||
|
.bin_name(if cfg!(windows) { "topgrade.exe" } else { "topgrade" })
|
||||||
|
.show_output(true)
|
||||||
|
.show_download_progress(true)
|
||||||
|
.current_version(self_update_crate::cargo_crate_version!())
|
||||||
|
.no_confirm(assume_yes)
|
||||||
|
.build()?
|
||||||
|
.update_extended()?;
|
||||||
|
|
||||||
#[cfg(unix)]
|
if let UpdateStatus::Updated(release) = &result {
|
||||||
{
|
println!("\nTopgrade upgraded to {}:\n", release.version);
|
||||||
let err = command.exec();
|
if let Some(body) = &release.body {
|
||||||
bail!(err);
|
println!("{body}");
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
println!("Topgrade is up-to-date");
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
{
|
||||||
{
|
if result.updated() {
|
||||||
#[allow(clippy::disallowed_methods)]
|
print_info("Respawning...");
|
||||||
let status = command.status()?;
|
let mut command = Command::new(current_exe?);
|
||||||
bail!(Upgraded(status));
|
command.args(env::args().skip(1)).env("TOPGRADE_NO_SELF_UPGRADE", "");
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
|
let err = command.exec();
|
||||||
|
bail!(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
|
#[allow(clippy::disallowed_methods)]
|
||||||
|
let status = command.status()?;
|
||||||
|
bail!(Upgraded(status));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,9 @@ impl Display for Container {
|
|||||||
|
|
||||||
/// Returns a Vector of all containers, with Strings in the format
|
/// Returns a Vector of all containers, with Strings in the format
|
||||||
/// "REGISTRY/[PATH/]CONTAINER_NAME:TAG"
|
/// "REGISTRY/[PATH/]CONTAINER_NAME:TAG"
|
||||||
fn list_containers(crt: &Path) -> Result<Vec<Container>> {
|
///
|
||||||
|
/// Containers specified in `ignored_containers` will be filtered out.
|
||||||
|
fn list_containers(crt: &Path, ignored_containers: Option<&Vec<String>>) -> Result<Vec<Container>> {
|
||||||
debug!(
|
debug!(
|
||||||
"Querying '{} image ls --format \"{{{{.Repository}}}}:{{{{.Tag}}}}/{{{{.ID}}}}\"' for containers",
|
"Querying '{} image ls --format \"{{{{.Repository}}}}:{{{{.Tag}}}}/{{{{.ID}}}}\"' for containers",
|
||||||
crt.display()
|
crt.display()
|
||||||
@@ -83,6 +85,16 @@ fn list_containers(crt: &Path) -> Result<Vec<Container>> {
|
|||||||
assert_eq!(split_res.len(), 2);
|
assert_eq!(split_res.len(), 2);
|
||||||
let (repo_tag, image_id) = (split_res[0], split_res[1]);
|
let (repo_tag, image_id) = (split_res[0], split_res[1]);
|
||||||
|
|
||||||
|
if let Some(ignored_containers) = ignored_containers {
|
||||||
|
if ignored_containers
|
||||||
|
.iter()
|
||||||
|
.any(|ignored_container| repo_tag.eq(ignored_container))
|
||||||
|
{
|
||||||
|
debug!("Skipping ignored container '{}'", line);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
debug!(
|
debug!(
|
||||||
"Querying '{} image inspect --format \"{{{{.Os}}}}/{{{{.Architecture}}}}\"' for container {}",
|
"Querying '{} image inspect --format \"{{{{.Os}}}}/{{{{.Architecture}}}}\"' for container {}",
|
||||||
crt.display(),
|
crt.display(),
|
||||||
@@ -109,7 +121,8 @@ pub fn run_containers(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
|
|
||||||
print_separator("Containers");
|
print_separator("Containers");
|
||||||
let mut success = true;
|
let mut success = true;
|
||||||
let containers = list_containers(&crt).context("Failed to list Docker containers")?;
|
let containers =
|
||||||
|
list_containers(&crt, ctx.config().containers_ignored_tags()).context("Failed to list Docker containers")?;
|
||||||
debug!("Containers to inspect: {:?}", containers);
|
debug!("Containers to inspect: {:?}", containers);
|
||||||
|
|
||||||
for container in containers.iter() {
|
for container in containers.iter() {
|
||||||
|
|||||||
@@ -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 semver::Version;
|
||||||
use tempfile::tempfile_in;
|
use tempfile::tempfile_in;
|
||||||
use tracing::{debug, error};
|
use tracing::{debug, error};
|
||||||
|
|
||||||
@@ -23,6 +24,18 @@ use crate::{
|
|||||||
terminal::print_warning,
|
terminal::print_warning,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
pub fn is_wsl() -> Result<bool> {
|
||||||
|
let output = Command::new("uname").arg("-r").output_checked_utf8()?.stdout;
|
||||||
|
debug!("Uname output: {}", output);
|
||||||
|
Ok(output.contains("microsoft"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "linux"))]
|
||||||
|
pub fn is_wsl() -> Result<bool> {
|
||||||
|
Ok(false)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn run_cargo_update(ctx: &ExecutionContext) -> Result<()> {
|
pub fn run_cargo_update(ctx: &ExecutionContext) -> Result<()> {
|
||||||
let cargo_dir = env::var_os("CARGO_HOME")
|
let cargo_dir = env::var_os("CARGO_HOME")
|
||||||
.map(PathBuf::from)
|
.map(PathBuf::from)
|
||||||
@@ -287,7 +300,13 @@ pub fn run_opam_update(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
print_separator("OCaml Package Manager");
|
print_separator("OCaml Package Manager");
|
||||||
|
|
||||||
ctx.run_type().execute(&opam).arg("update").status_checked()?;
|
ctx.run_type().execute(&opam).arg("update").status_checked()?;
|
||||||
ctx.run_type().execute(&opam).arg("upgrade").status_checked()?;
|
|
||||||
|
let mut command = ctx.run_type().execute(&opam);
|
||||||
|
command.arg("upgrade");
|
||||||
|
if ctx.config().yes(Step::Opam) {
|
||||||
|
command.arg("--yes");
|
||||||
|
}
|
||||||
|
command.status_checked()?;
|
||||||
|
|
||||||
if ctx.config().cleanup() {
|
if ctx.config().cleanup() {
|
||||||
ctx.run_type().execute(&opam).arg("clean").status_checked()?;
|
ctx.run_type().execute(&opam).arg("clean").status_checked()?;
|
||||||
@@ -318,11 +337,57 @@ pub fn run_vcpkg_update(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
command.args(["upgrade", "--no-dry-run"]).status_checked()
|
command.args(["upgrade", "--no-dry-run"]).status_checked()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn run_vscode_extensions_update(ctx: &ExecutionContext) -> Result<()> {
|
||||||
|
// Calling vscode in WSL may install a server instead of updating extensions (https://github.com/topgrade-rs/topgrade/issues/594#issuecomment-1782157367)
|
||||||
|
if is_wsl()? {
|
||||||
|
return Err(SkipStep(String::from("Should not run in WSL")).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let vscode = require("code")?;
|
||||||
|
|
||||||
|
// Vscode has update command only since 1.86 version ("january 2024" update), disable the update for prior versions
|
||||||
|
// Use command `code --version` which returns 3 lines: version, git commit, instruction set. We parse only the first one
|
||||||
|
let version: Result<Version> = match Command::new("code")
|
||||||
|
.arg("--version")
|
||||||
|
.output_checked_utf8()?
|
||||||
|
.stdout
|
||||||
|
.lines()
|
||||||
|
.next()
|
||||||
|
{
|
||||||
|
Some(item) => Version::parse(item).map_err(|err| err.into()),
|
||||||
|
_ => return Err(SkipStep(String::from("Cannot find vscode version")).into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
if !matches!(version, Ok(version) if version >= Version::new(1, 86, 0)) {
|
||||||
|
return Err(SkipStep(String::from("Too old vscode version to have update extensions command")).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
print_separator("Visual Studio Code extensions");
|
||||||
|
|
||||||
|
ctx.run_type()
|
||||||
|
.execute(vscode)
|
||||||
|
.arg("--update-extensions")
|
||||||
|
.status_checked()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn run_pipx_update(ctx: &ExecutionContext) -> Result<()> {
|
pub fn run_pipx_update(ctx: &ExecutionContext) -> Result<()> {
|
||||||
let pipx = require("pipx")?;
|
let pipx = require("pipx")?;
|
||||||
print_separator("pipx");
|
print_separator("pipx");
|
||||||
|
|
||||||
ctx.run_type().execute(pipx).arg("upgrade-all").status_checked()
|
let mut command_args = vec!["upgrade-all"];
|
||||||
|
|
||||||
|
// pipx version 1.4.0 introduced a new command argument `pipx upgrade-all --quiet`
|
||||||
|
// (see https://pipx.pypa.io/stable/docs/#pipx-upgrade-all)
|
||||||
|
let version_str = Command::new("pipx")
|
||||||
|
.args(["--version"])
|
||||||
|
.output_checked_utf8()
|
||||||
|
.map(|s| s.stdout.trim().to_owned());
|
||||||
|
let version = Version::parse(&version_str?);
|
||||||
|
if matches!(version, Ok(version) if version >= Version::new(1, 4, 0)) {
|
||||||
|
command_args.push("--quiet")
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.run_type().execute(pipx).args(command_args).status_checked()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_conda_update(ctx: &ExecutionContext) -> Result<()> {
|
pub fn run_conda_update(ctx: &ExecutionContext) -> Result<()> {
|
||||||
@@ -367,6 +432,16 @@ pub fn run_mamba_update(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
command.status_checked()
|
command.status_checked()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn run_miktex_packages_update(ctx: &ExecutionContext) -> Result<()> {
|
||||||
|
let miktex = require("miktex")?;
|
||||||
|
print_separator("miktex");
|
||||||
|
|
||||||
|
ctx.run_type()
|
||||||
|
.execute(miktex)
|
||||||
|
.args(["packages", "update"])
|
||||||
|
.status_checked()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn run_pip3_update(ctx: &ExecutionContext) -> Result<()> {
|
pub fn run_pip3_update(ctx: &ExecutionContext) -> Result<()> {
|
||||||
let py = require("python").and_then(check_is_python_2_or_shim);
|
let py = require("python").and_then(check_is_python_2_or_shim);
|
||||||
let py3 = require("python3").and_then(check_is_python_2_or_shim);
|
let py3 = require("python3").and_then(check_is_python_2_or_shim);
|
||||||
@@ -383,22 +458,55 @@ pub fn run_pip3_update(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
Command::new(&python3)
|
Command::new(&python3)
|
||||||
.args(["-m", "pip"])
|
.args(["-m", "pip"])
|
||||||
.output_checked_utf8()
|
.output_checked_utf8()
|
||||||
.map_err(|_| SkipStep("pip does not exists".to_string()))?;
|
.map_err(|_| SkipStep("pip does not exist".to_string()))?;
|
||||||
|
|
||||||
let check_externally_managed = "import sysconfig; from os import path; print('Y') if path.isfile(path.join(sysconfig.get_path('stdlib'), 'EXTERNALLY-MANAGED')) else print('N')";
|
let check_extern_managed_script = "import sysconfig; from os import path; print('Y') if path.isfile(path.join(sysconfig.get_path('stdlib'), 'EXTERNALLY-MANAGED')) else print('N')";
|
||||||
Command::new(&python3)
|
let output = Command::new(&python3)
|
||||||
.args(["-c", check_externally_managed])
|
.args(["-c", check_extern_managed_script])
|
||||||
|
.output_checked_utf8()?;
|
||||||
|
let stdout = output.stdout.trim();
|
||||||
|
let extern_managed = match stdout {
|
||||||
|
"N" => false,
|
||||||
|
"Y" => true,
|
||||||
|
_ => unreachable!("unexpected output from `check_extern_managed_script`"),
|
||||||
|
};
|
||||||
|
|
||||||
|
let allow_break_sys_pkg = match Command::new(&python3)
|
||||||
|
.args(["-m", "pip", "config", "get", "global.break-system-packages"])
|
||||||
.output_checked_utf8()
|
.output_checked_utf8()
|
||||||
.map_err(|_| SkipStep("pip may be externally managed".to_string()))
|
{
|
||||||
.and_then(|output| match output.stdout.trim() {
|
Ok(output) => {
|
||||||
"N" => Ok(()),
|
let stdout = output.stdout.trim();
|
||||||
"Y" => Err(SkipStep("pip is externally managed".to_string())),
|
stdout
|
||||||
_ => {
|
.parse::<bool>()
|
||||||
print_warning("Unexpected output when checking EXTERNALLY-MANAGED");
|
.expect("unexpected output that is not `true` or `false`")
|
||||||
print_warning(output.stdout.trim());
|
}
|
||||||
Err(SkipStep("pip may be externally managed".to_string()))
|
// it can fail because this key may not be set
|
||||||
}
|
//
|
||||||
})?;
|
// ```sh
|
||||||
|
// $ pip --version
|
||||||
|
// pip 23.0.1 from /usr/lib/python3/dist-packages/pip (python 3.11)
|
||||||
|
//
|
||||||
|
// $ pip config get global.break-system-packages
|
||||||
|
// ERROR: No such key - global.break-system-packages
|
||||||
|
//
|
||||||
|
// $ echo $?
|
||||||
|
// 1
|
||||||
|
// ```
|
||||||
|
Err(_) => false,
|
||||||
|
};
|
||||||
|
|
||||||
|
debug!("pip3 externally managed: {} ", extern_managed);
|
||||||
|
debug!("pip3 global.break-system-packages: {}", allow_break_sys_pkg);
|
||||||
|
|
||||||
|
// Even though pip3 is externally managed, we should still update it if
|
||||||
|
// `global.break-system-packages` is true.
|
||||||
|
if extern_managed && !allow_break_sys_pkg {
|
||||||
|
return Err(SkipStep(
|
||||||
|
"Skip pip3 update as it is externally managed and global.break-system-packages is not true".to_string(),
|
||||||
|
)
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
|
||||||
print_separator("pip3");
|
print_separator("pip3");
|
||||||
if env::var("VIRTUAL_ENV").is_ok() {
|
if env::var("VIRTUAL_ENV").is_ok() {
|
||||||
@@ -636,6 +744,10 @@ pub fn run_dotnet_upgrade(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
.run_type()
|
.run_type()
|
||||||
.execute(&dotnet)
|
.execute(&dotnet)
|
||||||
.args(["tool", "list", "--global"])
|
.args(["tool", "list", "--global"])
|
||||||
|
// dotnet will print a greeting message on its first run, from this question:
|
||||||
|
// https://stackoverflow.com/q/70493706/14092446
|
||||||
|
// Setting `DOTNET_NOLOGO` to `true` should disable it
|
||||||
|
.env("DOTNET_NOLOGO", "true")
|
||||||
.output_checked_utf8()
|
.output_checked_utf8()
|
||||||
{
|
{
|
||||||
Ok(output) => output,
|
Ok(output) => output,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use std::io;
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::process::{Command, Output, Stdio};
|
use std::process::{Command, Output, Stdio};
|
||||||
|
|
||||||
|
use color_eyre::eyre::Context;
|
||||||
use color_eyre::eyre::{eyre, Result};
|
use color_eyre::eyre::{eyre, Result};
|
||||||
use console::style;
|
use console::style;
|
||||||
use futures::stream::{iter, FuturesUnordered};
|
use futures::stream::{iter, FuturesUnordered};
|
||||||
@@ -33,10 +34,12 @@ pub struct Repositories<'a> {
|
|||||||
bad_patterns: Vec<String>,
|
bad_patterns: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
fn output_checked_utf8(output: Output) -> Result<()> {
|
fn output_checked_utf8(output: Output) -> Result<()> {
|
||||||
if !(output.status.success()) {
|
if !(output.status.success()) {
|
||||||
let stderr = String::from_utf8(output.stderr).unwrap();
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||||
Err(eyre!(stderr))
|
let stderr = stderr.trim();
|
||||||
|
Err(eyre!("{stderr}"))
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -66,11 +69,12 @@ async fn pull_repository(repo: String, git: &Path, ctx: &ExecutionContext<'_>) -
|
|||||||
.stdin(Stdio::null())
|
.stdin(Stdio::null())
|
||||||
.output()
|
.output()
|
||||||
.await?;
|
.await?;
|
||||||
let result = output_checked_utf8(pull_output).and_then(|_| output_checked_utf8(submodule_output));
|
let result = output_checked_utf8(pull_output)
|
||||||
|
.and_then(|_| output_checked_utf8(submodule_output))
|
||||||
|
.wrap_err_with(|| format!("Failed to pull {repo}"));
|
||||||
|
|
||||||
if let Err(message) = &result {
|
if result.is_err() {
|
||||||
println!("{} pulling {}", style("Failed").red().bold(), &repo);
|
println!("{} pulling {}", style("Failed").red().bold(), &repo);
|
||||||
print!("{message}");
|
|
||||||
} else {
|
} else {
|
||||||
let after_revision = get_head_revision(git, &repo);
|
let after_revision = get_head_revision(git, &repo);
|
||||||
|
|
||||||
@@ -170,7 +174,7 @@ impl Git {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => match e.kind() {
|
Err(e) => match e.kind() {
|
||||||
io::ErrorKind::NotFound => debug!("{} does not exists", path.as_ref().display()),
|
io::ErrorKind::NotFound => debug!("{} does not exist", path.as_ref().display()),
|
||||||
_ => error!("Error looking for {}: {}", path.as_ref().display(), e),
|
_ => error!("Error looking for {}: {}", path.as_ref().display(), e),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -302,6 +306,8 @@ impl<'a> Repositories<'a> {
|
|||||||
self.repositories.is_empty()
|
self.repositories.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The following 2 functions are `#[cfg(unix)]` because they are only used in
|
||||||
|
// the `oh-my-zsh` step, which is UNIX-only.
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
pub fn remove(&mut self, path: &str) {
|
pub fn remove(&mut self, path: &str) {
|
||||||
let _removed = self.repositories.remove(path);
|
let _removed = self.repositories.remove(path);
|
||||||
|
|||||||
@@ -277,7 +277,7 @@ impl Aura {
|
|||||||
|
|
||||||
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_else(PathBuf::new);
|
let sudo = which("sudo").unwrap_or_default();
|
||||||
let mut aur_update = ctx.run_type().execute(&sudo);
|
let mut aur_update = ctx.run_type().execute(&sudo);
|
||||||
|
|
||||||
if sudo.ends_with("sudo") {
|
if sudo.ends_with("sudo") {
|
||||||
|
|||||||
@@ -2,13 +2,14 @@ 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::{require_option, REQUIRE_SUDO};
|
||||||
|
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(), REQUIRE_SUDO.to_string())?;
|
||||||
print_separator("DragonFly BSD Packages");
|
print_separator("DragonFly BSD Packages");
|
||||||
let mut cmd = ctx.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) {
|
||||||
cmd.arg("-y");
|
cmd.arg("-y");
|
||||||
@@ -18,9 +19,16 @@ 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(), REQUIRE_SUDO.to_string())?;
|
||||||
println!();
|
|
||||||
Command::new(sudo)
|
print_separator("DragonFly BSD Audit");
|
||||||
|
|
||||||
|
#[allow(clippy::disallowed_methods)]
|
||||||
|
if !Command::new(sudo)
|
||||||
.args(["/usr/local/sbin/pkg", "audit", "-Fr"])
|
.args(["/usr/local/sbin/pkg", "audit", "-Fr"])
|
||||||
.status_checked()?;
|
.status()?
|
||||||
|
.success()
|
||||||
|
{
|
||||||
|
println!("The package audit was successful, but vulnerable packages still remain on the system");
|
||||||
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +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(), REQUIRE_SUDO.to_string())?;
|
||||||
println!();
|
|
||||||
|
print_separator("FreeBSD Audit");
|
||||||
|
|
||||||
Command::new(sudo)
|
Command::new(sudo)
|
||||||
.args(["/usr/sbin/pkg", "audit", "-Fr"])
|
.args(["/usr/sbin/pkg", "audit", "-Fr"])
|
||||||
.status_checked()?;
|
.status_checked()?;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ use tracing::{debug, warn};
|
|||||||
use crate::command::CommandExt;
|
use crate::command::CommandExt;
|
||||||
use crate::error::{SkipStep, TopgradeError};
|
use crate::error::{SkipStep, TopgradeError};
|
||||||
use crate::execution_context::ExecutionContext;
|
use crate::execution_context::ExecutionContext;
|
||||||
|
use crate::steps::generic::is_wsl;
|
||||||
use crate::steps::os::archlinux;
|
use crate::steps::os::archlinux;
|
||||||
use crate::terminal::print_separator;
|
use crate::terminal::print_separator;
|
||||||
use crate::utils::{require, require_option, which, PathExt, REQUIRE_SUDO};
|
use crate::utils::{require, require_option, which, PathExt, REQUIRE_SUDO};
|
||||||
@@ -24,7 +25,7 @@ pub enum Distribution {
|
|||||||
CentOS,
|
CentOS,
|
||||||
ClearLinux,
|
ClearLinux,
|
||||||
Fedora,
|
Fedora,
|
||||||
FedoraSilverblue,
|
FedoraImmutable,
|
||||||
Debian,
|
Debian,
|
||||||
Gentoo,
|
Gentoo,
|
||||||
OpenMandriva,
|
OpenMandriva,
|
||||||
@@ -38,6 +39,7 @@ pub enum Distribution {
|
|||||||
Exherbo,
|
Exherbo,
|
||||||
NixOS,
|
NixOS,
|
||||||
KDENeon,
|
KDENeon,
|
||||||
|
Nobara,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Distribution {
|
impl Distribution {
|
||||||
@@ -52,18 +54,23 @@ impl Distribution {
|
|||||||
Some("alpine") => Distribution::Alpine,
|
Some("alpine") => Distribution::Alpine,
|
||||||
Some("centos") | Some("rhel") | Some("ol") => Distribution::CentOS,
|
Some("centos") | Some("rhel") | Some("ol") => Distribution::CentOS,
|
||||||
Some("clear-linux-os") => Distribution::ClearLinux,
|
Some("clear-linux-os") => Distribution::ClearLinux,
|
||||||
Some("fedora") | Some("nobara") => {
|
Some("fedora") => {
|
||||||
return if let Some(variant) = variant {
|
return if let Some(variant) = variant {
|
||||||
if variant.contains(&"Silverblue") {
|
if variant.contains(&"Silverblue")
|
||||||
Ok(Distribution::FedoraSilverblue)
|
|| variant.contains(&"Kinoite")
|
||||||
|
|| variant.contains(&"Sericea")
|
||||||
|
|| variant.contains(&"Onyx")
|
||||||
|
{
|
||||||
|
Ok(Distribution::FedoraImmutable)
|
||||||
} else {
|
} else {
|
||||||
Ok(Distribution::Fedora)
|
Ok(Distribution::Fedora)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Ok(Distribution::Fedora)
|
Ok(Distribution::Fedora)
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Some("nobara") => Distribution::Nobara,
|
||||||
Some("void") => Distribution::Void,
|
Some("void") => Distribution::Void,
|
||||||
Some("debian") | Some("pureos") | Some("Deepin") => Distribution::Debian,
|
Some("debian") | Some("pureos") | Some("Deepin") => Distribution::Debian,
|
||||||
Some("arch") | Some("manjaro-arm") | Some("garuda") | Some("artix") => Distribution::Arch,
|
Some("arch") | Some("manjaro-arm") | Some("garuda") | Some("artix") => Distribution::Arch,
|
||||||
@@ -131,7 +138,7 @@ impl Distribution {
|
|||||||
Distribution::Alpine => upgrade_alpine_linux(ctx),
|
Distribution::Alpine => upgrade_alpine_linux(ctx),
|
||||||
Distribution::Arch => archlinux::upgrade_arch_linux(ctx),
|
Distribution::Arch => archlinux::upgrade_arch_linux(ctx),
|
||||||
Distribution::CentOS | Distribution::Fedora => upgrade_redhat(ctx),
|
Distribution::CentOS | Distribution::Fedora => upgrade_redhat(ctx),
|
||||||
Distribution::FedoraSilverblue => upgrade_fedora_silverblue(ctx),
|
Distribution::FedoraImmutable => upgrade_fedora_immutable(ctx),
|
||||||
Distribution::ClearLinux => upgrade_clearlinux(ctx),
|
Distribution::ClearLinux => upgrade_clearlinux(ctx),
|
||||||
Distribution::Debian => upgrade_debian(ctx),
|
Distribution::Debian => upgrade_debian(ctx),
|
||||||
Distribution::Gentoo => upgrade_gentoo(ctx),
|
Distribution::Gentoo => upgrade_gentoo(ctx),
|
||||||
@@ -147,6 +154,7 @@ impl Distribution {
|
|||||||
Distribution::Bedrock => update_bedrock(ctx),
|
Distribution::Bedrock => update_bedrock(ctx),
|
||||||
Distribution::OpenMandriva => upgrade_openmandriva(ctx),
|
Distribution::OpenMandriva => upgrade_openmandriva(ctx),
|
||||||
Distribution::PCLinuxOS => upgrade_pclinuxos(ctx),
|
Distribution::PCLinuxOS => upgrade_pclinuxos(ctx),
|
||||||
|
Distribution::Nobara => upgrade_nobara(ctx),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,12 +193,6 @@ fn update_bedrock(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_wsl() -> Result<bool> {
|
|
||||||
let output = Command::new("uname").arg("-r").output_checked_utf8()?.stdout;
|
|
||||||
debug!("Uname output: {}", output);
|
|
||||||
Ok(output.contains("microsoft"))
|
|
||||||
}
|
|
||||||
|
|
||||||
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(), REQUIRE_SUDO.to_string())?;
|
||||||
@@ -230,7 +232,41 @@ fn upgrade_redhat(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn upgrade_fedora_silverblue(ctx: &ExecutionContext) -> Result<()> {
|
fn upgrade_nobara(ctx: &ExecutionContext) -> Result<()> {
|
||||||
|
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
|
||||||
|
let pkg_manager = require("dnf")?;
|
||||||
|
|
||||||
|
let mut update_command = ctx.run_type().execute(sudo);
|
||||||
|
update_command.arg(&pkg_manager);
|
||||||
|
|
||||||
|
if ctx.config().yes(Step::System) {
|
||||||
|
update_command.arg("-y");
|
||||||
|
}
|
||||||
|
|
||||||
|
update_command.arg("update");
|
||||||
|
// See https://nobaraproject.org/docs/upgrade-troubleshooting/how-do-i-update-the-system/
|
||||||
|
update_command.args([
|
||||||
|
"rpmfusion-nonfree-release",
|
||||||
|
"rpmfusion-free-release",
|
||||||
|
"fedora-repos",
|
||||||
|
"nobara-repos",
|
||||||
|
]);
|
||||||
|
update_command.arg("--refresh").status_checked()?;
|
||||||
|
|
||||||
|
let mut upgrade_command = ctx.run_type().execute(sudo);
|
||||||
|
upgrade_command.arg(&pkg_manager);
|
||||||
|
|
||||||
|
if ctx.config().yes(Step::System) {
|
||||||
|
upgrade_command.arg("-y");
|
||||||
|
}
|
||||||
|
|
||||||
|
upgrade_command.arg("distro-sync");
|
||||||
|
|
||||||
|
upgrade_command.status_checked()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
command.arg("upgrade");
|
command.arg("upgrade");
|
||||||
@@ -317,6 +353,7 @@ fn upgrade_openmandriva(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
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(), REQUIRE_SUDO.to_string())?;
|
||||||
let mut command_update = ctx.run_type().execute(sudo);
|
let mut command_update = ctx.run_type().execute(sudo);
|
||||||
@@ -708,14 +745,52 @@ fn upgrade_neon(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `needrestart` should be skipped if:
|
||||||
|
///
|
||||||
|
/// 1. This is a redhat-based distribution
|
||||||
|
/// 2. This is a debian-based distribution and it is using `nala` as the `apt`
|
||||||
|
/// alternative
|
||||||
|
fn should_skip_needrestart() -> Result<()> {
|
||||||
|
let distribution = Distribution::detect()?;
|
||||||
|
let msg = "needrestart will be ran by the package manager";
|
||||||
|
|
||||||
|
if distribution.redhat_based() {
|
||||||
|
return Err(SkipStep(String::from(msg)).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
if matches!(distribution, Distribution::Debian) {
|
||||||
|
let apt = which("apt-fast")
|
||||||
|
.or_else(|| {
|
||||||
|
if which("mist").is_some() {
|
||||||
|
Some(PathBuf::from("mist"))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.or_else(|| {
|
||||||
|
if Path::new("/usr/bin/nala").exists() {
|
||||||
|
Some(Path::new("/usr/bin/nala").to_path_buf())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| PathBuf::from("apt-get"));
|
||||||
|
|
||||||
|
let is_nala = apt.ends_with("nala");
|
||||||
|
|
||||||
|
if is_nala {
|
||||||
|
return Err(SkipStep(String::from(msg)).into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn run_needrestart(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(), REQUIRE_SUDO.to_string())?;
|
||||||
let needrestart = require("needrestart")?;
|
let needrestart = require("needrestart")?;
|
||||||
let distribution = Distribution::detect()?;
|
|
||||||
|
|
||||||
if distribution.redhat_based() {
|
should_skip_needrestart()?;
|
||||||
return Err(SkipStep(String::from("needrestart will be ran by the package manager")).into());
|
|
||||||
}
|
|
||||||
|
|
||||||
print_separator("Check for needed restarts");
|
print_separator("Check for needed restarts");
|
||||||
|
|
||||||
@@ -840,7 +915,12 @@ pub fn run_protonup_update(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
|
|
||||||
print_separator("protonup");
|
print_separator("protonup");
|
||||||
|
|
||||||
ctx.run_type().execute(protonup).status_checked()?;
|
let mut cmd = ctx.run_type().execute(protonup);
|
||||||
|
if ctx.config().yes(Step::Protonup) {
|
||||||
|
cmd.arg("--yes");
|
||||||
|
}
|
||||||
|
cmd.status_checked()?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -913,6 +993,22 @@ pub fn run_config_update(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn run_lure_update(ctx: &ExecutionContext) -> Result<()> {
|
||||||
|
let lure = require("lure")?;
|
||||||
|
|
||||||
|
print_separator("LURE");
|
||||||
|
|
||||||
|
let mut exe = ctx.run_type().execute(lure);
|
||||||
|
|
||||||
|
if ctx.config().yes(Step::Lure) {
|
||||||
|
exe.args(["-i=false", "up"]);
|
||||||
|
} else {
|
||||||
|
exe.arg("up");
|
||||||
|
}
|
||||||
|
|
||||||
|
exe.status_checked()
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -976,6 +1072,17 @@ mod tests {
|
|||||||
test_template(include_str!("os_release/fedora"), Distribution::Fedora);
|
test_template(include_str!("os_release/fedora"), Distribution::Fedora);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_fedora_immutable() {
|
||||||
|
test_template(
|
||||||
|
include_str!("os_release/fedorasilverblue"),
|
||||||
|
Distribution::FedoraImmutable,
|
||||||
|
);
|
||||||
|
test_template(include_str!("os_release/fedorakinoite"), Distribution::FedoraImmutable);
|
||||||
|
test_template(include_str!("os_release/fedoraonyx"), Distribution::FedoraImmutable);
|
||||||
|
test_template(include_str!("os_release/fedorasericea"), Distribution::FedoraImmutable);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_manjaro() {
|
fn test_manjaro() {
|
||||||
test_template(include_str!("os_release/manjaro"), Distribution::Arch);
|
test_template(include_str!("os_release/manjaro"), Distribution::Arch);
|
||||||
@@ -1045,4 +1152,9 @@ mod tests {
|
|||||||
fn test_solus() {
|
fn test_solus() {
|
||||||
test_template(include_str!("os_release/solus"), Distribution::Solus);
|
test_template(include_str!("os_release/solus"), Distribution::Solus);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_nobara() {
|
||||||
|
test_template(include_str!("os_release/nobara"), Distribution::Nobara);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
23
src/steps/os/os_release/fedorakinoite
Normal file
23
src/steps/os/os_release/fedorakinoite
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
NAME="Fedora Linux"
|
||||||
|
VERSION="39.20240105.0 (Kinoite)"
|
||||||
|
ID=fedora
|
||||||
|
VERSION_ID=39
|
||||||
|
VERSION_CODENAME=""
|
||||||
|
PLATFORM_ID="platform:f39"
|
||||||
|
PRETTY_NAME="Fedora Linux 39.20240105.0 (Kinoite)"
|
||||||
|
ANSI_COLOR="0;38;2;60;110;180"
|
||||||
|
LOGO=fedora-logo-icon
|
||||||
|
CPE_NAME="cpe:/o:fedoraproject:fedora:39"
|
||||||
|
DEFAULT_HOSTNAME="fedora"
|
||||||
|
HOME_URL="https://kinoite.fedoraproject.org"
|
||||||
|
DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora-kinoite/"
|
||||||
|
SUPPORT_URL="https://ask.fedoraproject.org/"
|
||||||
|
BUG_REPORT_URL="https://pagure.io/fedora-kde/SIG/issues"
|
||||||
|
REDHAT_BUGZILLA_PRODUCT="Fedora"
|
||||||
|
REDHAT_BUGZILLA_PRODUCT_VERSION=39
|
||||||
|
REDHAT_SUPPORT_PRODUCT="Fedora"
|
||||||
|
REDHAT_SUPPORT_PRODUCT_VERSION=39
|
||||||
|
SUPPORT_END=2024-11-12
|
||||||
|
VARIANT="Kinoite"
|
||||||
|
VARIANT_ID=kinoite
|
||||||
|
OSTREE_VERSION='39.20240105.0'
|
||||||
22
src/steps/os/os_release/fedoraonyx
Normal file
22
src/steps/os/os_release/fedoraonyx
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
NAME="Fedora Linux"
|
||||||
|
VERSION="39 (Onyx)"
|
||||||
|
ID=fedora
|
||||||
|
VERSION_ID=39
|
||||||
|
VERSION_CODENAME=""
|
||||||
|
PLATFORM_ID="platform:f39"
|
||||||
|
PRETTY_NAME="Fedora Linux 39 (Onyx)"
|
||||||
|
ANSI_COLOR="0;38;2;60;110;180"
|
||||||
|
LOGO=fedora-logo-icon
|
||||||
|
CPE_NAME="cpe:/o:fedoraproject:fedora:39"
|
||||||
|
DEFAULT_HOSTNAME="fedora"
|
||||||
|
HOME_URL="https://fedoraproject.org/onyx/"
|
||||||
|
DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora-onyx/"
|
||||||
|
SUPPORT_URL="https://ask.fedoraproject.org/"
|
||||||
|
BUG_REPORT_URL="https://bugzilla.redhat.com/"
|
||||||
|
REDHAT_BUGZILLA_PRODUCT="Fedora"
|
||||||
|
REDHAT_BUGZILLA_PRODUCT_VERSION=39
|
||||||
|
REDHAT_SUPPORT_PRODUCT="Fedora"
|
||||||
|
REDHAT_SUPPORT_PRODUCT_VERSION=39
|
||||||
|
SUPPORT_END=2024-05-14
|
||||||
|
VARIANT="Onyx"
|
||||||
|
VARIANT_ID=onyx
|
||||||
22
src/steps/os/os_release/fedorasericea
Normal file
22
src/steps/os/os_release/fedorasericea
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
NAME="Fedora Linux"
|
||||||
|
VERSION="39 (Sericea)"
|
||||||
|
ID=fedora
|
||||||
|
VERSION_ID=39
|
||||||
|
VERSION_CODENAME=""
|
||||||
|
PLATFORM_ID="platform:f39"
|
||||||
|
PRETTY_NAME="Fedora Linux 39 (Sericea)"
|
||||||
|
ANSI_COLOR="0;38;2;60;110;180"
|
||||||
|
LOGO=fedora-logo-icon
|
||||||
|
CPE_NAME="cpe:/o:fedoraproject:fedora:39"
|
||||||
|
DEFAULT_HOSTNAME="fedora"
|
||||||
|
HOME_URL="https://fedoraproject.org/sericea/"
|
||||||
|
DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora-sericea/"
|
||||||
|
SUPPORT_URL="https://ask.fedoraproject.org/"
|
||||||
|
BUG_REPORT_URL="https://gitlab.com/fedora/sigs/sway/SIG/-/issues"
|
||||||
|
REDHAT_BUGZILLA_PRODUCT="Fedora"
|
||||||
|
REDHAT_BUGZILLA_PRODUCT_VERSION=39
|
||||||
|
REDHAT_SUPPORT_PRODUCT="Fedora"
|
||||||
|
REDHAT_SUPPORT_PRODUCT_VERSION=39
|
||||||
|
SUPPORT_END=2024-05-14
|
||||||
|
VARIANT="Sericea"
|
||||||
|
VARIANT_ID=sericea
|
||||||
22
src/steps/os/os_release/fedorasilverblue
Normal file
22
src/steps/os/os_release/fedorasilverblue
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
NAME="Fedora Linux"
|
||||||
|
VERSION="39 (Silverblue)"
|
||||||
|
ID=fedora
|
||||||
|
VERSION_ID=39
|
||||||
|
VERSION_CODENAME=""
|
||||||
|
PLATFORM_ID="platform:f39"
|
||||||
|
PRETTY_NAME="Fedora Linux 39 (Silverblue)"
|
||||||
|
ANSI_COLOR="0;38;2;60;110;180"
|
||||||
|
LOGO=fedora-logo-icon
|
||||||
|
CPE_NAME="cpe:/o:fedoraproject:fedora:39"
|
||||||
|
DEFAULT_HOSTNAME="fedora"
|
||||||
|
HOME_URL="https://silverblue.fedoraproject.org"
|
||||||
|
DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora-silverblue/"
|
||||||
|
SUPPORT_URL="https://ask.fedoraproject.org/"
|
||||||
|
BUG_REPORT_URL="https://github.com/fedora-silverblue/issue-tracker/issues"
|
||||||
|
REDHAT_BUGZILLA_PRODUCT="Fedora"
|
||||||
|
REDHAT_BUGZILLA_PRODUCT_VERSION=39
|
||||||
|
REDHAT_SUPPORT_PRODUCT="Fedora"
|
||||||
|
REDHAT_SUPPORT_PRODUCT_VERSION=39
|
||||||
|
SUPPORT_END=2024-05-14
|
||||||
|
VARIANT="Silverblue"
|
||||||
|
VARIANT_ID=silverblue
|
||||||
23
src/steps/os/os_release/nobara
Normal file
23
src/steps/os/os_release/nobara
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
NAME="Nobara Linux"
|
||||||
|
VERSION="39 (GNOME Edition)"
|
||||||
|
ID=nobara
|
||||||
|
ID_LIKE="rhel centos fedora"
|
||||||
|
VERSION_ID=39
|
||||||
|
VERSION_CODENAME=""
|
||||||
|
PLATFORM_ID="platform:f39"
|
||||||
|
PRETTY_NAME="Nobara Linux 39 (GNOME Edition)"
|
||||||
|
ANSI_COLOR="0;38;2;60;110;180"
|
||||||
|
LOGO=nobara-logo-icon
|
||||||
|
CPE_NAME="cpe:/o:nobaraproject:nobara:39"
|
||||||
|
DEFAULT_HOSTNAME="nobara"
|
||||||
|
HOME_URL="https://nobaraproject.org/"
|
||||||
|
DOCUMENTATION_URL="https://www.nobaraproject.org/"
|
||||||
|
SUPPORT_URL="https://www.nobaraproject.org/"
|
||||||
|
BUG_REPORT_URL="https://gitlab.com/gloriouseggroll/nobara-images"
|
||||||
|
REDHAT_BUGZILLA_PRODUCT="Nobara"
|
||||||
|
REDHAT_BUGZILLA_PRODUCT_VERSION=39
|
||||||
|
REDHAT_SUPPORT_PRODUCT="Nobara"
|
||||||
|
REDHAT_SUPPORT_PRODUCT_VERSION=39
|
||||||
|
SUPPORT_END=2024-05-14
|
||||||
|
VARIANT="GNOME Edition"
|
||||||
|
VARIANT_ID=gnome
|
||||||
@@ -1,16 +1,22 @@
|
|||||||
|
use std::ffi::OsStr;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::os::unix::fs::MetadataExt;
|
use std::os::unix::fs::MetadataExt;
|
||||||
|
use std::path::Component;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use std::{env::var, path::Path};
|
use std::{env::var, path::Path};
|
||||||
|
|
||||||
use crate::command::CommandExt;
|
use crate::command::CommandExt;
|
||||||
use crate::{Step, HOME_DIR};
|
use crate::{Step, HOME_DIR};
|
||||||
|
use color_eyre::eyre::eyre;
|
||||||
|
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;
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
use super::linux::Distribution;
|
||||||
use crate::error::SkipStep;
|
use crate::error::SkipStep;
|
||||||
use crate::execution_context::ExecutionContext;
|
use crate::execution_context::ExecutionContext;
|
||||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||||
@@ -281,7 +287,7 @@ pub fn run_brew_formula(ctx: &ExecutionContext, variant: BrewVariant) -> Result<
|
|||||||
variant.execute(run_type).arg("update").status_checked()?;
|
variant.execute(run_type).arg("update").status_checked()?;
|
||||||
variant
|
variant
|
||||||
.execute(run_type)
|
.execute(run_type)
|
||||||
.args(["upgrade", "--ignore-pinned", "--formula"])
|
.args(["upgrade", "--formula"])
|
||||||
.status_checked()?;
|
.status_checked()?;
|
||||||
|
|
||||||
if ctx.config().cleanup() {
|
if ctx.config().cleanup() {
|
||||||
@@ -363,24 +369,8 @@ pub fn run_nix(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
debug!("nix profile: {:?}", profile_path);
|
debug!("nix profile: {:?}", profile_path);
|
||||||
let manifest_json_path = profile_path.join("manifest.json");
|
let manifest_json_path = profile_path.join("manifest.json");
|
||||||
|
|
||||||
let output = Command::new(&nix_env).args(["--query", "nix"]).output_checked_utf8();
|
|
||||||
debug!("nix-env output: {:?}", output);
|
|
||||||
let should_self_upgrade = output.is_ok();
|
|
||||||
|
|
||||||
print_separator("Nix");
|
print_separator("Nix");
|
||||||
|
|
||||||
let multi_user = fs::metadata(&nix)?.uid() == 0;
|
|
||||||
debug!("Multi user nix: {}", multi_user);
|
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
{
|
|
||||||
use super::linux::Distribution;
|
|
||||||
|
|
||||||
if let Ok(Distribution::NixOS) = Distribution::detect() {
|
|
||||||
return Err(SkipStep(String::from("Nix on NixOS must be upgraded via nixos-rebuild switch")).into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
{
|
{
|
||||||
if require("darwin-rebuild").is_ok() {
|
if require("darwin-rebuild").is_ok() {
|
||||||
@@ -392,29 +382,144 @@ pub fn run_nix(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let run_type = ctx.run_type();
|
let run_type = ctx.run_type();
|
||||||
|
|
||||||
if should_self_upgrade {
|
|
||||||
if multi_user {
|
|
||||||
ctx.execute_elevated(&nix, true)?.arg("upgrade-nix").status_checked()?;
|
|
||||||
} else {
|
|
||||||
run_type.execute(&nix).arg("upgrade-nix").status_checked()?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
run_type.execute(nix_channel).arg("--update").status_checked()?;
|
run_type.execute(nix_channel).arg("--update").status_checked()?;
|
||||||
|
|
||||||
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())
|
||||||
.arg("profile")
|
.arg("profile")
|
||||||
.arg("upgrade")
|
.arg("upgrade")
|
||||||
.arg(".*")
|
.arg(".*")
|
||||||
|
.arg("--verbose")
|
||||||
.status_checked()
|
.status_checked()
|
||||||
} else {
|
} else {
|
||||||
run_type.execute(&nix_env).arg("--upgrade").status_checked()
|
let mut command = run_type.execute(nix_env);
|
||||||
|
command.arg("--upgrade");
|
||||||
|
if let Some(args) = ctx.config().nix_env_arguments() {
|
||||||
|
command.args(args.split_whitespace());
|
||||||
|
};
|
||||||
|
command.status_checked()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn run_nix_self_upgrade(ctx: &ExecutionContext) -> Result<()> {
|
||||||
|
let nix = require("nix")?;
|
||||||
|
|
||||||
|
// Should we attempt to upgrade Nix with `nix upgrade-nix`?
|
||||||
|
#[allow(unused_mut)]
|
||||||
|
let mut should_self_upgrade = cfg!(target_os = "macos");
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
{
|
||||||
|
// We can't use `nix upgrade-nix` on NixOS.
|
||||||
|
if let Ok(Distribution::NixOS) = Distribution::detect() {
|
||||||
|
should_self_upgrade = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !should_self_upgrade {
|
||||||
|
return Err(SkipStep(String::from(
|
||||||
|
"`nix upgrade-nix` can only be used on macOS or non-NixOS Linux",
|
||||||
|
))
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
if nix_profile_dir(&nix)?.is_none() {
|
||||||
|
return Err(SkipStep(String::from(
|
||||||
|
"`nix upgrade-nix` cannot be run when Nix is installed in a profile",
|
||||||
|
))
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
print_separator("Nix (self-upgrade)");
|
||||||
|
|
||||||
|
let multi_user = fs::metadata(&nix)?.uid() == 0;
|
||||||
|
debug!("Multi user nix: {}", multi_user);
|
||||||
|
|
||||||
|
let nix_args = nix_args();
|
||||||
|
if multi_user {
|
||||||
|
ctx.execute_elevated(&nix, true)?
|
||||||
|
.args(nix_args)
|
||||||
|
.arg("upgrade-nix")
|
||||||
|
.status_checked()
|
||||||
|
} else {
|
||||||
|
ctx.run_type()
|
||||||
|
.execute(&nix)
|
||||||
|
.args(nix_args)
|
||||||
|
.arg("upgrade-nix")
|
||||||
|
.status_checked()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If we try to `nix upgrade-nix` but Nix is installed with `nix profile`, we'll get a `does not
|
||||||
|
/// appear to be part of a Nix profile` error.
|
||||||
|
///
|
||||||
|
/// We duplicate some of the `nix` logic here to avoid this.
|
||||||
|
/// See: <https://github.com/NixOS/nix/blob/f0180487a0e4c0091b46cb1469c44144f5400240/src/nix/upgrade-nix.cc#L102-L139>
|
||||||
|
///
|
||||||
|
/// See: <https://github.com/NixOS/nix/issues/5473>
|
||||||
|
fn nix_profile_dir(nix: &Path) -> Result<Option<PathBuf>> {
|
||||||
|
// NOTE: `nix` uses the location of the `nix-env` binary for this but we're using the `nix`
|
||||||
|
// binary; should be the same.
|
||||||
|
let nix_bin_dir = nix.parent();
|
||||||
|
if nix_bin_dir.and_then(|p| p.file_name()) != Some(OsStr::new("bin")) {
|
||||||
|
debug!("Nix is not installed in a `bin` directory: {nix_bin_dir:?}");
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
let nix_dir = nix_bin_dir
|
||||||
|
.and_then(|bin_dir| bin_dir.parent())
|
||||||
|
.ok_or_else(|| eyre!("Unable to find Nix install directory from Nix binary {nix:?}"))?;
|
||||||
|
|
||||||
|
debug!("Found Nix in {nix_dir:?}");
|
||||||
|
|
||||||
|
let mut profile_dir = nix_dir.to_path_buf();
|
||||||
|
while profile_dir.is_symlink() {
|
||||||
|
profile_dir = profile_dir
|
||||||
|
.parent()
|
||||||
|
.ok_or_else(|| eyre!("Path has no parent: {profile_dir:?}"))?
|
||||||
|
.join(
|
||||||
|
profile_dir
|
||||||
|
.read_link()
|
||||||
|
.wrap_err_with(|| format!("Failed to read symlink {profile_dir:?}"))?,
|
||||||
|
);
|
||||||
|
|
||||||
|
// NOTE: `nix` uses a hand-rolled canonicalize function, Rust just uses `realpath`.
|
||||||
|
if profile_dir
|
||||||
|
.canonicalize()
|
||||||
|
.wrap_err_with(|| format!("Failed to canonicalize {profile_dir:?}"))?
|
||||||
|
.components()
|
||||||
|
.any(|component| component == Component::Normal(OsStr::new("profiles")))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
debug!("Found Nix profile {profile_dir:?}");
|
||||||
|
|
||||||
|
let user_env = profile_dir
|
||||||
|
.canonicalize()
|
||||||
|
.wrap_err_with(|| format!("Failed to canonicalize {profile_dir:?}"))?;
|
||||||
|
|
||||||
|
Ok(
|
||||||
|
if user_env
|
||||||
|
.file_name()
|
||||||
|
.and_then(|name| name.to_str())
|
||||||
|
.map(|name| name.ends_with("user-environment"))
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
|
Some(profile_dir)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn nix_args() -> [&'static str; 2] {
|
||||||
|
["--extra-experimental-features", "nix-command"]
|
||||||
|
}
|
||||||
|
|
||||||
pub fn run_yadm(ctx: &ExecutionContext) -> Result<()> {
|
pub fn run_yadm(ctx: &ExecutionContext) -> Result<()> {
|
||||||
let yadm = require("yadm")?;
|
let yadm = require("yadm")?;
|
||||||
|
|
||||||
@@ -538,6 +643,19 @@ pub fn run_bun(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
ctx.run_type().execute(bun).arg("upgrade").status_checked()
|
ctx.run_type().execute(bun).arg("upgrade").status_checked()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn run_bun_packages(ctx: &ExecutionContext) -> Result<()> {
|
||||||
|
let bun = require("bun")?;
|
||||||
|
|
||||||
|
print_separator("Bun Packages");
|
||||||
|
|
||||||
|
if !HOME_DIR.join(".bun/install/global/package.json").exists() {
|
||||||
|
println!("No global packages installed");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.run_type().execute(bun).args(["-g", "update"]).status_checked()
|
||||||
|
}
|
||||||
|
|
||||||
/// Update dotfiles with `rcm(7)`.
|
/// Update dotfiles with `rcm(7)`.
|
||||||
///
|
///
|
||||||
/// See: <https://github.com/thoughtbot/rcm>
|
/// See: <https://github.com/thoughtbot/rcm>
|
||||||
|
|||||||
@@ -128,12 +128,45 @@ 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(String::from("Could not find Topgrade installed in WSL")))?
|
||||||
|
.stdout // The normal output from `which topgrade` appends a newline, so we trim it here.
|
||||||
|
.trim_end()
|
||||||
|
.to_owned();
|
||||||
|
|
||||||
let mut command = ctx.run_type().execute(wsl);
|
let mut command = ctx.run_type().execute(wsl);
|
||||||
|
|
||||||
|
// The `arg` method automatically quotes its arguments.
|
||||||
|
// This means we can't append additional arguments to `topgrade` in WSL
|
||||||
|
// by calling `arg` successively.
|
||||||
|
//
|
||||||
|
// For example:
|
||||||
|
//
|
||||||
|
// ```rust
|
||||||
|
// command
|
||||||
|
// .args(["-d", dist, "bash", "-c"])
|
||||||
|
// .arg(format!("TOPGRADE_PREFIX={dist} exec {topgrade}"));
|
||||||
|
// ```
|
||||||
|
//
|
||||||
|
// creates a command string like:
|
||||||
|
// > `C:\WINDOWS\system32\wsl.EXE -d Ubuntu bash -c 'TOPGRADE_PREFIX=Ubuntu exec /bin/topgrade'`
|
||||||
|
//
|
||||||
|
// Adding the following:
|
||||||
|
//
|
||||||
|
// ```rust
|
||||||
|
// command.arg("-v");
|
||||||
|
// ```
|
||||||
|
//
|
||||||
|
// appends the next argument like so:
|
||||||
|
// > `C:\WINDOWS\system32\wsl.EXE -d Ubuntu bash -c 'TOPGRADE_PREFIX=Ubuntu exec /bin/topgrade' -v`
|
||||||
|
// which means `-v` isn't passed to `topgrade`.
|
||||||
|
let mut args = String::new();
|
||||||
|
if ctx.config().verbose() {
|
||||||
|
args.push_str("-v");
|
||||||
|
}
|
||||||
|
|
||||||
command
|
command
|
||||||
.args(["-d", dist, "bash", "-c"])
|
.args(["-d", dist, "bash", "-c"])
|
||||||
.arg(format!("TOPGRADE_PREFIX={dist} exec {topgrade}"));
|
.arg(format!("TOPGRADE_PREFIX={dist} exec {topgrade} {args}"));
|
||||||
|
|
||||||
if ctx.config().yes(Step::Wsl) {
|
if ctx.config().yes(Step::Wsl) {
|
||||||
command.arg("-y");
|
command.arg("-y");
|
||||||
|
|||||||
@@ -122,7 +122,10 @@ pub fn run_zinit(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
|
|
||||||
print_separator("zinit");
|
print_separator("zinit");
|
||||||
|
|
||||||
let cmd = format!("source {} && zinit self-update && zinit update --all", zshrc.display(),);
|
let cmd = format!(
|
||||||
|
"source {} && zinit self-update && zinit update --all -p",
|
||||||
|
zshrc.display(),
|
||||||
|
);
|
||||||
ctx.run_type()
|
ctx.run_type()
|
||||||
.execute(zsh)
|
.execute(zsh)
|
||||||
.args(["-i", "-c", cmd.as_str()])
|
.args(["-i", "-c", cmd.as_str()])
|
||||||
@@ -137,7 +140,7 @@ pub fn run_zi(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
|
|
||||||
print_separator("zi");
|
print_separator("zi");
|
||||||
|
|
||||||
let cmd = format!("source {} && zi self-update && zi update --all", zshrc.display(),);
|
let cmd = format!("source {} && zi self-update && zi update --all -p", zshrc.display(),);
|
||||||
ctx.run_type().execute(zsh).args(["-i", "-c", &cmd]).status_checked()
|
ctx.run_type().execute(zsh).args(["-i", "-c", &cmd]).status_checked()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,15 +179,16 @@ pub fn run_oh_my_zsh(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
// children processes won't get it either, so we source the zshrc and set
|
// children processes won't get it either, so we source the zshrc and set
|
||||||
// the ZSH variable for topgrade here.
|
// the ZSH variable for topgrade here.
|
||||||
if ctx.under_ssh() {
|
if ctx.under_ssh() {
|
||||||
let zshrc_path = zshrc().require()?;
|
let res_env_zsh = Command::new("zsh")
|
||||||
let output = Command::new("zsh")
|
.args(["-ic", "print -rn -- ${ZSH:?}"])
|
||||||
.args([
|
.output_checked_utf8();
|
||||||
"-c",
|
|
||||||
// ` > /dev/null` is used in case the user's zshrc will have some stdout output.
|
// this command will fail if `ZSH` is not set
|
||||||
format!("source {} > /dev/null && echo $ZSH", zshrc_path.display()).as_str(),
|
if let Ok(output) = res_env_zsh {
|
||||||
])
|
let env_zsh = output.stdout;
|
||||||
.output_checked_utf8()?;
|
debug!("Oh-my-zsh: under SSH, setting ZSH={}", env_zsh);
|
||||||
env::set_var("ZSH", output.stdout.trim());
|
env::set_var("ZSH", env_zsh);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let oh_my_zsh = env::var("ZSH")
|
let oh_my_zsh = env::var("ZSH")
|
||||||
|
|||||||
16
src/sudo.rs
16
src/sudo.rs
@@ -26,10 +26,10 @@ impl Sudo {
|
|||||||
pub fn detect() -> Option<Self> {
|
pub fn detect() -> Option<Self> {
|
||||||
which("doas")
|
which("doas")
|
||||||
.map(|p| (p, SudoKind::Doas))
|
.map(|p| (p, SudoKind::Doas))
|
||||||
.or_else(|| which("please").map(|p| (p, SudoKind::Please)))
|
|
||||||
.or_else(|| which("sudo").map(|p| (p, SudoKind::Sudo)))
|
.or_else(|| which("sudo").map(|p| (p, SudoKind::Sudo)))
|
||||||
.or_else(|| which("gsudo").map(|p| (p, SudoKind::Gsudo)))
|
.or_else(|| which("gsudo").map(|p| (p, SudoKind::Gsudo)))
|
||||||
.or_else(|| which("pkexec").map(|p| (p, SudoKind::Pkexec)))
|
.or_else(|| which("pkexec").map(|p| (p, SudoKind::Pkexec)))
|
||||||
|
.or_else(|| which("please").map(|p| (p, SudoKind::Please)))
|
||||||
.map(|(path, kind)| Self { path, kind })
|
.map(|(path, kind)| Self { path, kind })
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,12 +55,6 @@ impl Sudo {
|
|||||||
// See: https://man.openbsd.org/doas
|
// See: https://man.openbsd.org/doas
|
||||||
cmd.arg("echo");
|
cmd.arg("echo");
|
||||||
}
|
}
|
||||||
SudoKind::Please => {
|
|
||||||
// From `man please`
|
|
||||||
// -w, --warm
|
|
||||||
// Warm the access token and exit.
|
|
||||||
cmd.arg("-w");
|
|
||||||
}
|
|
||||||
SudoKind::Sudo => {
|
SudoKind::Sudo => {
|
||||||
// From `man sudo` on macOS:
|
// From `man sudo` on macOS:
|
||||||
// -v, --validate
|
// -v, --validate
|
||||||
@@ -85,6 +79,12 @@ impl Sudo {
|
|||||||
// See: https://linux.die.net/man/1/pkexec
|
// See: https://linux.die.net/man/1/pkexec
|
||||||
cmd.arg("echo");
|
cmd.arg("echo");
|
||||||
}
|
}
|
||||||
|
SudoKind::Please => {
|
||||||
|
// From `man please`
|
||||||
|
// -w, --warm
|
||||||
|
// Warm the access token and exit.
|
||||||
|
cmd.arg("-w");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
cmd.status_checked().wrap_err("Failed to elevate permissions")
|
cmd.status_checked().wrap_err("Failed to elevate permissions")
|
||||||
}
|
}
|
||||||
@@ -112,10 +112,10 @@ impl Sudo {
|
|||||||
#[strum(serialize_all = "lowercase")]
|
#[strum(serialize_all = "lowercase")]
|
||||||
pub enum SudoKind {
|
pub enum SudoKind {
|
||||||
Doas,
|
Doas,
|
||||||
Please,
|
|
||||||
Sudo,
|
Sudo,
|
||||||
Gsudo,
|
Gsudo,
|
||||||
Pkexec,
|
Pkexec,
|
||||||
|
Please,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsRef<OsStr> for Sudo {
|
impl AsRef<OsStr> for Sudo {
|
||||||
|
|||||||
95
src/utils.rs
95
src/utils.rs
@@ -5,9 +5,17 @@ use std::path::{Path, PathBuf};
|
|||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
use color_eyre::eyre::Result;
|
use color_eyre::eyre::Result;
|
||||||
|
|
||||||
use tracing::{debug, error};
|
use tracing::{debug, error};
|
||||||
|
use tracing_subscriber::fmt::format::FmtSpan;
|
||||||
|
use tracing_subscriber::layer::SubscriberExt;
|
||||||
|
use tracing_subscriber::reload::{Handle, Layer};
|
||||||
|
use tracing_subscriber::util::SubscriberInitExt;
|
||||||
|
use tracing_subscriber::{fmt, Registry};
|
||||||
|
use tracing_subscriber::{registry, EnvFilter};
|
||||||
|
|
||||||
use crate::command::CommandExt;
|
use crate::command::CommandExt;
|
||||||
|
use crate::config::DEFAULT_LOG_LEVEL;
|
||||||
use crate::error::SkipStep;
|
use crate::error::SkipStep;
|
||||||
|
|
||||||
pub trait PathExt
|
pub trait PathExt
|
||||||
@@ -111,44 +119,13 @@ pub fn string_prepend_str(string: &mut String, s: &str) {
|
|||||||
*string = new_string;
|
*string = new_string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* sys-info-rs
|
|
||||||
*
|
|
||||||
* Copyright (c) 2015 Siyu Wang
|
|
||||||
*
|
|
||||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
* of this software and associated documentation files (the "Software"), to deal
|
|
||||||
* in the Software without restriction, including without limitation the rights
|
|
||||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
* copies of the Software, and to permit persons to whom the Software is
|
|
||||||
* furnished to do so, subject to the following conditions:
|
|
||||||
*
|
|
||||||
* The above copyright notice and this permission notice shall be included in all
|
|
||||||
* copies or substantial portions of the Software.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
* SOFTWARE.
|
|
||||||
*/
|
|
||||||
#[cfg(target_family = "unix")]
|
#[cfg(target_family = "unix")]
|
||||||
pub fn hostname() -> Result<String> {
|
pub fn hostname() -> Result<String> {
|
||||||
use std::ffi;
|
match nix::unistd::gethostname() {
|
||||||
extern crate libc;
|
Ok(os_str) => Ok(os_str
|
||||||
|
.into_string()
|
||||||
unsafe {
|
.map_err(|_| SkipStep("Failed to get a UTF-8 encoded hostname".into()))?),
|
||||||
let buf_size = libc::sysconf(libc::_SC_HOST_NAME_MAX) as usize;
|
Err(e) => Err(e.into()),
|
||||||
let mut buf = Vec::<u8>::with_capacity(buf_size + 1);
|
|
||||||
|
|
||||||
if libc::gethostname(buf.as_mut_ptr() as *mut libc::c_char, buf_size) < 0 {
|
|
||||||
return Err(SkipStep(format!("Failed to get hostname: {}", std::io::Error::last_os_error())).into());
|
|
||||||
}
|
|
||||||
let hostname_len = libc::strnlen(buf.as_ptr() as *const libc::c_char, buf_size);
|
|
||||||
buf.set_len(hostname_len);
|
|
||||||
|
|
||||||
Ok(ffi::CString::new(buf).unwrap().into_string().unwrap())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -251,3 +228,49 @@ pub fn check_is_python_2_or_shim(python: PathBuf) -> Result<PathBuf> {
|
|||||||
|
|
||||||
Ok(python)
|
Ok(python)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set up the tracing logger
|
||||||
|
///
|
||||||
|
/// # Return value
|
||||||
|
/// A reload handle will be returned so that we can change the log level at
|
||||||
|
/// runtime.
|
||||||
|
pub fn install_tracing(filter_directives: &str) -> Result<Handle<EnvFilter, Registry>> {
|
||||||
|
let env_filter = EnvFilter::try_new(filter_directives)
|
||||||
|
.or_else(|_| EnvFilter::try_from_default_env())
|
||||||
|
.or_else(|_| EnvFilter::try_new(DEFAULT_LOG_LEVEL))?;
|
||||||
|
|
||||||
|
let fmt_layer = fmt::layer()
|
||||||
|
.with_target(false)
|
||||||
|
.with_span_events(FmtSpan::NEW | FmtSpan::CLOSE)
|
||||||
|
.without_time();
|
||||||
|
|
||||||
|
let (filter, reload_handle) = Layer::new(env_filter);
|
||||||
|
|
||||||
|
registry().with(filter).with(fmt_layer).init();
|
||||||
|
|
||||||
|
Ok(reload_handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update the tracing logger with new `filter_directives`.
|
||||||
|
pub fn update_tracing(reload_handle: &Handle<EnvFilter, Registry>, filter_directives: &str) -> Result<()> {
|
||||||
|
let new = EnvFilter::try_new(filter_directives)
|
||||||
|
.or_else(|_| EnvFilter::try_from_default_env())
|
||||||
|
.or_else(|_| EnvFilter::try_new(DEFAULT_LOG_LEVEL))?;
|
||||||
|
reload_handle.modify(|old| *old = new)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set up the error handler crate
|
||||||
|
pub fn install_color_eyre() -> Result<()> {
|
||||||
|
color_eyre::config::HookBuilder::new()
|
||||||
|
// Don't display the backtrace reminder by default:
|
||||||
|
// Backtrace omitted. Run with RUST_BACKTRACE=1 environment variable to display it.
|
||||||
|
// Run with RUST_BACKTRACE=full to include source snippets.
|
||||||
|
.display_env_section(false)
|
||||||
|
// Display location information by default:
|
||||||
|
// Location:
|
||||||
|
// src/steps.rs:92
|
||||||
|
.display_location_section(true)
|
||||||
|
.install()
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user