Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8e580457a5 | ||
|
|
5350658dab | ||
|
|
1ec0ac50a5 | ||
|
|
635bfce198 | ||
|
|
1307d2d7e8 | ||
|
|
d21141fefe | ||
|
|
0ec0e5a9dd | ||
|
|
9415d7c61f | ||
|
|
42188af02b | ||
|
|
e9581bcf15 | ||
|
|
6afe4f51c6 | ||
|
|
f623746d6c | ||
|
|
1ce4d66e74 | ||
|
|
3735d5c537 | ||
|
|
f3b1d2dfb3 | ||
|
|
7f7d2633cd | ||
|
|
afd95e3d5c | ||
|
|
8f72545894 | ||
|
|
d0d447deac | ||
|
|
53a8683788 | ||
|
|
81491a8d03 | ||
|
|
83504754ac | ||
|
|
2068c2c169 | ||
|
|
dbac121a90 |
2
.github/workflows/check-and-lint.yaml
vendored
2
.github/workflows/check-and-lint.yaml
vendored
@@ -7,7 +7,7 @@ on:
|
||||
name: CI
|
||||
|
||||
env:
|
||||
RUST_VER: '1.68.0'
|
||||
RUST_VER: '1.71.0'
|
||||
CROSS_VER: '0.2.5'
|
||||
CARGO_NET_RETRY: 3
|
||||
|
||||
|
||||
@@ -57,10 +57,3 @@ jobs:
|
||||
# token: ${{ secrets.CODECOV_TOKEN }}
|
||||
files: ./lcov.info
|
||||
fail_ci_if_error: true
|
||||
- name: Test creation of config file
|
||||
run: |
|
||||
CONFIG_PATH=~/.config/topgrade.toml;
|
||||
if [ -f "$CONFIG_PATH" ]; then rm $CONFIG_PATH; fi
|
||||
cargo build;
|
||||
./target/debug/topgrade --dry-run --only system;
|
||||
stat $CONFIG_PATH;
|
||||
2
.github/workflows/crates-publish.yml
vendored
2
.github/workflows/crates-publish.yml
vendored
@@ -4,7 +4,7 @@ on:
|
||||
# types:
|
||||
# - completed
|
||||
release:
|
||||
types: [published, edited]
|
||||
types: [published]
|
||||
|
||||
name: Publish to crates.io on release
|
||||
|
||||
|
||||
21
.github/workflows/test-config-creation.yml
vendored
Normal file
21
.github/workflows/test-config-creation.yml
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
name: Test Configuration File Creation
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
|
||||
jobs:
|
||||
TestConfig:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- run: |
|
||||
CONFIG_PATH=~/.config/topgrade.toml;
|
||||
if [ -f "$CONFIG_PATH" ]; then rm $CONFIG_PATH; fi
|
||||
cargo build;
|
||||
./target/debug/topgrade --dry-run --only system;
|
||||
stat $CONFIG_PATH;
|
||||
99
.github/workflows/update_pypi.yml
vendored
Normal file
99
.github/workflows/update_pypi.yml
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
name: Update PyPi
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
linux:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
target: [x86_64, x86, aarch64]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Build wheels
|
||||
uses: PyO3/maturin-action@v1
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
args: --release --out dist
|
||||
sccache: 'true'
|
||||
manylinux: auto
|
||||
- name: Upload wheels
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: wheels
|
||||
path: dist
|
||||
|
||||
windows:
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
matrix:
|
||||
target: [x64, x86]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Build wheels
|
||||
uses: PyO3/maturin-action@v1
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
args: --release --out dist
|
||||
sccache: 'true'
|
||||
- name: Upload wheels
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: wheels
|
||||
path: dist
|
||||
|
||||
macos:
|
||||
runs-on: macos-latest
|
||||
strategy:
|
||||
matrix:
|
||||
target: [x86_64, aarch64]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Build wheels
|
||||
uses: PyO3/maturin-action@v1
|
||||
with:
|
||||
target: ${{ matrix.target }}
|
||||
args: --release --out dist
|
||||
sccache: 'true'
|
||||
- name: Upload wheels
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: wheels
|
||||
path: dist
|
||||
|
||||
sdist:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Build sdist
|
||||
uses: PyO3/maturin-action@v1
|
||||
with:
|
||||
command: sdist
|
||||
args: --out dist
|
||||
- name: Upload sdist
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: wheels
|
||||
path: dist
|
||||
|
||||
release:
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
if: "startsWith(github.ref, 'refs/tags/')"
|
||||
needs: [linux, windows, macos, sdist]
|
||||
steps:
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: wheels
|
||||
- name: Publish to PyPI
|
||||
uses: PyO3/maturin-action@v1
|
||||
env:
|
||||
MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
|
||||
with:
|
||||
command: upload
|
||||
args: --skip-existing *
|
||||
@@ -1,16 +1,19 @@
|
||||
## Contributing to `topgrade`
|
||||
|
||||
Thank you for your interest in contributing to `topgrade`! We welcome and encourage
|
||||
contributions of all kinds, such as:
|
||||
Thank you for your interest in contributing to `topgrade`!
|
||||
We welcome and encourage contributions of all kinds, such as:
|
||||
|
||||
1. Issue reports or feature requests
|
||||
2. Documentation improvements
|
||||
3. Code (PR or PR Review)
|
||||
|
||||
Please follow the [Karma Runner guidelines](http://karma-runner.github.io/6.2/dev/git-commit-msg.html)
|
||||
for commit messages.
|
||||
|
||||
## Adding a new `step`
|
||||
|
||||
In `topgrade`'s term, package manager is called `step`. To add a new `step` to
|
||||
`topgrade`:
|
||||
In `topgrade`'s term, package manager is called `step`.
|
||||
To add a new `step` to `topgrade`:
|
||||
|
||||
1. Add a new variant to
|
||||
[`enum Step`](https://github.com/topgrade-rs/topgrade/blob/cb7adc8ced8a77addf2cb051d18bba9f202ab866/src/config.rs#L100)
|
||||
|
||||
120
Cargo.lock
generated
120
Cargo.lock
generated
@@ -17,17 +17,6 @@ version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.7.20"
|
||||
@@ -406,6 +395,28 @@ dependencies = [
|
||||
"windows-sys 0.45.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-random"
|
||||
version = "0.1.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "368a7a772ead6ce7e1de82bfb04c485f3db8ec744f72925af5735e29a22cc18e"
|
||||
dependencies = [
|
||||
"const-random-macro",
|
||||
"proc-macro-hack",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "const-random-macro"
|
||||
version = "0.1.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d7d6ab3c3a2282db210df5f02c4dab6e0a7057af0fb7ebd4070f30fe05c0ddb"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"once_cell",
|
||||
"proc-macro-hack",
|
||||
"tiny-keccak",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.4"
|
||||
@@ -439,6 +450,12 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crunchy"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.6"
|
||||
@@ -513,9 +530,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dlv-list"
|
||||
version = "0.3.0"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257"
|
||||
checksum = "d529fd73d344663edfd598ccb3f344e46034db51ebd103518eae34338248ad73"
|
||||
dependencies = [
|
||||
"const-random",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
@@ -624,7 +644,7 @@ checksum = "5cbc844cecaee9d4443931972e1289c8ff485cb4cc2767cb03ca139ed6885153"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"redox_syscall 0.2.16",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
@@ -814,9 +834,12 @@ name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
@@ -980,7 +1003,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown",
|
||||
"hashbrown 0.12.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1291,12 +1314,12 @@ checksum = "9670a07f94779e00908f3e686eab508878ebb390ba6e604d3a284c00e8d0487b"
|
||||
|
||||
[[package]]
|
||||
name = "ordered-multimap"
|
||||
version = "0.4.3"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccd746e37177e1711c20dd619a1620f34f5c8b569c53590a72dedd5344d8924a"
|
||||
checksum = "4ed8acf08e98e744e5384c8bc63ceb0364e68a6854187221c18df61c4797690e"
|
||||
dependencies = [
|
||||
"dlv-list",
|
||||
"hashbrown",
|
||||
"hashbrown 0.13.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1420,6 +1443,12 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-hack"
|
||||
version = "0.5.20+deprecated"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.63"
|
||||
@@ -1495,6 +1524,15 @@ dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.4.3"
|
||||
@@ -1502,7 +1540,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"redox_syscall",
|
||||
"redox_syscall 0.2.16",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
@@ -1541,15 +1579,6 @@ version = "0.6.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
|
||||
|
||||
[[package]]
|
||||
name = "remove_dir_all"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.11.18"
|
||||
@@ -1612,9 +1641,9 @@ checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316"
|
||||
|
||||
[[package]]
|
||||
name = "rust-ini"
|
||||
version = "0.18.0"
|
||||
version = "0.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6d5f2436026b4f6e79dc829837d467cc7e9a55ee40e750d716713540715a2df"
|
||||
checksum = "7e2a3bcec1f113553ef1c88aae6c020a369d03d55b58de9869a0908930385091"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"ordered-multimap",
|
||||
@@ -1947,16 +1976,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.2.0"
|
||||
version = "3.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22"
|
||||
checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"rand",
|
||||
"redox_syscall",
|
||||
"remove_dir_all",
|
||||
"winapi",
|
||||
"fastrand",
|
||||
"redox_syscall 0.3.5",
|
||||
"rustix",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2042,6 +2071,15 @@ dependencies = [
|
||||
"time-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tiny-keccak"
|
||||
version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
|
||||
dependencies = [
|
||||
"crunchy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.6.0"
|
||||
@@ -2126,7 +2164,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "topgrade"
|
||||
version = "12.0.0"
|
||||
version = "12.0.2"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"chrono",
|
||||
|
||||
@@ -5,7 +5,7 @@ categories = ["os"]
|
||||
keywords = ["upgrade", "update"]
|
||||
license = "GPL-3.0"
|
||||
repository = "https://github.com/topgrade-rs/topgrade"
|
||||
version = "12.0.0"
|
||||
version = "12.0.2"
|
||||
authors = ["Roey Darwish Dror <roey.ghost@gmail.com>", "Thomas Schönauer <t.schoenauer@hgs-wt.at>"]
|
||||
exclude = ["doc/screenshot.gif"]
|
||||
edition = "2021"
|
||||
@@ -37,7 +37,7 @@ chrono = "~0.4"
|
||||
glob = "~0.3"
|
||||
strum = { version = "~0.24", features = ["derive"] }
|
||||
thiserror = "~1.0"
|
||||
tempfile = "~3.2"
|
||||
tempfile = "~3.6"
|
||||
cfg-if = "~1.0"
|
||||
tokio = { version = "~1.18", features = ["process", "rt-multi-thread"] }
|
||||
futures = "~0.3"
|
||||
@@ -63,7 +63,7 @@ depends = "$auto,git"
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
libc = "~0.2"
|
||||
nix = "~0.24"
|
||||
rust-ini = "~0.18"
|
||||
rust-ini = "~0.19"
|
||||
self_update_crate = { version = "~0.30", default-features = false, optional = true, package = "self_update", features = ["archive-tar", "compression-flate2", "rustls"] }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
|
||||
@@ -34,11 +34,13 @@ To remedy this, **Topgrade** detects which tools you use and runs the appropriat
|
||||
- Void Linux: [XBPS](https://voidlinux.org/packages/?arch=x86_64&q=topgrade)
|
||||
- macOS: [Homebrew](https://formulae.brew.sh/formula/topgrade) or [MacPorts](https://ports.macports.org/port/topgrade/)
|
||||
- Windows: [Scoop](https://github.com/ScoopInstaller/Main/blob/master/bucket/topgrade.json)
|
||||
- PyPi: [pip](https://pypi.org/project/topgrade/)
|
||||
|
||||
Other systems users can either use `cargo install` or the compiled binaries from the release page.
|
||||
The compiled binaries contain a self-upgrading feature.
|
||||
|
||||
Topgrade requires Rust 1.60 or above.
|
||||
> Currently, Topgrade requires Rust 1.65 or above. In general, Topgrade tracks
|
||||
> the latest stable toolchain.
|
||||
|
||||
## Usage
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Include any additional configuration file(s)
|
||||
# [include] sections are processed in the order you write them
|
||||
# Files in $CONFIG_DIR/topgrade/topgrade.d/ are automatically included at the beginning of this file
|
||||
# Files in $CONFIG_DIR/topgrade.d/ are automatically included before this file
|
||||
[include]
|
||||
#paths = ["/etc/topgrade.toml"]
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
# Sudo command to be used
|
||||
#sudo_command = "sudo"
|
||||
|
||||
# Run `sudo -v` to cache credentials at the start of the run; this avoids a
|
||||
# blocking password prompt in the middle of a possibly-unattended 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
|
||||
#pre_sudo = false
|
||||
|
||||
# Run inside tmux
|
||||
@@ -33,7 +33,7 @@
|
||||
# List of remote machines with Topgrade installed on them
|
||||
#remote_topgrades = ["toothless", "pi", "parnas"]
|
||||
|
||||
# Arguments to pass SSH when upgrading remote systems
|
||||
# Arguments to pass to SSH when upgrading remote systems
|
||||
#ssh_arguments = "-o ConnectTimeout=2"
|
||||
|
||||
# Path to Topgrade executable on remote machines
|
||||
@@ -57,32 +57,31 @@
|
||||
# Whether to self update (this is ignored if the binary has been built without self update support, available also via setting the environment variable TOPGRADE_NO_SELF_UPGRADE)
|
||||
#no_self_update = true
|
||||
|
||||
[git]
|
||||
#max_concurrency = 5
|
||||
# Additional git repositories to pull
|
||||
#repos = [
|
||||
# "~/src/*/",
|
||||
# "~/.config/something"
|
||||
#]
|
||||
|
||||
# Don't pull the predefined git repos
|
||||
#pull_predefined = false
|
||||
|
||||
# Arguments to pass Git when pulling Repositories
|
||||
#arguments = "--rebase --autostash"
|
||||
|
||||
[composer]
|
||||
#self_update = true
|
||||
# Extra Home Manager arguments
|
||||
#home_manager_arguments = ["--flake", "file"]
|
||||
|
||||
# Commands to run before anything
|
||||
[pre_commands]
|
||||
#"Emacs Snapshot" = "rm -rf ~/.emacs.d/elpa.bak && cp -rl ~/.emacs.d/elpa ~/.emacs.d/elpa.bak"
|
||||
|
||||
# Commands to run after anything
|
||||
[post_commands]
|
||||
#"Emacs Snapshot" = "rm -rf ~/.emacs.d/elpa.bak && cp -rl ~/.emacs.d/elpa ~/.emacs.d/elpa.bak"
|
||||
|
||||
# Custom commands
|
||||
[commands]
|
||||
#"Python Environment" = "~/dev/.env/bin/pip install -i https://pypi.python.org/simple -U --upgrade-strategy eager jupyter"
|
||||
#"Custom command using interactive shell (unix)" = "-i vim_upgrade"
|
||||
|
||||
[python]
|
||||
#enable_pip_review = true ###disabled by default
|
||||
#enable_pip_review_local = true ###disabled by default
|
||||
#enable_pipupgrade = true ###disabled by default
|
||||
#pipupgrade_arguments = "-y -u --pip-path pip" ###disabled by default
|
||||
|
||||
[composer]
|
||||
#self_update = true
|
||||
|
||||
[brew]
|
||||
#greedy_cask = true
|
||||
#autoremove = true
|
||||
@@ -109,11 +108,19 @@
|
||||
#rpm_ostree = false
|
||||
#nix_arguments = "--flake"
|
||||
|
||||
[python]
|
||||
#enable_pip_review = true ###disabled by default
|
||||
#enable_pip_review_local = true ###disabled by default
|
||||
#enable_pipupgrade = true ###disabled by default
|
||||
#pipupgrade_arguments = "-y -u --pip-path pip" ###disabled by default
|
||||
[git]
|
||||
#max_concurrency = 5
|
||||
# Additional git repositories to pull
|
||||
#repos = [
|
||||
# "~/src/*/",
|
||||
# "~/.config/something"
|
||||
#]
|
||||
|
||||
# Don't pull the predefined git repos
|
||||
#pull_predefined = false
|
||||
|
||||
# Arguments to pass Git when pulling Repositories
|
||||
#arguments = "--rebase --autostash"
|
||||
|
||||
[windows]
|
||||
# Manually select Windows updates
|
||||
@@ -131,10 +138,28 @@
|
||||
# Use sudo if the NPM directory isn't owned by the current user
|
||||
#use_sudo = true
|
||||
|
||||
[yarn]
|
||||
# Run `yarn global upgrade` with `sudo`
|
||||
#use_sudo = true
|
||||
|
||||
[vim]
|
||||
# For `vim-plug`, execute `PlugUpdate!` instead of `PlugUpdate`
|
||||
#force_plug_update = true
|
||||
|
||||
[firmware]
|
||||
# Offer to update firmware; if false just check for and display available updates
|
||||
#upgrade = true
|
||||
|
||||
[vagrant]
|
||||
# Vagrant directories
|
||||
#directories = []
|
||||
|
||||
# power on vagrant boxes if needed
|
||||
#power_on = true
|
||||
|
||||
# Always suspend vagrant boxes instead of powering off
|
||||
#always_suspend = true
|
||||
|
||||
[flatpak]
|
||||
# Use sudo for updating the system-wide installation
|
||||
#use_sudo = true
|
||||
|
||||
16
pyproject.toml
Normal file
16
pyproject.toml
Normal file
@@ -0,0 +1,16 @@
|
||||
[build-system]
|
||||
requires = ["maturin>=1.0,<2.0"]
|
||||
build-backend = "maturin"
|
||||
|
||||
[project]
|
||||
name = "topgrade"
|
||||
requires-python = ">=3.7"
|
||||
classifiers = [
|
||||
"Programming Language :: Rust",
|
||||
"Programming Language :: Python :: Implementation :: CPython",
|
||||
"Programming Language :: Python :: Implementation :: PyPy",
|
||||
]
|
||||
|
||||
|
||||
[tool.maturin]
|
||||
bindings = "bin"
|
||||
@@ -351,6 +351,9 @@ pub struct Linux {
|
||||
|
||||
#[merge(strategy = crate::utils::merge_strategies::string_append_opt)]
|
||||
emerge_update_flags: Option<String>,
|
||||
|
||||
#[merge(strategy = crate::utils::merge_strategies::vec_prepend_opt)]
|
||||
home_manager_arguments: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default, Debug, Merge)]
|
||||
@@ -370,6 +373,8 @@ pub struct Vim {
|
||||
pub struct Misc {
|
||||
pre_sudo: Option<bool>,
|
||||
|
||||
sudo_command: Option<SudoKind>,
|
||||
|
||||
#[merge(strategy = crate::utils::merge_strategies::vec_prepend_opt)]
|
||||
git_repos: Option<Vec<String>>,
|
||||
|
||||
@@ -440,8 +445,6 @@ pub struct ConfigFile {
|
||||
#[merge(strategy = crate::utils::merge_strategies::inner_merge_opt)]
|
||||
misc: Option<Misc>,
|
||||
|
||||
sudo_command: Option<SudoKind>,
|
||||
|
||||
#[merge(strategy = crate::utils::merge_strategies::commands_merge_opt)]
|
||||
pre_commands: Option<Commands>,
|
||||
|
||||
@@ -1275,6 +1278,14 @@ impl Config {
|
||||
.and_then(|linux| linux.nix_arguments.as_deref())
|
||||
}
|
||||
|
||||
/// Extra Home Manager arguments
|
||||
pub fn home_manager(&self) -> Option<&Vec<String>> {
|
||||
self.config_file
|
||||
.linux
|
||||
.as_ref()
|
||||
.and_then(|misc| misc.home_manager_arguments.as_ref())
|
||||
}
|
||||
|
||||
/// Distrobox use root
|
||||
pub fn distrobox_root(&self) -> bool {
|
||||
self.config_file
|
||||
@@ -1389,7 +1400,7 @@ impl Config {
|
||||
}
|
||||
|
||||
pub fn sudo_command(&self) -> Option<SudoKind> {
|
||||
self.config_file.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
|
||||
|
||||
@@ -14,6 +14,10 @@ pub enum TopgradeError {
|
||||
#[cfg(target_os = "linux")]
|
||||
UnknownLinuxDistribution,
|
||||
|
||||
#[error("File \"/etc/os-release\" does not exist or is empty")]
|
||||
#[cfg(target_os = "linux")]
|
||||
EmptyOSReleaseFile,
|
||||
|
||||
#[error("Failed getting the system package manager")]
|
||||
#[cfg(target_os = "linux")]
|
||||
FailedGettingPackageManager,
|
||||
|
||||
@@ -5,6 +5,7 @@ use crate::sudo::Sudo;
|
||||
use crate::utils::{require_option, REQUIRE_SUDO};
|
||||
use crate::{config::Config, executor::Executor};
|
||||
use color_eyre::eyre::Result;
|
||||
use std::env::var;
|
||||
use std::path::Path;
|
||||
use std::sync::Mutex;
|
||||
|
||||
@@ -17,16 +18,20 @@ pub struct ExecutionContext<'a> {
|
||||
/// This is used in `./steps/remote/ssh.rs`, where we want to run `topgrade` in a new
|
||||
/// tmux window for each remote.
|
||||
tmux_session: Mutex<Option<String>>,
|
||||
/// True if topgrade is running under ssh.
|
||||
under_ssh: bool,
|
||||
}
|
||||
|
||||
impl<'a> ExecutionContext<'a> {
|
||||
pub fn new(run_type: RunType, sudo: Option<Sudo>, git: &'a Git, config: &'a Config) -> Self {
|
||||
let under_ssh = var("SSH_CLIENT").is_ok() || var("SSH_TTY").is_ok();
|
||||
Self {
|
||||
run_type,
|
||||
sudo,
|
||||
git,
|
||||
config,
|
||||
tmux_session: Mutex::new(None),
|
||||
under_ssh,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +56,10 @@ impl<'a> ExecutionContext<'a> {
|
||||
self.config
|
||||
}
|
||||
|
||||
pub fn under_ssh(&self) -> bool {
|
||||
self.under_ssh
|
||||
}
|
||||
|
||||
pub fn set_tmux_session(&self, session_name: String) {
|
||||
self.tmux_session.lock().unwrap().replace(session_name);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#[cfg(any(windows))]
|
||||
#[cfg(windows)]
|
||||
use std::env;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
|
||||
@@ -339,7 +339,7 @@ pub fn run_conda_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
print_separator("Conda");
|
||||
|
||||
let mut command = ctx.run_type().execute(conda);
|
||||
command.args(["update", "--all"]);
|
||||
command.args(["update", "--all", "-n", "base"]);
|
||||
if ctx.config().yes(Step::Conda) {
|
||||
command.arg("--yes");
|
||||
}
|
||||
@@ -360,7 +360,7 @@ pub fn run_mamba_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
print_separator("Mamba");
|
||||
|
||||
let mut command = ctx.run_type().execute(mamba);
|
||||
command.args(["update", "--all"]);
|
||||
command.args(["update", "--all", "-n", "base"]);
|
||||
if ctx.config().yes(Step::Mamba) {
|
||||
command.arg("--yes");
|
||||
}
|
||||
@@ -720,7 +720,8 @@ pub fn bin_update(ctx: &ExecutionContext) -> Result<()> {
|
||||
}
|
||||
|
||||
pub fn spicetify_upgrade(ctx: &ExecutionContext) -> Result<()> {
|
||||
let spicetify = require("spicetify")?;
|
||||
// As of 04-07-2023 NixOS packages Spicetify with the `spicetify-cli` binary name
|
||||
let spicetify = require("spicetify").or(require("spicetify-cli"))?;
|
||||
|
||||
print_separator("Spicetify");
|
||||
ctx.run_type().execute(spicetify).arg("upgrade").status_checked()
|
||||
|
||||
@@ -8,9 +8,12 @@ use std::process::Command;
|
||||
pub fn upgrade_packages(ctx: &ExecutionContext) -> Result<()> {
|
||||
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
|
||||
print_separator("DragonFly BSD Packages");
|
||||
ctx.execute(sudo)
|
||||
.args(["/usr/local/sbin/pkg", "upgrade"])
|
||||
.status_checked()
|
||||
let mut cmd = ctx.execute(sudo);
|
||||
cmd.args(["/usr/local/sbin/pkg", "upgrade"]);
|
||||
if ctx.config().yes(Step::System) {
|
||||
cmd.arg("-y");
|
||||
}
|
||||
cmd.status_checked()
|
||||
}
|
||||
|
||||
pub fn audit_packages(ctx: &ExecutionContext) -> Result<()> {
|
||||
|
||||
@@ -114,10 +114,14 @@ impl Distribution {
|
||||
if PathBuf::from(OS_RELEASE_PATH).exists() {
|
||||
let os_release = Ini::load_from_file(OS_RELEASE_PATH)?;
|
||||
|
||||
if os_release.general_section().is_empty() {
|
||||
return Err(TopgradeError::EmptyOSReleaseFile.into());
|
||||
}
|
||||
|
||||
return Self::parse_os_release(&os_release);
|
||||
}
|
||||
|
||||
Err(TopgradeError::UnknownLinuxDistribution.into())
|
||||
Err(TopgradeError::EmptyOSReleaseFile.into())
|
||||
}
|
||||
|
||||
pub fn upgrade(self, ctx: &ExecutionContext) -> Result<()> {
|
||||
@@ -248,15 +252,18 @@ fn upgrade_suse(ctx: &ExecutionContext) -> Result<()> {
|
||||
.args(["zypper", "refresh"])
|
||||
.status_checked()?;
|
||||
|
||||
ctx.run_type()
|
||||
.execute(sudo)
|
||||
.arg("zypper")
|
||||
.arg(if ctx.config().suse_dup() {
|
||||
let mut cmd = ctx.run_type().execute(sudo);
|
||||
cmd.arg("zypper");
|
||||
cmd.arg(if ctx.config().suse_dup() {
|
||||
"dist-upgrade"
|
||||
} else {
|
||||
"update"
|
||||
})
|
||||
.status_checked()?;
|
||||
});
|
||||
if ctx.config().yes(Step::System) {
|
||||
cmd.arg("-y");
|
||||
}
|
||||
|
||||
cmd.status_checked()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -268,21 +275,26 @@ fn upgrade_opensuse_tumbleweed(ctx: &ExecutionContext) -> Result<()> {
|
||||
.args(["zypper", "refresh"])
|
||||
.status_checked()?;
|
||||
|
||||
ctx.run_type()
|
||||
.execute(sudo)
|
||||
.arg("zypper")
|
||||
.arg("dist-upgrade")
|
||||
.status_checked()?;
|
||||
let mut cmd = ctx.run_type().execute(sudo);
|
||||
cmd.args(["zypper", "dist-upgrade"]);
|
||||
if ctx.config().yes(Step::System) {
|
||||
cmd.arg("-y");
|
||||
}
|
||||
|
||||
cmd.status_checked()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn upgrade_suse_micro(ctx: &ExecutionContext) -> Result<()> {
|
||||
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
|
||||
ctx.run_type()
|
||||
.execute(sudo)
|
||||
.args(["transactional-update", "dup"])
|
||||
.status_checked()?;
|
||||
let mut cmd = ctx.run_type().execute(sudo);
|
||||
cmd.arg("transactional-update");
|
||||
if ctx.config().yes(Step::System) {
|
||||
cmd.arg("-n");
|
||||
}
|
||||
|
||||
cmd.arg("dup").status_checked()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -321,11 +333,13 @@ fn upgrade_pclinuxos(ctx: &ExecutionContext) -> Result<()> {
|
||||
|
||||
command_update.status_checked()?;
|
||||
|
||||
ctx.run_type()
|
||||
.execute(sudo)
|
||||
.arg(&which("apt-get").unwrap())
|
||||
.arg("dist-upgrade")
|
||||
.status_checked()?;
|
||||
let mut cmd = ctx.run_type().execute(sudo);
|
||||
cmd.arg(&which("apt-get").unwrap());
|
||||
cmd.arg("dist-upgrade");
|
||||
if ctx.config().yes(Step::System) {
|
||||
cmd.arg("-y");
|
||||
}
|
||||
cmd.status_checked()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -497,10 +511,12 @@ pub fn run_deb_get(ctx: &ExecutionContext) -> Result<()> {
|
||||
|
||||
fn upgrade_solus(ctx: &ExecutionContext) -> Result<()> {
|
||||
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
|
||||
ctx.run_type()
|
||||
.execute(sudo)
|
||||
.args(["eopkg", "upgrade"])
|
||||
.status_checked()?;
|
||||
let mut cmd = ctx.run_type().execute(sudo);
|
||||
cmd.arg("eopkg");
|
||||
if ctx.config().yes(Step::System) {
|
||||
cmd.arg("-y");
|
||||
}
|
||||
cmd.arg("upgrade").status_checked()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -539,10 +555,12 @@ pub fn run_pacdef(ctx: &ExecutionContext) -> Result<()> {
|
||||
let new_version = string.contains("version: 1");
|
||||
|
||||
if new_version {
|
||||
ctx.run_type()
|
||||
.execute(&pacdef)
|
||||
.args(["package", "sync"])
|
||||
.status_checked()?;
|
||||
let mut cmd = ctx.run_type().execute(&pacdef);
|
||||
cmd.args(["package", "sync"]);
|
||||
if ctx.config().yes(Step::System) {
|
||||
cmd.arg("--noconfirm");
|
||||
}
|
||||
cmd.status_checked()?;
|
||||
|
||||
println!();
|
||||
ctx.run_type()
|
||||
@@ -550,7 +568,13 @@ pub fn run_pacdef(ctx: &ExecutionContext) -> Result<()> {
|
||||
.args(["package", "review"])
|
||||
.status_checked()?;
|
||||
} else {
|
||||
ctx.run_type().execute(&pacdef).arg("sync").status_checked()?;
|
||||
let mut cmd = ctx.run_type().execute(&pacdef);
|
||||
cmd.arg("sync");
|
||||
if ctx.config().yes(Step::System) {
|
||||
cmd.arg("--noconfirm");
|
||||
}
|
||||
|
||||
cmd.status_checked()?;
|
||||
|
||||
println!();
|
||||
ctx.run_type().execute(&pacdef).arg("review").status_checked()?;
|
||||
@@ -596,10 +620,12 @@ pub fn run_packer_nu(ctx: &ExecutionContext) -> Result<()> {
|
||||
|
||||
fn upgrade_clearlinux(ctx: &ExecutionContext) -> Result<()> {
|
||||
let sudo = require_option(ctx.sudo().as_ref(), REQUIRE_SUDO.to_string())?;
|
||||
ctx.run_type()
|
||||
.execute(sudo)
|
||||
.args(["swupd", "update"])
|
||||
.status_checked()?;
|
||||
let mut cmd = ctx.run_type().execute(sudo);
|
||||
cmd.args(["swupd", "update"]);
|
||||
if ctx.config().yes(Step::System) {
|
||||
cmd.arg("--assume=yes");
|
||||
}
|
||||
cmd.status_checked()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1014,4 +1040,9 @@ mod tests {
|
||||
fn test_vanilla() {
|
||||
test_template(include_str!("os_release/vanilla"), Distribution::Vanilla);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_solus() {
|
||||
test_template(include_str!("os_release/solus"), Distribution::Solus);
|
||||
}
|
||||
}
|
||||
|
||||
11
src/steps/os/os_release/solus
Normal file
11
src/steps/os/os_release/solus
Normal file
@@ -0,0 +1,11 @@
|
||||
NAME="Solus"
|
||||
VERSION="4.4"
|
||||
ID="solus"
|
||||
VERSION_CODENAME=harmony
|
||||
VERSION_ID="4.4"
|
||||
PRETTY_NAME="Solus 4.4 Harmony"
|
||||
ANSI_COLOR="1;34"
|
||||
HOME_URL="https://getsol.us"
|
||||
SUPPORT_URL="https://help.getsol.us/docs/user/contributing/getting-involved"
|
||||
BUG_REPORT_URL="https://dev.getsol.us/"
|
||||
LOGO="distributor-logo-solus"
|
||||
@@ -155,7 +155,7 @@ pub fn run_oh_my_bash(ctx: &ExecutionContext) -> Result<()> {
|
||||
print_separator("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()
|
||||
}
|
||||
@@ -383,7 +383,7 @@ pub fn run_nix(ctx: &ExecutionContext) -> Result<()> {
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
if let Ok(..) = require("darwin-rebuild") {
|
||||
if require("darwin-rebuild").is_ok() {
|
||||
return Err(SkipStep(String::from(
|
||||
"Nix-darwin on macOS must be upgraded via darwin-rebuild switch",
|
||||
))
|
||||
@@ -442,7 +442,15 @@ pub fn run_home_manager(ctx: &ExecutionContext) -> Result<()> {
|
||||
let home_manager = require("home-manager")?;
|
||||
|
||||
print_separator("home-manager");
|
||||
ctx.run_type().execute(home_manager).arg("switch").status_checked()
|
||||
|
||||
let mut cmd = ctx.run_type().execute(home_manager);
|
||||
cmd.arg("switch");
|
||||
|
||||
if let Some(extra_args) = ctx.config().home_manager() {
|
||||
cmd.args(extra_args);
|
||||
}
|
||||
|
||||
cmd.status_checked()
|
||||
}
|
||||
|
||||
pub fn run_tldr(ctx: &ExecutionContext) -> Result<()> {
|
||||
|
||||
@@ -9,7 +9,7 @@ use tracing::debug;
|
||||
use crate::command::CommandExt;
|
||||
use crate::execution_context::ExecutionContext;
|
||||
use crate::terminal::{print_separator, print_warning};
|
||||
use crate::utils::require;
|
||||
use crate::utils::{require, which};
|
||||
use crate::{error::SkipStep, steps::git::Repositories};
|
||||
use crate::{powershell, Step};
|
||||
|
||||
@@ -69,6 +69,10 @@ pub fn run_scoop(ctx: &ExecutionContext) -> Result<()> {
|
||||
}
|
||||
|
||||
pub fn update_wsl(ctx: &ExecutionContext) -> Result<()> {
|
||||
if !is_wsl_installed()? {
|
||||
return Err(SkipStep("WSL not installed".to_string()).into());
|
||||
}
|
||||
|
||||
let wsl = require("wsl")?;
|
||||
|
||||
print_separator("Update WSL");
|
||||
@@ -87,6 +91,30 @@ pub fn update_wsl(ctx: &ExecutionContext) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Detect if WSL is installed or not.
|
||||
///
|
||||
/// For WSL, we cannot simply check if command `wsl` is installed as on newer
|
||||
/// versions of Windows (since windows 10 version 2004), this commmand is
|
||||
/// installed by default.
|
||||
///
|
||||
/// If the command is installed and the user hasn't installed any Linux distros
|
||||
/// on it, command `wsl -l` would print a help message and exit with failure, we
|
||||
/// use this to check whether WSL is install or not.
|
||||
fn is_wsl_installed() -> Result<bool> {
|
||||
if let Some(wsl) = which("wsl") {
|
||||
// Don't use `output_checked` as an execution failure log is not wanted
|
||||
#[allow(clippy::disallowed_methods)]
|
||||
let output = Command::new(wsl).arg("-l").output()?;
|
||||
let status = output.status;
|
||||
|
||||
if status.success() {
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
fn get_wsl_distributions(wsl: &Path) -> Result<Vec<String>> {
|
||||
let output = Command::new(wsl).args(["--list", "-q"]).output_checked_utf8()?.stdout;
|
||||
Ok(output
|
||||
@@ -115,6 +143,10 @@ fn upgrade_wsl_distribution(wsl: &Path, dist: &str, ctx: &ExecutionContext) -> R
|
||||
}
|
||||
|
||||
pub fn run_wsl_topgrade(ctx: &ExecutionContext) -> Result<()> {
|
||||
if !is_wsl_installed()? {
|
||||
return Err(SkipStep("WSL not installed".to_string()).into());
|
||||
}
|
||||
|
||||
let wsl = require("wsl")?;
|
||||
let wsl_distributions = get_wsl_distributions(&wsl)?;
|
||||
let mut ran = false;
|
||||
|
||||
@@ -52,6 +52,8 @@ pub fn run_toolbx(ctx: &ExecutionContext) -> Result<()> {
|
||||
topgrade_path,
|
||||
"--only",
|
||||
"system",
|
||||
"--no-self-update",
|
||||
"--skip-notify",
|
||||
];
|
||||
if ctx.config().yes(Step::Toolbx) {
|
||||
args.push("--yes");
|
||||
|
||||
@@ -165,6 +165,28 @@ pub fn run_zim(ctx: &ExecutionContext) -> Result<()> {
|
||||
|
||||
pub fn run_oh_my_zsh(ctx: &ExecutionContext) -> Result<()> {
|
||||
require("zsh")?;
|
||||
|
||||
// When updating `oh-my-zsh` on a remote machine through topgrade, the
|
||||
// following processes will be created:
|
||||
//
|
||||
// SSH -> ZSH -> ZSH ($SHELL) -> topgrade -> ZSH
|
||||
//
|
||||
// The first ZSH process, won't source zshrc (as it is a login shell),
|
||||
// and thus it won't have the ZSH environment variable, as a result, the
|
||||
// children processes won't get it either, so we source the zshrc and set
|
||||
// the ZSH variable for topgrade here.
|
||||
if ctx.under_ssh() {
|
||||
let zshrc_path = zshrc().require()?;
|
||||
let output = Command::new("zsh")
|
||||
.args([
|
||||
"-c",
|
||||
// ` > /dev/null` is used in case the user's zshrc will have some stdout output.
|
||||
format!("source {} > /dev/null && echo $ZSH", zshrc_path.display()).as_str(),
|
||||
])
|
||||
.output_checked_utf8()?;
|
||||
env::set_var("ZSH", output.stdout.trim());
|
||||
}
|
||||
|
||||
let oh_my_zsh = env::var("ZSH")
|
||||
.map(PathBuf::from)
|
||||
// default to `~/.oh-my-zsh`
|
||||
|
||||
Reference in New Issue
Block a user