pass_typer: Try more candidate globs for user and host variants
This commit is contained in:
parent
7c9f062061
commit
f0c26851e5
@ -1,11 +1,14 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import dataclasses
|
||||||
|
import getpass
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
|
from collections.abc import Iterator
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from itertools import chain
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
PASSWORD_STORE = Path(
|
PASSWORD_STORE = Path(
|
||||||
os.environ.get("PASSWORD_STORE_DIR", Path("~/.password-store").expanduser())
|
os.environ.get("PASSWORD_STORE_DIR", Path("~/.password-store").expanduser())
|
||||||
@ -14,18 +17,79 @@ PASSWORD_STORE = Path(
|
|||||||
cmd = partial(subprocess.run, capture_output=True, encoding="ascii")
|
cmd = partial(subprocess.run, capture_output=True, encoding="ascii")
|
||||||
|
|
||||||
|
|
||||||
def alias(server_name):
|
@dataclasses.dataclass
|
||||||
match server_name.rpartition("@"):
|
class ServerMatch:
|
||||||
case [user, at, ("salt" | "salt.sawtooth.claremontmakerspace.org")]:
|
folder: Optional[str]
|
||||||
return f"cms/{user}{at}cms-net-svcs"
|
user: Optional[str]
|
||||||
case [user, at, host] if host.lower().startswith("cms-wall-display") or host.startswith("iPad"):
|
host: str
|
||||||
return f"cms/{user}{at}ipads"
|
|
||||||
case [user, at, ("octopi-taz-6" | "octopi-lulzbot-mini")]:
|
@classmethod
|
||||||
return f"cms/{user}{at}octopi"
|
def from_destination(cls, server_name: str) -> 'ServerMatch':
|
||||||
case [user, at, server] if re.match(".*-pidgey(.vpn)?", server):
|
user, _, host = server_name.rpartition("@")
|
||||||
return "tsrc/pidgey"
|
return cls(None, user if user != "" else None, host).alias()
|
||||||
case _:
|
|
||||||
return f"**/{server_name}"
|
def alias(self) -> 'ServerMatch':
|
||||||
|
# TODO: this still feels a bit hacky
|
||||||
|
host = self.host
|
||||||
|
if host.endswith(".sawtooth.claremontmakerspace.org"):
|
||||||
|
self.folder = "cms"
|
||||||
|
host = self.host.removesuffix(".sawtooth.claremontmakerspace.org")
|
||||||
|
|
||||||
|
if host == "salt":
|
||||||
|
self.folder = "cms"
|
||||||
|
self.host = "cms-net-svcs"
|
||||||
|
elif host.lower().startswith("cms-wall-display") or host.startswith("iPad"):
|
||||||
|
self.folder = "cms"
|
||||||
|
self.host = "ipads"
|
||||||
|
elif host in ("octopi-taz-6", "octopi-lulzbot-mini"):
|
||||||
|
self.folder = "cms"
|
||||||
|
self.host = "octopi"
|
||||||
|
elif re.match(".*-pidgey(.vpn)?", host):
|
||||||
|
self.folder = "tsrc"
|
||||||
|
self.host = "pidgey"
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
|
def to_globs(self) -> Iterator[Path]:
|
||||||
|
path = Path("servers")
|
||||||
|
if self.folder is not None:
|
||||||
|
path = path / Path(self.folder)
|
||||||
|
else:
|
||||||
|
path = path / "**"
|
||||||
|
|
||||||
|
user_candidates = [None, getpass.getuser()]
|
||||||
|
if self.user is None:
|
||||||
|
user_candidates.append("*")
|
||||||
|
else:
|
||||||
|
user_candidates.insert(0, self.user)
|
||||||
|
|
||||||
|
# try host without each subdomain level
|
||||||
|
host_parts = self.host.split(".")
|
||||||
|
host_candidates = [
|
||||||
|
".".join(host_parts[:i]) for i in range(len(host_parts), 0, -1)
|
||||||
|
]
|
||||||
|
host_candidates.append(self.host + ".*")
|
||||||
|
|
||||||
|
# generate all combinations of user and host
|
||||||
|
for user in user_candidates:
|
||||||
|
for host in host_candidates:
|
||||||
|
if user is None:
|
||||||
|
yield path / f"{host}.gpg"
|
||||||
|
else:
|
||||||
|
yield path / f"{user}@{host}.gpg"
|
||||||
|
|
||||||
|
def to_paths(self) -> Iterator[Path]:
|
||||||
|
print("Candidate globs:")
|
||||||
|
for glob in self.to_globs():
|
||||||
|
print(glob)
|
||||||
|
yield from PASSWORD_STORE.glob(str(glob))
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
folder = self.folder if self.folder is not None else '**'
|
||||||
|
if self.user is not None and self.user != "":
|
||||||
|
return f"{folder}/{self.user}@{self.host}"
|
||||||
|
else:
|
||||||
|
return f"{folder}/{self.host}"
|
||||||
|
|
||||||
|
|
||||||
def notify(summary: str, body: str) -> None:
|
def notify(summary: str, body: str) -> None:
|
||||||
@ -44,15 +108,15 @@ def get_password(password_name: str) -> str:
|
|||||||
return password
|
return password
|
||||||
|
|
||||||
|
|
||||||
def select_and_type(server_name: Path) -> None:
|
def select_and_type(server_match: ServerMatch) -> None:
|
||||||
path = ("servers" / server_name).with_name(server_name.name + ".gpg")
|
paths = server_match.to_paths()
|
||||||
files = chain(
|
|
||||||
PASSWORD_STORE.glob(str(path)),
|
|
||||||
PASSWORD_STORE.glob(str(path.with_name("*@" + path.name))),
|
|
||||||
)
|
|
||||||
file_list = list(
|
file_list = list(
|
||||||
dict.fromkeys(str(f.relative_to(PASSWORD_STORE).with_suffix("")) for f in files)
|
dict.fromkeys(str(f.relative_to(PASSWORD_STORE).with_suffix("")) for f in paths)
|
||||||
)
|
)
|
||||||
|
if len(file_list) == 0:
|
||||||
|
notify(f"No matches found for '{server_match}'", "")
|
||||||
|
return
|
||||||
|
|
||||||
selected = rofi_select(file_list)
|
selected = rofi_select(file_list)
|
||||||
if selected:
|
if selected:
|
||||||
@ -63,13 +127,13 @@ def select_and_type(server_name: Path) -> None:
|
|||||||
|
|
||||||
window_name = cmd(["xdotool", "getactivewindow", "getwindowname"]).stdout.strip()
|
window_name = cmd(["xdotool", "getactivewindow", "getwindowname"]).stdout.strip()
|
||||||
|
|
||||||
ssh_match = re.search(":([^ ]+=[^ ]* )*(mosh|ssh) (?P<server>.*)", window_name)
|
ssh_match = re.search(":([^ ]+=[^ ]* )*(mosh|ssh) (?P<destination>.*)", window_name)
|
||||||
if ssh_match:
|
if ssh_match:
|
||||||
server_name = alias(ssh_match.group("server"))
|
server_match = ServerMatch.from_destination(ssh_match.group("destination"))
|
||||||
|
|
||||||
notify(f"Matched server '{server_name}'", f"Window name: {window_name}")
|
notify(f"Matched server '{server_match}'", f"Window name: {window_name}")
|
||||||
|
|
||||||
select_and_type(Path(server_name))
|
select_and_type(server_match)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
notify("Window name did not match any rules", f"Window name: {window_name}")
|
notify("Window name did not match any rules", f"Window name: {window_name}")
|
||||||
|
Loading…
Reference in New Issue
Block a user