Files
operation-bin/list_bin_commands
T

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()