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 shutil
|
||||||
|
import subprocess
|
||||||
import textwrap
|
import textwrap
|
||||||
from argparse import Namespace
|
from argparse import Namespace
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from caelestia.utils.dots.deployer import Deployer
|
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.manifest import ComponentError, Manifest, ManifestError
|
||||||
from caelestia.utils.dots.misc import build_local_packages, run_hooks
|
from caelestia.utils.dots.misc import build_local_packages, run_hooks
|
||||||
from caelestia.utils.dots.packages import DEFAULT_AUR_HELPER, PackageInstaller
|
from caelestia.utils.dots.packages import DEFAULT_AUR_HELPER, PackageInstaller
|
||||||
@@ -34,14 +36,15 @@ class Command:
|
|||||||
|
|
||||||
self.print_greeting()
|
self.print_greeting()
|
||||||
self.create_backup()
|
self.create_backup()
|
||||||
|
legacy_dir = detect_legacy_repo() # Detect legacy repo first cause deploy overwrites legacy syms
|
||||||
|
|
||||||
source, tip, manifest = self.fetch_manifest()
|
source, tip, manifest = self.fetch_manifest()
|
||||||
deployed = self.deploy_configs(source, 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")
|
run_hooks(manifest, "post_install")
|
||||||
|
|
||||||
DotsState(
|
DotsState(
|
||||||
aur_helper=helper,
|
aur_helper=getattr(installer, "helper", DEFAULT_AUR_HELPER),
|
||||||
applied_rev=tip,
|
applied_rev=tip,
|
||||||
enabled_components=manifest.enabled_components,
|
enabled_components=manifest.enabled_components,
|
||||||
packages=packages,
|
packages=packages,
|
||||||
@@ -49,6 +52,7 @@ class Command:
|
|||||||
deployed_files=deployed,
|
deployed_files=deployed,
|
||||||
).save()
|
).save()
|
||||||
|
|
||||||
|
self.migrate_legacy(installer, legacy_dir)
|
||||||
self.print_done()
|
self.print_done()
|
||||||
|
|
||||||
def print_greeting(self) -> None:
|
def print_greeting(self) -> None:
|
||||||
@@ -144,7 +148,9 @@ class Command:
|
|||||||
|
|
||||||
return deployer.deployed_files
|
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)
|
installer = PackageInstaller.get(self.args.aur_helper, self.args.noconfirm)
|
||||||
|
|
||||||
packages = manifest.enabled_packages()
|
packages = manifest.enabled_packages()
|
||||||
@@ -160,7 +166,32 @@ class Command:
|
|||||||
log("Building local packages...")
|
log("Building local packages...")
|
||||||
local_packages = build_local_packages(installer, source, local_dirs)
|
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:
|
def print_done(self) -> None:
|
||||||
print()
|
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]:
|
def build_install(self, directory: Path) -> list[str]:
|
||||||
"""Build and install the PKGBUILD in `directory`, returning the installed package names."""
|
"""Build and install the PKGBUILD in `directory`, returning the installed package names."""
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def is_installed(self, package: str) -> bool: ...
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def system_update(self) -> None: ...
|
def system_update(self) -> None: ...
|
||||||
|
|
||||||
@@ -92,6 +95,9 @@ class NoopInstaller(PackageInstaller):
|
|||||||
info(f"Skipping local package build (not on Arch): {directory}")
|
info(f"Skipping local package build (not on Arch): {directory}")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
def is_installed(self, package: str) -> bool:
|
||||||
|
return False
|
||||||
|
|
||||||
def system_update(self) -> None:
|
def system_update(self) -> None:
|
||||||
info("Skipping system update (not on Arch)")
|
info("Skipping system update (not on Arch)")
|
||||||
|
|
||||||
@@ -101,10 +107,18 @@ class ArchInstaller(PackageInstaller):
|
|||||||
self.helper = helper
|
self.helper = helper
|
||||||
self.flags = ["--noconfirm"] if noconfirm else []
|
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:
|
if not packages:
|
||||||
return
|
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:
|
def remove(self, packages: list[str]) -> None:
|
||||||
if not packages:
|
if not packages:
|
||||||
@@ -126,7 +140,7 @@ class ArchInstaller(PackageInstaller):
|
|||||||
elif key == "depends":
|
elif key == "depends":
|
||||||
depends.append(value.strip())
|
depends.append(value.strip())
|
||||||
|
|
||||||
self.install(depends, extra_flags=["--asdeps"])
|
self.install(depends, explicit=False)
|
||||||
|
|
||||||
# Stop makepkg from resetting sudo
|
# Stop makepkg from resetting sudo
|
||||||
env = {**os.environ, "PACMAN_AUTH": "sudo"}
|
env = {**os.environ, "PACMAN_AUTH": "sudo"}
|
||||||
@@ -135,5 +149,15 @@ class ArchInstaller(PackageInstaller):
|
|||||||
|
|
||||||
return names
|
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:
|
def system_update(self) -> None:
|
||||||
subprocess.run([self.helper, "-Syu", *self.flags], check=True)
|
subprocess.run([self.helper, "-Syu", *self.flags], check=True)
|
||||||
|
|||||||
Reference in New Issue
Block a user