Compare commits
282 Commits
v16.0.0
...
renovate/i
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a9c8f51153 | ||
|
|
84a50afa83 | ||
|
|
b13c1bd2d7 | ||
|
|
7749f41d56 | ||
|
|
593a2a33d9 | ||
|
|
4f693aeaf3 | ||
|
|
c3d34184d0 | ||
|
|
4aa224de87 | ||
|
|
320b13c06b | ||
|
|
907d778c55 | ||
|
|
f3fccb86c0 | ||
|
|
bb4afb71e9 | ||
|
|
ec8d30f634 | ||
|
|
50d318641a | ||
|
|
c5267f6087 | ||
|
|
d80e8f64d1 | ||
|
|
c6f2e0cc44 | ||
|
|
99c3e8af26 | ||
|
|
30d3537c0e | ||
|
|
90cb16e3d0 | ||
|
|
5192a0f1dc | ||
|
|
bec7edf1fc | ||
|
|
051784ac0d | ||
|
|
17d715479a | ||
|
|
39a90f5ebe | ||
|
|
80c4bd5065 | ||
|
|
222d800a32 | ||
|
|
b29699fc55 | ||
|
|
fc0e5461eb | ||
|
|
75da4a709c | ||
|
|
02e388122b | ||
|
|
02fe1087de | ||
|
|
34b7943fd1 | ||
|
|
b61886f0f9 | ||
|
|
d9a8ecfd33 | ||
|
|
6c68bfaf64 | ||
|
|
5866a0570f | ||
|
|
28f5754efd | ||
|
|
ea1b286c98 | ||
|
|
22ab77de6d | ||
|
|
410bd61c75 | ||
|
|
07b422915c | ||
|
|
5b9d387ef3 | ||
|
|
5b5dd27834 | ||
|
|
79f65981a5 | ||
|
|
a52c775247 | ||
|
|
549111db3a | ||
|
|
1572974ec4 | ||
|
|
8387468607 | ||
|
|
94979d6b7a | ||
|
|
6652a2aa90 | ||
|
|
f943b220d9 | ||
|
|
0fc7016d68 | ||
|
|
9e9e6c9d55 | ||
|
|
bafa15c8f1 | ||
|
|
f669de8272 | ||
|
|
ff26835406 | ||
|
|
99892359c7 | ||
|
|
8fc25d7fd4 | ||
|
|
942bfaa708 | ||
|
|
a1fd324e82 | ||
|
|
62bf2c6e90 | ||
|
|
9f1e0c8eef | ||
|
|
027de7c865 | ||
|
|
266adabd13 | ||
|
|
7d2e43f83b | ||
|
|
294a90a7c3 | ||
|
|
af5def1551 | ||
|
|
8c63ee6a18 | ||
|
|
ee8ae8623d | ||
|
|
222e6b55c0 | ||
|
|
2050a80665 | ||
|
|
e01be14041 | ||
|
|
dd6bc580fa | ||
|
|
22ef36a185 | ||
|
|
b57ceccbe1 | ||
|
|
e9d430a4e4 | ||
|
|
8a247fba95 | ||
|
|
5b6c31bd89 | ||
|
|
c316e2af69 | ||
|
|
7270aa96f0 | ||
|
|
dd823eb489 | ||
|
|
cb29305385 | ||
|
|
2dfa37dd0c | ||
|
|
b3d3284f18 | ||
|
|
73e3e133c6 | ||
|
|
9828af9f03 | ||
|
|
48aa6b5ac5 | ||
|
|
e5b3ed1461 | ||
|
|
5fad9f0ec6 | ||
|
|
f4a5507716 | ||
|
|
2ea9d1d6fd | ||
|
|
e393b1f90b | ||
|
|
2259e81bb0 | ||
|
|
65a30292a3 | ||
|
|
494eef3472 | ||
|
|
bee1e865b2 | ||
|
|
c92b049bbc | ||
|
|
c9985480fe | ||
|
|
e1b5b76d8e | ||
|
|
762a74f5f9 | ||
|
|
a7a2d8493e | ||
|
|
bc6538d209 | ||
|
|
93d841310e | ||
|
|
2ac679f17e | ||
|
|
9a3ef463f9 | ||
|
|
affc5fcd75 | ||
|
|
96bff5e974 | ||
|
|
e828e14fa5 | ||
|
|
4c6dc8ff82 | ||
|
|
75bd7c90d3 | ||
|
|
2aa3d94a98 | ||
|
|
f4ac809dff | ||
|
|
0dee534f84 | ||
|
|
ad9f2c2ccb | ||
|
|
a886d20a7b | ||
|
|
791993795a | ||
|
|
a2afdb821f | ||
|
|
47b51a8be0 | ||
|
|
7c7e7c3ce4 | ||
|
|
fec08a5ad1 | ||
|
|
3961ef61c8 | ||
|
|
84692da9a2 | ||
|
|
fb7ba52e39 | ||
|
|
898823abb2 | ||
|
|
ccefd0a43a | ||
|
|
98f0be61ed | ||
|
|
6bb1d54cb0 | ||
|
|
69a76e32b7 | ||
|
|
99d989d486 | ||
|
|
456d62224e | ||
|
|
b662fae11e | ||
|
|
0926bd2f6c | ||
|
|
3fb473ae95 | ||
|
|
603ed18a4c | ||
|
|
0307fdd296 | ||
|
|
c10dcdbfdb | ||
|
|
31d8e579c6 | ||
|
|
7b3fec0349 | ||
|
|
e32a58f6ff | ||
|
|
36cd726676 | ||
|
|
8dc08de628 | ||
|
|
0361954919 | ||
|
|
29a62575f4 | ||
|
|
547a6df2ae | ||
|
|
53d08cdf28 | ||
|
|
a033152c60 | ||
|
|
9472aaca7a | ||
|
|
6254b99e02 | ||
|
|
3d17bdb747 | ||
|
|
d2eeeb9129 | ||
|
|
1d626e0add | ||
|
|
9f4cb8c1b6 | ||
|
|
91fc5e3902 | ||
|
|
9048cd8f47 | ||
|
|
4f5e8a8836 | ||
|
|
29b05fa50f | ||
|
|
dbe1a5c988 | ||
|
|
b6c1290934 | ||
|
|
306ff3c7c5 | ||
|
|
0e43e0d7fc | ||
|
|
4da696321a | ||
|
|
a95dd1e037 | ||
|
|
012a6bbde3 | ||
|
|
32197f79f3 | ||
|
|
257d202646 | ||
|
|
c00365c19d | ||
|
|
3f9fe845e5 | ||
|
|
b166aae835 | ||
|
|
31f0097862 | ||
|
|
f78514dbd8 | ||
|
|
9fc5fe9798 | ||
|
|
6d14ac1693 | ||
|
|
b8ab573c00 | ||
|
|
75ac6808a1 | ||
|
|
6719ff93d8 | ||
|
|
6b8327faad | ||
|
|
85c8bd2277 | ||
|
|
23fff2a09f | ||
|
|
689db93c99 | ||
|
|
1114556661 | ||
|
|
f8c910a3c2 | ||
|
|
f18ae089ff | ||
|
|
4a64992054 | ||
|
|
9fefb47242 | ||
|
|
fc5cc3c43b | ||
|
|
27464b795e | ||
|
|
845558c1da | ||
|
|
31fe5aa452 | ||
|
|
b354e07ef3 | ||
|
|
50a74dac4b | ||
|
|
7558bbfe9b | ||
|
|
7518676ac9 | ||
|
|
b7b665ff48 | ||
|
|
1be941e815 | ||
|
|
d1b7eba44e | ||
|
|
38e2d5663a | ||
|
|
3db95a3e67 | ||
|
|
ef0a0d69bb | ||
|
|
4b3a3e74f8 | ||
|
|
2c4751c7b2 | ||
|
|
30941ed26d | ||
|
|
c7163b63db | ||
|
|
6e6b3dcbfe | ||
|
|
1d136a6635 | ||
|
|
0ee67d78ef | ||
|
|
7356b920d4 | ||
|
|
ce8a325c1f | ||
|
|
a2f57e4769 | ||
|
|
751f41bc5e | ||
|
|
fd406f0f82 | ||
|
|
801dddacd4 | ||
|
|
397a537eef | ||
|
|
0423c836eb | ||
|
|
3250337e70 | ||
|
|
9dcd7fffe2 | ||
|
|
30b727b138 | ||
|
|
b86d6981ab | ||
|
|
2bf6a2b100 | ||
|
|
3dc8d31d57 | ||
|
|
b308fb92c0 | ||
|
|
bc9746455e | ||
|
|
109a9c76e3 | ||
|
|
4488f3d5d3 | ||
|
|
5a7958d20e | ||
|
|
481a942b76 | ||
|
|
a601d8429d | ||
|
|
a4a2d52a6d | ||
|
|
47fa3ba7de | ||
|
|
e6bb6709b3 | ||
|
|
c421742c4f | ||
|
|
1312cc8f6e | ||
|
|
ed37763d30 | ||
|
|
583bbf65e2 | ||
|
|
5770a5caa7 | ||
|
|
722903fec3 | ||
|
|
30f1c3c1b4 | ||
|
|
ef7d146282 | ||
|
|
20667a23d3 | ||
|
|
26f05827ae | ||
|
|
b1ffe7d553 | ||
|
|
368a060529 | ||
|
|
b40bffb1f2 | ||
|
|
488ae149f7 | ||
|
|
fa3e4726b7 | ||
|
|
66a12cc8bf | ||
|
|
3e0c21e981 | ||
|
|
da270ae7d9 | ||
|
|
4624f11ba5 | ||
|
|
224bb96a98 | ||
|
|
9a6fe8eea9 | ||
|
|
aebc035ec0 | ||
|
|
bd348c328e | ||
|
|
c5f2d7b473 | ||
|
|
dc9d8d55f2 | ||
|
|
b172ba7f03 | ||
|
|
8227890808 | ||
|
|
a0963fe3fc | ||
|
|
4df30c2587 | ||
|
|
305a5fbcae | ||
|
|
4f4dcbb643 | ||
|
|
202897ba35 | ||
|
|
444689c899 | ||
|
|
98ec13f8db | ||
|
|
39f76a3a71 | ||
|
|
f181a795a6 | ||
|
|
ea2f3e07e9 | ||
|
|
8aad6eae0d | ||
|
|
e86e5fe3e7 | ||
|
|
2c2569c4f8 | ||
|
|
9ffdc9649e | ||
|
|
a5d4f2eec9 | ||
|
|
a5df40e01d | ||
|
|
0573fc97c6 | ||
|
|
1ae95f41a1 | ||
|
|
8a7af2e14d | ||
|
|
c36da89933 | ||
|
|
bbb84c2ee7 | ||
|
|
36fd4b13c0 | ||
|
|
49327000fc | ||
|
|
9c25cd7426 | ||
|
|
9767e4169c |
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
2
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -53,7 +53,7 @@ Execute the erroneous command directly to see if the problem persists
|
|||||||
- [ ] Yes
|
- [ ] Yes
|
||||||
- [ ] No
|
- [ ] No
|
||||||
|
|
||||||
If yes, does the issue still occur when you run topgrade directlly in your
|
If yes, does the issue still occur when you run topgrade directly in your
|
||||||
remote host
|
remote host
|
||||||
|
|
||||||
- [ ] Yes
|
- [ ] Yes
|
||||||
|
|||||||
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -18,8 +18,10 @@ assignees: ''
|
|||||||
option to skip this step?
|
option to skip this step?
|
||||||
|
|
||||||
## I want to suggest some general feature
|
## I want to suggest some general feature
|
||||||
|
|
||||||
Topgrade should...
|
Topgrade should...
|
||||||
|
|
||||||
## More information
|
## More information
|
||||||
|
|
||||||
<!-- Assuming that someone else implements the feature,
|
<!-- Assuming that someone else implements the feature,
|
||||||
please state if you know how to test it from a side branch of Topgrade. -->
|
please state if you know how to test it from a side branch of Topgrade. -->
|
||||||
|
|||||||
4
.github/PULL_REQUEST_TEMPLATE.md
vendored
4
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,11 +1,11 @@
|
|||||||
## What does this PR do
|
## What does this PR do
|
||||||
|
|
||||||
|
|
||||||
## Standards checklist
|
## Standards checklist
|
||||||
|
|
||||||
- [ ] The PR title is descriptive.
|
- [ ] The PR title is descriptive
|
||||||
- [ ] I have read `CONTRIBUTING.md`
|
- [ ] I have read `CONTRIBUTING.md`
|
||||||
- [ ] *Optional:* I have tested the code myself
|
- [ ] *Optional:* I have tested the code myself
|
||||||
|
- [ ] If this PR introduces new user-facing messages they are translated
|
||||||
|
|
||||||
## For new steps
|
## For new steps
|
||||||
|
|
||||||
|
|||||||
10
.github/dependabot.yml
vendored
10
.github/dependabot.yml
vendored
@@ -1,10 +0,0 @@
|
|||||||
# Set update schedule for GitHub Actions
|
|
||||||
|
|
||||||
version: 2
|
|
||||||
updates:
|
|
||||||
|
|
||||||
- package-ecosystem: "github-actions"
|
|
||||||
directory: "/"
|
|
||||||
schedule:
|
|
||||||
# Check for updates to GitHub Actions every week
|
|
||||||
interval: "weekly"
|
|
||||||
@@ -7,13 +7,19 @@ env:
|
|||||||
CARGO_TERM_COLOR: always
|
CARGO_TERM_COLOR: always
|
||||||
|
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
TestConfig:
|
TestConfig:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
- run: |
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
|
- run: |
|
||||||
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;
|
||||||
|
|||||||
7
.github/workflows/check_i18n.yml
vendored
7
.github/workflows/check_i18n.yml
vendored
@@ -6,12 +6,17 @@ on:
|
|||||||
|
|
||||||
name: Check i18n
|
name: Check i18n
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
check_locale:
|
check_locale:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Install checker
|
- name: Install checker
|
||||||
# Build it with the dev profile as this is faster and the checker still works
|
# Build it with the dev profile as this is faster and the checker still works
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
lint:
|
lint:
|
||||||
name: DevSkim
|
name: DevSkim
|
||||||
@@ -21,12 +24,14 @@ jobs:
|
|||||||
security-events: write
|
security-events: write
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Run DevSkim scanner
|
- name: Run DevSkim scanner
|
||||||
uses: microsoft/DevSkim-Action@v1
|
uses: microsoft/DevSkim-Action@4b5047945a44163b94642a1cecc0d93a3f428cc6 # v1.0.16
|
||||||
|
|
||||||
- name: Upload DevSkim scan results to GitHub Security tab
|
- name: Upload DevSkim scan results to GitHub Security tab
|
||||||
uses: github/codeql-action/upload-sarif@v3
|
uses: github/codeql-action/upload-sarif@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2
|
||||||
with:
|
with:
|
||||||
sarif_file: devskim-results.sarif
|
sarif_file: devskim-results.sarif
|
||||||
|
|||||||
27
.github/workflows/check_semver.yml
vendored
27
.github/workflows/check_semver.yml
vendored
@@ -1,27 +0,0 @@
|
|||||||
on:
|
|
||||||
release:
|
|
||||||
types: [published, edited]
|
|
||||||
|
|
||||||
name: Check SemVer compliance
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
prepare:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: actions-rs/toolchain@v1
|
|
||||||
with:
|
|
||||||
toolchain: nightly-2022-08-03
|
|
||||||
override: true
|
|
||||||
components: rustfmt, clippy
|
|
||||||
|
|
||||||
semver:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions-rs/cargo@v1
|
|
||||||
with:
|
|
||||||
command: install
|
|
||||||
args: --git https://github.com/rust-lang/rust-semverver
|
|
||||||
- run: eval "current_version=$(grep -e '^version = .*$' Cargo.toml | cut -d ' ' -f 3)"
|
|
||||||
- run: cargo semver | tee semver_out
|
|
||||||
- run: (head -n 1 semver_out | grep "\-> $current_version") || (echo "versioning mismatch" && return 1)
|
|
||||||
96
.github/workflows/ci.yml
vendored
96
.github/workflows/ci.yml
vendored
@@ -10,22 +10,90 @@ env:
|
|||||||
CROSS_VER: '0.2.5'
|
CROSS_VER: '0.2.5'
|
||||||
CARGO_NET_RETRY: 3
|
CARGO_NET_RETRY: 3
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
fmt:
|
fmt:
|
||||||
name: Rustfmt
|
name: Rustfmt
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Run cargo fmt
|
- name: Run cargo fmt
|
||||||
env:
|
env:
|
||||||
TERM: xterm-256color
|
TERM: xterm-256color
|
||||||
run: |
|
run: |
|
||||||
|
rustup component add rustfmt
|
||||||
cargo fmt --all -- --check
|
cargo fmt --all -- --check
|
||||||
|
|
||||||
|
custom-checks:
|
||||||
|
name: Custom checks
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
|
- name: Check if `Step` enum is sorted
|
||||||
|
run: |
|
||||||
|
ENUM_NAME="Step"
|
||||||
|
FILE="src/step.rs"
|
||||||
|
awk "/enum $ENUM_NAME/,/}/" "$FILE" | \
|
||||||
|
grep -E '^\s*[A-Za-z_][A-Za-z0-9_]*\s*,?$' | \
|
||||||
|
sed 's/[, ]//g' > original.txt
|
||||||
|
sort original.txt > sorted.txt
|
||||||
|
diff original.txt sorted.txt
|
||||||
|
|
||||||
|
- name: Check if `Step::run()`'s match is sorted
|
||||||
|
run: |
|
||||||
|
FILE="src/step.rs"
|
||||||
|
awk '/[[:alpha:]] =>/{print $1}' $FILE > original.txt
|
||||||
|
sort original.txt > sorted.txt
|
||||||
|
diff original.txt sorted.txt
|
||||||
|
|
||||||
|
- name: Check if `default_steps` contains every step
|
||||||
|
run: |
|
||||||
|
# Extract all variants from enum Step
|
||||||
|
all_variants=$(sed -n '/^pub enum Step {/,/^}/p' src/step.rs | grep -Po '^\s*\K[A-Z][A-Za-z0-9_]*' | sort)
|
||||||
|
|
||||||
|
# Extract variants used inside default_steps
|
||||||
|
used_variants=$(sed -n '/^pub(crate) fn default_steps()/,/^}/p' src/step.rs | \
|
||||||
|
grep -Po '\b[A-Z][A-Za-z0-9_]*\b' | \
|
||||||
|
grep -Fx -f <(echo "$all_variants") | \
|
||||||
|
sort)
|
||||||
|
|
||||||
|
# Check for missing variants
|
||||||
|
missing=$(comm -23 <(echo "$all_variants") <(echo "$used_variants"))
|
||||||
|
if [[ -z "$missing" ]]; then
|
||||||
|
echo "All variants are used."
|
||||||
|
else
|
||||||
|
echo "Missing variants:"
|
||||||
|
echo "$missing"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check for duplicates
|
||||||
|
duplicates=$(echo "$used_variants" | uniq -c | awk '$1 > 1 {print $2}')
|
||||||
|
if [[ -z "$duplicates" ]]; then
|
||||||
|
echo "No duplicates found."
|
||||||
|
else
|
||||||
|
echo "Duplicates found:"
|
||||||
|
echo "$duplicates"
|
||||||
|
# We allow duplicates, but lets keep this check for potential future usefulness
|
||||||
|
# exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
main:
|
main:
|
||||||
needs: fmt
|
needs: [ fmt, custom-checks ]
|
||||||
name: ${{ matrix.target_name }} (check, clippy)
|
name: ${{ matrix.target_name }} (check, clippy)
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
@@ -48,7 +116,7 @@ jobs:
|
|||||||
|
|
||||||
- target: x86_64-apple-darwin
|
- target: x86_64-apple-darwin
|
||||||
target_name: macOS-x86_64
|
target_name: macOS-x86_64
|
||||||
os: macos-13
|
os: macos-15-intel
|
||||||
|
|
||||||
- target: aarch64-apple-darwin
|
- target: aarch64-apple-darwin
|
||||||
target_name: macOS-aarch64
|
target_name: macOS-aarch64
|
||||||
@@ -62,26 +130,36 @@ jobs:
|
|||||||
- target: x86_64-pc-windows-msvc
|
- target: x86_64-pc-windows-msvc
|
||||||
target_name: Windows
|
target_name: Windows
|
||||||
os: windows-latest
|
os: windows-latest
|
||||||
|
env:
|
||||||
|
cargo_cmd: ${{ matrix.use_cross == true && 'cross' || 'cargo' }}
|
||||||
|
matrix_target: ${{ matrix.target }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Setup Rust Cache
|
- name: Setup Rust Cache
|
||||||
uses: Swatinem/rust-cache@v2
|
uses: Swatinem/rust-cache@f13886b937689c021905a6b90929199931d60db1 # v2.8.1
|
||||||
with:
|
with:
|
||||||
prefix-key: ${{ matrix.target }}
|
prefix-key: ${{ matrix.target }}
|
||||||
|
|
||||||
- name: Setup cross
|
- name: Setup cross
|
||||||
if: matrix.use_cross == true
|
if: matrix.use_cross == true
|
||||||
run: curl -fL --retry 3 https://github.com/cross-rs/cross/releases/download/v${{ env.CROSS_VER }}/cross-x86_64-unknown-linux-musl.tar.gz | tar vxz -C /usr/local/bin
|
run: |
|
||||||
|
curl -fL --retry 3 "https://github.com/cross-rs/cross/releases/download/v${CROSS_VER}/cross-x86_64-unknown-linux-musl.tar.gz" | tar vxz -C /usr/local/bin
|
||||||
|
|
||||||
- name: Run cargo/cross check
|
- name: Run cargo/cross check
|
||||||
run: ${{ matrix.use_cross == true && 'cross' || 'cargo' }} check --locked --target ${{ matrix.target }}
|
run: |
|
||||||
|
"${cargo_cmd}" check --locked --target "${matrix_target}"
|
||||||
|
|
||||||
- name: Run cargo/cross clippy
|
- name: Run cargo/cross clippy
|
||||||
run: ${{ matrix.use_cross == true && 'cross' || 'cargo' }} clippy --locked --target ${{ matrix.target }} --all-features -- -D warnings
|
run: |
|
||||||
|
rustup component add clippy
|
||||||
|
"${cargo_cmd}" clippy --locked --target "${matrix_target}" --all-features -- -D warnings
|
||||||
|
|
||||||
- name: Run cargo test
|
- name: Run cargo test
|
||||||
# ONLY run test with cargo
|
# ONLY run test with cargo
|
||||||
if: matrix.use_cross == false
|
if: matrix.use_cross == false
|
||||||
run: cargo test --locked --target ${{ matrix.target }}
|
run: |
|
||||||
|
cargo test --locked --target "${matrix_target}"
|
||||||
|
|||||||
251
.github/workflows/create_release_assets.yml
vendored
251
.github/workflows/create_release_assets.yml
vendored
@@ -1,26 +1,50 @@
|
|||||||
name: Publish release files for CD native environments
|
name: Publish release files for CD native and non-cd-native environments
|
||||||
|
|
||||||
on:
|
on:
|
||||||
# workflow_run:
|
repository_dispatch:
|
||||||
# workflows: ["Check SemVer compliance"]
|
types: [ release-created ]
|
||||||
# types:
|
|
||||||
# - completed
|
permissions:
|
||||||
release:
|
# Write permissions to call the repository dispatch
|
||||||
types: [ created ]
|
contents: write
|
||||||
|
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
# Publish release files for CD native environments
|
||||||
|
native_build:
|
||||||
|
permissions:
|
||||||
|
# Use to sign the release artifacts
|
||||||
|
id-token: write
|
||||||
|
# Used to upload release artifacts
|
||||||
|
contents: write
|
||||||
|
# Used to generate artifact attestations
|
||||||
|
attestations: write
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
platform: [ ubuntu-latest, macos-latest, macos-13, windows-latest ]
|
# Use the Ubuntu 22.04 image to link with a low version of glibc
|
||||||
|
#
|
||||||
|
# https://github.com/topgrade-rs/topgrade/issues/1095
|
||||||
|
platform: [ ubuntu-22.04, macos-latest, macos-15-intel, windows-latest ]
|
||||||
runs-on: ${{ matrix.platform }}
|
runs-on: ${{ matrix.platform }}
|
||||||
|
env:
|
||||||
|
tag: ${{ github.event.client_payload.tag }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
|
- name: Install needed components
|
||||||
|
run: |
|
||||||
|
rustup component add rustfmt
|
||||||
|
rustup component add clippy
|
||||||
|
|
||||||
- name: Install cargo-deb
|
- name: Install cargo-deb
|
||||||
run: cargo install cargo-deb
|
run: cargo install cargo-deb
|
||||||
if: ${{ matrix.platform == 'ubuntu-latest' }}
|
if: ${{ startsWith(matrix.platform, 'ubuntu-') }}
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Check format
|
- name: Check format
|
||||||
@@ -35,6 +59,30 @@ jobs:
|
|||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: cargo test
|
run: cargo test
|
||||||
|
|
||||||
|
# Used `https://github.com/BurntSushi/ripgrep/blob/master/.github/workflows/release.yml`
|
||||||
|
# as a reference.
|
||||||
|
- name: Build debug binary to create release assets
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
cargo build --all-features
|
||||||
|
bin="target/debug/topgrade"
|
||||||
|
echo "BIN=$bin" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Create deployment directory
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
dir=deployment/deb
|
||||||
|
mkdir -p "$dir"
|
||||||
|
echo "DEPLOY_DIR=$dir" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Generate man page and shell completions
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
"$BIN" --gen-manpage > "$DEPLOY_DIR/topgrade.1"
|
||||||
|
"$BIN" --gen-completion bash > "$DEPLOY_DIR/topgrade.bash"
|
||||||
|
"$BIN" --gen-completion fish > "$DEPLOY_DIR/topgrade.fish"
|
||||||
|
"$BIN" --gen-completion zsh > "$DEPLOY_DIR/_topgrade"
|
||||||
|
|
||||||
- name: Build in Release profile with all features enabled
|
- name: Build in Release profile with all features enabled
|
||||||
run: cargo build --release --all-features
|
run: cargo build --release --all-features
|
||||||
|
|
||||||
@@ -42,7 +90,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
cargo install default-target
|
cargo install default-target
|
||||||
mkdir -p assets
|
mkdir -p assets
|
||||||
FILENAME=topgrade-${{github.event.release.tag_name}}-$(default-target)
|
FILENAME=topgrade-${tag}-$(default-target)
|
||||||
mv target/release/topgrade assets
|
mv target/release/topgrade assets
|
||||||
cd assets
|
cd assets
|
||||||
tar --format=ustar -czf $FILENAME.tar.gz topgrade
|
tar --format=ustar -czf $FILENAME.tar.gz topgrade
|
||||||
@@ -59,21 +107,21 @@ jobs:
|
|||||||
rm -rf target/release
|
rm -rf target/release
|
||||||
cargo build --release
|
cargo build --release
|
||||||
cargo deb --no-build --no-strip
|
cargo deb --no-build --no-strip
|
||||||
if: ${{ matrix.platform == 'ubuntu-latest' }}
|
if: ${{ startsWith(matrix.platform, 'ubuntu-') }}
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Move Debian-based system package
|
- name: Move Debian-based system package
|
||||||
run: |
|
run: |
|
||||||
mkdir -p assets
|
mkdir -p assets
|
||||||
mv target/debian/*.deb assets
|
mv target/debian/*.deb assets
|
||||||
if: ${{ matrix.platform == 'ubuntu-latest' }}
|
if: ${{ startsWith(matrix.platform, 'ubuntu-') }}
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Rename Release (Windows)
|
- name: Rename Release (Windows)
|
||||||
run: |
|
run: |
|
||||||
cargo install default-target
|
cargo install default-target
|
||||||
mkdir assets
|
mkdir assets
|
||||||
FILENAME=topgrade-${{github.event.release.tag_name}}-$(default-target)
|
FILENAME=topgrade-${tag}-$(default-target)
|
||||||
mv target/release/topgrade.exe assets/topgrade.exe
|
mv target/release/topgrade.exe assets/topgrade.exe
|
||||||
cd assets
|
cd assets
|
||||||
powershell Compress-Archive -Path * -Destination ${FILENAME}.zip
|
powershell Compress-Archive -Path * -Destination ${FILENAME}.zip
|
||||||
@@ -82,7 +130,174 @@ jobs:
|
|||||||
if: ${{ matrix.platform == 'windows-latest' }}
|
if: ${{ matrix.platform == 'windows-latest' }}
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Release
|
- name: Upload assets
|
||||||
uses: softprops/action-gh-release@v2
|
run: |
|
||||||
|
gh release upload "${tag}" assets/*
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Generate artifact attestations
|
||||||
|
uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3.0.0
|
||||||
with:
|
with:
|
||||||
files: assets/*
|
subject-path: assets/*
|
||||||
|
|
||||||
|
# Publish release files for non-CD-native environments
|
||||||
|
cross_build:
|
||||||
|
permissions:
|
||||||
|
# Use to sign the release artifacts
|
||||||
|
id-token: write
|
||||||
|
# Used to upload release artifacts
|
||||||
|
contents: write
|
||||||
|
# Used to generate artifact attestations
|
||||||
|
attestations: write
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
target:
|
||||||
|
[
|
||||||
|
"aarch64-unknown-linux-gnu",
|
||||||
|
"armv7-unknown-linux-gnueabihf",
|
||||||
|
"x86_64-unknown-linux-musl",
|
||||||
|
"aarch64-unknown-linux-musl",
|
||||||
|
"x86_64-unknown-freebsd",
|
||||||
|
]
|
||||||
|
# Run this one on an older version as well, to limit glibc to 2.34 instead of 2.39.
|
||||||
|
# Even though this is cross-compiled, it links to the libc6-<arch>-cross installed on the host
|
||||||
|
# (see the apt-get install calls below)
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
env:
|
||||||
|
matrix_target: ${{ matrix.target }}
|
||||||
|
tag: ${{ github.event.client_payload.tag }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
|
- name: Install needed components
|
||||||
|
run: |
|
||||||
|
rustup component add rustfmt
|
||||||
|
rustup component add clippy
|
||||||
|
|
||||||
|
- name: Install cargo-deb cross compilation dependencies
|
||||||
|
run: sudo apt-get install libc6-arm64-cross libgcc-s1-arm64-cross
|
||||||
|
if: ${{ matrix.target == 'aarch64-unknown-linux-gnu' }}
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Install cargo-deb cross compilation dependencies for armv7
|
||||||
|
run: sudo apt-get install libc6-armhf-cross libgcc-s1-armhf-cross
|
||||||
|
if: ${{ matrix.target == 'armv7-unknown-linux-gnueabihf' }}
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Install cargo-deb
|
||||||
|
run: cargo install cargo-deb
|
||||||
|
if: ${{ matrix.target == 'aarch64-unknown-linux-gnu' || matrix.target == 'armv7-unknown-linux-gnueabihf' }}
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: install targets
|
||||||
|
run: rustup target add "${matrix_target}"
|
||||||
|
|
||||||
|
- name: install cross
|
||||||
|
# Install from source to fix `ld: cannot find -lgeom` for freebsd build
|
||||||
|
run: cargo +stable install --git https://github.com/cross-rs/cross cross
|
||||||
|
|
||||||
|
- name: Run clippy
|
||||||
|
run: cross clippy --all-targets --locked --target "${matrix_target}" -- -D warnings
|
||||||
|
|
||||||
|
- name: Run clippy (All features)
|
||||||
|
run: cross clippy --locked --all-features --target "${matrix_target}" -- -D warnings
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: cross test --target "${matrix_target}"
|
||||||
|
# Running tests on FreeBSD is impossible; see https://github.com/cross-rs/cross/wiki/FAQ#running-bsd-tests
|
||||||
|
# Not that this is *NOT* the same as the original issue with `ld: cannot find -lgeom`, but a new issue:
|
||||||
|
# error: test failed, to rerun pass `--lib`
|
||||||
|
# Caused by:
|
||||||
|
# could not execute process `/target/x86_64-unknown-freebsd/debug/deps/topgrade-9b1670d87ca863dd` (never executed)
|
||||||
|
# Caused by:
|
||||||
|
# No such file or directory (os error 2)
|
||||||
|
# TODO: I have not tested this in GHA yet, only locally
|
||||||
|
if: ${{ matrix.target != 'x86_64-unknown-freebsd' }}
|
||||||
|
|
||||||
|
# Used `https://github.com/BurntSushi/ripgrep/blob/master/.github/workflows/release.yml`
|
||||||
|
# as a reference.
|
||||||
|
- name: Build debug binary to create release assets
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
# This build is not using the target arch since this binary is only needed in CI. It needs
|
||||||
|
# to be the compiled for the runner since it has the run the binary to generate completion
|
||||||
|
# scripts.
|
||||||
|
cargo build --all-features
|
||||||
|
bin="target/debug/topgrade"
|
||||||
|
echo "BIN=$bin" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Create deployment directory
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
dir=deployment/deb
|
||||||
|
mkdir -p "$dir"
|
||||||
|
echo "DEPLOY_DIR=$dir" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Generate man page and shell completions
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
"$BIN" --gen-manpage > "$DEPLOY_DIR/topgrade.1"
|
||||||
|
"$BIN" --gen-completion bash > "$DEPLOY_DIR/topgrade.bash"
|
||||||
|
"$BIN" --gen-completion fish > "$DEPLOY_DIR/topgrade.fish"
|
||||||
|
"$BIN" --gen-completion zsh > "$DEPLOY_DIR/_topgrade"
|
||||||
|
|
||||||
|
- name: Build in Release profile with all features enabled
|
||||||
|
run: cross build --release --all-features --target "${matrix_target}"
|
||||||
|
|
||||||
|
- name: Rename Release
|
||||||
|
run: |
|
||||||
|
mkdir -p assets
|
||||||
|
FILENAME=topgrade-${tag}-${matrix_target}
|
||||||
|
mv "target/${matrix_target}/release/topgrade" assets
|
||||||
|
cd assets
|
||||||
|
tar --format=ustar -czf "$FILENAME.tar.gz" topgrade
|
||||||
|
rm topgrade
|
||||||
|
ls .
|
||||||
|
|
||||||
|
- name: Build Debian-based system package without autoupdate feature
|
||||||
|
# First remove the binary built by previous steps
|
||||||
|
# because we don't want the auto-update feature,
|
||||||
|
# then build the new binary without auto-updating.
|
||||||
|
run: |
|
||||||
|
rm -rf "target/${matrix_target}"
|
||||||
|
cross build --release --target "${matrix_target}"
|
||||||
|
cargo deb --target="${matrix_target}" --no-build --no-strip
|
||||||
|
if: ${{ matrix.target == 'aarch64-unknown-linux-gnu' || matrix.target == 'armv7-unknown-linux-gnueabihf' }}
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Move Debian-based system package
|
||||||
|
run: |
|
||||||
|
mkdir -p assets
|
||||||
|
mv target/"${matrix_target}"/debian/*.deb assets
|
||||||
|
if: ${{ matrix.target == 'aarch64-unknown-linux-gnu' || matrix.target == 'armv7-unknown-linux-gnueabihf' }}
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
|
||||||
|
- name: Upload assets
|
||||||
|
run:
|
||||||
|
gh release upload "${tag}" assets/*
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Generate artifact attestations
|
||||||
|
uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3.0.0
|
||||||
|
with:
|
||||||
|
subject-path: assets/*
|
||||||
|
|
||||||
|
triggers:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [ native_build, cross_build ]
|
||||||
|
env:
|
||||||
|
tag: ${{ github.event.client_payload.tag }}
|
||||||
|
steps:
|
||||||
|
- name: Trigger workflows
|
||||||
|
run: |
|
||||||
|
gh api "repos/${GITHUB_REPOSITORY}/dispatches" \
|
||||||
|
-f "event_type=release-assets-built" \
|
||||||
|
-F "client_payload[tag]=${tag}"
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
@@ -1,91 +0,0 @@
|
|||||||
name: Publish release files for non-cd-native environments
|
|
||||||
|
|
||||||
on:
|
|
||||||
# workflow_run:
|
|
||||||
# workflows: ["Check SemVer compliance"]
|
|
||||||
# types:
|
|
||||||
# - completed
|
|
||||||
release:
|
|
||||||
types: [ created ]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
target: [
|
|
||||||
"aarch64-unknown-linux-gnu",
|
|
||||||
"armv7-unknown-linux-gnueabihf",
|
|
||||||
"x86_64-unknown-linux-musl",
|
|
||||||
"aarch64-unknown-linux-musl",
|
|
||||||
"x86_64-unknown-freebsd",
|
|
||||||
]
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Install cargo-deb cross compilation dependencies
|
|
||||||
run: sudo apt-get install libc6-arm64-cross libgcc-s1-arm64-cross
|
|
||||||
if: ${{ matrix.target == 'aarch64-unknown-linux-gnu' }}
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Install cargo-deb
|
|
||||||
run: cargo install cargo-deb
|
|
||||||
if: ${{ matrix.target == 'aarch64-unknown-linux-gnu' }}
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: install targets
|
|
||||||
run: rustup target add ${{ matrix.target }}
|
|
||||||
|
|
||||||
- name: install cross
|
|
||||||
uses: taiki-e/install-action@v2
|
|
||||||
with:
|
|
||||||
tool: cross@0.2.5
|
|
||||||
|
|
||||||
- name: Check format
|
|
||||||
run: cross fmt --all -- --check
|
|
||||||
|
|
||||||
- name: Run clippy
|
|
||||||
run: cross clippy --all-targets --locked --target ${{matrix.target}} -- -D warnings
|
|
||||||
|
|
||||||
- name: Run clippy (All features)
|
|
||||||
run: cross clippy --locked --all-features --target ${{matrix.target}} -- -D warnings
|
|
||||||
|
|
||||||
- name: Run tests
|
|
||||||
run: cross test --target ${{matrix.target}}
|
|
||||||
|
|
||||||
- name: Build in Release profile with all features enabled
|
|
||||||
run: cross build --release --all-features --target ${{matrix.target}}
|
|
||||||
|
|
||||||
- name: Rename Release
|
|
||||||
run: |
|
|
||||||
mkdir -p assets
|
|
||||||
FILENAME=topgrade-${{github.event.release.tag_name}}-${{matrix.target}}
|
|
||||||
mv target/${{matrix.target}}/release/topgrade assets
|
|
||||||
cd assets
|
|
||||||
tar --format=ustar -czf $FILENAME.tar.gz topgrade
|
|
||||||
rm topgrade
|
|
||||||
ls .
|
|
||||||
|
|
||||||
- name: Build Debian-based system package without autoupdate feature
|
|
||||||
# First remove the binary built by previous steps
|
|
||||||
# because we don't want the auto-update feature,
|
|
||||||
# then build the new binary without auto-updating.
|
|
||||||
run: |
|
|
||||||
rm -rf target/${{matrix.target}}
|
|
||||||
cross build --release --target ${{matrix.target}}
|
|
||||||
cargo deb --target=${{matrix.target}} --no-build --no-strip
|
|
||||||
if: ${{ matrix.target == 'aarch64-unknown-linux-gnu' }}
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Move Debian-based system package
|
|
||||||
run: |
|
|
||||||
mkdir -p assets
|
|
||||||
mv target/${{matrix.target}}/debian/*.deb assets
|
|
||||||
if: ${{ matrix.target == 'aarch64-unknown-linux-gnu' }}
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Release
|
|
||||||
uses: softprops/action-gh-release@v2
|
|
||||||
with:
|
|
||||||
files: assets/*
|
|
||||||
25
.github/workflows/dependency-review.yml
vendored
Normal file
25
.github/workflows/dependency-review.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Dependency Review Action
|
||||||
|
#
|
||||||
|
# This Action will scan dependency manifest files that change as part of a Pull Request,
|
||||||
|
# surfacing known-vulnerable versions of the packages declared or updated in the PR.
|
||||||
|
# Once installed, if the workflow run is marked as required,
|
||||||
|
# PRs introducing known-vulnerable packages will be blocked from merging.
|
||||||
|
#
|
||||||
|
# Source repository: https://github.com/actions/dependency-review-action
|
||||||
|
name: 'Dependency Review'
|
||||||
|
on: [pull_request]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
dependency-review:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: 'Checkout Repository'
|
||||||
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
|
- name: 'Dependency Review'
|
||||||
|
uses: actions/dependency-review-action@3c4e3dcb1aa7874d2c16be7d79418e9b7efd6261 # v4.8.2
|
||||||
19
.github/workflows/lint_pr.yml
vendored
Normal file
19
.github/workflows/lint_pr.yml
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
name: 'Lint PR'
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request_target:
|
||||||
|
types:
|
||||||
|
- opened
|
||||||
|
- edited
|
||||||
|
- reopened
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
main:
|
||||||
|
name: Validate PR title
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
pull-requests: read
|
||||||
|
steps:
|
||||||
|
- uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
67
.github/workflows/release-plz.yml
vendored
Normal file
67
.github/workflows/release-plz.yml
vendored
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
name: Release-plz
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
|
||||||
|
# Release unpublished packages.
|
||||||
|
release-plz-release:
|
||||||
|
name: Release-plz release
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
environment: crates_io
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
id-token: write # For trusted publishing
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
persist-credentials: false
|
||||||
|
- name: Install Rust toolchain
|
||||||
|
uses: dtolnay/rust-toolchain@stable
|
||||||
|
- name: Run release-plz
|
||||||
|
id: release-plz
|
||||||
|
uses: release-plz/action@d529f731ae3e89610ada96eda34e5c6ba3b12214 # v0.5
|
||||||
|
with:
|
||||||
|
command: release
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Trigger workflows
|
||||||
|
if: steps.release-plz.outputs.releases_created == 'true'
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
tag: ${{ fromJSON(steps.release-plz.outputs.releases)[0].tag }}
|
||||||
|
run: |
|
||||||
|
gh api "repos/${GITHUB_REPOSITORY}/dispatches" \
|
||||||
|
-f "event_type=release-created" \
|
||||||
|
-F "client_payload[tag]=${tag}"
|
||||||
|
|
||||||
|
# Create a PR with the new versions and changelog, preparing the next release.
|
||||||
|
release-plz-pr:
|
||||||
|
name: Release-plz PR
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
pull-requests: write
|
||||||
|
concurrency:
|
||||||
|
group: release-plz-${{ github.ref }}
|
||||||
|
cancel-in-progress: false
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
persist-credentials: false
|
||||||
|
- name: Install Rust toolchain
|
||||||
|
uses: dtolnay/rust-toolchain@stable
|
||||||
|
- name: Run release-plz
|
||||||
|
uses: release-plz/action@d529f731ae3e89610ada96eda34e5c6ba3b12214 # v0.5
|
||||||
|
with:
|
||||||
|
command: release-pr
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
35
.github/workflows/release_to_aur.yml
vendored
35
.github/workflows/release_to_aur.yml
vendored
@@ -1,23 +1,38 @@
|
|||||||
name: Publish to AUR
|
name: Publish to AUR
|
||||||
|
|
||||||
on:
|
on:
|
||||||
# workflow_run:
|
repository_dispatch:
|
||||||
# workflows: ["Check SemVer compliance"]
|
types: [ release-assets-built ]
|
||||||
# types:
|
|
||||||
# - completed
|
permissions:
|
||||||
push:
|
contents: read
|
||||||
tags:
|
|
||||||
- "v*"
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
aur-publish:
|
aur-publish:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Publish AUR package
|
- name: Determine version
|
||||||
uses: aksh1618/update-aur-package@v1.0.5
|
id: determine_version
|
||||||
|
env:
|
||||||
|
tag: ${{ github.event.client_payload.tag }}
|
||||||
|
run: |
|
||||||
|
# tag should be something like "v16.0.4", remove the prefix v here
|
||||||
|
echo "version=${tag#v}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Publish source AUR package
|
||||||
|
uses: varabyte/update-aur-package@572e31b1972fa289a27b1926c06a489eb89c7fd7
|
||||||
with:
|
with:
|
||||||
tag_version_prefix: v
|
version: ${{ steps.determine_version.outputs.version }}
|
||||||
package_name: topgrade
|
package_name: topgrade
|
||||||
commit_username: "Thomas Schönauer"
|
commit_username: "Thomas Schönauer"
|
||||||
commit_email: t.schoenauer@hgs-wt.at
|
commit_email: t.schoenauer@hgs-wt.at
|
||||||
ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
|
ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
|
||||||
|
|
||||||
|
- name: Publish binary AUR package
|
||||||
|
uses: varabyte/update-aur-package@572e31b1972fa289a27b1926c06a489eb89c7fd7
|
||||||
|
with:
|
||||||
|
version: ${{ steps.determine_version.outputs.version }}
|
||||||
|
package_name: topgrade-bin
|
||||||
|
commit_username: "Thomas Schönauer"
|
||||||
|
commit_email: t.schoenauer@hgs-wt.at
|
||||||
|
ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
|
||||||
|
|||||||
29
.github/workflows/release_to_crates_io.yml
vendored
29
.github/workflows/release_to_crates_io.yml
vendored
@@ -1,29 +0,0 @@
|
|||||||
on:
|
|
||||||
# workflow_run:
|
|
||||||
# workflows: ["Check SemVer compliance"]
|
|
||||||
# types:
|
|
||||||
# - completed
|
|
||||||
release:
|
|
||||||
types: [published]
|
|
||||||
|
|
||||||
name: Publish to crates.io on release
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
prepare:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- uses: actions-rs/toolchain@v1
|
|
||||||
with:
|
|
||||||
toolchain: stable
|
|
||||||
override: true
|
|
||||||
|
|
||||||
publish:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: katyo/publish-crates@v2
|
|
||||||
with:
|
|
||||||
dry-run: true
|
|
||||||
check-repo: ${{ github.event_name == 'push' }}
|
|
||||||
registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
|
||||||
ignore-unpublished-changes: true
|
|
||||||
37
.github/workflows/release_to_homebrew.yml
vendored
37
.github/workflows/release_to_homebrew.yml
vendored
@@ -1,39 +1,22 @@
|
|||||||
name: Publish to Homebrew
|
name: Publish to Homebrew
|
||||||
|
|
||||||
on:
|
on:
|
||||||
# workflow_run:
|
repository_dispatch:
|
||||||
# workflows: ["Check SemVer compliance"]
|
types: [ release-created ]
|
||||||
# types:
|
|
||||||
# - completed
|
permissions:
|
||||||
workflow_dispatch:
|
contents: read
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- "v*"
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
homebrew-publish:
|
homebrew-publish:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Set up Homebrew
|
|
||||||
id: set-up-homebrew
|
|
||||||
uses: Homebrew/actions/setup-homebrew@master
|
|
||||||
- name: Cache Homebrew Bundler RubyGems
|
|
||||||
id: cache
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: ${{ steps.set-up-homebrew.outputs.gems-path }}
|
|
||||||
key: ${{ runner.os }}-rubygems-${{ steps.set-up-homebrew.outputs.gems-hash }}
|
|
||||||
restore-keys: ${{ runner.os }}-rubygems-
|
|
||||||
|
|
||||||
- name: Install Homebrew Bundler RubyGems
|
|
||||||
if: steps.cache.outputs.cache-hit != 'true'
|
|
||||||
run: brew install-bundler-gems
|
|
||||||
- name: Bump formulae
|
- name: Bump formulae
|
||||||
uses: Homebrew/actions/bump-packages@master
|
uses: dawidd6/action-homebrew-bump-formula@3428a0601bba3173ec0bdcc945be23fa27aa4c31 # v5
|
||||||
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}}
|
||||||
# Bump only these formulae if outdated
|
formula: topgrade
|
||||||
formulae: |
|
tag: ${{ github.event.client_payload.tag }}
|
||||||
topgrade
|
# We cannot use an org because org forks cannot give push access to maintainers, which Homebrew requires.
|
||||||
|
# org: topgrade-rs
|
||||||
|
|||||||
72
.github/workflows/release_to_pypi.yml
vendored
72
.github/workflows/release_to_pypi.yml
vendored
@@ -1,31 +1,34 @@
|
|||||||
name: Update PyPi
|
name: Update PyPi
|
||||||
|
|
||||||
on:
|
on:
|
||||||
release:
|
repository_dispatch:
|
||||||
types: [published]
|
types: [ release-created ]
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
# TODO: make linux/windows/macos/sdist a matrix. See how other workflows do it.
|
||||||
linux:
|
linux:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
target: [x86_64, x86, aarch64]
|
target: [x86_64, x86, aarch64]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Build wheels
|
- name: Build wheels
|
||||||
uses: PyO3/maturin-action@v1
|
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
|
||||||
with:
|
with:
|
||||||
target: ${{ matrix.target }}
|
target: ${{ matrix.target }}
|
||||||
args: --release --out dist
|
args: --release --out dist
|
||||||
sccache: 'true'
|
|
||||||
manylinux: auto
|
manylinux: auto
|
||||||
- name: Upload wheels
|
- name: Upload wheels
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||||
with:
|
with:
|
||||||
name: wheels
|
name: wheels-linux-${{ matrix.target }}
|
||||||
path: dist
|
path: dist
|
||||||
|
|
||||||
windows:
|
windows:
|
||||||
@@ -34,17 +37,19 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
target: [x64, x86]
|
target: [x64, x86]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Build wheels
|
- name: Build wheels
|
||||||
uses: PyO3/maturin-action@v1
|
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
|
||||||
with:
|
with:
|
||||||
target: ${{ matrix.target }}
|
target: ${{ matrix.target }}
|
||||||
args: --release --out dist
|
args: --release --out dist
|
||||||
sccache: 'true'
|
|
||||||
- name: Upload wheels
|
- name: Upload wheels
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||||
with:
|
with:
|
||||||
name: wheels
|
name: wheels-windows-${{ matrix.target }}
|
||||||
path: dist
|
path: dist
|
||||||
|
|
||||||
macos:
|
macos:
|
||||||
@@ -53,47 +58,62 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
target: [x86_64, aarch64]
|
target: [x86_64, aarch64]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Build wheels
|
- name: Build wheels
|
||||||
uses: PyO3/maturin-action@v1
|
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
|
||||||
with:
|
with:
|
||||||
target: ${{ matrix.target }}
|
target: ${{ matrix.target }}
|
||||||
args: --release --out dist
|
args: --release --out dist
|
||||||
sccache: 'true'
|
|
||||||
- name: Upload wheels
|
- name: Upload wheels
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||||
with:
|
with:
|
||||||
name: wheels
|
name: wheels-macos-${{ matrix.target }}
|
||||||
path: dist
|
path: dist
|
||||||
|
|
||||||
sdist:
|
sdist:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Build sdist
|
- name: Build sdist
|
||||||
uses: PyO3/maturin-action@v1
|
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
|
||||||
with:
|
with:
|
||||||
command: sdist
|
command: sdist
|
||||||
args: --out dist
|
args: --out dist
|
||||||
- name: Upload sdist
|
- name: Upload sdist
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||||
with:
|
with:
|
||||||
name: wheels
|
name: wheels-sdist
|
||||||
path: dist
|
path: dist
|
||||||
|
|
||||||
release:
|
release:
|
||||||
name: Release
|
name: Release
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
if: "startsWith(github.ref, 'refs/tags/')"
|
|
||||||
needs: [linux, windows, macos, sdist]
|
needs: [linux, windows, macos, sdist]
|
||||||
|
permissions:
|
||||||
|
# Use to sign the release artifacts
|
||||||
|
id-token: write
|
||||||
|
# Used to upload release artifacts
|
||||||
|
contents: write
|
||||||
|
# Used to generate artifact attestation
|
||||||
|
attestations: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/download-artifact@v4
|
- uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
|
||||||
|
|
||||||
|
- name: Generate artifact attestation
|
||||||
|
uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3.0.0
|
||||||
with:
|
with:
|
||||||
name: wheels
|
subject-path: 'wheels-*/*'
|
||||||
|
|
||||||
- name: Publish to PyPI
|
- name: Publish to PyPI
|
||||||
uses: PyO3/maturin-action@v1
|
uses: PyO3/maturin-action@86b9d133d34bc1b40018696f782949dac11bd380 # v1.49.4
|
||||||
env:
|
env:
|
||||||
MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
|
MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
|
||||||
with:
|
with:
|
||||||
command: upload
|
command: upload
|
||||||
args: --skip-existing *
|
args: --non-interactive --skip-existing wheels-*/*
|
||||||
|
|||||||
12
.github/workflows/release_to_winget.yml
vendored
12
.github/workflows/release_to_winget.yml
vendored
@@ -1,13 +1,19 @@
|
|||||||
name: Publish to WinGet
|
name: Publish to WinGet
|
||||||
|
|
||||||
on:
|
on:
|
||||||
release:
|
repository_dispatch:
|
||||||
types: [released]
|
types: [ release-created ]
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
publish:
|
publish:
|
||||||
runs-on: windows-latest
|
runs-on: windows-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: vedantmgoyal2009/winget-releaser@main
|
- uses: vedantmgoyal2009/winget-releaser@19e706d4c9121098010096f9c495a70a7518b30f # main
|
||||||
with:
|
with:
|
||||||
|
release-tag: ${{ github.event.client_payload.tag }}
|
||||||
identifier: topgrade-rs.topgrade
|
identifier: topgrade-rs.topgrade
|
||||||
max-versions-to-keep: 5 # keep only latest 5 versions
|
max-versions-to-keep: 5 # keep only latest 5 versions
|
||||||
token: ${{ secrets.WINGET_TOKEN }}
|
token: ${{ secrets.WINGET_TOKEN }}
|
||||||
76
.github/workflows/scorecards.yml
vendored
Normal file
76
.github/workflows/scorecards.yml
vendored
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
# This workflow uses actions that are not certified by GitHub. They are provided
|
||||||
|
# by a third-party and are governed by separate terms of service, privacy
|
||||||
|
# policy, and support documentation.
|
||||||
|
|
||||||
|
name: Scorecard supply-chain security
|
||||||
|
on:
|
||||||
|
# For Branch-Protection check. Only the default branch is supported. See
|
||||||
|
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection
|
||||||
|
branch_protection_rule:
|
||||||
|
# To guarantee Maintained check is occasionally updated. See
|
||||||
|
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained
|
||||||
|
schedule:
|
||||||
|
- cron: '20 7 * * 2'
|
||||||
|
push:
|
||||||
|
branches: ["main"]
|
||||||
|
|
||||||
|
# Declare default permissions as read only.
|
||||||
|
permissions: read-all
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
analysis:
|
||||||
|
name: Scorecard analysis
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
# Needed to upload the results to code-scanning dashboard.
|
||||||
|
security-events: write
|
||||||
|
# Needed to publish results and get a badge (see publish_results below).
|
||||||
|
id-token: write
|
||||||
|
contents: read
|
||||||
|
actions: read
|
||||||
|
# To allow GraphQL ListCommits to work
|
||||||
|
issues: read
|
||||||
|
pull-requests: read
|
||||||
|
# To detect SAST tools
|
||||||
|
checks: read
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: "Checkout code"
|
||||||
|
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||||
|
with:
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
|
- name: "Run analysis"
|
||||||
|
uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
|
||||||
|
with:
|
||||||
|
results_file: results.sarif
|
||||||
|
results_format: sarif
|
||||||
|
# (Optional) "write" PAT token. Uncomment the `repo_token` line below if:
|
||||||
|
# - you want to enable the Branch-Protection check on a *public* repository, or
|
||||||
|
# - you are installing Scorecards on a *private* repository
|
||||||
|
# To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat.
|
||||||
|
# repo_token: ${{ secrets.SCORECARD_TOKEN }}
|
||||||
|
|
||||||
|
# Public repositories:
|
||||||
|
# - Publish results to OpenSSF REST API for easy access by consumers
|
||||||
|
# - Allows the repository to include the Scorecard badge.
|
||||||
|
# - See https://github.com/ossf/scorecard-action#publishing-results.
|
||||||
|
# For private repositories:
|
||||||
|
# - `publish_results` will always be set to `false`, regardless
|
||||||
|
# of the value entered here.
|
||||||
|
publish_results: true
|
||||||
|
|
||||||
|
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
|
||||||
|
# format to the repository Actions tab.
|
||||||
|
- name: "Upload artifact"
|
||||||
|
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||||
|
with:
|
||||||
|
name: SARIF file
|
||||||
|
path: results.sarif
|
||||||
|
retention-days: 5
|
||||||
|
|
||||||
|
# Upload the results to GitHub's code scanning dashboard.
|
||||||
|
- name: "Upload to code-scanning"
|
||||||
|
uses: github/codeql-action/upload-sarif@0499de31b99561a6d14a36a5f662c2a54f91beee # v4.31.2
|
||||||
|
with:
|
||||||
|
sarif_file: results.sarif
|
||||||
25
.pre-commit-config.yaml
Normal file
25
.pre-commit-config.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
repos:
|
||||||
|
- repo: https://github.com/gitleaks/gitleaks
|
||||||
|
rev: v8.28.0
|
||||||
|
hooks:
|
||||||
|
- id: gitleaks
|
||||||
|
|
||||||
|
- repo: https://github.com/shellcheck-py/shellcheck-py
|
||||||
|
rev: v0.11.0.1
|
||||||
|
hooks:
|
||||||
|
- id: shellcheck
|
||||||
|
|
||||||
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
|
rev: v6.0.0
|
||||||
|
hooks:
|
||||||
|
- id: end-of-file-fixer
|
||||||
|
- id: trailing-whitespace
|
||||||
|
|
||||||
|
- repo: https://github.com/crate-ci/typos
|
||||||
|
rev: v1.38.1
|
||||||
|
hooks:
|
||||||
|
- id: typos
|
||||||
|
|
||||||
|
|
||||||
|
ci:
|
||||||
|
autoupdate_commit_msg: "chore(pre-commit): autoupdate"
|
||||||
20
.typos.toml
Normal file
20
.typos.toml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# Typos configuration (minimal, conservative)
|
||||||
|
# Exclude locales and OS fingerprint data to avoid false positives
|
||||||
|
# - Recognize a few project-specific proper nouns
|
||||||
|
|
||||||
|
[files]
|
||||||
|
extend-exclude = [
|
||||||
|
"src/steps/os/os_release/**",
|
||||||
|
"locales/**",
|
||||||
|
# Include only English locale files - TODO: Split locales/app.yml into a Separate english File
|
||||||
|
# "!locales/en/**"
|
||||||
|
]
|
||||||
|
|
||||||
|
[default]
|
||||||
|
# Mark specific words as always valid by mapping them to themselves
|
||||||
|
check-file = true
|
||||||
|
check-filename = true
|
||||||
|
|
||||||
|
[default.extend-words]
|
||||||
|
# Add project-specific terms that should not be flagged as typos
|
||||||
|
# Example: topgrade = "topgrade"
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
# Containers step
|
|
||||||
|
|
||||||
* New default behavior: In the previous versions, if you have both Docker and
|
|
||||||
Podman installed, Podman will be used by Topgrade. Now the default option
|
|
||||||
has been changed to Docker. This can be overridden by setting the
|
|
||||||
`containers.runtime` option in the configuration TOML to "podman".
|
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
1. The `jet_brains_toolbox` step was renamed to `jetbrains_toolbox`. If you're
|
||||||
|
using the old name in your configuration file in the `disable` or `only`
|
||||||
|
fields, simply change it to `jetbrains_toolbox`.
|
||||||
|
|||||||
229
CHANGELOG.md
Normal file
229
CHANGELOG.md
Normal file
@@ -0,0 +1,229 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [16.2.1](https://github.com/topgrade-rs/topgrade/compare/v16.2.0...v16.2.1) - 2025-11-10
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- *(release)* Use bash in Windows to fix powershell issues ([#1461](https://github.com/topgrade-rs/topgrade/pull/1461))
|
||||||
|
- *(release)* Fix .deb distribution ([#1460](https://github.com/topgrade-rs/topgrade/pull/1460))
|
||||||
|
- *(release)* Fix .deb distribution ([#1458](https://github.com/topgrade-rs/topgrade/pull/1458))
|
||||||
|
|
||||||
|
## [16.2.0](https://github.com/topgrade-rs/topgrade/compare/v16.1.2...v16.2.0) - 2025-11-10
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- *(mise)* run `mise self-update` ([#1450](https://github.com/topgrade-rs/topgrade/pull/1450))
|
||||||
|
- *(falconf)* add falconf step ([#1219](https://github.com/topgrade-rs/topgrade/pull/1219))
|
||||||
|
- *(hyprpm)* add hyprpm step ([#1213](https://github.com/topgrade-rs/topgrade/pull/1213))
|
||||||
|
- *(doom)* add doom.aot option ([#1214](https://github.com/topgrade-rs/topgrade/pull/1214))
|
||||||
|
- add show_distribution_summary config option ([#1259](https://github.com/topgrade-rs/topgrade/pull/1259))
|
||||||
|
- *(rustup)* add rustup.channels config ([#1206](https://github.com/topgrade-rs/topgrade/pull/1206))
|
||||||
|
- *(os)* add AOSC OS support ([#1424](https://github.com/topgrade-rs/topgrade/pull/1424))
|
||||||
|
- add damp run type ([#1217](https://github.com/topgrade-rs/topgrade/pull/1217))
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- *(release)* fix homebrew releases by migrating to dawidd6/action-homebrew-bump-formula ([#1457](https://github.com/topgrade-rs/topgrade/pull/1457))
|
||||||
|
- *(mise)* fix mise self-update failing when installed via a package manager ([#1456](https://github.com/topgrade-rs/topgrade/pull/1456))
|
||||||
|
- *(release)* Add man page to .deb distribution ([#1455](https://github.com/topgrade-rs/topgrade/pull/1455))
|
||||||
|
- *(self-update)* fix windows self-update reporting failure on successful self-update ([#1452](https://github.com/topgrade-rs/topgrade/pull/1452))
|
||||||
|
- *(pkgfile)* make pkgfile opt-in ([#1449](https://github.com/topgrade-rs/topgrade/pull/1449))
|
||||||
|
- *(vcpkg)* fix permission denied when updating vcpkg if it's installed as root ([#1447](https://github.com/topgrade-rs/topgrade/pull/1447))
|
||||||
|
- *(zh_TW)* fixed zh_TW strings ([#1446](https://github.com/topgrade-rs/topgrade/pull/1446))
|
||||||
|
- *(git)* fix shellexpand::tilde in git_repos in topgrade.d/* ([#1223](https://github.com/topgrade-rs/topgrade/pull/1223))
|
||||||
|
- *(auto-cpufreq)* skip when install script is not used ([#1215](https://github.com/topgrade-rs/topgrade/pull/1215))
|
||||||
|
- *(vim)* change nvimrc base_dir for windows ([#1433](https://github.com/topgrade-rs/topgrade/pull/1433))
|
||||||
|
- *(guix)* fix overcomplicated Guix step ([#1290](https://github.com/topgrade-rs/topgrade/pull/1290))
|
||||||
|
- *(gem)* fix incorrectly placed debug message in `gem` step ([#1212](https://github.com/topgrade-rs/topgrade/pull/1212))
|
||||||
|
- *(conda)* replace deprecated `auto_activate_base` ([#1158](https://github.com/topgrade-rs/topgrade/pull/1158))
|
||||||
|
- *(containers)* fix panic in `containers` step ([#1150](https://github.com/topgrade-rs/topgrade/pull/1150))
|
||||||
|
- *(jetbrains-toolbox)* fix step not dry running ([#1253](https://github.com/topgrade-rs/topgrade/pull/1253))
|
||||||
|
|
||||||
|
### Other
|
||||||
|
|
||||||
|
- comment run_config_update ([#1448](https://github.com/topgrade-rs/topgrade/pull/1448))
|
||||||
|
- Expand LLM guidelines in CONTRIBUTING.md ([#1445](https://github.com/topgrade-rs/topgrade/pull/1445))
|
||||||
|
- Add AI guidelines to CONTRIBUTING.md ([#1444](https://github.com/topgrade-rs/topgrade/pull/1444))
|
||||||
|
- add comments to Config::allowed_steps ([#1291](https://github.com/topgrade-rs/topgrade/pull/1291))
|
||||||
|
- *(nix)* Deduplicate run_nix and run_nix_self_upgrade nix --version checking ([#1376](https://github.com/topgrade-rs/topgrade/pull/1376))
|
||||||
|
- remove commented-out library code and unnecessary bin declaration ([#1373](https://github.com/topgrade-rs/topgrade/pull/1373))
|
||||||
|
- Simplify target cfgs ([#1346](https://github.com/topgrade-rs/topgrade/pull/1346))
|
||||||
|
- tidy up binary-conflict code ([#1329](https://github.com/topgrade-rs/topgrade/pull/1329))
|
||||||
|
- Improve installation section ([#1442](https://github.com/topgrade-rs/topgrade/pull/1442))
|
||||||
|
- *(deps)* Update jetbrains-toolbox-updater ([#1438](https://github.com/topgrade-rs/topgrade/pull/1438))
|
||||||
|
- remove template expansion in code contexts ([#1434](https://github.com/topgrade-rs/topgrade/pull/1434))
|
||||||
|
- *(deps)* bump github/codeql-action from 4.31.0 to 4.31.2 ([#1427](https://github.com/topgrade-rs/topgrade/pull/1427))
|
||||||
|
- don't persist credentials in actions/checkout ([#1422](https://github.com/topgrade-rs/topgrade/pull/1422))
|
||||||
|
- Improve CONTRIBUTING.md ([#1420](https://github.com/topgrade-rs/topgrade/pull/1420))
|
||||||
|
- Update SECURITY.md ([#1421](https://github.com/topgrade-rs/topgrade/pull/1421))
|
||||||
|
- Enforce conventional commits in PR titles ([#1418](https://github.com/topgrade-rs/topgrade/pull/1418))
|
||||||
|
- Improve contributing section
|
||||||
|
- Remove roadmap
|
||||||
|
- Reformat README.md
|
||||||
|
- Update installation methods
|
||||||
|
- *(release)* Fix dispatch error in create_release_assets.yml ([#1406](https://github.com/topgrade-rs/topgrade/pull/1406))
|
||||||
|
|
||||||
|
## [16.1.2](https://github.com/topgrade-rs/topgrade/compare/v16.1.1...v16.1.2) - 2025-11-01
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- *(release)* Fix cross-compilation for arm requiring glibc>=2.39 ([#1405](https://github.com/topgrade-rs/topgrade/pull/1405))
|
||||||
|
- *(release)* Fix FreeBSD build ([#1404](https://github.com/topgrade-rs/topgrade/pull/1404))
|
||||||
|
- *(release)* Fix FreeBSD build ([#1402](https://github.com/topgrade-rs/topgrade/pull/1402))
|
||||||
|
- *(release)* Fix manual workflow trigger ([#1401](https://github.com/topgrade-rs/topgrade/pull/1401))
|
||||||
|
- *(release)* Fix FreeBSD build and add manual workflow trigger ([#1399](https://github.com/topgrade-rs/topgrade/pull/1399))
|
||||||
|
|
||||||
|
### Other
|
||||||
|
|
||||||
|
- *(release)* Fix cross trying to fmt ([#1403](https://github.com/topgrade-rs/topgrade/pull/1403))
|
||||||
|
|
||||||
|
## [16.1.1](https://github.com/topgrade-rs/topgrade/compare/v16.1.0...v16.1.1) - 2025-11-01
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- *(typst)* Skip typst when self-update is disabled ([#1397](https://github.com/topgrade-rs/topgrade/pull/1397))
|
||||||
|
- *(release)* Fix winget release workflow ([#1395](https://github.com/topgrade-rs/topgrade/pull/1395))
|
||||||
|
- *(release)* Fix FreeBSD release ([#1393](https://github.com/topgrade-rs/topgrade/pull/1393))
|
||||||
|
- *(release)* Fix FreeBSD release ([#1391](https://github.com/topgrade-rs/topgrade/pull/1391))
|
||||||
|
|
||||||
|
### Other
|
||||||
|
|
||||||
|
- Update from deprecated macos-13 to macos-15-intel ([#1394](https://github.com/topgrade-rs/topgrade/pull/1394))
|
||||||
|
|
||||||
|
## [16.1.0](https://github.com/topgrade-rs/topgrade/compare/v16.0.4...v16.1.0) - 2025-10-31
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- *(deb-get)* Skip non-deb-get packages by passing --dg-only ([#1386](https://github.com/topgrade-rs/topgrade/pull/1386))
|
||||||
|
- *(typst)* add typst step ([#1374](https://github.com/topgrade-rs/topgrade/pull/1374))
|
||||||
|
- *(step)* Add atuin step ([#1367](https://github.com/topgrade-rs/topgrade/pull/1367))
|
||||||
|
- *(nix)* support upgrading Determinate Nix ([#1366](https://github.com/topgrade-rs/topgrade/pull/1366))
|
||||||
|
- *(sudo)* print warning if Windows Sudo is misconfigured
|
||||||
|
- *(sudo)* print warning if steps were skipped due to missing sudo
|
||||||
|
- *(sudo)* add SudoKind::Null
|
||||||
|
- detect and warn if running as root
|
||||||
|
- add `--no-tmux` flag ([#1328](https://github.com/topgrade-rs/topgrade/pull/1328))
|
||||||
|
- add step for mandb - user and system (update man entries) ([#1319](https://github.com/topgrade-rs/topgrade/pull/1319))
|
||||||
|
- support for pkgfile ([#1306](https://github.com/topgrade-rs/topgrade/pull/1306))
|
||||||
|
- add "show_skipped" option in config file #1280 ([#1286](https://github.com/topgrade-rs/topgrade/pull/1286))
|
||||||
|
- fix typos ([#1221](https://github.com/topgrade-rs/topgrade/pull/1221))
|
||||||
|
- *(conda)* allow configuring additional envs to update ([#1048](https://github.com/topgrade-rs/topgrade/pull/1048))
|
||||||
|
- *(step)* nix-helper ([#1045](https://github.com/topgrade-rs/topgrade/pull/1045))
|
||||||
|
- *(winget)* winget uses sudo when `[windows] winget_use_sudo = true` ([#1061](https://github.com/topgrade-rs/topgrade/pull/1061))
|
||||||
|
- suppress pixi release notes by default ([#1225](https://github.com/topgrade-rs/topgrade/pull/1225))
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- *(freshclam)* run with sudo when running without sudo fails ([#1118](https://github.com/topgrade-rs/topgrade/pull/1118))
|
||||||
|
- *(tldr)* move tldr to be a generic step ([#1370](https://github.com/topgrade-rs/topgrade/pull/1370))
|
||||||
|
- *(nix)* fix nix upgrade command selection for profiles in XDG_STATE_HOME ([#1354](https://github.com/topgrade-rs/topgrade/pull/1354))
|
||||||
|
- *(containers)* Docker update fails on M Macs due to platform / ([#1360](https://github.com/topgrade-rs/topgrade/pull/1360))
|
||||||
|
- *(sudo)* reorder require_sudo() after print_separator()
|
||||||
|
- *(sudo)* use require_sudo for windows commands
|
||||||
|
- *(sudo)* prevent sudo_command = "sudo" finding gsudo
|
||||||
|
- *(sudo)* set sudo flags depending on kind
|
||||||
|
- skip gcloud update step if component manager is disabled ([#1237](https://github.com/topgrade-rs/topgrade/pull/1237))
|
||||||
|
- *(i18n)* use double-quotes for translations with newlines
|
||||||
|
- *(powershell)* run microsoft_store command directly
|
||||||
|
- *(powershell)* remove mentions of USOClient
|
||||||
|
- *(powershell)* execution policy check breaks when run in pwsh
|
||||||
|
- *(powershell)* don't use sudo with Update-Module for pwsh
|
||||||
|
- *(powershell)* add -Command to module update cmdline
|
||||||
|
- *(tmux)* support all default `tpm` locations (xdg and both hardcoded locations) ([#1146](https://github.com/topgrade-rs/topgrade/pull/1146))
|
||||||
|
- fixed the German translation for "y/n/s/q" ([#1220](https://github.com/topgrade-rs/topgrade/pull/1220))
|
||||||
|
|
||||||
|
### Other
|
||||||
|
|
||||||
|
- *(release)* switch to release-plz ([#1333](https://github.com/topgrade-rs/topgrade/pull/1333))
|
||||||
|
- *(pre-commit)* Make pre-commit.ci use conventional commits ([#1388](https://github.com/topgrade-rs/topgrade/pull/1388))
|
||||||
|
- *(pre-commit)* pre-commit autoupdate ([#1383](https://github.com/topgrade-rs/topgrade/pull/1383))
|
||||||
|
- *(deps)* bump actions/upload-artifact from 4.6.2 to 5.0.0 ([#1382](https://github.com/topgrade-rs/topgrade/pull/1382))
|
||||||
|
- *(deps)* bump github/codeql-action from 4.30.9 to 4.31.0 ([#1379](https://github.com/topgrade-rs/topgrade/pull/1379))
|
||||||
|
- *(deps)* bump actions/download-artifact from 5.0.0 to 6.0.0 ([#1380](https://github.com/topgrade-rs/topgrade/pull/1380))
|
||||||
|
- *(deps)* bump taiki-e/install-action from 2.62.33 to 2.62.38 ([#1381](https://github.com/topgrade-rs/topgrade/pull/1381))
|
||||||
|
- *(pre-commit)* Fix pre-commit-config.yaml ([#1378](https://github.com/topgrade-rs/topgrade/pull/1378))
|
||||||
|
- *(release)* Add .deb auto completion script ([#1353](https://github.com/topgrade-rs/topgrade/pull/1353))
|
||||||
|
- *(deps)* bump github/codeql-action from 4.30.8 to 4.30.9 ([#1369](https://github.com/topgrade-rs/topgrade/pull/1369))
|
||||||
|
- *(deps)* bump taiki-e/install-action from 2.62.28 to 2.62.33 ([#1368](https://github.com/topgrade-rs/topgrade/pull/1368))
|
||||||
|
- *(deps)* bump actions/dependency-review-action from 4.8.0 to 4.8.1 ([#1362](https://github.com/topgrade-rs/topgrade/pull/1362))
|
||||||
|
- *(deps)* bump softprops/action-gh-release from 2.3.4 to 2.4.1 ([#1364](https://github.com/topgrade-rs/topgrade/pull/1364))
|
||||||
|
- *(deps)* bump taiki-e/install-action from 2.62.21 to 2.62.28 ([#1363](https://github.com/topgrade-rs/topgrade/pull/1363))
|
||||||
|
- *(deps)* bump github/codeql-action from 3.30.6 to 4.30.8 ([#1365](https://github.com/topgrade-rs/topgrade/pull/1365))
|
||||||
|
- *(deps)* bump github/codeql-action from 3.30.5 to 3.30.6 ([#1355](https://github.com/topgrade-rs/topgrade/pull/1355))
|
||||||
|
- *(deps)* bump softprops/action-gh-release from 2.3.3 to 2.3.4 ([#1356](https://github.com/topgrade-rs/topgrade/pull/1356))
|
||||||
|
- *(deps)* bump taiki-e/install-action from 2.62.13 to 2.62.21 ([#1357](https://github.com/topgrade-rs/topgrade/pull/1357))
|
||||||
|
- *(deps)* bump ossf/scorecard-action from 2.4.2 to 2.4.3 ([#1358](https://github.com/topgrade-rs/topgrade/pull/1358))
|
||||||
|
- *(deps)* bump actions/dependency-review-action from 4.7.3 to 4.8.0 ([#1350](https://github.com/topgrade-rs/topgrade/pull/1350))
|
||||||
|
- *(deps)* bump github/codeql-action from 3.30.3 to 3.30.5 ([#1349](https://github.com/topgrade-rs/topgrade/pull/1349))
|
||||||
|
- *(deps)* bump taiki-e/install-action from 2.62.1 to 2.62.13 ([#1351](https://github.com/topgrade-rs/topgrade/pull/1351))
|
||||||
|
- *(deps)* bump actions/cache from 4.2.4 to 4.3.0 ([#1352](https://github.com/topgrade-rs/topgrade/pull/1352))
|
||||||
|
- Fix WSL distribution name cleanup ([#1348](https://github.com/topgrade-rs/topgrade/pull/1348))
|
||||||
|
- *(pyproject)* mark version as dynamic ([#1347](https://github.com/topgrade-rs/topgrade/pull/1347))
|
||||||
|
- *(deps)* replace winapi with windows
|
||||||
|
- *(sudo)* rename interactive to login_shell
|
||||||
|
- Fix "WSL already reported" panic ([#1344](https://github.com/topgrade-rs/topgrade/pull/1344))
|
||||||
|
- Move step logic out of Powershell struct ([#1345](https://github.com/topgrade-rs/topgrade/pull/1345))
|
||||||
|
- *(deps)* bump taiki-e/install-action from 2.61.5 to 2.62.1 ([#1335](https://github.com/topgrade-rs/topgrade/pull/1335))
|
||||||
|
- *(deps)* bump Swatinem/rust-cache from 2.8.0 to 2.8.1 ([#1336](https://github.com/topgrade-rs/topgrade/pull/1336))
|
||||||
|
- Fixes for #1188; custom_commands broken ([#1332](https://github.com/topgrade-rs/topgrade/pull/1332))
|
||||||
|
- use login shell when executing topgrade ([#1327](https://github.com/topgrade-rs/topgrade/pull/1327))
|
||||||
|
- *(deps)* bump taiki-e/install-action from 2.60.0 to 2.61.5 ([#1325](https://github.com/topgrade-rs/topgrade/pull/1325))
|
||||||
|
- *(deps)* bump github/codeql-action from 3.30.1 to 3.30.3 ([#1324](https://github.com/topgrade-rs/topgrade/pull/1324))
|
||||||
|
- *(pre-commit)* add typos with conservative excludes; no content changes ([#1317](https://github.com/topgrade-rs/topgrade/pull/1317))
|
||||||
|
- fix simple typos in code and comments (split var, whether, Extensions) ([#1318](https://github.com/topgrade-rs/topgrade/pull/1318))
|
||||||
|
- *(deps)* bump github/codeql-action from 3.29.11 to 3.30.1 ([#1301](https://github.com/topgrade-rs/topgrade/pull/1301))
|
||||||
|
- *(deps)* bump softprops/action-gh-release from 2.3.2 to 2.3.3 ([#1302](https://github.com/topgrade-rs/topgrade/pull/1302))
|
||||||
|
- *(deps)* bump taiki-e/install-action from 2.58.21 to 2.60.0 ([#1303](https://github.com/topgrade-rs/topgrade/pull/1303))
|
||||||
|
- *(deps)* bump actions/dependency-review-action from 4.7.2 to 4.7.3 ([#1304](https://github.com/topgrade-rs/topgrade/pull/1304))
|
||||||
|
- *(deps)* bump actions/attest-build-provenance from 2.4.0 to 3.0.0 ([#1305](https://github.com/topgrade-rs/topgrade/pull/1305))
|
||||||
|
- update tracing-subscriber to ~0.3.20 (ANSI escape injection fix, GHSA-xwfj-jgwm-7wp5) ([#1288](https://github.com/topgrade-rs/topgrade/pull/1288))
|
||||||
|
- *(deps)* bump github/codeql-action from 3.29.8 to 3.29.11 ([#1281](https://github.com/topgrade-rs/topgrade/pull/1281))
|
||||||
|
- *(deps)* bump actions/dependency-review-action from 4.7.1 to 4.7.2 ([#1282](https://github.com/topgrade-rs/topgrade/pull/1282))
|
||||||
|
- *(deps)* bump taiki-e/install-action from 2.58.9 to 2.58.21 ([#1283](https://github.com/topgrade-rs/topgrade/pull/1283))
|
||||||
|
- *(deps)* bump PyO3/maturin-action from 1.49.3 to 1.49.4 ([#1285](https://github.com/topgrade-rs/topgrade/pull/1285))
|
||||||
|
- *(deps)* bump actions/cache from 4.2.3 to 4.2.4 ([#1284](https://github.com/topgrade-rs/topgrade/pull/1284))
|
||||||
|
- Support "Insiders" versions of VSCode and VSCodium ([#1279](https://github.com/topgrade-rs/topgrade/pull/1279))
|
||||||
|
- Sudo preserve env list argument is `--preserve-env` ([#1276](https://github.com/topgrade-rs/topgrade/pull/1276))
|
||||||
|
- Clippy fixes from rust 1.91 nightly ([#1267](https://github.com/topgrade-rs/topgrade/pull/1267))
|
||||||
|
- *(deps)* bump actions/checkout from 4.2.2 to 5.0.0 ([#1264](https://github.com/topgrade-rs/topgrade/pull/1264))
|
||||||
|
- *(deps)* bump actions/download-artifact from 4.3.0 to 5.0.0 ([#1263](https://github.com/topgrade-rs/topgrade/pull/1263))
|
||||||
|
- *(deps)* bump taiki-e/install-action from 2.58.0 to 2.58.9 ([#1261](https://github.com/topgrade-rs/topgrade/pull/1261))
|
||||||
|
- *(deps)* bump ossf/scorecard-action from 2.4.0 to 2.4.2 ([#1262](https://github.com/topgrade-rs/topgrade/pull/1262))
|
||||||
|
- *(deps)* bump github/codeql-action from 3.29.5 to 3.29.8 ([#1265](https://github.com/topgrade-rs/topgrade/pull/1265))
|
||||||
|
- *(ci)* Dependabot, workflow security ([#1257](https://github.com/topgrade-rs/topgrade/pull/1257))
|
||||||
|
- replace once_cell crate with std equivalent ([#1260](https://github.com/topgrade-rs/topgrade/pull/1260))
|
||||||
|
- *(deps)* bump tokio from 1.38 to 1.47 ([#1256](https://github.com/topgrade-rs/topgrade/pull/1256))
|
||||||
|
- *(app.yml)* fix fr language #1248
|
||||||
|
- *(sudo)* add SudoKind::WinSudo
|
||||||
|
- *(sudo)* add SudoExecuteOpts builder functions and preserve_env enum
|
||||||
|
- *(yarn)* remove unnecessary Yarn::yarn field
|
||||||
|
- *(apt)* extract detect_apt() function
|
||||||
|
- route sudo usage through Sudo::execute*
|
||||||
|
- move RunType::execute to ExecutionContext
|
||||||
|
- *(powershell)* store powershell path directly
|
||||||
|
- *(powershell)* cleanup and simplify code
|
||||||
|
- Move step running into enum for dynamic ordering ([#1188](https://github.com/topgrade-rs/topgrade/pull/1188))
|
||||||
|
- Generate artifact attestations for release assets ([#1216](https://github.com/topgrade-rs/topgrade/pull/1216))
|
||||||
|
- windows update, use explicit reboot policy ([#1143](https://github.com/topgrade-rs/topgrade/pull/1143))
|
||||||
|
- add Discord invite link to README ([#1203](https://github.com/topgrade-rs/topgrade/pull/1203))
|
||||||
|
- Catch secondary uv self-update error ([#1201](https://github.com/topgrade-rs/topgrade/pull/1201))
|
||||||
|
- Handle another format change in asdf version ([#1194](https://github.com/topgrade-rs/topgrade/pull/1194))
|
||||||
|
- Preserve custom command order from config instead of sorting alphabetically ([#1182](https://github.com/topgrade-rs/topgrade/pull/1182))
|
||||||
|
- Add support for multiple binary names and idea having multiple binaries ([#1167](https://github.com/topgrade-rs/topgrade/pull/1167))
|
||||||
|
- fix the invalid action version ([#1185](https://github.com/topgrade-rs/topgrade/pull/1185))
|
||||||
|
- allow us to re-run AUR CI ([#1184](https://github.com/topgrade-rs/topgrade/pull/1184))
|
||||||
|
- Update Yazi upgrade step to use ya pkg. ([#1163](https://github.com/topgrade-rs/topgrade/pull/1163))
|
||||||
|
- use the new tag name and specify shell to bash ([#1183](https://github.com/topgrade-rs/topgrade/pull/1183))
|
||||||
|
- allow specifying tag when manually run 'create_release_assets.yml' ([#1180](https://github.com/topgrade-rs/topgrade/pull/1180))
|
||||||
|
- fix homebrew ci, remove duplicate trigger event ([#1179](https://github.com/topgrade-rs/topgrade/pull/1179))
|
||||||
|
- fix PyPI pipeline duplicate wheel name ([#1178](https://github.com/topgrade-rs/topgrade/pull/1178))
|
||||||
|
- add event workflow_dispatch to release pipelines ([#1177](https://github.com/topgrade-rs/topgrade/pull/1177))
|
||||||
|
- fix pipeline release to PyPI ([#1176](https://github.com/topgrade-rs/topgrade/pull/1176))
|
||||||
|
- Install rustfmt and clippy where necessary ([#1171](https://github.com/topgrade-rs/topgrade/pull/1171))
|
||||||
146
CONTRIBUTING.md
146
CONTRIBUTING.md
@@ -7,24 +7,47 @@ We welcome and encourage contributions of all kinds, such as:
|
|||||||
2. Documentation improvements
|
2. Documentation improvements
|
||||||
3. Code (PR or PR Review)
|
3. Code (PR or PR Review)
|
||||||
|
|
||||||
Please follow the [Karma Runner guidelines](http://karma-runner.github.io/6.2/dev/git-commit-msg.html)
|
### LLM/AI guidelines
|
||||||
for commit messages.
|
|
||||||
|
|
||||||
## Adding a new `step`
|
You may use LLMs (AI tools) for:
|
||||||
|
|
||||||
In `topgrade`'s term, package manager is called `step`.
|
* Inspiration, problem solving, help with Rust, translation, etc.
|
||||||
To add a new `step` to `topgrade`:
|
* Generating small and self-contained snippets of code (e.g., shell scripts or utility functions)
|
||||||
|
|
||||||
|
Do **not** use LLMs to:
|
||||||
|
|
||||||
|
* Generate ("vibe code") entire pull requests
|
||||||
|
* Write or generate issue or pull request descriptions
|
||||||
|
|
||||||
|
### General guidelines
|
||||||
|
|
||||||
|
**Please use [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) for your PR title**.
|
||||||
|
|
||||||
|
We use [pre-commit](https://github.com/pre-commit/pre-commit). It runs in CI, but you can optionally install the hook
|
||||||
|
locally with `pre-commit install`. If you don't want to use pre-commit, make sure the following pass before submitting
|
||||||
|
your PR:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ cargo fmt
|
||||||
|
$ cargo clippy
|
||||||
|
$ cargo test
|
||||||
|
```
|
||||||
|
|
||||||
|
### Adding a new step
|
||||||
|
|
||||||
|
In `topgrade`'s terms, a package manager (or something else that can be upgraded) is called a step.
|
||||||
|
To add a new step to `topgrade`:
|
||||||
|
|
||||||
1. Add a new variant to
|
1. Add a new variant to
|
||||||
[`enum Step`](https://github.com/topgrade-rs/topgrade/blob/cb7adc8ced8a77addf2cb051d18bba9f202ab866/src/config.rs#L100)
|
[`enum Step`](https://github.com/topgrade-rs/topgrade/blob/main/src/step.rs)
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
pub enum Step {
|
pub enum Step {
|
||||||
// Existed steps
|
// Existing steps
|
||||||
// ...
|
// ...
|
||||||
|
|
||||||
// Your new step here!
|
// Your new step here!
|
||||||
// You may want it to be sorted alphabetically because that looks great:)
|
// Make sure it stays sorted alphabetically because that looks great :)
|
||||||
Xxx,
|
Xxx,
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -32,9 +55,10 @@ To add a new `step` to `topgrade`:
|
|||||||
2. Implement the update function
|
2. Implement the update function
|
||||||
|
|
||||||
You need to find the appropriate location where this update function goes, it should be
|
You need to find the appropriate location where this update function goes, it should be
|
||||||
a file under [`src/steps`](https://github.com/topgrade-rs/topgrade/tree/master/src/steps),
|
a file under [`src/steps`](https://github.com/topgrade-rs/topgrade/tree/main/src/steps),
|
||||||
the file names are self-explanatory, for example, `step`s related to `zsh` are
|
the file names are self-explanatory, for example, steps related to `zsh` are
|
||||||
placed in [`steps/zsh.rs`](https://github.com/topgrade-rs/topgrade/blob/master/src/steps/zsh.rs).
|
placed in [`steps/zsh.rs`](https://github.com/topgrade-rs/topgrade/blob/main/src/steps/zsh.rs), and steps that run on
|
||||||
|
Linux only are placed in [`steps/linux.rs`](https://github.com/topgrade-rs/topgrade/blob/main/src/steps/linux.rs).
|
||||||
|
|
||||||
Then you implement the update function, and put it in the file where it belongs.
|
Then you implement the update function, and put it in the file where it belongs.
|
||||||
|
|
||||||
@@ -47,38 +71,35 @@ To add a new `step` to `topgrade`:
|
|||||||
print_separator("xxx");
|
print_separator("xxx");
|
||||||
|
|
||||||
// Invoke the new step to get things updated!
|
// Invoke the new step to get things updated!
|
||||||
ctx.run_type()
|
ctx.execute(xxx)
|
||||||
.execute(xxx)
|
|
||||||
.arg(/* args required by this step */)
|
.arg(/* args required by this step */)
|
||||||
.status_checked()
|
.status_checked()
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Such a update function would be conventionally named `run_xxx()`, where `xxx`
|
Such an update function would be conventionally named `run_xxx()`, where `xxx`
|
||||||
is the name of the new step, and it should take a argument of type
|
is the name of the new step, and it should take an argument of type
|
||||||
`&ExecutionContext`, this is adequate for most cases unless some extra stuff is
|
`&ExecutionContext`.
|
||||||
needed (You can find some examples where extra arguments are needed
|
|
||||||
[here](https://github.com/topgrade-rs/topgrade/blob/7e48c5dedcfd5d0124bb9f39079a03e27ed23886/src/main.rs#L201-L219)).
|
|
||||||
|
|
||||||
Update function would usually do 3 things:
|
The update function should usually do 3 things:
|
||||||
1. Check if the step is installed
|
1. Check if the step is installed
|
||||||
2. Output the Separator
|
2. Output the separator
|
||||||
3. Invoke the step
|
3. Execute commands
|
||||||
|
|
||||||
Still, this is sufficient for most tools, but you may need some extra stuff
|
This is sufficient for most tools, but you may need some extra stuff
|
||||||
with complicated `step`.
|
for complicated steps.
|
||||||
|
|
||||||
3. Finally, invoke that update function in `main.rs`
|
3. Add a match arm to `Step::run()`
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
runner.execute(Step::Xxx, "xxx", || ItsModule::run_xxx(&ctx))?;
|
Xxx => runner.execute(*self, "xxx", || ItsModule::run_xxx(ctx))?
|
||||||
```
|
```
|
||||||
|
|
||||||
We use [conditional compilation](https://doc.rust-lang.org/reference/conditional-compilation.html)
|
We use [conditional compilation](https://doc.rust-lang.org/reference/conditional-compilation.html)
|
||||||
to separate the steps, for example, for steps that are Linux-only, it goes
|
to separate the steps. For example, for steps that are Linux-only, it goes
|
||||||
like this:
|
like this:
|
||||||
|
|
||||||
```
|
```rust
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
{
|
{
|
||||||
// Xxx is Linux-only
|
// Xxx is Linux-only
|
||||||
@@ -86,25 +107,31 @@ To add a new `step` to `topgrade`:
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Congrats, you just added a new `step`:)
|
4. Finally, add the step to `default_steps()` in `step.rs`
|
||||||
|
```rust
|
||||||
|
steps.push(Xxx)
|
||||||
|
```
|
||||||
|
Keep the conditional compilation the same as in the above step 3.
|
||||||
|
|
||||||
## Modification to the configuration entries
|
Congrats, you just added a new step :)
|
||||||
|
|
||||||
|
### Modification to the configuration entries
|
||||||
|
|
||||||
If your PR has the configuration options
|
If your PR has the configuration options
|
||||||
(in [`src/config.rs`](https://github.com/topgrade-rs/topgrade/blob/master/src/config.rs))
|
(in [`src/config.rs`](https://github.com/topgrade-rs/topgrade/blob/main/src/config.rs))
|
||||||
modified:
|
modified:
|
||||||
|
|
||||||
1. Adding new options
|
1. Adding new options
|
||||||
2. Changing the existing options
|
2. Changing the existing options
|
||||||
|
|
||||||
Be sure to apply your changes to
|
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/main/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
|
### Breaking changes
|
||||||
|
|
||||||
If your PR introduces a breaking change, document it in [`BREAKINGCHANGES_dev.md`][bc_dev],
|
If your PR introduces a breaking change, document it in [`BREAKINGCHANGES_dev.md`][bc_dev].
|
||||||
it should be written in Markdown and wrapped at 80, for example:
|
It should be written in Markdown and wrapped at 80, for example:
|
||||||
|
|
||||||
```md
|
```md
|
||||||
1. The configuration location has been updated to x.
|
1. The configuration location has been updated to x.
|
||||||
@@ -116,37 +143,40 @@ it should be written in Markdown and wrapped at 80, for example:
|
|||||||
|
|
||||||
[bc_dev]: https://github.com/topgrade-rs/topgrade/blob/main/BREAKINGCHANGES_dev.md
|
[bc_dev]: https://github.com/topgrade-rs/topgrade/blob/main/BREAKINGCHANGES_dev.md
|
||||||
|
|
||||||
## Before you submit your PR
|
### I18n
|
||||||
|
|
||||||
Make sure your patch passes the following tests on your host:
|
If your PR introduces user-facing messages, we need to ensure they are translated.
|
||||||
|
Please add the translations to [`locales/app.yml`][app_yml]. For simple messages
|
||||||
|
without arguments (e.g., "hello world"), we can simply translate them according
|
||||||
|
(Tip: LLMs are good at translation). If a message contains
|
||||||
|
arguments, e.g., "hello <NAME>", please follow this convention:
|
||||||
|
|
||||||
```shell
|
```yml
|
||||||
$ cargo build
|
"hello {name}": # key
|
||||||
$ cargo fmt
|
en: "hello %{name}" # translation
|
||||||
$ cargo clippy
|
|
||||||
$ cargo test
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Don't worry about other platforms, we have most of them covered in our CI.
|
Arguments in the key should be in format `{argument_name}`, and they will have
|
||||||
|
a preceding `%` when used in translations.
|
||||||
|
|
||||||
## Some tips
|
[app_yml]: https://github.com/topgrade-rs/topgrade/blob/main/locales/app.yml
|
||||||
|
|
||||||
1. Locale
|
### Locales
|
||||||
|
|
||||||
Some `step` respects locale, which means their output can be in language other
|
Some steps respect locale, which means their output can be in language other
|
||||||
than English, we should not do check on it.
|
than English. In those cases, we cannot rely on the output of a command.
|
||||||
|
|
||||||
For example, one may want to check if a tool works by doing this:
|
For example, one may want to check if a tool works by doing this:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
let output = Command::new("xxx").arg("--help").output().unwrap();
|
let output = Command::new("xxx").arg("--help").output().unwrap();
|
||||||
let stdout = from_utf8(output.stdout).expect("Assume it is UTF-8 encoded");
|
let stdout = from_utf8(output.stdout).expect("Assume it is UTF-8 encoded");
|
||||||
|
|
||||||
if stdout.contains("help") {
|
if stdout.contains("help") {
|
||||||
// xxx works
|
// xxx works
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
If `xxx` respects locale, then the above code should work on English system,
|
If `xxx` respects locale, then the above code should work on English system,
|
||||||
on a system that does not use English, e.g., it uses Chinese, that `"help"` may be
|
on a system that does not use English, e.g., it uses Chinese, that `"help"` may be
|
||||||
translated to `"帮助"`, and the above code won't work.
|
translated to `"帮助"`, and the above code won't work.
|
||||||
|
|||||||
2372
Cargo.lock
generated
2372
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
42
Cargo.toml
42
Cargo.toml
@@ -5,25 +5,17 @@ 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"
|
||||||
rust-version = "1.76.0"
|
rust-version = "1.84.1"
|
||||||
version = "16.0.0"
|
version = "16.2.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", "BREAKINGCHANGES_dev.md"]
|
exclude = ["doc/screenshot.gif", "BREAKINGCHANGES_dev.md"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "topgrade"
|
|
||||||
path = "src/main.rs"
|
|
||||||
|
|
||||||
##[lib]
|
|
||||||
##name = "topgrade_lib"
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
home = "~0.5"
|
home = "~0.5,<0.5.11"
|
||||||
etcetera = "~0.8"
|
etcetera = "~0.8"
|
||||||
once_cell = "~1.19"
|
|
||||||
serde = { version = "~1.0", features = ["derive"] }
|
serde = { version = "~1.0", features = ["derive"] }
|
||||||
toml = "0.8"
|
toml = "0.8"
|
||||||
which_crate = { version = "~6.0", package = "which" }
|
which_crate = { version = "~6.0", package = "which" }
|
||||||
@@ -33,27 +25,33 @@ clap_complete = "~4.5"
|
|||||||
clap_mangen = "~0.2"
|
clap_mangen = "~0.2"
|
||||||
walkdir = "~2.5"
|
walkdir = "~2.5"
|
||||||
console = "~0.15"
|
console = "~0.15"
|
||||||
lazy_static = "~1.4"
|
|
||||||
chrono = "~0.4"
|
chrono = "~0.4"
|
||||||
glob = "~0.3"
|
glob = "~0.3"
|
||||||
strum = { version = "~0.26", features = ["derive"] }
|
strum = { version = "~0.26", features = ["derive"] }
|
||||||
thiserror = "~1.0"
|
thiserror = "~1.0"
|
||||||
tempfile = "~3.10"
|
tempfile = "~3.10"
|
||||||
cfg-if = "~1.0"
|
cfg-if = "~1.0"
|
||||||
tokio = { version = "~1.38", features = ["process", "rt-multi-thread"] }
|
tokio = { version = "~1.47", features = ["process", "rt-multi-thread"] }
|
||||||
futures = "~0.3"
|
futures = "~0.3"
|
||||||
regex = "~1.10"
|
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"
|
||||||
tracing = { version = "~0.1", features = ["attributes", "log"] }
|
tracing = { version = "~0.1", features = ["attributes", "log"] }
|
||||||
tracing-subscriber = { version = "~0.3", features = ["env-filter", "time"] }
|
tracing-subscriber = { version = "~0.3.20", features = ["env-filter", "time"] }
|
||||||
merge = "~0.1"
|
merge = "~0.1"
|
||||||
regex-split = "~0.1"
|
regex-split = "~0.1"
|
||||||
notify-rust = "~4.11"
|
notify-rust = "~4.11"
|
||||||
wildmatch = "2.3.0"
|
wildmatch = "2.3.0"
|
||||||
rust-i18n = "3.0.1"
|
rust-i18n = "3.0.1"
|
||||||
sys-locale = "0.3.1"
|
sys-locale = "0.3.1"
|
||||||
|
jetbrains-toolbox-updater = "5.0.0"
|
||||||
|
indexmap = { version = "2.9.0", features = ["serde"] }
|
||||||
|
serde_json = "1.0.145"
|
||||||
|
# Temporary transitive dependency pins
|
||||||
|
ignore = "=0.4.25"
|
||||||
|
globset = "=0.4.16"
|
||||||
|
base64ct = "<1.8.0"
|
||||||
|
|
||||||
[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" }]
|
||||||
@@ -71,6 +69,16 @@ extended-description = "Keeping your system up to date usually involves invoking
|
|||||||
section = "utils"
|
section = "utils"
|
||||||
priority = "optional"
|
priority = "optional"
|
||||||
default-features = true
|
default-features = true
|
||||||
|
assets = [
|
||||||
|
["target/release/topgrade", "usr/bin/", "755"],
|
||||||
|
["README.md", "usr/share/doc/topgrade/README.md", "644"],
|
||||||
|
# The man page and shell completions are automatically generated by topgrade's build process in CI,
|
||||||
|
# so these files aren't actually committed.
|
||||||
|
["deployment/deb/topgrade.1", "usr/share/man/man1/topgrade.1", "644"],
|
||||||
|
["deployment/deb/topgrade.bash", "usr/share/bash-completion/completions/topgrade", "644"],
|
||||||
|
["deployment/deb/topgrade.fish", "usr/share/fish/vendor_completions.d/topgrade.fish", "644"],
|
||||||
|
["deployment/deb/_topgrade", "usr/share/zsh/vendor-completions/", "644"],
|
||||||
|
]
|
||||||
|
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
nix = { version = "~0.29", features = ["hostname", "signal", "user"] }
|
nix = { version = "~0.29", features = ["hostname", "signal", "user"] }
|
||||||
@@ -78,9 +86,11 @@ rust-ini = "~0.21"
|
|||||||
self_update_crate = { version = "~0.40", default-features = false, optional = true, package = "self_update", features = ["archive-tar", "compression-flate2", "rustls"] }
|
self_update_crate = { version = "~0.40", default-features = false, optional = true, package = "self_update", features = ["archive-tar", "compression-flate2", "rustls"] }
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
self_update_crate = { version = "~0.40", default-features = false, optional = true, package = "self_update", features = ["archive-zip", "compression-zip-deflate", "rustls"] }
|
is_elevated = "~0.1"
|
||||||
winapi = "~0.3"
|
|
||||||
parselnk = "~0.1"
|
parselnk = "~0.1"
|
||||||
|
self_update_crate = { version = "~0.40", default-features = false, optional = true, package = "self_update", features = ["archive-zip", "compression-zip-deflate", "rustls"] }
|
||||||
|
windows = { version = "~0.62", features = ["Win32_System_Console"] }
|
||||||
|
windows-registry = "~0.6"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = true
|
lto = true
|
||||||
|
|||||||
68
README.md
68
README.md
@@ -11,7 +11,6 @@
|
|||||||
<img alt="Demo" src="doc/topgrade_demo.gif">
|
<img alt="Demo" src="doc/topgrade_demo.gif">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
> **Note**
|
> **Note**
|
||||||
@@ -25,19 +24,32 @@ To remedy this, **Topgrade** detects which tools you use and runs the appropriat
|
|||||||
|
|
||||||
[](https://repology.org/project/topgrade/versions)
|
[](https://repology.org/project/topgrade/versions)
|
||||||
|
|
||||||
- Arch Linux: [AUR](https://aur.archlinux.org/packages/topgrade)
|
### Official
|
||||||
- NixOS: [Nixpkgs](https://search.nixos.org/packages?show=topgrade)
|
|
||||||
- Void Linux: [XBPS](https://voidlinux.org/packages/?arch=x86_64&q=topgrade)
|
|
||||||
- macOS: [Homebrew](https://formulae.brew.sh/formula/topgrade) or [MacPorts](https://ports.macports.org/port/topgrade/)
|
|
||||||
- Windows: [Chocolatey][choco], [Scoop][scoop] or [Winget][winget]
|
|
||||||
- PyPi: [pip](https://pypi.org/project/topgrade/)
|
|
||||||
|
|
||||||
[choco]: https://community.chocolatey.org/packages/topgrade
|
- Self-updating binary (all platforms): [releases](https://github.com/topgrade-rs/topgrade/releases)
|
||||||
[scoop]: https://scoop.sh/#/apps?q=topgrade
|
- Install from source (all platforms): [`cargo install topgrade`](https://crates.io/crates/topgrade)
|
||||||
[winget]: https://winstall.app/apps/topgrade-rs.topgrade
|
- Debian/Ubuntu ([deb-get](https://github.com/wimpysworld/deb-get)):
|
||||||
|
[`deb-get install topgrade`](https://github.com/wimpysworld/deb-get/blob/main/01-main/packages/topgrade)
|
||||||
|
- Arch Linux (AUR): [topgrade](https://aur.archlinux.org/packages/topgrade)
|
||||||
|
or [topgrade-bin](https://aur.archlinux.org/packages/topgrade-bin)
|
||||||
|
- [PyPi](https://pypi.org/): `pip`, `pipx`, or `uv tool` [
|
||||||
|
`install topgrade`](https://pypi.org/project/topgrade/)
|
||||||
|
- Windows ([Winget](https://learn.microsoft.com/en-us/windows/package-manager/winget/)): [
|
||||||
|
`winget install --id=topgrade-rs.topgrade -e`](https://winstall.app/apps/topgrade-rs.topgrade)
|
||||||
|
- macOS or Linux ([Homebrew](https://brew.sh/)): [`brew install topgrade`](https://formulae.brew.sh/formula/topgrade)
|
||||||
|
|
||||||
Other systems users can either use `cargo install` or the compiled binaries from the release page.
|
### Community-maintained
|
||||||
The compiled binaries contain a self-upgrading feature.
|
|
||||||
|
- Windows ([Chocolatey](https://chocolatey.org/)): [
|
||||||
|
`choco install topgrade`](https://community.chocolatey.org/packages/topgrade)
|
||||||
|
- Windows ([Scoop](https://scoop.sh/)): [
|
||||||
|
`scoop bucket add main && scoop install main/topgrade`](https://scoop.sh/#/apps?q=topgrade)
|
||||||
|
- macOS ([MacPorts](https://www.macports.org/)): [
|
||||||
|
`sudo port install topgrade`](https://ports.macports.org/port/topgrade/)
|
||||||
|
- Fedora ([Copr](https://copr.fedorainfracloud.org/)): [
|
||||||
|
`dnf copr enable lilay/topgrade && dnf install topgrade`](https://copr.fedorainfracloud.org/coprs/lilay/topgrade/)
|
||||||
|
- NixOS or Nix (nixpkgs): [topgrade](https://search.nixos.org/packages?show=topgrade)
|
||||||
|
- Void Linux: [`sudo xbps-install -S topgrade`](https://voidlinux.org/packages/?arch=x86_64&q=topgrade)
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
@@ -58,6 +70,7 @@ it when updated to a major release.
|
|||||||
### Configuration Path
|
### Configuration Path
|
||||||
|
|
||||||
#### `CONFIG_DIR` on each platform
|
#### `CONFIG_DIR` on each platform
|
||||||
|
|
||||||
- **Windows**: `%APPDATA%`
|
- **Windows**: `%APPDATA%`
|
||||||
- **macOS** and **other Unix systems**: `${XDG_CONFIG_HOME:-~/.config}`
|
- **macOS** and **other Unix systems**: `${XDG_CONFIG_HOME:-~/.config}`
|
||||||
|
|
||||||
@@ -66,16 +79,21 @@ it when updated to a major release.
|
|||||||
1. `CONFIG_DIR/topgrade.toml`
|
1. `CONFIG_DIR/topgrade.toml`
|
||||||
2. `CONFIG_DIR/topgrade/topgrade.toml`
|
2. `CONFIG_DIR/topgrade/topgrade.toml`
|
||||||
|
|
||||||
If the file with higher priority is present, no matter it is valid or not, the other configuration files will be ignored.
|
If the file with higher priority is present, no matter it is valid or not, the other configuration files will be
|
||||||
|
ignored.
|
||||||
|
|
||||||
On the first run(no configuration file exists), `topgrade` will create a configuration file at `CONFIG_DIR/topgrade.toml` for you.
|
On the first run(no configuration file exists), `topgrade` will create a configuration file at
|
||||||
|
`CONFIG_DIR/topgrade.toml` for you.
|
||||||
|
|
||||||
### Custom Commands
|
### Custom Commands
|
||||||
|
|
||||||
Custom commands can be defined in the config file which can be run before, during, or after the inbuilt commands, as required.
|
Custom commands can be defined in the config file which can be run before, during, or after the inbuilt commands, as
|
||||||
By default, the custom commands are run using a new shell according to the `$SHELL` environment variable on unix (falls back to `sh`) or `pwsh` on windows (falls back to `powershell`).
|
required.
|
||||||
|
By default, the custom commands are run using a new shell according to the `$SHELL` environment variable on unix (falls
|
||||||
|
back to `sh`) or `pwsh` on windows (falls back to `powershell`).
|
||||||
|
|
||||||
On unix, if you want to run your command using an interactive shell, for example to source your shell's rc files, you can add `-i` at the start of your custom command.
|
On unix, if you want to run your command using an interactive shell, for example to source your shell's rc files, you
|
||||||
|
can add `-i` at the start of your custom command.
|
||||||
But note that this requires the command to exit the shell correctly or else the shell will hang indefinitely.
|
But note that this requires the command to exit the shell correctly or else the shell will hang indefinitely.
|
||||||
|
|
||||||
## Remote Execution
|
## Remote Execution
|
||||||
@@ -94,18 +112,14 @@ Open a new issue describing your problem and if possible provide a solution.
|
|||||||
### Missing a feature or found an unsupported tool/distro?
|
### Missing a feature or found an unsupported tool/distro?
|
||||||
|
|
||||||
Just let us now what you are missing by opening an issue.
|
Just let us now what you are missing by opening an issue.
|
||||||
For tools, please open an issue describing the tool, which platforms it supports and if possible, give us an example of its usage.
|
For tools, please open an issue describing the tool, which platforms it supports and if possible, give us an example of
|
||||||
|
its usage.
|
||||||
|
|
||||||
### Want to contribute to the code?
|
### Want to contribute?
|
||||||
|
|
||||||
Just fork the repository and start coding.
|
|
||||||
|
|
||||||
### Contribution Guidelines
|
|
||||||
|
|
||||||
See [CONTRIBUTING.md](https://github.com/topgrade-rs/topgrade/blob/master/CONTRIBUTING.md)
|
See [CONTRIBUTING.md](https://github.com/topgrade-rs/topgrade/blob/master/CONTRIBUTING.md)
|
||||||
|
|
||||||
## Roadmap
|
## Discord server
|
||||||
|
|
||||||
- [ ] Add a proper testing framework to the code base.
|
Welcome to [join](https://discord.gg/Q8HGGWundY) our Discord server if you want
|
||||||
- [ ] Add unit tests for package managers.
|
to discuss Topgrade!
|
||||||
- [ ] Split up code into more maintainable parts, eg. putting every linux package manager in a own submodule of linux.rs.
|
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
> This document lists the steps that lead to a successful release of Topgrade.
|
Non-major versions go via release-plz.
|
||||||
|
|
||||||
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.
|
1. bumps the version number.
|
||||||
|
|
||||||
> If there are breaking changes, the major version number should be increased.
|
> If there are breaking changes, the major version number should be increased.
|
||||||
|
|
||||||
2. Overwrite [`BREAKINGCHANGES`][breaking_changes] with
|
2. If the major versioin number gets bumped, update [SECURITY.md][SECURITY_file_link].
|
||||||
|
|
||||||
|
[SECURITY_file_link]: https://github.com/topgrade-rs/topgrade/blob/main/SECURITY.md
|
||||||
|
|
||||||
|
3. Overwrite [`BREAKINGCHANGES`][breaking_changes] with
|
||||||
[`BREAKINGCHANGES_dev`][breaking_changes_dev], and create a new dev file:
|
[`BREAKINGCHANGES_dev`][breaking_changes_dev], and create a new dev file:
|
||||||
|
|
||||||
```sh'
|
```sh'
|
||||||
@@ -20,46 +19,3 @@
|
|||||||
|
|
||||||
[breaking_changes_dev]: https://github.com/topgrade-rs/topgrade/blob/main/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
|
[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)
|
|
||||||
4. Winget
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
12
SECURITY.md
12
SECURITY.md
@@ -1,11 +1,9 @@
|
|||||||
# Security Policy
|
# Security Policy
|
||||||
|
|
||||||
|
## Reporting a vulnerability
|
||||||
|
|
||||||
|
To report a security vulnerability, go to [the security tab](https://github.com/topgrade-rs/topgrade/security) and click "Report a vulnerability".
|
||||||
|
|
||||||
## Supported Versions
|
## Supported Versions
|
||||||
|
|
||||||
We only support the latest major version and each subversion.
|
We only support the latest version of Topgrade. Fixes are not backported.
|
||||||
|
|
||||||
| Version | Supported |
|
|
||||||
| -------- | ------------------ |
|
|
||||||
| 15.0.x | :white_check_mark: |
|
|
||||||
| < 15.0 | :x: |
|
|
||||||
|
|
||||||
|
|||||||
35
build-all.sh
35
build-all.sh
@@ -1,4 +1,5 @@
|
|||||||
#!/usr/bin/env sh
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
build_function() {
|
build_function() {
|
||||||
rustup update
|
rustup update
|
||||||
cargo install cross
|
cargo install cross
|
||||||
@@ -20,7 +21,7 @@ build_function() {
|
|||||||
|
|
||||||
package_function() {
|
package_function() {
|
||||||
|
|
||||||
cd build
|
cd build || exit 1
|
||||||
mkdir x86_64-unknown-linux-gnu/
|
mkdir x86_64-unknown-linux-gnu/
|
||||||
mkdir x86_64-unknown-linux-musl/
|
mkdir x86_64-unknown-linux-musl/
|
||||||
mkdir x86_64-unknown-freebsd/
|
mkdir x86_64-unknown-freebsd/
|
||||||
@@ -35,28 +36,28 @@ package_function() {
|
|||||||
cp ../target/aarch64-unknown-linux-musl/release/topgrade aarch64-unknown-linux-musl/topgrade
|
cp ../target/aarch64-unknown-linux-musl/release/topgrade aarch64-unknown-linux-musl/topgrade
|
||||||
cp ../target/x86_64-pc-windows-gnu/release/topgrade.exe x86_64-pc-windows-gnu/topgrade.exe
|
cp ../target/x86_64-pc-windows-gnu/release/topgrade.exe x86_64-pc-windows-gnu/topgrade.exe
|
||||||
|
|
||||||
cd x86_64-unknown-linux-gnu/
|
cd x86_64-unknown-linux-gnu/ || exit 1
|
||||||
tar -czf ../topgrade-${ans}-x86_64-linux-gnu.tar.gz topgrade
|
tar -czf "../topgrade-${ans}-x86_64-linux-gnu.tar.gz" topgrade
|
||||||
cd ..
|
cd ..
|
||||||
|
|
||||||
cd x86_64-unknown-linux-musl
|
cd x86_64-unknown-linux-musl/ || exit 1
|
||||||
tar -czf ../topgrade-${ans}-x86_64-linux-musl.tar.gz topgrade
|
tar -czf "../topgrade-${ans}-x86_64-linux-musl.tar.gz" topgrade
|
||||||
cd ..
|
cd ..
|
||||||
|
|
||||||
cd x86_64-unknown-freebsd/
|
cd x86_64-unknown-freebsd/ || exit 1
|
||||||
tar -czf ../topgrade-${ans}-x86_64-freebsd.tar.gz topgrade
|
tar -czf "../topgrade-${ans}-x86_64-freebsd.tar.gz" topgrade
|
||||||
cd ..
|
cd ..
|
||||||
|
|
||||||
cd aarch64-unknown-linux-gnu/
|
cd aarch64-unknown-linux-gnu/ || exit 1
|
||||||
tar -czf ../topgrade-${ans}-aarch64-linux-gnu.tar.gz topgrade
|
tar -czf "../topgrade-${ans}-aarch64-linux-gnu.tar.gz" topgrade
|
||||||
cd ..
|
cd ..
|
||||||
|
|
||||||
cd aarch64-unknown-linux-musl/
|
cd aarch64-unknown-linux-musl/ || exit 1
|
||||||
tar -czf ../topgrade-${ans}-aarch64-linux-musl.tar.gz topgrade
|
tar -czf "../topgrade-${ans}-aarch64-linux-musl.tar.gz" topgrade
|
||||||
cd ..
|
cd ..
|
||||||
|
|
||||||
cd x86_64-pc-windows-gnu/
|
cd x86_64-pc-windows-gnu/ || exit 1
|
||||||
zip -q ../topgrade-${ans}-x86_64-windows.zip topgrade.exe
|
zip -q "../topgrade-${ans}-x86_64-windows.zip" topgrade.exe
|
||||||
cd ..
|
cd ..
|
||||||
cd ..
|
cd ..
|
||||||
|
|
||||||
@@ -65,17 +66,19 @@ package_function() {
|
|||||||
print_checksums() {
|
print_checksums() {
|
||||||
|
|
||||||
|
|
||||||
cd build/
|
cd build/ || exit 1
|
||||||
sha256sum topgrade-${ans}-*
|
sha256sum topgrade-"${ans}"-*
|
||||||
cd ../
|
cd ../
|
||||||
}
|
}
|
||||||
|
|
||||||
while true; do
|
while true; do
|
||||||
|
|
||||||
echo "You should always have a look on scripts you download from the internet."
|
echo "You should always have a look on scripts you download from the internet."
|
||||||
|
# shellcheck disable=SC2162
|
||||||
read -p "Do you still want to proceed? (y/n) " yn
|
read -p "Do you still want to proceed? (y/n) " yn
|
||||||
|
|
||||||
echo -n "Input version number: "
|
echo -n "Input version number: "
|
||||||
|
# shellcheck disable=SC2162
|
||||||
read ans
|
read ans
|
||||||
mkdir build
|
mkdir build
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,13 @@
|
|||||||
|
|
||||||
|
|
||||||
[misc]
|
[misc]
|
||||||
|
# On Unix systems, Topgrade should not be run as root, it
|
||||||
|
# will run commands with sudo or equivalent where needed.
|
||||||
|
# Set this to true to suppress the warning and confirmation
|
||||||
|
# prompt if Topgrade detects it is being run as root.
|
||||||
|
# (default: false)
|
||||||
|
# allow_root = false
|
||||||
|
|
||||||
# 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
|
||||||
# (default: false)
|
# (default: false)
|
||||||
@@ -44,6 +51,10 @@
|
|||||||
# Do not ask to retry failed steps (default: false)
|
# Do not ask to retry failed steps (default: false)
|
||||||
# no_retry = true
|
# no_retry = true
|
||||||
|
|
||||||
|
# Show the reason for skipped steps (default: false)
|
||||||
|
# This has no effect if the "only" option is specified
|
||||||
|
# show_skipped = true
|
||||||
|
|
||||||
# Run inside tmux (default: false)
|
# Run inside tmux (default: false)
|
||||||
# run_in_tmux = true
|
# run_in_tmux = true
|
||||||
|
|
||||||
@@ -80,6 +91,11 @@
|
|||||||
# See: https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives
|
# See: https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives
|
||||||
# log_filters = ["topgrade::command=debug", "warn"]
|
# log_filters = ["topgrade::command=debug", "warn"]
|
||||||
|
|
||||||
|
# Whether to show a distribution-specific summary if applicable, e.g. listing
|
||||||
|
# Pacman backup configuration files (*.pacsave and *.pacnew)
|
||||||
|
# (default: true)
|
||||||
|
# show_distribution_summary = false
|
||||||
|
|
||||||
|
|
||||||
# Commands to run before anything
|
# Commands to run before anything
|
||||||
[pre_commands]
|
[pre_commands]
|
||||||
@@ -103,6 +119,26 @@
|
|||||||
# 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
|
||||||
|
|
||||||
|
# For the poetry step, by default, Topgrade skips its update if poetry is not
|
||||||
|
# installed with the official script. This configuration entry forces Topgrade
|
||||||
|
# to run the update in this case.
|
||||||
|
#
|
||||||
|
# (default: false)
|
||||||
|
# poetry_force_self_update = true
|
||||||
|
|
||||||
|
|
||||||
|
[conda]
|
||||||
|
# Additional named conda environments to update (`conda env update -n env_name`)
|
||||||
|
# env_names = [
|
||||||
|
# "Toolbox",
|
||||||
|
# "PyTorch"
|
||||||
|
# ]
|
||||||
|
# Additional conda environment paths to update (`conda env update -p env_path`)
|
||||||
|
# env_paths = [
|
||||||
|
# "~/webserver/.conda/",
|
||||||
|
# "~/experiments/.conda/"
|
||||||
|
# ]
|
||||||
|
|
||||||
|
|
||||||
[composer]
|
[composer]
|
||||||
# self_update = true
|
# self_update = true
|
||||||
@@ -172,6 +208,11 @@
|
|||||||
|
|
||||||
# rpm_ostree = false
|
# rpm_ostree = false
|
||||||
|
|
||||||
|
# For Fedora/CentOS/RHEL Atomic variants, if `bootc` is available and this configuration entry is set to true, use
|
||||||
|
# it to do the update - Will also supersede rpm-ostree if enabled
|
||||||
|
# (default: false)
|
||||||
|
# bootc = false
|
||||||
|
|
||||||
# nix_arguments = "--flake"
|
# nix_arguments = "--flake"
|
||||||
|
|
||||||
# nix_env_arguments = "--prebuilt-only"
|
# nix_env_arguments = "--prebuilt-only"
|
||||||
@@ -180,6 +221,20 @@
|
|||||||
# home_manager_arguments = ["--flake", "file"]
|
# home_manager_arguments = ["--flake", "file"]
|
||||||
|
|
||||||
|
|
||||||
|
[mandb]
|
||||||
|
# Enable the mandb step (to update manual entries).
|
||||||
|
# Mandb is updated in the background by a service on most systems by default.
|
||||||
|
# (default: false)
|
||||||
|
# enable = true
|
||||||
|
|
||||||
|
|
||||||
|
[pkgfile]
|
||||||
|
# Enable the pkgfile step (to update the pkgfile database).
|
||||||
|
# Pkgfile is sometimes installed by default, but often not used and heavy to update.
|
||||||
|
# (default: false)
|
||||||
|
# enable = true
|
||||||
|
|
||||||
|
|
||||||
[git]
|
[git]
|
||||||
# How many repos to pull at max in parallel
|
# How many repos to pull at max in parallel
|
||||||
# max_concurrency = 5
|
# max_concurrency = 5
|
||||||
@@ -201,17 +256,40 @@
|
|||||||
# Manually select Windows updates
|
# Manually select Windows updates
|
||||||
# accept_all_updates = false
|
# accept_all_updates = false
|
||||||
|
|
||||||
|
# Controls whether to automatically reboot the computer when updates are
|
||||||
|
# installed that request it. (default: "no", allowed values: "yes", "no", "ask")
|
||||||
|
# updates_auto_reboot = "yes"
|
||||||
|
|
||||||
# open_remotes_in_new_terminal = true
|
# open_remotes_in_new_terminal = true
|
||||||
|
|
||||||
# wsl_update_pre_release = true
|
# wsl_update_pre_release = true
|
||||||
|
|
||||||
# wsl_update_use_web_download = true
|
# wsl_update_use_web_download = true
|
||||||
|
|
||||||
|
# The default for winget_install_silently is true,
|
||||||
|
# this example turns off silent install.
|
||||||
|
# winget_install_silently = false
|
||||||
|
|
||||||
# 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
|
||||||
|
|
||||||
|
# Use sudo to elevate privileges for the Windows Package Manager (winget)
|
||||||
|
# Only use this option if you want to run the Winget step in sudo-mode.
|
||||||
|
# Running winget in sudo-mode is generally not recommended, as not every
|
||||||
|
# package supports installing / upgrading in sudo-mode and it may cause issues
|
||||||
|
# with some packages or may even cause the Winget-step to fail.
|
||||||
|
# If any problems occur, please try running Topgrade without this option first
|
||||||
|
# before reporting an issue.
|
||||||
|
# (default: false)
|
||||||
|
# winget_use_sudo = true
|
||||||
|
|
||||||
|
|
||||||
|
[chezmoi]
|
||||||
|
# Exclude encrypted files from update
|
||||||
|
# (default: false)
|
||||||
|
# exclude_encrypted = false
|
||||||
|
|
||||||
[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
|
||||||
@@ -223,6 +301,11 @@
|
|||||||
# use_sudo = true
|
# use_sudo = true
|
||||||
|
|
||||||
|
|
||||||
|
[deno]
|
||||||
|
# Upgrade deno executable to the given version.
|
||||||
|
# version = "stable"
|
||||||
|
|
||||||
|
|
||||||
[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
|
||||||
@@ -260,8 +343,64 @@
|
|||||||
# runtime = "podman"
|
# runtime = "podman"
|
||||||
|
|
||||||
[lensfun]
|
[lensfun]
|
||||||
# If disabled, Topgrade invokes `lensfun‑update‑data` without root priviledge,
|
# If disabled, Topgrade invokes `lensfun‑update‑data` without root privilege,
|
||||||
# then the update will be only available to you. Otherwise, `sudo` is required,
|
# then the update will be only available to you. Otherwise, `sudo` is required,
|
||||||
# and the update will be installed system-wide, i.e., available to all users.
|
# and the update will be installed system-wide, i.e., available to all users.
|
||||||
# (default: false)
|
# (default: false)
|
||||||
# use_sudo = false
|
# use_sudo = false
|
||||||
|
|
||||||
|
[julia]
|
||||||
|
# If disabled, Topgrade invokes julia with the --startup-file=no CLI option.
|
||||||
|
#
|
||||||
|
# This may be desirable to avoid loading outdated packages with "using" directives
|
||||||
|
# in the startup file, which might cause the update run to fail.
|
||||||
|
# (default: true)
|
||||||
|
# startup_file = true
|
||||||
|
|
||||||
|
[zigup]
|
||||||
|
# Version strings passed to zigup.
|
||||||
|
# These may be pinned versions such as "0.13.0" or branches such as "master".
|
||||||
|
# Each one will be updated in its own zigup invocation.
|
||||||
|
# (default: ["master"])
|
||||||
|
# target_versions = ["master", "0.13.0"]
|
||||||
|
|
||||||
|
# Specifies the directory that the zig files will be installed to.
|
||||||
|
# If defined, passed with the --install-dir command line flag.
|
||||||
|
# If not defined, zigup will use its default behaviour.
|
||||||
|
# (default: not defined)
|
||||||
|
# install_dir = "~/.zig"
|
||||||
|
|
||||||
|
# Specifies the path of the symlink which will be set to point at the default compiler version.
|
||||||
|
# If defined, passed with the --path-link command line flag.
|
||||||
|
# If not defined, zigup will use its default behaviour.
|
||||||
|
# This is not meaningful if set_default is not enabled.
|
||||||
|
# (default: not defined)
|
||||||
|
# path_link = "~/.bin/zig"
|
||||||
|
|
||||||
|
# If enabled, run `zigup clean` after updating all versions.
|
||||||
|
# If enabled, each updated version above will be marked with `zigup keep`.
|
||||||
|
# (default: false)
|
||||||
|
# cleanup = false
|
||||||
|
|
||||||
|
[vscode]
|
||||||
|
# If this is set and is a non-empty string, it specifies the profile the
|
||||||
|
# extensions should be updated for.
|
||||||
|
# (default: this won't be set by default)
|
||||||
|
# profile = ""
|
||||||
|
|
||||||
|
[pixi]
|
||||||
|
# Show the release notes of the latest pixi release
|
||||||
|
# during the pixi step
|
||||||
|
# (default: false)
|
||||||
|
# include_release_notes = false
|
||||||
|
|
||||||
|
[doom]
|
||||||
|
# If this is set to true, the `--aot` flag is added to `doom upgrade`,
|
||||||
|
# which enables ahead-of-time native compilation of packages.
|
||||||
|
# (default: false)
|
||||||
|
# aot = true
|
||||||
|
|
||||||
|
[rustup]
|
||||||
|
# If set, updates only these channels.
|
||||||
|
# (default: [] (all channels))
|
||||||
|
# channels = ["stable"]
|
||||||
|
|||||||
1163
locales/app.yml
1163
locales/app.yml
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,7 @@ build-backend = "maturin"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "topgrade"
|
name = "topgrade"
|
||||||
|
dynamic = ["version"]
|
||||||
requires-python = ">=3.7"
|
requires-python = ">=3.7"
|
||||||
classifiers = [
|
classifiers = [
|
||||||
"Programming Language :: Rust",
|
"Programming Language :: Rust",
|
||||||
|
|||||||
10
renovate.json
Normal file
10
renovate.json
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
|
"extends": [
|
||||||
|
"config:best-practices",
|
||||||
|
":semanticCommits"
|
||||||
|
],
|
||||||
|
"lockFileMaintenance": {
|
||||||
|
"enabled": true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,2 +1,2 @@
|
|||||||
[toolchain]
|
[toolchain]
|
||||||
channel = "1.76.0"
|
channel = "1.84.1"
|
||||||
|
|||||||
@@ -45,13 +45,13 @@ impl TryFrom<&Output> for Utf8Output {
|
|||||||
type Error = eyre::Error;
|
type Error = eyre::Error;
|
||||||
|
|
||||||
fn try_from(Output { status, stdout, stderr }: &Output) -> Result<Self, Self::Error> {
|
fn try_from(Output { status, stdout, stderr }: &Output) -> Result<Self, Self::Error> {
|
||||||
let stdout = String::from_utf8(stdout.to_vec()).map_err(|err| {
|
let stdout = String::from_utf8(stdout.clone()).map_err(|err| {
|
||||||
eyre!(
|
eyre!(
|
||||||
"Stdout contained invalid UTF-8: {}",
|
"Stdout contained invalid UTF-8: {}",
|
||||||
String::from_utf8_lossy(err.as_bytes())
|
String::from_utf8_lossy(err.as_bytes())
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
let stderr = String::from_utf8(stderr.to_vec()).map_err(|err| {
|
let stderr = String::from_utf8(stderr.clone()).map_err(|err| {
|
||||||
eyre!(
|
eyre!(
|
||||||
"Stderr contained invalid UTF-8: {}",
|
"Stderr contained invalid UTF-8: {}",
|
||||||
String::from_utf8_lossy(err.as_bytes())
|
String::from_utf8_lossy(err.as_bytes())
|
||||||
@@ -115,6 +115,9 @@ pub trait CommandExt {
|
|||||||
///
|
///
|
||||||
/// Returns an `Err` if the command failed to execute, if `succeeded` returns an `Err`, or if
|
/// Returns an `Err` if the command failed to execute, if `succeeded` returns an `Err`, or if
|
||||||
/// the output contains invalid UTF-8.
|
/// the output contains invalid UTF-8.
|
||||||
|
// This function is currently unused, but is useful and makes sense with `output_checked_with`
|
||||||
|
// and `output_checked_utf8` existing.
|
||||||
|
#[allow(dead_code)]
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn output_checked_with_utf8(
|
fn output_checked_with_utf8(
|
||||||
&mut self,
|
&mut self,
|
||||||
@@ -149,6 +152,7 @@ pub trait CommandExt {
|
|||||||
/// Like [`Command::spawn`], but gives a nice error message if the command fails to
|
/// Like [`Command::spawn`], but gives a nice error message if the command fails to
|
||||||
/// execute.
|
/// execute.
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
|
#[allow(dead_code)]
|
||||||
fn spawn_checked(&mut self) -> eyre::Result<Self::Child>;
|
fn spawn_checked(&mut self) -> eyre::Result<Self::Child>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
545
src/config.rs
545
src/config.rs
@@ -1,6 +1,5 @@
|
|||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
use std::collections::BTreeMap;
|
|
||||||
use std::fs::{write, File};
|
use std::fs::{write, File};
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
@@ -12,19 +11,21 @@ use clap_complete::Shell;
|
|||||||
use color_eyre::eyre::Context;
|
use color_eyre::eyre::Context;
|
||||||
use color_eyre::eyre::Result;
|
use color_eyre::eyre::Result;
|
||||||
use etcetera::base_strategy::BaseStrategy;
|
use etcetera::base_strategy::BaseStrategy;
|
||||||
|
use indexmap::IndexMap;
|
||||||
use merge::Merge;
|
use merge::Merge;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use regex_split::RegexSplit;
|
use regex_split::RegexSplit;
|
||||||
use rust_i18n::t;
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use strum::{EnumIter, EnumString, IntoEnumIterator, VariantNames};
|
use strum::IntoEnumIterator;
|
||||||
|
use tracing::{debug, error};
|
||||||
use which_crate::which;
|
use which_crate::which;
|
||||||
|
|
||||||
use super::utils::editor;
|
use super::utils::editor;
|
||||||
use crate::command::CommandExt;
|
use crate::command::CommandExt;
|
||||||
|
use crate::execution_context::RunType;
|
||||||
|
use crate::step::Step;
|
||||||
use crate::sudo::SudoKind;
|
use crate::sudo::SudoKind;
|
||||||
use crate::utils::string_prepend_str;
|
use crate::utils::string_prepend_str;
|
||||||
use tracing::{debug, error};
|
|
||||||
|
|
||||||
// TODO: Add i18n to this. Tracking issue: https://github.com/topgrade-rs/topgrade/issues/859
|
// TODO: Add i18n to this. Tracking issue: https://github.com/topgrade-rs/topgrade/issues/859
|
||||||
pub static EXAMPLE_CONFIG: &str = include_str!("../config.example.toml");
|
pub static EXAMPLE_CONFIG: &str = include_str!("../config.example.toml");
|
||||||
@@ -44,133 +45,7 @@ macro_rules! str_value {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Commands = BTreeMap<String, String>;
|
pub type Commands = IndexMap<String, String>;
|
||||||
|
|
||||||
#[derive(ValueEnum, EnumString, VariantNames, Debug, Clone, PartialEq, Eq, Deserialize, EnumIter, Copy)]
|
|
||||||
#[clap(rename_all = "snake_case")]
|
|
||||||
#[serde(rename_all = "snake_case")]
|
|
||||||
#[strum(serialize_all = "snake_case")]
|
|
||||||
pub enum Step {
|
|
||||||
AM,
|
|
||||||
AppMan,
|
|
||||||
Asdf,
|
|
||||||
Atom,
|
|
||||||
Aqua,
|
|
||||||
Audit,
|
|
||||||
AutoCpufreq,
|
|
||||||
Bin,
|
|
||||||
Bob,
|
|
||||||
BrewCask,
|
|
||||||
BrewFormula,
|
|
||||||
Bun,
|
|
||||||
BunPackages,
|
|
||||||
Cargo,
|
|
||||||
Certbot,
|
|
||||||
Chezmoi,
|
|
||||||
Chocolatey,
|
|
||||||
Choosenim,
|
|
||||||
ClamAvDb,
|
|
||||||
Composer,
|
|
||||||
Conda,
|
|
||||||
ConfigUpdate,
|
|
||||||
Containers,
|
|
||||||
CustomCommands,
|
|
||||||
DebGet,
|
|
||||||
Deno,
|
|
||||||
Distrobox,
|
|
||||||
DkpPacman,
|
|
||||||
Dotnet,
|
|
||||||
Elan,
|
|
||||||
Emacs,
|
|
||||||
Firmware,
|
|
||||||
Flatpak,
|
|
||||||
Flutter,
|
|
||||||
Fossil,
|
|
||||||
Gcloud,
|
|
||||||
Gem,
|
|
||||||
Ghcup,
|
|
||||||
GithubCliExtensions,
|
|
||||||
GitRepos,
|
|
||||||
GnomeShellExtensions,
|
|
||||||
Go,
|
|
||||||
Guix,
|
|
||||||
Haxelib,
|
|
||||||
Helm,
|
|
||||||
HomeManager,
|
|
||||||
Jetpack,
|
|
||||||
Julia,
|
|
||||||
Juliaup,
|
|
||||||
Kakoune,
|
|
||||||
Helix,
|
|
||||||
Krew,
|
|
||||||
Lure,
|
|
||||||
Lensfun,
|
|
||||||
Macports,
|
|
||||||
Mamba,
|
|
||||||
Miktex,
|
|
||||||
Mas,
|
|
||||||
Maza,
|
|
||||||
Micro,
|
|
||||||
Mise,
|
|
||||||
Myrepos,
|
|
||||||
Nix,
|
|
||||||
Node,
|
|
||||||
Opam,
|
|
||||||
Pacdef,
|
|
||||||
Pacstall,
|
|
||||||
Pearl,
|
|
||||||
Pip3,
|
|
||||||
PipReview,
|
|
||||||
PipReviewLocal,
|
|
||||||
Pipupgrade,
|
|
||||||
Pipx,
|
|
||||||
Pixi,
|
|
||||||
Pkg,
|
|
||||||
Pkgin,
|
|
||||||
PlatformioCore,
|
|
||||||
Pnpm,
|
|
||||||
Poetry,
|
|
||||||
Powershell,
|
|
||||||
Protonup,
|
|
||||||
Pyenv,
|
|
||||||
Raco,
|
|
||||||
Rcm,
|
|
||||||
Remotes,
|
|
||||||
Restarts,
|
|
||||||
Rtcl,
|
|
||||||
RubyGems,
|
|
||||||
Rustup,
|
|
||||||
Rye,
|
|
||||||
Scoop,
|
|
||||||
Sdkman,
|
|
||||||
SelfUpdate,
|
|
||||||
Sheldon,
|
|
||||||
Shell,
|
|
||||||
Snap,
|
|
||||||
Sparkle,
|
|
||||||
Spicetify,
|
|
||||||
Stack,
|
|
||||||
Stew,
|
|
||||||
System,
|
|
||||||
Tldr,
|
|
||||||
Tlmgr,
|
|
||||||
Tmux,
|
|
||||||
Toolbx,
|
|
||||||
Uv,
|
|
||||||
Vagrant,
|
|
||||||
Vcpkg,
|
|
||||||
Vim,
|
|
||||||
VoltaPackages,
|
|
||||||
Vscode,
|
|
||||||
Waydroid,
|
|
||||||
Winget,
|
|
||||||
Wsl,
|
|
||||||
WslUpdate,
|
|
||||||
Xcodes,
|
|
||||||
Yadm,
|
|
||||||
Yarn,
|
|
||||||
Zvm,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Deserialize, Default, Debug, Merge)]
|
#[derive(Deserialize, Default, Debug, Merge)]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
@@ -187,6 +62,12 @@ pub struct Containers {
|
|||||||
runtime: Option<ContainerRuntime>,
|
runtime: Option<ContainerRuntime>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Default, Debug, Merge)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
pub struct Mandb {
|
||||||
|
enable: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Default, Debug, Merge)]
|
#[derive(Deserialize, Default, Debug, Merge)]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct Git {
|
pub struct Git {
|
||||||
@@ -211,14 +92,26 @@ pub struct Vagrant {
|
|||||||
always_suspend: Option<bool>,
|
always_suspend: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Default, Debug, Copy, Clone)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum UpdatesAutoReboot {
|
||||||
|
Yes,
|
||||||
|
#[default]
|
||||||
|
No,
|
||||||
|
Ask,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Default, Debug, Merge)]
|
#[derive(Deserialize, Default, Debug, Merge)]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct Windows {
|
pub struct Windows {
|
||||||
accept_all_updates: Option<bool>,
|
accept_all_updates: Option<bool>,
|
||||||
|
updates_auto_reboot: Option<UpdatesAutoReboot>,
|
||||||
self_rename: Option<bool>,
|
self_rename: Option<bool>,
|
||||||
open_remotes_in_new_terminal: Option<bool>,
|
open_remotes_in_new_terminal: Option<bool>,
|
||||||
wsl_update_pre_release: Option<bool>,
|
wsl_update_pre_release: Option<bool>,
|
||||||
wsl_update_use_web_download: Option<bool>,
|
wsl_update_use_web_download: Option<bool>,
|
||||||
|
winget_silent_install: Option<bool>,
|
||||||
|
winget_use_sudo: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Default, Debug, Merge)]
|
#[derive(Deserialize, Default, Debug, Merge)]
|
||||||
@@ -228,6 +121,17 @@ pub struct Python {
|
|||||||
enable_pip_review_local: Option<bool>,
|
enable_pip_review_local: Option<bool>,
|
||||||
enable_pipupgrade: Option<bool>,
|
enable_pipupgrade: Option<bool>,
|
||||||
pipupgrade_arguments: Option<String>,
|
pipupgrade_arguments: Option<String>,
|
||||||
|
poetry_force_self_update: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Default, Debug, Merge)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
pub struct Conda {
|
||||||
|
#[merge(strategy = crate::utils::merge_strategies::vec_prepend_opt)]
|
||||||
|
env_names: Option<Vec<String>>,
|
||||||
|
|
||||||
|
#[merge(strategy = crate::utils::merge_strategies::vec_prepend_opt)]
|
||||||
|
env_paths: Option<Vec<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Default, Debug, Merge)]
|
#[derive(Deserialize, Default, Debug, Merge)]
|
||||||
@@ -254,6 +158,20 @@ pub struct NPM {
|
|||||||
use_sudo: Option<bool>,
|
use_sudo: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Default, Debug, Merge)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
#[allow(clippy::upper_case_acronyms)]
|
||||||
|
pub struct Deno {
|
||||||
|
version: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Default, Debug, Merge)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
#[allow(clippy::upper_case_acronyms)]
|
||||||
|
pub struct Chezmoi {
|
||||||
|
exclude_encrypted: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Default, Debug, Merge)]
|
#[derive(Deserialize, Default, Debug, Merge)]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
#[allow(clippy::upper_case_acronyms)]
|
#[allow(clippy::upper_case_acronyms)]
|
||||||
@@ -268,6 +186,13 @@ pub struct Flatpak {
|
|||||||
use_sudo: Option<bool>,
|
use_sudo: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Default, Debug, Merge)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
#[allow(clippy::upper_case_acronyms)]
|
||||||
|
pub struct Pixi {
|
||||||
|
include_release_notes: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Default, Debug, Merge)]
|
#[derive(Deserialize, Default, Debug, Merge)]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct Brew {
|
pub struct Brew {
|
||||||
@@ -350,6 +275,7 @@ pub struct Linux {
|
|||||||
redhat_distro_sync: Option<bool>,
|
redhat_distro_sync: Option<bool>,
|
||||||
suse_dup: Option<bool>,
|
suse_dup: Option<bool>,
|
||||||
rpm_ostree: Option<bool>,
|
rpm_ostree: Option<bool>,
|
||||||
|
bootc: Option<bool>,
|
||||||
|
|
||||||
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
|
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
|
||||||
emerge_sync_flags: Option<String>,
|
emerge_sync_flags: Option<String>,
|
||||||
@@ -376,6 +302,8 @@ pub struct Vim {
|
|||||||
#[derive(Deserialize, Default, Debug, Merge)]
|
#[derive(Deserialize, Default, Debug, Merge)]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
pub struct Misc {
|
pub struct Misc {
|
||||||
|
allow_root: Option<bool>,
|
||||||
|
|
||||||
pre_sudo: Option<bool>,
|
pre_sudo: Option<bool>,
|
||||||
|
|
||||||
sudo_command: Option<SudoKind>,
|
sudo_command: Option<SudoKind>,
|
||||||
@@ -405,6 +333,8 @@ pub struct Misc {
|
|||||||
|
|
||||||
no_retry: Option<bool>,
|
no_retry: Option<bool>,
|
||||||
|
|
||||||
|
show_skipped: Option<bool>,
|
||||||
|
|
||||||
run_in_tmux: Option<bool>,
|
run_in_tmux: Option<bool>,
|
||||||
|
|
||||||
tmux_session_mode: Option<TmuxSessionMode>,
|
tmux_session_mode: Option<TmuxSessionMode>,
|
||||||
@@ -423,6 +353,8 @@ pub struct Misc {
|
|||||||
no_self_update: Option<bool>,
|
no_self_update: Option<bool>,
|
||||||
|
|
||||||
log_filters: Option<Vec<String>>,
|
log_filters: Option<Vec<String>>,
|
||||||
|
|
||||||
|
show_distribution_summary: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Deserialize, ValueEnum)]
|
#[derive(Clone, Copy, Debug, Deserialize, ValueEnum)]
|
||||||
@@ -444,6 +376,45 @@ pub struct Lensfun {
|
|||||||
use_sudo: Option<bool>,
|
use_sudo: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Default, Debug, Merge)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
pub struct JuliaConfig {
|
||||||
|
startup_file: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Default, Debug, Merge)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
pub struct Zigup {
|
||||||
|
target_versions: Option<Vec<String>>,
|
||||||
|
install_dir: Option<String>,
|
||||||
|
path_link: Option<String>,
|
||||||
|
cleanup: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Default, Debug, Merge)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
pub struct VscodeConfig {
|
||||||
|
profile: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Default, Debug, Merge)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
pub struct DoomConfig {
|
||||||
|
aot: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Default, Debug, Merge)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
pub struct Rustup {
|
||||||
|
channels: Option<Vec<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Default, Debug, Merge)]
|
||||||
|
#[serde(deny_unknown_fields)]
|
||||||
|
pub struct Pkgfile {
|
||||||
|
enable: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Default, Debug, Merge)]
|
#[derive(Deserialize, Default, Debug, Merge)]
|
||||||
#[serde(deny_unknown_fields)]
|
#[serde(deny_unknown_fields)]
|
||||||
/// Configuration file
|
/// Configuration file
|
||||||
@@ -463,6 +434,9 @@ pub struct ConfigFile {
|
|||||||
#[merge(strategy = crate::utils::merge_strategies::commands_merge_opt)]
|
#[merge(strategy = crate::utils::merge_strategies::commands_merge_opt)]
|
||||||
commands: Option<Commands>,
|
commands: Option<Commands>,
|
||||||
|
|
||||||
|
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
||||||
|
conda: Option<Conda>,
|
||||||
|
|
||||||
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
||||||
python: Option<Python>,
|
python: Option<Python>,
|
||||||
|
|
||||||
@@ -475,6 +449,9 @@ pub struct ConfigFile {
|
|||||||
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
||||||
linux: Option<Linux>,
|
linux: Option<Linux>,
|
||||||
|
|
||||||
|
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
||||||
|
mandb: Option<Mandb>,
|
||||||
|
|
||||||
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
||||||
git: Option<Git>,
|
git: Option<Git>,
|
||||||
|
|
||||||
@@ -487,9 +464,15 @@ pub struct ConfigFile {
|
|||||||
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
||||||
npm: Option<NPM>,
|
npm: Option<NPM>,
|
||||||
|
|
||||||
|
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
||||||
|
chezmoi: Option<Chezmoi>,
|
||||||
|
|
||||||
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
||||||
yarn: Option<Yarn>,
|
yarn: Option<Yarn>,
|
||||||
|
|
||||||
|
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
||||||
|
deno: Option<Deno>,
|
||||||
|
|
||||||
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
||||||
vim: Option<Vim>,
|
vim: Option<Vim>,
|
||||||
|
|
||||||
@@ -502,11 +485,32 @@ pub struct ConfigFile {
|
|||||||
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
||||||
flatpak: Option<Flatpak>,
|
flatpak: Option<Flatpak>,
|
||||||
|
|
||||||
|
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
||||||
|
pixi: Option<Pixi>,
|
||||||
|
|
||||||
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
||||||
distrobox: Option<Distrobox>,
|
distrobox: Option<Distrobox>,
|
||||||
|
|
||||||
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
||||||
lensfun: Option<Lensfun>,
|
lensfun: Option<Lensfun>,
|
||||||
|
|
||||||
|
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
||||||
|
julia: Option<JuliaConfig>,
|
||||||
|
|
||||||
|
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
||||||
|
zigup: Option<Zigup>,
|
||||||
|
|
||||||
|
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
||||||
|
vscode: Option<VscodeConfig>,
|
||||||
|
|
||||||
|
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
||||||
|
doom: Option<DoomConfig>,
|
||||||
|
|
||||||
|
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
||||||
|
rustup: Option<Rustup>,
|
||||||
|
|
||||||
|
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
||||||
|
pkgfile: Option<Pkgfile>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn config_directory() -> PathBuf {
|
fn config_directory() -> PathBuf {
|
||||||
@@ -538,7 +542,7 @@ impl ConfigFile {
|
|||||||
];
|
];
|
||||||
|
|
||||||
// Search for the main config file
|
// Search for the main config file
|
||||||
for path in possible_config_paths.iter() {
|
for path in &possible_config_paths {
|
||||||
if path.exists() {
|
if path.exists() {
|
||||||
debug!("Configuration at {}", path.display());
|
debug!("Configuration at {}", path.display());
|
||||||
res.0.clone_from(path);
|
res.0.clone_from(path);
|
||||||
@@ -673,17 +677,6 @@ impl ConfigFile {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(paths) = result.git.as_mut().and_then(|git| git.repos.as_mut()) {
|
|
||||||
for path in paths.iter_mut() {
|
|
||||||
let expanded = shellexpand::tilde::<&str>(&path.as_ref()).into_owned();
|
|
||||||
debug!(
|
|
||||||
"{}",
|
|
||||||
t!("Path {path} expanded to {expanded}", path = path, expanded = expanded)
|
|
||||||
);
|
|
||||||
*path = expanded;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
debug!("Loaded configuration: {:?}", result);
|
debug!("Loaded configuration: {:?}", result);
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
@@ -734,14 +727,24 @@ pub struct CommandLineArgs {
|
|||||||
#[arg(short = 't', long = "tmux")]
|
#[arg(short = 't', long = "tmux")]
|
||||||
run_in_tmux: bool,
|
run_in_tmux: bool,
|
||||||
|
|
||||||
|
/// Don't run inside tmux
|
||||||
|
#[arg(long = "no-tmux")]
|
||||||
|
no_tmux: bool,
|
||||||
|
|
||||||
/// Cleanup temporary or old files
|
/// Cleanup temporary or old files
|
||||||
#[arg(short = 'c', long = "cleanup")]
|
#[arg(short = 'c', long = "cleanup")]
|
||||||
cleanup: bool,
|
cleanup: bool,
|
||||||
|
|
||||||
/// Print what would be done
|
/// Print what would be done
|
||||||
|
///
|
||||||
|
/// Alias for --run-type dry
|
||||||
#[arg(short = 'n', long = "dry-run")]
|
#[arg(short = 'n', long = "dry-run")]
|
||||||
dry_run: bool,
|
dry_run: bool,
|
||||||
|
|
||||||
|
/// Pick between just running commands, running and logging commands, and just logging commands
|
||||||
|
#[arg(short = 'r', long = "run-type", value_enum, default_value_t)]
|
||||||
|
run_type: RunType,
|
||||||
|
|
||||||
/// Do not ask to retry failed steps
|
/// Do not ask to retry failed steps
|
||||||
#[arg(long = "no-retry")]
|
#[arg(long = "no-retry")]
|
||||||
no_retry: bool,
|
no_retry: bool,
|
||||||
@@ -800,9 +803,13 @@ pub struct CommandLineArgs {
|
|||||||
#[arg(long = "show-skipped")]
|
#[arg(long = "show-skipped")]
|
||||||
show_skipped: bool,
|
show_skipped: bool,
|
||||||
|
|
||||||
|
/// Suppress warning and confirmation prompt if running as root
|
||||||
|
#[arg(long = "allow-root")]
|
||||||
|
allow_root: bool,
|
||||||
|
|
||||||
/// Tracing filter directives.
|
/// Tracing filter directives.
|
||||||
///
|
///
|
||||||
/// See: https://docs.rs/tracing-subscriber/latest/tracing_subscriber/struct.EnvFilter.html
|
/// See: https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives
|
||||||
#[arg(long, default_value = DEFAULT_LOG_LEVEL)]
|
#[arg(long, default_value = DEFAULT_LOG_LEVEL)]
|
||||||
pub log_filter: String,
|
pub log_filter: String,
|
||||||
|
|
||||||
@@ -913,6 +920,22 @@ impl Config {
|
|||||||
&self.config_file.commands
|
&self.config_file.commands
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The list of additional named conda environments.
|
||||||
|
pub fn conda_env_names(&self) -> Option<&Vec<String>> {
|
||||||
|
self.config_file
|
||||||
|
.conda
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|conda| conda.env_names.as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The list of additional conda environment paths.
|
||||||
|
pub fn conda_env_paths(&self) -> Option<&Vec<String>> {
|
||||||
|
self.config_file
|
||||||
|
.conda
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|conda| conda.env_paths.as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
/// 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>> {
|
||||||
self.config_file.git.as_ref().and_then(|git| git.repos.as_ref())
|
self.config_file.git.as_ref().and_then(|git| git.repos.as_ref())
|
||||||
@@ -944,16 +967,21 @@ impl Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn allowed_steps(opt: &CommandLineArgs, config_file: &ConfigFile) -> Vec<Step> {
|
fn allowed_steps(opt: &CommandLineArgs, config_file: &ConfigFile) -> Vec<Step> {
|
||||||
|
// The enabled steps are
|
||||||
let mut enabled_steps: Vec<Step> = Vec::new();
|
let mut enabled_steps: Vec<Step> = Vec::new();
|
||||||
|
// Any steps that are passed with `--only`
|
||||||
enabled_steps.extend(&opt.only);
|
enabled_steps.extend(&opt.only);
|
||||||
|
|
||||||
|
// Plus any steps in the config file's `misc.only`
|
||||||
if let Some(misc) = config_file.misc.as_ref() {
|
if let Some(misc) = config_file.misc.as_ref() {
|
||||||
if let Some(only) = misc.only.as_ref() {
|
if let Some(only) = misc.only.as_ref() {
|
||||||
enabled_steps.extend(only);
|
enabled_steps.extend(only);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If neither of those contain anything
|
||||||
if enabled_steps.is_empty() {
|
if enabled_steps.is_empty() {
|
||||||
|
// All steps are enabled
|
||||||
enabled_steps.extend(Step::iter());
|
enabled_steps.extend(Step::iter());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -965,6 +993,7 @@ impl Config {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// All steps that are disabled are not enabled, except ones that are passed to `--only`
|
||||||
enabled_steps.retain(|e| !disabled_steps.contains(e) || opt.only.contains(e));
|
enabled_steps.retain(|e| !disabled_steps.contains(e) || opt.only.contains(e));
|
||||||
enabled_steps
|
enabled_steps
|
||||||
}
|
}
|
||||||
@@ -982,13 +1011,14 @@ impl Config {
|
|||||||
|
|
||||||
/// Tell whether we should run in tmux.
|
/// Tell whether we should run in tmux.
|
||||||
pub fn run_in_tmux(&self) -> bool {
|
pub fn run_in_tmux(&self) -> bool {
|
||||||
self.opt.run_in_tmux
|
!self.opt.no_tmux
|
||||||
|| self
|
&& (self.opt.run_in_tmux
|
||||||
.config_file
|
|| self
|
||||||
.misc
|
.config_file
|
||||||
.as_ref()
|
.misc
|
||||||
.and_then(|misc| misc.run_in_tmux)
|
.as_ref()
|
||||||
.unwrap_or(false)
|
.and_then(|misc| misc.run_in_tmux)
|
||||||
|
.unwrap_or(false))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The preferred way to run the new tmux session.
|
/// The preferred way to run the new tmux session.
|
||||||
@@ -1011,9 +1041,13 @@ impl Config {
|
|||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tell whether we are dry-running.
|
/// Get the [RunType] for the current execution
|
||||||
pub fn dry_run(&self) -> bool {
|
pub fn run_type(&self) -> RunType {
|
||||||
self.opt.dry_run
|
if self.opt.dry_run {
|
||||||
|
RunType::Dry
|
||||||
|
} else {
|
||||||
|
self.opt.run_type
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tell whether we should not attempt to retry anything.
|
/// Tell whether we should not attempt to retry anything.
|
||||||
@@ -1142,6 +1176,15 @@ impl Config {
|
|||||||
.unwrap_or(true)
|
.unwrap_or(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether to auto reboot for Windows updates that require it
|
||||||
|
pub fn windows_updates_auto_reboot(&self) -> UpdatesAutoReboot {
|
||||||
|
self.config_file
|
||||||
|
.windows
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|windows| windows.updates_auto_reboot)
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
/// Whether to self rename the Topgrade executable during the run
|
/// Whether to self rename the Topgrade executable during the run
|
||||||
pub fn self_rename(&self) -> bool {
|
pub fn self_rename(&self) -> bool {
|
||||||
self.config_file
|
self.config_file
|
||||||
@@ -1169,6 +1212,15 @@ impl Config {
|
|||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Should use sudo for Winget
|
||||||
|
pub fn winget_use_sudo(&self) -> bool {
|
||||||
|
self.config_file
|
||||||
|
.windows
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|w| w.winget_use_sudo)
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
/// Whether Brew cask should be greedy
|
/// Whether Brew cask should be greedy
|
||||||
pub fn brew_cask_greedy(&self) -> bool {
|
pub fn brew_cask_greedy(&self) -> bool {
|
||||||
self.config_file
|
self.config_file
|
||||||
@@ -1278,6 +1330,15 @@ impl Config {
|
|||||||
.unwrap_or("")
|
.unwrap_or("")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Show release notes of latest pixi release
|
||||||
|
pub fn show_pixi_release_notes(&self) -> bool {
|
||||||
|
self.config_file
|
||||||
|
.pixi
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|s| s.include_release_notes)
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
/// Show news on Arch Linux
|
/// Show news on Arch Linux
|
||||||
pub fn show_arch_news(&self) -> bool {
|
pub fn show_arch_news(&self) -> bool {
|
||||||
self.config_file
|
self.config_file
|
||||||
@@ -1437,14 +1498,22 @@ impl Config {
|
|||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Use bootc in *when bootc is detected* (default: false)
|
||||||
|
pub fn bootc(&self) -> bool {
|
||||||
|
self.config_file
|
||||||
|
.linux
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|linux| linux.bootc)
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
/// Determine if we should ignore failures for this step
|
/// Determine if we should ignore failures for this step
|
||||||
pub fn ignore_failure(&self, step: Step) -> bool {
|
pub fn ignore_failure(&self, step: Step) -> bool {
|
||||||
self.config_file
|
self.config_file
|
||||||
.misc
|
.misc
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|misc| misc.ignore_failures.as_ref())
|
.and_then(|misc| misc.ignore_failures.as_ref())
|
||||||
.map(|v| v.contains(&step))
|
.is_some_and(|v| v.contains(&step))
|
||||||
.unwrap_or(false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn use_predefined_git_repos(&self) -> bool {
|
pub fn use_predefined_git_repos(&self) -> bool {
|
||||||
@@ -1457,6 +1526,14 @@ impl Config {
|
|||||||
.unwrap_or(true)
|
.unwrap_or(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn rustup_channels(&self) -> Vec<String> {
|
||||||
|
self.config_file
|
||||||
|
.rustup
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|rustup| rustup.channels.clone())
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn verbose(&self) -> bool {
|
pub fn verbose(&self) -> bool {
|
||||||
self.opt.verbose
|
self.opt.verbose
|
||||||
}
|
}
|
||||||
@@ -1484,6 +1561,20 @@ impl Config {
|
|||||||
|
|
||||||
pub fn show_skipped(&self) -> bool {
|
pub fn show_skipped(&self) -> bool {
|
||||||
self.opt.show_skipped
|
self.opt.show_skipped
|
||||||
|
|| self
|
||||||
|
.config_file
|
||||||
|
.misc
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|misc| misc.show_skipped)
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn enable_mandb(&self) -> bool {
|
||||||
|
self.config_file
|
||||||
|
.mandb
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|mandb| mandb.enable)
|
||||||
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn open_remotes_in_new_terminal(&self) -> bool {
|
pub fn open_remotes_in_new_terminal(&self) -> bool {
|
||||||
@@ -1494,12 +1585,29 @@ impl Config {
|
|||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn winget_silent_install(&self) -> bool {
|
||||||
|
self.config_file
|
||||||
|
.windows
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|windows| windows.winget_silent_install)
|
||||||
|
.unwrap_or(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn allow_root(&self) -> bool {
|
||||||
|
self.opt.allow_root
|
||||||
|
|| self
|
||||||
|
.config_file
|
||||||
|
.misc
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|misc| misc.allow_root)
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn sudo_command(&self) -> Option<SudoKind> {
|
pub fn sudo_command(&self) -> Option<SudoKind> {
|
||||||
self.config_file.misc.as_ref().and_then(|misc| misc.sudo_command)
|
self.config_file.misc.as_ref().and_then(|misc| misc.sudo_command)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If `true`, `sudo` should be called after `pre_commands` in order to elevate at the
|
/// If `true`, `sudo -v` should be called to cache credentials at the start of the run
|
||||||
/// start of the session (and not in the middle).
|
|
||||||
pub fn pre_sudo(&self) -> bool {
|
pub fn pre_sudo(&self) -> bool {
|
||||||
self.config_file
|
self.config_file
|
||||||
.misc
|
.misc
|
||||||
@@ -1508,6 +1616,14 @@ impl Config {
|
|||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn show_distribution_summary(&self) -> bool {
|
||||||
|
self.config_file
|
||||||
|
.misc
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|misc| misc.show_distribution_summary)
|
||||||
|
.unwrap_or(true)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
pub fn npm_use_sudo(&self) -> bool {
|
pub fn npm_use_sudo(&self) -> bool {
|
||||||
self.config_file
|
self.config_file
|
||||||
@@ -1525,6 +1641,10 @@ impl Config {
|
|||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn deno_version(&self) -> Option<&str> {
|
||||||
|
self.config_file.deno.as_ref().and_then(|deno| deno.version.as_deref())
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
pub fn firmware_upgrade(&self) -> bool {
|
pub fn firmware_upgrade(&self) -> bool {
|
||||||
self.config_file
|
self.config_file
|
||||||
@@ -1566,12 +1686,11 @@ impl Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn enable_pipupgrade(&self) -> bool {
|
pub fn enable_pipupgrade(&self) -> bool {
|
||||||
return self
|
self.config_file
|
||||||
.config_file
|
|
||||||
.python
|
.python
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|python| python.enable_pipupgrade)
|
.and_then(|python| python.enable_pipupgrade)
|
||||||
.unwrap_or(false);
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
pub fn pipupgrade_arguments(&self) -> &str {
|
pub fn pipupgrade_arguments(&self) -> &str {
|
||||||
self.config_file
|
self.config_file
|
||||||
@@ -1581,20 +1700,25 @@ impl Config {
|
|||||||
.unwrap_or("")
|
.unwrap_or("")
|
||||||
}
|
}
|
||||||
pub fn enable_pip_review(&self) -> bool {
|
pub fn enable_pip_review(&self) -> bool {
|
||||||
return self
|
self.config_file
|
||||||
.config_file
|
|
||||||
.python
|
.python
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|python| python.enable_pip_review)
|
.and_then(|python| python.enable_pip_review)
|
||||||
.unwrap_or(false);
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
pub fn enable_pip_review_local(&self) -> bool {
|
pub fn enable_pip_review_local(&self) -> bool {
|
||||||
return self
|
self.config_file
|
||||||
.config_file
|
|
||||||
.python
|
.python
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|python| python.enable_pip_review_local)
|
.and_then(|python| python.enable_pip_review_local)
|
||||||
.unwrap_or(false);
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
pub fn poetry_force_self_update(&self) -> bool {
|
||||||
|
self.config_file
|
||||||
|
.python
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|python| python.poetry_force_self_update)
|
||||||
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn display_time(&self) -> bool {
|
pub fn display_time(&self) -> bool {
|
||||||
@@ -1620,6 +1744,79 @@ impl Config {
|
|||||||
.and_then(|lensfun| lensfun.use_sudo)
|
.and_then(|lensfun| lensfun.use_sudo)
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn julia_use_startup_file(&self) -> bool {
|
||||||
|
self.config_file
|
||||||
|
.julia
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|julia| julia.startup_file)
|
||||||
|
.unwrap_or(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn zigup_target_versions(&self) -> Vec<String> {
|
||||||
|
self.config_file
|
||||||
|
.zigup
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|zigup| zigup.target_versions.clone())
|
||||||
|
.unwrap_or(vec!["master".to_owned()])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn zigup_install_dir(&self) -> Option<&str> {
|
||||||
|
self.config_file
|
||||||
|
.zigup
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|zigup| zigup.install_dir.as_deref())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn zigup_path_link(&self) -> Option<&str> {
|
||||||
|
self.config_file
|
||||||
|
.zigup
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|zigup| zigup.path_link.as_deref())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn zigup_cleanup(&self) -> bool {
|
||||||
|
self.config_file
|
||||||
|
.zigup
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|zigup| zigup.cleanup)
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn chezmoi_exclude_encrypted(&self) -> bool {
|
||||||
|
self.config_file
|
||||||
|
.chezmoi
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|chezmoi| chezmoi.exclude_encrypted)
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn vscode_profile(&self) -> Option<&str> {
|
||||||
|
let vscode_cfg = self.config_file.vscode.as_ref()?;
|
||||||
|
let profile = vscode_cfg.profile.as_ref()?;
|
||||||
|
|
||||||
|
if profile.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(profile.as_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn doom_aot(&self) -> bool {
|
||||||
|
self.config_file
|
||||||
|
.doom
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|doom| doom.aot)
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn enable_pkgfile(&self) -> bool {
|
||||||
|
self.config_file
|
||||||
|
.pkgfile
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|pkgfile| pkgfile.enable)
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -1646,40 +1843,40 @@ mod test {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_should_execute_remote_different_hostname() {
|
fn test_should_execute_remote_different_hostname() {
|
||||||
assert!(config().should_execute_remote(Ok("hostname".to_string()), "remote_hostname"))
|
assert!(config().should_execute_remote(Ok("hostname".to_string()), "remote_hostname"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_should_execute_remote_different_hostname_with_user() {
|
fn test_should_execute_remote_different_hostname_with_user() {
|
||||||
assert!(config().should_execute_remote(Ok("hostname".to_string()), "user@remote_hostname"))
|
assert!(config().should_execute_remote(Ok("hostname".to_string()), "user@remote_hostname"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_should_execute_remote_unknown_hostname() {
|
fn test_should_execute_remote_unknown_hostname() {
|
||||||
assert!(config().should_execute_remote(Err(eyre!("failed to get hostname")), "remote_hostname"))
|
assert!(config().should_execute_remote(Err(eyre!("failed to get hostname")), "remote_hostname"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_should_not_execute_remote_same_hostname() {
|
fn test_should_not_execute_remote_same_hostname() {
|
||||||
assert!(!config().should_execute_remote(Ok("hostname".to_string()), "hostname"))
|
assert!(!config().should_execute_remote(Ok("hostname".to_string()), "hostname"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_should_not_execute_remote_same_hostname_with_user() {
|
fn test_should_not_execute_remote_same_hostname_with_user() {
|
||||||
assert!(!config().should_execute_remote(Ok("hostname".to_string()), "user@hostname"))
|
assert!(!config().should_execute_remote(Ok("hostname".to_string()), "user@hostname"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_should_execute_remote_matching_limit() {
|
fn test_should_execute_remote_matching_limit() {
|
||||||
let mut config = config();
|
let mut config = config();
|
||||||
config.opt = CommandLineArgs::parse_from(["topgrade", "--remote-host-limit", "remote_hostname"]);
|
config.opt = CommandLineArgs::parse_from(["topgrade", "--remote-host-limit", "remote_hostname"]);
|
||||||
assert!(config.should_execute_remote(Ok("hostname".to_string()), "user@remote_hostname"))
|
assert!(config.should_execute_remote(Ok("hostname".to_string()), "user@remote_hostname"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_should_not_execute_remote_not_matching_limit() {
|
fn test_should_not_execute_remote_not_matching_limit() {
|
||||||
let mut config = config();
|
let mut config = config();
|
||||||
config.opt = CommandLineArgs::parse_from(["topgrade", "--remote-host-limit", "other_hostname"]);
|
config.opt = CommandLineArgs::parse_from(["topgrade", "--remote-host-limit", "other_hostname"]);
|
||||||
assert!(!config.should_execute_remote(Ok("hostname".to_string()), "user@remote_hostname"))
|
assert!(!config.should_execute_remote(Ok("hostname".to_string()), "user@remote_hostname"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ pub fn interrupted() -> bool {
|
|||||||
/// Clears the interrupted flag
|
/// Clears the interrupted flag
|
||||||
pub fn unset_interrupted() {
|
pub fn unset_interrupted() {
|
||||||
debug_assert!(INTERRUPTED.load(Ordering::SeqCst));
|
debug_assert!(INTERRUPTED.load(Ordering::SeqCst));
|
||||||
INTERRUPTED.store(false, Ordering::SeqCst)
|
INTERRUPTED.store(false, Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_interrupted() {
|
pub fn set_interrupted() {
|
||||||
INTERRUPTED.store(true, Ordering::SeqCst)
|
INTERRUPTED.store(true, Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ 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) {
|
||||||
set_interrupted()
|
set_interrupted();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set the necessary signal handlers.
|
/// Set the necessary signal handlers.
|
||||||
|
|||||||
@@ -1,22 +1,21 @@
|
|||||||
//! 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 tracing::error;
|
||||||
use winapi::shared::minwindef::{BOOL, DWORD, FALSE, TRUE};
|
use windows::core::BOOL;
|
||||||
use winapi::um::consoleapi::SetConsoleCtrlHandler;
|
use windows::Win32::System::Console::{SetConsoleCtrlHandler, CTRL_C_EVENT};
|
||||||
use winapi::um::wincon::CTRL_C_EVENT;
|
|
||||||
|
|
||||||
extern "system" fn handler(ctrl_type: DWORD) -> BOOL {
|
extern "system" fn handler(ctrl_type: u32) -> BOOL {
|
||||||
match ctrl_type {
|
match ctrl_type {
|
||||||
CTRL_C_EVENT => {
|
CTRL_C_EVENT => {
|
||||||
set_interrupted();
|
set_interrupted();
|
||||||
TRUE
|
true.into()
|
||||||
}
|
}
|
||||||
_ => FALSE,
|
_ => false.into(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_handler() {
|
pub fn set_handler() {
|
||||||
if 0 == unsafe { SetConsoleCtrlHandler(Some(handler), TRUE) } {
|
if let Err(e) = unsafe { SetConsoleCtrlHandler(Some(handler), true) } {
|
||||||
error!("Cannot set a control C handler")
|
error!("Cannot set a control C handler: {e}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
46
src/error.rs
46
src/error.rs
@@ -3,6 +3,8 @@ use std::{fmt::Display, process::ExitStatus};
|
|||||||
use rust_i18n::t;
|
use rust_i18n::t;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
use crate::sudo::SudoKind;
|
||||||
|
|
||||||
#[derive(Error, Debug, PartialEq, Eq)]
|
#[derive(Error, Debug, PartialEq, Eq)]
|
||||||
pub enum TopgradeError {
|
pub enum TopgradeError {
|
||||||
ProcessFailed(String, ExitStatus),
|
ProcessFailed(String, ExitStatus),
|
||||||
@@ -27,7 +29,7 @@ impl Display for TopgradeError {
|
|||||||
f,
|
f,
|
||||||
"{}",
|
"{}",
|
||||||
t!(
|
t!(
|
||||||
"`{process}` failed: {exit_satus}",
|
"`{process}` failed: {exit_status}",
|
||||||
process = process,
|
process = process,
|
||||||
exit_status = exit_status
|
exit_status = exit_status
|
||||||
)
|
)
|
||||||
@@ -38,7 +40,7 @@ impl Display for TopgradeError {
|
|||||||
f,
|
f,
|
||||||
"{}",
|
"{}",
|
||||||
t!(
|
t!(
|
||||||
"`{process}` failed: {exit_satus} with {output}",
|
"`{process}` failed: {exit_status} with {output}",
|
||||||
process = process,
|
process = process,
|
||||||
exit_status = exit_status,
|
exit_status = exit_status,
|
||||||
output = output
|
output = output
|
||||||
@@ -68,6 +70,35 @@ impl Display for StepFailed {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub struct UnsupportedSudo<'a> {
|
||||||
|
pub sudo_kind: SudoKind,
|
||||||
|
pub option: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for UnsupportedSudo<'_> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}",
|
||||||
|
t!(
|
||||||
|
"{sudo_kind} does not support the {option} option",
|
||||||
|
sudo_kind = self.sudo_kind,
|
||||||
|
option = self.option
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub struct MissingSudo();
|
||||||
|
|
||||||
|
impl Display for MissingSudo {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", t!("Could not find sudo"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub struct DryRun();
|
pub struct DryRun();
|
||||||
|
|
||||||
@@ -85,14 +116,3 @@ impl Display for SkipStep {
|
|||||||
write!(f, "{}", self.0)
|
write!(f, "{}", self.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(all(windows, feature = "self-update"))]
|
|
||||||
#[derive(Error, Debug)]
|
|
||||||
pub struct Upgraded(pub ExitStatus);
|
|
||||||
|
|
||||||
#[cfg(all(windows, feature = "self-update"))]
|
|
||||||
impl Display for Upgraded {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{}", t!("Topgrade Upgraded"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,12 +1,48 @@
|
|||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
use crate::executor::RunType;
|
|
||||||
use crate::sudo::Sudo;
|
|
||||||
use crate::utils::{get_require_sudo_string, require_option};
|
|
||||||
use crate::{config::Config, executor::Executor};
|
|
||||||
use color_eyre::eyre::Result;
|
|
||||||
use std::env::var;
|
use std::env::var;
|
||||||
use std::path::Path;
|
use std::ffi::OsStr;
|
||||||
use std::sync::Mutex;
|
use std::process::Command;
|
||||||
|
use std::sync::{LazyLock, Mutex};
|
||||||
|
|
||||||
|
use clap::ValueEnum;
|
||||||
|
use color_eyre::eyre::Result;
|
||||||
|
use rust_i18n::t;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use strum::EnumString;
|
||||||
|
|
||||||
|
use crate::config::Config;
|
||||||
|
use crate::error::MissingSudo;
|
||||||
|
use crate::executor::{DryCommand, Executor};
|
||||||
|
use crate::powershell::Powershell;
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
use crate::steps::linux::Distribution;
|
||||||
|
use crate::sudo::Sudo;
|
||||||
|
use crate::utils::require_option;
|
||||||
|
|
||||||
|
/// An enum telling whether Topgrade should perform dry runs or actually perform the steps.
|
||||||
|
#[derive(Clone, Copy, Debug, Deserialize, Default, EnumString, ValueEnum)]
|
||||||
|
pub enum RunType {
|
||||||
|
/// Executing commands will just print the command with its argument.
|
||||||
|
Dry,
|
||||||
|
|
||||||
|
/// Executing commands will perform actual execution.
|
||||||
|
#[default]
|
||||||
|
Wet,
|
||||||
|
|
||||||
|
/// Executing commands will print the command and perform actual execution.
|
||||||
|
Damp,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RunType {
|
||||||
|
/// Tells whether we're performing a dry run.
|
||||||
|
pub fn dry(self) -> bool {
|
||||||
|
match self {
|
||||||
|
RunType::Dry => true,
|
||||||
|
RunType::Wet => false,
|
||||||
|
RunType::Damp => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct ExecutionContext<'a> {
|
pub struct ExecutionContext<'a> {
|
||||||
run_type: RunType,
|
run_type: RunType,
|
||||||
@@ -18,10 +54,18 @@ pub struct ExecutionContext<'a> {
|
|||||||
tmux_session: Mutex<Option<String>>,
|
tmux_session: Mutex<Option<String>>,
|
||||||
/// True if topgrade is running under ssh.
|
/// True if topgrade is running under ssh.
|
||||||
under_ssh: bool,
|
under_ssh: bool,
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
distribution: &'a Result<Distribution>,
|
||||||
|
powershell: LazyLock<Option<Powershell>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ExecutionContext<'a> {
|
impl<'a> ExecutionContext<'a> {
|
||||||
pub fn new(run_type: RunType, sudo: Option<Sudo>, config: &'a Config) -> Self {
|
pub fn new(
|
||||||
|
run_type: RunType,
|
||||||
|
sudo: Option<Sudo>,
|
||||||
|
config: &'a Config,
|
||||||
|
#[cfg(target_os = "linux")] distribution: &'a Result<Distribution>,
|
||||||
|
) -> Self {
|
||||||
let under_ssh = var("SSH_CLIENT").is_ok() || var("SSH_TTY").is_ok();
|
let under_ssh = var("SSH_CLIENT").is_ok() || var("SSH_TTY").is_ok();
|
||||||
Self {
|
Self {
|
||||||
run_type,
|
run_type,
|
||||||
@@ -29,12 +73,19 @@ impl<'a> ExecutionContext<'a> {
|
|||||||
config,
|
config,
|
||||||
tmux_session: Mutex::new(None),
|
tmux_session: Mutex::new(None),
|
||||||
under_ssh,
|
under_ssh,
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
distribution,
|
||||||
|
powershell: LazyLock::new(Powershell::new),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn execute_elevated(&self, command: &Path, interactive: bool) -> Result<Executor> {
|
/// Create an instance of `Executor` that should run `program`.
|
||||||
let sudo = require_option(self.sudo.as_ref(), get_require_sudo_string())?;
|
pub fn execute<S: AsRef<OsStr>>(&self, program: S) -> Executor {
|
||||||
Ok(sudo.execute_elevated(self, command, interactive))
|
match self.run_type {
|
||||||
|
RunType::Dry => Executor::Dry(DryCommand::new(program)),
|
||||||
|
RunType::Wet => Executor::Wet(Command::new(program)),
|
||||||
|
RunType::Damp => Executor::Damp(Command::new(program)),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_type(&self) -> RunType {
|
pub fn run_type(&self) -> RunType {
|
||||||
@@ -45,6 +96,14 @@ impl<'a> ExecutionContext<'a> {
|
|||||||
&self.sudo
|
&self.sudo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn require_sudo(&self) -> Result<&Sudo> {
|
||||||
|
if let Some(value) = self.sudo() {
|
||||||
|
Ok(value)
|
||||||
|
} else {
|
||||||
|
Err(MissingSudo().into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn config(&self) -> &Config {
|
pub fn config(&self) -> &Config {
|
||||||
self.config
|
self.config
|
||||||
}
|
}
|
||||||
@@ -60,4 +119,17 @@ impl<'a> ExecutionContext<'a> {
|
|||||||
pub fn get_tmux_session(&self) -> Option<String> {
|
pub fn get_tmux_session(&self) -> Option<String> {
|
||||||
self.tmux_session.lock().unwrap().clone()
|
self.tmux_session.lock().unwrap().clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
pub fn distribution(&self) -> &Result<Distribution> {
|
||||||
|
self.distribution
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn powershell(&self) -> &Option<Powershell> {
|
||||||
|
&self.powershell
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn require_powershell(&self) -> Result<&Powershell> {
|
||||||
|
require_option(self.powershell.as_ref(), t!("Powershell is not installed").to_string())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
194
src/executor.rs
194
src/executor.rs
@@ -1,60 +1,23 @@
|
|||||||
//! Utilities for command execution
|
//! Utilities for command execution
|
||||||
use std::ffi::{OsStr, OsString};
|
use std::ffi::{OsStr, OsString};
|
||||||
|
use std::fmt::Debug;
|
||||||
|
use std::iter;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::process::{Child, Command, ExitStatus, Output};
|
use std::process::{Child, Command, ExitStatus, Output};
|
||||||
|
|
||||||
use color_eyre::eyre::Result;
|
use color_eyre::eyre::Result;
|
||||||
use rust_i18n::t;
|
use rust_i18n::t;
|
||||||
use tracing::debug;
|
use tracing::{debug, enabled, Level};
|
||||||
|
|
||||||
use crate::command::CommandExt;
|
use crate::command::CommandExt;
|
||||||
use crate::error::DryRun;
|
use crate::error::DryRun;
|
||||||
|
|
||||||
/// An enum telling whether Topgrade should perform dry runs or actually perform the steps.
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
|
||||||
pub enum RunType {
|
|
||||||
/// Executing commands will just print the command with its argument.
|
|
||||||
Dry,
|
|
||||||
|
|
||||||
/// Executing commands will perform actual execution.
|
|
||||||
Wet,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RunType {
|
|
||||||
/// Create a new instance from a boolean telling whether to dry run.
|
|
||||||
pub fn new(dry_run: bool) -> Self {
|
|
||||||
if dry_run {
|
|
||||||
RunType::Dry
|
|
||||||
} else {
|
|
||||||
RunType::Wet
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create an instance of `Executor` that should run `program`.
|
|
||||||
pub fn execute<S: AsRef<OsStr>>(self, program: S) -> Executor {
|
|
||||||
match self {
|
|
||||||
RunType::Dry => Executor::Dry(DryCommand {
|
|
||||||
program: program.as_ref().into(),
|
|
||||||
..Default::default()
|
|
||||||
}),
|
|
||||||
RunType::Wet => Executor::Wet(Command::new(program)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Tells whether we're performing a dry run.
|
|
||||||
pub fn dry(self) -> bool {
|
|
||||||
match self {
|
|
||||||
RunType::Dry => true,
|
|
||||||
RunType::Wet => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An enum providing a similar interface to `std::process::Command`.
|
/// An enum providing a similar interface to `std::process::Command`.
|
||||||
/// If the enum is set to `Wet`, execution will be performed with `std::process::Command`.
|
/// If the enum is set to `Wet`, execution will be performed with `std::process::Command`.
|
||||||
/// If the enum is set to `Dry`, execution will just print the command with its arguments.
|
/// If the enum is set to `Dry`, execution will just print the command with its arguments.
|
||||||
pub enum Executor {
|
pub enum Executor {
|
||||||
Wet(Command),
|
Wet(Command),
|
||||||
|
Damp(Command),
|
||||||
Dry(DryCommand),
|
Dry(DryCommand),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,7 +27,7 @@ impl Executor {
|
|||||||
/// Will give weird results for non-UTF-8 programs; see `to_string_lossy()`.
|
/// Will give weird results for non-UTF-8 programs; see `to_string_lossy()`.
|
||||||
pub fn get_program(&self) -> String {
|
pub fn get_program(&self) -> String {
|
||||||
match self {
|
match self {
|
||||||
Executor::Wet(c) => c.get_program().to_string_lossy().into_owned(),
|
Executor::Wet(c) | Executor::Damp(c) => c.get_program().to_string_lossy().into_owned(),
|
||||||
Executor::Dry(c) => c.program.to_string_lossy().into_owned(),
|
Executor::Dry(c) => c.program.to_string_lossy().into_owned(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -72,7 +35,7 @@ impl Executor {
|
|||||||
/// See `std::process::Command::arg`
|
/// See `std::process::Command::arg`
|
||||||
pub fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Executor {
|
pub fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Executor {
|
||||||
match self {
|
match self {
|
||||||
Executor::Wet(c) => {
|
Executor::Wet(c) | Executor::Damp(c) => {
|
||||||
c.arg(arg);
|
c.arg(arg);
|
||||||
}
|
}
|
||||||
Executor::Dry(c) => {
|
Executor::Dry(c) => {
|
||||||
@@ -90,7 +53,7 @@ impl Executor {
|
|||||||
S: AsRef<OsStr>,
|
S: AsRef<OsStr>,
|
||||||
{
|
{
|
||||||
match self {
|
match self {
|
||||||
Executor::Wet(c) => {
|
Executor::Wet(c) | Executor::Damp(c) => {
|
||||||
c.args(args);
|
c.args(args);
|
||||||
}
|
}
|
||||||
Executor::Dry(c) => {
|
Executor::Dry(c) => {
|
||||||
@@ -105,7 +68,7 @@ impl Executor {
|
|||||||
/// See `std::process::Command::current_dir`
|
/// See `std::process::Command::current_dir`
|
||||||
pub fn current_dir<P: AsRef<Path>>(&mut self, dir: P) -> &mut Executor {
|
pub fn current_dir<P: AsRef<Path>>(&mut self, dir: P) -> &mut Executor {
|
||||||
match self {
|
match self {
|
||||||
Executor::Wet(c) => {
|
Executor::Wet(c) | Executor::Damp(c) => {
|
||||||
c.current_dir(dir);
|
c.current_dir(dir);
|
||||||
}
|
}
|
||||||
Executor::Dry(c) => c.directory = Some(dir.as_ref().into()),
|
Executor::Dry(c) => c.directory = Some(dir.as_ref().into()),
|
||||||
@@ -121,7 +84,7 @@ impl Executor {
|
|||||||
K: AsRef<OsStr>,
|
K: AsRef<OsStr>,
|
||||||
{
|
{
|
||||||
match self {
|
match self {
|
||||||
Executor::Wet(c) => {
|
Executor::Wet(c) | Executor::Damp(c) => {
|
||||||
c.env_remove(key);
|
c.env_remove(key);
|
||||||
}
|
}
|
||||||
Executor::Dry(_) => (),
|
Executor::Dry(_) => (),
|
||||||
@@ -138,7 +101,7 @@ impl Executor {
|
|||||||
V: AsRef<OsStr>,
|
V: AsRef<OsStr>,
|
||||||
{
|
{
|
||||||
match self {
|
match self {
|
||||||
Executor::Wet(c) => {
|
Executor::Wet(c) | Executor::Damp(c) => {
|
||||||
c.env(key, val);
|
c.env(key, val);
|
||||||
}
|
}
|
||||||
Executor::Dry(_) => (),
|
Executor::Dry(_) => (),
|
||||||
@@ -149,15 +112,16 @@ impl Executor {
|
|||||||
|
|
||||||
/// See `std::process::Command::spawn`
|
/// See `std::process::Command::spawn`
|
||||||
pub fn spawn(&mut self) -> Result<ExecutorChild> {
|
pub fn spawn(&mut self) -> Result<ExecutorChild> {
|
||||||
|
self.log_command();
|
||||||
let result = match self {
|
let result = match self {
|
||||||
Executor::Wet(c) => {
|
Executor::Wet(c) | Executor::Damp(c) => {
|
||||||
debug!("Running {:?}", c);
|
debug!("Running {:?}", c);
|
||||||
c.spawn_checked().map(ExecutorChild::Wet)?
|
// We should use `spawn()` here rather than `spawn_checked()` since
|
||||||
}
|
// their semantics and behaviors are different.
|
||||||
Executor::Dry(c) => {
|
#[allow(clippy::disallowed_methods)]
|
||||||
c.dry_run();
|
c.spawn().map(ExecutorChild::Wet)?
|
||||||
ExecutorChild::Dry
|
|
||||||
}
|
}
|
||||||
|
Executor::Dry(_) => ExecutorChild::Dry,
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(result)
|
Ok(result)
|
||||||
@@ -165,12 +129,15 @@ impl Executor {
|
|||||||
|
|
||||||
/// See `std::process::Command::output`
|
/// See `std::process::Command::output`
|
||||||
pub fn output(&mut self) -> Result<ExecutorOutput> {
|
pub fn output(&mut self) -> Result<ExecutorOutput> {
|
||||||
|
self.log_command();
|
||||||
match self {
|
match self {
|
||||||
Executor::Wet(c) => Ok(ExecutorOutput::Wet(c.output_checked()?)),
|
Executor::Wet(c) | Executor::Damp(c) => {
|
||||||
Executor::Dry(c) => {
|
// We should use `output()` here rather than `output_checked()` since
|
||||||
c.dry_run();
|
// their semantics and behaviors are different.
|
||||||
Ok(ExecutorOutput::Dry)
|
#[allow(clippy::disallowed_methods)]
|
||||||
|
Ok(ExecutorOutput::Wet(c.output()?))
|
||||||
}
|
}
|
||||||
|
Executor::Dry(_) => Ok(ExecutorOutput::Dry),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,18 +145,38 @@ impl Executor {
|
|||||||
/// that can indicate success of a script
|
/// that can indicate success of a script
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn status_checked_with_codes(&mut self, codes: &[i32]) -> Result<()> {
|
pub fn status_checked_with_codes(&mut self, codes: &[i32]) -> Result<()> {
|
||||||
|
self.log_command();
|
||||||
match self {
|
match self {
|
||||||
Executor::Wet(c) => c.status_checked_with(|status| {
|
Executor::Wet(c) | Executor::Damp(c) => c.status_checked_with(|status| {
|
||||||
if status.success() || status.code().as_ref().map(|c| codes.contains(c)).unwrap_or(false) {
|
if status.success() || status.code().as_ref().is_some_and(|c| codes.contains(c)) {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(())
|
Err(())
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
Executor::Dry(c) => {
|
Executor::Dry(_) => Ok(()),
|
||||||
c.dry_run();
|
}
|
||||||
Ok(())
|
}
|
||||||
|
|
||||||
|
fn log_command(&self) {
|
||||||
|
match self {
|
||||||
|
Executor::Wet(_) => (),
|
||||||
|
Executor::Damp(c) => {
|
||||||
|
log_command(
|
||||||
|
"Executing: {program_name} {arguments}",
|
||||||
|
c.get_program(),
|
||||||
|
c.get_args(),
|
||||||
|
c.get_envs(),
|
||||||
|
c.get_current_dir(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
Executor::Dry(c) => log_command(
|
||||||
|
"Dry running: {program_name} {arguments}",
|
||||||
|
&c.program,
|
||||||
|
&c.args,
|
||||||
|
iter::empty(),
|
||||||
|
c.directory.as_ref(),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -199,8 +186,7 @@ pub enum ExecutorOutput {
|
|||||||
Dry,
|
Dry,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A struct represending a command. Trying to execute it will just print its arguments.
|
/// A struct representing a command. Trying to execute it will just print its arguments.
|
||||||
#[derive(Default)]
|
|
||||||
pub struct DryCommand {
|
pub struct DryCommand {
|
||||||
program: OsString,
|
program: OsString,
|
||||||
args: Vec<OsString>,
|
args: Vec<OsString>,
|
||||||
@@ -208,29 +194,18 @@ pub struct DryCommand {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl DryCommand {
|
impl DryCommand {
|
||||||
fn dry_run(&self) {
|
pub fn new<S: AsRef<OsStr>>(program: S) -> Self {
|
||||||
print!(
|
Self {
|
||||||
"{}",
|
program: program.as_ref().to_os_string(),
|
||||||
t!(
|
args: Vec::new(),
|
||||||
"Dry running: {program_name} {arguments}",
|
directory: None,
|
||||||
program_name = self.program.to_string_lossy(),
|
}
|
||||||
arguments = shell_words::join(
|
|
||||||
self.args
|
|
||||||
.iter()
|
|
||||||
.map(|a| String::from(a.to_string_lossy()))
|
|
||||||
.collect::<Vec<String>>()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
match &self.directory {
|
|
||||||
Some(dir) => println!(" {}", t!("in {directory}", directory = dir.to_string_lossy())),
|
|
||||||
None => println!(),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The Result of spawn. Contains an actual `std::process::Child` if executed by a wet command.
|
/// The Result of spawn. Contains an actual `std::process::Child` if executed by a wet command.
|
||||||
pub enum ExecutorChild {
|
pub enum ExecutorChild {
|
||||||
|
// Both RunType::Wet and RunType::Damp use this variant
|
||||||
#[allow(unused)] // this type has not been used
|
#[allow(unused)] // this type has not been used
|
||||||
Wet(Child),
|
Wet(Child),
|
||||||
Dry,
|
Dry,
|
||||||
@@ -243,22 +218,18 @@ impl CommandExt for Executor {
|
|||||||
// variant for wet/dry runs.
|
// variant for wet/dry runs.
|
||||||
|
|
||||||
fn output_checked_with(&mut self, succeeded: impl Fn(&Output) -> Result<(), ()>) -> Result<Output> {
|
fn output_checked_with(&mut self, succeeded: impl Fn(&Output) -> Result<(), ()>) -> Result<Output> {
|
||||||
|
self.log_command();
|
||||||
match self {
|
match self {
|
||||||
Executor::Wet(c) => c.output_checked_with(succeeded),
|
Executor::Wet(c) | Executor::Damp(c) => c.output_checked_with(succeeded),
|
||||||
Executor::Dry(c) => {
|
Executor::Dry(_) => Err(DryRun().into()),
|
||||||
c.dry_run();
|
|
||||||
Err(DryRun().into())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn status_checked_with(&mut self, succeeded: impl Fn(ExitStatus) -> Result<(), ()>) -> Result<()> {
|
fn status_checked_with(&mut self, succeeded: impl Fn(ExitStatus) -> Result<(), ()>) -> Result<()> {
|
||||||
|
self.log_command();
|
||||||
match self {
|
match self {
|
||||||
Executor::Wet(c) => c.status_checked_with(succeeded),
|
Executor::Wet(c) | Executor::Damp(c) => c.status_checked_with(succeeded),
|
||||||
Executor::Dry(c) => {
|
Executor::Dry(_) => Ok(()),
|
||||||
c.dry_run();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,3 +237,42 @@ impl CommandExt for Executor {
|
|||||||
self.spawn()
|
self.spawn()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn log_command<
|
||||||
|
'a,
|
||||||
|
I: ExactSizeIterator<Item = (&'a (impl Debug + 'a + ?Sized), Option<&'a (impl Debug + 'a + ?Sized)>)>,
|
||||||
|
>(
|
||||||
|
prefix: &str,
|
||||||
|
exec: &OsStr,
|
||||||
|
args: impl IntoIterator<Item = &'a (impl AsRef<OsStr> + ?Sized + 'a)>,
|
||||||
|
env: impl IntoIterator<Item = (&'a OsStr, Option<&'a OsStr>), IntoIter = I>,
|
||||||
|
dir: Option<&'a (impl AsRef<Path> + ?Sized)>,
|
||||||
|
) {
|
||||||
|
println!(
|
||||||
|
"{}",
|
||||||
|
t!(
|
||||||
|
prefix,
|
||||||
|
program_name = exec.to_string_lossy(),
|
||||||
|
arguments = shell_words::join(args.into_iter().map(|s| s.as_ref().to_string_lossy()))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
let env_iter = env.into_iter();
|
||||||
|
if env_iter.len() != 0 && enabled!(Level::DEBUG) {
|
||||||
|
println!(
|
||||||
|
" {}",
|
||||||
|
t!(
|
||||||
|
"with env: {env}",
|
||||||
|
env = env_iter
|
||||||
|
.filter(|(_, val)| val.is_some())
|
||||||
|
.map(|(key, val)| format!("{:?}={:?}", key, val.unwrap()))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(" ")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(d) = dir {
|
||||||
|
println!(" {}", t!("in {directory}", directory = d.as_ref().display()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
//pub mod steps;
|
|
||||||
//pub mod utils;
|
|
||||||
441
src/main.rs
441
src/main.rs
@@ -17,18 +17,19 @@ use etcetera::base_strategy::BaseStrategy;
|
|||||||
use etcetera::base_strategy::Windows;
|
use etcetera::base_strategy::Windows;
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use etcetera::base_strategy::Xdg;
|
use etcetera::base_strategy::Xdg;
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
use rust_i18n::{i18n, t};
|
use rust_i18n::{i18n, t};
|
||||||
|
use std::sync::LazyLock;
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
use self::config::{CommandLineArgs, Config, Step};
|
use self::config::{CommandLineArgs, Config};
|
||||||
use self::error::StepFailed;
|
use self::error::StepFailed;
|
||||||
#[cfg(all(windows, feature = "self-update"))]
|
use self::runner::StepResult;
|
||||||
use self::error::Upgraded;
|
#[allow(clippy::wildcard_imports)]
|
||||||
use self::steps::{remote::*, *};
|
use self::steps::{remote::*, *};
|
||||||
|
use self::sudo::{Sudo, SudoCreateError, SudoKind};
|
||||||
|
#[allow(clippy::wildcard_imports)]
|
||||||
use self::terminal::*;
|
use self::terminal::*;
|
||||||
|
use self::utils::{install_color_eyre, install_tracing, is_elevated, update_tracing};
|
||||||
use self::utils::{hostname, install_color_eyre, install_tracing, update_tracing};
|
|
||||||
|
|
||||||
mod breaking_changes;
|
mod breaking_changes;
|
||||||
mod command;
|
mod command;
|
||||||
@@ -37,27 +38,28 @@ mod ctrlc;
|
|||||||
mod error;
|
mod error;
|
||||||
mod execution_context;
|
mod execution_context;
|
||||||
mod executor;
|
mod executor;
|
||||||
mod report;
|
|
||||||
mod runner;
|
mod runner;
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
mod self_renamer;
|
mod self_renamer;
|
||||||
#[cfg(feature = "self-update")]
|
#[cfg(feature = "self-update")]
|
||||||
mod self_update;
|
mod self_update;
|
||||||
|
mod step;
|
||||||
mod steps;
|
mod steps;
|
||||||
mod sudo;
|
mod sudo;
|
||||||
mod terminal;
|
mod terminal;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
pub(crate) static HOME_DIR: Lazy<PathBuf> = Lazy::new(|| home::home_dir().expect("No home directory"));
|
pub(crate) static HOME_DIR: LazyLock<PathBuf> = LazyLock::new(|| home::home_dir().expect("No home directory"));
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
pub(crate) static XDG_DIRS: Lazy<Xdg> = Lazy::new(|| Xdg::new().expect("No home directory"));
|
pub(crate) static XDG_DIRS: LazyLock<Xdg> = LazyLock::new(|| Xdg::new().expect("No home directory"));
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
pub(crate) static WINDOWS_DIRS: Lazy<Windows> = Lazy::new(|| Windows::new().expect("No home directory"));
|
pub(crate) static WINDOWS_DIRS: LazyLock<Windows> = LazyLock::new(|| Windows::new().expect("No home directory"));
|
||||||
|
|
||||||
// Init and load the i18n files
|
// Init and load the i18n files
|
||||||
i18n!("locales", fallback = "en");
|
i18n!("locales", fallback = "en");
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_lines)]
|
||||||
fn run() -> Result<()> {
|
fn run() -> Result<()> {
|
||||||
install_color_eyre()?;
|
install_color_eyre()?;
|
||||||
ctrlc::set_handler();
|
ctrlc::set_handler();
|
||||||
@@ -94,9 +96,9 @@ fn run() -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for env in opt.env_variables() {
|
for env in opt.env_variables() {
|
||||||
let mut splitted = env.split('=');
|
let mut parts = env.split('=');
|
||||||
let var = splitted.next().unwrap();
|
let var = parts.next().unwrap();
|
||||||
let value = splitted.next().unwrap();
|
let value = parts.next().unwrap();
|
||||||
env::set_var(var, value);
|
env::set_var(var, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,8 +121,8 @@ fn run() -> Result<()> {
|
|||||||
|
|
||||||
debug!("Version: {}", crate_version!());
|
debug!("Version: {}", crate_version!());
|
||||||
debug!("OS: {}", env!("TARGET"));
|
debug!("OS: {}", env!("TARGET"));
|
||||||
debug!("{:?}", std::env::args());
|
debug!("{:?}", env::args());
|
||||||
debug!("Binary path: {:?}", std::env::current_exe());
|
debug!("Binary path: {:?}", env::current_exe());
|
||||||
debug!("self-update Feature Enabled: {:?}", cfg!(feature = "self-update"));
|
debug!("self-update Feature Enabled: {:?}", cfg!(feature = "self-update"));
|
||||||
debug!("Configuration: {:?}", config);
|
debug!("Configuration: {:?}", config);
|
||||||
|
|
||||||
@@ -132,44 +134,60 @@ fn run() -> Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let powershell = powershell::Powershell::new();
|
let elevated = is_elevated();
|
||||||
let should_run_powershell = powershell.profile().is_some() && config.should_run(Step::Powershell);
|
|
||||||
let emacs = emacs::Emacs::new();
|
#[cfg(unix)]
|
||||||
|
if !config.allow_root() && elevated {
|
||||||
|
print_warning(t!(
|
||||||
|
"Topgrade should not be run as root, it will run commands with sudo or equivalent where needed."
|
||||||
|
));
|
||||||
|
if !prompt_yesno(&t!("Continue?"))? {
|
||||||
|
exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let sudo = match config.sudo_command() {
|
||||||
|
Some(kind) => Sudo::new(kind),
|
||||||
|
None if elevated => Sudo::new(SudoKind::Null),
|
||||||
|
None => Sudo::detect(),
|
||||||
|
};
|
||||||
|
debug!("Sudo: {:?}", sudo);
|
||||||
|
|
||||||
|
let (sudo, sudo_err) = match sudo {
|
||||||
|
Ok(sudo) => (Some(sudo), None),
|
||||||
|
Err(e) => (None, Some(e)),
|
||||||
|
};
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
let distribution = linux::Distribution::detect();
|
let distribution = linux::Distribution::detect();
|
||||||
|
|
||||||
let sudo = config.sudo_command().map_or_else(sudo::Sudo::detect, sudo::Sudo::new);
|
let run_type = config.run_type();
|
||||||
let run_type = executor::RunType::new(config.dry_run());
|
let ctx = execution_context::ExecutionContext::new(
|
||||||
let ctx = execution_context::ExecutionContext::new(run_type, sudo, &config);
|
run_type,
|
||||||
|
sudo,
|
||||||
|
&config,
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
&distribution,
|
||||||
|
);
|
||||||
let mut runner = runner::Runner::new(&ctx);
|
let mut runner = runner::Runner::new(&ctx);
|
||||||
|
|
||||||
// If
|
// If
|
||||||
//
|
//
|
||||||
// 1. the breaking changes notification shouldnot be skipped
|
// 1. the breaking changes notification shouldn't be skipped
|
||||||
// 2. this is the first execution of a major release
|
// 2. this is the first execution of a major release
|
||||||
//
|
//
|
||||||
// inform user of breaking changes
|
// inform user of breaking changes
|
||||||
if !should_skip() && first_run_of_major_release()? {
|
if !should_skip() && first_run_of_major_release()? {
|
||||||
print_breaking_changes();
|
print_breaking_changes();
|
||||||
|
|
||||||
if prompt_yesno("Confirmed?")? {
|
if prompt_yesno(&t!("Continue?"))? {
|
||||||
write_keep_file()?;
|
write_keep_file()?;
|
||||||
} else {
|
} else {
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Self-Update step, this will execute only if:
|
step::Step::SelfUpdate.run(&mut runner, &ctx)?;
|
||||||
// 1. the `self-update` feature is enabled
|
|
||||||
// 2. it is not disabled from configuration (env var/CLI opt/file)
|
|
||||||
#[cfg(feature = "self-update")]
|
|
||||||
{
|
|
||||||
let should_self_update = env::var("TOPGRADE_NO_SELF_UPGRADE").is_err() && !config.no_self_update();
|
|
||||||
|
|
||||||
if should_self_update {
|
|
||||||
runner.execute(Step::SelfUpdate, "Self Update", || self_update::self_update(&ctx))?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
let _self_rename = if config.self_rename() {
|
let _self_rename = if config.self_rename() {
|
||||||
@@ -178,308 +196,85 @@ fn run() -> Result<()> {
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(commands) = config.pre_commands() {
|
|
||||||
for (name, command) in commands {
|
|
||||||
generic::run_custom_command(name, command, &ctx)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.pre_sudo() {
|
if config.pre_sudo() {
|
||||||
if let Some(sudo) = ctx.sudo() {
|
if let Some(sudo) = ctx.sudo() {
|
||||||
sudo.elevate(&ctx)?;
|
sudo.elevate(&ctx)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(topgrades) = config.remote_topgrades() {
|
if let Some(commands) = config.pre_commands() {
|
||||||
for remote_topgrade in topgrades.iter().filter(|t| config.should_execute_remote(hostname(), t)) {
|
|
||||||
runner.execute(Step::Remotes, format!("Remote ({remote_topgrade})"), || {
|
|
||||||
ssh::ssh_step(&ctx, remote_topgrade)
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
|
||||||
{
|
|
||||||
runner.execute(Step::Wsl, "WSL", || windows::run_wsl_topgrade(&ctx))?;
|
|
||||||
runner.execute(Step::WslUpdate, "WSL", || windows::update_wsl(&ctx))?;
|
|
||||||
runner.execute(Step::Chocolatey, "Chocolatey", || windows::run_chocolatey(&ctx))?;
|
|
||||||
runner.execute(Step::Scoop, "Scoop", || windows::run_scoop(&ctx))?;
|
|
||||||
runner.execute(Step::Winget, "Winget", || windows::run_winget(&ctx))?;
|
|
||||||
runner.execute(Step::System, "Windows update", || windows::windows_update(&ctx))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
{
|
|
||||||
// NOTE: Due to breaking `nu` updates, `packer.nu` needs to be updated before `nu` get updated
|
|
||||||
// by other package managers.
|
|
||||||
runner.execute(Step::Shell, "packer.nu", || linux::run_packer_nu(&ctx))?;
|
|
||||||
|
|
||||||
match &distribution {
|
|
||||||
Ok(distribution) => {
|
|
||||||
runner.execute(Step::System, "System update", || distribution.upgrade(&ctx))?;
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
println!("{}", t!("Error detecting current distribution: {error}", error = e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
runner.execute(Step::ConfigUpdate, "config-update", || linux::run_config_update(&ctx))?;
|
|
||||||
|
|
||||||
runner.execute(Step::AM, "am", || linux::run_am(&ctx))?;
|
|
||||||
runner.execute(Step::AppMan, "appman", || linux::run_appman(&ctx))?;
|
|
||||||
runner.execute(Step::DebGet, "deb-get", || linux::run_deb_get(&ctx))?;
|
|
||||||
runner.execute(Step::Toolbx, "toolbx", || toolbx::run_toolbx(&ctx))?;
|
|
||||||
runner.execute(Step::Snap, "snap", || linux::run_snap(&ctx))?;
|
|
||||||
runner.execute(Step::Pacstall, "pacstall", || linux::run_pacstall(&ctx))?;
|
|
||||||
runner.execute(Step::Pacdef, "pacdef", || linux::run_pacdef(&ctx))?;
|
|
||||||
runner.execute(Step::Protonup, "protonup", || linux::run_protonup_update(&ctx))?;
|
|
||||||
runner.execute(Step::Distrobox, "distrobox", || linux::run_distrobox_update(&ctx))?;
|
|
||||||
runner.execute(Step::DkpPacman, "dkp-pacman", || linux::run_dkp_pacman_update(&ctx))?;
|
|
||||||
runner.execute(Step::System, "pihole", || linux::run_pihole_update(&ctx))?;
|
|
||||||
runner.execute(Step::Firmware, "Firmware upgrades", || linux::run_fwupdmgr(&ctx))?;
|
|
||||||
runner.execute(Step::Restarts, "Restarts", || linux::run_needrestart(&ctx))?;
|
|
||||||
|
|
||||||
runner.execute(Step::Flatpak, "Flatpak", || linux::run_flatpak(&ctx))?;
|
|
||||||
runner.execute(Step::BrewFormula, "Brew", || {
|
|
||||||
unix::run_brew_formula(&ctx, unix::BrewVariant::Path)
|
|
||||||
})?;
|
|
||||||
runner.execute(Step::Lure, "LURE", || linux::run_lure_update(&ctx))?;
|
|
||||||
runner.execute(Step::Waydroid, "Waydroid", || linux::run_waydroid(&ctx))?;
|
|
||||||
runner.execute(Step::AutoCpufreq, "auto-cpufreq", || linux::run_auto_cpufreq(&ctx))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
{
|
|
||||||
runner.execute(Step::BrewFormula, "Brew (ARM)", || {
|
|
||||||
unix::run_brew_formula(&ctx, unix::BrewVariant::MacArm)
|
|
||||||
})?;
|
|
||||||
runner.execute(Step::BrewFormula, "Brew (Intel)", || {
|
|
||||||
unix::run_brew_formula(&ctx, unix::BrewVariant::MacIntel)
|
|
||||||
})?;
|
|
||||||
runner.execute(Step::BrewFormula, "Brew", || {
|
|
||||||
unix::run_brew_formula(&ctx, unix::BrewVariant::Path)
|
|
||||||
})?;
|
|
||||||
runner.execute(Step::BrewCask, "Brew Cask (ARM)", || {
|
|
||||||
unix::run_brew_cask(&ctx, unix::BrewVariant::MacArm)
|
|
||||||
})?;
|
|
||||||
runner.execute(Step::BrewCask, "Brew Cask (Intel)", || {
|
|
||||||
unix::run_brew_cask(&ctx, unix::BrewVariant::MacIntel)
|
|
||||||
})?;
|
|
||||||
runner.execute(Step::BrewCask, "Brew Cask", || {
|
|
||||||
unix::run_brew_cask(&ctx, unix::BrewVariant::Path)
|
|
||||||
})?;
|
|
||||||
runner.execute(Step::Macports, "MacPorts", || macos::run_macports(&ctx))?;
|
|
||||||
runner.execute(Step::Xcodes, "Xcodes", || macos::update_xcodes(&ctx))?;
|
|
||||||
runner.execute(Step::Sparkle, "Sparkle", || macos::run_sparkle(&ctx))?;
|
|
||||||
runner.execute(Step::Mas, "App Store", || macos::run_mas(&ctx))?;
|
|
||||||
runner.execute(Step::System, "System upgrade", || macos::upgrade_macos(&ctx))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "dragonfly")]
|
|
||||||
{
|
|
||||||
runner.execute(Step::Pkg, "DragonFly BSD Packages", || {
|
|
||||||
dragonfly::upgrade_packages(&ctx)
|
|
||||||
})?;
|
|
||||||
runner.execute(Step::Audit, "DragonFly Audit", || dragonfly::audit_packages(&ctx))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "freebsd")]
|
|
||||||
{
|
|
||||||
runner.execute(Step::Pkg, "FreeBSD Packages", || freebsd::upgrade_packages(&ctx))?;
|
|
||||||
runner.execute(Step::System, "FreeBSD Upgrade", || freebsd::upgrade_freebsd(&ctx))?;
|
|
||||||
runner.execute(Step::Audit, "FreeBSD Audit", || freebsd::audit_packages(&ctx))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "openbsd")]
|
|
||||||
{
|
|
||||||
runner.execute(Step::Pkg, "OpenBSD Packages", || openbsd::upgrade_packages(&ctx))?;
|
|
||||||
runner.execute(Step::System, "OpenBSD Upgrade", || openbsd::upgrade_openbsd(&ctx))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(target_os = "android")]
|
|
||||||
{
|
|
||||||
runner.execute(Step::Pkg, "Termux Packages", || android::upgrade_packages(&ctx))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
{
|
|
||||||
runner.execute(Step::Yadm, "yadm", || unix::run_yadm(&ctx))?;
|
|
||||||
runner.execute(Step::Nix, "nix", || unix::run_nix(&ctx))?;
|
|
||||||
runner.execute(Step::Nix, "nix upgrade-nix", || unix::run_nix_self_upgrade(&ctx))?;
|
|
||||||
runner.execute(Step::Guix, "guix", || unix::run_guix(&ctx))?;
|
|
||||||
runner.execute(Step::HomeManager, "home-manager", || unix::run_home_manager(&ctx))?;
|
|
||||||
runner.execute(Step::Asdf, "asdf", || unix::run_asdf(&ctx))?;
|
|
||||||
runner.execute(Step::Mise, "mise", || unix::run_mise(&ctx))?;
|
|
||||||
runner.execute(Step::Pkgin, "pkgin", || unix::run_pkgin(&ctx))?;
|
|
||||||
runner.execute(Step::BunPackages, "bun-packages", || unix::run_bun_packages(&ctx))?;
|
|
||||||
runner.execute(Step::Shell, "zr", || zsh::run_zr(&ctx))?;
|
|
||||||
runner.execute(Step::Shell, "antibody", || zsh::run_antibody(&ctx))?;
|
|
||||||
runner.execute(Step::Shell, "antidote", || zsh::run_antidote(&ctx))?;
|
|
||||||
runner.execute(Step::Shell, "antigen", || zsh::run_antigen(&ctx))?;
|
|
||||||
runner.execute(Step::Shell, "zgenom", || zsh::run_zgenom(&ctx))?;
|
|
||||||
runner.execute(Step::Shell, "zplug", || zsh::run_zplug(&ctx))?;
|
|
||||||
runner.execute(Step::Shell, "zinit", || zsh::run_zinit(&ctx))?;
|
|
||||||
runner.execute(Step::Shell, "zi", || zsh::run_zi(&ctx))?;
|
|
||||||
runner.execute(Step::Shell, "zim", || zsh::run_zim(&ctx))?;
|
|
||||||
runner.execute(Step::Shell, "oh-my-zsh", || zsh::run_oh_my_zsh(&ctx))?;
|
|
||||||
runner.execute(Step::Shell, "oh-my-bash", || unix::run_oh_my_bash(&ctx))?;
|
|
||||||
runner.execute(Step::Shell, "fisher", || unix::run_fisher(&ctx))?;
|
|
||||||
runner.execute(Step::Shell, "bash-it", || unix::run_bashit(&ctx))?;
|
|
||||||
runner.execute(Step::Shell, "oh-my-fish", || unix::run_oh_my_fish(&ctx))?;
|
|
||||||
runner.execute(Step::Shell, "fish-plug", || unix::run_fish_plug(&ctx))?;
|
|
||||||
runner.execute(Step::Shell, "fundle", || unix::run_fundle(&ctx))?;
|
|
||||||
runner.execute(Step::Tmux, "tmux", || tmux::run_tpm(&ctx))?;
|
|
||||||
runner.execute(Step::Tldr, "TLDR", || unix::run_tldr(&ctx))?;
|
|
||||||
runner.execute(Step::Pearl, "pearl", || unix::run_pearl(&ctx))?;
|
|
||||||
#[cfg(not(any(target_os = "macos", target_os = "android")))]
|
|
||||||
runner.execute(Step::GnomeShellExtensions, "Gnome Shell Extensions", || {
|
|
||||||
unix::upgrade_gnome_extensions(&ctx)
|
|
||||||
})?;
|
|
||||||
runner.execute(Step::Pyenv, "pyenv", || unix::run_pyenv(&ctx))?;
|
|
||||||
runner.execute(Step::Sdkman, "SDKMAN!", || unix::run_sdkman(&ctx))?;
|
|
||||||
runner.execute(Step::Rcm, "rcm", || unix::run_rcm(&ctx))?;
|
|
||||||
runner.execute(Step::Maza, "maza", || unix::run_maza(&ctx))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(any(
|
|
||||||
target_os = "freebsd",
|
|
||||||
target_os = "openbsd",
|
|
||||||
target_os = "netbsd",
|
|
||||||
target_os = "dragonfly"
|
|
||||||
)))]
|
|
||||||
{
|
|
||||||
runner.execute(Step::Atom, "apm", || generic::run_apm(&ctx))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The following update function should be executed on all OSes.
|
|
||||||
runner.execute(Step::Fossil, "fossil", || generic::run_fossil(&ctx))?;
|
|
||||||
runner.execute(Step::Elan, "elan", || generic::run_elan(&ctx))?;
|
|
||||||
runner.execute(Step::Rye, "rye", || generic::run_rye(&ctx))?;
|
|
||||||
runner.execute(Step::Rustup, "rustup", || generic::run_rustup(&ctx))?;
|
|
||||||
runner.execute(Step::Juliaup, "juliaup", || generic::run_juliaup(&ctx))?;
|
|
||||||
runner.execute(Step::Dotnet, ".NET", || generic::run_dotnet_upgrade(&ctx))?;
|
|
||||||
runner.execute(Step::Choosenim, "choosenim", || generic::run_choosenim(&ctx))?;
|
|
||||||
runner.execute(Step::Cargo, "cargo", || generic::run_cargo_update(&ctx))?;
|
|
||||||
runner.execute(Step::Flutter, "Flutter", || generic::run_flutter_upgrade(&ctx))?;
|
|
||||||
runner.execute(Step::Go, "go-global-update", || go::run_go_global_update(&ctx))?;
|
|
||||||
runner.execute(Step::Go, "gup", || go::run_go_gup(&ctx))?;
|
|
||||||
runner.execute(Step::Emacs, "Emacs", || emacs.upgrade(&ctx))?;
|
|
||||||
runner.execute(Step::Opam, "opam", || generic::run_opam_update(&ctx))?;
|
|
||||||
runner.execute(Step::Vcpkg, "vcpkg", || generic::run_vcpkg_update(&ctx))?;
|
|
||||||
runner.execute(Step::Pipx, "pipx", || generic::run_pipx_update(&ctx))?;
|
|
||||||
runner.execute(Step::Vscode, "Visual Studio Code extensions", || {
|
|
||||||
generic::run_vscode_extensions_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::Pixi, "pixi", || generic::run_pixi_update(&ctx))?;
|
|
||||||
runner.execute(Step::Miktex, "miktex", || generic::run_miktex_packages_update(&ctx))?;
|
|
||||||
runner.execute(Step::Pip3, "pip3", || generic::run_pip3_update(&ctx))?;
|
|
||||||
runner.execute(Step::PipReview, "pip-review", || generic::run_pip_review_update(&ctx))?;
|
|
||||||
runner.execute(Step::PipReviewLocal, "pip-review (local)", || {
|
|
||||||
generic::run_pip_review_local_update(&ctx)
|
|
||||||
})?;
|
|
||||||
runner.execute(Step::Pipupgrade, "pipupgrade", || generic::run_pipupgrade_update(&ctx))?;
|
|
||||||
runner.execute(Step::Ghcup, "ghcup", || generic::run_ghcup_update(&ctx))?;
|
|
||||||
runner.execute(Step::Stack, "stack", || generic::run_stack_update(&ctx))?;
|
|
||||||
runner.execute(Step::Tlmgr, "tlmgr", || generic::run_tlmgr_update(&ctx))?;
|
|
||||||
runner.execute(Step::Myrepos, "myrepos", || generic::run_myrepos_update(&ctx))?;
|
|
||||||
runner.execute(Step::Chezmoi, "chezmoi", || generic::run_chezmoi_update(&ctx))?;
|
|
||||||
runner.execute(Step::Jetpack, "jetpack", || generic::run_jetpack(&ctx))?;
|
|
||||||
runner.execute(Step::Vim, "vim", || vim::upgrade_vim(&ctx))?;
|
|
||||||
runner.execute(Step::Vim, "Neovim", || vim::upgrade_neovim(&ctx))?;
|
|
||||||
runner.execute(Step::Vim, "The Ultimate vimrc", || vim::upgrade_ultimate_vimrc(&ctx))?;
|
|
||||||
runner.execute(Step::Vim, "voom", || vim::run_voom(&ctx))?;
|
|
||||||
runner.execute(Step::Kakoune, "Kakoune", || kakoune::upgrade_kak_plug(&ctx))?;
|
|
||||||
runner.execute(Step::Helix, "helix", || generic::run_helix_grammars(&ctx))?;
|
|
||||||
runner.execute(Step::Node, "npm", || node::run_npm_upgrade(&ctx))?;
|
|
||||||
runner.execute(Step::Yarn, "yarn", || node::run_yarn_upgrade(&ctx))?;
|
|
||||||
runner.execute(Step::Pnpm, "pnpm", || node::run_pnpm_upgrade(&ctx))?;
|
|
||||||
runner.execute(Step::VoltaPackages, "volta packages", || {
|
|
||||||
node::run_volta_packages_upgrade(&ctx)
|
|
||||||
})?;
|
|
||||||
runner.execute(Step::Containers, "Containers", || containers::run_containers(&ctx))?;
|
|
||||||
runner.execute(Step::Deno, "deno", || node::deno_upgrade(&ctx))?;
|
|
||||||
runner.execute(Step::Composer, "composer", || generic::run_composer_update(&ctx))?;
|
|
||||||
runner.execute(Step::Krew, "krew", || generic::run_krew_upgrade(&ctx))?;
|
|
||||||
runner.execute(Step::Helm, "helm", || generic::run_helm_repo_update(&ctx))?;
|
|
||||||
runner.execute(Step::Gem, "gem", || generic::run_gem(&ctx))?;
|
|
||||||
runner.execute(Step::RubyGems, "rubygems", || generic::run_rubygems(&ctx))?;
|
|
||||||
runner.execute(Step::Julia, "julia", || generic::update_julia_packages(&ctx))?;
|
|
||||||
runner.execute(Step::Haxelib, "haxelib", || generic::run_haxelib_update(&ctx))?;
|
|
||||||
runner.execute(Step::Sheldon, "sheldon", || generic::run_sheldon(&ctx))?;
|
|
||||||
runner.execute(Step::Stew, "stew", || generic::run_stew(&ctx))?;
|
|
||||||
runner.execute(Step::Rtcl, "rtcl", || generic::run_rtcl(&ctx))?;
|
|
||||||
runner.execute(Step::Bin, "bin", || generic::bin_update(&ctx))?;
|
|
||||||
runner.execute(Step::Gcloud, "gcloud", || generic::run_gcloud_components_update(&ctx))?;
|
|
||||||
runner.execute(Step::Micro, "micro", || generic::run_micro(&ctx))?;
|
|
||||||
runner.execute(Step::Raco, "raco", || generic::run_raco_update(&ctx))?;
|
|
||||||
runner.execute(Step::Spicetify, "spicetify", || generic::spicetify_upgrade(&ctx))?;
|
|
||||||
runner.execute(Step::GithubCliExtensions, "GitHub CLI Extensions", || {
|
|
||||||
generic::run_ghcli_extensions_upgrade(&ctx)
|
|
||||||
})?;
|
|
||||||
runner.execute(Step::Bob, "Bob", || generic::run_bob(&ctx))?;
|
|
||||||
runner.execute(Step::Certbot, "Certbot", || generic::run_certbot(&ctx))?;
|
|
||||||
runner.execute(Step::GitRepos, "Git Repositories", || git::run_git_pull(&ctx))?;
|
|
||||||
runner.execute(Step::ClamAvDb, "ClamAV Databases", || generic::run_freshclam(&ctx))?;
|
|
||||||
runner.execute(Step::PlatformioCore, "PlatformIO Core", || {
|
|
||||||
generic::run_platform_io(&ctx)
|
|
||||||
})?;
|
|
||||||
runner.execute(Step::Lensfun, "Lensfun's database update", || {
|
|
||||||
generic::run_lensfun_update_data(&ctx)
|
|
||||||
})?;
|
|
||||||
runner.execute(Step::Poetry, "Poetry", || generic::run_poetry(&ctx))?;
|
|
||||||
runner.execute(Step::Uv, "uv", || generic::run_uv(&ctx))?;
|
|
||||||
runner.execute(Step::Zvm, "ZVM", || generic::run_zvm(&ctx))?;
|
|
||||||
runner.execute(Step::Aqua, "aqua", || generic::run_aqua(&ctx))?;
|
|
||||||
runner.execute(Step::Bun, "bun", || generic::run_bun(&ctx))?;
|
|
||||||
|
|
||||||
if should_run_powershell {
|
|
||||||
runner.execute(Step::Powershell, "Powershell Modules Update", || {
|
|
||||||
powershell.update_modules(&ctx)
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(commands) = config.commands() {
|
|
||||||
for (name, command) in commands {
|
for (name, command) in commands {
|
||||||
if config.should_run_custom_command(name) {
|
generic::run_custom_command(name, command, &ctx)?;
|
||||||
runner.execute(Step::CustomCommands, name, || {
|
|
||||||
generic::run_custom_command(name, command, &ctx)
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.should_run(Step::Vagrant) {
|
for step in step::default_steps() {
|
||||||
if let Ok(boxes) = vagrant::collect_boxes(&ctx) {
|
step.run(&mut runner, &ctx)?
|
||||||
for vagrant_box in boxes {
|
|
||||||
runner.execute(Step::Vagrant, format!("Vagrant ({})", vagrant_box.smart_name()), || {
|
|
||||||
vagrant::topgrade_vagrant_box(&ctx, &vagrant_box)
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
runner.execute(Step::Vagrant, "Vagrant boxes", || vagrant::upgrade_vagrant_boxes(&ctx))?;
|
|
||||||
|
|
||||||
if !runner.report().data().is_empty() {
|
let mut failed = false;
|
||||||
|
|
||||||
|
let report = runner.report();
|
||||||
|
if !report.is_empty() {
|
||||||
print_separator(t!("Summary"));
|
print_separator(t!("Summary"));
|
||||||
|
|
||||||
for (key, result) in runner.report().data() {
|
let mut skipped_missing_sudo = false;
|
||||||
|
|
||||||
|
for (key, result) in report {
|
||||||
|
if !failed && result.failed() {
|
||||||
|
failed = true;
|
||||||
|
}
|
||||||
|
if let StepResult::SkippedMissingSudo = result {
|
||||||
|
skipped_missing_sudo = true;
|
||||||
|
}
|
||||||
print_result(key, result);
|
print_result(key, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
if skipped_missing_sudo {
|
||||||
{
|
print_warning(t!(
|
||||||
if let Ok(distribution) = &distribution {
|
"\nSome steps were skipped as sudo or equivalent could not be found."
|
||||||
distribution.show_summary();
|
));
|
||||||
|
// Steps can only fail with SkippedMissingSudo if sudo is None,
|
||||||
|
// therefore we must have a sudo_err
|
||||||
|
match sudo_err.unwrap() {
|
||||||
|
SudoCreateError::CannotFindBinary => {
|
||||||
|
#[cfg(unix)]
|
||||||
|
print_warning(t!(
|
||||||
|
"Install one of `sudo`, `doas`, `pkexec`, `run0` or `please` to run these steps."
|
||||||
|
));
|
||||||
|
|
||||||
|
// if this windows version supported Windows Sudo, the error would have been WinSudoDisabled
|
||||||
|
#[cfg(windows)]
|
||||||
|
print_warning(t!("Install gsudo to run these steps."));
|
||||||
|
}
|
||||||
|
#[cfg(windows)]
|
||||||
|
SudoCreateError::WinSudoDisabled => {
|
||||||
|
print_warning(t!(
|
||||||
|
"Install gsudo or enable Windows Sudo to run these steps.\nFor Windows Sudo, the default 'In a new window' mode is not supported as it prevents Topgrade from waiting for commands to finish. Please configure it to use 'Inline' mode instead.\nGo to https://go.microsoft.com/fwlink/?linkid=2257346 to learn more."
|
||||||
|
));
|
||||||
|
}
|
||||||
|
#[cfg(windows)]
|
||||||
|
SudoCreateError::WinSudoNewWindowMode => {
|
||||||
|
print_warning(t!(
|
||||||
|
"Windows Sudo was found, but it is set to 'In a new window' mode, which prevents Topgrade from waiting for commands to finish. Please configure it to use 'Inline' mode instead.\nGo to https://go.microsoft.com/fwlink/?linkid=2257346 to learn more."
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut post_command_failed = false;
|
#[cfg(target_os = "linux")]
|
||||||
|
if config.show_distribution_summary() {
|
||||||
|
if let Ok(distribution) = &distribution {
|
||||||
|
distribution.show_summary();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(commands) = config.post_commands() {
|
if let Some(commands) = config.post_commands() {
|
||||||
for (name, command) in commands {
|
for (name, command) in commands {
|
||||||
if generic::run_custom_command(name, command, &ctx).is_err() {
|
let result = generic::run_custom_command(name, command, &ctx);
|
||||||
post_command_failed = true;
|
if !failed && result.is_err() {
|
||||||
|
failed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -488,13 +283,14 @@ fn run() -> Result<()> {
|
|||||||
print_info(t!("\n(R)eboot\n(S)hell\n(Q)uit"));
|
print_info(t!("\n(R)eboot\n(S)hell\n(Q)uit"));
|
||||||
loop {
|
loop {
|
||||||
match get_key() {
|
match get_key() {
|
||||||
Ok(Key::Char('s')) | Ok(Key::Char('S')) => {
|
Ok(Key::Char('s' | 'S')) => {
|
||||||
run_shell().context("Failed to execute shell")?;
|
run_shell().context("Failed to execute shell")?;
|
||||||
}
|
}
|
||||||
Ok(Key::Char('r')) | Ok(Key::Char('R')) => {
|
Ok(Key::Char('r' | 'R')) => {
|
||||||
reboot().context("Failed to reboot")?;
|
println!("{}", t!("Rebooting..."));
|
||||||
|
reboot(&ctx).context("Failed to reboot")?;
|
||||||
}
|
}
|
||||||
Ok(Key::Char('q')) | Ok(Key::Char('Q')) => (),
|
Ok(Key::Char('q' | 'Q')) => (),
|
||||||
_ => {
|
_ => {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -503,8 +299,6 @@ fn run() -> Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let failed = post_command_failed || runner.report().data().iter().any(|(_, result)| result.failed());
|
|
||||||
|
|
||||||
if !config.skip_notify() {
|
if !config.skip_notify() {
|
||||||
notify_desktop(
|
notify_desktop(
|
||||||
if failed {
|
if failed {
|
||||||
@@ -513,7 +307,7 @@ fn run() -> Result<()> {
|
|||||||
t!("Topgrade finished successfully")
|
t!("Topgrade finished successfully")
|
||||||
},
|
},
|
||||||
Some(Duration::from_secs(10)),
|
Some(Duration::from_secs(10)),
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if failed {
|
if failed {
|
||||||
@@ -529,13 +323,6 @@ fn main() {
|
|||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
#[cfg(all(windows, feature = "self-update"))]
|
|
||||||
{
|
|
||||||
if let Some(Upgraded(status)) = error.downcast_ref::<Upgraded>() {
|
|
||||||
exit(status.code().unwrap());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let skip_print = (error.downcast_ref::<StepFailed>().is_some())
|
let skip_print = (error.downcast_ref::<StepFailed>().is_some())
|
||||||
|| (error
|
|| (error
|
||||||
.downcast_ref::<io::Error>()
|
.downcast_ref::<io::Error>()
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
use std::borrow::Cow;
|
|
||||||
|
|
||||||
pub enum StepResult {
|
|
||||||
Success,
|
|
||||||
Failure,
|
|
||||||
Ignored,
|
|
||||||
Skipped(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl StepResult {
|
|
||||||
pub fn failed(&self) -> bool {
|
|
||||||
match self {
|
|
||||||
StepResult::Success | StepResult::Ignored | StepResult::Skipped(_) => false,
|
|
||||||
StepResult::Failure => true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type CowString<'a> = Cow<'a, str>;
|
|
||||||
type ReportData<'a> = Vec<(CowString<'a>, StepResult)>;
|
|
||||||
pub struct Report<'a> {
|
|
||||||
data: ReportData<'a>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Report<'a> {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self { data: Vec::new() }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push_result<M>(&mut self, result: Option<(M, StepResult)>)
|
|
||||||
where
|
|
||||||
M: Into<CowString<'a>>,
|
|
||||||
{
|
|
||||||
if let Some((key, success)) = result {
|
|
||||||
let key = key.into();
|
|
||||||
|
|
||||||
debug_assert!(!self.data.iter().any(|(k, _)| k == &key), "{key} already reported");
|
|
||||||
self.data.push((key, success));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn data(&self) -> &ReportData<'a> {
|
|
||||||
&self.data
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,14 +1,36 @@
|
|||||||
use crate::ctrlc;
|
|
||||||
use crate::error::{DryRun, SkipStep};
|
|
||||||
use crate::execution_context::ExecutionContext;
|
|
||||||
use crate::report::{Report, StepResult};
|
|
||||||
use crate::terminal::print_error;
|
|
||||||
use crate::{config::Step, terminal::should_retry};
|
|
||||||
use color_eyre::eyre::Result;
|
use color_eyre::eyre::Result;
|
||||||
|
use rust_i18n::t;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
|
use crate::ctrlc;
|
||||||
|
use crate::error::{DryRun, MissingSudo, SkipStep};
|
||||||
|
use crate::execution_context::ExecutionContext;
|
||||||
|
use crate::step::Step;
|
||||||
|
use crate::terminal::{print_error, print_warning, should_retry};
|
||||||
|
|
||||||
|
pub enum StepResult {
|
||||||
|
Success,
|
||||||
|
Failure,
|
||||||
|
Ignored,
|
||||||
|
SkippedMissingSudo,
|
||||||
|
Skipped(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StepResult {
|
||||||
|
pub fn failed(&self) -> bool {
|
||||||
|
use StepResult::*;
|
||||||
|
|
||||||
|
match self {
|
||||||
|
Success | Ignored | Skipped(_) | SkippedMissingSudo => false,
|
||||||
|
Failure => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Report<'a> = Vec<(Cow<'a, str>, StepResult)>;
|
||||||
|
|
||||||
pub struct Runner<'a> {
|
pub struct Runner<'a> {
|
||||||
ctx: &'a ExecutionContext<'a>,
|
ctx: &'a ExecutionContext<'a>,
|
||||||
report: Report<'a>,
|
report: Report<'a>,
|
||||||
@@ -18,20 +40,25 @@ impl<'a> Runner<'a> {
|
|||||||
pub fn new(ctx: &'a ExecutionContext) -> Runner<'a> {
|
pub fn new(ctx: &'a ExecutionContext) -> Runner<'a> {
|
||||||
Runner {
|
Runner {
|
||||||
ctx,
|
ctx,
|
||||||
report: Report::new(),
|
report: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn execute<F, M>(&mut self, step: Step, key: M, func: F) -> Result<()>
|
fn push_result(&mut self, key: Cow<'a, str>, result: StepResult) {
|
||||||
|
debug_assert!(!self.report.iter().any(|(k, _)| k == &key), "{key} already reported");
|
||||||
|
self.report.push((key, result));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn execute<K, F>(&mut self, step: Step, key: K, func: F) -> Result<()>
|
||||||
where
|
where
|
||||||
|
K: Into<Cow<'a, str>> + Debug,
|
||||||
F: Fn() -> Result<()>,
|
F: Fn() -> Result<()>,
|
||||||
M: Into<Cow<'a, str>> + Debug,
|
|
||||||
{
|
{
|
||||||
if !self.ctx.config().should_run(step) {
|
if !self.ctx.config().should_run(step) {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let key = key.into();
|
let key: Cow<'a, str> = key.into();
|
||||||
debug!("Step {:?}", key);
|
debug!("Step {:?}", key);
|
||||||
|
|
||||||
// alter the `func` to put it in a span
|
// alter the `func` to put it in a span
|
||||||
@@ -45,13 +72,18 @@ impl<'a> Runner<'a> {
|
|||||||
loop {
|
loop {
|
||||||
match func() {
|
match func() {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
self.report.push_result(Some((key, StepResult::Success)));
|
self.push_result(key, StepResult::Success);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
Err(e) if e.downcast_ref::<DryRun>().is_some() => break,
|
Err(e) if e.downcast_ref::<DryRun>().is_some() => break,
|
||||||
|
Err(e) if e.downcast_ref::<MissingSudo>().is_some() => {
|
||||||
|
print_warning(t!("Skipping step, sudo is required"));
|
||||||
|
self.push_result(key, StepResult::SkippedMissingSudo);
|
||||||
|
break;
|
||||||
|
}
|
||||||
Err(e) if e.downcast_ref::<SkipStep>().is_some() => {
|
Err(e) if e.downcast_ref::<SkipStep>().is_some() => {
|
||||||
if self.ctx.config().verbose() || self.ctx.config().show_skipped() {
|
if self.ctx.config().verbose() || self.ctx.config().show_skipped() {
|
||||||
self.report.push_result(Some((key, StepResult::Skipped(e.to_string()))));
|
self.push_result(key, StepResult::Skipped(e.to_string()));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -72,14 +104,14 @@ impl<'a> Runner<'a> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if !should_retry {
|
if !should_retry {
|
||||||
self.report.push_result(Some((
|
self.push_result(
|
||||||
key,
|
key,
|
||||||
if ignore_failure {
|
if ignore_failure {
|
||||||
StepResult::Ignored
|
StepResult::Ignored
|
||||||
} else {
|
} else {
|
||||||
StepResult::Failure
|
StepResult::Failure
|
||||||
},
|
},
|
||||||
)));
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -89,7 +121,7 @@ impl<'a> Runner<'a> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn report(&self) -> &Report {
|
pub fn report(&self) -> &Report<'_> {
|
||||||
&self.report
|
&self.report
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,19 @@
|
|||||||
use std::env;
|
use std::env;
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use std::os::unix::process::CommandExt as _;
|
use std::os::unix::process::CommandExt as _;
|
||||||
|
#[cfg(windows)]
|
||||||
|
use std::process::exit;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
use crate::config::Step;
|
use crate::step::Step;
|
||||||
use color_eyre::eyre::{bail, Result};
|
#[cfg(unix)]
|
||||||
|
use color_eyre::eyre::bail;
|
||||||
|
use color_eyre::eyre::Result;
|
||||||
use rust_i18n::t;
|
use rust_i18n::t;
|
||||||
use self_update_crate::backends::github::Update;
|
use self_update_crate::backends::github::Update;
|
||||||
use self_update_crate::update::UpdateStatus;
|
use self_update_crate::update::UpdateStatus;
|
||||||
|
|
||||||
use super::terminal::*;
|
use super::terminal::{print_info, print_separator};
|
||||||
#[cfg(windows)]
|
|
||||||
use crate::error::Upgraded;
|
|
||||||
|
|
||||||
use crate::execution_context::ExecutionContext;
|
use crate::execution_context::ExecutionContext;
|
||||||
|
|
||||||
pub fn self_update(ctx: &ExecutionContext) -> Result<()> {
|
pub fn self_update(ctx: &ExecutionContext) -> Result<()> {
|
||||||
@@ -63,7 +64,7 @@ pub fn self_update(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
{
|
{
|
||||||
#[allow(clippy::disallowed_methods)]
|
#[allow(clippy::disallowed_methods)]
|
||||||
let status = command.status()?;
|
let status = command.status()?;
|
||||||
bail!(Upgraded(status));
|
exit(status.code().expect("This cannot return None on Windows"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
901
src/step.rs
Normal file
901
src/step.rs
Normal file
@@ -0,0 +1,901 @@
|
|||||||
|
use crate::execution_context::ExecutionContext;
|
||||||
|
use crate::runner::Runner;
|
||||||
|
use clap::ValueEnum;
|
||||||
|
use color_eyre::Result;
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
use rust_i18n::t;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use strum::{EnumCount, EnumIter, EnumString, VariantNames};
|
||||||
|
|
||||||
|
#[cfg(feature = "self-update")]
|
||||||
|
use crate::self_update;
|
||||||
|
use crate::steps::remote::vagrant;
|
||||||
|
#[allow(clippy::wildcard_imports)]
|
||||||
|
use crate::steps::*;
|
||||||
|
use crate::utils::hostname;
|
||||||
|
|
||||||
|
#[derive(ValueEnum, EnumString, VariantNames, Debug, Clone, PartialEq, Eq, Deserialize, EnumIter, Copy, EnumCount)]
|
||||||
|
#[clap(rename_all = "snake_case")]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
#[strum(serialize_all = "snake_case")]
|
||||||
|
pub enum Step {
|
||||||
|
AM,
|
||||||
|
AndroidStudio,
|
||||||
|
AppMan,
|
||||||
|
Aqua,
|
||||||
|
Asdf,
|
||||||
|
Atom,
|
||||||
|
Atuin,
|
||||||
|
Audit,
|
||||||
|
AutoCpufreq,
|
||||||
|
Bin,
|
||||||
|
Bob,
|
||||||
|
BrewCask,
|
||||||
|
BrewFormula,
|
||||||
|
Bun,
|
||||||
|
BunPackages,
|
||||||
|
Cargo,
|
||||||
|
Certbot,
|
||||||
|
Chezmoi,
|
||||||
|
Chocolatey,
|
||||||
|
Choosenim,
|
||||||
|
CinnamonSpices,
|
||||||
|
ClamAvDb,
|
||||||
|
Composer,
|
||||||
|
Conda,
|
||||||
|
ConfigUpdate,
|
||||||
|
Containers,
|
||||||
|
CustomCommands,
|
||||||
|
DebGet,
|
||||||
|
Deno,
|
||||||
|
Distrobox,
|
||||||
|
DkpPacman,
|
||||||
|
Dotnet,
|
||||||
|
Elan,
|
||||||
|
Emacs,
|
||||||
|
Falconf,
|
||||||
|
Firmware,
|
||||||
|
Flatpak,
|
||||||
|
Flutter,
|
||||||
|
Fossil,
|
||||||
|
Gcloud,
|
||||||
|
Gem,
|
||||||
|
Ghcup,
|
||||||
|
GitRepos,
|
||||||
|
GithubCliExtensions,
|
||||||
|
GnomeShellExtensions,
|
||||||
|
Go,
|
||||||
|
Guix,
|
||||||
|
Haxelib,
|
||||||
|
Helix,
|
||||||
|
Helm,
|
||||||
|
HomeManager,
|
||||||
|
Hyprpm,
|
||||||
|
// These names are miscapitalized on purpose, so the CLI name is
|
||||||
|
// `jetbrains_pycharm` instead of `jet_brains_py_charm`.
|
||||||
|
JetbrainsAqua,
|
||||||
|
JetbrainsClion,
|
||||||
|
JetbrainsDatagrip,
|
||||||
|
JetbrainsDataspell,
|
||||||
|
JetbrainsGateway,
|
||||||
|
JetbrainsGoland,
|
||||||
|
JetbrainsIdea,
|
||||||
|
JetbrainsMps,
|
||||||
|
JetbrainsPhpstorm,
|
||||||
|
JetbrainsPycharm,
|
||||||
|
JetbrainsRider,
|
||||||
|
JetbrainsRubymine,
|
||||||
|
JetbrainsRustrover,
|
||||||
|
JetbrainsToolbox,
|
||||||
|
JetbrainsWebstorm,
|
||||||
|
Jetpack,
|
||||||
|
Julia,
|
||||||
|
Juliaup,
|
||||||
|
Kakoune,
|
||||||
|
Krew,
|
||||||
|
Lensfun,
|
||||||
|
Lure,
|
||||||
|
Macports,
|
||||||
|
Mamba,
|
||||||
|
Mandb,
|
||||||
|
Mas,
|
||||||
|
Maza,
|
||||||
|
Micro,
|
||||||
|
MicrosoftStore,
|
||||||
|
Miktex,
|
||||||
|
Mise,
|
||||||
|
Myrepos,
|
||||||
|
Nix,
|
||||||
|
NixHelper,
|
||||||
|
Node,
|
||||||
|
Opam,
|
||||||
|
Pacdef,
|
||||||
|
Pacstall,
|
||||||
|
Pearl,
|
||||||
|
Pip3,
|
||||||
|
PipReview,
|
||||||
|
PipReviewLocal,
|
||||||
|
Pipupgrade,
|
||||||
|
Pipx,
|
||||||
|
Pipxu,
|
||||||
|
Pixi,
|
||||||
|
Pkg,
|
||||||
|
Pkgfile,
|
||||||
|
Pkgin,
|
||||||
|
PlatformioCore,
|
||||||
|
Pnpm,
|
||||||
|
Poetry,
|
||||||
|
Powershell,
|
||||||
|
Protonup,
|
||||||
|
Pyenv,
|
||||||
|
Raco,
|
||||||
|
Rcm,
|
||||||
|
Remotes,
|
||||||
|
Restarts,
|
||||||
|
Rtcl,
|
||||||
|
RubyGems,
|
||||||
|
Rustup,
|
||||||
|
Rye,
|
||||||
|
Scoop,
|
||||||
|
Sdkman,
|
||||||
|
SelfUpdate,
|
||||||
|
Sheldon,
|
||||||
|
Shell,
|
||||||
|
Snap,
|
||||||
|
Sparkle,
|
||||||
|
Spicetify,
|
||||||
|
Stack,
|
||||||
|
Stew,
|
||||||
|
System,
|
||||||
|
Tldr,
|
||||||
|
Tlmgr,
|
||||||
|
Tmux,
|
||||||
|
Toolbx,
|
||||||
|
Typst,
|
||||||
|
Uv,
|
||||||
|
Vagrant,
|
||||||
|
Vcpkg,
|
||||||
|
Vim,
|
||||||
|
VoltaPackages,
|
||||||
|
Vscode,
|
||||||
|
VscodeInsiders,
|
||||||
|
Vscodium,
|
||||||
|
VscodiumInsiders,
|
||||||
|
Waydroid,
|
||||||
|
Winget,
|
||||||
|
Wsl,
|
||||||
|
WslUpdate,
|
||||||
|
Xcodes,
|
||||||
|
Yadm,
|
||||||
|
Yarn,
|
||||||
|
Yazi,
|
||||||
|
Zigup,
|
||||||
|
Zvm,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Step {
|
||||||
|
#[allow(clippy::too_many_lines)]
|
||||||
|
pub fn run(&self, runner: &mut Runner, ctx: &ExecutionContext) -> Result<()> {
|
||||||
|
use Step::*;
|
||||||
|
|
||||||
|
match *self {
|
||||||
|
AM =>
|
||||||
|
{
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
runner.execute(*self, "am", || linux::run_am(ctx))?
|
||||||
|
}
|
||||||
|
AndroidStudio => runner.execute(*self, "Android Studio Plugins", || generic::run_android_studio(ctx))?,
|
||||||
|
AppMan =>
|
||||||
|
{
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
runner.execute(*self, "appman", || linux::run_appman(ctx))?
|
||||||
|
}
|
||||||
|
Aqua => runner.execute(*self, "aqua", || generic::run_aqua(ctx))?,
|
||||||
|
Asdf =>
|
||||||
|
{
|
||||||
|
#[cfg(unix)]
|
||||||
|
runner.execute(*self, "asdf", || unix::run_asdf(ctx))?
|
||||||
|
}
|
||||||
|
Atom =>
|
||||||
|
{
|
||||||
|
#[cfg(not(any(
|
||||||
|
target_os = "freebsd",
|
||||||
|
target_os = "openbsd",
|
||||||
|
target_os = "netbsd",
|
||||||
|
target_os = "dragonfly"
|
||||||
|
)))]
|
||||||
|
runner.execute(*self, "apm", || generic::run_apm(ctx))?
|
||||||
|
}
|
||||||
|
Atuin =>
|
||||||
|
{
|
||||||
|
#[cfg(unix)]
|
||||||
|
runner.execute(*self, "atuin", || unix::run_atuin(ctx))?
|
||||||
|
}
|
||||||
|
Audit => {
|
||||||
|
#[cfg(target_os = "dragonfly")]
|
||||||
|
runner.execute(*self, "DragonFly Audit", || dragonfly::audit_packages(ctx))?;
|
||||||
|
#[cfg(target_os = "freebsd")]
|
||||||
|
runner.execute(*self, "FreeBSD Audit", || freebsd::audit_packages(ctx))?
|
||||||
|
}
|
||||||
|
AutoCpufreq =>
|
||||||
|
{
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
runner.execute(*self, "auto-cpufreq", || linux::run_auto_cpufreq(ctx))?
|
||||||
|
}
|
||||||
|
Bin => runner.execute(*self, "bin", || generic::bin_update(ctx))?,
|
||||||
|
Bob => runner.execute(*self, "Bob", || generic::run_bob(ctx))?,
|
||||||
|
BrewCask => {
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
runner.execute(*self, "Brew Cask", || unix::run_brew_cask(ctx, unix::BrewVariant::Path))?;
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
runner.execute(*self, "Brew Cask (Intel)", || {
|
||||||
|
unix::run_brew_cask(ctx, unix::BrewVariant::MacIntel)
|
||||||
|
})?;
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
runner.execute(*self, "Brew Cask (ARM)", || {
|
||||||
|
unix::run_brew_cask(ctx, unix::BrewVariant::MacArm)
|
||||||
|
})?
|
||||||
|
}
|
||||||
|
BrewFormula => {
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
runner.execute(*self, "Brew", || unix::run_brew_formula(ctx, unix::BrewVariant::Path))?;
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
runner.execute(*self, "Brew (ARM)", || {
|
||||||
|
unix::run_brew_formula(ctx, unix::BrewVariant::MacArm)
|
||||||
|
})?;
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
runner.execute(*self, "Brew (Intel)", || {
|
||||||
|
unix::run_brew_formula(ctx, unix::BrewVariant::MacIntel)
|
||||||
|
})?
|
||||||
|
}
|
||||||
|
Bun => runner.execute(*self, "bun", || generic::run_bun(ctx))?,
|
||||||
|
BunPackages =>
|
||||||
|
{
|
||||||
|
#[cfg(unix)]
|
||||||
|
runner.execute(*self, "bun-packages", || unix::run_bun_packages(ctx))?
|
||||||
|
}
|
||||||
|
Cargo => runner.execute(*self, "cargo", || generic::run_cargo_update(ctx))?,
|
||||||
|
Certbot => runner.execute(*self, "Certbot", || generic::run_certbot(ctx))?,
|
||||||
|
Chezmoi => runner.execute(*self, "chezmoi", || generic::run_chezmoi_update(ctx))?,
|
||||||
|
Chocolatey =>
|
||||||
|
{
|
||||||
|
#[cfg(windows)]
|
||||||
|
runner.execute(*self, "Chocolatey", || windows::run_chocolatey(ctx))?
|
||||||
|
}
|
||||||
|
Choosenim => runner.execute(*self, "choosenim", || generic::run_choosenim(ctx))?,
|
||||||
|
CinnamonSpices =>
|
||||||
|
{
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
runner.execute(*self, "Cinnamon spices", || linux::run_cinnamon_spices_updater(ctx))?
|
||||||
|
}
|
||||||
|
ClamAvDb => runner.execute(*self, "ClamAV Databases", || generic::run_freshclam(ctx))?,
|
||||||
|
Composer => runner.execute(*self, "composer", || generic::run_composer_update(ctx))?,
|
||||||
|
Conda => runner.execute(*self, "conda", || generic::run_conda_update(ctx))?,
|
||||||
|
ConfigUpdate =>
|
||||||
|
{
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
runner.execute(*self, "config-update", || linux::run_config_update(ctx))?
|
||||||
|
}
|
||||||
|
Containers => runner.execute(*self, "Containers", || containers::run_containers(ctx))?,
|
||||||
|
CustomCommands => {
|
||||||
|
if let Some(commands) = ctx.config().commands() {
|
||||||
|
for (name, command) in commands
|
||||||
|
.iter()
|
||||||
|
.filter(|(n, _)| ctx.config().should_run_custom_command(n))
|
||||||
|
{
|
||||||
|
runner.execute(*self, name.clone(), || generic::run_custom_command(name, command, ctx))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DebGet =>
|
||||||
|
{
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
runner.execute(*self, "deb-get", || linux::run_deb_get(ctx))?
|
||||||
|
}
|
||||||
|
Deno => runner.execute(*self, "deno", || node::deno_upgrade(ctx))?,
|
||||||
|
Distrobox =>
|
||||||
|
{
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
runner.execute(*self, "distrobox", || linux::run_distrobox_update(ctx))?
|
||||||
|
}
|
||||||
|
DkpPacman =>
|
||||||
|
{
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
runner.execute(*self, "dkp-pacman", || linux::run_dkp_pacman_update(ctx))?
|
||||||
|
}
|
||||||
|
Dotnet => runner.execute(*self, ".NET", || generic::run_dotnet_upgrade(ctx))?,
|
||||||
|
Elan => runner.execute(*self, "elan", || generic::run_elan(ctx))?,
|
||||||
|
Emacs => runner.execute(*self, "Emacs", || emacs::Emacs::new().upgrade(ctx))?,
|
||||||
|
Falconf => runner.execute(*self, "falconf sync", || generic::run_falconf(ctx))?,
|
||||||
|
Firmware =>
|
||||||
|
{
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
runner.execute(*self, "Firmware", || linux::run_fwupdmgr(ctx))?
|
||||||
|
}
|
||||||
|
Flatpak =>
|
||||||
|
{
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
runner.execute(*self, "Flatpak", || linux::run_flatpak(ctx))?
|
||||||
|
}
|
||||||
|
Flutter => runner.execute(*self, "Flutter", || generic::run_flutter_upgrade(ctx))?,
|
||||||
|
Fossil => runner.execute(*self, "fossil", || generic::run_fossil(ctx))?,
|
||||||
|
Gcloud => runner.execute(*self, "gcloud", || generic::run_gcloud_components_update(ctx))?,
|
||||||
|
Gem => runner.execute(*self, "gem", || generic::run_gem(ctx))?,
|
||||||
|
Ghcup => runner.execute(*self, "ghcup", || generic::run_ghcup_update(ctx))?,
|
||||||
|
GitRepos => runner.execute(*self, "Git Repositories", || git::run_git_pull(ctx))?,
|
||||||
|
GithubCliExtensions => runner.execute(*self, "GitHub CLI Extensions", || {
|
||||||
|
generic::run_ghcli_extensions_upgrade(ctx)
|
||||||
|
})?,
|
||||||
|
GnomeShellExtensions =>
|
||||||
|
{
|
||||||
|
#[cfg(all(unix, not(any(target_os = "macos", target_os = "android"))))]
|
||||||
|
runner.execute(*self, "Gnome Shell Extensions", || unix::upgrade_gnome_extensions(ctx))?
|
||||||
|
}
|
||||||
|
Go => {
|
||||||
|
runner.execute(*self, "go-global-update", || go::run_go_global_update(ctx))?;
|
||||||
|
runner.execute(*self, "gup", || go::run_go_gup(ctx))?
|
||||||
|
}
|
||||||
|
Guix =>
|
||||||
|
{
|
||||||
|
#[cfg(unix)]
|
||||||
|
runner.execute(*self, "guix", || unix::run_guix(ctx))?
|
||||||
|
}
|
||||||
|
Haxelib => runner.execute(*self, "haxelib", || generic::run_haxelib_update(ctx))?,
|
||||||
|
Helix => runner.execute(*self, "helix", || generic::run_helix_grammars(ctx))?,
|
||||||
|
Helm => runner.execute(*self, "helm", || generic::run_helm_repo_update(ctx))?,
|
||||||
|
HomeManager =>
|
||||||
|
{
|
||||||
|
#[cfg(unix)]
|
||||||
|
runner.execute(*self, "home-manager", || unix::run_home_manager(ctx))?
|
||||||
|
}
|
||||||
|
Hyprpm =>
|
||||||
|
{
|
||||||
|
#[cfg(unix)]
|
||||||
|
runner.execute(*self, "hyprpm", || unix::run_hyprpm(ctx))?
|
||||||
|
}
|
||||||
|
JetbrainsAqua => runner.execute(*self, "JetBrains Aqua Plugins", || generic::run_jetbrains_aqua(ctx))?,
|
||||||
|
JetbrainsClion => runner.execute(*self, "JetBrains CL", || generic::run_jetbrains_clion(ctx))?,
|
||||||
|
JetbrainsDatagrip => {
|
||||||
|
runner.execute(*self, "JetBrains DataGrip", || generic::run_jetbrains_datagrip(ctx))?
|
||||||
|
}
|
||||||
|
JetbrainsDataspell => runner.execute(*self, "JetBrains DataSpell Plugins", || {
|
||||||
|
generic::run_jetbrains_dataspell(ctx)
|
||||||
|
})?,
|
||||||
|
JetbrainsGateway => runner.execute(*self, "JetBrains Gateway Plugins", || {
|
||||||
|
generic::run_jetbrains_gateway(ctx)
|
||||||
|
})?,
|
||||||
|
JetbrainsGoland => {
|
||||||
|
runner.execute(*self, "JetBrains GoLand Plugins", || generic::run_jetbrains_goland(ctx))?
|
||||||
|
}
|
||||||
|
JetbrainsIdea => runner.execute(*self, "JetBrains IntelliJ IDEA Plugins", || {
|
||||||
|
generic::run_jetbrains_idea(ctx)
|
||||||
|
})?,
|
||||||
|
JetbrainsMps => runner.execute(*self, "JetBrains MPS Plugins", || generic::run_jetbrains_mps(ctx))?,
|
||||||
|
JetbrainsPhpstorm => runner.execute(*self, "JetBrains PhpStorm Plugins", || {
|
||||||
|
generic::run_jetbrains_phpstorm(ctx)
|
||||||
|
})?,
|
||||||
|
JetbrainsPycharm => runner.execute(*self, "JetBrains PyCharm Plugins", || {
|
||||||
|
generic::run_jetbrains_pycharm(ctx)
|
||||||
|
})?,
|
||||||
|
JetbrainsRider => runner.execute(*self, "JetBrains Rider Plugins", || generic::run_jetbrains_rider(ctx))?,
|
||||||
|
JetbrainsRubymine => runner.execute(*self, "JetBrains RubyMine Plugins", || {
|
||||||
|
generic::run_jetbrains_rubymine(ctx)
|
||||||
|
})?,
|
||||||
|
JetbrainsRustrover => runner.execute(*self, "JetBrains RustRover Plugins", || {
|
||||||
|
generic::run_jetbrains_rustrover(ctx)
|
||||||
|
})?,
|
||||||
|
JetbrainsToolbox => runner.execute(*self, "JetBrains Toolbox", || generic::run_jetbrains_toolbox(ctx))?,
|
||||||
|
JetbrainsWebstorm => runner.execute(*self, "JetBrains WebStorm Plugins", || {
|
||||||
|
generic::run_jetbrains_webstorm(ctx)
|
||||||
|
})?,
|
||||||
|
Jetpack => runner.execute(*self, "jetpack", || generic::run_jetpack(ctx))?,
|
||||||
|
Julia => runner.execute(*self, "julia", || generic::update_julia_packages(ctx))?,
|
||||||
|
Juliaup => runner.execute(*self, "juliaup", || generic::run_juliaup(ctx))?,
|
||||||
|
Kakoune => runner.execute(*self, "Kakoune", || kakoune::upgrade_kak_plug(ctx))?,
|
||||||
|
Krew => runner.execute(*self, "krew", || generic::run_krew_upgrade(ctx))?,
|
||||||
|
Lensfun => runner.execute(*self, "Lensfun's database update", || {
|
||||||
|
generic::run_lensfun_update_data(ctx)
|
||||||
|
})?,
|
||||||
|
Lure =>
|
||||||
|
{
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
runner.execute(*self, "LURE", || linux::run_lure_update(ctx))?
|
||||||
|
}
|
||||||
|
Macports =>
|
||||||
|
{
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
runner.execute(*self, "MacPorts", || macos::run_macports(ctx))?
|
||||||
|
}
|
||||||
|
Mamba => runner.execute(*self, "mamba", || generic::run_mamba_update(ctx))?,
|
||||||
|
Mandb =>
|
||||||
|
{
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
runner.execute(*self, "Manual Entries", || linux::run_mandb(ctx))?
|
||||||
|
}
|
||||||
|
Mas =>
|
||||||
|
{
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
runner.execute(*self, "App Store", || macos::run_mas(ctx))?
|
||||||
|
}
|
||||||
|
Maza =>
|
||||||
|
{
|
||||||
|
#[cfg(unix)]
|
||||||
|
runner.execute(*self, "maza", || unix::run_maza(ctx))?
|
||||||
|
}
|
||||||
|
Micro => runner.execute(*self, "micro", || generic::run_micro(ctx))?,
|
||||||
|
MicrosoftStore =>
|
||||||
|
{
|
||||||
|
#[cfg(windows)]
|
||||||
|
runner.execute(*self, "Microsoft Store", || windows::microsoft_store(ctx))?
|
||||||
|
}
|
||||||
|
Miktex => runner.execute(*self, "miktex", || generic::run_miktex_packages_update(ctx))?,
|
||||||
|
Mise =>
|
||||||
|
{
|
||||||
|
#[cfg(unix)]
|
||||||
|
runner.execute(*self, "mise", || unix::run_mise(ctx))?
|
||||||
|
}
|
||||||
|
Myrepos => runner.execute(*self, "myrepos", || generic::run_myrepos_update(ctx))?,
|
||||||
|
Nix => {
|
||||||
|
#[cfg(unix)]
|
||||||
|
runner.execute(*self, "nix", || unix::run_nix(ctx))?;
|
||||||
|
#[cfg(unix)]
|
||||||
|
runner.execute(*self, "nix upgrade-nix", || unix::run_nix_self_upgrade(ctx))?
|
||||||
|
}
|
||||||
|
NixHelper =>
|
||||||
|
{
|
||||||
|
#[cfg(unix)]
|
||||||
|
runner.execute(*self, "nh", || unix::run_nix_helper(ctx))?
|
||||||
|
}
|
||||||
|
Node => runner.execute(*self, "npm", || node::run_npm_upgrade(ctx))?,
|
||||||
|
Opam => runner.execute(*self, "opam", || generic::run_opam_update(ctx))?,
|
||||||
|
Pacdef =>
|
||||||
|
{
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
runner.execute(*self, "pacdef", || linux::run_pacdef(ctx))?
|
||||||
|
}
|
||||||
|
Pacstall =>
|
||||||
|
{
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
runner.execute(*self, "pacstall", || linux::run_pacstall(ctx))?
|
||||||
|
}
|
||||||
|
Pearl =>
|
||||||
|
{
|
||||||
|
#[cfg(unix)]
|
||||||
|
runner.execute(*self, "pearl", || unix::run_pearl(ctx))?
|
||||||
|
}
|
||||||
|
Pip3 => runner.execute(*self, "pip3", || generic::run_pip3_update(ctx))?,
|
||||||
|
PipReview => runner.execute(*self, "pip-review", || generic::run_pip_review_update(ctx))?,
|
||||||
|
PipReviewLocal => runner.execute(*self, "pip-review (local)", || {
|
||||||
|
generic::run_pip_review_local_update(ctx)
|
||||||
|
})?,
|
||||||
|
Pipupgrade => runner.execute(*self, "pipupgrade", || generic::run_pipupgrade_update(ctx))?,
|
||||||
|
Pipx => runner.execute(*self, "pipx", || generic::run_pipx_update(ctx))?,
|
||||||
|
Pipxu => runner.execute(*self, "pipxu", || generic::run_pipxu_update(ctx))?,
|
||||||
|
Pixi => runner.execute(*self, "pixi", || generic::run_pixi_update(ctx))?,
|
||||||
|
Pkg => {
|
||||||
|
#[cfg(target_os = "dragonfly")]
|
||||||
|
runner.execute(*self, "Dragonfly BSD Packages", || dragonfly::upgrade_packages(ctx))?;
|
||||||
|
#[cfg(target_os = "freebsd")]
|
||||||
|
runner.execute(*self, "FreeBSD Packages", || freebsd::upgrade_packages(ctx))?;
|
||||||
|
#[cfg(target_os = "openbsd")]
|
||||||
|
runner.execute(*self, "OpenBSD Packages", || openbsd::upgrade_packages(ctx))?;
|
||||||
|
#[cfg(target_os = "android")]
|
||||||
|
runner.execute(*self, "Termux Packages", || android::upgrade_packages(ctx))?
|
||||||
|
}
|
||||||
|
Pkgfile =>
|
||||||
|
{
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
runner.execute(*self, "pkgfile", || linux::run_pkgfile(ctx))?
|
||||||
|
}
|
||||||
|
Pkgin =>
|
||||||
|
{
|
||||||
|
#[cfg(unix)]
|
||||||
|
runner.execute(*self, "pkgin", || unix::run_pkgin(ctx))?
|
||||||
|
}
|
||||||
|
PlatformioCore => runner.execute(*self, "PlatformIO Core", || generic::run_platform_io(ctx))?,
|
||||||
|
Pnpm => runner.execute(*self, "pnpm", || node::run_pnpm_upgrade(ctx))?,
|
||||||
|
Poetry => runner.execute(*self, "Poetry", || generic::run_poetry(ctx))?,
|
||||||
|
Powershell => runner.execute(*self, "Powershell Modules Update", || generic::run_powershell(ctx))?,
|
||||||
|
Protonup =>
|
||||||
|
{
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
runner.execute(*self, "protonup", || linux::run_protonup_update(ctx))?
|
||||||
|
}
|
||||||
|
Pyenv =>
|
||||||
|
{
|
||||||
|
#[cfg(unix)]
|
||||||
|
runner.execute(*self, "pyenv", || unix::run_pyenv(ctx))?
|
||||||
|
}
|
||||||
|
Raco => runner.execute(*self, "raco", || generic::run_raco_update(ctx))?,
|
||||||
|
Rcm =>
|
||||||
|
{
|
||||||
|
#[cfg(unix)]
|
||||||
|
runner.execute(*self, "rcm", || unix::run_rcm(ctx))?
|
||||||
|
}
|
||||||
|
Remotes => {
|
||||||
|
if let Some(topgrades) = ctx.config().remote_topgrades() {
|
||||||
|
for remote_topgrade in topgrades
|
||||||
|
.iter()
|
||||||
|
.filter(|t| ctx.config().should_execute_remote(hostname(), t))
|
||||||
|
{
|
||||||
|
runner.execute(*self, format!("Remote ({remote_topgrade})"), || {
|
||||||
|
crate::ssh::ssh_step(ctx, remote_topgrade)
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Restarts =>
|
||||||
|
{
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
runner.execute(*self, "Restarts", || linux::run_needrestart(ctx))?
|
||||||
|
}
|
||||||
|
Rtcl => runner.execute(*self, "rtcl", || generic::run_rtcl(ctx))?,
|
||||||
|
RubyGems => runner.execute(*self, "rubygems", || generic::run_rubygems(ctx))?,
|
||||||
|
Rustup => runner.execute(*self, "rustup", || generic::run_rustup(ctx))?,
|
||||||
|
Rye => runner.execute(*self, "rye", || generic::run_rye(ctx))?,
|
||||||
|
Scoop =>
|
||||||
|
{
|
||||||
|
#[cfg(windows)]
|
||||||
|
runner.execute(*self, "Scoop", || windows::run_scoop(ctx))?
|
||||||
|
}
|
||||||
|
Sdkman =>
|
||||||
|
{
|
||||||
|
#[cfg(unix)]
|
||||||
|
runner.execute(*self, "SDKMAN!", || unix::run_sdkman(ctx))?
|
||||||
|
}
|
||||||
|
SelfUpdate => {
|
||||||
|
// Self-Update step, this will execute only if:
|
||||||
|
// 1. the `self-update` feature is enabled
|
||||||
|
// 2. it is not disabled from configuration (env var/CLI opt/file)
|
||||||
|
#[cfg(feature = "self-update")]
|
||||||
|
{
|
||||||
|
if std::env::var("TOPGRADE_NO_SELF_UPGRADE").is_err() && !ctx.config().no_self_update() {
|
||||||
|
runner.execute(*self, "Self Update", || self_update::self_update(ctx))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Sheldon => runner.execute(*self, "sheldon", || generic::run_sheldon(ctx))?,
|
||||||
|
Shell => {
|
||||||
|
#[cfg(unix)]
|
||||||
|
{
|
||||||
|
runner.execute(*self, "zr", || zsh::run_zr(ctx))?;
|
||||||
|
runner.execute(*self, "antibody", || zsh::run_antibody(ctx))?;
|
||||||
|
runner.execute(*self, "antidote", || zsh::run_antidote(ctx))?;
|
||||||
|
runner.execute(*self, "antigen", || zsh::run_antigen(ctx))?;
|
||||||
|
runner.execute(*self, "zgenom", || zsh::run_zgenom(ctx))?;
|
||||||
|
runner.execute(*self, "zplug", || zsh::run_zplug(ctx))?;
|
||||||
|
runner.execute(*self, "zinit", || zsh::run_zinit(ctx))?;
|
||||||
|
runner.execute(*self, "zi", || zsh::run_zi(ctx))?;
|
||||||
|
runner.execute(*self, "zim", || zsh::run_zim(ctx))?;
|
||||||
|
runner.execute(*self, "oh-my-zsh", || zsh::run_oh_my_zsh(ctx))?;
|
||||||
|
runner.execute(*self, "oh-my-bash", || unix::run_oh_my_bash(ctx))?;
|
||||||
|
runner.execute(*self, "fisher", || unix::run_fisher(ctx))?;
|
||||||
|
runner.execute(*self, "bash-it", || unix::run_bashit(ctx))?;
|
||||||
|
runner.execute(*self, "oh-my-fish", || unix::run_oh_my_fish(ctx))?;
|
||||||
|
runner.execute(*self, "fish-plug", || unix::run_fish_plug(ctx))?;
|
||||||
|
runner.execute(*self, "fundle", || unix::run_fundle(ctx))?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Snap =>
|
||||||
|
{
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
runner.execute(*self, "snap", || linux::run_snap(ctx))?
|
||||||
|
}
|
||||||
|
Sparkle =>
|
||||||
|
{
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
runner.execute(*self, "Sparkle", || macos::run_sparkle(ctx))?
|
||||||
|
}
|
||||||
|
Spicetify => runner.execute(*self, "spicetify", || generic::spicetify_upgrade(ctx))?,
|
||||||
|
Stack => runner.execute(*self, "stack", || generic::run_stack_update(ctx))?,
|
||||||
|
Stew => runner.execute(*self, "stew", || generic::run_stew(ctx))?,
|
||||||
|
System => {
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
{
|
||||||
|
// NOTE: Due to breaking `nu` updates, `packer.nu` needs to be updated before `nu` get updated
|
||||||
|
// by other package managers.
|
||||||
|
runner.execute(Shell, "packer.nu", || linux::run_packer_nu(ctx))?;
|
||||||
|
|
||||||
|
match ctx.distribution() {
|
||||||
|
Ok(distribution) => {
|
||||||
|
runner.execute(*self, "System update", || distribution.upgrade(ctx))?;
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
println!("{}", t!("Error detecting current distribution: {error}", error = e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
runner.execute(*self, "pihole", || linux::run_pihole_update(ctx))?;
|
||||||
|
}
|
||||||
|
#[cfg(windows)]
|
||||||
|
runner.execute(*self, "Windows update", || windows::windows_update(ctx))?;
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
runner.execute(*self, "System update", || macos::upgrade_macos(ctx))?;
|
||||||
|
#[cfg(target_os = "freebsd")]
|
||||||
|
runner.execute(*self, "FreeBSD Upgrade", || freebsd::upgrade_freebsd(ctx))?;
|
||||||
|
#[cfg(target_os = "openbsd")]
|
||||||
|
runner.execute(*self, "OpenBSD Upgrade", || openbsd::upgrade_openbsd(ctx))?
|
||||||
|
}
|
||||||
|
Tldr => runner.execute(*self, "TLDR", || generic::run_tldr(ctx))?,
|
||||||
|
Tlmgr => runner.execute(*self, "tlmgr", || generic::run_tlmgr_update(ctx))?,
|
||||||
|
Tmux =>
|
||||||
|
{
|
||||||
|
#[cfg(unix)]
|
||||||
|
runner.execute(*self, "tmux", || tmux::run_tpm(ctx))?
|
||||||
|
}
|
||||||
|
Toolbx =>
|
||||||
|
{
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
runner.execute(*self, "toolbx", || toolbx::run_toolbx(ctx))?
|
||||||
|
}
|
||||||
|
Typst => runner.execute(*self, "Typst", || generic::run_typst(ctx))?,
|
||||||
|
Uv => runner.execute(*self, "uv", || generic::run_uv(ctx))?,
|
||||||
|
Vagrant => {
|
||||||
|
if ctx.config().should_run(Vagrant) {
|
||||||
|
if let Ok(boxes) = vagrant::collect_boxes(ctx) {
|
||||||
|
for vagrant_box in boxes {
|
||||||
|
runner.execute(*self, format!("Vagrant ({})", vagrant_box.smart_name()), || {
|
||||||
|
vagrant::topgrade_vagrant_box(ctx, &vagrant_box)
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
runner.execute(*self, "Vagrant boxes", || vagrant::upgrade_vagrant_boxes(ctx))?;
|
||||||
|
}
|
||||||
|
Vcpkg => runner.execute(*self, "vcpkg", || generic::run_vcpkg_update(ctx))?,
|
||||||
|
Vim => {
|
||||||
|
runner.execute(*self, "vim", || vim::upgrade_vim(ctx))?;
|
||||||
|
runner.execute(*self, "Neovim", || vim::upgrade_neovim(ctx))?;
|
||||||
|
runner.execute(*self, "The Ultimate vimrc", || vim::upgrade_ultimate_vimrc(ctx))?;
|
||||||
|
runner.execute(*self, "voom", || vim::run_voom(ctx))?
|
||||||
|
}
|
||||||
|
VoltaPackages => runner.execute(*self, "volta packages", || node::run_volta_packages_upgrade(ctx))?,
|
||||||
|
Vscode => runner.execute(*self, "Visual Studio Code extensions", || {
|
||||||
|
generic::run_vscode_extensions_update(ctx)
|
||||||
|
})?,
|
||||||
|
VscodeInsiders => runner.execute(*self, "Visual Studio Code Insiders extensions", || {
|
||||||
|
generic::run_vscode_insiders_extensions_update(ctx)
|
||||||
|
})?,
|
||||||
|
Vscodium => runner.execute(*self, "VSCodium extensions", || {
|
||||||
|
generic::run_vscodium_extensions_update(ctx)
|
||||||
|
})?,
|
||||||
|
VscodiumInsiders => runner.execute(*self, "VSCodium Insiders extensions", || {
|
||||||
|
generic::run_vscodium_insiders_extensions_update(ctx)
|
||||||
|
})?,
|
||||||
|
Waydroid =>
|
||||||
|
{
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
runner.execute(*self, "Waydroid", || linux::run_waydroid(ctx))?
|
||||||
|
}
|
||||||
|
Winget =>
|
||||||
|
{
|
||||||
|
#[cfg(windows)]
|
||||||
|
runner.execute(*self, "Winget", || windows::run_winget(ctx))?
|
||||||
|
}
|
||||||
|
Wsl =>
|
||||||
|
{
|
||||||
|
#[cfg(windows)]
|
||||||
|
runner.execute(*self, "WSL", || windows::run_wsl_topgrade(ctx))?
|
||||||
|
}
|
||||||
|
WslUpdate =>
|
||||||
|
{
|
||||||
|
#[cfg(windows)]
|
||||||
|
runner.execute(*self, "Update WSL", || windows::update_wsl(ctx))?
|
||||||
|
}
|
||||||
|
Xcodes =>
|
||||||
|
{
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
runner.execute(*self, "Xcodes", || macos::update_xcodes(ctx))?
|
||||||
|
}
|
||||||
|
Yadm =>
|
||||||
|
{
|
||||||
|
#[cfg(unix)]
|
||||||
|
runner.execute(*self, "yadm", || unix::run_yadm(ctx))?
|
||||||
|
}
|
||||||
|
Yarn => runner.execute(*self, "yarn", || node::run_yarn_upgrade(ctx))?,
|
||||||
|
Yazi => runner.execute(*self, "Yazi packages", || generic::run_yazi(ctx))?,
|
||||||
|
Zigup => runner.execute(*self, "zigup", || generic::run_zigup(ctx))?,
|
||||||
|
Zvm => runner.execute(*self, "ZVM", || generic::run_zvm(ctx))?,
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_lines)]
|
||||||
|
pub(crate) fn default_steps() -> Vec<Step> {
|
||||||
|
// For now, SelfRenamer and SelfUpdate isn't included as they're ran before the other non-steps (pre-commands, sudo, etc)
|
||||||
|
|
||||||
|
use Step::*;
|
||||||
|
// Could probably have a smaller starting capacity, but this at least ensures only 2 allocations:
|
||||||
|
// initial and shrink
|
||||||
|
let mut steps = Vec::with_capacity(Step::COUNT);
|
||||||
|
|
||||||
|
// Not combined with other generic steps to preserve the order as it was in main.rs originally,
|
||||||
|
// but this can be changed in the future.
|
||||||
|
steps.push(Remotes);
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
steps.extend_from_slice(&[Wsl, WslUpdate, Chocolatey, Scoop, Winget, System, MicrosoftStore]);
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
steps.extend_from_slice(&[BrewFormula, BrewCask, Macports, Xcodes, Sparkle, Mas, System]);
|
||||||
|
|
||||||
|
#[cfg(target_os = "dragonfly")]
|
||||||
|
steps.extend_from_slice(&[Pkg, Audit]);
|
||||||
|
|
||||||
|
#[cfg(target_os = "freebsd")]
|
||||||
|
steps.extend_from_slice(&[Pkg, System, Audit]);
|
||||||
|
|
||||||
|
#[cfg(target_os = "openbsd")]
|
||||||
|
steps.extend_from_slice(&[Pkg, System]);
|
||||||
|
|
||||||
|
#[cfg(target_os = "android")]
|
||||||
|
steps.push(Pkg);
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
steps.extend_from_slice(&[
|
||||||
|
System,
|
||||||
|
ConfigUpdate,
|
||||||
|
AM,
|
||||||
|
AppMan,
|
||||||
|
DebGet,
|
||||||
|
Toolbx,
|
||||||
|
Snap,
|
||||||
|
Pacstall,
|
||||||
|
Pacdef,
|
||||||
|
Protonup,
|
||||||
|
Distrobox,
|
||||||
|
DkpPacman,
|
||||||
|
Firmware,
|
||||||
|
Restarts,
|
||||||
|
Flatpak,
|
||||||
|
BrewFormula,
|
||||||
|
Lure,
|
||||||
|
Waydroid,
|
||||||
|
AutoCpufreq,
|
||||||
|
CinnamonSpices,
|
||||||
|
Mandb,
|
||||||
|
Pkgfile,
|
||||||
|
]);
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
steps.extend_from_slice(&[
|
||||||
|
Yadm,
|
||||||
|
Nix,
|
||||||
|
NixHelper,
|
||||||
|
Guix,
|
||||||
|
HomeManager,
|
||||||
|
Asdf,
|
||||||
|
Mise,
|
||||||
|
Pkgin,
|
||||||
|
BunPackages,
|
||||||
|
Shell,
|
||||||
|
Tmux,
|
||||||
|
Pearl,
|
||||||
|
#[cfg(not(any(target_os = "macos", target_os = "android")))]
|
||||||
|
GnomeShellExtensions,
|
||||||
|
Pyenv,
|
||||||
|
Sdkman,
|
||||||
|
Rcm,
|
||||||
|
Maza,
|
||||||
|
Hyprpm,
|
||||||
|
Atuin,
|
||||||
|
]);
|
||||||
|
|
||||||
|
#[cfg(not(any(
|
||||||
|
target_os = "freebsd",
|
||||||
|
target_os = "openbsd",
|
||||||
|
target_os = "netbsd",
|
||||||
|
target_os = "dragonfly"
|
||||||
|
)))]
|
||||||
|
steps.push(Atom);
|
||||||
|
|
||||||
|
// The following update function should be executed on all OSes.
|
||||||
|
steps.extend_from_slice(&[
|
||||||
|
Fossil,
|
||||||
|
Elan,
|
||||||
|
Rye,
|
||||||
|
Rustup,
|
||||||
|
Juliaup,
|
||||||
|
Dotnet,
|
||||||
|
Choosenim,
|
||||||
|
Cargo,
|
||||||
|
Flutter,
|
||||||
|
Go,
|
||||||
|
Emacs,
|
||||||
|
Opam,
|
||||||
|
Vcpkg,
|
||||||
|
Pipx,
|
||||||
|
Pipxu,
|
||||||
|
Vscode,
|
||||||
|
VscodeInsiders,
|
||||||
|
Vscodium,
|
||||||
|
VscodiumInsiders,
|
||||||
|
Conda,
|
||||||
|
Mamba,
|
||||||
|
Pixi,
|
||||||
|
Miktex,
|
||||||
|
Pip3,
|
||||||
|
PipReview,
|
||||||
|
PipReviewLocal,
|
||||||
|
Pipupgrade,
|
||||||
|
Ghcup,
|
||||||
|
Stack,
|
||||||
|
Tldr,
|
||||||
|
Tlmgr,
|
||||||
|
Myrepos,
|
||||||
|
Chezmoi,
|
||||||
|
Jetpack,
|
||||||
|
Vim,
|
||||||
|
Kakoune,
|
||||||
|
Helix,
|
||||||
|
Node,
|
||||||
|
Yarn,
|
||||||
|
Pnpm,
|
||||||
|
VoltaPackages,
|
||||||
|
Containers,
|
||||||
|
Deno,
|
||||||
|
Composer,
|
||||||
|
Krew,
|
||||||
|
Helm,
|
||||||
|
Gem,
|
||||||
|
RubyGems,
|
||||||
|
Julia,
|
||||||
|
Haxelib,
|
||||||
|
Sheldon,
|
||||||
|
Stew,
|
||||||
|
Rtcl,
|
||||||
|
Bin,
|
||||||
|
Gcloud,
|
||||||
|
Micro,
|
||||||
|
Raco,
|
||||||
|
Spicetify,
|
||||||
|
GithubCliExtensions,
|
||||||
|
Bob,
|
||||||
|
Certbot,
|
||||||
|
GitRepos,
|
||||||
|
ClamAvDb,
|
||||||
|
PlatformioCore,
|
||||||
|
Lensfun,
|
||||||
|
Poetry,
|
||||||
|
Uv,
|
||||||
|
Zvm,
|
||||||
|
Aqua,
|
||||||
|
Bun,
|
||||||
|
Zigup,
|
||||||
|
JetbrainsToolbox,
|
||||||
|
AndroidStudio,
|
||||||
|
JetbrainsAqua,
|
||||||
|
JetbrainsClion,
|
||||||
|
JetbrainsDatagrip,
|
||||||
|
JetbrainsDataspell,
|
||||||
|
// JetBrains dotCover has no CLI
|
||||||
|
// JetBrains dotMemory has no CLI
|
||||||
|
// JetBrains dotPeek has no CLI
|
||||||
|
// JetBrains dotTrace has no CLI
|
||||||
|
// JetBrains Fleet has a different CLI without a `fleet update` command.
|
||||||
|
JetbrainsGateway,
|
||||||
|
JetbrainsGoland,
|
||||||
|
JetbrainsIdea,
|
||||||
|
JetbrainsMps,
|
||||||
|
JetbrainsPhpstorm,
|
||||||
|
JetbrainsPycharm,
|
||||||
|
// JetBrains ReSharper has no CLI (it's a VSCode extension)
|
||||||
|
// JetBrains ReSharper C++ has no CLI (it's a VSCode extension)
|
||||||
|
JetbrainsRider,
|
||||||
|
JetbrainsRubymine,
|
||||||
|
JetbrainsRustrover,
|
||||||
|
// JetBrains Space Desktop does not have a CLI
|
||||||
|
JetbrainsWebstorm,
|
||||||
|
Yazi,
|
||||||
|
Falconf,
|
||||||
|
Powershell,
|
||||||
|
CustomCommands,
|
||||||
|
Vagrant,
|
||||||
|
Typst,
|
||||||
|
]);
|
||||||
|
|
||||||
|
steps.shrink_to_fit();
|
||||||
|
|
||||||
|
steps
|
||||||
|
}
|
||||||
@@ -1,15 +1,17 @@
|
|||||||
use std::fmt::{Display, Formatter};
|
use std::fmt::{Display, Formatter};
|
||||||
|
use std::io;
|
||||||
|
use std::io::Write;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
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 color_eyre::eyre::{eyre, OptionExt};
|
||||||
use tracing::{debug, error, warn};
|
use tracing::{debug, error, warn};
|
||||||
use wildmatch::WildMatch;
|
use wildmatch::WildMatch;
|
||||||
|
|
||||||
use crate::command::CommandExt;
|
use crate::command::CommandExt;
|
||||||
use crate::error::{self, TopgradeError};
|
use crate::error::{self, SkipStep, TopgradeError};
|
||||||
use crate::terminal::print_separator;
|
use crate::terminal::print_separator;
|
||||||
use crate::{execution_context::ExecutionContext, utils::require};
|
use crate::{execution_context::ExecutionContext, utils::require};
|
||||||
use rust_i18n::t;
|
use rust_i18n::t;
|
||||||
@@ -21,6 +23,9 @@ use rust_i18n::t;
|
|||||||
// themselves or when using docker-compose.
|
// themselves or when using docker-compose.
|
||||||
const NONEXISTENT_REPO: &str = "repository does not exist";
|
const NONEXISTENT_REPO: &str = "repository does not exist";
|
||||||
|
|
||||||
|
// A string found in the output of docker when Docker Desktop is not running.
|
||||||
|
const DOCKER_NOT_RUNNING: &str = "We recommend to activate the WSL integration in Docker Desktop settings.";
|
||||||
|
|
||||||
/// Uniquely identifies a `Container`.
|
/// Uniquely identifies a `Container`.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Container {
|
struct Container {
|
||||||
@@ -74,7 +79,7 @@ fn list_containers(crt: &Path, ignored_containers: Option<&Vec<String>>) -> Resu
|
|||||||
);
|
);
|
||||||
let output = Command::new(crt)
|
let output = Command::new(crt)
|
||||||
.args(["image", "ls", "--format", "{{.Repository}}:{{.Tag}} {{.ID}}"])
|
.args(["image", "ls", "--format", "{{.Repository}}:{{.Tag}} {{.ID}}"])
|
||||||
.output_checked_with_utf8(|_| Ok(()))?;
|
.output_checked_utf8()?;
|
||||||
|
|
||||||
let mut retval = vec![];
|
let mut retval = vec![];
|
||||||
for line in output.stdout.lines() {
|
for line in output.stdout.lines() {
|
||||||
@@ -99,7 +104,12 @@ fn list_containers(crt: &Path, ignored_containers: Option<&Vec<String>>) -> Resu
|
|||||||
|
|
||||||
// line is of format: `Repository:Tag ImageID`, e.g., `nixos/nix:latest d80fea9c32b4`
|
// line is of format: `Repository:Tag ImageID`, e.g., `nixos/nix:latest d80fea9c32b4`
|
||||||
let split_res = line.split(' ').collect::<Vec<&str>>();
|
let split_res = line.split(' ').collect::<Vec<&str>>();
|
||||||
assert_eq!(split_res.len(), 2);
|
if split_res.len() != 2 {
|
||||||
|
return Err(eyre!(format!(
|
||||||
|
"Got erroneous output from `{} image ls --format \"{{.Repository}}:{{.Tag}} {{.ID}}\"; Expected line to split into 2 parts",
|
||||||
|
crt.display()
|
||||||
|
)));
|
||||||
|
}
|
||||||
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(ref ignored_containers) = ignored_containers {
|
if let Some(ref ignored_containers) = ignored_containers {
|
||||||
@@ -116,11 +126,16 @@ fn list_containers(crt: &Path, ignored_containers: Option<&Vec<String>>) -> Resu
|
|||||||
);
|
);
|
||||||
let inspect_output = Command::new(crt)
|
let inspect_output = Command::new(crt)
|
||||||
.args(["image", "inspect", image_id, "--format", "{{.Os}}/{{.Architecture}}"])
|
.args(["image", "inspect", image_id, "--format", "{{.Os}}/{{.Architecture}}"])
|
||||||
.output_checked_with_utf8(|_| Ok(()))?;
|
.output_checked_utf8()?;
|
||||||
let mut platform = inspect_output.stdout;
|
let mut platform = inspect_output.stdout;
|
||||||
// truncate the tailing new line character
|
// truncate the tailing new line character
|
||||||
platform.truncate(platform.len() - 1);
|
platform.truncate(platform.len() - 1);
|
||||||
assert!(platform.contains('/'));
|
if !platform.contains('/') {
|
||||||
|
return Err(eyre!(format!(
|
||||||
|
"Got erroneous output from `{} image ls --format \"{{.Repository}}:{{.Tag}} {{.ID}}\"; Expected platform to contain '/'",
|
||||||
|
crt.display()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
retval.push(Container::new(repo_tag.to_string(), platform));
|
retval.push(Container::new(repo_tag.to_string(), platform));
|
||||||
}
|
}
|
||||||
@@ -135,20 +150,55 @@ pub fn run_containers(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
debug!("Using container runtime '{}'", crt.display());
|
debug!("Using container runtime '{}'", crt.display());
|
||||||
|
|
||||||
print_separator(t!("Containers"));
|
print_separator(t!("Containers"));
|
||||||
|
|
||||||
|
let output = Command::new(&crt).arg("--help").output_checked_with(|_| Ok(()))?;
|
||||||
|
let status_code = output
|
||||||
|
.status
|
||||||
|
.code()
|
||||||
|
.ok_or_eyre("Couldn't get status code (terminated by signal)")?;
|
||||||
|
let stdout = std::str::from_utf8(&output.stdout).wrap_err("Expected output to be valid UTF-8")?;
|
||||||
|
if stdout.contains(DOCKER_NOT_RUNNING) && status_code == 1 {
|
||||||
|
// Write the output
|
||||||
|
io::stdout().write_all(&output.stdout)?;
|
||||||
|
io::stderr().write_all(&output.stderr)?;
|
||||||
|
// Don't crash, but don't be silent either.
|
||||||
|
// This can happen in other ways than Docker Desktop not running, but even in those cases
|
||||||
|
// we don't want to crash, since the containers step is enabled by default.
|
||||||
|
warn!(
|
||||||
|
"{} seems to be non-functional right now (see above). Is WSL integration enabled for Docker Desktop? Is Docker Desktop running?",
|
||||||
|
crt.display()
|
||||||
|
);
|
||||||
|
return Err(SkipStep(format!(
|
||||||
|
"{} seems to be non-functional right now. Possibly WSL integration is not enabled for Docker Desktop, or Docker Desktop is not running.",
|
||||||
|
crt.display()
|
||||||
|
)).into());
|
||||||
|
} else if !output.status.success() {
|
||||||
|
// Write the output
|
||||||
|
io::stdout().write_all(&output.stdout)?;
|
||||||
|
io::stderr().write_all(&output.stderr)?;
|
||||||
|
// If we saw the message, but the code is not 1 (e.g. 0, or a non-1 failure), crash, as we expect a 1.
|
||||||
|
// If we did not see the message, it's broken in some way we do not understand.
|
||||||
|
return Err(eyre!(
|
||||||
|
"{0} seems to be non-functional (`{0} --help` returned non-zero exit code {1})",
|
||||||
|
crt.display(),
|
||||||
|
status_code,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
let mut success = true;
|
let mut success = true;
|
||||||
let containers =
|
let containers =
|
||||||
list_containers(&crt, ctx.config().containers_ignored_tags()).context("Failed to list Docker containers")?;
|
list_containers(&crt, ctx.config().containers_ignored_tags()).context("Failed to list Docker containers")?;
|
||||||
debug!("Containers to inspect: {:?}", containers);
|
debug!("Containers to inspect: {:?}", containers);
|
||||||
|
|
||||||
for container in containers.iter() {
|
for container in &containers {
|
||||||
debug!("Pulling container '{}'", container);
|
debug!("Pulling container '{}'", container);
|
||||||
let args = vec![
|
let mut args = vec!["pull", container.repo_tag.as_str()];
|
||||||
"pull",
|
if container.platform.as_str() != "/" {
|
||||||
container.repo_tag.as_str(),
|
args.push("--platform");
|
||||||
"--platform",
|
args.push(container.platform.as_str());
|
||||||
container.platform.as_str(),
|
}
|
||||||
];
|
|
||||||
let mut exec = ctx.run_type().execute(&crt);
|
let mut exec = ctx.execute(&crt);
|
||||||
|
|
||||||
if let Err(e) = exec.args(&args).status_checked() {
|
if let Err(e) = exec.args(&args).status_checked() {
|
||||||
error!("Pulling container '{}' failed: {}", container, e);
|
error!("Pulling container '{}' failed: {}", container, e);
|
||||||
@@ -177,12 +227,7 @@ pub fn run_containers(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
if ctx.config().cleanup() {
|
if ctx.config().cleanup() {
|
||||||
// Remove dangling images
|
// Remove dangling images
|
||||||
debug!("Removing dangling images");
|
debug!("Removing dangling images");
|
||||||
if let Err(e) = ctx
|
if let Err(e) = ctx.execute(&crt).args(["image", "prune", "-f"]).status_checked() {
|
||||||
.run_type()
|
|
||||||
.execute(&crt)
|
|
||||||
.args(["image", "prune", "-f"])
|
|
||||||
.status_checked()
|
|
||||||
{
|
|
||||||
error!("Removing dangling images failed: {}", e);
|
error!("Removing dangling images failed: {}", e);
|
||||||
success = false;
|
success = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,4 @@
|
|||||||
(when (fboundp 'paradox-upgrade-packages)
|
(when (featurep 'package)
|
||||||
(progn
|
(if (fboundp 'package-upgrade-all)
|
||||||
(unless (boundp 'paradox-github-token)
|
(package-upgrade-all nil)
|
||||||
(setq paradox-github-token t))
|
(message "Your Emacs version doesn't support unattended packages upgrade")))
|
||||||
(paradox-upgrade-packages)
|
|
||||||
(princ
|
|
||||||
(if (get-buffer "*Paradox Report*")
|
|
||||||
(with-current-buffer "*Paradox Report*" (buffer-string))
|
|
||||||
"\nNothing to upgrade\n"))))
|
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ use rust_i18n::t;
|
|||||||
|
|
||||||
use crate::command::CommandExt;
|
use crate::command::CommandExt;
|
||||||
use crate::execution_context::ExecutionContext;
|
use crate::execution_context::ExecutionContext;
|
||||||
|
use crate::step::Step;
|
||||||
use crate::terminal::print_separator;
|
use crate::terminal::print_separator;
|
||||||
use crate::utils::{require, require_option, PathExt};
|
use crate::utils::{require, require_option, PathExt};
|
||||||
use crate::Step;
|
|
||||||
|
|
||||||
const EMACS_UPGRADE: &str = include_str!("emacs.el");
|
const EMACS_UPGRADE: &str = include_str!("emacs.el");
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
@@ -60,12 +60,16 @@ impl Emacs {
|
|||||||
fn update_doom(doom: &Path, ctx: &ExecutionContext) -> Result<()> {
|
fn update_doom(doom: &Path, ctx: &ExecutionContext) -> Result<()> {
|
||||||
print_separator("Doom Emacs");
|
print_separator("Doom Emacs");
|
||||||
|
|
||||||
let mut command = ctx.run_type().execute(doom);
|
let mut command = ctx.execute(doom);
|
||||||
if ctx.config().yes(Step::Emacs) {
|
if ctx.config().yes(Step::Emacs) {
|
||||||
command.arg("--force");
|
command.arg("--force");
|
||||||
}
|
}
|
||||||
|
|
||||||
command.args(["upgrade"]);
|
command.arg("upgrade");
|
||||||
|
|
||||||
|
if ctx.config().doom_aot() {
|
||||||
|
command.arg("--aot");
|
||||||
|
}
|
||||||
|
|
||||||
command.status_checked()
|
command.status_checked()
|
||||||
}
|
}
|
||||||
@@ -84,7 +88,7 @@ impl Emacs {
|
|||||||
|
|
||||||
print_separator("Emacs");
|
print_separator("Emacs");
|
||||||
|
|
||||||
let mut command = ctx.run_type().execute(emacs);
|
let mut command = ctx.execute(emacs);
|
||||||
|
|
||||||
command
|
command
|
||||||
.args(["--batch", "--debug-init", "-l"])
|
.args(["--batch", "--debug-init", "-l"])
|
||||||
|
|||||||
1248
src/steps/generic.rs
1248
src/steps/generic.rs
File diff suppressed because it is too large
Load Diff
@@ -13,8 +13,8 @@ use tokio::runtime;
|
|||||||
use tracing::{debug, error};
|
use tracing::{debug, error};
|
||||||
|
|
||||||
use crate::command::CommandExt;
|
use crate::command::CommandExt;
|
||||||
use crate::config::Step;
|
|
||||||
use crate::execution_context::ExecutionContext;
|
use crate::execution_context::ExecutionContext;
|
||||||
|
use crate::step::Step;
|
||||||
use crate::steps::emacs::Emacs;
|
use crate::steps::emacs::Emacs;
|
||||||
use crate::terminal::print_separator;
|
use crate::terminal::print_separator;
|
||||||
use crate::utils::{require, PathExt};
|
use crate::utils::{require, PathExt};
|
||||||
@@ -58,9 +58,10 @@ pub fn run_git_pull(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
repos.insert_if_repo(HOME_DIR.join(".dotfiles"));
|
repos.insert_if_repo(HOME_DIR.join(".dotfiles"));
|
||||||
}
|
}
|
||||||
|
|
||||||
let powershell = crate::steps::powershell::Powershell::new();
|
if let Some(powershell) = ctx.powershell() {
|
||||||
if let Some(profile) = powershell.profile() {
|
if let Some(profile) = powershell.profile() {
|
||||||
repos.insert_if_repo(profile);
|
repos.insert_if_repo(profile);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,7 +93,7 @@ pub fn run_git_pull(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
// Handle user-defined repos
|
// Handle user-defined repos
|
||||||
if let Some(custom_git_repos) = config.git_repos() {
|
if let Some(custom_git_repos) = config.git_repos() {
|
||||||
for git_repo in custom_git_repos {
|
for git_repo in custom_git_repos {
|
||||||
repos.glob_insert(git_repo);
|
repos.glob_insert(&shellexpand::tilde(git_repo));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,7 +106,7 @@ pub fn run_git_pull(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
print_warning(t!(
|
print_warning(t!(
|
||||||
"Path {pattern} did not contain any git repositories",
|
"Path {pattern} did not contain any git repositories",
|
||||||
pattern = pattern
|
pattern = pattern
|
||||||
))
|
));
|
||||||
});
|
});
|
||||||
|
|
||||||
if repos.is_repos_empty() {
|
if repos.is_repos_empty() {
|
||||||
@@ -207,10 +208,13 @@ impl RepoStep {
|
|||||||
|
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
Err(e) => match e.kind() {
|
Err(e) => {
|
||||||
io::ErrorKind::NotFound => debug!("{} does not exist", path.as_ref().display()),
|
if e.kind() == io::ErrorKind::NotFound {
|
||||||
_ => error!("Error looking for {}: {e}", path.as_ref().display(),),
|
debug!("{} does not exist", path.as_ref().display());
|
||||||
},
|
} else {
|
||||||
|
error!("Error looking for {}: {e}", path.as_ref().display());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
@@ -321,7 +325,7 @@ impl RepoStep {
|
|||||||
.output()
|
.output()
|
||||||
.await?;
|
.await?;
|
||||||
let result = output_checked_utf8(pull_output)
|
let result = output_checked_utf8(pull_output)
|
||||||
.and_then(|_| output_checked_utf8(submodule_output))
|
.and_then(|()| output_checked_utf8(submodule_output))
|
||||||
.wrap_err_with(|| format!("Failed to pull {}", repo.as_ref().display()));
|
.wrap_err_with(|| format!("Failed to pull {}", repo.as_ref().display()));
|
||||||
|
|
||||||
if result.is_err() {
|
if result.is_err() {
|
||||||
@@ -359,7 +363,7 @@ impl RepoStep {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result.map(|_| ())
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pull the repositories specified in `self.repos`.
|
/// Pull the repositories specified in `self.repos`.
|
||||||
@@ -410,7 +414,7 @@ impl RepoStep {
|
|||||||
let basic_rt = runtime::Runtime::new()?;
|
let basic_rt = runtime::Runtime::new()?;
|
||||||
let results = basic_rt.block_on(async { stream_of_futures.collect::<Vec<Result<()>>>().await });
|
let results = basic_rt.block_on(async { stream_of_futures.collect::<Vec<Result<()>>>().await });
|
||||||
|
|
||||||
let error = results.into_iter().find(|r| r.is_err());
|
let error = results.into_iter().find(std::result::Result::is_err);
|
||||||
error.unwrap_or(Ok(()))
|
error.unwrap_or(Ok(()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ pub fn run_go_global_update(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
|
|
||||||
print_separator("go-global-update");
|
print_separator("go-global-update");
|
||||||
|
|
||||||
ctx.run_type().execute(go_global_update).status_checked()
|
ctx.execute(go_global_update).status_checked()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <https://github.com/nao1215/gup>
|
/// <https://github.com/nao1215/gup>
|
||||||
@@ -24,7 +24,7 @@ pub fn run_go_gup(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
|
|
||||||
print_separator("gup");
|
print_separator("gup");
|
||||||
|
|
||||||
ctx.run_type().execute(gup).arg("update").status_checked()
|
ctx.execute(gup).arg("update").status_checked()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the path of a Go binary.
|
/// Get the path of a Go binary.
|
||||||
|
|||||||
@@ -12,11 +12,8 @@ pub fn upgrade_kak_plug(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
|
|
||||||
print_separator("Kakoune");
|
print_separator("Kakoune");
|
||||||
|
|
||||||
// TODO: Why supress output for this command?
|
// TODO: Why suppress output for this command?
|
||||||
ctx.run_type()
|
ctx.execute(kak).args(["-ui", "dummy", "-e", UPGRADE_KAK]).output()?;
|
||||||
.execute(kak)
|
|
||||||
.args(["-ui", "dummy", "-e", UPGRADE_KAK])
|
|
||||||
.output()?;
|
|
||||||
|
|
||||||
println!("{}", t!("Plugins upgraded"));
|
println!("{}", t!("Plugins upgraded"));
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ use std::os::unix::fs::MetadataExt;
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
use crate::utils::{get_require_sudo_string, require_option};
|
|
||||||
use crate::HOME_DIR;
|
use crate::HOME_DIR;
|
||||||
use color_eyre::eyre::Result;
|
use color_eyre::eyre::Result;
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
@@ -87,20 +86,16 @@ impl NPM {
|
|||||||
.args(["--version"])
|
.args(["--version"])
|
||||||
.output_checked_utf8()
|
.output_checked_utf8()
|
||||||
.map(|s| s.stdout.trim().to_owned());
|
.map(|s| s.stdout.trim().to_owned());
|
||||||
Version::parse(&version_str?).map_err(|err| err.into())
|
Version::parse(&version_str?).map_err(std::convert::Into::into)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn upgrade(&self, ctx: &ExecutionContext, use_sudo: bool) -> Result<()> {
|
fn upgrade(&self, ctx: &ExecutionContext, use_sudo: bool) -> Result<()> {
|
||||||
let args = ["update", self.global_location_arg()];
|
let args = ["update", self.global_location_arg()];
|
||||||
if use_sudo {
|
if use_sudo {
|
||||||
let sudo = require_option(ctx.sudo().clone(), get_require_sudo_string())?;
|
let sudo = ctx.require_sudo()?;
|
||||||
ctx.run_type()
|
sudo.execute(ctx, &self.command)?.args(args).status_checked()?;
|
||||||
.execute(sudo)
|
|
||||||
.arg(&self.command)
|
|
||||||
.args(args)
|
|
||||||
.status_checked()?;
|
|
||||||
} else {
|
} else {
|
||||||
ctx.run_type().execute(&self.command).args(args).status_checked()?;
|
ctx.execute(&self.command).args(args).status_checked()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -122,15 +117,11 @@ impl NPM {
|
|||||||
|
|
||||||
struct Yarn {
|
struct Yarn {
|
||||||
command: PathBuf,
|
command: PathBuf,
|
||||||
yarn: Option<PathBuf>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Yarn {
|
impl Yarn {
|
||||||
fn new(command: PathBuf) -> Self {
|
fn new(command: PathBuf) -> Self {
|
||||||
Self {
|
Self { command }
|
||||||
command,
|
|
||||||
yarn: require("yarn").ok(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn has_global_subcmd(&self) -> bool {
|
fn has_global_subcmd(&self) -> bool {
|
||||||
@@ -157,14 +148,10 @@ impl Yarn {
|
|||||||
let args = ["global", "upgrade"];
|
let args = ["global", "upgrade"];
|
||||||
|
|
||||||
if use_sudo {
|
if use_sudo {
|
||||||
let sudo = require_option(ctx.sudo().clone(), get_require_sudo_string())?;
|
let sudo = ctx.require_sudo()?;
|
||||||
ctx.run_type()
|
sudo.execute(ctx, &self.command)?.args(args).status_checked()?;
|
||||||
.execute(sudo)
|
|
||||||
.arg(self.yarn.as_ref().unwrap_or(&self.command))
|
|
||||||
.args(args)
|
|
||||||
.status_checked()?;
|
|
||||||
} else {
|
} else {
|
||||||
ctx.run_type().execute(&self.command).args(args).status_checked()?;
|
ctx.execute(&self.command).args(args).status_checked()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -184,6 +171,88 @@ impl Yarn {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct Deno {
|
||||||
|
command: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deno {
|
||||||
|
fn new(command: PathBuf) -> Self {
|
||||||
|
Self { command }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn upgrade(&self, ctx: &ExecutionContext) -> Result<()> {
|
||||||
|
let mut args = vec![];
|
||||||
|
|
||||||
|
let version = ctx.config().deno_version();
|
||||||
|
if let Some(version) = version {
|
||||||
|
let bin_version = self.version()?;
|
||||||
|
|
||||||
|
if bin_version >= Version::new(2, 0, 0) {
|
||||||
|
args.push(version);
|
||||||
|
} else if bin_version >= Version::new(1, 6, 0) {
|
||||||
|
match version {
|
||||||
|
"stable" => { /* do nothing, as stable is the default channel to upgrade */ }
|
||||||
|
"rc" => {
|
||||||
|
return Err(SkipStep(
|
||||||
|
"Deno (1.6.0-2.0.0) cannot be upgraded to a release candidate".to_string(),
|
||||||
|
)
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
"canary" => args.push("--canary"),
|
||||||
|
_ => {
|
||||||
|
if Version::parse(version).is_err() {
|
||||||
|
return Err(SkipStep("Invalid Deno version".to_string()).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
args.push("--version");
|
||||||
|
args.push(version);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if bin_version >= Version::new(1, 0, 0) {
|
||||||
|
match version {
|
||||||
|
"stable" | "rc" | "canary" => {
|
||||||
|
// Prior to v1.6.0, `deno upgrade` is not able fetch the latest tag version.
|
||||||
|
return Err(
|
||||||
|
SkipStep("Deno (1.0.0-1.6.0) cannot be upgraded to a named channel".to_string()).into(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
if Version::parse(version).is_err() {
|
||||||
|
return Err(SkipStep("Invalid Deno version".to_string()).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
args.push("--version");
|
||||||
|
args.push(version);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// v0.x cannot be upgraded with `deno upgrade` to v1.x or v2.x
|
||||||
|
// nor can be upgraded to a specific version.
|
||||||
|
return Err(SkipStep("Unsupported Deno version".to_string()).into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.execute(&self.command).arg("upgrade").args(args).status_checked()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the version of Deno.
|
||||||
|
///
|
||||||
|
/// This function will return the version of Deno installed on the system.
|
||||||
|
/// The version is parsed from the output of `deno -V`.
|
||||||
|
///
|
||||||
|
/// ```sh
|
||||||
|
/// deno -V # deno 1.6.0
|
||||||
|
/// ```
|
||||||
|
fn version(&self) -> Result<Version> {
|
||||||
|
let version_str = Command::new(&self.command)
|
||||||
|
.args(["-V"])
|
||||||
|
.output_checked_utf8()
|
||||||
|
.map(|s| s.stdout.trim().to_owned().split_off(5)); // remove "deno " prefix
|
||||||
|
Version::parse(&version_str?).map_err(std::convert::Into::into)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
fn should_use_sudo(npm: &NPM, ctx: &ExecutionContext) -> Result<bool> {
|
fn should_use_sudo(npm: &NPM, ctx: &ExecutionContext) -> Result<bool> {
|
||||||
if npm.should_use_sudo()? {
|
if npm.should_use_sudo()? {
|
||||||
@@ -266,16 +335,16 @@ pub fn run_yarn_upgrade(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn deno_upgrade(ctx: &ExecutionContext) -> Result<()> {
|
pub fn deno_upgrade(ctx: &ExecutionContext) -> Result<()> {
|
||||||
let deno = require("deno")?;
|
let deno = require("deno").map(Deno::new)?;
|
||||||
let deno_dir = HOME_DIR.join(".deno");
|
let deno_dir = HOME_DIR.join(".deno");
|
||||||
|
|
||||||
if !deno.canonicalize()?.is_descendant_of(&deno_dir) {
|
if !deno.command.canonicalize()?.is_descendant_of(&deno_dir) {
|
||||||
let skip_reason = SkipStep(t!("Deno installed outside of .deno directory").to_string());
|
let skip_reason = SkipStep(t!("Deno installed outside of .deno directory").to_string());
|
||||||
return Err(skip_reason.into());
|
return Err(skip_reason.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
print_separator("Deno");
|
print_separator("Deno");
|
||||||
ctx.run_type().execute(&deno).arg("upgrade").status_checked()
|
deno.upgrade(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// There is no `volta upgrade` command, so we need to upgrade each package
|
/// There is no `volta upgrade` command, so we need to upgrade each package
|
||||||
@@ -290,7 +359,6 @@ pub fn run_volta_packages_upgrade(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let list_output = ctx
|
let list_output = ctx
|
||||||
.run_type()
|
|
||||||
.execute(&volta)
|
.execute(&volta)
|
||||||
.args(["list", "--format=plain"])
|
.args(["list", "--format=plain"])
|
||||||
.output_checked_utf8()?
|
.output_checked_utf8()?
|
||||||
@@ -313,11 +381,8 @@ pub fn run_volta_packages_upgrade(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
for package in installed_packages.iter() {
|
for package in &installed_packages {
|
||||||
ctx.run_type()
|
ctx.execute(&volta).args(["install", package]).status_checked()?;
|
||||||
.execute(&volta)
|
|
||||||
.args(["install", package])
|
|
||||||
.status_checked()?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
use crate::command::CommandExt;
|
use crate::command::CommandExt;
|
||||||
use crate::execution_context::ExecutionContext;
|
use crate::execution_context::ExecutionContext;
|
||||||
|
use crate::step::Step;
|
||||||
use crate::terminal::print_separator;
|
use crate::terminal::print_separator;
|
||||||
use crate::utils::require;
|
use crate::utils::require;
|
||||||
use crate::utils::which;
|
use crate::utils::which;
|
||||||
use crate::Step;
|
use color_eyre::Result;
|
||||||
use color_eyre::eyre::Result;
|
|
||||||
|
|
||||||
pub fn upgrade_packages(ctx: &ExecutionContext) -> Result<()> {
|
pub fn upgrade_packages(ctx: &ExecutionContext) -> Result<()> {
|
||||||
//let pkg = require("pkg")?;
|
//let pkg = require("pkg")?;
|
||||||
@@ -14,7 +14,7 @@ pub fn upgrade_packages(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
|
|
||||||
let is_nala = pkg.ends_with("nala");
|
let is_nala = pkg.ends_with("nala");
|
||||||
|
|
||||||
let mut command = ctx.run_type().execute(&pkg);
|
let mut command = ctx.execute(&pkg);
|
||||||
command.arg("upgrade");
|
command.arg("upgrade");
|
||||||
|
|
||||||
if ctx.config().yes(Step::System) {
|
if ctx.config().yes(Step::System) {
|
||||||
@@ -23,10 +23,10 @@ pub fn upgrade_packages(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
command.status_checked()?;
|
command.status_checked()?;
|
||||||
|
|
||||||
if !is_nala && ctx.config().cleanup() {
|
if !is_nala && ctx.config().cleanup() {
|
||||||
ctx.run_type().execute(&pkg).arg("clean").status_checked()?;
|
ctx.execute(&pkg).arg("clean").status_checked()?;
|
||||||
|
|
||||||
let apt = require("apt")?;
|
let apt = require("apt")?;
|
||||||
let mut command = ctx.run_type().execute(apt);
|
let mut command = ctx.execute(apt);
|
||||||
command.arg("autoremove");
|
command.arg("autoremove");
|
||||||
if ctx.config().yes(Step::System) {
|
if ctx.config().yes(Step::System) {
|
||||||
command.arg("-y");
|
command.arg("-y");
|
||||||
|
|||||||
@@ -3,16 +3,16 @@ use std::ffi::OsString;
|
|||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
use color_eyre::eyre;
|
use color_eyre::eyre;
|
||||||
use color_eyre::eyre::Result;
|
use color_eyre::eyre::{Context, Result};
|
||||||
use rust_i18n::t;
|
use rust_i18n::t;
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
use crate::command::CommandExt;
|
use crate::command::CommandExt;
|
||||||
use crate::error::TopgradeError;
|
use crate::error::TopgradeError;
|
||||||
use crate::execution_context::ExecutionContext;
|
use crate::execution_context::ExecutionContext;
|
||||||
use crate::utils::require_option;
|
use crate::step::Step;
|
||||||
use crate::utils::which;
|
use crate::utils::which;
|
||||||
use crate::{config, Step};
|
use crate::{config, output_changed_message};
|
||||||
|
|
||||||
fn get_execution_path() -> OsString {
|
fn get_execution_path() -> OsString {
|
||||||
let mut path = OsString::from("/usr/bin:");
|
let mut path = OsString::from("/usr/bin:");
|
||||||
@@ -32,13 +32,12 @@ pub struct YayParu {
|
|||||||
impl ArchPackageManager for YayParu {
|
impl ArchPackageManager for YayParu {
|
||||||
fn upgrade(&self, ctx: &ExecutionContext) -> Result<()> {
|
fn upgrade(&self, ctx: &ExecutionContext) -> Result<()> {
|
||||||
if ctx.config().show_arch_news() {
|
if ctx.config().show_arch_news() {
|
||||||
ctx.run_type()
|
ctx.execute(&self.executable)
|
||||||
.execute(&self.executable)
|
|
||||||
.arg("-Pw")
|
.arg("-Pw")
|
||||||
.status_checked_with_codes(&[1, 0])?;
|
.status_checked_with_codes(&[1, 0])?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut command = ctx.run_type().execute(&self.executable);
|
let mut command = ctx.execute(&self.executable);
|
||||||
|
|
||||||
command
|
command
|
||||||
.arg("--pacman")
|
.arg("--pacman")
|
||||||
@@ -53,7 +52,7 @@ impl ArchPackageManager for YayParu {
|
|||||||
command.status_checked()?;
|
command.status_checked()?;
|
||||||
|
|
||||||
if ctx.config().cleanup() {
|
if ctx.config().cleanup() {
|
||||||
let mut command = ctx.run_type().execute(&self.executable);
|
let mut command = ctx.execute(&self.executable);
|
||||||
command.arg("--pacman").arg(&self.pacman).arg("-Scc");
|
command.arg("--pacman").arg(&self.pacman).arg("-Scc");
|
||||||
if ctx.config().yes(Step::System) {
|
if ctx.config().yes(Step::System) {
|
||||||
command.arg("--noconfirm");
|
command.arg("--noconfirm");
|
||||||
@@ -80,7 +79,7 @@ pub struct GarudaUpdate {
|
|||||||
|
|
||||||
impl ArchPackageManager for GarudaUpdate {
|
impl ArchPackageManager for GarudaUpdate {
|
||||||
fn upgrade(&self, ctx: &ExecutionContext) -> Result<()> {
|
fn upgrade(&self, ctx: &ExecutionContext) -> Result<()> {
|
||||||
let mut command = ctx.run_type().execute(&self.executable);
|
let mut command = ctx.execute(&self.executable);
|
||||||
|
|
||||||
command
|
command
|
||||||
.env("PATH", get_execution_path())
|
.env("PATH", get_execution_path())
|
||||||
@@ -111,7 +110,7 @@ pub struct Trizen {
|
|||||||
|
|
||||||
impl ArchPackageManager for Trizen {
|
impl ArchPackageManager for Trizen {
|
||||||
fn upgrade(&self, ctx: &ExecutionContext) -> Result<()> {
|
fn upgrade(&self, ctx: &ExecutionContext) -> Result<()> {
|
||||||
let mut command = ctx.run_type().execute(&self.executable);
|
let mut command = ctx.execute(&self.executable);
|
||||||
|
|
||||||
command
|
command
|
||||||
.arg("-Syu")
|
.arg("-Syu")
|
||||||
@@ -124,7 +123,7 @@ impl ArchPackageManager for Trizen {
|
|||||||
command.status_checked()?;
|
command.status_checked()?;
|
||||||
|
|
||||||
if ctx.config().cleanup() {
|
if ctx.config().cleanup() {
|
||||||
let mut command = ctx.run_type().execute(&self.executable);
|
let mut command = ctx.execute(&self.executable);
|
||||||
command.arg("-Sc");
|
command.arg("-Sc");
|
||||||
if ctx.config().yes(Step::System) {
|
if ctx.config().yes(Step::System) {
|
||||||
command.arg("--noconfirm");
|
command.arg("--noconfirm");
|
||||||
@@ -150,20 +149,17 @@ pub struct Pacman {
|
|||||||
|
|
||||||
impl ArchPackageManager for Pacman {
|
impl ArchPackageManager for Pacman {
|
||||||
fn upgrade(&self, ctx: &ExecutionContext) -> Result<()> {
|
fn upgrade(&self, ctx: &ExecutionContext) -> Result<()> {
|
||||||
let sudo = require_option(ctx.sudo().as_ref(), "sudo is required to run pacman".into())?;
|
let sudo = ctx.require_sudo()?;
|
||||||
let mut command = ctx.run_type().execute(sudo);
|
let mut command = sudo.execute(ctx, &self.executable)?;
|
||||||
command
|
command.arg("-Syu").env("PATH", get_execution_path());
|
||||||
.arg(&self.executable)
|
|
||||||
.arg("-Syu")
|
|
||||||
.env("PATH", get_execution_path());
|
|
||||||
if ctx.config().yes(Step::System) {
|
if ctx.config().yes(Step::System) {
|
||||||
command.arg("--noconfirm");
|
command.arg("--noconfirm");
|
||||||
}
|
}
|
||||||
command.status_checked()?;
|
command.status_checked()?;
|
||||||
|
|
||||||
if ctx.config().cleanup() {
|
if ctx.config().cleanup() {
|
||||||
let mut command = ctx.run_type().execute(sudo);
|
let mut command = sudo.execute(ctx, &self.executable)?;
|
||||||
command.arg(&self.executable).arg("-Scc");
|
command.arg("-Scc");
|
||||||
if ctx.config().yes(Step::System) {
|
if ctx.config().yes(Step::System) {
|
||||||
command.arg("--noconfirm");
|
command.arg("--noconfirm");
|
||||||
}
|
}
|
||||||
@@ -196,7 +192,7 @@ impl Pikaur {
|
|||||||
|
|
||||||
impl ArchPackageManager for Pikaur {
|
impl ArchPackageManager for Pikaur {
|
||||||
fn upgrade(&self, ctx: &ExecutionContext) -> Result<()> {
|
fn upgrade(&self, ctx: &ExecutionContext) -> Result<()> {
|
||||||
let mut command = ctx.run_type().execute(&self.executable);
|
let mut command = ctx.execute(&self.executable);
|
||||||
|
|
||||||
command
|
command
|
||||||
.arg("-Syu")
|
.arg("-Syu")
|
||||||
@@ -210,7 +206,7 @@ impl ArchPackageManager for Pikaur {
|
|||||||
command.status_checked()?;
|
command.status_checked()?;
|
||||||
|
|
||||||
if ctx.config().cleanup() {
|
if ctx.config().cleanup() {
|
||||||
let mut command = ctx.run_type().execute(&self.executable);
|
let mut command = ctx.execute(&self.executable);
|
||||||
command.arg("-Sc");
|
command.arg("-Sc");
|
||||||
if ctx.config().yes(Step::System) {
|
if ctx.config().yes(Step::System) {
|
||||||
command.arg("--noconfirm");
|
command.arg("--noconfirm");
|
||||||
@@ -235,7 +231,7 @@ impl Pamac {
|
|||||||
}
|
}
|
||||||
impl ArchPackageManager for Pamac {
|
impl ArchPackageManager for Pamac {
|
||||||
fn upgrade(&self, ctx: &ExecutionContext) -> Result<()> {
|
fn upgrade(&self, ctx: &ExecutionContext) -> Result<()> {
|
||||||
let mut command = ctx.run_type().execute(&self.executable);
|
let mut command = ctx.execute(&self.executable);
|
||||||
|
|
||||||
command
|
command
|
||||||
.arg("upgrade")
|
.arg("upgrade")
|
||||||
@@ -249,7 +245,7 @@ impl ArchPackageManager for Pamac {
|
|||||||
command.status_checked()?;
|
command.status_checked()?;
|
||||||
|
|
||||||
if ctx.config().cleanup() {
|
if ctx.config().cleanup() {
|
||||||
let mut command = ctx.run_type().execute(&self.executable);
|
let mut command = ctx.execute(&self.executable);
|
||||||
command.arg("clean");
|
command.arg("clean");
|
||||||
if ctx.config().yes(Step::System) {
|
if ctx.config().yes(Step::System) {
|
||||||
command.arg("--no-confirm");
|
command.arg("--no-confirm");
|
||||||
@@ -277,15 +273,12 @@ impl ArchPackageManager for Aura {
|
|||||||
fn upgrade(&self, ctx: &ExecutionContext) -> Result<()> {
|
fn upgrade(&self, ctx: &ExecutionContext) -> Result<()> {
|
||||||
use semver::Version;
|
use semver::Version;
|
||||||
|
|
||||||
let version_cmd_output = ctx
|
let version_cmd_output = ctx.execute(&self.executable).arg("--version").output_checked_utf8()?;
|
||||||
.run_type()
|
|
||||||
.execute(&self.executable)
|
|
||||||
.arg("--version")
|
|
||||||
.output_checked_utf8()?;
|
|
||||||
// Output will be something like: "aura x.x.x\n"
|
// Output will be something like: "aura x.x.x\n"
|
||||||
let version_cmd_stdout = version_cmd_output.stdout;
|
let version_cmd_stdout = version_cmd_output.stdout;
|
||||||
let version_str = version_cmd_stdout.trim_start_matches("aura ").trim_end();
|
let version_str = version_cmd_stdout.trim_start_matches("aura ").trim_end();
|
||||||
let version = Version::parse(version_str).expect("invalid version");
|
let version = Version::parse(version_str)
|
||||||
|
.wrap_err_with(|| output_changed_message!("aura --version", "invalid version"))?;
|
||||||
|
|
||||||
// Aura, since version 4.0.6, no longer needs sudo.
|
// Aura, since version 4.0.6, no longer needs sudo.
|
||||||
//
|
//
|
||||||
@@ -293,7 +286,7 @@ impl ArchPackageManager for Aura {
|
|||||||
let version_no_sudo = Version::new(4, 0, 6);
|
let version_no_sudo = Version::new(4, 0, 6);
|
||||||
|
|
||||||
if version >= version_no_sudo {
|
if version >= version_no_sudo {
|
||||||
let mut cmd = ctx.run_type().execute(&self.executable);
|
let mut cmd = ctx.execute(&self.executable);
|
||||||
cmd.arg("-Au")
|
cmd.arg("-Au")
|
||||||
.args(ctx.config().aura_aur_arguments().split_whitespace());
|
.args(ctx.config().aura_aur_arguments().split_whitespace());
|
||||||
if ctx.config().yes(Step::System) {
|
if ctx.config().yes(Step::System) {
|
||||||
@@ -301,7 +294,7 @@ impl ArchPackageManager for Aura {
|
|||||||
}
|
}
|
||||||
cmd.status_checked()?;
|
cmd.status_checked()?;
|
||||||
|
|
||||||
let mut cmd = ctx.run_type().execute(&self.executable);
|
let mut cmd = ctx.execute(&self.executable);
|
||||||
cmd.arg("-Syu")
|
cmd.arg("-Syu")
|
||||||
.args(ctx.config().aura_pacman_arguments().split_whitespace());
|
.args(ctx.config().aura_pacman_arguments().split_whitespace());
|
||||||
if ctx.config().yes(Step::System) {
|
if ctx.config().yes(Step::System) {
|
||||||
@@ -309,23 +302,18 @@ impl ArchPackageManager for Aura {
|
|||||||
}
|
}
|
||||||
cmd.status_checked()?;
|
cmd.status_checked()?;
|
||||||
} else {
|
} else {
|
||||||
let sudo = crate::utils::require_option(
|
let sudo = ctx.require_sudo()?;
|
||||||
ctx.sudo().as_ref(),
|
|
||||||
t!("Aura(<0.4.6) requires sudo installed to work with AUR packages").to_string(),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let mut cmd = ctx.run_type().execute(sudo);
|
let mut cmd = sudo.execute(ctx, &self.executable)?;
|
||||||
cmd.arg(&self.executable)
|
cmd.arg("-Au")
|
||||||
.arg("-Au")
|
|
||||||
.args(ctx.config().aura_aur_arguments().split_whitespace());
|
.args(ctx.config().aura_aur_arguments().split_whitespace());
|
||||||
if ctx.config().yes(Step::System) {
|
if ctx.config().yes(Step::System) {
|
||||||
cmd.arg("--noconfirm");
|
cmd.arg("--noconfirm");
|
||||||
}
|
}
|
||||||
cmd.status_checked()?;
|
cmd.status_checked()?;
|
||||||
|
|
||||||
let mut cmd = ctx.run_type().execute(sudo);
|
let mut cmd = sudo.execute(ctx, &self.executable)?;
|
||||||
cmd.arg(&self.executable)
|
cmd.arg("-Syu")
|
||||||
.arg("-Syu")
|
|
||||||
.args(ctx.config().aura_pacman_arguments().split_whitespace());
|
.args(ctx.config().aura_pacman_arguments().split_whitespace());
|
||||||
if ctx.config().yes(Step::System) {
|
if ctx.config().yes(Step::System) {
|
||||||
cmd.arg("--noconfirm");
|
cmd.arg("--noconfirm");
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
use crate::command::CommandExt;
|
use crate::command::CommandExt;
|
||||||
use crate::execution_context::ExecutionContext;
|
use crate::execution_context::ExecutionContext;
|
||||||
|
use crate::step::Step;
|
||||||
use crate::terminal::print_separator;
|
use crate::terminal::print_separator;
|
||||||
use crate::utils::{get_require_sudo_string, require_option};
|
|
||||||
use crate::Step;
|
|
||||||
use color_eyre::eyre::Result;
|
use color_eyre::eyre::Result;
|
||||||
use std::process::Command;
|
use rust_i18n::t;
|
||||||
|
|
||||||
pub fn upgrade_packages(ctx: &ExecutionContext) -> Result<()> {
|
pub fn upgrade_packages(ctx: &ExecutionContext) -> Result<()> {
|
||||||
let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
|
|
||||||
print_separator(t!("DragonFly BSD Packages"));
|
print_separator(t!("DragonFly BSD Packages"));
|
||||||
let mut cmd = ctx.run_type().execute(sudo);
|
|
||||||
cmd.args(["/usr/local/sbin/pkg", "upgrade"]);
|
let sudo = ctx.require_sudo()?;
|
||||||
|
let mut cmd = sudo.execute(ctx, "/usr/local/sbin/pkg")?;
|
||||||
|
cmd.arg("upgrade");
|
||||||
if ctx.config().yes(Step::System) {
|
if ctx.config().yes(Step::System) {
|
||||||
cmd.arg("-y");
|
cmd.arg("-y");
|
||||||
}
|
}
|
||||||
@@ -18,19 +18,18 @@ 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(), get_require_sudo_string())?;
|
|
||||||
|
|
||||||
print_separator(t!("DragonFly BSD Audit"));
|
print_separator(t!("DragonFly BSD Audit"));
|
||||||
|
|
||||||
#[allow(clippy::disallowed_methods)]
|
let sudo = ctx.require_sudo()?;
|
||||||
if !Command::new(sudo)
|
sudo.execute(ctx, "/usr/local/sbin/pkg")?
|
||||||
.args(["/usr/local/sbin/pkg", "audit", "-Fr"])
|
.args(["audit", "-Fr"])
|
||||||
.status()?
|
.status_checked_with(|status| {
|
||||||
.success()
|
if !status.success() {
|
||||||
{
|
println!(
|
||||||
println!(t!(
|
"{}",
|
||||||
"The package audit was successful, but vulnerable packages still remain on the system"
|
t!("The package audit was successful, but vulnerable packages still remain on the system")
|
||||||
));
|
);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,25 @@
|
|||||||
use crate::command::CommandExt;
|
use crate::command::CommandExt;
|
||||||
use crate::execution_context::ExecutionContext;
|
use crate::execution_context::ExecutionContext;
|
||||||
|
use crate::step::Step;
|
||||||
use crate::terminal::print_separator;
|
use crate::terminal::print_separator;
|
||||||
use crate::utils::{get_require_sudo_string, require_option};
|
use color_eyre::Result;
|
||||||
use crate::Step;
|
|
||||||
use color_eyre::eyre::Result;
|
|
||||||
use rust_i18n::t;
|
use rust_i18n::t;
|
||||||
use std::process::Command;
|
|
||||||
|
|
||||||
pub fn upgrade_freebsd(ctx: &ExecutionContext) -> Result<()> {
|
pub fn upgrade_freebsd(ctx: &ExecutionContext) -> Result<()> {
|
||||||
let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
|
|
||||||
print_separator(t!("FreeBSD Update"));
|
print_separator(t!("FreeBSD Update"));
|
||||||
ctx.run_type()
|
|
||||||
.execute(sudo)
|
let sudo = ctx.require_sudo()?;
|
||||||
.args(["/usr/sbin/freebsd-update", "fetch", "install"])
|
sudo.execute(ctx, "/usr/sbin/freebsd-update")?
|
||||||
|
.args(["fetch", "install"])
|
||||||
.status_checked()
|
.status_checked()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn upgrade_packages(ctx: &ExecutionContext) -> Result<()> {
|
pub fn upgrade_packages(ctx: &ExecutionContext) -> Result<()> {
|
||||||
let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
|
|
||||||
print_separator(t!("FreeBSD Packages"));
|
print_separator(t!("FreeBSD Packages"));
|
||||||
|
|
||||||
let mut command = ctx.run_type().execute(sudo);
|
let sudo = ctx.require_sudo()?;
|
||||||
|
let mut command = sudo.execute(ctx, "/usr/sbin/pkg")?;
|
||||||
command.args(["/usr/sbin/pkg", "upgrade"]);
|
command.arg("upgrade");
|
||||||
if ctx.config().yes(Step::System) {
|
if ctx.config().yes(Step::System) {
|
||||||
command.arg("-y");
|
command.arg("-y");
|
||||||
}
|
}
|
||||||
@@ -30,12 +27,10 @@ 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(), get_require_sudo_string())?;
|
|
||||||
|
|
||||||
print_separator(t!("FreeBSD Audit"));
|
print_separator(t!("FreeBSD Audit"));
|
||||||
|
|
||||||
Command::new(sudo)
|
let sudo = ctx.require_sudo()?;
|
||||||
.args(["/usr/sbin/pkg", "audit", "-Fr"])
|
sudo.execute(ctx, "/usr/sbin/pkg")?
|
||||||
.status_checked()?;
|
.args(["audit", "-Fr"])
|
||||||
Ok(())
|
.status_checked()
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,8 @@
|
|||||||
use crate::command::CommandExt;
|
use crate::command::CommandExt;
|
||||||
use crate::execution_context::ExecutionContext;
|
use crate::execution_context::ExecutionContext;
|
||||||
|
use crate::step::Step;
|
||||||
use crate::terminal::{print_separator, prompt_yesno};
|
use crate::terminal::{print_separator, prompt_yesno};
|
||||||
use crate::utils::{get_require_sudo_string, require_option};
|
use crate::utils::require;
|
||||||
use crate::{utils::require, Step};
|
|
||||||
use color_eyre::eyre::Result;
|
use color_eyre::eyre::Result;
|
||||||
use rust_i18n::t;
|
use rust_i18n::t;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
@@ -11,23 +11,18 @@ use std::process::Command;
|
|||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
pub fn run_macports(ctx: &ExecutionContext) -> Result<()> {
|
pub fn run_macports(ctx: &ExecutionContext) -> Result<()> {
|
||||||
require("port")?;
|
let port = require("port")?;
|
||||||
let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
|
|
||||||
|
|
||||||
print_separator("MacPorts");
|
print_separator("MacPorts");
|
||||||
ctx.run_type()
|
|
||||||
.execute(sudo)
|
let sudo = ctx.require_sudo()?;
|
||||||
.args(["port", "selfupdate"])
|
|
||||||
.status_checked()?;
|
sudo.execute(ctx, &port)?.arg("selfupdate").status_checked()?;
|
||||||
ctx.run_type()
|
sudo.execute(ctx, &port)?
|
||||||
.execute(sudo)
|
.args(["-u", "upgrade", "outdated"])
|
||||||
.args(["port", "-u", "upgrade", "outdated"])
|
|
||||||
.status_checked()?;
|
.status_checked()?;
|
||||||
if ctx.config().cleanup() {
|
if ctx.config().cleanup() {
|
||||||
ctx.run_type()
|
sudo.execute(ctx, &port)?.args(["-N", "reclaim"]).status_checked()?;
|
||||||
.execute(sudo)
|
|
||||||
.args(["port", "-N", "reclaim"])
|
|
||||||
.status_checked()?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -37,13 +32,13 @@ pub fn run_mas(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
let mas = require("mas")?;
|
let mas = require("mas")?;
|
||||||
print_separator(t!("macOS App Store"));
|
print_separator(t!("macOS App Store"));
|
||||||
|
|
||||||
ctx.run_type().execute(mas).arg("upgrade").status_checked()
|
ctx.execute(mas).arg("upgrade").status_checked()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn upgrade_macos(ctx: &ExecutionContext) -> Result<()> {
|
pub fn upgrade_macos(ctx: &ExecutionContext) -> Result<()> {
|
||||||
print_separator(t!("macOS system update"));
|
print_separator(t!("macOS system update"));
|
||||||
|
|
||||||
let should_ask = !(ctx.config().yes(Step::System) || ctx.config().dry_run());
|
let should_ask = !(ctx.config().yes(Step::System) || ctx.run_type().dry());
|
||||||
if should_ask {
|
if should_ask {
|
||||||
println!("{}", t!("Finding available software"));
|
println!("{}", t!("Finding available software"));
|
||||||
if system_update_available()? {
|
if system_update_available()? {
|
||||||
@@ -58,7 +53,7 @@ pub fn upgrade_macos(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut command = ctx.run_type().execute("softwareupdate");
|
let mut command = ctx.execute("softwareupdate");
|
||||||
command.args(["--install", "--all"]);
|
command.args(["--install", "--all"]);
|
||||||
|
|
||||||
if should_ask {
|
if should_ask {
|
||||||
@@ -87,7 +82,7 @@ pub fn run_sparkle(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
.arg(application.path())
|
.arg(application.path())
|
||||||
.output_checked_utf8();
|
.output_checked_utf8();
|
||||||
if probe.is_ok() {
|
if probe.is_ok() {
|
||||||
let mut command = ctx.run_type().execute(&sparkle);
|
let mut command = ctx.execute(&sparkle);
|
||||||
command.args(["bundle", "--check-immediately", "--application"]);
|
command.args(["bundle", "--check-immediately", "--application"]);
|
||||||
command.arg(application.path());
|
command.arg(application.path());
|
||||||
command.status_checked()?;
|
command.status_checked()?;
|
||||||
@@ -100,14 +95,9 @@ pub fn update_xcodes(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
let xcodes = require("xcodes")?;
|
let xcodes = require("xcodes")?;
|
||||||
print_separator("Xcodes");
|
print_separator("Xcodes");
|
||||||
|
|
||||||
let should_ask = !(ctx.config().yes(Step::Xcodes) || ctx.config().dry_run());
|
let should_ask = !(ctx.config().yes(Step::Xcodes) || ctx.run_type().dry());
|
||||||
|
|
||||||
let releases = ctx
|
let releases = ctx.execute(&xcodes).args(["update"]).output_checked_utf8()?.stdout;
|
||||||
.run_type()
|
|
||||||
.execute(&xcodes)
|
|
||||||
.args(["update"])
|
|
||||||
.output_checked_utf8()?
|
|
||||||
.stdout;
|
|
||||||
|
|
||||||
let releases_installed: Vec<String> = releases
|
let releases_installed: Vec<String> = releases
|
||||||
.lines()
|
.lines()
|
||||||
@@ -164,12 +154,7 @@ pub fn update_xcodes(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
process_xcodes_releases(releases_regular, should_ask, ctx)?;
|
process_xcodes_releases(releases_regular, should_ask, ctx)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let releases_new = ctx
|
let releases_new = ctx.execute(&xcodes).args(["list"]).output_checked_utf8()?.stdout;
|
||||||
.run_type()
|
|
||||||
.execute(&xcodes)
|
|
||||||
.args(["list"])
|
|
||||||
.output_checked_utf8()?
|
|
||||||
.stdout;
|
|
||||||
|
|
||||||
let releases_gm_new_installed: HashSet<_> = releases_new
|
let releases_gm_new_installed: HashSet<_> = releases_new
|
||||||
.lines()
|
.lines()
|
||||||
@@ -199,11 +184,10 @@ pub fn update_xcodes(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
prompt_yesno(t!("Would you like to move the former Xcode release to the trash?").as_ref())?;
|
prompt_yesno(t!("Would you like to move the former Xcode release to the trash?").as_ref())?;
|
||||||
if answer_uninstall {
|
if answer_uninstall {
|
||||||
let _ = ctx
|
let _ = ctx
|
||||||
.run_type()
|
|
||||||
.execute(&xcodes)
|
.execute(&xcodes)
|
||||||
.args([
|
.args([
|
||||||
"uninstall",
|
"uninstall",
|
||||||
releases_new_installed.iter().next().cloned().unwrap_or_default(),
|
releases_new_installed.iter().next().copied().unwrap_or_default(),
|
||||||
])
|
])
|
||||||
.status_checked();
|
.status_checked();
|
||||||
}
|
}
|
||||||
@@ -216,12 +200,7 @@ pub fn update_xcodes(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
pub fn process_xcodes_releases(releases_filtered: Vec<String>, should_ask: bool, ctx: &ExecutionContext) -> Result<()> {
|
pub fn process_xcodes_releases(releases_filtered: Vec<String>, should_ask: bool, ctx: &ExecutionContext) -> Result<()> {
|
||||||
let xcodes = require("xcodes")?;
|
let xcodes = require("xcodes")?;
|
||||||
|
|
||||||
if releases_filtered
|
if releases_filtered.last().map_or(true, |s| !s.contains("(Installed)")) && !releases_filtered.is_empty() {
|
||||||
.last()
|
|
||||||
.map(|s| !s.contains("(Installed)"))
|
|
||||||
.unwrap_or(true)
|
|
||||||
&& !releases_filtered.is_empty()
|
|
||||||
{
|
|
||||||
println!(
|
println!(
|
||||||
"{} {}",
|
"{} {}",
|
||||||
t!("New Xcode release detected:"),
|
t!("New Xcode release detected:"),
|
||||||
@@ -231,7 +210,6 @@ pub fn process_xcodes_releases(releases_filtered: Vec<String>, should_ask: bool,
|
|||||||
let answer_install = prompt_yesno(t!("Would you like to install it?").as_ref())?;
|
let answer_install = prompt_yesno(t!("Would you like to install it?").as_ref())?;
|
||||||
if answer_install {
|
if answer_install {
|
||||||
let _ = ctx
|
let _ = ctx
|
||||||
.run_type()
|
|
||||||
.execute(xcodes)
|
.execute(xcodes)
|
||||||
.args(["install", &releases_filtered.last().cloned().unwrap_or_default()])
|
.args(["install", &releases_filtered.last().cloned().unwrap_or_default()])
|
||||||
.status_checked();
|
.status_checked();
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ pub mod macos;
|
|||||||
pub mod openbsd;
|
pub mod openbsd;
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
pub mod unix;
|
pub mod unix;
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(windows)]
|
||||||
pub mod windows;
|
pub mod windows;
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
|
|||||||
@@ -1,33 +1,49 @@
|
|||||||
use crate::command::CommandExt;
|
use crate::command::CommandExt;
|
||||||
use crate::execution_context::ExecutionContext;
|
use crate::execution_context::ExecutionContext;
|
||||||
use crate::terminal::print_separator;
|
use crate::terminal::print_separator;
|
||||||
use crate::utils::{get_require_sudo_string, require_option};
|
|
||||||
use color_eyre::eyre::Result;
|
use color_eyre::eyre::Result;
|
||||||
|
use rust_i18n::t;
|
||||||
|
use std::fs;
|
||||||
|
use tracing::debug;
|
||||||
|
|
||||||
|
fn is_openbsd_current() -> Result<bool> {
|
||||||
|
let motd_content = fs::read_to_string("/etc/motd")?;
|
||||||
|
let is_current = ["-current", "-beta"].iter().any(|&s| motd_content.contains(s));
|
||||||
|
|
||||||
|
debug!("OpenBSD is -current/-beta: {is_current}");
|
||||||
|
|
||||||
|
Ok(is_current)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn upgrade_openbsd(ctx: &ExecutionContext) -> Result<()> {
|
pub fn upgrade_openbsd(ctx: &ExecutionContext) -> Result<()> {
|
||||||
let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
|
|
||||||
print_separator(t!("OpenBSD Update"));
|
print_separator(t!("OpenBSD Update"));
|
||||||
ctx.run_type()
|
|
||||||
.execute(sudo)
|
let sudo = ctx.require_sudo()?;
|
||||||
.args(["/usr/sbin/sysupgrade", "-n"])
|
|
||||||
.status_checked()
|
let is_current = is_openbsd_current()?;
|
||||||
|
|
||||||
|
if is_current {
|
||||||
|
sudo.execute(ctx, "/usr/sbin/sysupgrade")?.arg("-sn").status_checked()
|
||||||
|
} else {
|
||||||
|
sudo.execute(ctx, "/usr/sbin/syspatch")?.status_checked()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn upgrade_packages(ctx: &ExecutionContext) -> Result<()> {
|
pub fn upgrade_packages(ctx: &ExecutionContext) -> Result<()> {
|
||||||
let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
|
|
||||||
print_separator(t!("OpenBSD Packages"));
|
print_separator(t!("OpenBSD Packages"));
|
||||||
|
|
||||||
|
let sudo = ctx.require_sudo()?;
|
||||||
|
|
||||||
|
let is_current = is_openbsd_current()?;
|
||||||
|
|
||||||
if ctx.config().cleanup() {
|
if ctx.config().cleanup() {
|
||||||
ctx.run_type()
|
sudo.execute(ctx, "/usr/sbin/pkg_delete")?.arg("-ac").status_checked()?;
|
||||||
.execute(sudo)
|
|
||||||
.args(["/usr/sbin/pkg_delete", "-ac"])
|
|
||||||
.status_checked()?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.run_type()
|
let mut command = sudo.execute(ctx, "/usr/sbin/pkg_add")?;
|
||||||
.execute(sudo)
|
command.arg("-u");
|
||||||
.args(["/usr/sbin/pkg_add", "-u"])
|
if is_current {
|
||||||
.status_checked()?;
|
command.arg("-Dsnap");
|
||||||
|
}
|
||||||
Ok(())
|
command.status_checked()
|
||||||
}
|
}
|
||||||
|
|||||||
10
src/steps/os/os_release/aosc
Normal file
10
src/steps/os/os_release/aosc
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
PRETTY_NAME="AOSC OS (12.2.2)"
|
||||||
|
NAME="AOSC OS"
|
||||||
|
VERSION_ID="12.2.2"
|
||||||
|
VERSION="12.2.2 (localhost)"
|
||||||
|
BUILD_ID="20250916"
|
||||||
|
ID=aosc
|
||||||
|
ANSI_COLOR="1;36"
|
||||||
|
HOME_URL="https://aosc.io/"
|
||||||
|
SUPPORT_URL="https://github.com/AOSC-Dev/aosc-os-abbs"
|
||||||
|
BUG_REPORT_URL="https://github.com/AOSC-Dev/aosc-os-abbs/issues"
|
||||||
23
src/steps/os/os_release/aurora
Normal file
23
src/steps/os/os_release/aurora
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
NAME="Aurora"
|
||||||
|
VERSION="latest-41.20250210.4 (Kinoite)"
|
||||||
|
RELEASE_TYPE=stable
|
||||||
|
ID=aurora
|
||||||
|
ID_LIKE="fedora"
|
||||||
|
VERSION_ID=41
|
||||||
|
VERSION_CODENAME=""
|
||||||
|
PLATFORM_ID="platform:f41"
|
||||||
|
PRETTY_NAME="Aurora (Version: latest-41.20250210.4 / FROM Fedora Kinoite 41)"
|
||||||
|
ANSI_COLOR="0;38;2;60;110;180"
|
||||||
|
LOGO=fedora-logo-icon
|
||||||
|
CPE_NAME="cpe:/o:universal-blue:aurora:41"
|
||||||
|
DEFAULT_HOSTNAME="aurora"
|
||||||
|
HOME_URL="https://getaurora.dev/"
|
||||||
|
DOCUMENTATION_URL="https://docs.getaurora.dev"
|
||||||
|
SUPPORT_URL="https://github.com/ublue-os/aurora/issues/"
|
||||||
|
BUG_REPORT_URL="https://github.com/ublue-os/aurora/issues/"
|
||||||
|
SUPPORT_END=2025-12-15
|
||||||
|
VARIANT="Kinoite"
|
||||||
|
VARIANT_ID=aurora
|
||||||
|
OSTREE_VERSION='latest-41.20250210.4'
|
||||||
|
BUILD_ID="fc1570c"
|
||||||
|
IMAGE_ID="aurora"
|
||||||
25
src/steps/os/os_release/bazzite
Normal file
25
src/steps/os/os_release/bazzite
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
NAME="Bazzite"
|
||||||
|
VERSION="41.20250208.0 (Kinoite)"
|
||||||
|
RELEASE_TYPE=stable
|
||||||
|
ID=bazzite
|
||||||
|
ID_LIKE="fedora"
|
||||||
|
VERSION_ID=41
|
||||||
|
VERSION_CODENAME="Holographic"
|
||||||
|
PLATFORM_ID="platform:f41"
|
||||||
|
PRETTY_NAME="Bazzite 41 (FROM Fedora Kinoite)"
|
||||||
|
ANSI_COLOR="0;38;2;138;43;226"
|
||||||
|
LOGO=bazzite-logo-icon
|
||||||
|
CPE_NAME="cpe:/o:universal-blue:bazzite:41"
|
||||||
|
DEFAULT_HOSTNAME="bazzite"
|
||||||
|
HOME_URL="https://bazzite.gg"
|
||||||
|
DOCUMENTATION_URL="https://docs.bazzite.gg"
|
||||||
|
SUPPORT_URL="https://discord.bazzite.gg"
|
||||||
|
BUG_REPORT_URL="https://github.com/ublue-os/bazzite/issues/"
|
||||||
|
SUPPORT_END=2025-12-15
|
||||||
|
VARIANT="Kinoite"
|
||||||
|
VARIANT_ID=bazzite-nvidia-open
|
||||||
|
OSTREE_VERSION='41.20250208.0'
|
||||||
|
BUILD_ID="Stable (F41.20250208)"
|
||||||
|
BOOTLOADER_NAME="Bazzite Stable (F41.20250208)"
|
||||||
|
BUILD_ID="Stable (F41.20250208)"
|
||||||
|
BOOTLOADER_NAME="Bazzite Stable (F41.20250208)"
|
||||||
24
src/steps/os/os_release/bluefin
Normal file
24
src/steps/os/os_release/bluefin
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
NAME="Bluefin"
|
||||||
|
VERSION="41.20250216.1 (Silverblue)"
|
||||||
|
RELEASE_TYPE=stable
|
||||||
|
ID=bluefin
|
||||||
|
ID_LIKE="fedora"
|
||||||
|
VERSION_ID=41
|
||||||
|
VERSION_CODENAME="Archaeopteryx"
|
||||||
|
PLATFORM_ID="platform:f41"
|
||||||
|
PRETTY_NAME="Bluefin (Version: 41.20250216.1 / FROM Fedora Silverblue 41)"
|
||||||
|
ANSI_COLOR="0;38;2;60;110;180"
|
||||||
|
LOGO=fedora-logo-icon
|
||||||
|
CPE_NAME="cpe:/o:universal-blue:bluefin:41"
|
||||||
|
DEFAULT_HOSTNAME="bluefin"
|
||||||
|
HOME_URL="https://projectbluefin.io"
|
||||||
|
DOCUMENTATION_URL="https://docs.projectbluefin.io"
|
||||||
|
SUPPORT_URL="https://github.com/ublue-os/bluefin/issues/"
|
||||||
|
BUG_REPORT_URL="https://github.com/ublue-os/bluefin/issues/"
|
||||||
|
SUPPORT_END=2025-12-15
|
||||||
|
VARIANT="Silverblue"
|
||||||
|
VARIANT_ID=bluefin
|
||||||
|
OSTREE_VERSION='41.20250216.1'
|
||||||
|
BUILD_ID="185146a"
|
||||||
|
IMAGE_ID="bluefin"
|
||||||
|
IMAGE_VERSION="41.20250216.1"
|
||||||
11
src/steps/os/os_release/cachyos
Normal file
11
src/steps/os/os_release/cachyos
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
NAME="CachyOS Linux"
|
||||||
|
PRETTY_NAME="CachyOS"
|
||||||
|
ID=cachyos
|
||||||
|
BUILD_ID=rolling
|
||||||
|
ANSI_COLOR="38;2;23;147;209"
|
||||||
|
HOME_URL="https://cachyos.org/"
|
||||||
|
DOCUMENTATION_URL="https://wiki.cachyos.org/"
|
||||||
|
SUPPORT_URL="https://discuss.cachyos.org/"
|
||||||
|
BUG_REPORT_URL="https://github.com/cachyos"
|
||||||
|
PRIVACY_POLICY_URL="https://terms.archlinux.org/docs/privacy-policy/"
|
||||||
|
LOGO=cachyos
|
||||||
@@ -13,4 +13,3 @@ CENTOS_MANTISBT_PROJECT="CentOS-7"
|
|||||||
CENTOS_MANTISBT_PROJECT_VERSION="7"
|
CENTOS_MANTISBT_PROJECT_VERSION="7"
|
||||||
REDHAT_SUPPORT_PRODUCT="centos"
|
REDHAT_SUPPORT_PRODUCT="centos"
|
||||||
REDHAT_SUPPORT_PRODUCT_VERSION="7"
|
REDHAT_SUPPORT_PRODUCT_VERSION="7"
|
||||||
|
|
||||||
|
|||||||
23
src/steps/os/os_release/coreos
Normal file
23
src/steps/os/os_release/coreos
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
NAME="Fedora Linux"
|
||||||
|
VERSION="41.20250117.3.0 (CoreOS)"
|
||||||
|
RELEASE_TYPE=stable
|
||||||
|
ID=fedora
|
||||||
|
VERSION_ID=41
|
||||||
|
VERSION_CODENAME=""
|
||||||
|
PLATFORM_ID="platform:f41"
|
||||||
|
PRETTY_NAME="Fedora CoreOS 41.20250117.3.0 (uCore)"
|
||||||
|
ANSI_COLOR="0;38;2;60;110;180"
|
||||||
|
LOGO=fedora-logo-icon
|
||||||
|
CPE_NAME="cpe:/o:fedoraproject:fedora:41"
|
||||||
|
HOME_URL="https://getfedora.org/coreos/"
|
||||||
|
DOCUMENTATION_URL="https://docs.fedoraproject.org/en-US/fedora-coreos/"
|
||||||
|
SUPPORT_URL="https://github.com/coreos/fedora-coreos-tracker/"
|
||||||
|
BUG_REPORT_URL="https://github.com/coreos/fedora-coreos-tracker/"
|
||||||
|
REDHAT_BUGZILLA_PRODUCT="Fedora"
|
||||||
|
REDHAT_BUGZILLA_PRODUCT_VERSION=41
|
||||||
|
REDHAT_SUPPORT_PRODUCT="Fedora"
|
||||||
|
REDHAT_SUPPORT_PRODUCT_VERSION=41
|
||||||
|
SUPPORT_END=2025-12-15
|
||||||
|
VARIANT="CoreOS"
|
||||||
|
VARIANT_ID=coreos
|
||||||
|
OSTREE_VERSION='41.20250117.3.0'
|
||||||
@@ -4,4 +4,3 @@ PRETTY_NAME="Manjaro ARM"
|
|||||||
ANSI_COLOR="1;32"
|
ANSI_COLOR="1;32"
|
||||||
HOME_URL="https://www.manjaro.org/"
|
HOME_URL="https://www.manjaro.org/"
|
||||||
SUPPORT_URL="https://forum.manjaro.org/c/manjaro-arm/"
|
SUPPORT_URL="https://forum.manjaro.org/c/manjaro-arm/"
|
||||||
|
|
||||||
|
|||||||
@@ -1,34 +1,39 @@
|
|||||||
use std::ffi::OsStr;
|
|
||||||
use std::fs;
|
|
||||||
use std::os::unix::fs::MetadataExt;
|
|
||||||
use std::path::Component;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::process::Command;
|
|
||||||
use std::{env::var, path::Path};
|
|
||||||
|
|
||||||
use crate::command::CommandExt;
|
|
||||||
use crate::{Step, HOME_DIR};
|
|
||||||
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 color_eyre::eyre::{eyre, OptionExt};
|
||||||
|
use etcetera::BaseStrategy;
|
||||||
use home;
|
use home;
|
||||||
use ini::Ini;
|
use ini::Ini;
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
use nix::unistd::Uid;
|
use nix::unistd::Uid;
|
||||||
|
use regex::Regex;
|
||||||
use rust_i18n::t;
|
use rust_i18n::t;
|
||||||
use semver::Version;
|
use semver::Version;
|
||||||
use tracing::debug;
|
use std::ffi::OsStr;
|
||||||
|
use std::io::Write;
|
||||||
|
use std::os::unix::fs::MetadataExt;
|
||||||
|
use std::path::Component;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::process::Command;
|
||||||
|
use std::sync::LazyLock;
|
||||||
|
use std::{env::var, path::Path};
|
||||||
|
use std::{fs, io};
|
||||||
|
use tracing::{debug, warn};
|
||||||
|
|
||||||
|
use crate::command::CommandExt;
|
||||||
|
use crate::sudo::SudoExecuteOpts;
|
||||||
|
use crate::XDG_DIRS;
|
||||||
|
use crate::{output_changed_message, HOME_DIR};
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
use super::linux::Distribution;
|
use super::linux::Distribution;
|
||||||
use crate::error::SkipStep;
|
use crate::error::{SkipStep, StepFailed};
|
||||||
use crate::execution_context::ExecutionContext;
|
use crate::execution_context::ExecutionContext;
|
||||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||||
use crate::executor::Executor;
|
use crate::executor::Executor;
|
||||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
use crate::step::Step;
|
||||||
use crate::executor::RunType;
|
|
||||||
use crate::terminal::print_separator;
|
use crate::terminal::print_separator;
|
||||||
use crate::utils::{get_require_sudo_string, require, require_option, PathExt};
|
use crate::utils::{require, PathExt};
|
||||||
|
|
||||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||||
const INTEL_BREW: &str = "/usr/local/bin/brew";
|
const INTEL_BREW: &str = "/usr/local/bin/brew";
|
||||||
@@ -73,19 +78,41 @@ impl BrewVariant {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn execute(self, run_type: RunType) -> Executor {
|
/// Execute an "internal" brew command, i.e. one that should always be run
|
||||||
|
/// even when dry-running. Basically just a wrapper around [`Command::new`]
|
||||||
|
/// that uses `arch` to run using the correct architecture if needed.
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
fn execute_internal(self) -> Command {
|
||||||
match self {
|
match self {
|
||||||
BrewVariant::MacIntel if cfg!(target_arch = "aarch64") => {
|
BrewVariant::MacIntel if cfg!(target_arch = "aarch64") => {
|
||||||
let mut command = run_type.execute("arch");
|
let mut command = Command::new("arch");
|
||||||
command.arg("-x86_64").arg(self.binary_name());
|
command.arg("-x86_64").arg(self.binary_name());
|
||||||
command
|
command
|
||||||
}
|
}
|
||||||
BrewVariant::MacArm if cfg!(target_arch = "x86_64") => {
|
BrewVariant::MacArm if cfg!(target_arch = "x86_64") => {
|
||||||
let mut command = run_type.execute("arch");
|
let mut command = Command::new("arch");
|
||||||
command.arg("-arm64e").arg(self.binary_name());
|
command.arg("-arm64e").arg(self.binary_name());
|
||||||
command
|
command
|
||||||
}
|
}
|
||||||
_ => run_type.execute(self.binary_name()),
|
_ => Command::new(self.binary_name()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Execute a brew command. Uses `arch` to run using the correct
|
||||||
|
/// architecture on macOS if needed.
|
||||||
|
fn execute(self, ctx: &ExecutionContext) -> Executor {
|
||||||
|
match self {
|
||||||
|
BrewVariant::MacIntel if cfg!(target_arch = "aarch64") => {
|
||||||
|
let mut command = ctx.execute("arch");
|
||||||
|
command.arg("-x86_64").arg(self.binary_name());
|
||||||
|
command
|
||||||
|
}
|
||||||
|
BrewVariant::MacArm if cfg!(target_arch = "x86_64") => {
|
||||||
|
let mut command = ctx.execute("arch");
|
||||||
|
command.arg("-arm64e").arg(self.binary_name());
|
||||||
|
command
|
||||||
|
}
|
||||||
|
_ => ctx.execute(self.binary_name()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,7 +146,6 @@ pub fn run_fisher(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
print_separator("Fisher");
|
print_separator("Fisher");
|
||||||
|
|
||||||
let version_str = ctx
|
let version_str = ctx
|
||||||
.run_type()
|
|
||||||
.execute(&fish)
|
.execute(&fish)
|
||||||
.args(["-c", "fisher --version"])
|
.args(["-c", "fisher --version"])
|
||||||
.output_checked_utf8()?
|
.output_checked_utf8()?
|
||||||
@@ -128,13 +154,10 @@ pub fn run_fisher(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
|
|
||||||
if version_str.starts_with("fisher version 3.") {
|
if version_str.starts_with("fisher version 3.") {
|
||||||
// v3 - see https://github.com/topgrade-rs/topgrade/pull/37#issuecomment-1283844506
|
// v3 - see https://github.com/topgrade-rs/topgrade/pull/37#issuecomment-1283844506
|
||||||
ctx.run_type().execute(&fish).args(["-c", "fisher"]).status_checked()
|
ctx.execute(&fish).args(["-c", "fisher"]).status_checked()
|
||||||
} else {
|
} else {
|
||||||
// v4
|
// v4
|
||||||
ctx.run_type()
|
ctx.execute(&fish).args(["-c", "fisher update"]).status_checked()
|
||||||
.execute(&fish)
|
|
||||||
.args(["-c", "fisher update"])
|
|
||||||
.status_checked()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,8 +166,7 @@ pub fn run_bashit(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
|
|
||||||
print_separator("Bash-it");
|
print_separator("Bash-it");
|
||||||
|
|
||||||
ctx.run_type()
|
ctx.execute("bash")
|
||||||
.execute("bash")
|
|
||||||
.args(["-lic", &format!("bash-it update {}", ctx.config().bashit_branch())])
|
.args(["-lic", &format!("bash-it update {}", ctx.config().bashit_branch())])
|
||||||
.status_checked()
|
.status_checked()
|
||||||
}
|
}
|
||||||
@@ -167,7 +189,7 @@ pub fn run_oh_my_bash(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
let mut update_script = oh_my_bash;
|
let mut update_script = oh_my_bash;
|
||||||
update_script.push_str("/tools/upgrade.sh");
|
update_script.push_str("/tools/upgrade.sh");
|
||||||
|
|
||||||
ctx.run_type().execute("bash").arg(update_script).status_checked()
|
ctx.execute("bash").arg(update_script).status_checked()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_oh_my_fish(ctx: &ExecutionContext) -> Result<()> {
|
pub fn run_oh_my_fish(ctx: &ExecutionContext) -> Result<()> {
|
||||||
@@ -176,24 +198,25 @@ pub fn run_oh_my_fish(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
|
|
||||||
print_separator("oh-my-fish");
|
print_separator("oh-my-fish");
|
||||||
|
|
||||||
ctx.run_type().execute(fish).args(["-c", "omf update"]).status_checked()
|
ctx.execute(fish).args(["-c", "omf update"]).status_checked()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_pkgin(ctx: &ExecutionContext) -> Result<()> {
|
pub fn run_pkgin(ctx: &ExecutionContext) -> Result<()> {
|
||||||
let pkgin = require("pkgin")?;
|
let pkgin = require("pkgin")?;
|
||||||
let sudo = require_option(ctx.sudo().as_ref(), get_require_sudo_string())?;
|
|
||||||
|
|
||||||
print_separator("Pkgin");
|
print_separator("Pkgin");
|
||||||
|
|
||||||
let mut command = ctx.run_type().execute(sudo);
|
let sudo = ctx.require_sudo()?;
|
||||||
command.arg(&pkgin).arg("update");
|
|
||||||
|
let mut command = sudo.execute(ctx, &pkgin)?;
|
||||||
|
command.arg("update");
|
||||||
if ctx.config().yes(Step::Pkgin) {
|
if ctx.config().yes(Step::Pkgin) {
|
||||||
command.arg("-y");
|
command.arg("-y");
|
||||||
}
|
}
|
||||||
command.status_checked()?;
|
command.status_checked()?;
|
||||||
|
|
||||||
let mut command = ctx.run_type().execute(sudo);
|
let mut command = sudo.execute(ctx, &pkgin)?;
|
||||||
command.arg(&pkgin).arg("upgrade");
|
command.arg("upgrade");
|
||||||
if ctx.config().yes(Step::Pkgin) {
|
if ctx.config().yes(Step::Pkgin) {
|
||||||
command.arg("-y");
|
command.arg("-y");
|
||||||
}
|
}
|
||||||
@@ -208,10 +231,7 @@ pub fn run_fish_plug(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
|
|
||||||
print_separator("fish-plug");
|
print_separator("fish-plug");
|
||||||
|
|
||||||
ctx.run_type()
|
ctx.execute(fish).args(["-c", "plug update"]).status_checked()
|
||||||
.execute(fish)
|
|
||||||
.args(["-c", "plug update"])
|
|
||||||
.status_checked()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Upgrades `fundle` and `fundle` plugins.
|
/// Upgrades `fundle` and `fundle` plugins.
|
||||||
@@ -225,8 +245,7 @@ pub fn run_fundle(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
|
|
||||||
print_separator("fundle");
|
print_separator("fundle");
|
||||||
|
|
||||||
ctx.run_type()
|
ctx.execute(fish)
|
||||||
.execute(fish)
|
|
||||||
.args(["-c", "fundle self-update && fundle update"])
|
.args(["-c", "fundle self-update && fundle update"])
|
||||||
.status_checked()
|
.status_checked()
|
||||||
}
|
}
|
||||||
@@ -234,9 +253,9 @@ pub fn run_fundle(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
#[cfg(not(any(target_os = "android", target_os = "macos")))]
|
#[cfg(not(any(target_os = "android", target_os = "macos")))]
|
||||||
pub fn upgrade_gnome_extensions(ctx: &ExecutionContext) -> Result<()> {
|
pub fn upgrade_gnome_extensions(ctx: &ExecutionContext) -> Result<()> {
|
||||||
let gdbus = require("gdbus")?;
|
let gdbus = require("gdbus")?;
|
||||||
require_option(
|
crate::utils::require_option(
|
||||||
var("XDG_CURRENT_DESKTOP").ok().filter(|p| p.contains("GNOME")),
|
var("XDG_CURRENT_DESKTOP").ok().filter(|p| p.contains("GNOME")),
|
||||||
t!("Desktop doest not appear to be gnome").to_string(),
|
t!("Desktop does not appear to be GNOME").to_string(),
|
||||||
)?;
|
)?;
|
||||||
let output = Command::new("gdbus")
|
let output = Command::new("gdbus")
|
||||||
.args([
|
.args([
|
||||||
@@ -251,15 +270,14 @@ pub fn upgrade_gnome_extensions(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
])
|
])
|
||||||
.output_checked_utf8()?;
|
.output_checked_utf8()?;
|
||||||
|
|
||||||
debug!("Checking for gnome extensions: {}", output);
|
debug!("Checking for GNOME extensions: {}", output);
|
||||||
if !output.stdout.contains("org.gnome.Shell.Extensions") {
|
if !output.stdout.contains("org.gnome.Shell.Extensions") {
|
||||||
return Err(SkipStep(t!("Gnome shell extensions are unregistered in DBus").to_string()).into());
|
return Err(SkipStep(t!("GNOME shell extensions are unregistered in DBus").to_string()).into());
|
||||||
}
|
}
|
||||||
|
|
||||||
print_separator(t!("Gnome Shell extensions"));
|
print_separator(t!("GNOME Shell extensions"));
|
||||||
|
|
||||||
ctx.run_type()
|
ctx.execute(gdbus)
|
||||||
.execute(gdbus)
|
|
||||||
.args([
|
.args([
|
||||||
"call",
|
"call",
|
||||||
"--session",
|
"--session",
|
||||||
@@ -315,26 +333,19 @@ pub fn run_brew_formula(ctx: &ExecutionContext, variant: BrewVariant) -> Result<
|
|||||||
let sudo_as_user = t!("sudo as user '{user}'", user = user.name);
|
let sudo_as_user = t!("sudo as user '{user}'", user = user.name);
|
||||||
print_separator(format!("{} ({})", variant.step_title(), sudo_as_user));
|
print_separator(format!("{} ({})", variant.step_title(), sudo_as_user));
|
||||||
|
|
||||||
let sudo = crate::utils::require_option(ctx.sudo().as_ref(), crate::utils::get_require_sudo_string())?;
|
let sudo = ctx.require_sudo()?;
|
||||||
ctx.run_type()
|
sudo.execute_opts(ctx, &binary_name, SudoExecuteOpts::new().set_home().user(&user.name))?
|
||||||
.execute(sudo)
|
|
||||||
.current_dir("/tmp") // brew needs a writable current directory
|
.current_dir("/tmp") // brew needs a writable current directory
|
||||||
.args([
|
.arg("update")
|
||||||
"--set-home",
|
|
||||||
&format!("--user={}", user.name),
|
|
||||||
&format!("{}", binary_name.to_string_lossy()),
|
|
||||||
"update",
|
|
||||||
])
|
|
||||||
.status_checked()?;
|
.status_checked()?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
print_separator(variant.step_title());
|
print_separator(variant.step_title());
|
||||||
let run_type = ctx.run_type();
|
|
||||||
|
|
||||||
variant.execute(run_type).arg("update").status_checked()?;
|
variant.execute(ctx).arg("update").status_checked()?;
|
||||||
|
|
||||||
let mut command = variant.execute(run_type);
|
let mut command = variant.execute(ctx);
|
||||||
command.args(["upgrade", "--formula"]);
|
command.args(["upgrade", "--formula"]);
|
||||||
|
|
||||||
if ctx.config().brew_fetch_head() {
|
if ctx.config().brew_fetch_head() {
|
||||||
@@ -344,11 +355,11 @@ pub fn run_brew_formula(ctx: &ExecutionContext, variant: BrewVariant) -> Result<
|
|||||||
command.status_checked()?;
|
command.status_checked()?;
|
||||||
|
|
||||||
if ctx.config().cleanup() {
|
if ctx.config().cleanup() {
|
||||||
variant.execute(run_type).arg("cleanup").status_checked()?;
|
variant.execute(ctx).arg("cleanup").status_checked()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.config().brew_autoremove() {
|
if ctx.config().brew_autoremove() {
|
||||||
variant.execute(run_type).arg("autoremove").status_checked()?;
|
variant.execute(ctx).arg("autoremove").status_checked()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -361,10 +372,9 @@ pub fn run_brew_cask(ctx: &ExecutionContext, variant: BrewVariant) -> Result<()>
|
|||||||
return Err(SkipStep(t!("Not a custom brew for macOS").to_string()).into());
|
return Err(SkipStep(t!("Not a custom brew for macOS").to_string()).into());
|
||||||
}
|
}
|
||||||
print_separator(format!("{} - Cask", variant.step_title()));
|
print_separator(format!("{} - Cask", variant.step_title()));
|
||||||
let run_type = ctx.run_type();
|
|
||||||
|
|
||||||
let cask_upgrade_exists = variant
|
let cask_upgrade_exists = variant
|
||||||
.execute(RunType::Wet)
|
.execute_internal()
|
||||||
.args(["--repository", "buo/cask-upgrade"])
|
.args(["--repository", "buo/cask-upgrade"])
|
||||||
.output_checked_utf8()
|
.output_checked_utf8()
|
||||||
.map(|p| Path::new(p.stdout.trim()).exists())?;
|
.map(|p| Path::new(p.stdout.trim()).exists())?;
|
||||||
@@ -389,10 +399,10 @@ pub fn run_brew_cask(ctx: &ExecutionContext, variant: BrewVariant) -> Result<()>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
variant.execute(run_type).args(&brew_args).status_checked()?;
|
variant.execute(ctx).args(&brew_args).status_checked()?;
|
||||||
|
|
||||||
if ctx.config().cleanup() {
|
if ctx.config().cleanup() {
|
||||||
variant.execute(run_type).arg("cleanup").status_checked()?;
|
variant.execute(ctx).arg("cleanup").status_checked()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -401,19 +411,80 @@ pub fn run_brew_cask(ctx: &ExecutionContext, variant: BrewVariant) -> Result<()>
|
|||||||
pub fn run_guix(ctx: &ExecutionContext) -> Result<()> {
|
pub fn run_guix(ctx: &ExecutionContext) -> Result<()> {
|
||||||
let guix = require("guix")?;
|
let guix = require("guix")?;
|
||||||
|
|
||||||
let run_type = ctx.run_type();
|
|
||||||
|
|
||||||
let output = Command::new(&guix).arg("pull").output_checked_utf8();
|
|
||||||
debug!("guix pull output: {:?}", output);
|
|
||||||
let should_upgrade = output.is_ok();
|
|
||||||
debug!("Can Upgrade Guix: {:?}", should_upgrade);
|
|
||||||
|
|
||||||
print_separator("Guix");
|
print_separator("Guix");
|
||||||
|
|
||||||
if should_upgrade {
|
ctx.execute(&guix).arg("pull").status_checked()?;
|
||||||
return run_type.execute(&guix).args(["package", "-u"]).status_checked();
|
ctx.execute(&guix).args(["package", "-u"]).status_checked()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
struct NixVersion {
|
||||||
|
version_string: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NixVersion {
|
||||||
|
fn new(ctx: &ExecutionContext, nix: &Path) -> Result<Self> {
|
||||||
|
let version_output = ctx.execute(nix).arg("--version").output_checked_utf8()?;
|
||||||
|
|
||||||
|
debug!(
|
||||||
|
output=%version_output,
|
||||||
|
"`nix --version` output"
|
||||||
|
);
|
||||||
|
|
||||||
|
let version_string = version_output
|
||||||
|
.stdout
|
||||||
|
.lines()
|
||||||
|
.next()
|
||||||
|
.ok_or_else(|| eyre!("`nix --version` output is empty"))?
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
if version_string.is_empty() {
|
||||||
|
return Err(eyre!("`nix --version` output was empty"));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self { version_string })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn version(&self) -> Result<Version> {
|
||||||
|
static NIX_VERSION_REGEX: LazyLock<Regex> =
|
||||||
|
LazyLock::new(|| Regex::new(r"^nix \([^)]*\) ([0-9.]+)").expect("Nix version regex always compiles"));
|
||||||
|
|
||||||
|
let captures = NIX_VERSION_REGEX
|
||||||
|
.captures(&self.version_string)
|
||||||
|
.ok_or_else(|| eyre!(output_changed_message!("nix --version", "regex did not match")))?;
|
||||||
|
let raw_version = &captures[1];
|
||||||
|
|
||||||
|
debug!("Raw Nix version: {raw_version}");
|
||||||
|
|
||||||
|
// Nix 2.29.0 outputs "2.29" instead of "2.29.0", so we need to add that if necessary.
|
||||||
|
let corrected_raw_version = if raw_version.chars().filter(|&c| c == '.').count() == 1 {
|
||||||
|
&format!("{raw_version}.0")
|
||||||
|
} else {
|
||||||
|
raw_version
|
||||||
|
};
|
||||||
|
|
||||||
|
debug!("Corrected raw Nix version: {corrected_raw_version}");
|
||||||
|
|
||||||
|
let version = Version::parse(corrected_raw_version)
|
||||||
|
.wrap_err_with(|| output_changed_message!("nix --version", "Invalid version"))?;
|
||||||
|
|
||||||
|
debug!("Nix version: {:?}", version);
|
||||||
|
|
||||||
|
Ok(version)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_lix(&self) -> bool {
|
||||||
|
let is_lix = self.version_string.contains("Lix");
|
||||||
|
debug!(?is_lix);
|
||||||
|
is_lix
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_determinate_nix(&self) -> bool {
|
||||||
|
let is_determinate_nix = self.version_string.contains("Determinate Nix");
|
||||||
|
debug!(?is_determinate_nix);
|
||||||
|
is_determinate_nix
|
||||||
}
|
}
|
||||||
Err(SkipStep(t!("Guix Pull Failed, Skipping").to_string()).into())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_nix(ctx: &ExecutionContext) -> Result<()> {
|
pub fn run_nix(ctx: &ExecutionContext) -> Result<()> {
|
||||||
@@ -422,7 +493,11 @@ pub fn run_nix(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
let nix_env = require("nix-env")?;
|
let nix_env = require("nix-env")?;
|
||||||
// TODO: Is None possible here?
|
// TODO: Is None possible here?
|
||||||
let profile_path = match home::home_dir() {
|
let profile_path = match home::home_dir() {
|
||||||
Some(home) => Path::new(&home).join(".nix-profile"),
|
Some(home) => XDG_DIRS
|
||||||
|
.state_dir()
|
||||||
|
.map(|d| d.join("nix/profile"))
|
||||||
|
.filter(|p| p.exists())
|
||||||
|
.unwrap_or(Path::new(&home).join(".nix-profile")),
|
||||||
None => Path::new("/nix/var/nix/profiles/per-user/default").into(),
|
None => Path::new("/nix/var/nix/profiles/per-user/default").into(),
|
||||||
};
|
};
|
||||||
debug!("nix profile: {:?}", profile_path);
|
debug!("nix profile: {:?}", profile_path);
|
||||||
@@ -439,36 +514,20 @@ pub fn run_nix(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let run_type = ctx.run_type();
|
ctx.execute(nix_channel).arg("--update").status_checked()?;
|
||||||
run_type.execute(nix_channel).arg("--update").status_checked()?;
|
|
||||||
|
|
||||||
let mut get_version_cmd = ctx.run_type().execute(&nix);
|
let nix_version = NixVersion::new(ctx, &nix)?;
|
||||||
get_version_cmd.arg("--version");
|
|
||||||
let get_version_cmd_output = get_version_cmd.output_checked_utf8()?;
|
|
||||||
let get_version_cmd_first_line_stdout = get_version_cmd_output
|
|
||||||
.stdout
|
|
||||||
.lines()
|
|
||||||
.next()
|
|
||||||
.expect("nix --version gives an empty output");
|
|
||||||
let splitted: Vec<&str> = get_version_cmd_first_line_stdout.split_whitespace().collect();
|
|
||||||
let version = if splitted.len() >= 3 {
|
|
||||||
Version::parse(splitted[2]).expect("invalid version")
|
|
||||||
} else {
|
|
||||||
panic!("nix --version output format changed, file an issue to Topgrade!")
|
|
||||||
};
|
|
||||||
|
|
||||||
debug!("Nix version: {:?}", version);
|
// Nix since 2.21.0 uses `--all --impure` rather than `.*` to upgrade all packages.
|
||||||
|
// Lix is based on Nix 2.18, so it doesn't!
|
||||||
// Nix since 2.21.0 uses `--all --impure` rather than `.*` to upgrade all packages
|
let packages = if nix_version.version()? >= Version::new(2, 21, 0) && !nix_version.is_lix() {
|
||||||
let packages = if version >= Version::new(2, 21, 0) {
|
|
||||||
vec!["--all", "--impure"]
|
vec!["--all", "--impure"]
|
||||||
} else {
|
} else {
|
||||||
vec![".*"]
|
vec![".*"]
|
||||||
};
|
};
|
||||||
|
|
||||||
if Path::new(&manifest_json_path).exists() {
|
if Path::new(&manifest_json_path).exists() {
|
||||||
run_type
|
ctx.execute(nix)
|
||||||
.execute(nix)
|
|
||||||
.args(nix_args())
|
.args(nix_args())
|
||||||
.arg("profile")
|
.arg("profile")
|
||||||
.arg("upgrade")
|
.arg("upgrade")
|
||||||
@@ -476,7 +535,7 @@ pub fn run_nix(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
.arg("--verbose")
|
.arg("--verbose")
|
||||||
.status_checked()
|
.status_checked()
|
||||||
} else {
|
} else {
|
||||||
let mut command = run_type.execute(nix_env);
|
let mut command = ctx.execute(nix_env);
|
||||||
command.arg("--upgrade");
|
command.arg("--upgrade");
|
||||||
if let Some(args) = ctx.config().nix_env_arguments() {
|
if let Some(args) = ctx.config().nix_env_arguments() {
|
||||||
command.args(args.split_whitespace());
|
command.args(args.split_whitespace());
|
||||||
@@ -512,21 +571,37 @@ pub fn run_nix_self_upgrade(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
|
|
||||||
print_separator(t!("Nix (self-upgrade)"));
|
print_separator(t!("Nix (self-upgrade)"));
|
||||||
|
|
||||||
|
let nix_version = NixVersion::new(ctx, &nix)?;
|
||||||
|
|
||||||
|
if nix_version.is_determinate_nix() {
|
||||||
|
let nixd = require("determinate-nixd");
|
||||||
|
let nixd = match nixd {
|
||||||
|
Err(_) => {
|
||||||
|
println!("Found Determinate Nix, but could not find determinate-nixd");
|
||||||
|
return Err(StepFailed.into());
|
||||||
|
}
|
||||||
|
Ok(nixd) => nixd,
|
||||||
|
};
|
||||||
|
|
||||||
|
let sudo = ctx.require_sudo()?;
|
||||||
|
return sudo
|
||||||
|
.execute_opts(ctx, nixd, SudoExecuteOpts::new().login_shell())?
|
||||||
|
.arg("upgrade")
|
||||||
|
.status_checked();
|
||||||
|
}
|
||||||
|
|
||||||
let multi_user = fs::metadata(&nix)?.uid() == 0;
|
let multi_user = fs::metadata(&nix)?.uid() == 0;
|
||||||
debug!("Multi user nix: {}", multi_user);
|
debug!("Multi user nix: {}", multi_user);
|
||||||
|
|
||||||
let nix_args = nix_args();
|
let nix_args = nix_args();
|
||||||
if multi_user {
|
if multi_user {
|
||||||
ctx.execute_elevated(&nix, true)?
|
let sudo = ctx.require_sudo()?;
|
||||||
|
sudo.execute_opts(ctx, &nix, SudoExecuteOpts::new().login_shell())?
|
||||||
.args(nix_args)
|
.args(nix_args)
|
||||||
.arg("upgrade-nix")
|
.arg("upgrade-nix")
|
||||||
.status_checked()
|
.status_checked()
|
||||||
} else {
|
} else {
|
||||||
ctx.run_type()
|
ctx.execute(&nix).args(nix_args).arg("upgrade-nix").status_checked()
|
||||||
.execute(&nix)
|
|
||||||
.args(nix_args)
|
|
||||||
.arg("upgrade-nix")
|
|
||||||
.status_checked()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -583,8 +658,7 @@ fn nix_profile_dir(nix: &Path) -> Result<Option<PathBuf>> {
|
|||||||
if user_env
|
if user_env
|
||||||
.file_name()
|
.file_name()
|
||||||
.and_then(|name| name.to_str())
|
.and_then(|name| name.to_str())
|
||||||
.map(|name| name.ends_with("user-environment"))
|
.is_some_and(|name| name.ends_with("user-environment"))
|
||||||
.unwrap_or(false)
|
|
||||||
{
|
{
|
||||||
Some(profile_dir)
|
Some(profile_dir)
|
||||||
} else {
|
} else {
|
||||||
@@ -593,6 +667,84 @@ fn nix_profile_dir(nix: &Path) -> Result<Option<PathBuf>> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a directory from an environment variable, if and only if it is a directory which
|
||||||
|
/// contains a flake.nix
|
||||||
|
fn flake_dir(var: &'static str) -> Option<PathBuf> {
|
||||||
|
std::env::var_os(var)
|
||||||
|
.map(PathBuf::from)
|
||||||
|
.take_if(|x| std::fs::exists(x.join("flake.nix")).is_ok_and(|x| x))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update NixOS and home-manager through a flake using `nh`
|
||||||
|
///
|
||||||
|
/// See: https://github.com/viperML/nh
|
||||||
|
pub fn run_nix_helper(ctx: &ExecutionContext) -> Result<()> {
|
||||||
|
require("nix")?;
|
||||||
|
let nix_helper = require("nh")?;
|
||||||
|
|
||||||
|
let fallback_flake_path = flake_dir("NH_FLAKE");
|
||||||
|
let darwin_flake_path = flake_dir("NH_DARWIN_FLAKE");
|
||||||
|
let home_flake_path = flake_dir("NH_HOME_FLAKE");
|
||||||
|
let nixos_flake_path = flake_dir("NH_OS_FLAKE");
|
||||||
|
|
||||||
|
let all_flake_paths: Vec<_> = [
|
||||||
|
fallback_flake_path.as_ref(),
|
||||||
|
darwin_flake_path.as_ref(),
|
||||||
|
home_flake_path.as_ref(),
|
||||||
|
nixos_flake_path.as_ref(),
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// if none of the paths exist AND contain a `flake.nix`, skip
|
||||||
|
if all_flake_paths.is_empty() {
|
||||||
|
if flake_dir("FLAKE").is_some() {
|
||||||
|
warn!(
|
||||||
|
"{}",
|
||||||
|
t!("You have a flake inside of $FLAKE. This is deprecated for nh.")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Err(SkipStep(t!("nh cannot find any configured flakes").into()).into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let nh_switch = |ty: &'static str| -> Result<()> {
|
||||||
|
print_separator(format!("nh {ty}"));
|
||||||
|
|
||||||
|
let mut cmd = ctx.execute(&nix_helper);
|
||||||
|
cmd.arg(ty);
|
||||||
|
cmd.arg("switch");
|
||||||
|
cmd.arg("-u");
|
||||||
|
|
||||||
|
if !ctx.config().yes(Step::NixHelper) {
|
||||||
|
cmd.arg("--ask");
|
||||||
|
}
|
||||||
|
cmd.status_checked()?;
|
||||||
|
Ok(())
|
||||||
|
};
|
||||||
|
|
||||||
|
// We assume that if the user has set these variables, we can throw an error if nh cannot find
|
||||||
|
// a flake there. So we do not anymore perform an eval check to find out whether we should skip
|
||||||
|
// or not.
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
if darwin_flake_path.is_some() || fallback_flake_path.is_some() {
|
||||||
|
nh_switch("darwin")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if home_flake_path.is_some() || fallback_flake_path.is_some() {
|
||||||
|
nh_switch("home")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
if matches!(Distribution::detect(), Ok(Distribution::NixOS))
|
||||||
|
&& (nixos_flake_path.is_some() || fallback_flake_path.is_some())
|
||||||
|
{
|
||||||
|
nh_switch("os")?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn nix_args() -> [&'static str; 2] {
|
fn nix_args() -> [&'static str; 2] {
|
||||||
["--extra-experimental-features", "nix-command"]
|
["--extra-experimental-features", "nix-command"]
|
||||||
}
|
}
|
||||||
@@ -602,22 +754,47 @@ pub fn run_yadm(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
|
|
||||||
print_separator("yadm");
|
print_separator("yadm");
|
||||||
|
|
||||||
ctx.run_type().execute(yadm).arg("pull").status_checked()
|
ctx.execute(yadm).arg("pull").status_checked()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_asdf(ctx: &ExecutionContext) -> Result<()> {
|
pub fn run_asdf(ctx: &ExecutionContext) -> Result<()> {
|
||||||
let asdf = require("asdf")?;
|
let asdf = require("asdf")?;
|
||||||
|
|
||||||
print_separator("asdf");
|
print_separator("asdf");
|
||||||
ctx.run_type()
|
|
||||||
.execute(&asdf)
|
|
||||||
.arg("update")
|
|
||||||
.status_checked_with_codes(&[42])?;
|
|
||||||
|
|
||||||
ctx.run_type()
|
// asdf (>= 0.15.0) won't support the self-update command
|
||||||
.execute(&asdf)
|
//
|
||||||
.args(["plugin", "update", "--all"])
|
// https://github.com/topgrade-rs/topgrade/issues/1007
|
||||||
.status_checked()
|
let version_output = Command::new(&asdf).arg("version").output_checked_utf8()?;
|
||||||
|
// Example output
|
||||||
|
//
|
||||||
|
// ```
|
||||||
|
// $ asdf version
|
||||||
|
// v0.15.0-31e8c93
|
||||||
|
//
|
||||||
|
// ```
|
||||||
|
// ```
|
||||||
|
// $ asdf version
|
||||||
|
// v0.16.7
|
||||||
|
// ```
|
||||||
|
// ```
|
||||||
|
// $ asdf version
|
||||||
|
// 0.18.0 (revision unknown)
|
||||||
|
// ```
|
||||||
|
let version_stdout = version_output.stdout.trim();
|
||||||
|
// trim the starting 'v'
|
||||||
|
let mut remaining = version_stdout.trim_start_matches('v');
|
||||||
|
// remove the hash or revision part if present
|
||||||
|
if let Some(idx) = remaining.find(['-', ' ']) {
|
||||||
|
remaining = &remaining[..idx];
|
||||||
|
}
|
||||||
|
let version =
|
||||||
|
Version::parse(remaining).wrap_err_with(|| output_changed_message!("asdf version", "invalid version"))?;
|
||||||
|
if version < Version::new(0, 15, 0) {
|
||||||
|
ctx.execute(&asdf).arg("update").status_checked_with_codes(&[42])?;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.execute(&asdf).args(["plugin", "update", "--all"]).status_checked()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_mise(ctx: &ExecutionContext) -> Result<()> {
|
pub fn run_mise(ctx: &ExecutionContext) -> Result<()> {
|
||||||
@@ -625,12 +802,29 @@ pub fn run_mise(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
|
|
||||||
print_separator("mise");
|
print_separator("mise");
|
||||||
|
|
||||||
ctx.run_type().execute(&mise).arg("upgrade").status_checked()?;
|
ctx.execute(&mise).args(["plugins", "update"]).status_checked()?;
|
||||||
|
|
||||||
ctx.run_type()
|
let output = ctx
|
||||||
.execute(&mise)
|
.execute(&mise)
|
||||||
.args(["plugins", "update"])
|
.args(["self-update"])
|
||||||
.status_checked()
|
.output_checked_with(|_| Ok(()))?;
|
||||||
|
let status_code = output
|
||||||
|
.status
|
||||||
|
.code()
|
||||||
|
.ok_or_eyre("Couldn't get status code (terminated by signal)")?;
|
||||||
|
let stderr = std::str::from_utf8(&output.stderr).wrap_err("Expected output to be valid UTF-8")?;
|
||||||
|
if stderr.contains("mise is installed via a package manager") && status_code == 1 {
|
||||||
|
debug!("Mise self-update not available")
|
||||||
|
} else {
|
||||||
|
// Write the output
|
||||||
|
io::stdout().write_all(&output.stdout)?;
|
||||||
|
io::stderr().write_all(&output.stderr)?;
|
||||||
|
if status_code != 0 {
|
||||||
|
return Err(StepFailed.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.execute(&mise).arg("upgrade").status_checked()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_home_manager(ctx: &ExecutionContext) -> Result<()> {
|
pub fn run_home_manager(ctx: &ExecutionContext) -> Result<()> {
|
||||||
@@ -638,7 +832,7 @@ pub fn run_home_manager(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
|
|
||||||
print_separator("home-manager");
|
print_separator("home-manager");
|
||||||
|
|
||||||
let mut cmd = ctx.run_type().execute(home_manager);
|
let mut cmd = ctx.execute(home_manager);
|
||||||
cmd.arg("switch");
|
cmd.arg("switch");
|
||||||
|
|
||||||
if let Some(extra_args) = ctx.config().home_manager() {
|
if let Some(extra_args) = ctx.config().home_manager() {
|
||||||
@@ -648,27 +842,18 @@ pub fn run_home_manager(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
cmd.status_checked()
|
cmd.status_checked()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_tldr(ctx: &ExecutionContext) -> Result<()> {
|
|
||||||
let tldr = require("tldr")?;
|
|
||||||
|
|
||||||
print_separator("TLDR");
|
|
||||||
ctx.run_type().execute(tldr).arg("--update").status_checked()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run_pearl(ctx: &ExecutionContext) -> Result<()> {
|
pub fn run_pearl(ctx: &ExecutionContext) -> Result<()> {
|
||||||
let pearl = require("pearl")?;
|
let pearl = require("pearl")?;
|
||||||
print_separator("pearl");
|
print_separator("pearl");
|
||||||
|
|
||||||
ctx.run_type().execute(pearl).arg("update").status_checked()
|
ctx.execute(pearl).arg("update").status_checked()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_pyenv(ctx: &ExecutionContext) -> Result<()> {
|
pub fn run_pyenv(ctx: &ExecutionContext) -> Result<()> {
|
||||||
let pyenv = require("pyenv")?;
|
let pyenv = require("pyenv")?;
|
||||||
print_separator("pyenv");
|
print_separator("pyenv");
|
||||||
|
|
||||||
let pyenv_dir = var("PYENV_ROOT")
|
let pyenv_dir = var("PYENV_ROOT").map_or_else(|_| HOME_DIR.join(".pyenv"), PathBuf::from);
|
||||||
.map(PathBuf::from)
|
|
||||||
.unwrap_or_else(|_| HOME_DIR.join(".pyenv"));
|
|
||||||
|
|
||||||
if !pyenv_dir.exists() {
|
if !pyenv_dir.exists() {
|
||||||
return Err(SkipStep(t!("Pyenv is installed, but $PYENV_ROOT is not set correctly").to_string()).into());
|
return Err(SkipStep(t!("Pyenv is installed, but $PYENV_ROOT is not set correctly").to_string()).into());
|
||||||
@@ -682,15 +867,14 @@ pub fn run_pyenv(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
return Err(SkipStep(t!("pyenv-update plugin is not installed").to_string()).into());
|
return Err(SkipStep(t!("pyenv-update plugin is not installed").to_string()).into());
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.run_type().execute(pyenv).arg("update").status_checked()
|
ctx.execute(pyenv).arg("update").status_checked()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_sdkman(ctx: &ExecutionContext) -> Result<()> {
|
pub fn run_sdkman(ctx: &ExecutionContext) -> Result<()> {
|
||||||
let bash = require("bash")?;
|
let bash = require("bash")?;
|
||||||
|
|
||||||
let sdkman_init_path = var("SDKMAN_DIR")
|
let sdkman_init_path = var("SDKMAN_DIR")
|
||||||
.map(PathBuf::from)
|
.map_or_else(|_| HOME_DIR.join(".sdkman"), PathBuf::from)
|
||||||
.unwrap_or_else(|_| HOME_DIR.join(".sdkman"))
|
|
||||||
.join("bin")
|
.join("bin")
|
||||||
.join("sdkman-init.sh")
|
.join("sdkman-init.sh")
|
||||||
.require()
|
.require()
|
||||||
@@ -699,8 +883,7 @@ pub fn run_sdkman(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
print_separator("SDKMAN!");
|
print_separator("SDKMAN!");
|
||||||
|
|
||||||
let sdkman_config_path = var("SDKMAN_DIR")
|
let sdkman_config_path = var("SDKMAN_DIR")
|
||||||
.map(PathBuf::from)
|
.map_or_else(|_| HOME_DIR.join(".sdkman"), PathBuf::from)
|
||||||
.unwrap_or_else(|_| HOME_DIR.join(".sdkman"))
|
|
||||||
.join("etc")
|
.join("etc")
|
||||||
.join("config")
|
.join("config")
|
||||||
.require()?;
|
.require()?;
|
||||||
@@ -713,34 +896,25 @@ pub fn run_sdkman(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
|
|
||||||
if selfupdate_enabled == "true" {
|
if selfupdate_enabled == "true" {
|
||||||
let cmd_selfupdate = format!("source {} && sdk selfupdate", &sdkman_init_path);
|
let cmd_selfupdate = format!("source {} && sdk selfupdate", &sdkman_init_path);
|
||||||
ctx.run_type()
|
ctx.execute(&bash)
|
||||||
.execute(&bash)
|
|
||||||
.args(["-c", cmd_selfupdate.as_str()])
|
.args(["-c", cmd_selfupdate.as_str()])
|
||||||
.status_checked()?;
|
.status_checked()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let cmd_update = format!("source {} && sdk update", &sdkman_init_path);
|
let cmd_update = format!("source {} && sdk update", &sdkman_init_path);
|
||||||
ctx.run_type()
|
ctx.execute(&bash).args(["-c", cmd_update.as_str()]).status_checked()?;
|
||||||
.execute(&bash)
|
|
||||||
.args(["-c", cmd_update.as_str()])
|
|
||||||
.status_checked()?;
|
|
||||||
|
|
||||||
let cmd_upgrade = format!("source {} && sdk upgrade", &sdkman_init_path);
|
let cmd_upgrade = format!("source {} && sdk upgrade", &sdkman_init_path);
|
||||||
ctx.run_type()
|
ctx.execute(&bash).args(["-c", cmd_upgrade.as_str()]).status_checked()?;
|
||||||
.execute(&bash)
|
|
||||||
.args(["-c", cmd_upgrade.as_str()])
|
|
||||||
.status_checked()?;
|
|
||||||
|
|
||||||
if ctx.config().cleanup() {
|
if ctx.config().cleanup() {
|
||||||
let cmd_flush_archives = format!("source {} && sdk flush archives", &sdkman_init_path);
|
let cmd_flush_archives = format!("source {} && sdk flush archives", &sdkman_init_path);
|
||||||
ctx.run_type()
|
ctx.execute(&bash)
|
||||||
.execute(&bash)
|
|
||||||
.args(["-c", cmd_flush_archives.as_str()])
|
.args(["-c", cmd_flush_archives.as_str()])
|
||||||
.status_checked()?;
|
.status_checked()?;
|
||||||
|
|
||||||
let cmd_flush_temp = format!("source {} && sdk flush temp", &sdkman_init_path);
|
let cmd_flush_temp = format!("source {} && sdk flush temp", &sdkman_init_path);
|
||||||
ctx.run_type()
|
ctx.execute(&bash)
|
||||||
.execute(&bash)
|
|
||||||
.args(["-c", cmd_flush_temp.as_str()])
|
.args(["-c", cmd_flush_temp.as_str()])
|
||||||
.status_checked()?;
|
.status_checked()?;
|
||||||
}
|
}
|
||||||
@@ -753,9 +927,7 @@ pub fn run_bun_packages(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
|
|
||||||
print_separator(t!("Bun Packages"));
|
print_separator(t!("Bun Packages"));
|
||||||
|
|
||||||
let mut package_json: PathBuf = var("BUN_INSTALL")
|
let mut package_json: PathBuf = var("BUN_INSTALL").map_or_else(|_| HOME_DIR.join(".bun"), PathBuf::from);
|
||||||
.map(PathBuf::from)
|
|
||||||
.unwrap_or_else(|_| HOME_DIR.join(".bun"));
|
|
||||||
package_json.push("install/global/package.json");
|
package_json.push("install/global/package.json");
|
||||||
|
|
||||||
if !package_json.exists() {
|
if !package_json.exists() {
|
||||||
@@ -763,7 +935,7 @@ pub fn run_bun_packages(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.run_type().execute(bun).args(["-g", "update"]).status_checked()
|
ctx.execute(bun).args(["-g", "update"]).status_checked()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update dotfiles with `rcm(7)`.
|
/// Update dotfiles with `rcm(7)`.
|
||||||
@@ -773,18 +945,35 @@ pub fn run_rcm(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
let rcup = require("rcup")?;
|
let rcup = require("rcup")?;
|
||||||
|
|
||||||
print_separator("rcm");
|
print_separator("rcm");
|
||||||
ctx.run_type().execute(rcup).arg("-v").status_checked()
|
ctx.execute(rcup).arg("-v").status_checked()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_maza(ctx: &ExecutionContext) -> Result<()> {
|
pub fn run_maza(ctx: &ExecutionContext) -> Result<()> {
|
||||||
let maza = require("maza")?;
|
let maza = require("maza")?;
|
||||||
|
|
||||||
print_separator("maza");
|
print_separator("maza");
|
||||||
ctx.run_type().execute(maza).arg("update").status_checked()
|
ctx.execute(maza).arg("update").status_checked()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reboot() -> Result<()> {
|
pub fn run_hyprpm(ctx: &ExecutionContext) -> Result<()> {
|
||||||
print!("{}", t!("Rebooting..."));
|
let hyprpm = require("hyprpm")?;
|
||||||
|
|
||||||
Command::new("sudo").arg("reboot").status_checked()
|
print_separator("hyprpm");
|
||||||
|
|
||||||
|
ctx.execute(hyprpm).arg("update").status_checked()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_atuin(ctx: &ExecutionContext) -> Result<()> {
|
||||||
|
let atuin = require("atuin-update")?;
|
||||||
|
|
||||||
|
print_separator("atuin");
|
||||||
|
|
||||||
|
ctx.execute(atuin).status_checked()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reboot(ctx: &ExecutionContext) -> Result<()> {
|
||||||
|
match ctx.sudo() {
|
||||||
|
Some(sudo) => sudo.execute(ctx, "reboot")?.status_checked(),
|
||||||
|
None => ctx.execute("reboot").status_checked(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,15 +3,16 @@ use std::{ffi::OsStr, process::Command};
|
|||||||
|
|
||||||
use color_eyre::eyre::Result;
|
use color_eyre::eyre::Result;
|
||||||
use etcetera::base_strategy::BaseStrategy;
|
use etcetera::base_strategy::BaseStrategy;
|
||||||
|
use rust_i18n::t;
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
use crate::command::CommandExt;
|
use crate::command::CommandExt;
|
||||||
|
use crate::config::UpdatesAutoReboot;
|
||||||
use crate::execution_context::ExecutionContext;
|
use crate::execution_context::ExecutionContext;
|
||||||
|
use crate::step::Step;
|
||||||
use crate::terminal::{print_separator, print_warning};
|
use crate::terminal::{print_separator, print_warning};
|
||||||
use crate::utils::{require, which};
|
use crate::utils::{require, which};
|
||||||
use crate::{error::SkipStep, steps::git::RepoStep};
|
use crate::{error::SkipStep, steps::git::RepoStep};
|
||||||
use crate::{powershell, Step};
|
|
||||||
use rust_i18n::t;
|
|
||||||
|
|
||||||
pub fn run_chocolatey(ctx: &ExecutionContext) -> Result<()> {
|
pub fn run_chocolatey(ctx: &ExecutionContext) -> Result<()> {
|
||||||
let choco = require("choco")?;
|
let choco = require("choco")?;
|
||||||
@@ -19,15 +20,9 @@ pub fn run_chocolatey(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
|
|
||||||
print_separator("Chocolatey");
|
print_separator("Chocolatey");
|
||||||
|
|
||||||
let mut command = match ctx.sudo() {
|
let sudo = ctx.require_sudo()?;
|
||||||
Some(sudo) => {
|
|
||||||
let mut command = ctx.run_type().execute(sudo);
|
|
||||||
command.arg(choco);
|
|
||||||
command
|
|
||||||
}
|
|
||||||
None => ctx.run_type().execute(choco),
|
|
||||||
};
|
|
||||||
|
|
||||||
|
let mut command = sudo.execute(ctx, &choco)?;
|
||||||
command.args(["upgrade", "all"]);
|
command.args(["upgrade", "all"]);
|
||||||
|
|
||||||
if yes {
|
if yes {
|
||||||
@@ -42,10 +37,23 @@ pub fn run_winget(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
|
|
||||||
print_separator("winget");
|
print_separator("winget");
|
||||||
|
|
||||||
ctx.run_type()
|
ctx.execute(&winget).args(["source", "update"]).status_checked()?;
|
||||||
.execute(winget)
|
|
||||||
.args(["upgrade", "--all"])
|
let mut command = if ctx.config().winget_use_sudo() {
|
||||||
.status_checked()
|
let sudo = ctx.require_sudo()?;
|
||||||
|
sudo.execute(ctx, &winget)?
|
||||||
|
} else {
|
||||||
|
ctx.execute(winget)
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut args = vec!["upgrade", "--all"];
|
||||||
|
if ctx.config().winget_silent_install() {
|
||||||
|
args.push("--silent");
|
||||||
|
}
|
||||||
|
|
||||||
|
command.args(args).status_checked()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_scoop(ctx: &ExecutionContext) -> Result<()> {
|
pub fn run_scoop(ctx: &ExecutionContext) -> Result<()> {
|
||||||
@@ -53,17 +61,13 @@ pub fn run_scoop(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
|
|
||||||
print_separator("Scoop");
|
print_separator("Scoop");
|
||||||
|
|
||||||
ctx.run_type().execute(&scoop).args(["update"]).status_checked()?;
|
ctx.execute(&scoop).args(["update"]).status_checked()?;
|
||||||
ctx.run_type().execute(&scoop).args(["update", "*"]).status_checked()?;
|
ctx.execute(&scoop).args(["update", "*"]).status_checked()?;
|
||||||
|
|
||||||
if ctx.config().cleanup() {
|
if ctx.config().cleanup() {
|
||||||
ctx.run_type().execute(&scoop).args(["cleanup", "*"]).status_checked()?;
|
ctx.execute(&scoop).args(["cleanup", "*"]).status_checked()?;
|
||||||
ctx.run_type()
|
ctx.execute(&scoop).args(["cache", "rm", "-a"]).status_checked()?
|
||||||
.execute(&scoop)
|
|
||||||
.args(["cache", "rm", "-a"])
|
|
||||||
.status_checked()?
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,7 +80,7 @@ pub fn update_wsl(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
|
|
||||||
print_separator(t!("Update WSL"));
|
print_separator(t!("Update WSL"));
|
||||||
|
|
||||||
let mut wsl_command = ctx.run_type().execute(wsl);
|
let mut wsl_command = ctx.execute(wsl);
|
||||||
wsl_command.args(["--update"]);
|
wsl_command.args(["--update"]);
|
||||||
|
|
||||||
if ctx.config().wsl_update_pre_release() {
|
if ctx.config().wsl_update_pre_release() {
|
||||||
@@ -93,7 +97,7 @@ pub fn update_wsl(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
/// Detect if WSL is installed or not.
|
/// Detect if WSL is installed or not.
|
||||||
///
|
///
|
||||||
/// For WSL, we cannot simply check if command `wsl` is installed as on newer
|
/// For WSL, we cannot simply check if command `wsl` is installed as on newer
|
||||||
/// versions of Windows (since windows 10 version 2004), this commmand is
|
/// versions of Windows (since windows 10 version 2004), this command is
|
||||||
/// installed by default.
|
/// installed by default.
|
||||||
///
|
///
|
||||||
/// If the command is installed and the user hasn't installed any Linux distros
|
/// If the command is installed and the user hasn't installed any Linux distros
|
||||||
@@ -118,8 +122,8 @@ fn get_wsl_distributions(wsl: &Path) -> Result<Vec<String>> {
|
|||||||
let output = Command::new(wsl).args(["--list", "-q"]).output_checked_utf8()?.stdout;
|
let output = Command::new(wsl).args(["--list", "-q"]).output_checked_utf8()?.stdout;
|
||||||
Ok(output
|
Ok(output
|
||||||
.lines()
|
.lines()
|
||||||
|
.map(|x| x.replace(['\u{0}', '\r'], "").trim().to_owned())
|
||||||
.filter(|s| !s.is_empty())
|
.filter(|s| !s.is_empty())
|
||||||
.map(|x| x.replace(['\u{0}', '\r'], ""))
|
|
||||||
.collect())
|
.collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,7 +136,7 @@ fn upgrade_wsl_distribution(wsl: &Path, dist: &str, ctx: &ExecutionContext) -> R
|
|||||||
.trim_end()
|
.trim_end()
|
||||||
.to_owned();
|
.to_owned();
|
||||||
|
|
||||||
let mut command = ctx.run_type().execute(wsl);
|
let mut command = ctx.execute(wsl);
|
||||||
|
|
||||||
// The `arg` method automatically quotes its arguments.
|
// The `arg` method automatically quotes its arguments.
|
||||||
// This means we can't append additional arguments to `topgrade` in WSL
|
// This means we can't append additional arguments to `topgrade` in WSL
|
||||||
@@ -142,12 +146,12 @@ fn upgrade_wsl_distribution(wsl: &Path, dist: &str, ctx: &ExecutionContext) -> R
|
|||||||
//
|
//
|
||||||
// ```rust
|
// ```rust
|
||||||
// command
|
// command
|
||||||
// .args(["-d", dist, "bash", "-c"])
|
// .args(["-d", dist, "bash", "-lc"])
|
||||||
// .arg(format!("TOPGRADE_PREFIX={dist} exec {topgrade}"));
|
// .arg(format!("TOPGRADE_PREFIX={dist} exec {topgrade}"));
|
||||||
// ```
|
// ```
|
||||||
//
|
//
|
||||||
// creates a command string like:
|
// creates a command string like:
|
||||||
// > `C:\WINDOWS\system32\wsl.EXE -d Ubuntu bash -c 'TOPGRADE_PREFIX=Ubuntu exec /bin/topgrade'`
|
// > `C:\WINDOWS\system32\wsl.EXE -d Ubuntu bash -lc 'TOPGRADE_PREFIX=Ubuntu exec /bin/topgrade'`
|
||||||
//
|
//
|
||||||
// Adding the following:
|
// Adding the following:
|
||||||
//
|
//
|
||||||
@@ -156,7 +160,7 @@ fn upgrade_wsl_distribution(wsl: &Path, dist: &str, ctx: &ExecutionContext) -> R
|
|||||||
// ```
|
// ```
|
||||||
//
|
//
|
||||||
// appends the next argument like so:
|
// appends the next argument like so:
|
||||||
// > `C:\WINDOWS\system32\wsl.EXE -d Ubuntu bash -c 'TOPGRADE_PREFIX=Ubuntu exec /bin/topgrade' -v`
|
// > `C:\WINDOWS\system32\wsl.EXE -d Ubuntu bash -lc 'TOPGRADE_PREFIX=Ubuntu exec /bin/topgrade' -v`
|
||||||
// which means `-v` isn't passed to `topgrade`.
|
// which means `-v` isn't passed to `topgrade`.
|
||||||
let mut args = String::new();
|
let mut args = String::new();
|
||||||
if ctx.config().verbose() {
|
if ctx.config().verbose() {
|
||||||
@@ -164,7 +168,7 @@ fn upgrade_wsl_distribution(wsl: &Path, dist: &str, ctx: &ExecutionContext) -> R
|
|||||||
}
|
}
|
||||||
|
|
||||||
command
|
command
|
||||||
.args(["-d", dist, "bash", "-c"])
|
.args(["-d", dist, "bash", "-lc"])
|
||||||
.arg(format!("TOPGRADE_PREFIX={dist} exec {topgrade} {args}"));
|
.arg(format!("TOPGRADE_PREFIX={dist} exec {topgrade} {args}"));
|
||||||
|
|
||||||
if ctx.config().yes(Step::Wsl) {
|
if ctx.config().yes(Step::Wsl) {
|
||||||
@@ -199,32 +203,74 @@ pub fn run_wsl_topgrade(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
if ran {
|
if ran {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(SkipStep(t!("Could not find Topgrade in any WSL disribution").to_string()).into())
|
Err(SkipStep(t!("Could not find Topgrade in any WSL distribution").to_string()).into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn windows_update(ctx: &ExecutionContext) -> Result<()> {
|
pub fn windows_update(ctx: &ExecutionContext) -> Result<()> {
|
||||||
let powershell = powershell::Powershell::windows_powershell();
|
let powershell = ctx.require_powershell()?;
|
||||||
|
|
||||||
print_separator(t!("Windows Update"));
|
print_separator(t!("Windows Update"));
|
||||||
|
|
||||||
if powershell.supports_windows_update() {
|
if !powershell.has_module("PSWindowsUpdate") {
|
||||||
println!("The installer will request to run as administrator, expect a prompt.");
|
|
||||||
|
|
||||||
powershell.windows_update(ctx)
|
|
||||||
} else {
|
|
||||||
print_warning(t!(
|
print_warning(t!(
|
||||||
"Consider installing PSWindowsUpdate as the use of Windows Update via USOClient is not supported."
|
"The PSWindowsUpdate PowerShell module isn't installed so Topgrade can't run Windows Update.\nInstall PSWindowsUpdate by running `Install-Module PSWindowsUpdate` in PowerShell."
|
||||||
));
|
));
|
||||||
|
|
||||||
Err(SkipStep(t!("USOClient not supported.").to_string()).into())
|
return Err(SkipStep(t!("PSWindowsUpdate is not installed").to_string()).into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut cmd = "Import-Module PSWindowsUpdate; Install-WindowsUpdate -Verbose".to_string();
|
||||||
|
|
||||||
|
if ctx.config().accept_all_windows_updates() {
|
||||||
|
cmd.push_str(" -AcceptAll");
|
||||||
|
}
|
||||||
|
|
||||||
|
match ctx.config().windows_updates_auto_reboot() {
|
||||||
|
UpdatesAutoReboot::Yes => cmd.push_str(" -AutoReboot"),
|
||||||
|
UpdatesAutoReboot::No => cmd.push_str(" -IgnoreReboot"),
|
||||||
|
UpdatesAutoReboot::Ask => (), // Prompting is the default for Install-WindowsUpdate
|
||||||
|
}
|
||||||
|
|
||||||
|
powershell.build_command(ctx, &cmd, true)?.status_checked()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reboot() -> Result<()> {
|
pub fn microsoft_store(ctx: &ExecutionContext) -> Result<()> {
|
||||||
|
let powershell = ctx.require_powershell()?;
|
||||||
|
|
||||||
|
print_separator(t!("Microsoft Store"));
|
||||||
|
|
||||||
|
println!("{}", t!("Scanning for updates..."));
|
||||||
|
|
||||||
|
// Scan for updates using the MDM UpdateScanMethod
|
||||||
|
// This method is also available for non-MDM devices
|
||||||
|
let cmd = r#"(Get-CimInstance -Namespace "Root\cimv2\mdm\dmmap" -ClassName "MDM_EnterpriseModernAppManagement_AppManagement01" | Invoke-CimMethod -MethodName UpdateScanMethod).ReturnValue"#;
|
||||||
|
|
||||||
|
powershell
|
||||||
|
.build_command(ctx, cmd, true)?
|
||||||
|
.output_checked_with_utf8(|output| {
|
||||||
|
if !output.status.success() {
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
let ret_val = output.stdout.trim();
|
||||||
|
debug!("Command return value: {}", ret_val);
|
||||||
|
if ret_val == "0" {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
println!(
|
||||||
|
"{}",
|
||||||
|
t!("Success, Microsoft Store apps are being updated in the background")
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reboot(ctx: &ExecutionContext) -> Result<()> {
|
||||||
// If this works, it won't return, but if it doesn't work, it may return a useful error
|
// If this works, it won't return, but if it doesn't work, it may return a useful error
|
||||||
// message.
|
// message.
|
||||||
Command::new("shutdown").args(["/R", "/T", "0"]).status_checked()
|
ctx.execute("shutdown.exe").args(["/R", "/T", "0"]).status_checked()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert_startup_scripts(git_repos: &mut RepoStep) -> Result<()> {
|
pub fn insert_startup_scripts(git_repos: &mut RepoStep) -> Result<()> {
|
||||||
|
|||||||
@@ -1,122 +1,159 @@
|
|||||||
#[cfg(windows)]
|
|
||||||
use std::path::Path;
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
use color_eyre::eyre::eyre;
|
||||||
use color_eyre::eyre::Result;
|
use color_eyre::eyre::Result;
|
||||||
use rust_i18n::t;
|
use tracing::debug;
|
||||||
|
|
||||||
use crate::command::CommandExt;
|
use crate::command::CommandExt;
|
||||||
use crate::execution_context::ExecutionContext;
|
use crate::execution_context::ExecutionContext;
|
||||||
use crate::terminal::{is_dumb, print_separator};
|
use crate::terminal;
|
||||||
use crate::utils::{require_option, which, PathExt};
|
use crate::utils::{which, PathExt};
|
||||||
use crate::Step;
|
|
||||||
|
|
||||||
pub struct Powershell {
|
pub struct Powershell {
|
||||||
path: Option<PathBuf>,
|
path: PathBuf,
|
||||||
profile: Option<PathBuf>,
|
profile: Option<PathBuf>,
|
||||||
|
is_pwsh: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Powershell {
|
impl Powershell {
|
||||||
/// Returns a powershell instance.
|
pub fn new() -> Option<Self> {
|
||||||
///
|
if terminal::is_dumb() {
|
||||||
/// If the powershell binary is not found, or the current terminal is dumb
|
return None;
|
||||||
/// then the instance of this struct will skip all the powershell steps.
|
|
||||||
pub fn new() -> Self {
|
|
||||||
let path = which("pwsh").or_else(|| which("powershell")).filter(|_| !is_dumb());
|
|
||||||
|
|
||||||
let profile = path.as_ref().and_then(|path| {
|
|
||||||
Command::new(path)
|
|
||||||
.args(["-NoProfile", "-Command", "Split-Path $profile"])
|
|
||||||
.output_checked_utf8()
|
|
||||||
.map(|output| PathBuf::from(output.stdout.trim()))
|
|
||||||
.and_then(|p| p.require())
|
|
||||||
.ok()
|
|
||||||
});
|
|
||||||
|
|
||||||
Powershell { path, profile }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
|
||||||
pub fn windows_powershell() -> Self {
|
|
||||||
Powershell {
|
|
||||||
path: which("powershell").filter(|_| !is_dumb()),
|
|
||||||
profile: None,
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
let (path, is_pwsh) = which("pwsh")
|
||||||
pub fn has_module(powershell: &Path, command: &str) -> bool {
|
.map(|p| (Some(p), true))
|
||||||
Command::new(powershell)
|
.or_else(|| which("powershell").map(|p| (Some(p), false)))
|
||||||
.args([
|
.unwrap_or((None, false));
|
||||||
"-NoProfile",
|
|
||||||
"-Command",
|
path.map(|path| {
|
||||||
&format!("Get-Module -ListAvailable {command}"),
|
let mut ret = Self {
|
||||||
])
|
path,
|
||||||
.output_checked_utf8()
|
profile: None,
|
||||||
.map(|result| !result.stdout.is_empty())
|
is_pwsh,
|
||||||
.unwrap_or(false)
|
};
|
||||||
|
ret.set_profile();
|
||||||
|
ret
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn profile(&self) -> Option<&PathBuf> {
|
pub fn profile(&self) -> Option<&PathBuf> {
|
||||||
self.profile.as_ref()
|
self.profile.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_modules(&self, ctx: &ExecutionContext) -> Result<()> {
|
fn set_profile(&mut self) {
|
||||||
let powershell = require_option(self.path.as_ref(), t!("Powershell is not installed").to_string())?;
|
let profile = self
|
||||||
|
.build_command_internal("Split-Path $PROFILE")
|
||||||
print_separator(t!("Powershell Modules Update"));
|
.output_checked_utf8()
|
||||||
|
.map(|output| output.stdout.trim().to_string())
|
||||||
let mut cmd = vec!["Update-Module"];
|
.and_then(|s| PathBuf::from(s).require())
|
||||||
|
.ok();
|
||||||
if ctx.config().verbose() {
|
debug!("Found PowerShell profile: {:?}", profile);
|
||||||
cmd.push("-Verbose")
|
self.profile = profile;
|
||||||
}
|
|
||||||
|
|
||||||
if ctx.config().yes(Step::Powershell) {
|
|
||||||
cmd.push("-Force")
|
|
||||||
}
|
|
||||||
|
|
||||||
println!("{}", t!("Updating modules..."));
|
|
||||||
ctx.run_type()
|
|
||||||
.execute(powershell)
|
|
||||||
// This probably doesn't need `shell_words::join`.
|
|
||||||
.args(["-NoProfile", "-Command", &cmd.join(" ")])
|
|
||||||
.status_checked()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
pub fn is_pwsh(&self) -> bool {
|
||||||
pub fn supports_windows_update(&self) -> bool {
|
self.is_pwsh
|
||||||
self.path
|
|
||||||
.as_ref()
|
|
||||||
.map(|p| Self::has_module(p, "PSWindowsUpdate"))
|
|
||||||
.unwrap_or(false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(windows)]
|
/// Builds an "internal" powershell command
|
||||||
pub fn windows_update(&self, ctx: &ExecutionContext) -> Result<()> {
|
pub fn build_command_internal(&self, cmd: &str) -> Command {
|
||||||
let powershell = require_option(self.path.as_ref(), t!("Powershell is not installed").to_string())?;
|
let mut command = Command::new(&self.path);
|
||||||
|
|
||||||
debug_assert!(self.supports_windows_update());
|
command.args(["-NoProfile", "-Command"]);
|
||||||
|
command.arg(cmd);
|
||||||
|
|
||||||
let accept_all = if ctx.config().accept_all_windows_updates() {
|
// If topgrade was run from pwsh, but we are trying to run powershell, then
|
||||||
"-AcceptAll"
|
// the inherited PSModulePath breaks module imports
|
||||||
} else {
|
if !self.is_pwsh {
|
||||||
""
|
command.env_remove("PSModulePath");
|
||||||
};
|
}
|
||||||
|
|
||||||
let install_windowsupdate_verbose = "Install-WindowsUpdate -Verbose".to_string();
|
|
||||||
|
|
||||||
let mut command = if let Some(sudo) = ctx.sudo() {
|
|
||||||
let mut command = ctx.run_type().execute(sudo);
|
|
||||||
command.arg(powershell);
|
|
||||||
command
|
|
||||||
} else {
|
|
||||||
ctx.run_type().execute(powershell)
|
|
||||||
};
|
|
||||||
|
|
||||||
command
|
command
|
||||||
.args(["-NoProfile", &install_windowsupdate_verbose, accept_all])
|
}
|
||||||
.status_checked()
|
|
||||||
|
/// Builds a "primary" powershell command (uses dry-run if required):
|
||||||
|
/// {powershell} -NoProfile -Command {cmd}
|
||||||
|
pub fn build_command<'a>(
|
||||||
|
&self,
|
||||||
|
ctx: &'a ExecutionContext,
|
||||||
|
cmd: &str,
|
||||||
|
use_sudo: bool,
|
||||||
|
) -> Result<impl CommandExt + 'a> {
|
||||||
|
let mut command = if use_sudo {
|
||||||
|
let sudo = ctx.require_sudo()?;
|
||||||
|
sudo.execute(ctx, &self.path)?
|
||||||
|
} else {
|
||||||
|
ctx.execute(&self.path)
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
{
|
||||||
|
// Check execution policy and return early if it's not set correctly
|
||||||
|
self.execution_policy_args_if_needed()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
command.args(["-NoProfile", "-Command"]);
|
||||||
|
command.arg(cmd);
|
||||||
|
|
||||||
|
// If topgrade was run from pwsh, but we are trying to run powershell, then
|
||||||
|
// the inherited PSModulePath breaks module imports
|
||||||
|
if !self.is_pwsh {
|
||||||
|
command.env_remove("PSModulePath");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(command)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
fn execution_policy_args_if_needed(&self) -> Result<()> {
|
||||||
|
if !self.is_execution_policy_set("RemoteSigned") {
|
||||||
|
Err(eyre!(
|
||||||
|
"PowerShell execution policy is too restrictive. \
|
||||||
|
Please run 'Set-ExecutionPolicy RemoteSigned -Scope CurrentUser' in PowerShell \
|
||||||
|
(or use Unrestricted/Bypass if you're sure about the security implications)"
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
fn is_execution_policy_set(&self, policy: &str) -> bool {
|
||||||
|
// These policies are ordered from most restrictive to least restrictive
|
||||||
|
let valid_policies = ["Restricted", "AllSigned", "RemoteSigned", "Unrestricted", "Bypass"];
|
||||||
|
|
||||||
|
// Find the index of our target policy
|
||||||
|
let target_idx = valid_policies.iter().position(|&p| p == policy);
|
||||||
|
|
||||||
|
let current_policy = self
|
||||||
|
.build_command_internal("Get-ExecutionPolicy")
|
||||||
|
.output_checked_utf8()
|
||||||
|
.map(|output| output.stdout.trim().to_string());
|
||||||
|
|
||||||
|
debug!("Found PowerShell ExecutionPolicy: {:?}", current_policy);
|
||||||
|
|
||||||
|
current_policy.is_ok_and(|current_policy| {
|
||||||
|
// Find the index of the current policy
|
||||||
|
let current_idx = valid_policies.iter().position(|&p| p == current_policy);
|
||||||
|
|
||||||
|
// Check if current policy exists and is at least as permissive as the target
|
||||||
|
match (current_idx, target_idx) {
|
||||||
|
(Some(current), Some(target)) => current >= target,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
pub fn has_module(&self, module_name: &str) -> bool {
|
||||||
|
let cmd = format!("Get-Module -ListAvailable {}", module_name);
|
||||||
|
|
||||||
|
self.build_command_internal(&cmd)
|
||||||
|
.output_checked()
|
||||||
|
.map(|output| !output.stdout.trim_ascii().is_empty())
|
||||||
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ pub fn ssh_step(ctx: &ExecutionContext, hostname: &str) -> Result<()> {
|
|||||||
unreachable!("Tmux execution is only implemented in Unix");
|
unreachable!("Tmux execution is only implemented in Unix");
|
||||||
} else if ctx.config().open_remotes_in_new_terminal() && !ctx.run_type().dry() && cfg!(windows) {
|
} else if ctx.config().open_remotes_in_new_terminal() && !ctx.run_type().dry() && cfg!(windows) {
|
||||||
prepare_async_ssh_command(&mut args);
|
prepare_async_ssh_command(&mut args);
|
||||||
ctx.run_type().execute("wt").args(&args).spawn()?;
|
ctx.execute("wt").args(&args).spawn()?;
|
||||||
Err(SkipStep(String::from(t!("Remote Topgrade launched in an external terminal"))).into())
|
Err(SkipStep(String::from(t!("Remote Topgrade launched in an external terminal"))).into())
|
||||||
} else {
|
} else {
|
||||||
let mut args = vec!["-t", hostname];
|
let mut args = vec!["-t", hostname];
|
||||||
@@ -50,6 +50,6 @@ pub fn ssh_step(ctx: &ExecutionContext, hostname: &str) -> Result<()> {
|
|||||||
print_separator(format!("Remote ({hostname})"));
|
print_separator(format!("Remote ({hostname})"));
|
||||||
println!("{}", t!("Connecting to {hostname}...", hostname = hostname));
|
println!("{}", t!("Connecting to {hostname}...", hostname = hostname));
|
||||||
|
|
||||||
ctx.run_type().execute(ssh).args(&args).status_checked()
|
ctx.execute(ssh).args(&args).status_checked()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,9 @@ use tracing::{debug, error};
|
|||||||
|
|
||||||
use crate::command::CommandExt;
|
use crate::command::CommandExt;
|
||||||
use crate::execution_context::ExecutionContext;
|
use crate::execution_context::ExecutionContext;
|
||||||
|
use crate::step::Step;
|
||||||
use crate::terminal::print_separator;
|
use crate::terminal::print_separator;
|
||||||
use crate::{error::SkipStep, utils, Step};
|
use crate::{error::SkipStep, utils};
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, EnumString)]
|
#[derive(Debug, Copy, Clone, EnumString)]
|
||||||
#[strum(serialize_all = "lowercase")]
|
#[strum(serialize_all = "lowercase")]
|
||||||
@@ -113,8 +114,7 @@ impl<'a> TemporaryPowerOn<'a> {
|
|||||||
BoxStatus::Running => unreachable!(),
|
BoxStatus::Running => unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
ctx.run_type()
|
ctx.execute(vagrant)
|
||||||
.execute(vagrant)
|
|
||||||
.args([subcommand, &vagrant_box.name])
|
.args([subcommand, &vagrant_box.name])
|
||||||
.current_dir(vagrant_box.path.clone())
|
.current_dir(vagrant_box.path.clone())
|
||||||
.status_checked()?;
|
.status_checked()?;
|
||||||
@@ -126,7 +126,7 @@ impl<'a> TemporaryPowerOn<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Drop for TemporaryPowerOn<'a> {
|
impl Drop for TemporaryPowerOn<'_> {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
let subcommand = if self.ctx.config().vagrant_always_suspend().unwrap_or(false) {
|
let subcommand = if self.ctx.config().vagrant_always_suspend().unwrap_or(false) {
|
||||||
"suspend"
|
"suspend"
|
||||||
@@ -140,7 +140,6 @@ impl<'a> Drop for TemporaryPowerOn<'a> {
|
|||||||
|
|
||||||
println!();
|
println!();
|
||||||
self.ctx
|
self.ctx
|
||||||
.run_type()
|
|
||||||
.execute(self.vagrant)
|
.execute(self.vagrant)
|
||||||
.args([subcommand, &self.vagrant_box.name])
|
.args([subcommand, &self.vagrant_box.name])
|
||||||
.current_dir(self.vagrant_box.path.clone())
|
.current_dir(self.vagrant_box.path.clone())
|
||||||
@@ -180,7 +179,7 @@ pub fn topgrade_vagrant_box(ctx: &ExecutionContext, vagrant_box: &VagrantBox) ->
|
|||||||
path: utils::require("vagrant")?,
|
path: utils::require("vagrant")?,
|
||||||
};
|
};
|
||||||
|
|
||||||
let seperator = format!("Vagrant ({})", vagrant_box.smart_name());
|
let separator = format!("Vagrant ({})", vagrant_box.smart_name());
|
||||||
let mut _poweron = None;
|
let mut _poweron = None;
|
||||||
if !vagrant_box.initial_status.powered_on() {
|
if !vagrant_box.initial_status.powered_on() {
|
||||||
if !(ctx.config().vagrant_power_on().unwrap_or(true)) {
|
if !(ctx.config().vagrant_power_on().unwrap_or(true)) {
|
||||||
@@ -190,19 +189,18 @@ pub fn topgrade_vagrant_box(ctx: &ExecutionContext, vagrant_box: &VagrantBox) ->
|
|||||||
))
|
))
|
||||||
.into());
|
.into());
|
||||||
} else {
|
} else {
|
||||||
print_separator(seperator);
|
print_separator(separator);
|
||||||
_poweron = Some(vagrant.temporary_power_on(vagrant_box, ctx)?);
|
_poweron = Some(vagrant.temporary_power_on(vagrant_box, ctx)?);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
print_separator(seperator);
|
print_separator(separator);
|
||||||
}
|
}
|
||||||
let mut command = format!("env TOPGRADE_PREFIX={} topgrade", vagrant_box.smart_name());
|
let mut command = format!("env TOPGRADE_PREFIX={} topgrade", vagrant_box.smart_name());
|
||||||
if ctx.config().yes(Step::Vagrant) {
|
if ctx.config().yes(Step::Vagrant) {
|
||||||
command.push_str(" -y");
|
command.push_str(" -y");
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.run_type()
|
ctx.execute(&vagrant.path)
|
||||||
.execute(&vagrant.path)
|
|
||||||
.current_dir(&vagrant_box.path)
|
.current_dir(&vagrant_box.path)
|
||||||
.args(["ssh", "-c", &command])
|
.args(["ssh", "-c", &command])
|
||||||
.status_checked()
|
.status_checked()
|
||||||
@@ -222,7 +220,6 @@ pub fn upgrade_vagrant_boxes(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
for ele in re.captures_iter(&outdated.stdout) {
|
for ele in re.captures_iter(&outdated.stdout) {
|
||||||
found = true;
|
found = true;
|
||||||
let _ = ctx
|
let _ = ctx
|
||||||
.run_type()
|
|
||||||
.execute(&vagrant)
|
.execute(&vagrant)
|
||||||
.args(["box", "update", "--box"])
|
.args(["box", "update", "--box"])
|
||||||
.arg(ele.get(1).unwrap().as_str())
|
.arg(ele.get(1).unwrap().as_str())
|
||||||
@@ -232,12 +229,9 @@ pub fn upgrade_vagrant_boxes(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !found {
|
if !found {
|
||||||
println!("{}", t!("No outdated boxes"))
|
println!("{}", t!("No outdated boxes"));
|
||||||
} else {
|
} else {
|
||||||
ctx.run_type()
|
ctx.execute(&vagrant).args(["box", "prune"]).status_checked()?;
|
||||||
.execute(&vagrant)
|
|
||||||
.args(["box", "prune"])
|
|
||||||
.status_checked()?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -5,34 +5,50 @@ use std::process::Command;
|
|||||||
use color_eyre::eyre::eyre;
|
use color_eyre::eyre::eyre;
|
||||||
use color_eyre::eyre::Context;
|
use color_eyre::eyre::Context;
|
||||||
use color_eyre::eyre::Result;
|
use color_eyre::eyre::Result;
|
||||||
|
use etcetera::base_strategy::BaseStrategy;
|
||||||
|
|
||||||
use crate::command::CommandExt;
|
use crate::command::CommandExt;
|
||||||
use crate::config::TmuxConfig;
|
use crate::config::TmuxConfig;
|
||||||
use crate::config::TmuxSessionMode;
|
use crate::config::TmuxSessionMode;
|
||||||
use crate::terminal::print_separator;
|
use crate::terminal::print_separator;
|
||||||
use crate::HOME_DIR;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
execution_context::ExecutionContext,
|
execution_context::ExecutionContext,
|
||||||
utils::{which, PathExt},
|
utils::{which, PathExt},
|
||||||
};
|
};
|
||||||
|
use crate::{HOME_DIR, XDG_DIRS};
|
||||||
|
|
||||||
use rust_i18n::t;
|
use rust_i18n::t;
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
use std::os::unix::process::CommandExt as _;
|
use std::os::unix::process::CommandExt as _;
|
||||||
|
|
||||||
|
// update_plugins path is relative to the TPM path
|
||||||
|
const UPDATE_PLUGINS: &str = "bin/update_plugins";
|
||||||
|
// Default TPM path relative to the TMux config directory
|
||||||
|
const TPM_PATH: &str = "plugins/tpm";
|
||||||
|
|
||||||
pub fn run_tpm(ctx: &ExecutionContext) -> Result<()> {
|
pub fn run_tpm(ctx: &ExecutionContext) -> Result<()> {
|
||||||
let tpm = match env::var("TMUX_PLUGIN_MANAGER_PATH") {
|
let tpm = match env::var("TMUX_PLUGIN_MANAGER_PATH") {
|
||||||
// If `TMUX_PLUGIN_MANAGER_PATH` is set, search for
|
// Use `$TMUX_PLUGIN_MANAGER_PATH` if set,
|
||||||
// `$TMUX_PLUGIN_MANAGER_PATH/bin/install_plugins/tpm/bin/update_plugins`
|
Ok(var) => PathBuf::from(var).join(UPDATE_PLUGINS),
|
||||||
Ok(var) => PathBuf::from(var).join("bin/install_plugins/tpm/bin/update_plugins"),
|
Err(_) => {
|
||||||
// Otherwise, use the default location `~/.tmux/plugins/tpm/bin/update_plugins`
|
// otherwise, use the default XDG location `~/.config/tmux`
|
||||||
Err(_) => HOME_DIR.join(".tmux/plugins/tpm/bin/update_plugins"),
|
#[cfg(unix)]
|
||||||
|
let xdg_path = XDG_DIRS.config_dir().join("tmux").join(TPM_PATH).join(UPDATE_PLUGINS);
|
||||||
|
#[cfg(windows)]
|
||||||
|
let xdg_path = HOME_DIR.join(".config/tmux").join(TPM_PATH).join(UPDATE_PLUGINS);
|
||||||
|
if xdg_path.exists() {
|
||||||
|
xdg_path
|
||||||
|
} else {
|
||||||
|
// or fallback on the standard default location `~/.tmux`.
|
||||||
|
HOME_DIR.join(".tmux").join(TPM_PATH).join(UPDATE_PLUGINS)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.require()?;
|
.require()?;
|
||||||
|
|
||||||
print_separator("tmux plugins");
|
print_separator("tmux plugins");
|
||||||
|
|
||||||
ctx.run_type().execute(tpm).arg("all").status_checked()
|
ctx.execute(tpm).arg("all").status_checked()
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Tmux {
|
struct Tmux {
|
||||||
@@ -128,7 +144,7 @@ impl Tmux {
|
|||||||
.output_checked_utf8()?
|
.output_checked_utf8()?
|
||||||
.stdout
|
.stdout
|
||||||
.lines()
|
.lines()
|
||||||
.map(|l| l.parse())
|
.map(str::parse)
|
||||||
.collect::<Result<Vec<usize>, _>>()
|
.collect::<Result<Vec<usize>, _>>()
|
||||||
.context("Failed to compute tmux windows")
|
.context("Failed to compute tmux windows")
|
||||||
}
|
}
|
||||||
@@ -162,7 +178,7 @@ pub fn run_in_tmux(config: TmuxConfig) -> Result<()> {
|
|||||||
println!("{}", t!("Topgrade launched in a new tmux session"));
|
println!("{}", t!("Topgrade launched in a new tmux session"));
|
||||||
return Ok(());
|
return Ok(());
|
||||||
} else {
|
} else {
|
||||||
tmux.build().args(["attach-client", "-t", &session]).exec()
|
tmux.build().args(["attach-session", "-t", &session]).exec()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,7 +186,7 @@ pub fn run_in_tmux(config: TmuxConfig) -> Result<()> {
|
|||||||
if is_inside_tmux {
|
if is_inside_tmux {
|
||||||
tmux.build().args(["switch-client", "-t", &session]).exec()
|
tmux.build().args(["switch-client", "-t", &session]).exec()
|
||||||
} else {
|
} else {
|
||||||
tmux.build().args(["attach-client", "-t", &session]).exec()
|
tmux.build().args(["attach-session", "-t", &session]).exec()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -181,19 +197,16 @@ pub fn run_in_tmux(config: TmuxConfig) -> Result<()> {
|
|||||||
pub fn run_command(ctx: &ExecutionContext, window_name: &str, command: &str) -> Result<()> {
|
pub fn run_command(ctx: &ExecutionContext, window_name: &str, command: &str) -> Result<()> {
|
||||||
let tmux = Tmux::new(ctx.config().tmux_config()?.args);
|
let tmux = Tmux::new(ctx.config().tmux_config()?.args);
|
||||||
|
|
||||||
match ctx.get_tmux_session() {
|
if let Some(session_name) = ctx.get_tmux_session() {
|
||||||
Some(session_name) => {
|
let indices = tmux.window_indices(&session_name)?;
|
||||||
let indices = tmux.window_indices(&session_name)?;
|
let last_window = indices
|
||||||
let last_window = indices
|
.iter()
|
||||||
.iter()
|
.last()
|
||||||
.last()
|
.ok_or_else(|| eyre!("tmux session {session_name} has no windows"))?;
|
||||||
.ok_or_else(|| eyre!("tmux session {session_name} has no windows"))?;
|
tmux.new_window(&session_name, &format!("{last_window}"), command)?;
|
||||||
tmux.new_window(&session_name, &format!("{last_window}"), command)?;
|
} else {
|
||||||
}
|
let name = tmux.new_unique_session("topgrade", window_name, command)?;
|
||||||
None => {
|
ctx.set_tmux_session(name);
|
||||||
let name = tmux.new_unique_session("topgrade", window_name, command)?;
|
|
||||||
ctx.set_tmux_session(name);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use color_eyre::eyre::Result;
|
use color_eyre::eyre::Result;
|
||||||
|
|
||||||
use crate::command::CommandExt;
|
use crate::command::CommandExt;
|
||||||
use crate::config::Step;
|
use crate::step::Step;
|
||||||
use crate::terminal::print_separator;
|
use crate::terminal::print_separator;
|
||||||
use crate::{execution_context::ExecutionContext, utils::require};
|
use crate::{execution_context::ExecutionContext, utils::require};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
@@ -59,7 +59,7 @@ pub fn run_toolbx(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
args.push("--yes");
|
args.push("--yes");
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.run_type().execute(&toolbx).args(&args).status_checked()?;
|
ctx.execute(&toolbx).args(&args).status_checked()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@@ -29,15 +29,32 @@ pub fn vimrc() -> Result<PathBuf> {
|
|||||||
|
|
||||||
fn nvimrc() -> Result<PathBuf> {
|
fn nvimrc() -> Result<PathBuf> {
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
let base_dir = crate::XDG_DIRS.config_dir();
|
let bases: Vec<PathBuf> = vec![crate::XDG_DIRS.config_dir()];
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
let base_dir = crate::WINDOWS_DIRS.cache_dir();
|
let mut bases: Vec<PathBuf> = vec![crate::WINDOWS_DIRS.cache_dir()];
|
||||||
|
|
||||||
base_dir
|
#[cfg(windows)]
|
||||||
.join("nvim/init.vim")
|
{
|
||||||
.require()
|
if let Some(xdg) = std::env::var_os("XDG_CONFIG_HOME")
|
||||||
.or_else(|_| base_dir.join("nvim/init.lua").require())
|
.map(PathBuf::from)
|
||||||
|
.filter(|path| path.is_absolute())
|
||||||
|
{
|
||||||
|
bases.insert(0, xdg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for base_dir in bases {
|
||||||
|
if let Ok(p) = base_dir
|
||||||
|
.join("nvim/init.vim")
|
||||||
|
.require()
|
||||||
|
.or_else(|_| base_dir.join("nvim/init.lua").require())
|
||||||
|
{
|
||||||
|
return Ok(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(SkipStep(format!("{}", t!("No Neovim config found"))).into())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn upgrade_script() -> Result<tempfile::NamedTempFile> {
|
fn upgrade_script() -> Result<tempfile::NamedTempFile> {
|
||||||
@@ -65,7 +82,7 @@ fn upgrade(command: &mut Executor, ctx: &ExecutionContext) -> Result<()> {
|
|||||||
if !status.success() {
|
if !status.success() {
|
||||||
return Err(TopgradeError::ProcessFailed(command.get_program(), status).into());
|
return Err(TopgradeError::ProcessFailed(command.get_program(), status).into());
|
||||||
} else {
|
} else {
|
||||||
println!("{}", t!("Plugins upgraded"))
|
println!("{}", t!("Plugins upgraded"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,23 +97,19 @@ pub fn upgrade_ultimate_vimrc(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
|
|
||||||
print_separator(t!("The Ultimate vimrc"));
|
print_separator(t!("The Ultimate vimrc"));
|
||||||
|
|
||||||
ctx.run_type()
|
ctx.execute(&git)
|
||||||
.execute(&git)
|
|
||||||
.current_dir(&config_dir)
|
.current_dir(&config_dir)
|
||||||
.args(["reset", "--hard"])
|
.args(["reset", "--hard"])
|
||||||
.status_checked()?;
|
.status_checked()?;
|
||||||
ctx.run_type()
|
ctx.execute(&git)
|
||||||
.execute(&git)
|
|
||||||
.current_dir(&config_dir)
|
.current_dir(&config_dir)
|
||||||
.args(["clean", "-d", "--force"])
|
.args(["clean", "-d", "--force"])
|
||||||
.status_checked()?;
|
.status_checked()?;
|
||||||
ctx.run_type()
|
ctx.execute(&git)
|
||||||
.execute(&git)
|
|
||||||
.current_dir(&config_dir)
|
.current_dir(&config_dir)
|
||||||
.args(["pull", "--rebase"])
|
.args(["pull", "--rebase"])
|
||||||
.status_checked()?;
|
.status_checked()?;
|
||||||
ctx.run_type()
|
ctx.execute(python)
|
||||||
.execute(python)
|
|
||||||
.current_dir(config_dir)
|
.current_dir(config_dir)
|
||||||
.arg(update_plugins)
|
.arg(update_plugins)
|
||||||
.status_checked()?;
|
.status_checked()?;
|
||||||
@@ -116,8 +129,7 @@ pub fn upgrade_vim(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
|
|
||||||
print_separator("Vim");
|
print_separator("Vim");
|
||||||
upgrade(
|
upgrade(
|
||||||
ctx.run_type()
|
ctx.execute(&vim)
|
||||||
.execute(&vim)
|
|
||||||
.args(["-u"])
|
.args(["-u"])
|
||||||
.arg(vimrc)
|
.arg(vimrc)
|
||||||
.args(["-U", "NONE", "-V1", "-nNesS"])
|
.args(["-U", "NONE", "-V1", "-nNesS"])
|
||||||
@@ -132,8 +144,7 @@ pub fn upgrade_neovim(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
|
|
||||||
print_separator("Neovim");
|
print_separator("Neovim");
|
||||||
upgrade(
|
upgrade(
|
||||||
ctx.run_type()
|
ctx.execute(nvim)
|
||||||
.execute(nvim)
|
|
||||||
.args(["-u"])
|
.args(["-u"])
|
||||||
.arg(nvimrc)
|
.arg(nvimrc)
|
||||||
.args(["--headless", "-V1", "-nS"])
|
.args(["--headless", "-V1", "-nS"])
|
||||||
@@ -147,5 +158,5 @@ pub fn run_voom(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
|
|
||||||
print_separator("voom");
|
print_separator("voom");
|
||||||
|
|
||||||
ctx.run_type().execute(voom).arg("update").status_checked()
|
ctx.execute(voom).arg("update").status_checked()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,16 +23,11 @@ pub fn run_zr(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
print_separator("zr");
|
print_separator("zr");
|
||||||
|
|
||||||
let cmd = format!("source {} && zr --update", zshrc().display());
|
let cmd = format!("source {} && zr --update", zshrc().display());
|
||||||
ctx.run_type()
|
ctx.execute(zsh).args(["-l", "-c", cmd.as_str()]).status_checked()
|
||||||
.execute(zsh)
|
|
||||||
.args(["-l", "-c", cmd.as_str()])
|
|
||||||
.status_checked()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn zdotdir() -> PathBuf {
|
fn zdotdir() -> PathBuf {
|
||||||
env::var("ZDOTDIR")
|
env::var("ZDOTDIR").map_or_else(|_| HOME_DIR.clone(), PathBuf::from)
|
||||||
.map(PathBuf::from)
|
|
||||||
.unwrap_or_else(|_| HOME_DIR.clone())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn zshrc() -> PathBuf {
|
pub fn zshrc() -> PathBuf {
|
||||||
@@ -46,8 +41,7 @@ pub fn run_antidote(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
|
|
||||||
print_separator("antidote");
|
print_separator("antidote");
|
||||||
|
|
||||||
ctx.run_type()
|
ctx.execute(zsh)
|
||||||
.execute(zsh)
|
|
||||||
.arg("-c")
|
.arg("-c")
|
||||||
.arg(format!("source {} && antidote update", antidote.display()))
|
.arg(format!("source {} && antidote update", antidote.display()))
|
||||||
.status_checked()
|
.status_checked()
|
||||||
@@ -59,41 +53,33 @@ pub fn run_antibody(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
|
|
||||||
print_separator("antibody");
|
print_separator("antibody");
|
||||||
|
|
||||||
ctx.run_type().execute(antibody).arg("update").status_checked()
|
ctx.execute(antibody).arg("update").status_checked()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_antigen(ctx: &ExecutionContext) -> Result<()> {
|
pub fn run_antigen(ctx: &ExecutionContext) -> Result<()> {
|
||||||
let zsh = require("zsh")?;
|
let zsh = require("zsh")?;
|
||||||
let zshrc = zshrc().require()?;
|
let zshrc = zshrc().require()?;
|
||||||
env::var("ADOTDIR")
|
env::var("ADOTDIR")
|
||||||
.map(PathBuf::from)
|
.map_or_else(|_| HOME_DIR.join("antigen.zsh"), PathBuf::from)
|
||||||
.unwrap_or_else(|_| HOME_DIR.join("antigen.zsh"))
|
|
||||||
.require()?;
|
.require()?;
|
||||||
|
|
||||||
print_separator("antigen");
|
print_separator("antigen");
|
||||||
|
|
||||||
let cmd = format!("source {} && (antigen selfupdate ; antigen update)", zshrc.display());
|
let cmd = format!("source {} && (antigen selfupdate ; antigen update)", zshrc.display());
|
||||||
ctx.run_type()
|
ctx.execute(zsh).args(["-l", "-c", cmd.as_str()]).status_checked()
|
||||||
.execute(zsh)
|
|
||||||
.args(["-l", "-c", cmd.as_str()])
|
|
||||||
.status_checked()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_zgenom(ctx: &ExecutionContext) -> Result<()> {
|
pub fn run_zgenom(ctx: &ExecutionContext) -> Result<()> {
|
||||||
let zsh = require("zsh")?;
|
let zsh = require("zsh")?;
|
||||||
let zshrc = zshrc().require()?;
|
let zshrc = zshrc().require()?;
|
||||||
env::var("ZGEN_SOURCE")
|
env::var("ZGEN_SOURCE")
|
||||||
.map(PathBuf::from)
|
.map_or_else(|_| HOME_DIR.join(".zgenom"), PathBuf::from)
|
||||||
.unwrap_or_else(|_| HOME_DIR.join(".zgenom"))
|
|
||||||
.require()?;
|
.require()?;
|
||||||
|
|
||||||
print_separator("zgenom");
|
print_separator("zgenom");
|
||||||
|
|
||||||
let cmd = format!("source {} && zgenom selfupdate && zgenom update", zshrc.display());
|
let cmd = format!("source {} && zgenom selfupdate && zgenom update", zshrc.display());
|
||||||
ctx.run_type()
|
ctx.execute(zsh).args(["-l", "-c", cmd.as_str()]).status_checked()
|
||||||
.execute(zsh)
|
|
||||||
.args(["-l", "-c", cmd.as_str()])
|
|
||||||
.status_checked()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_zplug(ctx: &ExecutionContext) -> Result<()> {
|
pub fn run_zplug(ctx: &ExecutionContext) -> Result<()> {
|
||||||
@@ -101,16 +87,12 @@ pub fn run_zplug(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
zshrc().require()?;
|
zshrc().require()?;
|
||||||
|
|
||||||
env::var("ZPLUG_HOME")
|
env::var("ZPLUG_HOME")
|
||||||
.map(PathBuf::from)
|
.map_or_else(|_| HOME_DIR.join(".zplug"), PathBuf::from)
|
||||||
.unwrap_or_else(|_| HOME_DIR.join(".zplug"))
|
|
||||||
.require()?;
|
.require()?;
|
||||||
|
|
||||||
print_separator("zplug");
|
print_separator("zplug");
|
||||||
|
|
||||||
ctx.run_type()
|
ctx.execute(zsh).args(["-i", "-c", "zplug update"]).status_checked()
|
||||||
.execute(zsh)
|
|
||||||
.args(["-i", "-c", "zplug update"])
|
|
||||||
.status_checked()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_zinit(ctx: &ExecutionContext) -> Result<()> {
|
pub fn run_zinit(ctx: &ExecutionContext) -> Result<()> {
|
||||||
@@ -118,17 +100,13 @@ pub fn run_zinit(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
let zshrc = zshrc().require()?;
|
let zshrc = zshrc().require()?;
|
||||||
|
|
||||||
env::var("ZINIT_HOME")
|
env::var("ZINIT_HOME")
|
||||||
.map(PathBuf::from)
|
.map_or_else(|_| XDG_DIRS.data_dir().join("zinit"), PathBuf::from)
|
||||||
.unwrap_or_else(|_| XDG_DIRS.data_dir().join("zinit"))
|
|
||||||
.require()?;
|
.require()?;
|
||||||
|
|
||||||
print_separator("zinit");
|
print_separator("zinit");
|
||||||
|
|
||||||
let cmd = format!("source {} && zinit self-update && zinit update --all", zshrc.display());
|
let cmd = format!("source {} && zinit self-update && zinit update --all", zshrc.display());
|
||||||
ctx.run_type()
|
ctx.execute(zsh).args(["-i", "-c", cmd.as_str()]).status_checked()
|
||||||
.execute(zsh)
|
|
||||||
.args(["-i", "-c", cmd.as_str()])
|
|
||||||
.status_checked()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_zi(ctx: &ExecutionContext) -> Result<()> {
|
pub fn run_zi(ctx: &ExecutionContext) -> Result<()> {
|
||||||
@@ -140,7 +118,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", zshrc.display());
|
||||||
ctx.run_type().execute(zsh).args(["-i", "-c", &cmd]).status_checked()
|
ctx.execute(zsh).args(["-i", "-c", &cmd]).status_checked()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_zim(ctx: &ExecutionContext) -> Result<()> {
|
pub fn run_zim(ctx: &ExecutionContext) -> Result<()> {
|
||||||
@@ -153,14 +131,12 @@ pub fn run_zim(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
.output_checked_utf8()
|
.output_checked_utf8()
|
||||||
.map(|o| o.stdout)
|
.map(|o| o.stdout)
|
||||||
})
|
})
|
||||||
.map(PathBuf::from)
|
.map_or_else(|_| HOME_DIR.join(".zim"), PathBuf::from)
|
||||||
.unwrap_or_else(|_| HOME_DIR.join(".zim"))
|
|
||||||
.require()?;
|
.require()?;
|
||||||
|
|
||||||
print_separator("zim");
|
print_separator("zim");
|
||||||
|
|
||||||
ctx.run_type()
|
ctx.execute(zsh)
|
||||||
.execute(zsh)
|
|
||||||
.args(["-i", "-c", "zimfw upgrade && zimfw update"])
|
.args(["-i", "-c", "zimfw upgrade && zimfw update"])
|
||||||
.status_checked()
|
.status_checked()
|
||||||
}
|
}
|
||||||
@@ -226,8 +202,7 @@ pub fn run_oh_my_zsh(ctx: &ExecutionContext) -> Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
custom_repos.remove(&oh_my_zsh);
|
custom_repos.remove(&oh_my_zsh);
|
||||||
ctx.run_type()
|
ctx.execute("zsh")
|
||||||
.execute("zsh")
|
|
||||||
.arg(oh_my_zsh.join("tools/upgrade.sh"))
|
.arg(oh_my_zsh.join("tools/upgrade.sh"))
|
||||||
// oh-my-zsh returns 80 when it is already updated and no changes pulled
|
// oh-my-zsh returns 80 when it is already updated and no changes pulled
|
||||||
// in this update.
|
// in this update.
|
||||||
|
|||||||
495
src/sudo.rs
495
src/sudo.rs
@@ -2,12 +2,23 @@ use std::ffi::OsStr;
|
|||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
use color_eyre::eyre;
|
||||||
|
#[cfg(windows)]
|
||||||
|
use color_eyre::eyre::eyre;
|
||||||
use color_eyre::eyre::Context;
|
use color_eyre::eyre::Context;
|
||||||
use color_eyre::eyre::Result;
|
use color_eyre::eyre::Result;
|
||||||
|
use rust_i18n::t;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use strum::AsRefStr;
|
use strum::Display;
|
||||||
|
use thiserror::Error;
|
||||||
|
#[cfg(windows)]
|
||||||
|
use tracing::{debug, warn};
|
||||||
|
#[cfg(windows)]
|
||||||
|
use windows::Win32::Foundation::ERROR_FILE_NOT_FOUND;
|
||||||
|
|
||||||
use crate::command::CommandExt;
|
use crate::command::CommandExt;
|
||||||
|
use crate::error::UnsupportedSudo;
|
||||||
use crate::execution_context::ExecutionContext;
|
use crate::execution_context::ExecutionContext;
|
||||||
use crate::executor::Executor;
|
use crate::executor::Executor;
|
||||||
use crate::terminal::print_separator;
|
use crate::terminal::print_separator;
|
||||||
@@ -16,26 +27,241 @@ use crate::utils::which;
|
|||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Sudo {
|
pub struct Sudo {
|
||||||
/// The path to the `sudo` binary.
|
/// The path to the `sudo` binary.
|
||||||
path: PathBuf,
|
path: Option<PathBuf>,
|
||||||
/// The type of program being used as `sudo`.
|
/// The type of program being used as `sudo`.
|
||||||
kind: SudoKind,
|
kind: SudoKind,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum SudoCreateError {
|
||||||
|
CannotFindBinary,
|
||||||
|
#[cfg(windows)]
|
||||||
|
WinSudoDisabled,
|
||||||
|
#[cfg(windows)]
|
||||||
|
WinSudoNewWindowMode,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for SudoCreateError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
SudoCreateError::CannotFindBinary => {
|
||||||
|
write!(f, "{}", t!("Cannot find sudo binary"))
|
||||||
|
}
|
||||||
|
#[cfg(windows)]
|
||||||
|
SudoCreateError::WinSudoDisabled => {
|
||||||
|
write!(f, "{}", t!("Found Windows Sudo, but it is disabled"))
|
||||||
|
}
|
||||||
|
#[cfg(windows)]
|
||||||
|
SudoCreateError::WinSudoNewWindowMode => {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}",
|
||||||
|
t!("Found Windows Sudo, but it is using 'In a new window' mode")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
pub enum SudoPreserveEnv<'a> {
|
||||||
|
/// Preserve all environment variables.
|
||||||
|
All,
|
||||||
|
/// Preserve only the specified environment variables.
|
||||||
|
Some(&'a [&'a str]),
|
||||||
|
/// Preserve no environment variables.
|
||||||
|
#[default]
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generic sudo options, translated into flags to pass to `sudo`.
|
||||||
|
/// NOTE: Depending on the sudo kind, OS and system config, some options might be specified by
|
||||||
|
/// default or unsupported.
|
||||||
|
#[derive(Clone, Debug, Default)]
|
||||||
|
pub struct SudoExecuteOpts<'a> {
|
||||||
|
/// Run the command inside a login shell.
|
||||||
|
pub login_shell: bool,
|
||||||
|
/// Preserve environment variables across the sudo call.
|
||||||
|
pub preserve_env: SudoPreserveEnv<'a>,
|
||||||
|
/// Set the HOME environment variable to the target user's home directory.
|
||||||
|
pub set_home: bool,
|
||||||
|
/// Run the command as a user other than the root user.
|
||||||
|
pub user: Option<&'a str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> SudoExecuteOpts<'a> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Run the command inside a login shell.
|
||||||
|
#[allow(unused)]
|
||||||
|
pub fn login_shell(mut self) -> Self {
|
||||||
|
self.login_shell = true;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Preserve all environment variables across the sudo call.
|
||||||
|
#[allow(unused)]
|
||||||
|
pub fn preserve_env(mut self) -> Self {
|
||||||
|
self.preserve_env = SudoPreserveEnv::All;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Preserve only the specified environment variables across the sudo call.
|
||||||
|
#[allow(unused)]
|
||||||
|
pub fn preserve_env_list(mut self, vars: &'a [&'a str]) -> Self {
|
||||||
|
self.preserve_env = SudoPreserveEnv::Some(vars);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the HOME environment variable to the target user's home directory.
|
||||||
|
#[allow(unused)]
|
||||||
|
pub fn set_home(mut self) -> Self {
|
||||||
|
self.set_home = true;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Run the command as a user other than the root user.
|
||||||
|
#[allow(unused)]
|
||||||
|
pub fn user(mut self, user: &'a str) -> Self {
|
||||||
|
self.user = Some(user);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
const DETECT_ORDER: [SudoKind; 5] = [
|
||||||
|
SudoKind::Doas,
|
||||||
|
SudoKind::Sudo,
|
||||||
|
SudoKind::Pkexec,
|
||||||
|
SudoKind::Run0,
|
||||||
|
SudoKind::Please,
|
||||||
|
];
|
||||||
|
|
||||||
|
// NOTE: keep WinSudo last, allows short-circuit error return in Sudo::detect() to work
|
||||||
|
#[cfg(windows)]
|
||||||
|
const DETECT_ORDER: [SudoKind; 2] = [SudoKind::Gsudo, SudoKind::WinSudo];
|
||||||
|
|
||||||
impl Sudo {
|
impl Sudo {
|
||||||
/// Get the `sudo` binary for this platform.
|
/// Get the `sudo` binary for this platform.
|
||||||
pub fn detect() -> Option<Self> {
|
pub fn detect() -> Result<Self, SudoCreateError> {
|
||||||
which("doas")
|
use SudoCreateError::*;
|
||||||
.map(|p| (p, SudoKind::Doas))
|
|
||||||
.or_else(|| which("sudo").map(|p| (p, SudoKind::Sudo)))
|
for kind in DETECT_ORDER {
|
||||||
.or_else(|| which("gsudo").map(|p| (p, SudoKind::Gsudo)))
|
match Self::new(kind) {
|
||||||
.or_else(|| which("pkexec").map(|p| (p, SudoKind::Pkexec)))
|
Ok(sudo) => return Ok(sudo),
|
||||||
.or_else(|| which("please").map(|p| (p, SudoKind::Please)))
|
Err(CannotFindBinary) => continue,
|
||||||
.map(|(path, kind)| Self { path, kind })
|
#[cfg(windows)]
|
||||||
|
Err(e @ (WinSudoDisabled | WinSudoNewWindowMode)) => {
|
||||||
|
// we can return directly here since WinSudo is detected last
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(CannotFindBinary)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create Sudo from SudoKind, if found in the system
|
/// Create Sudo from SudoKind, if found in the system
|
||||||
pub fn new(kind: SudoKind) -> Option<Self> {
|
pub fn new(kind: SudoKind) -> Result<Self, SudoCreateError> {
|
||||||
which(kind.as_ref()).map(|path| Self { path, kind })
|
// no actual binary for null sudo
|
||||||
|
if let SudoKind::Null = kind {
|
||||||
|
return Ok(Self { path: None, kind });
|
||||||
|
}
|
||||||
|
|
||||||
|
match kind.which() {
|
||||||
|
Some(path) => {
|
||||||
|
let sudo = Self { path: Some(path), kind };
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
if let SudoKind::WinSudo = kind {
|
||||||
|
// Windows Sudo might be disabled, causing it to error on use.
|
||||||
|
//
|
||||||
|
// It checks two registry keys to determine its mode:
|
||||||
|
// a "policy" (HLKM\SOFTWARE\Policies\Microsoft\Windows\Sudo\Enabled)
|
||||||
|
// and a "setting" (HLKM\SOFTWARE\Microsoft\Windows\CurrentVersion\Sudo\Enabled).
|
||||||
|
//
|
||||||
|
// Both keys are u32's, with these meanings:
|
||||||
|
// 0 - Disabled
|
||||||
|
// 1 - ForceNewWindow
|
||||||
|
// 2 - DisableInput
|
||||||
|
// 3 - Normal
|
||||||
|
//
|
||||||
|
// Setting the sudo option in Settings changes the setting key, the policy key
|
||||||
|
// sets an upper limit on the setting key: mode = min(policy, setting).
|
||||||
|
// The default for the policy key is 3 (all modes allowed), and the default for
|
||||||
|
// the setting key is 0 (disabled).
|
||||||
|
//
|
||||||
|
// See https://github.com/microsoft/sudo/blob/9f50d79704a9d4d468bc59f725993714762981ca/sudo/src/helpers.rs#L442
|
||||||
|
|
||||||
|
#[derive(Debug, Eq, Ord, PartialEq, PartialOrd)]
|
||||||
|
enum SudoMode {
|
||||||
|
Disabled = 0,
|
||||||
|
ForceNewWindow = 1,
|
||||||
|
DisableInput = 2,
|
||||||
|
Normal = 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<u32> for SudoMode {
|
||||||
|
type Error = eyre::Error;
|
||||||
|
|
||||||
|
fn try_from(value: u32) -> Result<Self> {
|
||||||
|
match value {
|
||||||
|
0 => Ok(SudoMode::Disabled),
|
||||||
|
1 => Ok(SudoMode::ForceNewWindow),
|
||||||
|
2 => Ok(SudoMode::DisableInput),
|
||||||
|
3 => Ok(SudoMode::Normal),
|
||||||
|
_ => Err(eyre!("invalid integer SudoMode: {value}")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_mode(key: &str, on_missing: SudoMode) -> SudoMode {
|
||||||
|
match windows_registry::LOCAL_MACHINE
|
||||||
|
.open(key)
|
||||||
|
.and_then(|k| k.get_u32("Enabled"))
|
||||||
|
{
|
||||||
|
Ok(v) => v.min(3).try_into().unwrap(),
|
||||||
|
Err(e) if e.code() == ERROR_FILE_NOT_FOUND.to_hresult() => on_missing,
|
||||||
|
Err(e) => {
|
||||||
|
// warn, but treat as normal (using sudo should error)
|
||||||
|
warn!(r"Error reading registry key HKLM\{key}\Enabled: {e}");
|
||||||
|
SudoMode::Normal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// default to normal if key missing
|
||||||
|
let policy_mode = get_mode(r"SOFTWARE\Policies\Microsoft\Windows\Sudo", SudoMode::Normal);
|
||||||
|
debug!("Windows Sudo policy mode: {policy_mode:?}");
|
||||||
|
// default to disabled if key missing
|
||||||
|
let setting_mode = get_mode(r"SOFTWARE\Microsoft\Windows\CurrentVersion\Sudo", SudoMode::Disabled);
|
||||||
|
debug!("Windows Sudo setting mode: {setting_mode:?}");
|
||||||
|
|
||||||
|
let sudo_mode = policy_mode.min(setting_mode);
|
||||||
|
debug!("Windows Sudo mode: {sudo_mode:?}");
|
||||||
|
|
||||||
|
if sudo_mode == SudoMode::Disabled {
|
||||||
|
return Err(SudoCreateError::WinSudoDisabled);
|
||||||
|
} else if sudo_mode == SudoMode::ForceNewWindow {
|
||||||
|
return Err(SudoCreateError::WinSudoNewWindowMode);
|
||||||
|
}
|
||||||
|
// Normal mode is best, but DisableInput doesn't seem to cause issues
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(sudo)
|
||||||
|
}
|
||||||
|
None => Err(SudoCreateError::CannotFindBinary),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the path to the `sudo` binary. Do not use this to execute `sudo` directly - either use
|
||||||
|
/// [`Sudo::elevate`], or if you need to specify arguments to `sudo`, use [`Sudo::elevate_opts`].
|
||||||
|
/// This way, sudo options can be specified generically and the actual arguments customized
|
||||||
|
/// depending on the sudo kind.
|
||||||
|
#[allow(unused)]
|
||||||
|
pub fn path(&self) -> Option<&Path> {
|
||||||
|
self.path.as_deref()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Elevate permissions with `sudo`.
|
/// Elevate permissions with `sudo`.
|
||||||
@@ -45,8 +271,15 @@ impl Sudo {
|
|||||||
///
|
///
|
||||||
/// See: https://github.com/topgrade-rs/topgrade/issues/205
|
/// See: https://github.com/topgrade-rs/topgrade/issues/205
|
||||||
pub fn elevate(&self, ctx: &ExecutionContext) -> Result<()> {
|
pub fn elevate(&self, ctx: &ExecutionContext) -> Result<()> {
|
||||||
|
// skip if using null sudo
|
||||||
|
if let SudoKind::Null = self.kind {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
print_separator("Sudo");
|
print_separator("Sudo");
|
||||||
let mut cmd = ctx.run_type().execute(self);
|
|
||||||
|
// self.path is only None for null sudo, which we've handled above
|
||||||
|
let mut cmd = ctx.execute(self.path.as_deref().unwrap());
|
||||||
match self.kind {
|
match self.kind {
|
||||||
SudoKind::Doas => {
|
SudoKind::Doas => {
|
||||||
// `doas` doesn't have anything like `sudo -v` to cache credentials,
|
// `doas` doesn't have anything like `sudo -v` to cache credentials,
|
||||||
@@ -64,10 +297,19 @@ impl Sudo {
|
|||||||
// command. Not all security policies support cached credentials.
|
// command. Not all security policies support cached credentials.
|
||||||
cmd.arg("-v");
|
cmd.arg("-v");
|
||||||
}
|
}
|
||||||
|
SudoKind::WinSudo => {
|
||||||
|
// Windows `sudo` doesn't cache credentials, so we just execute a
|
||||||
|
// dummy command - the easiest on Windows is `rem` in cmd.
|
||||||
|
// See: https://learn.microsoft.com/en-us/windows/advanced-settings/sudo/
|
||||||
|
cmd.args(["cmd.exe", "/c", "rem"]);
|
||||||
|
}
|
||||||
SudoKind::Gsudo => {
|
SudoKind::Gsudo => {
|
||||||
// Shows current user, cache and console status.
|
// `gsudo` doesn't have anything like `sudo -v` to cache credentials,
|
||||||
|
// so we just execute a dummy command - the easiest on Windows is
|
||||||
|
// `rem` in cmd. `-d` tells it to run the command directly, without
|
||||||
|
// going through a shell (which could be powershell) first.
|
||||||
// See: https://gerardog.github.io/gsudo/docs/usage
|
// See: https://gerardog.github.io/gsudo/docs/usage
|
||||||
cmd.arg("status");
|
cmd.args(["-d", "cmd.exe", "/c", "rem"]);
|
||||||
}
|
}
|
||||||
SudoKind::Pkexec => {
|
SudoKind::Pkexec => {
|
||||||
// I don't think this does anything; `pkexec` usually asks for
|
// I don't think this does anything; `pkexec` usually asks for
|
||||||
@@ -79,47 +321,246 @@ 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::Run0 => {
|
||||||
|
// `run0` uses polkit for authentication
|
||||||
|
// and thus has the same issues as `pkexec`.
|
||||||
|
//
|
||||||
|
// See: https://www.freedesktop.org/software/systemd/man/devel/run0.html
|
||||||
|
cmd.arg("echo");
|
||||||
|
}
|
||||||
SudoKind::Please => {
|
SudoKind::Please => {
|
||||||
// From `man please`
|
// From `man please`
|
||||||
// -w, --warm
|
// -w, --warm
|
||||||
// Warm the access token and exit.
|
// Warm the access token and exit.
|
||||||
cmd.arg("-w");
|
cmd.arg("-w");
|
||||||
}
|
}
|
||||||
|
SudoKind::Null => unreachable!(),
|
||||||
}
|
}
|
||||||
cmd.status_checked().wrap_err("Failed to elevate permissions")
|
cmd.status_checked().wrap_err("Failed to elevate permissions")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Execute a command with `sudo`.
|
/// Execute a command with `sudo`.
|
||||||
pub fn execute_elevated(&self, ctx: &ExecutionContext, command: &Path, interactive: bool) -> Executor {
|
pub fn execute<S: AsRef<OsStr>>(&self, ctx: &ExecutionContext, command: S) -> Result<Executor> {
|
||||||
let mut cmd = ctx.run_type().execute(self);
|
self.execute_opts(ctx, command, SudoExecuteOpts::new())
|
||||||
|
}
|
||||||
|
|
||||||
if let SudoKind::Sudo = self.kind {
|
/// Execute a command with `sudo`, with custom options.
|
||||||
cmd.arg("--preserve-env=DIFFPROG");
|
pub fn execute_opts<S: AsRef<OsStr>>(
|
||||||
|
&self,
|
||||||
|
ctx: &ExecutionContext,
|
||||||
|
command: S,
|
||||||
|
opts: SudoExecuteOpts,
|
||||||
|
) -> Result<Executor> {
|
||||||
|
// null sudo is very different, do separately
|
||||||
|
if let SudoKind::Null = self.kind {
|
||||||
|
if opts.login_shell {
|
||||||
|
// TODO: emulate running in a login shell with su/runuser
|
||||||
|
return Err(UnsupportedSudo {
|
||||||
|
sudo_kind: self.kind,
|
||||||
|
option: "login_shell",
|
||||||
|
}
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
if opts.user.is_some() {
|
||||||
|
// TODO: emulate running as a different user with su/runuser
|
||||||
|
return Err(UnsupportedSudo {
|
||||||
|
sudo_kind: self.kind,
|
||||||
|
option: "user",
|
||||||
|
}
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: we ignore preserve_env and set_home, using
|
||||||
|
// no sudo effectively preserves these by default
|
||||||
|
|
||||||
|
// run command directly
|
||||||
|
return Ok(ctx.execute(command));
|
||||||
}
|
}
|
||||||
|
|
||||||
if interactive {
|
// self.path is only None for null sudo, which we've handled above
|
||||||
cmd.arg("-i");
|
let mut cmd = ctx.execute(self.path.as_ref().unwrap());
|
||||||
|
|
||||||
|
if opts.login_shell {
|
||||||
|
match self.kind {
|
||||||
|
SudoKind::Sudo => {
|
||||||
|
cmd.arg("-i");
|
||||||
|
}
|
||||||
|
SudoKind::Gsudo => {
|
||||||
|
// By default, gsudo runs all commands inside a shell. If login_shell
|
||||||
|
// is *not* specified, we add `-d` to run outside of a shell - see below.
|
||||||
|
}
|
||||||
|
SudoKind::Doas | SudoKind::WinSudo | SudoKind::Pkexec | SudoKind::Run0 | SudoKind::Please => {
|
||||||
|
return Err(UnsupportedSudo {
|
||||||
|
sudo_kind: self.kind,
|
||||||
|
option: "login_shell",
|
||||||
|
}
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
SudoKind::Null => unreachable!(),
|
||||||
|
}
|
||||||
|
} else if let SudoKind::Gsudo = self.kind {
|
||||||
|
// The `-d` (direct) flag disables shell detection, running the command directly
|
||||||
|
// rather than through the current shell.
|
||||||
|
// Additionally, if the current shell is pwsh >= 7.3.0, then not including this
|
||||||
|
// gives errors if the command to run has spaces in it: see
|
||||||
|
// https://github.com/gerardog/gsudo/issues/297
|
||||||
|
cmd.arg("-d");
|
||||||
|
}
|
||||||
|
|
||||||
|
match opts.preserve_env {
|
||||||
|
SudoPreserveEnv::All => match self.kind {
|
||||||
|
SudoKind::Sudo => {
|
||||||
|
cmd.arg("-E");
|
||||||
|
}
|
||||||
|
SudoKind::Gsudo => {
|
||||||
|
cmd.arg("--copyEV");
|
||||||
|
}
|
||||||
|
SudoKind::Doas | SudoKind::WinSudo | SudoKind::Pkexec | SudoKind::Run0 | SudoKind::Please => {
|
||||||
|
return Err(UnsupportedSudo {
|
||||||
|
sudo_kind: self.kind,
|
||||||
|
option: "preserve_env",
|
||||||
|
}
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
SudoKind::Null => unreachable!(),
|
||||||
|
},
|
||||||
|
SudoPreserveEnv::Some(vars) => match self.kind {
|
||||||
|
SudoKind::Sudo => {
|
||||||
|
cmd.arg(format!("--preserve-env={}", vars.join(",")));
|
||||||
|
}
|
||||||
|
SudoKind::Run0 => {
|
||||||
|
for env in vars {
|
||||||
|
cmd.arg(format!("--setenv={}", env));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SudoKind::Please => {
|
||||||
|
cmd.arg("-a");
|
||||||
|
cmd.arg(vars.join(","));
|
||||||
|
}
|
||||||
|
SudoKind::Doas | SudoKind::WinSudo | SudoKind::Gsudo | SudoKind::Pkexec => {
|
||||||
|
return Err(UnsupportedSudo {
|
||||||
|
sudo_kind: self.kind,
|
||||||
|
option: "preserve_env_list",
|
||||||
|
}
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
SudoKind::Null => unreachable!(),
|
||||||
|
},
|
||||||
|
SudoPreserveEnv::None => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.set_home {
|
||||||
|
match self.kind {
|
||||||
|
SudoKind::Sudo => {
|
||||||
|
cmd.arg("-H");
|
||||||
|
}
|
||||||
|
SudoKind::Doas
|
||||||
|
| SudoKind::WinSudo
|
||||||
|
| SudoKind::Gsudo
|
||||||
|
| SudoKind::Pkexec
|
||||||
|
| SudoKind::Run0
|
||||||
|
| SudoKind::Please => {
|
||||||
|
return Err(UnsupportedSudo {
|
||||||
|
sudo_kind: self.kind,
|
||||||
|
option: "set_home",
|
||||||
|
}
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
SudoKind::Null => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(user) = opts.user {
|
||||||
|
match self.kind {
|
||||||
|
SudoKind::Sudo => {
|
||||||
|
cmd.args(["-u", user]);
|
||||||
|
}
|
||||||
|
SudoKind::Doas | SudoKind::Gsudo | SudoKind::Run0 | SudoKind::Please => {
|
||||||
|
cmd.args(["-u", user]);
|
||||||
|
}
|
||||||
|
SudoKind::Pkexec => {
|
||||||
|
cmd.args(["--user", user]);
|
||||||
|
}
|
||||||
|
SudoKind::WinSudo => {
|
||||||
|
// Windows sudo is the only one that doesn't have a `-u` flag
|
||||||
|
return Err(UnsupportedSudo {
|
||||||
|
sudo_kind: self.kind,
|
||||||
|
option: "user",
|
||||||
|
}
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
SudoKind::Null => unreachable!(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd.arg(command);
|
cmd.arg(command);
|
||||||
|
|
||||||
cmd
|
Ok(cmd)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Deserialize, AsRefStr)]
|
// On unix we use `SudoKind::Sudo`, and on windows `SudoKind::WinSudo`.
|
||||||
|
// We always define both though, so that we don't have to put
|
||||||
|
// #[cfg(...)] everywhere.
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Display, Deserialize)]
|
||||||
#[serde(rename_all = "lowercase")]
|
#[serde(rename_all = "lowercase")]
|
||||||
#[strum(serialize_all = "lowercase")]
|
#[strum(serialize_all = "lowercase")]
|
||||||
pub enum SudoKind {
|
pub enum SudoKind {
|
||||||
Doas,
|
// On unix, "sudo" in the config file means Sudo
|
||||||
|
#[cfg(not(windows))]
|
||||||
Sudo,
|
Sudo,
|
||||||
|
// and WinSudo is skipped, making it unused.
|
||||||
|
#[cfg(not(windows))]
|
||||||
|
#[expect(unused, reason = "WinSudo is windows-only")]
|
||||||
|
#[serde(skip)]
|
||||||
|
WinSudo,
|
||||||
|
|
||||||
|
// On unix, Sudo is skipped and unused
|
||||||
|
#[cfg(windows)]
|
||||||
|
#[expect(unused, reason = "Sudo is unix-only")]
|
||||||
|
#[serde(skip)]
|
||||||
|
Sudo,
|
||||||
|
// and "sudo" in the config file means WinSudo.
|
||||||
|
#[cfg(windows)]
|
||||||
|
#[serde(rename = "sudo")]
|
||||||
|
WinSudo,
|
||||||
|
|
||||||
|
Doas,
|
||||||
Gsudo,
|
Gsudo,
|
||||||
Pkexec,
|
Pkexec,
|
||||||
|
Run0,
|
||||||
Please,
|
Please,
|
||||||
|
/// A "no-op" sudo, used when topgrade itself is running as root
|
||||||
|
Null,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AsRef<OsStr> for Sudo {
|
impl SudoKind {
|
||||||
fn as_ref(&self) -> &OsStr {
|
/// Get the name of the "sudo" binary.
|
||||||
self.path.as_ref()
|
///
|
||||||
|
/// For `SudoKind::WinSudo`, returns the full hardcoded path
|
||||||
|
/// instead to ensure we find Windows Sudo rather than gsudo
|
||||||
|
/// masquerading as sudo.
|
||||||
|
///
|
||||||
|
/// Only returns `None` for `SudoKind::Null`.
|
||||||
|
fn binary_name(self) -> Option<&'static str> {
|
||||||
|
match self {
|
||||||
|
SudoKind::Doas => Some("doas"),
|
||||||
|
SudoKind::Sudo => Some("sudo"),
|
||||||
|
SudoKind::WinSudo => Some(r"C:\Windows\System32\sudo.exe"),
|
||||||
|
SudoKind::Gsudo => Some("gsudo"),
|
||||||
|
SudoKind::Pkexec => Some("pkexec"),
|
||||||
|
SudoKind::Run0 => Some("run0"),
|
||||||
|
SudoKind::Please => Some("please"),
|
||||||
|
SudoKind::Null => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find the full path to the "sudo" binary, if it exists on the system.
|
||||||
|
fn which(self) -> Option<PathBuf> {
|
||||||
|
match self.binary_name() {
|
||||||
|
Some(name) => which(name),
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,14 +2,13 @@ use std::cmp::{max, min};
|
|||||||
use std::env;
|
use std::env;
|
||||||
use std::io::{self, Write};
|
use std::io::{self, Write};
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use std::sync::Mutex;
|
use std::sync::{LazyLock, Mutex};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use chrono::{Local, Timelike};
|
use chrono::{Local, Timelike};
|
||||||
use color_eyre::eyre;
|
use color_eyre::eyre;
|
||||||
use color_eyre::eyre::Context;
|
use color_eyre::eyre::Context;
|
||||||
use console::{style, Key, Term};
|
use console::{style, Key, Term};
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use notify_rust::{Notification, Timeout};
|
use notify_rust::{Notification, Timeout};
|
||||||
use rust_i18n::t;
|
use rust_i18n::t;
|
||||||
use tracing::{debug, error};
|
use tracing::{debug, error};
|
||||||
@@ -17,11 +16,9 @@ use tracing::{debug, error};
|
|||||||
use which_crate::which;
|
use which_crate::which;
|
||||||
|
|
||||||
use crate::command::CommandExt;
|
use crate::command::CommandExt;
|
||||||
use crate::report::StepResult;
|
use crate::runner::StepResult;
|
||||||
|
|
||||||
lazy_static! {
|
static TERMINAL: LazyLock<Mutex<Terminal>> = LazyLock::new(|| Mutex::new(Terminal::new()));
|
||||||
static ref TERMINAL: Mutex<Terminal> = Mutex::new(Terminal::new());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
pub fn shell() -> String {
|
pub fn shell() -> String {
|
||||||
@@ -52,9 +49,7 @@ impl Terminal {
|
|||||||
Self {
|
Self {
|
||||||
width: term.size_checked().map(|(_, w)| w),
|
width: term.size_checked().map(|(_, w)| w),
|
||||||
term,
|
term,
|
||||||
prefix: env::var("TOPGRADE_PREFIX")
|
prefix: env::var("TOPGRADE_PREFIX").map_or_else(|_| String::new(), |prefix| format!("({prefix}) ")),
|
||||||
.map(|prefix| format!("({prefix}) "))
|
|
||||||
.unwrap_or_else(|_| String::new()),
|
|
||||||
set_title: true,
|
set_title: true,
|
||||||
display_time: true,
|
display_time: true,
|
||||||
desktop_notification: false,
|
desktop_notification: false,
|
||||||
@@ -62,15 +57,15 @@ impl Terminal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn set_desktop_notifications(&mut self, desktop_notifications: bool) {
|
fn set_desktop_notifications(&mut self, desktop_notifications: bool) {
|
||||||
self.desktop_notification = desktop_notifications
|
self.desktop_notification = desktop_notifications;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_title(&mut self, set_title: bool) {
|
fn set_title(&mut self, set_title: bool) {
|
||||||
self.set_title = set_title
|
self.set_title = set_title;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn display_time(&mut self, display_time: bool) {
|
fn display_time(&mut self, display_time: bool) {
|
||||||
self.display_time = display_time
|
self.display_time = display_time;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn notify_desktop<P: AsRef<str>>(&self, message: P, timeout: Option<Duration>) {
|
fn notify_desktop<P: AsRef<str>>(&self, message: P, timeout: Option<Duration>) {
|
||||||
@@ -178,6 +173,11 @@ impl Terminal {
|
|||||||
StepResult::Success => format!("{}", style(t!("OK")).bold().green()),
|
StepResult::Success => format!("{}", style(t!("OK")).bold().green()),
|
||||||
StepResult::Failure => format!("{}", style(t!("FAILED")).bold().red()),
|
StepResult::Failure => format!("{}", style(t!("FAILED")).bold().red()),
|
||||||
StepResult::Ignored => format!("{}", style(t!("IGNORED")).bold().yellow()),
|
StepResult::Ignored => format!("{}", style(t!("IGNORED")).bold().yellow()),
|
||||||
|
StepResult::SkippedMissingSudo => format!(
|
||||||
|
"{}: {}",
|
||||||
|
style(t!("SKIPPED")).bold().yellow(),
|
||||||
|
t!("Could not find sudo")
|
||||||
|
),
|
||||||
StepResult::Skipped(reason) => format!("{}: {}", style(t!("SKIPPED")).bold().blue(), reason),
|
StepResult::Skipped(reason) => format!("{}: {}", style(t!("SKIPPED")).bold().blue(), reason),
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
@@ -223,8 +223,8 @@ impl Terminal {
|
|||||||
|
|
||||||
let answer = loop {
|
let answer = loop {
|
||||||
match self.term.read_key() {
|
match self.term.read_key() {
|
||||||
Ok(Key::Char('y')) | Ok(Key::Char('Y')) => break Ok(true),
|
Ok(Key::Char('y' | 'Y')) => break Ok(true),
|
||||||
Ok(Key::Char('s')) | Ok(Key::Char('S')) => {
|
Ok(Key::Char('s' | 'S')) => {
|
||||||
println!(
|
println!(
|
||||||
"\n\n{}\n",
|
"\n\n{}\n",
|
||||||
t!("Dropping you to shell. Fix what you need and then exit the shell.")
|
t!("Dropping you to shell. Fix what you need and then exit the shell.")
|
||||||
@@ -235,12 +235,12 @@ impl Terminal {
|
|||||||
break Ok(true);
|
break Ok(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(Key::Char('n')) | Ok(Key::Char('N')) | Ok(Key::Enter) => break Ok(false),
|
Ok(Key::Char('n' | 'N') | Key::Enter) => break Ok(false),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Error reading from terminal: {}", e);
|
error!("Error reading from terminal: {}", e);
|
||||||
break Ok(false);
|
break Ok(false);
|
||||||
}
|
}
|
||||||
Ok(Key::Char('q')) | Ok(Key::Char('Q')) => {
|
Ok(Key::Char('q' | 'Q')) => {
|
||||||
return Err(io::Error::from(io::ErrorKind::Interrupted)).context("Quit from user input")
|
return Err(io::Error::from(io::ErrorKind::Interrupted)).context("Quit from user input")
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
@@ -268,26 +268,26 @@ pub fn should_retry(interrupted: bool, step_name: &str) -> eyre::Result<bool> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn print_separator<P: AsRef<str>>(message: P) {
|
pub fn print_separator<P: AsRef<str>>(message: P) {
|
||||||
TERMINAL.lock().unwrap().print_separator(message)
|
TERMINAL.lock().unwrap().print_separator(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn print_error<P: AsRef<str>, Q: AsRef<str>>(key: Q, message: P) {
|
pub fn print_error<P: AsRef<str>, Q: AsRef<str>>(key: Q, message: P) {
|
||||||
TERMINAL.lock().unwrap().print_error(key, message)
|
TERMINAL.lock().unwrap().print_error(key, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn print_warning<P: AsRef<str>>(message: P) {
|
pub fn print_warning<P: AsRef<str>>(message: P) {
|
||||||
TERMINAL.lock().unwrap().print_warning(message)
|
TERMINAL.lock().unwrap().print_warning(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn print_info<P: AsRef<str>>(message: P) {
|
pub fn print_info<P: AsRef<str>>(message: P) {
|
||||||
TERMINAL.lock().unwrap().print_info(message)
|
TERMINAL.lock().unwrap().print_info(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn print_result<P: AsRef<str>>(key: P, result: &StepResult) {
|
pub fn print_result<P: AsRef<str>>(key: P, result: &StepResult) {
|
||||||
TERMINAL.lock().unwrap().print_result(key, result)
|
TERMINAL.lock().unwrap().print_result(key, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tells whether the terminal is dumb.
|
/// Tells whether the terminal is dumb.
|
||||||
@@ -316,7 +316,7 @@ pub fn prompt_yesno(question: &str) -> Result<bool, io::Error> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn notify_desktop<P: AsRef<str>>(message: P, timeout: Option<Duration>) {
|
pub fn notify_desktop<P: AsRef<str>>(message: P, timeout: Option<Duration>) {
|
||||||
TERMINAL.lock().unwrap().notify_desktop(message, timeout)
|
TERMINAL.lock().unwrap().notify_desktop(message, timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn display_time(display_time: bool) {
|
pub fn display_time(display_time: bool) {
|
||||||
|
|||||||
67
src/utils.rs
67
src/utils.rs
@@ -86,7 +86,7 @@ pub fn editor() -> Vec<String> {
|
|||||||
env::var("EDITOR")
|
env::var("EDITOR")
|
||||||
.unwrap_or_else(|_| String::from(if cfg!(windows) { "notepad" } else { "vi" }))
|
.unwrap_or_else(|_| String::from(if cfg!(windows) { "notepad" } else { "vi" }))
|
||||||
.split_whitespace()
|
.split_whitespace()
|
||||||
.map(|s| s.to_owned())
|
.map(std::borrow::ToOwned::to_owned)
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,6 +112,29 @@ pub fn require<T: AsRef<OsStr> + Debug>(binary_name: T) -> Result<PathBuf> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn require_one<T: AsRef<OsStr> + Debug>(binary_names: impl IntoIterator<Item = T>) -> Result<PathBuf> {
|
||||||
|
let mut failed_bins = Vec::new();
|
||||||
|
for bin in binary_names {
|
||||||
|
match require(&bin) {
|
||||||
|
Ok(path) => return Ok(path),
|
||||||
|
Err(_) => failed_bins.push(bin),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(SkipStep(format!(
|
||||||
|
"{}",
|
||||||
|
t!(
|
||||||
|
"Cannot find any of {binary_names} in PATH",
|
||||||
|
binary_names = failed_bins
|
||||||
|
.iter()
|
||||||
|
.map(|bin| format!("{:?}", bin))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", ")
|
||||||
|
)
|
||||||
|
))
|
||||||
|
.into())
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn require_option<T>(option: Option<T>, cause: String) -> Result<T> {
|
pub fn require_option<T>(option: Option<T>, cause: String) -> Result<T> {
|
||||||
if let Some(value) = option {
|
if let Some(value) = option {
|
||||||
@@ -128,7 +151,7 @@ pub fn string_prepend_str(string: &mut String, s: &str) {
|
|||||||
*string = new_string;
|
*string = new_string;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_family = "unix")]
|
#[cfg(unix)]
|
||||||
pub fn hostname() -> Result<String> {
|
pub fn hostname() -> Result<String> {
|
||||||
match nix::unistd::gethostname() {
|
match nix::unistd::gethostname() {
|
||||||
Ok(os_str) => Ok(os_str
|
Ok(os_str) => Ok(os_str
|
||||||
@@ -138,7 +161,7 @@ pub fn hostname() -> Result<String> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_family = "windows")]
|
#[cfg(windows)]
|
||||||
pub fn hostname() -> Result<String> {
|
pub fn hostname() -> Result<String> {
|
||||||
Command::new("hostname")
|
Command::new("hostname")
|
||||||
.output_checked_utf8()
|
.output_checked_utf8()
|
||||||
@@ -146,6 +169,22 @@ pub fn hostname() -> Result<String> {
|
|||||||
.map(|output| output.stdout.trim().to_owned())
|
.map(|output| output.stdout.trim().to_owned())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
pub fn is_elevated() -> bool {
|
||||||
|
let euid = nix::unistd::Uid::effective();
|
||||||
|
debug!("Running with euid: {euid}");
|
||||||
|
euid.is_root()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
pub fn is_elevated() -> bool {
|
||||||
|
let elevated = is_elevated::is_elevated();
|
||||||
|
if elevated {
|
||||||
|
debug!("Detected elevated process");
|
||||||
|
}
|
||||||
|
elevated
|
||||||
|
}
|
||||||
|
|
||||||
pub mod merge_strategies {
|
pub mod merge_strategies {
|
||||||
use merge::Merge;
|
use merge::Merge;
|
||||||
|
|
||||||
@@ -156,7 +195,7 @@ pub mod merge_strategies {
|
|||||||
if let Some(left_vec) = left {
|
if let Some(left_vec) = left {
|
||||||
if let Some(mut right_vec) = right {
|
if let Some(mut right_vec) = right {
|
||||||
right_vec.append(left_vec);
|
right_vec.append(left_vec);
|
||||||
let _ = std::mem::replace(left, Some(right_vec));
|
let _ = left.replace(right_vec);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
*left = right;
|
*left = right;
|
||||||
@@ -199,17 +238,11 @@ pub mod merge_strategies {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip causes
|
|
||||||
// TODO: Put them in a better place when we have more of them
|
|
||||||
pub fn get_require_sudo_string() -> String {
|
|
||||||
t!("Require sudo or counterpart but not found, skip").to_string()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return `Err(SkipStep)` if `python` is a Python 2 or shim.
|
/// Return `Err(SkipStep)` if `python` is a Python 2 or shim.
|
||||||
///
|
///
|
||||||
/// # Shim
|
/// # Shim
|
||||||
/// On Windows, if you install `python` through `winget`, an actual `python`
|
/// On Windows, if you install `python` through `winget`, an actual `python`
|
||||||
/// is installed as well as a `python3` shim. Shim is invokable, but when you
|
/// is installed as well as a `python3` shim. Shim is invocable, but when you
|
||||||
/// execute it, the Microsoft App Store will be launched instead of a Python
|
/// execute it, the Microsoft App Store will be launched instead of a Python
|
||||||
/// shell.
|
/// shell.
|
||||||
///
|
///
|
||||||
@@ -282,3 +315,15 @@ pub fn install_color_eyre() -> Result<()> {
|
|||||||
.display_location_section(true)
|
.display_location_section(true)
|
||||||
.install()
|
.install()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Macro to construct an error message for when the output of a command is unexpected.
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! output_changed_message {
|
||||||
|
($command:expr, $message:expr) => {
|
||||||
|
format!(
|
||||||
|
"The output of `{}` changed: {}. This is not your fault, this is an issue in Topgrade. Please open an issue at: https://github.com/topgrade-rs/topgrade/issues/new?template=bug_report.md",
|
||||||
|
$command,
|
||||||
|
$message,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
10
translate.sh
Executable file
10
translate.sh
Executable file
@@ -0,0 +1,10 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
## Translate the given string into $langs using translate-shell, outputting to the yaml structure expected for locales/app.yml
|
||||||
|
|
||||||
|
langs="en lt es fr zh_CN zh_TW de"
|
||||||
|
|
||||||
|
printf "\"%s\":\n" "$@"
|
||||||
|
for lang in $langs; do
|
||||||
|
result=$(trans -brief -no-auto -s en -t "${lang/_/-/}" "$@")
|
||||||
|
printf " %s: \"%s\"\n" "$lang" "$result"
|
||||||
|
done
|
||||||
Reference in New Issue
Block a user