326 lines
20 KiB
Python
Executable File
326 lines
20 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Generate the "Scripts in this directory" section of cheatsheet.md from
|
|
the actual contents of /sf/bernina/bin -- a full index of every script
|
|
here, grouped by topic, as a set of "Command | Description" tables.
|
|
|
|
Descriptions are hand-curated below (COMMANDS dict), not auto-derived --
|
|
most of these scripts have no usable header comment (many are a single
|
|
command line), so a heuristic would mostly just echo the command back.
|
|
Any file present in the directory but missing from COMMANDS lands in an
|
|
"Uncategorized" bucket at the end instead of being silently dropped, so
|
|
new scripts get noticed next time this runs rather than staying invisible
|
|
forever. Anything in COMMANDS that no longer exists on disk is silently
|
|
skipped (self-heals when scripts are removed).
|
|
|
|
These tables are headed "Command | Description" -- cheatsheet_to_shortcuts_json
|
|
recognizes that header (alongside "Shortcut") so this section also shows up
|
|
in the Super+F1 overlay, not just the full document.
|
|
|
|
To keep one script out of this listing entirely, put a comment containing
|
|
HIDE_MARKER (see below) anywhere in its first HEADER_SCAN_LINES lines --
|
|
no need to touch this generator. Takes priority over everything else,
|
|
including an entry already in COMMANDS.
|
|
|
|
Usage: list_bin_commands [bin-dir] [cheatsheet.md path]
|
|
Rewrites the content between the BEGIN/END markers in-place; everything
|
|
else in the file is left untouched.
|
|
"""
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
HIDE_MARKER = "cheatsheet: hide"
|
|
HEADER_SCAN_LINES = 20
|
|
|
|
|
|
def is_hidden(path):
|
|
try:
|
|
with path.open(encoding="utf-8", errors="replace") as f:
|
|
for _ in range(HEADER_SCAN_LINES):
|
|
line = f.readline()
|
|
if not line:
|
|
break
|
|
if HIDE_MARKER in line:
|
|
return True
|
|
except OSError:
|
|
pass
|
|
return False
|
|
|
|
BEGIN_MARKER = "<!-- BEGIN SCRIPT INDEX (generated by list_bin_commands) -->"
|
|
END_MARKER = "<!-- END SCRIPT INDEX -->"
|
|
|
|
# Files that aren't really commands: docs, backups, empty placeholders,
|
|
# data files consumed by another script, the cheat sheet doc itself, and
|
|
# this generator's own sibling infra (already covered in its own section
|
|
# above in cheatsheet.md).
|
|
EXCLUDE = {
|
|
"cheatsheet.md", "cheatsheet_admin.md",
|
|
"jungfrau_console_nox~", "mosaic01.vlm",
|
|
"bs_pvs_SLAAR21-CVME-LAS6991", "create_personal_dir.md", "gopass.md",
|
|
"migrate_to_network_drive.md", "list_bin_commands", "rhel9_onboarding.md",
|
|
# old names kept as links after the .sh cleanup (commands lost .sh,
|
|
# sourced files gained it) -- same thing, listed once under the new name
|
|
"kerberos_ticket_renew.sh", "pixi_local_sync.sh",
|
|
"bpy-env", "anaconda_env", "anaconda_bernina_env", "mm_bernina",
|
|
}
|
|
|
|
# (category, description) per command. Category order below is the
|
|
# display order; commands are sorted alphabetically within each.
|
|
COMMANDS = {
|
|
# --- Cheat sheet & help ---
|
|
"cheatsheet": ("Cheat sheet & help", "Show this cheat sheet in a terminal (glow + less, full detail)"),
|
|
"cheatsheet_overlay": ("Cheat sheet & help", "Show this cheat sheet as a floating overlay panel (bound to Super+F1)"),
|
|
"cheatsheet_to_shortcuts_json": ("Cheat sheet & help", "Regenerate the overlay's JSON from this file's headings/tables"),
|
|
"check_kerberos_renewal": ("Cheat sheet & help", "Diagnose why the Kerberos ticket renewer might be renewing/prompting too often"),
|
|
"useful_commands": ("Cheat sheet & help", "Show the older, plain-text useful-commands reference"),
|
|
|
|
# --- EPICS / eco environment ---
|
|
"eco": ("EPICS / eco environment", "Start eco, the beamline's Python control environment"),
|
|
"eco-dev": ("EPICS / eco environment", "Start eco from the development checkout (symlink into the eco repo)"),
|
|
"eco-dev-pull-new-version": ("EPICS / eco environment", "git pull the shared eco checkout in /sf/bernina/code/gac-bernina/eco"),
|
|
"eco-status-server": ("EPICS / eco environment", "Start/stop/check the eco status server (namespace mode)"),
|
|
"eco-status-server-install-user-service": ("EPICS / eco environment", "Install the eco status server as a systemd --user service"),
|
|
|
|
# --- Python, conda & pixi environments ---
|
|
"anaconda_bernina_env.sh": ("Python, conda & pixi environments", "Conda init hook for the bernina anaconda env (source this)"),
|
|
"anaconda_env.sh": ("Python, conda & pixi environments", "Activate a named anaconda environment"),
|
|
"anaconda_manual": ("Python, conda & pixi environments", "Show the anaconda environment manual (rendered markdown)"),
|
|
"anaconda_testenv": ("Python, conda & pixi environments", "Activate the anaconda test environment"),
|
|
"bipython": ("Python, conda & pixi environments", "ipython from the shared bernina_default anaconda env"),
|
|
"bjupyter": ("Python, conda & pixi environments", "jupyter from the shared bernina_default anaconda env"),
|
|
"bpy": ("Python, conda & pixi environments", "Activate a named bpy (pixi) environment"),
|
|
"bpy-env.sh": ("Python, conda & pixi environments", "Activate the Bernina pixi Python env -- local per-host mirror if in sync, else shared (source this)"),
|
|
"bpy-select": ("Python, conda & pixi environments", "Interactively pick/switch the active bpy pixi environment"),
|
|
"bpython": ("Python, conda & pixi environments", "python from the shared bernina_default anaconda env"),
|
|
"micromamba": ("Python, conda & pixi environments", "micromamba package manager"),
|
|
"mm_bernina.sh": ("Python, conda & pixi environments", "Micromamba shell-init hook for bernina (source this)"),
|
|
"mm_bernina_env": ("Python, conda & pixi environments", "Activate a named micromamba environment"),
|
|
"pixi": ("Python, conda & pixi environments", "pixi package manager"),
|
|
"pixi_local_sync": ("Python, conda & pixi environments", "Keep the local per-host pixi env mirror in sync (runs in background at login)"),
|
|
"pixi_rebuild_local": ("Python, conda & pixi environments", "Rebuild the bernina pixi env in an isolated local sandbox, for testing"),
|
|
"create-python-install": ("Python, conda & pixi environments", "Install the bernina pixi env into a pgroup work folder and show it in JupyterHub"),
|
|
|
|
# --- Jupyter & notebooks ---
|
|
"bernina-jupyter": ("Jupyter & notebooks", "Start JupyterLab in the bernina-scripts config dir (bpy env)"),
|
|
"ipylab": ("Jupyter & notebooks", "Start an ipylab session in a chosen anaconda environment"),
|
|
"jupyterlab-on-ra": ("Jupyter & notebooks", "Open JupyterLab on a remote-access host, for a given user"),
|
|
"jupyter-notebook-on-ra": ("Jupyter & notebooks", "Open a Jupyter Notebook on a remote-access host, for a given user"),
|
|
|
|
# --- Browsers ---
|
|
"chrome": ("Browsers", "Open Chrome with a per-host profile"),
|
|
"chrome_as": ("Browsers", "Open another user's Chrome profile from your own desktop"),
|
|
"chromeAs": ("Browsers", "Open another user's Chrome profile from your own desktop (older variant)"),
|
|
"firefox": ("Browsers", "Open Firefox with a per-host profile"),
|
|
"firefox_as": ("Browsers", "Open another user's default Firefox profile from your own desktop"),
|
|
"firefox-create-hostprofile": ("Browsers", "Create a fresh per-host Firefox profile"),
|
|
"firefox_default": ("Browsers", "Default Firefox profile launcher (symlink to firefox_shared_profile)"),
|
|
"firefox-hostprofile": ("Browsers", "Open Firefox with the per-host profile (alt entrypoint)"),
|
|
"firefox_shared_profile": ("Browsers", "Open Firefox using a shared/synced profile"),
|
|
"google-chrome": ("Browsers", "Open Chrome with a per-host profile (alt entrypoint)"),
|
|
|
|
# --- Displays & control panels ---
|
|
"alarms_caqtdm": ("Displays & control panels", "Open the EPICS alarms caQtDM panel"),
|
|
"Aramis": ("Displays & control panels", "Open the Aramis overview display (mydm/medm)"),
|
|
"aramis-home": ("Displays & control panels", "Open the Aramis photonics overview caQtDM panel"),
|
|
"bernina-home": ("Displays & control panels", "Open the esbMaster caQtDM panel"),
|
|
"cams_sf": ("Displays & control panels", "Open the SwissFEL cameras caQtDM panel"),
|
|
"diffractometer-config": ("Displays & control panels", "Open the diffractometer motor IOC configuration tool"),
|
|
"motion-home": ("Displays & control panels", "Open the ZamofingMaster motion caQtDM panel"),
|
|
"screen_panel": ("Displays & control panels", "Open the SF controls screen panel"),
|
|
"screenpanel": ("Displays & control panels", "Open the screen panel (uses screenpanel-sf if available)"),
|
|
"sf_archiver_panel": ("Displays & control panels", "Open the SwissFEL archiver viewer panel"),
|
|
"strip_chart": ("Displays & control panels", "Open the PShell strip chart viewer"),
|
|
"swissfelStatus": ("Displays & control panels", "Open the SwissFEL OP overview caQtDM panel"),
|
|
"thc_angles": ("Displays & control panels", "Open the THC angles bokeh app in Firefox"),
|
|
"thc_stripchart": ("Displays & control panels", "Open the THC temperature strip chart"),
|
|
"timetool_online_plot": ("Displays & control panels", "Open the timetool online plot (voila notebook)"),
|
|
|
|
# --- Cameras ---
|
|
"bernina-cam-mobile1": ("Cameras", "View the mobile1 camera feed (VLC)"),
|
|
"bernina-cam-n": ("Cameras", "View the north camera feed (VLC)"),
|
|
"bernina-cam-s": ("Cameras", "View the south camera feed (VLC)"),
|
|
"bernina-cams-fix": ("Cameras", "Launch the fixed multi-camera mosaic view (VLC)"),
|
|
"bernina-cam-w": ("Cameras", "View the west camera feed (VLC)"),
|
|
"bernina-cam-wideangle1": ("Cameras", "View wide-angle camera 1 feed (VLC)"),
|
|
"bernina-cam-wideangle2": ("Cameras", "View wide-angle camera 2 feed (VLC)"),
|
|
"bernina-cam-wideangle3": ("Cameras", "View wide-angle camera 3 feed (VLC)"),
|
|
"bernina-cam-wideangle4": ("Cameras", "View wide-angle camera 4 feed (VLC)"),
|
|
"monitoring_cameras": ("Cameras", "Open the monitoring cameras panel (pshell-based)"),
|
|
"record_camera": ("Cameras", "Record a camera feed to file"),
|
|
|
|
# --- FEL & laser control ---
|
|
"feloff": ("FEL & laser control", "Stop/block the FEL beam and announce it"),
|
|
"felon": ("FEL & laser control", "Open/enable the FEL beam and announce it"),
|
|
"laseroff": ("FEL & laser control", "Block the laser shutter and announce it"),
|
|
"laseron": ("FEL & laser control", "Open the laser shutter and announce it"),
|
|
|
|
# --- pshell & robot control ---
|
|
"pshell_be": ("pshell & robot control", "Start the PShell Workbench for the main Bernina beamline environment"),
|
|
"pshell_correlation": ("pshell & robot control", "Open the correlation panel"),
|
|
"pshell_default": ("pshell & robot control", "Default pshell entrypoint (symlink to pshell_be)"),
|
|
"robot_server": ("pshell & robot control", "Start the PShell Workbench for the Bernina robot -- needs a new binary, /opt/gfa/pshell is gone (see chat history: /sf/controls/bin/pshell -wb is the likely replacement, blocked on a dataFormat=null config issue)"),
|
|
|
|
# --- Desktop & window management ---
|
|
"code": ("Desktop & window management", "Launch VS Code"),
|
|
"darkscreens": ("Desktop & window management", "Blank/turn off the screens (xset dpms)"),
|
|
"flameshot": ("Desktop & window management", "Take a screenshot"),
|
|
"gnome-terminal_as": ("Desktop & window management", "Open a GNOME Terminal as another user"),
|
|
"launcher": ("Desktop & window management", "Main Bernina application launcher"),
|
|
"launcher-editor": ("Desktop & window management", "Edit the Bernina launcher in your own copy (~/launcher_config), preview it (F5) and publish it live; --live views the deployed one read-only"),
|
|
"launcher_lca": ("Desktop & window management", "Open the launcher on the machine/LCA network, for a given user"),
|
|
"launcher_legacy": ("Desktop & window management", "Legacy application launcher (python 3.5 environment)"),
|
|
"rdpWater": ("Desktop & window management", "RDP into the WaterLab Windows machine"),
|
|
"remoteDesktop": ("Desktop & window management", "RDP into an arbitrary PSI Windows host"),
|
|
"sf-launcher": ("Desktop & window management", "Open the launcher on the sf network, for a given user"),
|
|
"termAs": ("Desktop & window management", "Open a terminal as another user"),
|
|
"terminalAs": ("Desktop & window management", "Open a terminal as another user (alt entrypoint)"),
|
|
"window_arrange.py": ("Desktop & window management", "Arrange/tile windows helper script"),
|
|
"winterm": ("Desktop & window management", "Open a Windows-side terminal session"),
|
|
|
|
# --- Applications & viewers ---
|
|
"hdfview": ("Applications & viewers", "Open HDFView (HDF5 file viewer)"),
|
|
"jungfrau_console": ("Applications & viewers", "Open the Jungfrau detector console"),
|
|
"ncdu": ("Applications & viewers", "Disk usage analyzer"),
|
|
"paraview": ("Applications & viewers", "Open ParaView"),
|
|
"pycharm": ("Applications & viewers", "Open PyCharm Community"),
|
|
"pylote": ("Applications & viewers", "Open Pylote"),
|
|
"vesta": ("Applications & viewers", "Open VESTA (crystal structure viewer)"),
|
|
|
|
# --- Login session & keybinding setup ---
|
|
"create-links-in-home": ("Login session & keybinding setup", "Force-apply PSI's Partially Shared Home symlinks now"),
|
|
"refresh_home_links": ("Login session & keybinding setup", "Like create-links-in-home, but first clears local files that block the links (shows a diff, then delete or copy back)"),
|
|
"setup-network-drives": ("Login session & keybinding setup", "Mount ~/network_drives (PSI home etc.) now, running kinit first if needed -- run this first when gopass/shared Firefox profile/home links fail (link to PSI's /usr/local/bin/setup-network-drives)"),
|
|
"gogh": ("Login session & keybinding setup", "Gogh terminal color-scheme installer (legacy -- upstream download is broken, see install_terminal_profiles)"),
|
|
"install_terminal_profiles": ("Login session & keybinding setup", "Install Solarized/Spacedust GNOME Terminal profiles, set Spacedust default (runs at login)"),
|
|
"kerberos_ticket_renew": ("Login session & keybinding setup", "Keep the Kerberos ticket cache alive for the login session (runs in background)"),
|
|
"migrate_to_network_drive": ("Login session & keybinding setup", "Move a local dotfile onto PSI's Partially Shared Home and register it for sync"),
|
|
"set_gnome_keyboard_shortcut": ("Login session & keybinding setup", "Register one GNOME custom keyboard shortcut (used by startup_bernina_terminal)"),
|
|
"startup_bernina_terminal": ("Login session & keybinding setup", "Full per-login setup for a Bernina console -- runs automatically at login"),
|
|
"sync_personal_config": ("Login session & keybinding setup", "Seed .ipython/.condarc onto PSI's Partially Shared Home for cross-host sync"),
|
|
|
|
# --- gopass secrets & git ---
|
|
"git-push": ("gopass secrets & git", "git push, forcing the origin remote to SSH first"),
|
|
"gopass": ("gopass secrets & git", "Bernina gopass secrets store CLI"),
|
|
"gopass_bernina_service_setup": ("gopass secrets & git", "One-shot setup: give a service account (gac-bernina) access to the gopass store"),
|
|
"gopass_bernina_setup": ("gopass secrets & git", "One-shot onboarding for a human PSI account joining the gopass store"),
|
|
"gopass.py": ("gopass secrets & git", "Minimal Python interface to the Bernina gopass store"),
|
|
|
|
# --- Personal dirs & data ---
|
|
"create_data_symlinks": ("Personal dirs & data", "(Re)create the ~/bernina_data convenience symlink"),
|
|
"create_personal_dir": ("Personal dirs & data", "Create/verify a personal working directory under /sf/bernina/config/personal/ (your username)"),
|
|
"name2pgroups": ("Personal dirs & data", "Look up p-group(s) for a given user name"),
|
|
"pgroup2name": ("Personal dirs & data", "Look up the user name for a given p-group"),
|
|
"quotas-bernina": ("Personal dirs & data", "Show Bernina's storage quota usage"),
|
|
|
|
# --- Beamline status & monitoring ---
|
|
"chiller_flowrate": ("Beamline status & monitoring", "Query the JF16 chiller flowrate over telnet"),
|
|
"feedbacks_bernina": ("Beamline status & monitoring", "Open the undulator/feedbacks control tool, for a given user"),
|
|
"get_bernina_PVs_alive": ("Beamline status & monitoring", "Check whether a set of Bernina PVs are alive"),
|
|
"go2daq": ("Beamline status & monitoring", "SSH to the SwissFEL DAQ host (sf-daq-3)"),
|
|
"ioc-control-bernina": ("Beamline status & monitoring", "Open the IOC process monitor (PBMonitor)"),
|
|
"ioc-finder": ("Beamline status & monitoring", "Find/locate an EPICS IOC"),
|
|
"is_control": ("Beamline status & monitoring", "Check whether a given hostname is a primary controls terminal"),
|
|
"phonebook": ("Beamline status & monitoring", "Show the Bernina phone list"),
|
|
"remaining-slurm-job-time": ("Beamline status & monitoring", "Show remaining time for a SLURM job"),
|
|
"restart_service": ("Beamline status & monitoring", "Restart a systemd service as the dbe account (sudo)"),
|
|
"run-and-position.py": ("Beamline status & monitoring", "Run-and-position helper script"),
|
|
"saythis": ("Beamline status & monitoring", "Speak given text aloud"),
|
|
"set_bs_pvs": ("Beamline status & monitoring", "Configure the SLAAR21 beam-synchronous PVs"),
|
|
"slurm_resources": ("Beamline status & monitoring", "Show SLURM partition resource usage"),
|
|
"undulator-pointing": ("Beamline status & monitoring", "Open the undulator pointing control tool, for a given user"),
|
|
|
|
# --- Desktop entries ---
|
|
"bernifox.desktop": ("Desktop entries (.desktop files)", "Bernina-branded Firefox launcher (per-host profile), set as default browser"),
|
|
"Bernina.desktop": ("Desktop entries (.desktop files)", "Bernina launcher desktop icon (runs launcher)"),
|
|
"kerberos-ticket-renew.desktop": ("Desktop entries (.desktop files)", "Autostart entry for the Kerberos ticket renewer"),
|
|
}
|
|
|
|
CATEGORY_ORDER = [
|
|
"Cheat sheet & help",
|
|
"EPICS / eco environment",
|
|
"Python, conda & pixi environments",
|
|
"Jupyter & notebooks",
|
|
"Browsers",
|
|
"Displays & control panels",
|
|
"Cameras",
|
|
"FEL & laser control",
|
|
"pshell & robot control",
|
|
"Desktop & window management",
|
|
"Applications & viewers",
|
|
"Login session & keybinding setup",
|
|
"gopass secrets & git",
|
|
"Personal dirs & data",
|
|
"Beamline status & monitoring",
|
|
"Desktop entries (.desktop files)",
|
|
"Uncategorized (new, needs review)",
|
|
]
|
|
|
|
|
|
def build_section(bin_dir):
|
|
present = {p.name for p in bin_dir.iterdir() if p.is_file() and not p.name.startswith(".")}
|
|
by_category = {cat: [] for cat in CATEGORY_ORDER}
|
|
|
|
for name in sorted(present):
|
|
if name in EXCLUDE:
|
|
continue
|
|
if is_hidden(bin_dir / name):
|
|
continue
|
|
if name in COMMANDS:
|
|
cat, desc = COMMANDS[name]
|
|
else:
|
|
cat, desc = "Uncategorized (new, needs review)", "(no description yet -- add this to list_bin_commands)"
|
|
by_category[cat].append((name, desc))
|
|
|
|
lines = ["## Scripts in this directory", ""]
|
|
lines.append(
|
|
"Every script in `/sf/bernina/bin`, grouped by topic. Generated by "
|
|
"`list_bin_commands` from the actual directory contents -- re-run it "
|
|
"after adding/removing scripts to keep this in sync."
|
|
)
|
|
lines.append("")
|
|
for cat in CATEGORY_ORDER:
|
|
entries = by_category[cat]
|
|
if not entries:
|
|
continue
|
|
lines.append(f"### {cat}")
|
|
lines.append("")
|
|
lines.append("| Command | Description |")
|
|
lines.append("|---|---|")
|
|
for name, desc in entries:
|
|
lines.append(f"| `{name}` | {desc} |")
|
|
lines.append("")
|
|
return "\n".join(lines).rstrip() + "\n"
|
|
|
|
|
|
def main():
|
|
bin_dir = Path(sys.argv[1]) if len(sys.argv) > 1 else Path("/sf/bernina/bin")
|
|
md_path = Path(sys.argv[2]) if len(sys.argv) > 2 else bin_dir / "cheatsheet.md"
|
|
|
|
section = build_section(bin_dir)
|
|
text = md_path.read_text(encoding="utf-8")
|
|
|
|
if BEGIN_MARKER in text and END_MARKER in text:
|
|
before = text.split(BEGIN_MARKER)[0]
|
|
after = text.split(END_MARKER)[1]
|
|
new_text = f"{before}{BEGIN_MARKER}\n\n{section}\n{END_MARKER}{after}"
|
|
else:
|
|
sep = "\n" if text.endswith("\n") else "\n\n"
|
|
new_text = f"{text}{sep}\n{BEGIN_MARKER}\n\n{section}\n{END_MARKER}\n"
|
|
|
|
md_path.write_text(new_text, encoding="utf-8")
|
|
|
|
present = {p.name for p in bin_dir.iterdir() if p.is_file() and not p.name.startswith(".")}
|
|
missing = sorted(
|
|
n for n in present
|
|
if n not in COMMANDS and n not in EXCLUDE and not is_hidden(bin_dir / n)
|
|
)
|
|
if missing:
|
|
print("Uncategorized (added to the doc under 'needs review', but worth "
|
|
"adding real descriptions to list_bin_commands):", file=sys.stderr)
|
|
for n in missing:
|
|
print(f" {n}", file=sys.stderr)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|