Compare commits

...

46 Commits

Author SHA1 Message Date
Thomas Schönauer
d7182b5a6e v11.0.0 bump (#410) 2023-04-30 19:02:26 +00:00
Thomas Schönauer
93ec1172fe Update README.md 2023-04-30 18:58:22 +00:00
Thomas Schönauer
609477a373 Update README.md 2023-04-30 18:57:23 +00:00
Thomas Schönauer
1d49af10a7 Update README.md 2023-04-30 18:57:07 +00:00
Utkarsh Gupta
327ed837c2 Replace directories with home & etcetera (#407)
* Use global lazy HOME_DIR

* Remove unused base_dirs

* Use `etcetera` instead of `directories`

---------

Co-authored-by: Thomas Schönauer <37108907+DottoDev@users.noreply.github.com>
2023-04-30 18:32:13 +00:00
Thomas Schönauer
d406e2aeab Assume Fedora Silverblue based on os-release and not on existence of rpm-ostree (#393)
* Do not assume silverblue if rpm-ostree is available

* Fix typo

* Fix config error
2023-04-30 18:22:08 +00:00
dependabot[bot]
0991cc8a6f Bump enumflags2 from 0.7.5 to 0.7.7 (#408) 2023-04-24 20:37:00 +00:00
Brian Riccardi
ac6330fac8 Added support to 'mamba' (alternative to 'conda' with the exact same commands/interface) (#395) 2023-04-17 14:19:59 +00:00
dependabot[bot]
29f0d229d3 Bump h2 from 0.3.16 to 0.3.17 (#404) 2023-04-17 14:19:48 +00:00
Roey Darwish Dror
3dd11f7b52 No need to run self-update in Rustup (#403) 2023-04-05 12:42:47 +00:00
Roey Darwish Dror
ddb1a021bb Display the preamble in Linux only if notify-send is installed (#401) 2023-04-05 12:34:47 +00:00
PolpOnline
565aa405be Add no-self-update config and flag (#388) 2023-03-22 21:05:21 +00:00
Utkarsh Gupta
907465f891 run_custom_command: allow using interactive shell on unix (#383) 2023-03-17 16:28:58 +00:00
Trevor Sullivan
250485c826 Add Scoop manifest link for Windows installation (#384) 2023-03-15 07:40:31 +00:00
Thomas Schönauer
3a3f22b4e5 V10 3 2 bugfix + revert #347 (#382)
* Revert "run_custom_command: use interactive shell on unix (#347)"

This reverts commit d767ef31a5.

* v10-3-3 + revert of #347
2023-03-13 19:27:33 +00:00
Thomas de Queiroz Barros
a3628d0d49 Add sudo_command option (#379)
This allows the user to specify the preferred sudo command to be used
instead of the command chosen by Sudo::detect
2023-03-13 19:23:37 +00:00
Thomas Schönauer
462016e51e 10.3.2 patch (#378)
* 10.3.2 patch

* Clippy
2023-03-12 20:37:41 +00:00
Thomas Schönauer
199b81183b Update check-and-lint.yaml to use Rust version 1.68.0 2023-03-12 20:22:58 +00:00
Thomas Schönauer
342d7f7209 skip skip-notify warning on Win (#362) 2023-03-03 11:58:58 +00:00
Isaac Tay
9c2d121fc9 cargo: add cleanup step (using cargo-cache) (#371) 2023-03-03 11:58:15 +00:00
Roey Darwish Dror
7728819133 Support antidote (#368) 2023-02-26 21:45:43 +00:00
arctic-penguin
a5d5d987d2 pacdef: support new version 1.x (#364) 2023-02-23 22:01:53 +00:00
TGRCDev
fae5d80f0a pip3: Check for EXTERNALLY-MANAGED (PEP 668) (#367) 2023-02-23 22:01:26 +00:00
Thomas Schönauer
2369e371be apt: Recognise mist (#351) 2023-02-18 21:22:02 +00:00
Jason Stelzer
e3b71b647f Silence misleading warning on other platforms. (#353)

2023-02-07 17:21:15 +00:00
Guilherme Silva
e224ea38b3 CI: Update cross to v0.2.5 (#354) 2023-02-07 17:19:46 +00:00
Thomas Schönauer
8ec37bcd44 vim: Adds Astrovim support (#352) 2023-02-03 13:46:09 +00:00
Thomas Schönauer
6b7f6f4cc7 ruby_gems: Fixes asdf (#350) 2023-02-02 21:48:48 +00:00
Utkarsh Gupta
d767ef31a5 run_custom_command: use interactive shell on unix (#347) 2023-02-02 19:46:11 +00:00
Dan Sully
fcf776fe07 Add support for please (access elevation) (#310)
* Add support for please (access elevation)

Please is a sudo-like tool written in Rust.

https://gitlab.com/edneville/please

* Fixes code typo

---------

Co-authored-by: Thomas Schönauer <37108907+DottoDev@users.noreply.github.com>
Co-authored-by: Thomas Schönauer <t.schoenauer@hgs-wt.at>
2023-02-02 19:22:56 +00:00
edi
58060dda09 use documented way of updating (#344) 2023-01-31 22:19:01 +00:00
Thomas Schönauer
8cfc8d66be v10.3.1 patch (#342) 2023-01-30 21:24:06 +00:00
edi
9dcc8fdd0d (neo)vim: topgrade should only invoke plugin managers not plugins (#341)
* fix upgrade order of (n)vim plugins

* treesitter should use the synchronous cmd

* add lazy pkg manager for neovim

* fix lazy cmd

* change calls

* add autocmd, remove ts and coc

* fix vimscript err invalid range

---------

Co-authored-by: Thomas Schönauer <37108907+DottoDev@users.noreply.github.com>
2023-01-30 18:42:13 +00:00
Thomas Schönauer
828477b255 Update README.md 2023-01-30 18:41:48 +00:00
arctic-penguin
4eae1fedf7 fix ignored config display_preamble = false (#340)
Bug was introduced in f1e4009. Fixes #337.
2023-01-30 18:41:00 +00:00
Thomas Schönauer
1051e4cf47 AM fix + version bump (#335) 2023-01-29 21:53:26 +00:00
Thomas Schönauer
80a95cb404 Clippy (#331) 2023-01-29 19:31:37 +00:00
Thomas Schönauer
ab630cfbc6 v10.2.5 release (#330)
* Don't show desktop notification on error (if `skip_notify = true`) (#275)

* Use ─ (U+2500) to draw borders (#282)

* Adds Pclinuxos support (#283)

* Add Devkitpro Pacman support (#291)

* Added support for Neovim package manager lazy.nvim (#293)

* Added support for lazy.nvim

From https://github.com/folke/lazy.nvim
Authored-by: Jacob Lane Ledbetter <jledbetter460@gmail.com>

* Make garuda-update update AUR packages by default (#296)

* fix(#298): Don't throw error if no Helm repository found (#305)

* Skip .NET when `dotnet tool list` is not successful (#302)

* feat(pacstall): add `-y` flag variant (#312)

* Add openSUSE MicroOS support (#315)

* Adds notify-send timeout of 10s (#318)

* Don't run yum when rpm-ostree is available (#313)

* don't run yum when rpm-ostree is available

* Clippy fix

* rpm-ostree: set default value to true

* Fixes if loop error

* Fixes gem update --system requires sudo now (#317)

* Fixes gem update --system requires sudo now

* rubygem: Adds arg -EH to sudo

* Use fixed nala path instead of which(nala) (#314)

* Adds notify-send bug warning when topgrade is run (#324)

* Adds notify-send bug warning when topgrade is run

* fix typo + clippy

* notify-send warning respects skip_notify flag

* nix: Adds additional arguments support (#325)

* Adds pip-review and pipupgrade support (#316)

* Adds pip-review and pipupgrade support

* Python: fixes pip_review and pipupgrade

* v10.2.5 patch (#329)

* WSL: Adds new wsl --update flags (#327)

* wsl: Updates available flags

* Clippy fix

* Add WslUpdate runner

* wsl: Code Typo

* wsl: Code Typos

* wsl: Code Typos

* wsl: Code Typo

* Adds AM Package Manager (#328)

* Adds AM Package Manager

* Clippy fixes

* Cargo fmt

* Moves am to linux only in main file

---------

Co-authored-by: Guilherme Silva <626206+guihkx@users.noreply.github.com>
Co-authored-by: Gabriel Augendre <gabriel@augendre.info>
Co-authored-by: Cat Core <34719527+arthurbambou@users.noreply.github.com>
Co-authored-by: Hugo Haas <hugoh@hugoh.net>
Co-authored-by: Baptiste <32563450+BapRx@users.noreply.github.com>
Co-authored-by: bbx0 <39773919+bbx0@users.noreply.github.com>
Co-authored-by: Sourajyoti Basak <wiz28@protonmail.com>
2023-01-29 19:19:27 +00:00
edi
c13e14080c Add Lazy, a Neovim plugin manager (#326)
* fix upgrade order of (n)vim plugins

* treesitter should use the synchronous cmd

* add lazy pkg manager for neovim

* fix lazy cmd

---------

Co-authored-by: Thomas Schönauer <37108907+DottoDev@users.noreply.github.com>
2023-01-29 18:49:56 +00:00
edi
4abbee99cc fix upgrade order of (n)vim plugins (#322)
* fix upgrade order of (n)vim plugins

* treesitter should use the synchronous cmd

---------

Co-authored-by: Thomas Schönauer <37108907+DottoDev@users.noreply.github.com>
2023-01-27 21:41:48 +00:00
Giovanni Merlino
45d935eda3 Fix missing separator for Pkgin (pkgsrc) (#307)
* Fix missing separator for Pkgin (pkgsrc)

* Fix whitespace (giving issues with cargo fmt?)

---------

Co-authored-by: Thomas Schönauer <37108907+DottoDev@users.noreply.github.com>
2023-01-27 21:34:27 +00:00
pwygab
b4c5efde50 Fix small typo in warning when notify-send fails (#319)
Fix small typo
2023-01-27 20:52:20 +00:00
Roey Darwish Dror
cba9dc1c2c Fix windows build (#303) 2023-01-09 08:50:21 +01:00
dependabot[bot]
938647123c Bump tokio from 1.8.5 to 1.18.4 (#301) 2023-01-06 23:13:01 +00:00
Jacob Lane Ledbetter
9f24f6474e Add dnf config to config example (#292) 2022-12-30 22:52:04 +00:00
Thomas Schönauer
814e39644c fixes dotnet update + version bump 10.2.4 (#274) 2022-12-18 15:37:10 +00:00
32 changed files with 1181 additions and 612 deletions

View File

@@ -7,8 +7,8 @@ on:
name: CI name: CI
env: env:
RUST_VER: '1.60.0' RUST_VER: '1.68.0'
CROSS_VER: '0.2.4' CROSS_VER: '0.2.5'
CARGO_NET_RETRY: 3 CARGO_NET_RETRY: 3
jobs: jobs:

713
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,9 +4,8 @@ description = "Upgrade all the things"
categories = ["os"] categories = ["os"]
keywords = ["upgrade", "update"] keywords = ["upgrade", "update"]
license = "GPL-3.0" license = "GPL-3.0"
# license-file = "LICENSE"
repository = "https://github.com/topgrade-rs/topgrade" repository = "https://github.com/topgrade-rs/topgrade"
version = "10.2.3" version = "11.0.0"
authors = ["Roey Darwish Dror <roey.ghost@gmail.com>", "Thomas Schönauer <t.schoenauer@hgs-wt.at>"] authors = ["Roey Darwish Dror <roey.ghost@gmail.com>", "Thomas Schönauer <t.schoenauer@hgs-wt.at>"]
exclude = ["doc/screenshot.gif"] exclude = ["doc/screenshot.gif"]
edition = "2021" edition = "2021"
@@ -22,7 +21,8 @@ path = "src/main.rs"
[dependencies] [dependencies]
home = "~0.5" home = "~0.5"
directories = "~4.0" etcetera = "~0.8"
once_cell = "~1.17"
serde = { version = "~1.0", features = ["derive"] } serde = { version = "~1.0", features = ["derive"] }
toml = "0.5" toml = "0.5"
which_crate = { version = "~4.1", package = "which" } which_crate = { version = "~4.1", package = "which" }
@@ -39,7 +39,7 @@ strum = { version = "~0.24", features = ["derive"] }
thiserror = "~1.0" thiserror = "~1.0"
tempfile = "~3.2" tempfile = "~3.2"
cfg-if = "~1.0" cfg-if = "~1.0"
tokio = { version = "~1.8", features = ["process", "rt-multi-thread"] } tokio = { version = "~1.18", features = ["process", "rt-multi-thread"] }
futures = "~0.3" futures = "~0.3"
regex = "~1.5" regex = "~1.5"
semver = "~1.0" semver = "~1.0"

View File

@@ -1,4 +0,0 @@
# Workaround for: https://github.com/cross-rs/cross/issues/1100
# TODO: Remove this file altogether once a new version of cross (after v0.2.4) is released.
[target.x86_64-unknown-freebsd.env]
passthrough = ["AR_x86_64_unknown_freebsd=x86_64-unknown-freebsd12-ar"]

View File

@@ -10,7 +10,12 @@
<img alt="Demo" src="doc/screenshot.gif" width="550px"> <img alt="Demo" src="doc/screenshot.gif" width="550px">
</div> </div>
## Maintainers Wanted
I currently have not enough time to maintain this project on the level required and which the project deserves. For this reason I'm asking the community to help supporting the project, to help and work on resolving issues and create new features. Thanks for all your help.
## Introduction ## Introduction
> **Note** > **Note**
@@ -28,6 +33,7 @@ To remedy this, **Topgrade** detects which tools you use and runs the appropriat
- NixOS: [Nixpkgs](https://search.nixos.org/packages?show=topgrade) - NixOS: [Nixpkgs](https://search.nixos.org/packages?show=topgrade)
- Void Linux: [XBPS](https://voidlinux.org/packages/?arch=x86_64&q=topgrade) - Void Linux: [XBPS](https://voidlinux.org/packages/?arch=x86_64&q=topgrade)
- macOS: [Homebrew](https://formulae.brew.sh/formula/topgrade) or [MacPorts](https://ports.macports.org/port/topgrade/) - macOS: [Homebrew](https://formulae.brew.sh/formula/topgrade) or [MacPorts](https://ports.macports.org/port/topgrade/)
- Windows: [Scoop](https://github.com/ScoopInstaller/Main/blob/master/bucket/topgrade.json)
Other systems users can either use `cargo install` or the compiled binaries from the release page. Other systems users can either use `cargo install` or the compiled binaries from the release page.
The compiled binaries contain a self-upgrading feature. The compiled binaries contain a self-upgrading feature.
@@ -43,7 +49,7 @@ Visit the documentation at [topgrade-rs.github.io](https://topgrade-rs.github.io
> **Warning** > **Warning**
> Work in Progress > Work in Progress
## Customization ## Configuration
See `config.example.toml` for an example configuration file. See `config.example.toml` for an example configuration file.
@@ -54,6 +60,14 @@ The configuration should be placed in the following paths depending on the opera
- **Windows** - `%APPDATA%/topgrade.toml` - **Windows** - `%APPDATA%/topgrade.toml`
- **macOS** and **other Unix systems** - `${XDG_CONFIG_HOME:-~/.config}/topgrade.toml` - **macOS** and **other Unix systems** - `${XDG_CONFIG_HOME:-~/.config}/topgrade.toml`
### Custom Commands
Custom commands can be defined in the config file which can be run before, during, or after the inbuilt commands, as required.
By default, the custom commands are run using a new shell according to the `$SHELL` environment variable on unix (falls back to `sh`) or `pwsh` on windows (falls back to `powershell`).
On unix, if you want to run your command using an interactive shell, for example to source your shell's rc files, you can add `-i` at the start of your custom command.
But note that this requires the command to exit the shell correctly or else the shell will hang indefinitely.
## Remote Execution ## Remote Execution
You can specify a key called `remote_topgrades` in the configuration file. You can specify a key called `remote_topgrades` in the configuration file.
@@ -80,8 +94,6 @@ Just fork the repository and start coding.
- Check if your code passes `cargo fmt` and `cargo clippy`. - Check if your code passes `cargo fmt` and `cargo clippy`.
- Check if your code is self explanatory, if not it should be documented by comments. - Check if your code is self explanatory, if not it should be documented by comments.
- Make a pull request to the `dev` branch for new features or to the `bug-fixes` branch for bug fixes.
## Roadmap ## Roadmap
- [ ] Add a proper testing framework to the code base. - [ ] Add a proper testing framework to the code base.

View File

@@ -13,6 +13,9 @@
# Do not ask to retry failed steps (default: false) # Do not ask to retry failed steps (default: false)
#no_retry = true #no_retry = true
# Sudo command to be used
#sudo_command = "sudo"
# Run `sudo -v` to cache credentials at the start of the run; this avoids a # 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. # blocking password prompt in the middle of a possibly-unattended run.
#pre_sudo = false #pre_sudo = false
@@ -44,6 +47,12 @@
# Skip sending a notification at the end of a run # Skip sending a notification at the end of a run
#skip_notify = true #skip_notify = true
# Skip the preamble displayed when topgrade is run
#display_preamble = false
# 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] [git]
#max_concurrency = 5 #max_concurrency = 5
# Additional git repositories to pull # Additional git repositories to pull
@@ -68,18 +77,22 @@
# Custom commands # Custom commands
[commands] [commands]
#"Python Environment" = "~/dev/.env/bin/pip install -i https://pypi.python.org/simple -U --upgrade-strategy eager jupyter" #"Python Environment" = "~/dev/.env/bin/pip install -i https://pypi.python.org/simple -U --upgrade-strategy eager jupyter"
#"Custom command using interactive shell (unix)" = "-i vim_upgrade"
[brew] [brew]
#greedy_cask = true #greedy_cask = true
#autoremove = true #autoremove = true
[linux] [linux]
# Arch Package Manager to use. Allowed values: autodetect, trizen, aura, paru, yay, pikaur, pacman, pamac. # Arch Package Manager to use. Allowed values: autodetect, aura, garuda_update, pacman, pamac, paru, pikaur, trizen, yay.
#arch_package_manager = "pacman" #arch_package_manager = "pacman"
# Arguments to pass yay (or paru) when updating packages # Arguments to pass yay (or paru) when updating packages
#yay_arguments = "--nodevel" #yay_arguments = "--nodevel"
# Arguments to pass dnf when updating packages
#dnf_arguments = "--refresh"
#aura_aur_arguments = "-kx" #aura_aur_arguments = "-kx"
#aura_pacman_arguments = "" #aura_pacman_arguments = ""
#garuda_update_arguments = ""
#show_arch_news = true #show_arch_news = true
#trizen_arguments = "--devel" #trizen_arguments = "--devel"
#pikaur_arguments = "" #pikaur_arguments = ""
@@ -89,11 +102,18 @@
#emerge_update_flags = "-uDNa --with-bdeps=y world" #emerge_update_flags = "-uDNa --with-bdeps=y world"
#redhat_distro_sync = false #redhat_distro_sync = false
#rpm_ostree = false #rpm_ostree = false
#nix_arguments = "--flake"
[python]
#enable_pip_review = true ###disabled by default
#enable_pipupgrade = true ###disabled by default
[windows] [windows]
# Manually select Windows updates # Manually select Windows updates
#accept_all_updates = false #accept_all_updates = false
#open_remotes_in_new_terminal = true #open_remotes_in_new_terminal = true
#wsl_update_pre_release = true
#wsl_update_use_web_download = true
# Causes Topgrade to rename itself during the run to allow package managers # 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

View File

@@ -10,7 +10,7 @@ use clap_complete::Shell;
use color_eyre::eyre; use color_eyre::eyre;
use color_eyre::eyre::Context; use color_eyre::eyre::Context;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use directories::BaseDirs; use etcetera::base_strategy::BaseStrategy;
use regex::Regex; use regex::Regex;
use serde::Deserialize; use serde::Deserialize;
use strum::{EnumIter, EnumString, EnumVariantNames, IntoEnumIterator}; use strum::{EnumIter, EnumString, EnumVariantNames, IntoEnumIterator};
@@ -18,6 +18,7 @@ use tracing::debug;
use which_crate::which; use which_crate::which;
use crate::command::CommandExt; use crate::command::CommandExt;
use crate::sudo::SudoKind;
use super::utils::{editor, hostname}; use super::utils::{editor, hostname};
@@ -71,6 +72,7 @@ type Commands = BTreeMap<String, String>;
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
#[strum(serialize_all = "snake_case")] #[strum(serialize_all = "snake_case")]
pub enum Step { pub enum Step {
AM,
Asdf, Asdf,
Atom, Atom,
Bin, Bin,
@@ -89,6 +91,7 @@ pub enum Step {
DebGet, DebGet,
Deno, Deno,
Distrobox, Distrobox,
DkpPacman,
Dotnet, Dotnet,
Emacs, Emacs,
Firmware, Firmware,
@@ -113,6 +116,7 @@ pub enum Step {
Helix, Helix,
Krew, Krew,
Macports, Macports,
Mamba,
Mas, Mas,
Micro, Micro,
Myrepos, Myrepos,
@@ -123,6 +127,8 @@ pub enum Step {
Pacstall, Pacstall,
Pearl, Pearl,
Pip3, Pip3,
PipReview,
Pipupgrade,
Pipx, Pipx,
Pkg, Pkg,
Pkgin, Pkgin,
@@ -154,6 +160,7 @@ pub enum Step {
Vim, Vim,
Winget, Winget,
Wsl, Wsl,
WslUpdate,
Yadm, Yadm,
Yarn, Yarn,
} }
@@ -182,6 +189,15 @@ pub struct Windows {
self_rename: Option<bool>, self_rename: Option<bool>,
open_remotes_in_new_terminal: Option<bool>, open_remotes_in_new_terminal: Option<bool>,
enable_winget: Option<bool>, enable_winget: Option<bool>,
wsl_update_pre_release: Option<bool>,
wsl_update_use_web_download: Option<bool>,
}
#[derive(Deserialize, Default, Debug)]
#[serde(deny_unknown_fields)]
pub struct Python {
enable_pip_review: Option<bool>,
enable_pipupgrade: Option<bool>,
} }
#[derive(Deserialize, Default, Debug)] #[derive(Deserialize, Default, Debug)]
@@ -230,15 +246,15 @@ pub struct Brew {
#[derive(Debug, Deserialize, Clone, Copy)] #[derive(Debug, Deserialize, Clone, Copy)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub enum ArchPackageManager { pub enum ArchPackageManager {
GarudaUpdate,
Autodetect, Autodetect,
Trizen,
Paru,
Yay,
Pacman,
Pikaur,
Pamac,
Aura, Aura,
GarudaUpdate,
Pacman,
Pamac,
Paru,
Pikaur,
Trizen,
Yay,
} }
#[derive(Deserialize, Default, Debug)] #[derive(Deserialize, Default, Debug)]
@@ -249,10 +265,12 @@ pub struct Linux {
aura_pacman_arguments: Option<String>, aura_pacman_arguments: Option<String>,
arch_package_manager: Option<ArchPackageManager>, arch_package_manager: Option<ArchPackageManager>,
show_arch_news: Option<bool>, show_arch_news: Option<bool>,
garuda_update_arguments: Option<String>,
trizen_arguments: Option<String>, trizen_arguments: Option<String>,
pikaur_arguments: Option<String>, pikaur_arguments: Option<String>,
pamac_arguments: Option<String>, pamac_arguments: Option<String>,
dnf_arguments: Option<String>, dnf_arguments: Option<String>,
nix_arguments: Option<String>,
apt_arguments: Option<String>, apt_arguments: Option<String>,
enable_tlmgr: Option<bool>, enable_tlmgr: Option<bool>,
redhat_distro_sync: Option<bool>, redhat_distro_sync: Option<bool>,
@@ -277,6 +295,7 @@ pub struct Vim {
#[serde(deny_unknown_fields)] #[serde(deny_unknown_fields)]
/// Configuration file /// Configuration file
pub struct ConfigFile { pub struct ConfigFile {
sudo_command: Option<SudoKind>,
pre_sudo: Option<bool>, pre_sudo: Option<bool>,
pre_commands: Option<Commands>, pre_commands: Option<Commands>,
post_commands: Option<Commands>, post_commands: Option<Commands>,
@@ -292,10 +311,12 @@ pub struct ConfigFile {
tmux_arguments: Option<String>, tmux_arguments: Option<String>,
set_title: Option<bool>, set_title: Option<bool>,
display_time: Option<bool>, display_time: Option<bool>,
display_preamble: Option<bool>,
assume_yes: Option<bool>, assume_yes: Option<bool>,
yay_arguments: Option<String>, yay_arguments: Option<String>,
aura_aur_arguments: Option<String>, aura_aur_arguments: Option<String>,
aura_pacman_arguments: Option<String>, aura_pacman_arguments: Option<String>,
python: Option<Python>,
no_retry: Option<bool>, no_retry: Option<bool>,
run_in_tmux: Option<bool>, run_in_tmux: Option<bool>,
cleanup: Option<bool>, cleanup: Option<bool>,
@@ -316,19 +337,20 @@ pub struct ConfigFile {
vagrant: Option<Vagrant>, vagrant: Option<Vagrant>,
flatpak: Option<Flatpak>, flatpak: Option<Flatpak>,
distrobox: Option<Distrobox>, distrobox: Option<Distrobox>,
no_self_update: Option<bool>,
} }
fn config_directory(base_dirs: &BaseDirs) -> PathBuf { fn config_directory() -> PathBuf {
#[cfg(not(target_os = "macos"))] #[cfg(unix)]
return base_dirs.config_dir().to_owned(); return crate::XDG_DIRS.config_dir();
#[cfg(target_os = "macos")] #[cfg(windows)]
return base_dirs.home_dir().join(".config"); return crate::WINDOWS_DIRS.config_dir();
} }
impl ConfigFile { impl ConfigFile {
fn ensure(base_dirs: &BaseDirs) -> Result<PathBuf> { fn ensure() -> Result<PathBuf> {
let config_directory = config_directory(base_dirs); let config_directory = config_directory();
let config_path = config_directory.join("topgrade.toml"); let config_path = config_directory.join("topgrade.toml");
@@ -352,11 +374,11 @@ impl ConfigFile {
/// Read the configuration file. /// Read the configuration file.
/// ///
/// If the configuration file does not exist the function returns the default ConfigFile. /// If the configuration file does not exist the function returns the default ConfigFile.
fn read(base_dirs: &BaseDirs, config_path: Option<PathBuf>) -> Result<ConfigFile> { fn read(config_path: Option<PathBuf>) -> Result<ConfigFile> {
let config_path = if let Some(path) = config_path { let config_path = if let Some(path) = config_path {
path path
} else { } else {
Self::ensure(base_dirs)? Self::ensure()?
}; };
let contents = fs::read_to_string(&config_path).map_err(|e| { let contents = fs::read_to_string(&config_path).map_err(|e| {
@@ -390,8 +412,8 @@ impl ConfigFile {
Ok(result) Ok(result)
} }
fn edit(base_dirs: &BaseDirs) -> Result<()> { fn edit() -> Result<()> {
let config_path = Self::ensure(base_dirs)?; let config_path = Self::ensure()?;
let editor = editor(); let editor = editor();
debug!("Editor: {:?}", editor); debug!("Editor: {:?}", editor);
@@ -502,6 +524,10 @@ pub struct CommandLineArgs {
/// Print roff manpage and exit /// Print roff manpage and exit
#[clap(long, hide = true)] #[clap(long, hide = true)]
pub gen_manpage: bool, pub gen_manpage: bool,
/// Don't update Topgrade
#[clap(long = "no-self-update")]
pub no_self_update: bool,
} }
impl CommandLineArgs { impl CommandLineArgs {
@@ -541,10 +567,10 @@ impl Config {
/// Load the configuration. /// Load the configuration.
/// ///
/// The function parses the command line arguments and reading the configuration file. /// The function parses the command line arguments and reading the configuration file.
pub fn load(base_dirs: &BaseDirs, opt: CommandLineArgs) -> Result<Self> { pub fn load(opt: CommandLineArgs) -> Result<Self> {
let config_directory = config_directory(base_dirs); let config_directory = config_directory();
let config_file = if config_directory.is_dir() { let config_file = if config_directory.is_dir() {
ConfigFile::read(base_dirs, opt.config.clone()).unwrap_or_else(|e| { ConfigFile::read(opt.config.clone()).unwrap_or_else(|e| {
// Inform the user about errors when loading the configuration, // Inform the user about errors when loading the configuration,
// but fallback to the default config to at least attempt to do something // but fallback to the default config to at least attempt to do something
tracing::error!("failed to load configuration: {}", e); tracing::error!("failed to load configuration: {}", e);
@@ -571,8 +597,8 @@ impl Config {
} }
/// Launch an editor to edit the configuration /// Launch an editor to edit the configuration
pub fn edit(base_dirs: &BaseDirs) -> Result<()> { pub fn edit() -> Result<()> {
ConfigFile::edit(base_dirs) ConfigFile::edit()
} }
/// The list of commands to run before performing any step. /// The list of commands to run before performing any step.
@@ -625,6 +651,11 @@ impl Config {
enabled_steps enabled_steps
} }
/// Tell whether we should run a self update.
pub fn no_self_update(&self) -> bool {
self.opt.no_self_update || self.config_file.no_self_update.unwrap_or(false)
}
/// 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.config_file.run_in_tmux.unwrap_or(false) self.opt.run_in_tmux || self.config_file.run_in_tmux.unwrap_or(false)
@@ -739,6 +770,24 @@ impl Config {
.unwrap_or(false) .unwrap_or(false)
} }
// Should wsl --update should use the --pre-release flag
pub fn wsl_update_pre_release(&self) -> bool {
self.config_file
.windows
.as_ref()
.and_then(|w| w.wsl_update_pre_release)
.unwrap_or(false)
}
// Should wsl --update use the --web-download flag
pub fn wsl_update_use_web_download(&self) -> bool {
self.config_file
.windows
.as_ref()
.and_then(|w| w.wsl_update_use_web_download)
.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
@@ -780,6 +829,15 @@ impl Config {
self.config_file.notify_each_step.unwrap_or(false) self.config_file.notify_each_step.unwrap_or(false)
} }
/// Extra garuda-update arguments
pub fn garuda_update_arguments(&self) -> &str {
self.config_file
.linux
.as_ref()
.and_then(|s| s.garuda_update_arguments.as_deref())
.unwrap_or("")
}
/// Extra trizen arguments /// Extra trizen arguments
pub fn trizen_arguments(&self) -> &str { pub fn trizen_arguments(&self) -> &str {
self.config_file self.config_file
@@ -867,6 +925,14 @@ impl Config {
.and_then(|linux| linux.dnf_arguments.as_deref()) .and_then(|linux| linux.dnf_arguments.as_deref())
} }
/// Extra nix arguments
pub fn nix_arguments(&self) -> Option<&str> {
self.config_file
.linux
.as_ref()
.and_then(|linux| linux.nix_arguments.as_deref())
}
/// Distrobox use root /// Distrobox use root
pub fn distrobox_root(&self) -> bool { pub fn distrobox_root(&self) -> bool {
self.config_file self.config_file
@@ -964,6 +1030,10 @@ impl Config {
.unwrap_or(false) .unwrap_or(false)
} }
pub fn sudo_command(&self) -> Option<SudoKind> {
self.config_file.sudo_command
}
/// If `true`, `sudo` should be called after `pre_commands` in order to elevate at the /// If `true`, `sudo` should be called after `pre_commands` in order to elevate at the
/// start of the session (and not in the middle). /// start of the session (and not in the middle).
pub fn pre_sudo(&self) -> bool { pub fn pre_sudo(&self) -> bool {
@@ -1035,10 +1105,31 @@ impl Config {
.unwrap_or(false); .unwrap_or(false);
} }
pub fn enable_pipupgrade(&self) -> bool {
return self
.config_file
.python
.as_ref()
.and_then(|python| python.enable_pipupgrade)
.unwrap_or(false);
}
pub fn enable_pip_review(&self) -> bool {
return self
.config_file
.python
.as_ref()
.and_then(|python| python.enable_pip_review)
.unwrap_or(false);
}
pub fn display_time(&self) -> bool { pub fn display_time(&self) -> bool {
self.config_file.display_time.unwrap_or(true) self.config_file.display_time.unwrap_or(true)
} }
pub fn display_preamble(&self) -> bool {
self.config_file.display_preamble.unwrap_or(true)
}
pub fn should_run_custom_command(&self, name: &str) -> bool { pub fn should_run_custom_command(&self, name: &str) -> bool {
if self.opt.custom_commands.is_empty() { if self.opt.custom_commands.is_empty() {
return true; return true;

View File

@@ -5,7 +5,6 @@ use crate::sudo::Sudo;
use crate::utils::require_option; use crate::utils::require_option;
use crate::{config::Config, executor::Executor}; use crate::{config::Config, executor::Executor};
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use directories::BaseDirs;
use std::path::Path; use std::path::Path;
use std::sync::Mutex; use std::sync::Mutex;
@@ -14,7 +13,6 @@ pub struct ExecutionContext<'a> {
sudo: Option<Sudo>, sudo: Option<Sudo>,
git: &'a Git, git: &'a Git,
config: &'a Config, config: &'a Config,
base_dirs: &'a BaseDirs,
/// Name of a tmux session to execute commands in, if any. /// Name of a tmux session to execute commands in, if any.
/// This is used in `./steps/remote/ssh.rs`, where we want to run `topgrade` in a new /// This is used in `./steps/remote/ssh.rs`, where we want to run `topgrade` in a new
/// tmux window for each remote. /// tmux window for each remote.
@@ -22,19 +20,12 @@ pub struct ExecutionContext<'a> {
} }
impl<'a> ExecutionContext<'a> { impl<'a> ExecutionContext<'a> {
pub fn new( pub fn new(run_type: RunType, sudo: Option<Sudo>, git: &'a Git, config: &'a Config) -> Self {
run_type: RunType,
sudo: Option<Sudo>,
git: &'a Git,
config: &'a Config,
base_dirs: &'a BaseDirs,
) -> Self {
Self { Self {
run_type, run_type,
sudo, sudo,
git, git,
config, config,
base_dirs,
tmux_session: Mutex::new(None), tmux_session: Mutex::new(None),
} }
} }
@@ -60,10 +51,6 @@ impl<'a> ExecutionContext<'a> {
self.config self.config
} }
pub fn base_dirs(&self) -> &BaseDirs {
self.base_dirs
}
pub fn set_tmux_session(&self, session_name: String) { pub fn set_tmux_session(&self, session_name: String) {
self.tmux_session.lock().unwrap().replace(session_name); self.tmux_session.lock().unwrap().replace(session_name);
} }

View File

@@ -2,13 +2,19 @@
use std::env; use std::env;
use std::io; use std::io;
use std::path::PathBuf;
use std::process::exit; use std::process::exit;
use std::time::Duration;
use clap::CommandFactory; use clap::CommandFactory;
use clap::{crate_version, Parser}; use clap::{crate_version, Parser};
use color_eyre::eyre::Context; use color_eyre::eyre::Context;
use color_eyre::eyre::{eyre, Result}; use color_eyre::eyre::Result;
use console::Key; use console::Key;
#[cfg(windows)]
use etcetera::base_strategy::Windows;
use etcetera::base_strategy::{BaseStrategy, Xdg};
use once_cell::sync::Lazy;
use tracing::debug; use tracing::debug;
use self::config::{CommandLineArgs, Config, Step}; use self::config::{CommandLineArgs, Config, Step};
@@ -35,12 +41,15 @@ mod sudo;
mod terminal; mod terminal;
mod utils; mod utils;
pub static HOME_DIR: Lazy<PathBuf> = Lazy::new(|| home::home_dir().expect("No home directory"));
pub static XDG_DIRS: Lazy<Xdg> = Lazy::new(|| Xdg::new().expect("No home directory"));
#[cfg(windows)]
pub static WINDOWS_DIRS: Lazy<Windows> = Lazy::new(|| Windows::new().expect("No home directory"));
fn run() -> Result<()> { fn run() -> Result<()> {
color_eyre::install()?; color_eyre::install()?;
ctrlc::set_handler(); ctrlc::set_handler();
let base_dirs = directories::BaseDirs::new().ok_or_else(|| eyre!("No base directories"))?;
let opt = CommandLineArgs::parse(); let opt = CommandLineArgs::parse();
if let Some(shell) = opt.gen_completion { if let Some(shell) = opt.gen_completion {
@@ -65,7 +74,7 @@ fn run() -> Result<()> {
} }
if opt.edit_config() { if opt.edit_config() {
Config::edit(&base_dirs)?; Config::edit()?;
return Ok(()); return Ok(());
}; };
@@ -74,7 +83,7 @@ fn run() -> Result<()> {
return Ok(()); return Ok(());
} }
let config = Config::load(&base_dirs, opt)?; let config = Config::load(opt)?;
terminal::set_title(config.set_title()); terminal::set_title(config.set_title());
terminal::display_time(config.display_time()); terminal::display_time(config.display_time());
terminal::set_desktop_notifications(config.notify_each_step()); terminal::set_desktop_notifications(config.notify_each_step());
@@ -85,6 +94,16 @@ fn run() -> Result<()> {
debug!("Binary path: {:?}", std::env::current_exe()); debug!("Binary path: {:?}", std::env::current_exe());
debug!("Self Update: {:?}", cfg!(feature = "self-update")); debug!("Self Update: {:?}", cfg!(feature = "self-update"));
#[cfg(target_os = "linux")]
{
if config.display_preamble() && terminal::supports_notify_send() && !config.skip_notify() {
print_warning("Due to a design issue with notify-send it could be that topgrade hangs when it's finished.
If this is the case on your system add the --skip-notify flag to the topgrade command or set skip_notify = true in the config file.
If you don't want this message to appear any longer set display_preamble = false in the config file.
For more information about this issue see https://askubuntu.com/questions/110969/notify-send-ignores-timeout and https://github.com/topgrade-rs/topgrade/issues/288.");
}
}
if config.run_in_tmux() && env::var("TOPGRADE_INSIDE_TMUX").is_err() { if config.run_in_tmux() && env::var("TOPGRADE_INSIDE_TMUX").is_err() {
#[cfg(unix)] #[cfg(unix)]
{ {
@@ -96,16 +115,18 @@ fn run() -> Result<()> {
let git = git::Git::new(); let git = git::Git::new();
let mut git_repos = git::Repositories::new(&git); let mut git_repos = git::Repositories::new(&git);
let sudo = sudo::Sudo::detect(); let sudo = config.sudo_command().map_or_else(sudo::Sudo::detect, sudo::Sudo::new);
let run_type = executor::RunType::new(config.dry_run()); let run_type = executor::RunType::new(config.dry_run());
let ctx = execution_context::ExecutionContext::new(run_type, sudo, &git, &config, &base_dirs); let ctx = execution_context::ExecutionContext::new(run_type, sudo, &git, &config);
let mut runner = runner::Runner::new(&ctx); let mut runner = runner::Runner::new(&ctx);
#[cfg(feature = "self-update")] #[cfg(feature = "self-update")]
{ {
if !run_type.dry() && env::var("TOPGRADE_NO_SELF_UPGRADE").is_err() { let config_self_upgrade = env::var("TOPGRADE_NO_SELF_UPGRADE").is_err() && !config.no_self_update();
if !run_type.dry() && config_self_upgrade {
let result = self_update::self_update(); let result = self_update::self_update();
if let Err(e) = &result { if let Err(e) = &result {
@@ -115,7 +136,7 @@ fn run() -> Result<()> {
return result; return result;
} }
} }
print_warning(format!("Self update error: {}", e)); print_warning(format!("Self update error: {e}"));
} }
} }
} }
@@ -145,9 +166,12 @@ fn run() -> Result<()> {
#[cfg(windows)] #[cfg(windows)]
runner.execute(Step::Wsl, "WSL", || windows::run_wsl_topgrade(&ctx))?; runner.execute(Step::Wsl, "WSL", || windows::run_wsl_topgrade(&ctx))?;
#[cfg(windows)]
runner.execute(Step::WslUpdate, "WSL", || windows::update_wsl(&ctx))?;
if let Some(topgrades) = config.remote_topgrades() { if let Some(topgrades) = config.remote_topgrades() {
for remote_topgrade in topgrades.iter().filter(|t| config.should_execute_remote(t)) { for remote_topgrade in topgrades.iter().filter(|t| config.should_execute_remote(t)) {
runner.execute(Step::Remotes, format!("Remote ({})", remote_topgrade), || { runner.execute(Step::Remotes, format!("Remote ({remote_topgrade})"), || {
remote::ssh::ssh_step(&ctx, remote_topgrade) remote::ssh::ssh_step(&ctx, remote_topgrade)
})?; })?;
} }
@@ -163,7 +187,7 @@ fn run() -> Result<()> {
runner.execute(Step::System, "System update", || distribution.upgrade(&ctx))?; runner.execute(Step::System, "System update", || distribution.upgrade(&ctx))?;
} }
Err(e) => { Err(e) => {
println!("Error detecting current distribution: {}", e); println!("Error detecting current distribution: {e}");
} }
} }
runner.execute(Step::ConfigUpdate, "config-update", || linux::run_config_update(&ctx))?; runner.execute(Step::ConfigUpdate, "config-update", || linux::run_config_update(&ctx))?;
@@ -233,7 +257,7 @@ fn run() -> Result<()> {
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
runner.execute(Step::Pkg, "Termux Packages", || android::upgrade_packages(&ctx))?; runner.execute(Step::Pkg, "Termux Packages", || android::upgrade_packages(&ctx))?;
let emacs = emacs::Emacs::new(&base_dirs); let emacs = emacs::Emacs::new();
if config.use_predefined_git_repos() { if config.use_predefined_git_repos() {
if config.should_run(Step::Emacs) { if config.should_run(Step::Emacs) {
if !emacs.is_doom() { if !emacs.is_doom() {
@@ -241,43 +265,43 @@ fn run() -> Result<()> {
git_repos.insert_if_repo(directory); git_repos.insert_if_repo(directory);
} }
} }
git_repos.insert_if_repo(base_dirs.home_dir().join(".doom.d")); git_repos.insert_if_repo(HOME_DIR.join(".doom.d"));
} }
if config.should_run(Step::Vim) { if config.should_run(Step::Vim) {
git_repos.insert_if_repo(base_dirs.home_dir().join(".vim")); git_repos.insert_if_repo(HOME_DIR.join(".vim"));
git_repos.insert_if_repo(base_dirs.home_dir().join(".config/nvim")); git_repos.insert_if_repo(HOME_DIR.join(".config/nvim"));
} }
git_repos.insert_if_repo(base_dirs.home_dir().join(".ideavimrc")); git_repos.insert_if_repo(HOME_DIR.join(".ideavimrc"));
git_repos.insert_if_repo(base_dirs.home_dir().join(".intellimacs")); git_repos.insert_if_repo(HOME_DIR.join(".intellimacs"));
if config.should_run(Step::Rcm) { if config.should_run(Step::Rcm) {
git_repos.insert_if_repo(base_dirs.home_dir().join(".dotfiles")); git_repos.insert_if_repo(HOME_DIR.join(".dotfiles"));
} }
#[cfg(unix)] #[cfg(unix)]
{ {
git_repos.insert_if_repo(zsh::zshrc(&base_dirs)); git_repos.insert_if_repo(zsh::zshrc());
if config.should_run(Step::Tmux) { if config.should_run(Step::Tmux) {
git_repos.insert_if_repo(base_dirs.home_dir().join(".tmux")); git_repos.insert_if_repo(HOME_DIR.join(".tmux"));
} }
git_repos.insert_if_repo(base_dirs.home_dir().join(".config/fish")); git_repos.insert_if_repo(HOME_DIR.join(".config/fish"));
git_repos.insert_if_repo(base_dirs.config_dir().join("openbox")); git_repos.insert_if_repo(XDG_DIRS.config_dir().join("openbox"));
git_repos.insert_if_repo(base_dirs.config_dir().join("bspwm")); git_repos.insert_if_repo(XDG_DIRS.config_dir().join("bspwm"));
git_repos.insert_if_repo(base_dirs.config_dir().join("i3")); git_repos.insert_if_repo(XDG_DIRS.config_dir().join("i3"));
git_repos.insert_if_repo(base_dirs.config_dir().join("sway")); git_repos.insert_if_repo(XDG_DIRS.config_dir().join("sway"));
} }
#[cfg(windows)] #[cfg(windows)]
git_repos.insert_if_repo( git_repos.insert_if_repo(
base_dirs WINDOWS_DIRS
.data_local_dir() .cache_dir()
.join("Packages/Microsoft.WindowsTerminal_8wekyb3d8bbwe/LocalState"), .join("Packages/Microsoft.WindowsTerminal_8wekyb3d8bbwe/LocalState"),
); );
#[cfg(windows)] #[cfg(windows)]
windows::insert_startup_scripts(&ctx, &mut git_repos).ok(); windows::insert_startup_scripts(&mut git_repos).ok();
if let Some(profile) = powershell.profile() { if let Some(profile) = powershell.profile() {
git_repos.insert_if_repo(profile); git_repos.insert_if_repo(profile);
@@ -303,30 +327,29 @@ fn run() -> Result<()> {
#[cfg(unix)] #[cfg(unix)]
{ {
runner.execute(Step::Shell, "zr", || zsh::run_zr(&base_dirs, run_type))?; runner.execute(Step::Shell, "zr", || zsh::run_zr(run_type))?;
runner.execute(Step::Shell, "antibody", || zsh::run_antibody(run_type))?; runner.execute(Step::Shell, "antibody", || zsh::run_antibody(run_type))?;
runner.execute(Step::Shell, "antigen", || zsh::run_antigen(&base_dirs, run_type))?; runner.execute(Step::Shell, "antidote", || zsh::run_antidote(&ctx))?;
runner.execute(Step::Shell, "zgenom", || zsh::run_zgenom(&base_dirs, run_type))?; runner.execute(Step::Shell, "antigen", || zsh::run_antigen(run_type))?;
runner.execute(Step::Shell, "zplug", || zsh::run_zplug(&base_dirs, run_type))?; runner.execute(Step::Shell, "zgenom", || zsh::run_zgenom(run_type))?;
runner.execute(Step::Shell, "zinit", || zsh::run_zinit(&base_dirs, run_type))?; runner.execute(Step::Shell, "zplug", || zsh::run_zplug(run_type))?;
runner.execute(Step::Shell, "zi", || zsh::run_zi(&base_dirs, run_type))?; runner.execute(Step::Shell, "zinit", || zsh::run_zinit(run_type))?;
runner.execute(Step::Shell, "zim", || zsh::run_zim(&base_dirs, run_type))?; runner.execute(Step::Shell, "zi", || zsh::run_zi(run_type))?;
runner.execute(Step::Shell, "zim", || zsh::run_zim(run_type))?;
runner.execute(Step::Shell, "oh-my-zsh", || zsh::run_oh_my_zsh(&ctx))?; runner.execute(Step::Shell, "oh-my-zsh", || zsh::run_oh_my_zsh(&ctx))?;
runner.execute(Step::Shell, "fisher", || unix::run_fisher(run_type))?; runner.execute(Step::Shell, "fisher", || unix::run_fisher(run_type))?;
runner.execute(Step::Shell, "bash-it", || unix::run_bashit(&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, "oh-my-fish", || unix::run_oh_my_fish(&ctx))?;
runner.execute(Step::Shell, "fish-plug", || unix::run_fish_plug(&ctx))?; runner.execute(Step::Shell, "fish-plug", || unix::run_fish_plug(&ctx))?;
runner.execute(Step::Shell, "fundle", || unix::run_fundle(&ctx))?; runner.execute(Step::Shell, "fundle", || unix::run_fundle(&ctx))?;
runner.execute(Step::Tmux, "tmux", || tmux::run_tpm(&base_dirs, run_type))?; runner.execute(Step::Tmux, "tmux", || tmux::run_tpm(run_type))?;
runner.execute(Step::Tldr, "TLDR", || unix::run_tldr(run_type))?; runner.execute(Step::Tldr, "TLDR", || unix::run_tldr(run_type))?;
runner.execute(Step::Pearl, "pearl", || unix::run_pearl(run_type))?; runner.execute(Step::Pearl, "pearl", || unix::run_pearl(run_type))?;
#[cfg(not(any(target_os = "macos", target_os = "android")))] #[cfg(not(any(target_os = "macos", target_os = "android")))]
runner.execute(Step::GnomeShellExtensions, "Gnome Shell Extensions", || { runner.execute(Step::GnomeShellExtensions, "Gnome Shell Extensions", || {
unix::upgrade_gnome_extensions(&ctx) unix::upgrade_gnome_extensions(&ctx)
})?; })?;
runner.execute(Step::Sdkman, "SDKMAN!", || { runner.execute(Step::Sdkman, "SDKMAN!", || unix::run_sdkman(config.cleanup(), run_type))?;
unix::run_sdkman(&base_dirs, config.cleanup(), run_type)
})?;
runner.execute(Step::Rcm, "rcm", || unix::run_rcm(&ctx))?; runner.execute(Step::Rcm, "rcm", || unix::run_rcm(&ctx))?;
} }
@@ -338,8 +361,8 @@ fn run() -> Result<()> {
)))] )))]
runner.execute(Step::Atom, "apm", || generic::run_apm(run_type))?; runner.execute(Step::Atom, "apm", || generic::run_apm(run_type))?;
runner.execute(Step::Fossil, "fossil", || generic::run_fossil(run_type))?; runner.execute(Step::Fossil, "fossil", || generic::run_fossil(run_type))?;
runner.execute(Step::Rustup, "rustup", || generic::run_rustup(&base_dirs, run_type))?; runner.execute(Step::Rustup, "rustup", || generic::run_rustup(&ctx))?;
runner.execute(Step::Juliaup, "juliaup", || generic::run_juliaup(&base_dirs, run_type))?; runner.execute(Step::Juliaup, "juliaup", || generic::run_juliaup(run_type))?;
runner.execute(Step::Dotnet, ".NET", || generic::run_dotnet_upgrade(&ctx))?; runner.execute(Step::Dotnet, ".NET", || generic::run_dotnet_upgrade(&ctx))?;
runner.execute(Step::Choosenim, "choosenim", || generic::run_choosenim(&ctx))?; runner.execute(Step::Choosenim, "choosenim", || generic::run_choosenim(&ctx))?;
runner.execute(Step::Cargo, "cargo", || generic::run_cargo_update(&ctx))?; runner.execute(Step::Cargo, "cargo", || generic::run_cargo_update(&ctx))?;
@@ -351,21 +374,20 @@ fn run() -> Result<()> {
runner.execute(Step::Vcpkg, "vcpkg", || generic::run_vcpkg_update(&ctx))?; runner.execute(Step::Vcpkg, "vcpkg", || generic::run_vcpkg_update(&ctx))?;
runner.execute(Step::Pipx, "pipx", || generic::run_pipx_update(run_type))?; runner.execute(Step::Pipx, "pipx", || generic::run_pipx_update(run_type))?;
runner.execute(Step::Conda, "conda", || generic::run_conda_update(&ctx))?; runner.execute(Step::Conda, "conda", || generic::run_conda_update(&ctx))?;
runner.execute(Step::Mamba, "mamba", || generic::run_mamba_update(&ctx))?;
runner.execute(Step::Pip3, "pip3", || generic::run_pip3_update(run_type))?; runner.execute(Step::Pip3, "pip3", || generic::run_pip3_update(run_type))?;
runner.execute(Step::PipReview, "pip-review", || generic::run_pip_review_update(&ctx))?;
runner.execute(Step::Pipupgrade, "pipupgrade", || generic::run_pipupgrade_update(&ctx))?;
runner.execute(Step::Ghcup, "ghcup", || generic::run_ghcup_update(run_type))?; runner.execute(Step::Ghcup, "ghcup", || generic::run_ghcup_update(run_type))?;
runner.execute(Step::Stack, "stack", || generic::run_stack_update(run_type))?; runner.execute(Step::Stack, "stack", || generic::run_stack_update(run_type))?;
runner.execute(Step::Tlmgr, "tlmgr", || generic::run_tlmgr_update(&ctx))?; runner.execute(Step::Tlmgr, "tlmgr", || generic::run_tlmgr_update(&ctx))?;
runner.execute(Step::Myrepos, "myrepos", || { runner.execute(Step::Myrepos, "myrepos", || generic::run_myrepos_update(run_type))?;
generic::run_myrepos_update(&base_dirs, run_type) runner.execute(Step::Chezmoi, "chezmoi", || generic::run_chezmoi_update(run_type))?;
})?;
runner.execute(Step::Chezmoi, "chezmoi", || {
generic::run_chezmoi_update(&base_dirs, run_type)
})?;
runner.execute(Step::Jetpack, "jetpack", || generic::run_jetpack(run_type))?; runner.execute(Step::Jetpack, "jetpack", || generic::run_jetpack(run_type))?;
runner.execute(Step::Vim, "vim", || vim::upgrade_vim(&base_dirs, &ctx))?; runner.execute(Step::Vim, "vim", || vim::upgrade_vim(&ctx))?;
runner.execute(Step::Vim, "Neovim", || vim::upgrade_neovim(&base_dirs, &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, "The Ultimate vimrc", || vim::upgrade_ultimate_vimrc(&ctx))?;
runner.execute(Step::Vim, "voom", || vim::run_voom(&base_dirs, run_type))?; runner.execute(Step::Vim, "voom", || vim::run_voom(run_type))?;
runner.execute(Step::Kakoune, "Kakoune", || kakoune::upgrade_kak_plug(&ctx))?; runner.execute(Step::Kakoune, "Kakoune", || kakoune::upgrade_kak_plug(&ctx))?;
runner.execute(Step::Helix, "helix", || generic::run_helix_grammars(&ctx))?; runner.execute(Step::Helix, "helix", || generic::run_helix_grammars(&ctx))?;
runner.execute(Step::Node, "npm", || node::run_npm_upgrade(&ctx))?; runner.execute(Step::Node, "npm", || node::run_npm_upgrade(&ctx))?;
@@ -376,10 +398,8 @@ fn run() -> Result<()> {
runner.execute(Step::Composer, "composer", || generic::run_composer_update(&ctx))?; runner.execute(Step::Composer, "composer", || generic::run_composer_update(&ctx))?;
runner.execute(Step::Krew, "krew", || generic::run_krew_upgrade(run_type))?; runner.execute(Step::Krew, "krew", || generic::run_krew_upgrade(run_type))?;
runner.execute(Step::Helm, "helm", || generic::run_helm_repo_update(run_type))?; runner.execute(Step::Helm, "helm", || generic::run_helm_repo_update(run_type))?;
runner.execute(Step::Gem, "gem", || generic::run_gem(&base_dirs, run_type))?; runner.execute(Step::Gem, "gem", || generic::run_gem(run_type))?;
runner.execute(Step::RubyGems, "rubygems", || { runner.execute(Step::RubyGems, "rubygems", || generic::run_rubygems(&ctx))?;
generic::run_rubygems(&base_dirs, run_type)
})?;
runner.execute(Step::Julia, "julia", || generic::update_julia_packages(&ctx))?; runner.execute(Step::Julia, "julia", || generic::update_julia_packages(&ctx))?;
runner.execute(Step::Haxelib, "haxelib", || generic::run_haxelib_update(&ctx))?; runner.execute(Step::Haxelib, "haxelib", || generic::run_haxelib_update(&ctx))?;
runner.execute(Step::Sheldon, "sheldon", || generic::run_sheldon(&ctx))?; runner.execute(Step::Sheldon, "sheldon", || generic::run_sheldon(&ctx))?;
@@ -397,6 +417,7 @@ fn run() -> Result<()> {
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
{ {
runner.execute(Step::AM, "am", || linux::update_am(&ctx))?;
runner.execute(Step::DebGet, "deb-get", || linux::run_deb_get(&ctx))?; runner.execute(Step::DebGet, "deb-get", || linux::run_deb_get(&ctx))?;
runner.execute(Step::Toolbx, "toolbx", || toolbx::run_toolbx(&ctx))?; runner.execute(Step::Toolbx, "toolbx", || toolbx::run_toolbx(&ctx))?;
runner.execute(Step::Flatpak, "Flatpak", || linux::flatpak_update(&ctx))?; runner.execute(Step::Flatpak, "Flatpak", || linux::flatpak_update(&ctx))?;
@@ -405,6 +426,7 @@ fn run() -> Result<()> {
runner.execute(Step::Pacdef, "pacdef", || linux::run_pacdef(&ctx))?; runner.execute(Step::Pacdef, "pacdef", || linux::run_pacdef(&ctx))?;
runner.execute(Step::Protonup, "protonup", || linux::run_protonup_update(&ctx))?; runner.execute(Step::Protonup, "protonup", || linux::run_protonup_update(&ctx))?;
runner.execute(Step::Distrobox, "distrobox", || linux::run_distrobox_update(&ctx))?; runner.execute(Step::Distrobox, "distrobox", || linux::run_distrobox_update(&ctx))?;
runner.execute(Step::DkpPacman, "dkp-pacman", || linux::run_dkp_pacman_update(&ctx))?;
} }
if let Some(commands) = config.commands() { if let Some(commands) = config.commands() {
@@ -516,8 +538,8 @@ fn run() -> Result<()> {
"Topgrade finished {}", "Topgrade finished {}",
if failed { "with errors" } else { "successfully" } if failed { "with errors" } else { "successfully" }
), ),
None, Some(Duration::from_secs(10)),
); )
} }
if failed { if failed {
@@ -550,7 +572,7 @@ fn main() {
// The `Debug` implementation of `eyre::Result` prints a multi-line // The `Debug` implementation of `eyre::Result` prints a multi-line
// error message that includes all the 'causes' added with // error message that includes all the 'causes' added with
// `.with_context(...)` calls. // `.with_context(...)` calls.
println!("Error: {:?}", error); println!("Error: {error:?}");
} }
exit(1); exit(1);
} }

View File

@@ -34,7 +34,7 @@ impl<'a> Report<'a> {
if let Some((key, success)) = result { if let Some((key, success)) = result {
let key = key.into(); let key = key.into();
debug_assert!(!self.data.iter().any(|(k, _)| k == &key), "{} already reported", key); debug_assert!(!self.data.iter().any(|(k, _)| k == &key), "{key} already reported");
self.data.push((key, success)); self.data.push((key, success));
} }
} }

View File

@@ -31,7 +31,7 @@ pub fn self_update() -> Result<()> {
if let UpdateStatus::Updated(release) = &result { if let UpdateStatus::Updated(release) = &result {
println!("\nTopgrade upgraded to {}:\n", release.version); println!("\nTopgrade upgraded to {}:\n", release.version);
if let Some(body) = &release.body { if let Some(body) = &release.body {
println!("{}", body); println!("{body}");
} }
} else { } else {
println!("Topgrade is up-to-date"); println!("Topgrade is up-to-date");

View File

@@ -1,9 +1,9 @@
#[cfg(any(windows, target_os = "macos"))] #[cfg(any(windows))]
use std::env; use std::env;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use directories::BaseDirs; use etcetera::base_strategy::BaseStrategy;
use crate::command::CommandExt; use crate::command::CommandExt;
use crate::execution_context::ExecutionContext; use crate::execution_context::ExecutionContext;
@@ -23,20 +23,12 @@ pub struct Emacs {
} }
impl Emacs { impl Emacs {
fn directory_path(base_dirs: &BaseDirs) -> Option<PathBuf> { fn directory_path() -> Option<PathBuf> {
#[cfg(unix)] #[cfg(unix)]
cfg_if::cfg_if! { return {
if #[cfg(target_os = "macos")] { let emacs_xdg_dir = crate::XDG_DIRS.config_dir().join("emacs").if_exists();
let emacs_xdg_dir = env::var("XDG_CONFIG_HOME") crate::HOME_DIR.join(".emacs.d").if_exists().or(emacs_xdg_dir)
.ok() };
.and_then(|config| PathBuf::from(config).join("emacs").if_exists())
.or_else(|| base_dirs.home_dir().join(".config/emacs").if_exists());
} else {
let emacs_xdg_dir = base_dirs.config_dir().join("emacs").if_exists();
}
}
#[cfg(unix)]
return base_dirs.home_dir().join(".emacs.d").if_exists().or(emacs_xdg_dir);
#[cfg(windows)] #[cfg(windows)]
return env::var("HOME") return env::var("HOME")
@@ -47,11 +39,11 @@ impl Emacs {
.if_exists() .if_exists()
.or_else(|| PathBuf::from(&home).join(".config\\emacs").if_exists()) .or_else(|| PathBuf::from(&home).join(".config\\emacs").if_exists())
}) })
.or_else(|| base_dirs.data_dir().join(".emacs.d").if_exists()); .or_else(|| crate::WINDOWS_DIRS.data_dir().join(".emacs.d").if_exists());
} }
pub fn new(base_dirs: &BaseDirs) -> Self { pub fn new() -> Self {
let directory = Emacs::directory_path(base_dirs); let directory = Emacs::directory_path();
let doom = directory.as_ref().and_then(|d| d.join(DOOM_PATH).if_exists()); let doom = directory.as_ref().and_then(|d| d.join(DOOM_PATH).if_exists());
Self { directory, doom } Self { directory, doom }
} }

View File

@@ -8,24 +8,25 @@ use std::{fs, io::Write};
use color_eyre::eyre::eyre; use color_eyre::eyre::eyre;
use color_eyre::eyre::Context; use color_eyre::eyre::Context;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use directories::BaseDirs;
use tempfile::tempfile_in; use tempfile::tempfile_in;
use tracing::debug; use tracing::{debug, error};
use crate::command::{CommandExt, Utf8Output}; use crate::command::{CommandExt, Utf8Output};
use crate::execution_context::ExecutionContext; use crate::execution_context::ExecutionContext;
use crate::executor::{ExecutorOutput, RunType}; use crate::executor::{ExecutorOutput, RunType};
use crate::terminal::{print_separator, shell}; use crate::terminal::{print_separator, shell};
use crate::utils::{self, require_option, PathExt}; use crate::utils::{self, require, require_option, which, PathExt};
use crate::Step;
use crate::HOME_DIR;
use crate::{ use crate::{
error::{SkipStep, TopgradeError}, error::{SkipStep, StepFailed, TopgradeError},
terminal::print_warning, terminal::print_warning,
}; };
pub fn run_cargo_update(ctx: &ExecutionContext) -> Result<()> { pub fn run_cargo_update(ctx: &ExecutionContext) -> Result<()> {
let cargo_dir = env::var_os("CARGO_HOME") let cargo_dir = env::var_os("CARGO_HOME")
.map(PathBuf::from) .map(PathBuf::from)
.unwrap_or_else(|| ctx.base_dirs().home_dir().join(".cargo")) .unwrap_or_else(|| HOME_DIR.join(".cargo"))
.require()?; .require()?;
utils::require("cargo").or_else(|_| { utils::require("cargo").or_else(|_| {
require_option( require_option(
@@ -56,7 +57,24 @@ pub fn run_cargo_update(ctx: &ExecutionContext) -> Result<()> {
ctx.run_type() ctx.run_type()
.execute(cargo_update) .execute(cargo_update)
.args(["install-update", "--git", "--all"]) .args(["install-update", "--git", "--all"])
.status_checked() .status_checked()?;
if ctx.config().cleanup() {
let cargo_cache = utils::require("cargo-cache")
.ok()
.or_else(|| cargo_dir.join("bin/cargo-cache").if_exists());
match cargo_cache {
Some(e) => {
ctx.run_type().execute(e).args(["-a"]).status_checked()?;
}
None => {
let message = String::from("cargo-cache isn't installed so Topgrade can't cleanup cargo packages.\nInstall cargo-cache by running `cargo install cargo-cache`");
print_warning(message);
}
}
}
Ok(())
} }
pub fn run_flutter_upgrade(run_type: RunType) -> Result<()> { pub fn run_flutter_upgrade(run_type: RunType) -> Result<()> {
@@ -66,9 +84,9 @@ pub fn run_flutter_upgrade(run_type: RunType) -> Result<()> {
run_type.execute(flutter).arg("upgrade").status_checked() run_type.execute(flutter).arg("upgrade").status_checked()
} }
pub fn run_gem(base_dirs: &BaseDirs, run_type: RunType) -> Result<()> { pub fn run_gem(run_type: RunType) -> Result<()> {
let gem = utils::require("gem")?; let gem = utils::require("gem")?;
base_dirs.home_dir().join(".gem").require()?; HOME_DIR.join(".gem").require()?;
print_separator("Gems"); print_separator("Gems");
@@ -83,17 +101,30 @@ pub fn run_gem(base_dirs: &BaseDirs, run_type: RunType) -> Result<()> {
command.status_checked() command.status_checked()
} }
pub fn run_rubygems(base_dirs: &BaseDirs, run_type: RunType) -> Result<()> { pub fn run_rubygems(ctx: &ExecutionContext) -> Result<()> {
let gem = utils::require("gem")?; HOME_DIR.join(".gem").require()?;
base_dirs.home_dir().join(".gem").require()?; let gem = require("gem")?;
print_separator("RubyGems"); print_separator("RubyGems");
let gem_path_str = gem.as_os_str();
if !std::path::Path::new("/usr/lib/ruby/vendor_ruby/rubygems/defaults/operating_system.rb").exists() { if gem_path_str.to_str().unwrap().contains("asdf") {
run_type.execute(gem).args(["update", "--system"]).status_checked() ctx.run_type()
.execute(gem)
.args(["update", "--system"])
.status_checked()?;
} else if let Some(sudo) = &ctx.sudo() {
if !std::path::Path::new("/usr/lib/ruby/vendor_ruby/rubygems/defaults/operating_system.rb").exists() {
ctx.run_type()
.execute(sudo)
.arg("-EH")
.arg(gem)
.args(["update", "--system"])
.status_checked()?;
}
} else { } else {
Ok(()) print_warning("No sudo detected. Skipping system upgrade");
} }
Ok(())
} }
pub fn run_haxelib_update(ctx: &ExecutionContext) -> Result<()> { pub fn run_haxelib_update(ctx: &ExecutionContext) -> Result<()> {
@@ -176,24 +207,19 @@ pub fn run_apm(run_type: RunType) -> Result<()> {
.status_checked() .status_checked()
} }
pub fn run_rustup(base_dirs: &BaseDirs, run_type: RunType) -> Result<()> { pub fn run_rustup(ctx: &ExecutionContext) -> Result<()> {
let rustup = utils::require("rustup")?; let rustup = utils::require("rustup")?;
print_separator("rustup"); print_separator("rustup");
ctx.run_type().execute(rustup).arg("update").status_checked()
if rustup.canonicalize()?.is_descendant_of(base_dirs.home_dir()) {
run_type.execute(&rustup).args(["self", "update"]).status_checked()?;
}
run_type.execute(&rustup).arg("update").status_checked()
} }
pub fn run_juliaup(base_dirs: &BaseDirs, run_type: RunType) -> Result<()> { pub fn run_juliaup(run_type: RunType) -> Result<()> {
let juliaup = utils::require("juliaup")?; let juliaup = utils::require("juliaup")?;
print_separator("juliaup"); print_separator("juliaup");
if juliaup.canonicalize()?.is_descendant_of(base_dirs.home_dir()) { if juliaup.canonicalize()?.is_descendant_of(&HOME_DIR) {
run_type.execute(&juliaup).args(["self", "update"]).status_checked()?; run_type.execute(&juliaup).args(["self", "update"]).status_checked()?;
} }
@@ -307,10 +333,33 @@ pub fn run_conda_update(ctx: &ExecutionContext) -> Result<()> {
print_separator("Conda"); print_separator("Conda");
ctx.run_type() let mut command = ctx.run_type().execute(conda);
.execute(conda) command.args(["update", "--all"]);
.args(["update", "--all", "-y"]) if ctx.config().yes(Step::Conda) {
.status_checked() command.arg("--yes");
}
command.status_checked()
}
pub fn run_mamba_update(ctx: &ExecutionContext) -> Result<()> {
let mamba = utils::require("mamba")?;
let output = Command::new("mamba")
.args(["config", "--show", "auto_activate_base"])
.output_checked_utf8()?;
debug!("Mamba output: {}", output.stdout);
if output.stdout.contains("False") {
return Err(SkipStep("auto_activate_base is set to False".to_string()).into());
}
print_separator("Mamba");
let mut command = ctx.run_type().execute(mamba);
command.args(["update", "--all"]);
if ctx.config().yes(Step::Mamba) {
command.arg("--yes");
}
command.status_checked()
} }
pub fn run_pip3_update(run_type: RunType) -> Result<()> { pub fn run_pip3_update(run_type: RunType) -> Result<()> {
@@ -320,6 +369,21 @@ pub fn run_pip3_update(run_type: RunType) -> Result<()> {
.output_checked_utf8() .output_checked_utf8()
.map_err(|_| SkipStep("pip does not exists".to_string()))?; .map_err(|_| SkipStep("pip does not exists".to_string()))?;
let check_externally_managed = "import sysconfig; from os import path; print('Y') if path.isfile(path.join(sysconfig.get_path('stdlib'), 'EXTERNALLY-MANAGED')) else print('N')";
Command::new(&python3)
.args(["-c", check_externally_managed])
.output_checked_utf8()
.map_err(|_| SkipStep("pip may be externally managed".to_string()))
.and_then(|output| match output.stdout.trim() {
"N" => Ok(()),
"Y" => Err(SkipStep("pip is externally managed".to_string())),
_ => {
print_warning("Unexpected output when checking EXTERNALLY-MANAGED");
print_warning(output.stdout.trim());
Err(SkipStep("pip may be externally managed".to_string()))
}
})?;
print_separator("pip3"); print_separator("pip3");
if std::env::var("VIRTUAL_ENV").is_ok() { if std::env::var("VIRTUAL_ENV").is_ok() {
print_warning("This step is will be skipped when running inside a virtual environment"); print_warning("This step is will be skipped when running inside a virtual environment");
@@ -332,6 +396,39 @@ pub fn run_pip3_update(run_type: RunType) -> Result<()> {
.status_checked() .status_checked()
} }
pub fn run_pip_review_update(ctx: &ExecutionContext) -> Result<()> {
let pip_review = require("pip-review")?;
print_separator("pip-review");
if !ctx.config().enable_pip_review() {
print_warning(
"Pip-review is disabled by default. Enable it by setting enable_pip_review=true in the configuration.",
);
return Err(SkipStep(String::from("Pip-review is disabled by default")).into());
}
ctx.run_type()
.execute(pip_review)
.arg("--auto")
.status_checked_with_codes(&[1])?;
Ok(())
}
pub fn run_pipupgrade_update(ctx: &ExecutionContext) -> Result<()> {
let pipupgrade = require("pipupgrade")?;
print_separator("Pipupgrade");
if !ctx.config().enable_pip_review() {
print_warning(
"Pipupgrade is disabled by default. Enable it by setting enable_pipupgrade=true in the configuration.",
);
return Err(SkipStep(String::from("Pipupgrade is disabled by default")).into());
}
ctx.run_type().execute(pipupgrade).status_checked()?;
Ok(())
}
pub fn run_stack_update(run_type: RunType) -> Result<()> { pub fn run_stack_update(run_type: RunType) -> Result<()> {
if utils::require("ghcup").is_ok() { if utils::require("ghcup").is_ok() {
// `ghcup` is present and probably(?) being used to install `stack`. // `ghcup` is present and probably(?) being used to install `stack`.
@@ -396,38 +493,46 @@ pub fn run_tlmgr_update(ctx: &ExecutionContext) -> Result<()> {
command.status_checked() command.status_checked()
} }
pub fn run_chezmoi_update(base_dirs: &BaseDirs, run_type: RunType) -> Result<()> { pub fn run_chezmoi_update(run_type: RunType) -> Result<()> {
let chezmoi = utils::require("chezmoi")?; let chezmoi = utils::require("chezmoi")?;
base_dirs.home_dir().join(".local/share/chezmoi").require()?; HOME_DIR.join(".local/share/chezmoi").require()?;
print_separator("chezmoi"); print_separator("chezmoi");
run_type.execute(chezmoi).arg("update").status_checked() run_type.execute(chezmoi).arg("update").status_checked()
} }
pub fn run_myrepos_update(base_dirs: &BaseDirs, run_type: RunType) -> Result<()> { pub fn run_myrepos_update(run_type: RunType) -> Result<()> {
let myrepos = utils::require("mr")?; let myrepos = utils::require("mr")?;
base_dirs.home_dir().join(".mrconfig").require()?; HOME_DIR.join(".mrconfig").require()?;
print_separator("myrepos"); print_separator("myrepos");
run_type run_type
.execute(&myrepos) .execute(&myrepos)
.arg("--directory") .arg("--directory")
.arg(base_dirs.home_dir()) .arg(&*HOME_DIR)
.arg("checkout") .arg("checkout")
.status_checked()?; .status_checked()?;
run_type run_type
.execute(&myrepos) .execute(&myrepos)
.arg("--directory") .arg("--directory")
.arg(base_dirs.home_dir()) .arg(&*HOME_DIR)
.arg("update") .arg("update")
.status_checked() .status_checked()
} }
pub fn run_custom_command(name: &str, command: &str, ctx: &ExecutionContext) -> Result<()> { pub fn run_custom_command(name: &str, command: &str, ctx: &ExecutionContext) -> Result<()> {
print_separator(name); print_separator(name);
ctx.run_type().execute(shell()).arg("-c").arg(command).status_checked() let mut exec = ctx.run_type().execute(shell());
#[cfg(unix)]
let command = if let Some(command) = command.strip_prefix("-i ") {
exec.arg("-i");
command
} else {
command
};
exec.arg("-c").arg(command).status_checked()
} }
pub fn run_composer_update(ctx: &ExecutionContext) -> Result<()> { pub fn run_composer_update(ctx: &ExecutionContext) -> Result<()> {
@@ -435,11 +540,11 @@ pub fn run_composer_update(ctx: &ExecutionContext) -> Result<()> {
let composer_home = Command::new(&composer) let composer_home = Command::new(&composer)
.args(["global", "config", "--absolute", "--quiet", "home"]) .args(["global", "config", "--absolute", "--quiet", "home"])
.output_checked_utf8() .output_checked_utf8()
.map_err(|e| (SkipStep(format!("Error getting the composer directory: {}", e)))) .map_err(|e| (SkipStep(format!("Error getting the composer directory: {e}"))))
.map(|s| PathBuf::from(s.stdout.trim()))? .map(|s| PathBuf::from(s.stdout.trim()))?
.require()?; .require()?;
if !composer_home.is_descendant_of(ctx.base_dirs().home_dir()) { if !composer_home.is_descendant_of(&HOME_DIR) {
return Err(SkipStep(format!( return Err(SkipStep(format!(
"Composer directory {} isn't a decandent of the user's home directory", "Composer directory {} isn't a decandent of the user's home directory",
composer_home.display() composer_home.display()
@@ -488,34 +593,43 @@ pub fn run_composer_update(ctx: &ExecutionContext) -> Result<()> {
pub fn run_dotnet_upgrade(ctx: &ExecutionContext) -> Result<()> { pub fn run_dotnet_upgrade(ctx: &ExecutionContext) -> Result<()> {
let dotnet = utils::require("dotnet")?; let dotnet = utils::require("dotnet")?;
let dotnet_help_output = ctx.run_type().execute(&dotnet).arg("-h").output().err().unwrap(); //Skip when the `dotnet tool list` subcommand fails. (This is expected when a dotnet runtime is installed but no SDK.)
let output = match ctx
if dotnet_help_output.to_string().contains("tool") { .run_type()
let output = Command::new(dotnet) .execute(&dotnet)
.args(["tool", "list", "--global"]) .args(["tool", "list", "--global"])
.output_checked_utf8()?; .output_checked_utf8()
{
if !output.stdout.starts_with("Package Id") { Ok(output) => output,
return Err(SkipStep(String::from("dotnet did not output packages")).into()); Err(_) => {
return Err(SkipStep(String::from(
"Error running `dotnet tool list`. This is expected when a dotnet runtime is installed but no SDK.",
))
.into())
} }
};
let mut packages = output.stdout.lines().skip(2).filter(|line| !line.is_empty()).peekable(); if !output.stdout.starts_with("Package Id") {
return Err(SkipStep(String::from("dotnet did not output packages")).into());
if packages.peek().is_none() {
return Err(SkipStep(String::from("No dotnet global tools installed")).into());
}
print_separator(".NET");
for package in packages {
let package_name = package.split_whitespace().next().unwrap();
ctx.run_type()
.execute("dotnet")
.args(["tool", "update", package_name, "--global"])
.status_checked()
.with_context(|| format!("Failed to update .NET package {package_name}"))?;
}
} }
let mut packages = output.stdout.lines().skip(2).filter(|line| !line.is_empty()).peekable();
if packages.peek().is_none() {
return Err(SkipStep(String::from("No dotnet global tools installed")).into());
}
print_separator(".NET");
for package in packages {
let package_name = package.split_whitespace().next().unwrap();
ctx.run_type()
.execute(&dotnet)
.args(["tool", "update", package_name, "--global"])
.status_checked()
.with_context(|| format!("Failed to update .NET package {package_name}"))?;
}
Ok(()) Ok(())
} }
@@ -591,5 +705,24 @@ pub fn run_helm_repo_update(run_type: RunType) -> Result<()> {
let helm = utils::require("helm")?; let helm = utils::require("helm")?;
print_separator("Helm"); print_separator("Helm");
run_type.execute(helm).arg("repo").arg("update").status_checked()
let no_repo = "no repositories found";
let mut success = true;
let mut exec = run_type.execute(helm);
if let Err(e) = exec.arg("repo").arg("update").status_checked() {
error!("Updating repositories failed: {}", e);
success = match exec.output_checked_utf8() {
Ok(s) => s.stdout.contains(no_repo) || s.stderr.contains(no_repo),
Err(e) => match e.downcast_ref::<TopgradeError>() {
Some(TopgradeError::ProcessFailedWithOutput(_, _, stderr)) => stderr.contains(no_repo),
_ => false,
},
};
}
if success {
Ok(())
} else {
Err(eyre!(StepFailed))
}
} }

View File

@@ -71,7 +71,7 @@ async fn pull_repository(repo: String, git: &Path, ctx: &ExecutionContext<'_>) -
if let Err(message) = &result { if let Err(message) = &result {
println!("{} pulling {}", style("Failed").red().bold(), &repo); println!("{} pulling {}", style("Failed").red().bold(), &repo);
print!("{}", message); print!("{message}");
} else { } else {
let after_revision = get_head_revision(git, &repo); let after_revision = get_head_revision(git, &repo);
@@ -87,7 +87,7 @@ async fn pull_repository(repo: String, git: &Path, ctx: &ExecutionContext<'_>) -
"log", "log",
"--no-decorate", "--no-decorate",
"--oneline", "--oneline",
&format!("{}..{}", before, after), &format!("{before}..{after}"),
]) ])
.status_checked()?; .status_checked()?;
println!(); println!();
@@ -187,7 +187,7 @@ impl Git {
repositories repositories
.bad_patterns .bad_patterns
.iter() .iter()
.for_each(|pattern| print_warning(format!("Path {} did not contain any git repositories", pattern))); .for_each(|pattern| print_warning(format!("Path {pattern} did not contain any git repositories")));
self.multi_pull(repositories, ctx) self.multi_pull(repositories, ctx)
} }

View File

@@ -5,6 +5,7 @@ use std::path::PathBuf;
use std::process::Command; use std::process::Command;
use crate::utils::require_option; use crate::utils::require_option;
use crate::HOME_DIR;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
use nix::unistd::Uid; use nix::unistd::Uid;
@@ -265,7 +266,7 @@ 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")?;
let deno_dir = ctx.base_dirs().home_dir().join(".deno"); let deno_dir = HOME_DIR.join(".deno");
if !deno.canonicalize()?.is_descendant_of(&deno_dir) { if !deno.canonicalize()?.is_descendant_of(&deno_dir) {
let skip_reason = SkipStep("Deno installed outside of .deno directory".to_string()); let skip_reason = SkipStep("Deno installed outside of .deno directory".to_string());

View File

@@ -26,7 +26,7 @@ pub fn upgrade_packages(ctx: &ExecutionContext) -> Result<()> {
ctx.run_type().execute(&pkg).arg("clean").status_checked()?; ctx.run_type().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.run_type().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");

View File

@@ -80,8 +80,18 @@ 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.run_type().execute(&self.executable);
command.env("PATH", get_execution_path());
command
.env("PATH", get_execution_path())
.env("UPDATE_AUR", "1")
.env("SKIP_MIRRORLIST", "1");
if ctx.config().yes(Step::System) {
command.env("PACMAN_NOCONFIRM", "1");
}
command.args(ctx.config().garuda_update_arguments().split_whitespace());
command.status_checked()?; command.status_checked()?;
Ok(()) Ok(())
} }
} }

View File

@@ -26,10 +26,13 @@ pub enum Distribution {
CentOS, CentOS,
ClearLinux, ClearLinux,
Fedora, Fedora,
FedoraSilverblue,
Debian, Debian,
Gentoo, Gentoo,
OpenMandriva, OpenMandriva,
PCLinuxOS,
Suse, Suse,
SuseMicro,
Void, Void,
Solus, Solus,
Exherbo, Exherbo,
@@ -41,13 +44,25 @@ impl Distribution {
fn parse_os_release(os_release: &ini::Ini) -> Result<Self> { fn parse_os_release(os_release: &ini::Ini) -> Result<Self> {
let section = os_release.general_section(); let section = os_release.general_section();
let id = section.get("ID"); let id = section.get("ID");
let variant: Option<Vec<&str>> = section.get("VARIANT").map(|s| s.split_whitespace().collect());
let id_like: Option<Vec<&str>> = section.get("ID_LIKE").map(|s| s.split_whitespace().collect()); let id_like: Option<Vec<&str>> = section.get("ID_LIKE").map(|s| s.split_whitespace().collect());
Ok(match id { Ok(match id {
Some("alpine") => Distribution::Alpine, Some("alpine") => Distribution::Alpine,
Some("centos") | Some("rhel") | Some("ol") => Distribution::CentOS, Some("centos") | Some("rhel") | Some("ol") => Distribution::CentOS,
Some("clear-linux-os") => Distribution::ClearLinux, Some("clear-linux-os") => Distribution::ClearLinux,
Some("fedora") | Some("nobara") => Distribution::Fedora, Some("fedora") | Some("nobara") => {
if let Some(variant) = variant {
if variant.contains(&"Silverblue") {
return Ok(Distribution::FedoraSilverblue);
} else {
return Ok(Distribution::Fedora);
};
} else {
return Ok(Distribution::Fedora);
}
}
Some("void") => Distribution::Void, Some("void") => Distribution::Void,
Some("debian") | Some("pureos") => Distribution::Debian, Some("debian") | Some("pureos") => Distribution::Debian,
Some("arch") | Some("anarchy") | Some("manjaro-arm") | Some("garuda") | Some("artix") => Distribution::Arch, Some("arch") | Some("anarchy") | Some("manjaro-arm") | Some("garuda") | Some("artix") => Distribution::Arch,
@@ -55,8 +70,10 @@ impl Distribution {
Some("gentoo") => Distribution::Gentoo, Some("gentoo") => Distribution::Gentoo,
Some("exherbo") => Distribution::Exherbo, Some("exherbo") => Distribution::Exherbo,
Some("nixos") => Distribution::NixOS, Some("nixos") => Distribution::NixOS,
Some("opensuse-microos") => Distribution::SuseMicro,
Some("neon") => Distribution::KDENeon, Some("neon") => Distribution::KDENeon,
Some("openmandriva") => Distribution::OpenMandriva, Some("openmandriva") => Distribution::OpenMandriva,
Some("pclinuxos") => Distribution::PCLinuxOS,
_ => { _ => {
if let Some(id_like) = id_like { if let Some(id_like) = id_like {
if id_like.contains(&"debian") || id_like.contains(&"ubuntu") { if id_like.contains(&"debian") || id_like.contains(&"ubuntu") {
@@ -99,10 +116,12 @@ impl Distribution {
Distribution::Alpine => upgrade_alpine_linux(ctx), Distribution::Alpine => upgrade_alpine_linux(ctx),
Distribution::Arch => archlinux::upgrade_arch_linux(ctx), Distribution::Arch => archlinux::upgrade_arch_linux(ctx),
Distribution::CentOS | Distribution::Fedora => upgrade_redhat(ctx), Distribution::CentOS | Distribution::Fedora => upgrade_redhat(ctx),
Distribution::FedoraSilverblue => upgrade_fedora_silverblue(ctx),
Distribution::ClearLinux => upgrade_clearlinux(ctx), Distribution::ClearLinux => upgrade_clearlinux(ctx),
Distribution::Debian => upgrade_debian(ctx), Distribution::Debian => upgrade_debian(ctx),
Distribution::Gentoo => upgrade_gentoo(ctx), Distribution::Gentoo => upgrade_gentoo(ctx),
Distribution::Suse => upgrade_suse(ctx), Distribution::Suse => upgrade_suse(ctx),
Distribution::SuseMicro => upgrade_suse_micro(ctx),
Distribution::Void => upgrade_void(ctx), Distribution::Void => upgrade_void(ctx),
Distribution::Solus => upgrade_solus(ctx), Distribution::Solus => upgrade_solus(ctx),
Distribution::Exherbo => upgrade_exherbo(ctx), Distribution::Exherbo => upgrade_exherbo(ctx),
@@ -110,6 +129,7 @@ impl Distribution {
Distribution::KDENeon => upgrade_neon(ctx), Distribution::KDENeon => upgrade_neon(ctx),
Distribution::Bedrock => update_bedrock(ctx), Distribution::Bedrock => update_bedrock(ctx),
Distribution::OpenMandriva => upgrade_openmandriva(ctx), Distribution::OpenMandriva => upgrade_openmandriva(ctx),
Distribution::PCLinuxOS => upgrade_pclinuxos(ctx),
} }
} }
@@ -193,7 +213,14 @@ fn upgrade_redhat(ctx: &ExecutionContext) -> Result<()> {
} else { } else {
print_warning("No sudo detected. Skipping system upgrade"); print_warning("No sudo detected. Skipping system upgrade");
} }
Ok(())
}
fn upgrade_fedora_silverblue(ctx: &ExecutionContext) -> Result<()> {
let ostree = require("rpm-ostree")?;
let mut command = ctx.run_type().execute(ostree);
command.arg("upgrade");
command.status_checked()?;
Ok(()) Ok(())
} }
@@ -224,6 +251,18 @@ fn upgrade_suse(ctx: &ExecutionContext) -> Result<()> {
Ok(()) Ok(())
} }
fn upgrade_suse_micro(ctx: &ExecutionContext) -> Result<()> {
if let Some(sudo) = ctx.sudo() {
ctx.run_type()
.execute(sudo)
.args(["transactional-update", "dup"])
.status_checked()?;
} else {
print_warning("No sudo detected. Skipping system upgrade");
}
Ok(())
}
fn upgrade_openmandriva(ctx: &ExecutionContext) -> Result<()> { fn upgrade_openmandriva(ctx: &ExecutionContext) -> Result<()> {
if let Some(sudo) = &ctx.sudo() { if let Some(sudo) = &ctx.sudo() {
@@ -246,6 +285,33 @@ fn upgrade_openmandriva(ctx: &ExecutionContext) -> Result<()> {
Ok(()) Ok(())
} }
fn upgrade_pclinuxos(ctx: &ExecutionContext) -> Result<()> {
if let Some(sudo) = &ctx.sudo() {
let mut command_update = ctx.run_type().execute(sudo);
command_update.arg(&which("apt-get").unwrap()).arg("update");
if let Some(args) = ctx.config().dnf_arguments() {
command_update.args(args.split_whitespace());
}
if ctx.config().yes(Step::System) {
command_update.arg("-y");
}
command_update.status_checked()?;
ctx.run_type()
.execute(sudo)
.arg(&which("apt-get").unwrap())
.arg("dist-upgrade")
.status_checked()?;
} else {
print_warning("No sudo detected. Skipping system upgrade");
}
Ok(())
}
fn upgrade_void(ctx: &ExecutionContext) -> Result<()> { fn upgrade_void(ctx: &ExecutionContext) -> Result<()> {
if let Some(sudo) = ctx.sudo() { if let Some(sudo) = ctx.sudo() {
@@ -317,7 +383,20 @@ fn upgrade_gentoo(ctx: &ExecutionContext) -> Result<()> {
fn upgrade_debian(ctx: &ExecutionContext) -> Result<()> { fn upgrade_debian(ctx: &ExecutionContext) -> Result<()> {
if let Some(sudo) = &ctx.sudo() { if let Some(sudo) = &ctx.sudo() {
let apt = which("apt-fast") let apt = which("apt-fast")
.or_else(|| which("nala")) .or_else(|| {
if which("mist").is_some() {
Some(PathBuf::from("mist"))
} else {
None
}
})
.or_else(|| {
if Path::new("/usr/bin/nala").exists() {
Some(Path::new("/usr/bin/nala").to_path_buf())
} else {
None
}
})
.unwrap_or_else(|| PathBuf::from("apt-get")); .unwrap_or_else(|| PathBuf::from("apt-get"));
let is_nala = apt.ends_with("nala"); let is_nala = apt.ends_with("nala");
@@ -385,15 +464,44 @@ fn upgrade_solus(ctx: &ExecutionContext) -> Result<()> {
Ok(()) Ok(())
} }
pub fn update_am(ctx: &ExecutionContext) -> Result<()> {
let am = require("am")?;
if let Some(sudo) = ctx.sudo() {
ctx.run_type().execute(sudo).arg(am).arg("-u").status_checked()?;
} else {
print_warning("No sudo detected. Skipping AM Step");
}
Ok(())
}
pub fn run_pacdef(ctx: &ExecutionContext) -> Result<()> { pub fn run_pacdef(ctx: &ExecutionContext) -> Result<()> {
let pacdef = require("pacdef")?; let pacdef = require("pacdef")?;
print_separator("pacdef"); print_separator("pacdef");
ctx.run_type().execute(&pacdef).arg("sync").status_checked()?; let output = ctx.run_type().execute(&pacdef).arg("version").output_checked()?;
let string = String::from_utf8(output.stdout)?;
let new_version = string.contains("version: 1");
println!(); if new_version {
ctx.run_type().execute(&pacdef).arg("review").status_checked() ctx.run_type()
.execute(&pacdef)
.args(["package", "sync"])
.status_checked()?;
println!();
ctx.run_type()
.execute(&pacdef)
.args(["package", "review"])
.status_checked()?;
} else {
ctx.run_type().execute(&pacdef).arg("sync").status_checked()?;
println!();
ctx.run_type().execute(&pacdef).arg("review").status_checked()?;
}
Ok(())
} }
pub fn run_pacstall(ctx: &ExecutionContext) -> Result<()> { pub fn run_pacstall(ctx: &ExecutionContext) -> Result<()> {
@@ -401,8 +509,16 @@ pub fn run_pacstall(ctx: &ExecutionContext) -> Result<()> {
print_separator("Pacstall"); print_separator("Pacstall");
ctx.run_type().execute(&pacstall).arg("-U").status_checked()?; let mut update_cmd = ctx.run_type().execute(&pacstall);
ctx.run_type().execute(pacstall).arg("-Up").status_checked() let mut upgrade_cmd = ctx.run_type().execute(pacstall);
if ctx.config().yes(Step::Pacstall) {
update_cmd.arg("-P");
upgrade_cmd.arg("-P");
}
update_cmd.arg("-U").status_checked()?;
upgrade_cmd.arg("-Up").status_checked()
} }
fn upgrade_clearlinux(ctx: &ExecutionContext) -> Result<()> { fn upgrade_clearlinux(ctx: &ExecutionContext) -> Result<()> {
@@ -452,10 +568,13 @@ fn upgrade_exherbo(ctx: &ExecutionContext) -> Result<()> {
fn upgrade_nixos(ctx: &ExecutionContext) -> Result<()> { fn upgrade_nixos(ctx: &ExecutionContext) -> Result<()> {
if let Some(sudo) = ctx.sudo() { if let Some(sudo) = ctx.sudo() {
ctx.run_type() let mut command = ctx.run_type().execute(sudo);
.execute(sudo) command.args(["/run/current-system/sw/bin/nixos-rebuild", "switch", "--upgrade"]);
.args(["/run/current-system/sw/bin/nixos-rebuild", "switch", "--upgrade"])
.status_checked()?; if let Some(args) = ctx.config().nix_arguments() {
command.args(args.split_whitespace());
}
command.status_checked()?;
if ctx.config().cleanup() { if ctx.config().cleanup() {
ctx.run_type() ctx.run_type()
@@ -660,6 +779,29 @@ pub fn run_distrobox_update(ctx: &ExecutionContext) -> Result<()> {
.status_checked() .status_checked()
} }
pub fn run_dkp_pacman_update(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), String::from("sudo is not installed"))?;
let dkp_pacman = require("dkp-pacman")?;
print_separator("Devkitpro pacman");
ctx.run_type()
.execute(sudo)
.arg(&dkp_pacman)
.arg("-Syu")
.status_checked()?;
if ctx.config().cleanup() {
ctx.run_type()
.execute(sudo)
.arg(&dkp_pacman)
.arg("-Scc")
.status_checked()?;
}
Ok(())
}
pub fn run_config_update(ctx: &ExecutionContext) -> Result<()> { pub fn run_config_update(ctx: &ExecutionContext) -> Result<()> {
let sudo = require_option(ctx.sudo().as_ref(), String::from("sudo is not installed"))?; let sudo = require_option(ctx.sudo().as_ref(), String::from("sudo is not installed"))?;
if ctx.config().yes(Step::ConfigUpdate) { if ctx.config().yes(Step::ConfigUpdate) {

View File

@@ -0,0 +1,9 @@
NAME="PCLinuxOS"
VERSION="2022"
ID=pclinuxos
VERSION_ID=2022
ID_LIKE="mandriva"
PRETTY_NAME="PCLinuxOS 2022"
ANSI_COLOR="1;37"
HOME_URL="http://www.pclinuxos.com/"
SUPPORT_URL="http://www.pclinuxos.com/"

View File

@@ -5,9 +5,8 @@ use std::process::Command;
use std::{env, path::Path}; use std::{env, path::Path};
use crate::command::CommandExt; use crate::command::CommandExt;
use crate::Step; use crate::{Step, HOME_DIR};
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use directories::BaseDirs;
use home; use home;
use ini::Ini; use ini::Ini;
use tracing::debug; use tracing::debug;
@@ -127,7 +126,7 @@ pub fn run_fisher(run_type: RunType) -> Result<()> {
} }
pub fn run_bashit(ctx: &ExecutionContext) -> Result<()> { pub fn run_bashit(ctx: &ExecutionContext) -> Result<()> {
ctx.base_dirs().home_dir().join(".bash_it").require()?; HOME_DIR.join(".bash_it").require()?;
print_separator("Bash-it"); print_separator("Bash-it");
@@ -139,10 +138,7 @@ pub fn run_bashit(ctx: &ExecutionContext) -> Result<()> {
pub fn run_oh_my_fish(ctx: &ExecutionContext) -> Result<()> { pub fn run_oh_my_fish(ctx: &ExecutionContext) -> Result<()> {
let fish = require("fish")?; let fish = require("fish")?;
ctx.base_dirs() HOME_DIR.join(".local/share/omf/pkg/omf/functions/omf.fish").require()?;
.home_dir()
.join(".local/share/omf/pkg/omf/functions/omf.fish")
.require()?;
print_separator("oh-my-fish"); print_separator("oh-my-fish");
@@ -152,6 +148,8 @@ pub fn run_oh_my_fish(ctx: &ExecutionContext) -> Result<()> {
pub fn run_pkgin(ctx: &ExecutionContext) -> Result<()> { pub fn run_pkgin(ctx: &ExecutionContext) -> Result<()> {
let pkgin = require("pkgin")?; let pkgin = require("pkgin")?;
print_separator("Pkgin");
let mut command = ctx.run_type().execute(ctx.sudo().as_ref().unwrap()); let mut command = ctx.run_type().execute(ctx.sudo().as_ref().unwrap());
command.arg(&pkgin).arg("update"); command.arg(&pkgin).arg("update");
if ctx.config().yes(Step::Pkgin) { if ctx.config().yes(Step::Pkgin) {
@@ -169,8 +167,7 @@ pub fn run_pkgin(ctx: &ExecutionContext) -> Result<()> {
pub fn run_fish_plug(ctx: &ExecutionContext) -> Result<()> { pub fn run_fish_plug(ctx: &ExecutionContext) -> Result<()> {
let fish = require("fish")?; let fish = require("fish")?;
ctx.base_dirs() HOME_DIR
.home_dir()
.join(".local/share/fish/plug/kidonng/fish-plug/functions/plug.fish") .join(".local/share/fish/plug/kidonng/fish-plug/functions/plug.fish")
.require()?; .require()?;
@@ -189,7 +186,7 @@ pub fn run_fish_plug(ctx: &ExecutionContext) -> Result<()> {
/// See: <https://github.com/danhper/fundle> /// See: <https://github.com/danhper/fundle>
pub fn run_fundle(ctx: &ExecutionContext) -> Result<()> { pub fn run_fundle(ctx: &ExecutionContext) -> Result<()> {
let fish = require("fish")?; let fish = require("fish")?;
ctx.base_dirs().home_dir().join(".config/fish/fundle").require()?; HOME_DIR.join(".config/fish/fundle").require()?;
print_separator("fundle"); print_separator("fundle");
@@ -333,6 +330,7 @@ pub fn run_nix(ctx: &ExecutionContext) -> Result<()> {
let nix = require("nix")?; let nix = require("nix")?;
let nix_channel = require("nix-channel")?; let nix_channel = require("nix-channel")?;
let nix_env = require("nix-env")?; let nix_env = require("nix-env")?;
// 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) => 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(),
@@ -433,12 +431,12 @@ pub fn run_pearl(run_type: RunType) -> Result<()> {
run_type.execute(pearl).arg("update").status_checked() run_type.execute(pearl).arg("update").status_checked()
} }
pub fn run_sdkman(base_dirs: &BaseDirs, cleanup: bool, run_type: RunType) -> Result<()> { pub fn run_sdkman(cleanup: bool, run_type: RunType) -> Result<()> {
let bash = require("bash")?; let bash = require("bash")?;
let sdkman_init_path = env::var("SDKMAN_DIR") let sdkman_init_path = env::var("SDKMAN_DIR")
.map(PathBuf::from) .map(PathBuf::from)
.unwrap_or_else(|_| base_dirs.home_dir().join(".sdkman")) .unwrap_or_else(|_| HOME_DIR.join(".sdkman"))
.join("bin") .join("bin")
.join("sdkman-init.sh") .join("sdkman-init.sh")
.require() .require()
@@ -448,7 +446,7 @@ pub fn run_sdkman(base_dirs: &BaseDirs, cleanup: bool, run_type: RunType) -> Res
let sdkman_config_path = env::var("SDKMAN_DIR") let sdkman_config_path = env::var("SDKMAN_DIR")
.map(PathBuf::from) .map(PathBuf::from)
.unwrap_or_else(|_| base_dirs.home_dir().join(".sdkman")) .unwrap_or_else(|_| HOME_DIR.join(".sdkman"))
.join("etc") .join("etc")
.join("config") .join("config")
.require()?; .require()?;

View File

@@ -3,6 +3,7 @@ use std::path::Path;
use std::{ffi::OsStr, process::Command}; use std::{ffi::OsStr, process::Command};
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use etcetera::base_strategy::BaseStrategy;
use tracing::debug; use tracing::debug;
use crate::command::CommandExt; use crate::command::CommandExt;
@@ -48,7 +49,7 @@ pub fn run_winget(ctx: &ExecutionContext) -> Result<()> {
} }
ctx.run_type() ctx.run_type()
.execute(&winget) .execute(winget)
.args(["upgrade", "--all"]) .args(["upgrade", "--all"])
.status_checked() .status_checked()
} }
@@ -68,6 +69,25 @@ pub fn run_scoop(cleanup: bool, run_type: RunType) -> Result<()> {
Ok(()) Ok(())
} }
pub fn update_wsl(ctx: &ExecutionContext) -> Result<()> {
let wsl = require("wsl")?;
print_separator("Update WSL");
let mut wsl_command = ctx.run_type().execute(wsl);
wsl_command.args(["--update"]);
if ctx.config().wsl_update_pre_release() {
wsl_command.args(["--pre-release"]);
}
if ctx.config().wsl_update_use_web_download() {
wsl_command.args(["--web-download"]);
}
wsl_command.status_checked()?;
Ok(())
}
fn get_wsl_distributions(wsl: &Path) -> Result<Vec<String>> { 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
@@ -86,7 +106,7 @@ fn upgrade_wsl_distribution(wsl: &Path, dist: &str, ctx: &ExecutionContext) -> R
let mut command = ctx.run_type().execute(wsl); let mut command = ctx.run_type().execute(wsl);
command command
.args(["-d", dist, "bash", "-c"]) .args(["-d", dist, "bash", "-c"])
.arg(format!("TOPGRADE_PREFIX={} exec {}", dist, topgrade)); .arg(format!("TOPGRADE_PREFIX={dist} exec {topgrade}"));
if ctx.config().yes(Step::Wsl) { if ctx.config().yes(Step::Wsl) {
command.arg("-y"); command.arg("-y");
@@ -145,9 +165,8 @@ pub fn reboot() -> Result<()> {
Command::new("shutdown").args(["/R", "/T", "0"]).status_checked() Command::new("shutdown").args(["/R", "/T", "0"]).status_checked()
} }
pub fn insert_startup_scripts(ctx: &ExecutionContext, git_repos: &mut Repositories) -> Result<()> { pub fn insert_startup_scripts(git_repos: &mut Repositories) -> Result<()> {
let startup_dir = ctx let startup_dir = crate::WINDOWS_DIRS
.base_dirs()
.data_dir() .data_dir()
.join("Microsoft\\Windows\\Start Menu\\Programs\\Startup"); .join("Microsoft\\Windows\\Start Menu\\Programs\\Startup");
for entry in std::fs::read_dir(&startup_dir)?.flatten() { for entry in std::fs::read_dir(&startup_dir)?.flatten() {

View File

@@ -50,7 +50,7 @@ impl Powershell {
.args([ .args([
"-NoProfile", "-NoProfile",
"-Command", "-Command",
&format!("Get-Module -ListAvailable {}", command), &format!("Get-Module -ListAvailable {command}"),
]) ])
.output_checked_utf8() .output_checked_utf8()
.map(|result| !result.stdout.is_empty()) .map(|result| !result.stdout.is_empty())

View File

@@ -19,7 +19,7 @@ pub fn ssh_step(ctx: &ExecutionContext, hostname: &str) -> Result<()> {
args.extend(ssh_arguments.split_whitespace()); args.extend(ssh_arguments.split_whitespace());
} }
let env = format!("TOPGRADE_PREFIX={}", hostname); let env = format!("TOPGRADE_PREFIX={hostname}");
args.extend(["env", &env, "$SHELL", "-lc", topgrade]); args.extend(["env", &env, "$SHELL", "-lc", topgrade]);
if ctx.config().run_in_tmux() && !ctx.run_type().dry() { if ctx.config().run_in_tmux() && !ctx.run_type().dry() {
@@ -43,11 +43,11 @@ pub fn ssh_step(ctx: &ExecutionContext, hostname: &str) -> Result<()> {
args.extend(ssh_arguments.split_whitespace()); args.extend(ssh_arguments.split_whitespace());
} }
let env = format!("TOPGRADE_PREFIX={}", hostname); let env = format!("TOPGRADE_PREFIX={hostname}");
args.extend(["env", &env, "$SHELL", "-lc", topgrade]); args.extend(["env", &env, "$SHELL", "-lc", topgrade]);
print_separator(format!("Remote ({})", hostname)); print_separator(format!("Remote ({hostname})"));
println!("Connecting to {}...", hostname); println!("Connecting to {hostname}...");
ctx.run_type().execute(ssh).args(&args).status_checked() ctx.run_type().execute(ssh).args(&args).status_checked()
} }

View File

@@ -183,7 +183,7 @@ pub fn topgrade_vagrant_box(ctx: &ExecutionContext, vagrant_box: &VagrantBox) ->
let mut _poweron = None; let mut _poweron = None;
if !vagrant_box.initial_status.powered_on() { if !vagrant_box.initial_status.powered_on() {
if !(ctx.config().vagrant_power_on().unwrap_or(true)) { if !(ctx.config().vagrant_power_on().unwrap_or(true)) {
return Err(SkipStep(format!("Skipping powered off box {}", vagrant_box)).into()); return Err(SkipStep(format!("Skipping powered off box {vagrant_box}")).into());
} else { } else {
print_separator(seperator); print_separator(seperator);
_poweron = Some(vagrant.temporary_power_on(vagrant_box, ctx)?); _poweron = Some(vagrant.temporary_power_on(vagrant_box, ctx)?);

View File

@@ -5,11 +5,11 @@ 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 directories::BaseDirs;
use crate::command::CommandExt; use crate::command::CommandExt;
use crate::executor::RunType; use crate::executor::RunType;
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},
@@ -18,11 +18,8 @@ use crate::{
#[cfg(unix)] #[cfg(unix)]
use std::os::unix::process::CommandExt as _; use std::os::unix::process::CommandExt as _;
pub fn run_tpm(base_dirs: &BaseDirs, run_type: RunType) -> Result<()> { pub fn run_tpm(run_type: RunType) -> Result<()> {
let tpm = base_dirs let tpm = HOME_DIR.join(".tmux/plugins/tpm/bin/update_plugins").require()?;
.home_dir()
.join(".tmux/plugins/tpm/bin/update_plugins")
.require()?;
print_separator("tmux plugins"); print_separator("tmux plugins");

View File

@@ -42,7 +42,7 @@ pub fn run_toolbx(ctx: &ExecutionContext) -> Result<()> {
let topgrade_path = topgrade_path.to_str().unwrap(); let topgrade_path = topgrade_path.to_str().unwrap();
for tb in toolboxes.iter() { for tb in toolboxes.iter() {
let topgrade_prefix = format!("TOPGRADE_PREFIX='Toolbx {}'", tb); let topgrade_prefix = format!("TOPGRADE_PREFIX='Toolbx {tb}'");
let mut args = vec![ let mut args = vec![
"run", "run",
"-c", "-c",

View File

@@ -33,23 +33,20 @@ if exists(":PaqUpdate")
PaqUpdate PaqUpdate
endif endif
if exists(":CocUpdateSync") if exists(":Lazy")
echo "CocUpdateSync" echo "Lazy Update"
CocUpdateSync Lazy! sync | qa
endif endif
" TODO: Should this be after `PackerSync`? if exists(":AstroUpdate")
" Not sure how to sequence this after Packer without doing something weird echo "AstroUpdate"
" with that `PackerComplete` autocommand. AstroUpdate
if exists(":TSUpdate")
echo "TreeSitter Update"
TSUpdate
endif endif
if exists(':PackerSync') if exists(':PackerSync')
echo "Packer" echo "Packer"
autocmd User PackerComplete quitall autocmd User PackerComplete quitall
PackerSync PackerSync
else else
quitall quitall
endif endif

View File

@@ -1,6 +1,8 @@
use crate::command::CommandExt; use crate::command::CommandExt;
use crate::error::{SkipStep, TopgradeError}; use crate::error::{SkipStep, TopgradeError};
use crate::HOME_DIR;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use etcetera::base_strategy::BaseStrategy;
use crate::executor::{Executor, ExecutorOutput, RunType}; use crate::executor::{Executor, ExecutorOutput, RunType};
use crate::terminal::print_separator; use crate::terminal::print_separator;
@@ -8,7 +10,6 @@ use crate::{
execution_context::ExecutionContext, execution_context::ExecutionContext,
utils::{require, PathExt}, utils::{require, PathExt},
}; };
use directories::BaseDirs;
use std::path::PathBuf; use std::path::PathBuf;
use std::{ use std::{
io::{self, Write}, io::{self, Write},
@@ -18,22 +19,19 @@ use tracing::debug;
const UPGRADE_VIM: &str = include_str!("upgrade.vim"); const UPGRADE_VIM: &str = include_str!("upgrade.vim");
pub fn vimrc(base_dirs: &BaseDirs) -> Result<PathBuf> { pub fn vimrc() -> Result<PathBuf> {
base_dirs HOME_DIR
.home_dir()
.join(".vimrc") .join(".vimrc")
.require() .require()
.or_else(|_| base_dirs.home_dir().join(".vim/vimrc").require()) .or_else(|_| HOME_DIR.join(".vim/vimrc").require())
} }
fn nvimrc(base_dirs: &BaseDirs) -> Result<PathBuf> { fn nvimrc() -> Result<PathBuf> {
#[cfg(unix)] #[cfg(unix)]
let base_dir = let base_dir = crate::XDG_DIRS.config_dir();
// Bypass directories crate as nvim doesn't use the macOS-specific directories.
std::env::var_os("XDG_CONFIG_HOME").map_or_else(|| base_dirs.home_dir().join(".config"), PathBuf::from);
#[cfg(windows)] #[cfg(windows)]
let base_dir = base_dirs.cache_dir(); let base_dir = crate::WINDOWS_DIRS.cache_dir();
base_dir base_dir
.join("nvim/init.vim") .join("nvim/init.vim")
@@ -74,7 +72,7 @@ fn upgrade(command: &mut Executor, ctx: &ExecutionContext) -> Result<()> {
} }
pub fn upgrade_ultimate_vimrc(ctx: &ExecutionContext) -> Result<()> { pub fn upgrade_ultimate_vimrc(ctx: &ExecutionContext) -> Result<()> {
let config_dir = ctx.base_dirs().home_dir().join(".vim_runtime").require()?; let config_dir = HOME_DIR.join(".vim_runtime").require()?;
let git = require("git")?; let git = require("git")?;
let python = require("python3")?; let python = require("python3")?;
let update_plugins = config_dir.join("update_plugins.py").require()?; let update_plugins = config_dir.join("update_plugins.py").require()?;
@@ -105,7 +103,7 @@ pub fn upgrade_ultimate_vimrc(ctx: &ExecutionContext) -> Result<()> {
Ok(()) Ok(())
} }
pub fn upgrade_vim(base_dirs: &BaseDirs, ctx: &ExecutionContext) -> Result<()> { pub fn upgrade_vim(ctx: &ExecutionContext) -> Result<()> {
let vim = require("vim")?; let vim = require("vim")?;
let output = Command::new(&vim).arg("--version").output_checked_utf8()?; let output = Command::new(&vim).arg("--version").output_checked_utf8()?;
@@ -113,7 +111,7 @@ pub fn upgrade_vim(base_dirs: &BaseDirs, ctx: &ExecutionContext) -> Result<()> {
return Err(SkipStep(String::from("vim binary might be actually nvim")).into()); return Err(SkipStep(String::from("vim binary might be actually nvim")).into());
} }
let vimrc = vimrc(base_dirs)?; let vimrc = vimrc()?;
print_separator("Vim"); print_separator("Vim");
upgrade( upgrade(
@@ -127,9 +125,9 @@ pub fn upgrade_vim(base_dirs: &BaseDirs, ctx: &ExecutionContext) -> Result<()> {
) )
} }
pub fn upgrade_neovim(base_dirs: &BaseDirs, ctx: &ExecutionContext) -> Result<()> { pub fn upgrade_neovim(ctx: &ExecutionContext) -> Result<()> {
let nvim = require("nvim")?; let nvim = require("nvim")?;
let nvimrc = nvimrc(base_dirs)?; let nvimrc = nvimrc()?;
print_separator("Neovim"); print_separator("Neovim");
upgrade( upgrade(
@@ -143,7 +141,7 @@ pub fn upgrade_neovim(base_dirs: &BaseDirs, ctx: &ExecutionContext) -> Result<()
) )
} }
pub fn run_voom(_base_dirs: &BaseDirs, run_type: RunType) -> Result<()> { pub fn run_voom(run_type: RunType) -> Result<()> {
let voom = require("voom")?; let voom = require("voom")?;
print_separator("voom"); print_separator("voom");

View File

@@ -1,9 +1,8 @@
use std::env; use std::env;
use std::path::{Path, PathBuf}; use std::path::PathBuf;
use std::process::Command; use std::process::Command;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use directories::BaseDirs;
use tracing::debug; use tracing::debug;
use walkdir::WalkDir; use walkdir::WalkDir;
@@ -13,22 +12,41 @@ use crate::executor::RunType;
use crate::git::Repositories; use crate::git::Repositories;
use crate::terminal::print_separator; use crate::terminal::print_separator;
use crate::utils::{require, PathExt}; use crate::utils::{require, PathExt};
use crate::HOME_DIR;
pub fn run_zr(base_dirs: &BaseDirs, run_type: RunType) -> Result<()> { pub fn run_zr(run_type: RunType) -> Result<()> {
let zsh = require("zsh")?; let zsh = require("zsh")?;
require("zr")?; require("zr")?;
print_separator("zr"); print_separator("zr");
let cmd = format!("source {} && zr --update", zshrc(base_dirs).display()); let cmd = format!("source {} && zr --update", zshrc().display());
run_type.execute(zsh).args(["-l", "-c", cmd.as_str()]).status_checked() run_type.execute(zsh).args(["-l", "-c", cmd.as_str()]).status_checked()
} }
pub fn zshrc(base_dirs: &BaseDirs) -> PathBuf { fn zdotdir() -> PathBuf {
env::var("ZDOTDIR") env::var("ZDOTDIR")
.map(|p| Path::new(&p).join(".zshrc")) .map(PathBuf::from)
.unwrap_or_else(|_| base_dirs.home_dir().join(".zshrc")) .unwrap_or_else(|_| HOME_DIR.clone())
}
pub fn zshrc() -> PathBuf {
zdotdir().join(".zshrc")
}
pub fn run_antidote(ctx: &ExecutionContext) -> Result<()> {
let zsh = require("zsh")?;
let mut antidote = zdotdir().join(".antidote").require()?;
antidote.push("antidote.zsh");
print_separator("antidote");
ctx.run_type()
.execute(zsh)
.arg("-c")
.arg(format!("source {} && antidote update", antidote.display()))
.status_checked()
} }
pub fn run_antibody(run_type: RunType) -> Result<()> { pub fn run_antibody(run_type: RunType) -> Result<()> {
@@ -40,12 +58,12 @@ pub fn run_antibody(run_type: RunType) -> Result<()> {
run_type.execute(antibody).arg("update").status_checked() run_type.execute(antibody).arg("update").status_checked()
} }
pub fn run_antigen(base_dirs: &BaseDirs, run_type: RunType) -> Result<()> { pub fn run_antigen(run_type: RunType) -> Result<()> {
let zsh = require("zsh")?; let zsh = require("zsh")?;
let zshrc = zshrc(base_dirs).require()?; let zshrc = zshrc().require()?;
env::var("ADOTDIR") env::var("ADOTDIR")
.map(PathBuf::from) .map(PathBuf::from)
.unwrap_or_else(|_| base_dirs.home_dir().join("antigen.zsh")) .unwrap_or_else(|_| HOME_DIR.join("antigen.zsh"))
.require()?; .require()?;
print_separator("antigen"); print_separator("antigen");
@@ -54,12 +72,12 @@ pub fn run_antigen(base_dirs: &BaseDirs, run_type: RunType) -> Result<()> {
run_type.execute(zsh).args(["-l", "-c", cmd.as_str()]).status_checked() run_type.execute(zsh).args(["-l", "-c", cmd.as_str()]).status_checked()
} }
pub fn run_zgenom(base_dirs: &BaseDirs, run_type: RunType) -> Result<()> { pub fn run_zgenom(run_type: RunType) -> Result<()> {
let zsh = require("zsh")?; let zsh = require("zsh")?;
let zshrc = zshrc(base_dirs).require()?; let zshrc = zshrc().require()?;
env::var("ZGEN_SOURCE") env::var("ZGEN_SOURCE")
.map(PathBuf::from) .map(PathBuf::from)
.unwrap_or_else(|_| base_dirs.home_dir().join(".zgenom")) .unwrap_or_else(|_| HOME_DIR.join(".zgenom"))
.require()?; .require()?;
print_separator("zgenom"); print_separator("zgenom");
@@ -68,13 +86,13 @@ pub fn run_zgenom(base_dirs: &BaseDirs, run_type: RunType) -> Result<()> {
run_type.execute(zsh).args(["-l", "-c", cmd.as_str()]).status_checked() run_type.execute(zsh).args(["-l", "-c", cmd.as_str()]).status_checked()
} }
pub fn run_zplug(base_dirs: &BaseDirs, run_type: RunType) -> Result<()> { pub fn run_zplug(run_type: RunType) -> Result<()> {
let zsh = require("zsh")?; let zsh = require("zsh")?;
zshrc(base_dirs).require()?; zshrc().require()?;
env::var("ZPLUG_HOME") env::var("ZPLUG_HOME")
.map(PathBuf::from) .map(PathBuf::from)
.unwrap_or_else(|_| base_dirs.home_dir().join(".zplug")) .unwrap_or_else(|_| HOME_DIR.join(".zplug"))
.require()?; .require()?;
print_separator("zplug"); print_separator("zplug");
@@ -85,13 +103,13 @@ pub fn run_zplug(base_dirs: &BaseDirs, run_type: RunType) -> Result<()> {
.status_checked() .status_checked()
} }
pub fn run_zinit(base_dirs: &BaseDirs, run_type: RunType) -> Result<()> { pub fn run_zinit(run_type: RunType) -> Result<()> {
let zsh = require("zsh")?; let zsh = require("zsh")?;
let zshrc = zshrc(base_dirs).require()?; let zshrc = zshrc().require()?;
env::var("ZINIT_HOME") env::var("ZINIT_HOME")
.map(PathBuf::from) .map(PathBuf::from)
.unwrap_or_else(|_| base_dirs.home_dir().join(".zinit")) .unwrap_or_else(|_| HOME_DIR.join(".zinit"))
.require()?; .require()?;
print_separator("zinit"); print_separator("zinit");
@@ -100,11 +118,11 @@ pub fn run_zinit(base_dirs: &BaseDirs, run_type: RunType) -> Result<()> {
run_type.execute(zsh).args(["-i", "-c", cmd.as_str()]).status_checked() run_type.execute(zsh).args(["-i", "-c", cmd.as_str()]).status_checked()
} }
pub fn run_zi(base_dirs: &BaseDirs, run_type: RunType) -> Result<()> { pub fn run_zi(run_type: RunType) -> Result<()> {
let zsh = require("zsh")?; let zsh = require("zsh")?;
let zshrc = zshrc(base_dirs).require()?; let zshrc = zshrc().require()?;
base_dirs.home_dir().join(".zi").require()?; HOME_DIR.join(".zi").require()?;
print_separator("zi"); print_separator("zi");
@@ -112,7 +130,7 @@ pub fn run_zi(base_dirs: &BaseDirs, run_type: RunType) -> Result<()> {
run_type.execute(zsh).args(["-i", "-c", &cmd]).status_checked() run_type.execute(zsh).args(["-i", "-c", &cmd]).status_checked()
} }
pub fn run_zim(base_dirs: &BaseDirs, run_type: RunType) -> Result<()> { pub fn run_zim(run_type: RunType) -> Result<()> {
let zsh = require("zsh")?; let zsh = require("zsh")?;
env::var("ZIM_HOME") env::var("ZIM_HOME")
.or_else(|_| { .or_else(|_| {
@@ -123,7 +141,7 @@ pub fn run_zim(base_dirs: &BaseDirs, run_type: RunType) -> Result<()> {
.map(|o| o.stdout) .map(|o| o.stdout)
}) })
.map(PathBuf::from) .map(PathBuf::from)
.unwrap_or_else(|_| base_dirs.home_dir().join(".zim")) .unwrap_or_else(|_| HOME_DIR.join(".zim"))
.require()?; .require()?;
print_separator("zim"); print_separator("zim");
@@ -136,7 +154,7 @@ pub fn run_zim(base_dirs: &BaseDirs, run_type: RunType) -> Result<()> {
pub fn run_oh_my_zsh(ctx: &ExecutionContext) -> Result<()> { pub fn run_oh_my_zsh(ctx: &ExecutionContext) -> Result<()> {
require("zsh")?; require("zsh")?;
let oh_my_zsh = ctx.base_dirs().home_dir().join(".oh-my-zsh").require()?; let oh_my_zsh = HOME_DIR.join(".oh-my-zsh").require()?;
print_separator("oh-my-zsh"); print_separator("oh-my-zsh");

View File

@@ -4,6 +4,8 @@ use std::path::PathBuf;
use color_eyre::eyre::Context; use color_eyre::eyre::Context;
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use serde::Deserialize;
use strum::AsRefStr;
use crate::command::CommandExt; use crate::command::CommandExt;
use crate::execution_context::ExecutionContext; use crate::execution_context::ExecutionContext;
@@ -24,12 +26,18 @@ impl Sudo {
pub fn detect() -> Option<Self> { pub fn detect() -> Option<Self> {
which("doas") which("doas")
.map(|p| (p, SudoKind::Doas)) .map(|p| (p, SudoKind::Doas))
.or_else(|| which("please").map(|p| (p, SudoKind::Please)))
.or_else(|| which("sudo").map(|p| (p, SudoKind::Sudo))) .or_else(|| which("sudo").map(|p| (p, SudoKind::Sudo)))
.or_else(|| which("gsudo").map(|p| (p, SudoKind::Gsudo))) .or_else(|| which("gsudo").map(|p| (p, SudoKind::Gsudo)))
.or_else(|| which("pkexec").map(|p| (p, SudoKind::Pkexec))) .or_else(|| which("pkexec").map(|p| (p, SudoKind::Pkexec)))
.map(|(path, kind)| Self { path, kind }) .map(|(path, kind)| Self { path, kind })
} }
/// Create Sudo from SudoKind, if found in the system
pub fn new(kind: SudoKind) -> Option<Self> {
which(kind.as_ref()).map(|path| Self { path, kind })
}
/// Elevate permissions with `sudo`. /// Elevate permissions with `sudo`.
/// ///
/// This helps prevent blocking `sudo` prompts from stopping the run in the middle of a /// This helps prevent blocking `sudo` prompts from stopping the run in the middle of a
@@ -47,6 +55,12 @@ impl Sudo {
// See: https://man.openbsd.org/doas // See: https://man.openbsd.org/doas
cmd.arg("echo"); cmd.arg("echo");
} }
SudoKind::Please => {
// From `man please`
// -w, --warm
// Warm the access token and exit.
cmd.arg("-w");
}
SudoKind::Sudo => { SudoKind::Sudo => {
// From `man sudo` on macOS: // From `man sudo` on macOS:
// -v, --validate // -v, --validate
@@ -93,9 +107,12 @@ impl Sudo {
} }
} }
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug, Deserialize, AsRefStr)]
enum SudoKind { #[serde(rename_all = "lowercase")]
#[strum(serialize_all = "lowercase")]
pub enum SudoKind {
Doas, Doas,
Please,
Sudo, Sudo,
Gsudo, Gsudo,
Pkexec, Pkexec,

View File

@@ -60,7 +60,7 @@ impl Terminal {
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(|prefix| format!("({}) ", prefix)) .map(|prefix| format!("({prefix}) "))
.unwrap_or_else(|_| String::new()), .unwrap_or_else(|_| String::new()),
set_title: true, set_title: true,
display_time: true, display_time: true,
@@ -106,7 +106,7 @@ impl Terminal {
command.args(["-a", "Topgrade", "Topgrade"]); command.args(["-a", "Topgrade", "Topgrade"]);
command.arg(message.as_ref()); command.arg(message.as_ref());
if let Err(err) = command.output_checked() { if let Err(err) = command.output_checked() {
terminal::print_warning("Senfing notification failed with {err:?}"); terminal::print_warning("Sending notification failed with {err:?}");
} }
} }
} }
@@ -143,7 +143,7 @@ impl Terminal {
.write_fmt(format_args!( .write_fmt(format_args!(
"{}\n", "{}\n",
style(format_args!( style(format_args!(
"\n―― {} {:^border$}", "\n── {} {:^border$}",
message, message,
"", "",
border = max( border = max(
@@ -159,7 +159,7 @@ impl Terminal {
.ok(); .ok();
} }
None => { None => {
self.term.write_fmt(format_args!("―― {} ――\n", message)).ok(); self.term.write_fmt(format_args!("―― {message} ――\n")).ok();
} }
} }
} }
@@ -171,7 +171,7 @@ impl Terminal {
self.term self.term
.write_fmt(format_args!( .write_fmt(format_args!(
"{} {}", "{} {}",
style(format!("{} failed:", key)).red().bold(), style(format!("{key} failed:")).red().bold(),
message message
)) ))
.ok(); .ok();
@@ -215,7 +215,7 @@ impl Terminal {
self.term self.term
.write_fmt(format_args!( .write_fmt(format_args!(
"{}", "{}",
style(format!("{} (y)es/(N)o", question,)).yellow().bold() style(format!("{question} (y)es/(N)o",)).yellow().bold()
)) ))
.ok(); .ok();
@@ -237,13 +237,15 @@ impl Terminal {
self.term.set_title("Topgrade - Awaiting user"); self.term.set_title("Topgrade - Awaiting user");
} }
self.notify_desktop(format!("{} failed", step_name), None); if self.desktop_notification {
self.notify_desktop(format!("{step_name} failed"), None);
}
let prompt_inner = style(format!("{}Retry? (y)es/(N)o/(s)hell/(q)uit", self.prefix)) let prompt_inner = style(format!("{}Retry? (y)es/(N)o/(s)hell/(q)uit", self.prefix))
.yellow() .yellow()
.bold(); .bold();
self.term.write_fmt(format_args!("\n{}", prompt_inner)).ok(); self.term.write_fmt(format_args!("\n{prompt_inner}")).ok();
let answer = loop { let answer = loop {
match self.term.read_key() { match self.term.read_key() {
@@ -251,7 +253,7 @@ impl Terminal {
Ok(Key::Char('s')) | Ok(Key::Char('S')) => { Ok(Key::Char('s')) | Ok(Key::Char('S')) => {
println!("\n\nDropping you to shell. Fix what you need and then exit the shell.\n"); println!("\n\nDropping you to shell. Fix what you need and then exit the shell.\n");
if let Err(err) = run_shell().context("Failed to run shell") { if let Err(err) = run_shell().context("Failed to run shell") {
self.term.write_fmt(format_args!("{err:?}\n{}", prompt_inner)).ok(); self.term.write_fmt(format_args!("{err:?}\n{prompt_inner}")).ok();
} else { } else {
break Ok(true); break Ok(true);
} }
@@ -343,3 +345,8 @@ pub fn notify_desktop<P: AsRef<str>>(message: P, timeout: Option<Duration>) {
pub fn display_time(display_time: bool) { pub fn display_time(display_time: bool) {
TERMINAL.lock().unwrap().display_time(display_time); TERMINAL.lock().unwrap().display_time(display_time);
} }
#[cfg(target_os = "linux")]
pub fn supports_notify_send() -> bool {
TERMINAL.lock().unwrap().notify_send.is_some()
}

View File

@@ -149,6 +149,6 @@ pub fn hostname() -> Result<String> {
Command::new("hostname") Command::new("hostname")
.output_checked_utf8() .output_checked_utf8()
.map_err(|err| SkipStep(format!("Failed to get hostname: {}", err)).into()) .map_err(|err| SkipStep(format!("Failed to get hostname: {err}")).into())
.map(|output| output.stdout.trim().to_owned()) .map(|output| output.stdout.trim().to_owned())
} }