feat(desktop): consolidate declarative workstation profile
Capture desktop-specific system, session, Noctalia, and Pi integration alongside shared laptop support. Retire obsolete X11 configuration and make package ownership explicit for repeatable Decman deployments.
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Safely move or delete entries inside the knowledge vault."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import shutil
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
VAULT: Path
|
||||
|
||||
|
||||
def fail(message: str) -> int:
|
||||
print(message, file=sys.stderr)
|
||||
return 1
|
||||
|
||||
|
||||
def vault_entry(relative: str) -> Path | None:
|
||||
if not relative or relative.startswith("/") or "\0" in relative:
|
||||
return None
|
||||
candidate = VAULT / relative
|
||||
parent = candidate.parent.resolve()
|
||||
if parent != VAULT and VAULT not in parent.parents:
|
||||
return None
|
||||
return parent / candidate.name
|
||||
|
||||
|
||||
def move_entry(source_relative: str, destination_relative: str) -> int:
|
||||
source = vault_entry(source_relative)
|
||||
destination_directory = (VAULT / destination_relative).resolve()
|
||||
if source is None or not (source.exists() or source.is_symlink()):
|
||||
return fail("selected entry no longer exists")
|
||||
if destination_directory != VAULT and VAULT not in destination_directory.parents:
|
||||
return fail("destination escaped the vault")
|
||||
if not destination_directory.is_dir():
|
||||
return fail("destination directory no longer exists")
|
||||
if source.is_dir() and (
|
||||
destination_directory == source or source in destination_directory.parents
|
||||
):
|
||||
return fail("cannot move a directory into itself")
|
||||
|
||||
destination = destination_directory / source.name
|
||||
if destination.exists() or destination.is_symlink():
|
||||
return fail(f"already exists: {destination.name}")
|
||||
try:
|
||||
source.rename(destination)
|
||||
except OSError as error:
|
||||
return fail(f"cannot move {source.name}: {error}")
|
||||
|
||||
json.dump(
|
||||
{
|
||||
"action": "move",
|
||||
"relative": destination.relative_to(VAULT).as_posix(),
|
||||
"kind": "directory" if destination.is_dir() else "note",
|
||||
},
|
||||
sys.stdout,
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
def delete_entry(source_relative: str) -> int:
|
||||
source = vault_entry(source_relative)
|
||||
if source is None or not (source.exists() or source.is_symlink()):
|
||||
return fail("selected entry no longer exists")
|
||||
try:
|
||||
if source.is_symlink():
|
||||
source.unlink()
|
||||
elif source.is_dir():
|
||||
shutil.rmtree(source)
|
||||
else:
|
||||
source.unlink()
|
||||
except OSError as error:
|
||||
return fail(f"cannot delete {source.name}: {error}")
|
||||
json.dump({"action": "delete", "relative": source_relative}, sys.stdout)
|
||||
return 0
|
||||
|
||||
|
||||
def main() -> int:
|
||||
global VAULT
|
||||
if len(sys.argv) < 4:
|
||||
return fail(
|
||||
"usage: manage-entry.py VAULT ACTION SOURCE [DESTINATION_DIRECTORY]"
|
||||
)
|
||||
VAULT = Path(sys.argv[1]).expanduser().resolve()
|
||||
if not VAULT.is_dir():
|
||||
return fail(f"vault does not exist: {VAULT}")
|
||||
action, source = sys.argv[2:4]
|
||||
if action == "move" and len(sys.argv) == 5:
|
||||
return move_entry(source, sys.argv[4])
|
||||
if action == "delete" and len(sys.argv) == 4:
|
||||
return delete_entry(source)
|
||||
return fail("invalid file operation")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user