mirror of
https://github.com/caelestia-dots/cli.git
synced 2026-06-18 15:00:00 -05:00
feat: add migration step to install cmd (#126)
* feat: add migration step to install cmd * fix: set packages to explicitly installed * fix: legacy remote check Oops * fix: generator * fix: better legacy detection * fix: run legacy detection before deployment Also fix unlink on dir * fix: legacy file check issue * fix: handle no legacy * fix: make sure not to go past home when looking for repo * fix: wrong dir for default legacy path * fix: catch errors with deleting legacy install
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
import shutil
|
||||
import subprocess
|
||||
import textwrap
|
||||
from argparse import Namespace
|
||||
from pathlib import Path
|
||||
|
||||
from caelestia.utils.dots.deployer import Deployer
|
||||
from caelestia.utils.dots.legacy import LEGACY_META_PKG, detect_legacy_repo, legacy_to_delete
|
||||
from caelestia.utils.dots.manifest import ComponentError, Manifest, ManifestError
|
||||
from caelestia.utils.dots.misc import build_local_packages, run_hooks
|
||||
from caelestia.utils.dots.packages import DEFAULT_AUR_HELPER, PackageInstaller
|
||||
@@ -34,14 +36,15 @@ class Command:
|
||||
|
||||
self.print_greeting()
|
||||
self.create_backup()
|
||||
legacy_dir = detect_legacy_repo() # Detect legacy repo first cause deploy overwrites legacy syms
|
||||
|
||||
source, tip, manifest = self.fetch_manifest()
|
||||
deployed = self.deploy_configs(source, manifest)
|
||||
helper, packages, local_packages = self.install_packages(source, manifest)
|
||||
installer, packages, local_packages = self.install_packages(source, manifest)
|
||||
run_hooks(manifest, "post_install")
|
||||
|
||||
DotsState(
|
||||
aur_helper=helper,
|
||||
aur_helper=getattr(installer, "helper", DEFAULT_AUR_HELPER),
|
||||
applied_rev=tip,
|
||||
enabled_components=manifest.enabled_components,
|
||||
packages=packages,
|
||||
@@ -49,6 +52,7 @@ class Command:
|
||||
deployed_files=deployed,
|
||||
).save()
|
||||
|
||||
self.migrate_legacy(installer, legacy_dir)
|
||||
self.print_done()
|
||||
|
||||
def print_greeting(self) -> None:
|
||||
@@ -144,7 +148,9 @@ class Command:
|
||||
|
||||
return deployer.deployed_files
|
||||
|
||||
def install_packages(self, source: DotsSource, manifest: Manifest) -> tuple[str, list[str], dict[str, list[str]]]:
|
||||
def install_packages(
|
||||
self, source: DotsSource, manifest: Manifest
|
||||
) -> tuple[PackageInstaller, list[str], dict[str, list[str]]]:
|
||||
installer = PackageInstaller.get(self.args.aur_helper, self.args.noconfirm)
|
||||
|
||||
packages = manifest.enabled_packages()
|
||||
@@ -160,7 +166,32 @@ class Command:
|
||||
log("Building local packages...")
|
||||
local_packages = build_local_packages(installer, source, local_dirs)
|
||||
|
||||
return getattr(installer, "helper", DEFAULT_AUR_HELPER), packages, local_packages
|
||||
return installer, packages, local_packages
|
||||
|
||||
def migrate_legacy(self, installer: PackageInstaller, legacy_dir: Path | None) -> None:
|
||||
"""Clean up a previous install.fish setup (repo, symlinks and metapackage)."""
|
||||
|
||||
to_delete = legacy_to_delete(legacy_dir)
|
||||
meta_installed = installer.is_installed(LEGACY_META_PKG)
|
||||
if not to_delete and not meta_installed:
|
||||
return
|
||||
|
||||
print()
|
||||
log("Found a legacy Caelestia installation...")
|
||||
if not confirm("Clear legacy installation?"):
|
||||
return
|
||||
|
||||
deployer = Deployer()
|
||||
try:
|
||||
for path in to_delete:
|
||||
deployer.remove(path)
|
||||
info(f"Deleted {path}")
|
||||
|
||||
if meta_installed:
|
||||
log("Removing legacy meta package...")
|
||||
installer.remove([LEGACY_META_PKG])
|
||||
except (OSError, subprocess.CalledProcessError) as e:
|
||||
warn(f"could not fully clear the legacy installation: {e}")
|
||||
|
||||
def print_done(self) -> None:
|
||||
print()
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
from caelestia.utils.paths import config_dir, data_dir
|
||||
|
||||
LEGACY_META_PKG = "caelestia-meta"
|
||||
|
||||
_confs = [
|
||||
"hypr",
|
||||
"starship.toml",
|
||||
"foot",
|
||||
"fish",
|
||||
"fastfetch",
|
||||
"uwsm",
|
||||
"btop",
|
||||
"spicetify",
|
||||
"Code/User/settings.json",
|
||||
"VSCodium/User/settings.json",
|
||||
"Code/User/keybindings.json",
|
||||
"VSCodium/User/keybindings.json",
|
||||
"code-flags.conf",
|
||||
"codium-flags.conf",
|
||||
]
|
||||
|
||||
|
||||
def _find_legacy_repo(path: Path) -> Path | None:
|
||||
try:
|
||||
remote = subprocess.check_output(["git", "-C", path, "remote", "get-url", "origin"], text=True)
|
||||
except subprocess.CalledProcessError:
|
||||
return
|
||||
|
||||
# Check remote
|
||||
if remote.strip() != "https://github.com/caelestia-dots/caelestia.git":
|
||||
return
|
||||
|
||||
# Ignore anything outside home
|
||||
if Path.home() not in path.parents:
|
||||
return
|
||||
|
||||
# Walk up parents (capped at home) to find the repo root
|
||||
while path != Path.home() and not (path / ".git").is_dir():
|
||||
path = path.parent
|
||||
|
||||
# Only return path if didn't hit home (we really don't want to nuke home)
|
||||
if path != Path.home():
|
||||
return path
|
||||
|
||||
|
||||
def detect_legacy_repo() -> Path | None:
|
||||
for conf in _confs:
|
||||
path = config_dir / conf
|
||||
if not path.is_symlink():
|
||||
continue
|
||||
|
||||
legacy_dir = _find_legacy_repo(path.resolve())
|
||||
if legacy_dir:
|
||||
return legacy_dir
|
||||
|
||||
return _find_legacy_repo(data_dir / "caelestia")
|
||||
|
||||
|
||||
def legacy_to_delete(legacy_dir: Path | None) -> list[Path]:
|
||||
if not legacy_dir:
|
||||
return []
|
||||
|
||||
to_delete = []
|
||||
|
||||
for conf in _confs:
|
||||
path = config_dir / conf
|
||||
if path.is_symlink() and legacy_dir in path.resolve().parents:
|
||||
to_delete.append(path)
|
||||
|
||||
others = [
|
||||
*(Path.home() / ".zen").glob("*/chrome/userChrome.css"),
|
||||
Path.home() / ".local/lib/caelestia/caelestiafox",
|
||||
]
|
||||
for path in others:
|
||||
if path.is_symlink() and legacy_dir in path.resolve().parents:
|
||||
to_delete.append(path)
|
||||
|
||||
to_delete.append(legacy_dir)
|
||||
|
||||
return to_delete
|
||||
@@ -73,6 +73,9 @@ class PackageInstaller(ABC):
|
||||
def build_install(self, directory: Path) -> list[str]:
|
||||
"""Build and install the PKGBUILD in `directory`, returning the installed package names."""
|
||||
|
||||
@abstractmethod
|
||||
def is_installed(self, package: str) -> bool: ...
|
||||
|
||||
@abstractmethod
|
||||
def system_update(self) -> None: ...
|
||||
|
||||
@@ -92,6 +95,9 @@ class NoopInstaller(PackageInstaller):
|
||||
info(f"Skipping local package build (not on Arch): {directory}")
|
||||
return []
|
||||
|
||||
def is_installed(self, package: str) -> bool:
|
||||
return False
|
||||
|
||||
def system_update(self) -> None:
|
||||
info("Skipping system update (not on Arch)")
|
||||
|
||||
@@ -101,10 +107,18 @@ class ArchInstaller(PackageInstaller):
|
||||
self.helper = helper
|
||||
self.flags = ["--noconfirm"] if noconfirm else []
|
||||
|
||||
def install(self, packages: list[str], extra_flags: list[str] | None = None) -> None:
|
||||
def install(self, packages: list[str], explicit: bool = True) -> None:
|
||||
if not packages:
|
||||
return
|
||||
subprocess.run([self.helper, "-S", "--needed", *self.flags, *(extra_flags or []), *packages], check=True)
|
||||
|
||||
cmd = [self.helper, "-S", "--needed", *self.flags]
|
||||
if not explicit:
|
||||
cmd.append("--asdeps") # Set install reason to dep (does not affect already installed packages)
|
||||
subprocess.run(cmd + packages, check=True)
|
||||
|
||||
# Force install reason to explicit install
|
||||
if explicit:
|
||||
subprocess.run([self.helper, "-D", "--asexplicit", *self.flags, *packages], check=True)
|
||||
|
||||
def remove(self, packages: list[str]) -> None:
|
||||
if not packages:
|
||||
@@ -126,7 +140,7 @@ class ArchInstaller(PackageInstaller):
|
||||
elif key == "depends":
|
||||
depends.append(value.strip())
|
||||
|
||||
self.install(depends, extra_flags=["--asdeps"])
|
||||
self.install(depends, explicit=False)
|
||||
|
||||
# Stop makepkg from resetting sudo
|
||||
env = {**os.environ, "PACMAN_AUTH": "sudo"}
|
||||
@@ -135,5 +149,15 @@ class ArchInstaller(PackageInstaller):
|
||||
|
||||
return names
|
||||
|
||||
def is_installed(self, package: str) -> bool:
|
||||
return (
|
||||
subprocess.run(
|
||||
["pacman", "-Q", package],
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
).returncode
|
||||
== 0
|
||||
)
|
||||
|
||||
def system_update(self) -> None:
|
||||
subprocess.run([self.helper, "-Syu", *self.flags], check=True)
|
||||
|
||||
Reference in New Issue
Block a user