From f677ec82cf81e99e75a0778b09f87e689dca4bb8 Mon Sep 17 00:00:00 2001 From: lemke_h Date: Sat, 5 Sep 2026 13:08:27 +0200 Subject: [PATCH] additionas --- .vscode/settings.json | 29 + ...cheatsheet_to_shortcuts_jsoncpython-39.pyc | Bin 0 -> 3847 bytes __pycache__/gopass.cpython-312.pyc | Bin 10158 -> 10158 bytes __pycache__/list_bin_commandscpython-39.pyc | Bin 0 -> 16453 bytes cheatsheet.md | 232 ++++ cheatsheet_to_shortcuts_json | 5 +- create_personal_dir.md | 43 +- delme_x | 3 + delme_y | 3 + delme_z | 3 + eco-status-server | 183 +++ eco-status-server-install-user-service | 122 ++ flameshot | 2 +- gopass.md | 1043 +++++------------ gopass_bernina_service_setup.sh | 195 ++- gopass_bernina_setup.sh | 285 ++++- install_gtile | 22 + ioc-finder | 4 + list_bin_commands | 331 ++++++ migrate_to_network_drive.md | 427 ++----- startup_bernina_terminal | 40 +- 21 files changed, 1746 insertions(+), 1226 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 __pycache__/cheatsheet_to_shortcuts_jsoncpython-39.pyc create mode 100644 __pycache__/list_bin_commandscpython-39.pyc create mode 100644 delme_x create mode 100644 delme_y create mode 100644 delme_z create mode 100755 eco-status-server create mode 100755 eco-status-server-install-user-service create mode 100755 ioc-finder create mode 100755 list_bin_commands diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..254ac5f --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,29 @@ +{ + "workbench.colorCustomizations": { + "activityBar.activeBackground": "#2f7c47", + "activityBar.background": "#2f7c47", + "activityBar.foreground": "#e7e7e7", + "activityBar.inactiveForeground": "#e7e7e799", + "activityBarBadge.background": "#422c74", + "activityBarBadge.foreground": "#e7e7e7", + "activityBarTop.activeBackground": "#2f7c47", + "activityBarTop.background": "#2f7c47", + "activityBarTop.foreground": "#e7e7e7", + "activityBarTop.inactiveForeground": "#e7e7e799", + "commandCenter.border": "#e7e7e799", + "commandCenter.foreground": "#e7e7e7", + "sash.hoverBorder": "#2f7c47", + "statusBar.background": "#215732", + "statusBar.debuggingBackground": "#215732", + "statusBar.debuggingForeground": "#e7e7e7", + "statusBar.foreground": "#e7e7e7", + "statusBarItem.hoverBackground": "#2f7c47", + "statusBarItem.remoteBackground": "#1b122e", + "statusBarItem.remoteForeground": "#e7e7e7", + "titleBar.activeBackground": "#215732", + "titleBar.activeForeground": "#e7e7e7", + "titleBar.inactiveBackground": "#21573299", + "titleBar.inactiveForeground": "#e7e7e799" + }, + "peacock.remoteColor": "#215732" +} \ No newline at end of file diff --git a/__pycache__/cheatsheet_to_shortcuts_jsoncpython-39.pyc b/__pycache__/cheatsheet_to_shortcuts_jsoncpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..92482fe5313ccfadc7b0e69421126de1e01c07af GIT binary patch literal 3847 zcmZ`+O>7*u6&}vt&W={HV-&F!D9%*Qk5-m8c3`xHAt*te6lfbLMdhOCM$URPq@=NC zXVxUwvevUe0j0+vXi)T$1dxuo_LyT&Ircoq6eu7dw*o!*koJ4r)k<>QU2sS~@^RjK z{N6`SJUty+`2B78*9ZK9W&M*nXMYYl@1Uq1Jh;`iIOBH4+RW5;+cve+c7%QCa_5M( zJ??VvW2^0RpVv^=c);tZ10M1z)OFtA)2Kr}!!Mwo5)FQF*OpvN9azlb_PW)4A-3@1 zJ4OCTNF60RBGzh02;J)PD9v>d=^YXMYW;(?XseLjSeyR*`c5Hr(${J}+RlrvScXoP zh2pUmxk`&XTA0juXLmmntvKzfR-Cj3aWfK+dm_<_7K~EV_tnX2%UfwC?uSZfo#xxO zqd3Y^rK4gidNQBKT`_+h?C7F1S)ubl!R(bZ@Ab*|)o7*YYig5xH4Hz_rqe+BX(Op1c;yUdI zIU8qt@xCJ6a4{FqR(cV}iUc7*OvbTUic|q#=dWFxhh+&_V2qB_oMu5z1O3hMm_mI1-l3IK8Jz}b;ZD)*Ar@p}Gulr@##L10XH4q}Ia+Zk$_pLRSk~{t(Pdq13#?~` z=O@0j!egYIwKnkYmm7;4iwUFd_(yFI8> zi!mf6Mja%VzgAfTd!u~H%7L??u0Lf_)2qTe@4f$iXZ`MPfBBorfzOqvH1Jy0r08XF zA}U*oCaYYHbo$DYQ+Vgoxzo)2A#5uz$wZuACJTK$EOwDKn4CuIoGT6lE{btw4GoxX z!39<_a_NZ4t0g#xTYs`=M^bPOq_-foxHFP!2khgP4DQ<8qf;A6OGZM0lg|*A%;kRh}GB7|2K*?c)1r#B|5U$YprBO5@%`^jgxszPu7g8(y`Qv zFjfU3=cXm=G~}sXmg=Tcxs<4tm*%;UZ7%`Gsb?*yPhR#iRMiX~mL0GVJ3s~dFbl~! z_s>D<3P!c1*&$+0pgLg1i`*`)M~F_-SsT3g(S};wDBu2YL)|Wy?_Ea|f>)oiWwLLx zD6+B5Am?NU@gf*JYh)u;9VK9CtP8n-35vGBVv=k=XB(ky0{5rroM2?z!hTyrhqIxp zftUj}^h&q%b}jjzl9etH<7=Bzq4a@`T3M5KwOiH>Jvyr}+bMBs{nQ$|59dn%&_A*c zYup98z9ac%%dGUB4rndv&jM5OJ6$h*=mwB^qzfzRVACZstm+f0Y&NRUK*;cX`odUBZ_)anyme*4G244mlANK&{47w*J8^?s4;M(aJC1<_Y_ z<=~)ePo25d9Fv+^uN^4yurF|UpG#s|i8#*i>n$`4zb+Bcb0n4oeN&Amn?-<-vWz>_ z`qJU<7xsvM!IOl3gZ4gALIg`Ad6i@>deD}BWy_1GDhF7421W7`1~yR4ndI9x3%(y* zdj`|bBWUxArVZc5_ri7!1mBHw{sSk;EaT+sXc#B2KHo`hPuy}&-8%I{-oygWb3mMT z0703Dq(b8LQXOaMK*(1~;L$BlnpAF1RsJZ^B;6OC8ZNsJ61xziKe8@ zk*|=@>m%!B+co=qJ|E?4nDA>9MbwRKbSNJw6R)7Q*;~jz*LjH{#^3wFxb}@!LJB88 zg4`qr|A7vjI}dQ;AOjtFOM|vF#0C$3N|E`3^5F2~ih^7dq^MbpKFP$rP!7G1F|`35X{ z29=fD#icpNfT9Gmz{GR7X}pMf5$t=3NkU2$biny(-sv=_t6HbSi=@+$?_p$g5UtW2 zN~<1uPibV1C366cj-u-%Bj8QeqeBjhoMO|o%rz`n=@z`7iB)1AMOPYk#+(&)=mdT+ O6}Z7G!L^_fu>S!KW4s^$ literal 0 HcmV?d00001 diff --git a/__pycache__/gopass.cpython-312.pyc b/__pycache__/gopass.cpython-312.pyc index d4ebb23694f2b4729209f1706cf9bc6c049605fd..56d17300c40efa4c2b9e9a561dbd3820fa749c53 100644 GIT binary patch delta 20 acmZ4Izs{fgG%qg~0}wphGI1mKVs!vT83v{R delta 20 acmZ4Izs{fgG%qg~0}$8+_iW@|tPTJ^A_bTL diff --git a/__pycache__/list_bin_commandscpython-39.pyc b/__pycache__/list_bin_commandscpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..092c7652ad34965f92c63972439a093a314bdb50 GIT binary patch literal 16453 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�^nR{B<#I%>rs66C&aQJJl@n5&mGCH>&QU&&ZE*~PU1j#U z*j8jS>hbYS_bX7;X0D^qk0a{1=0L%-O{Lvb);)&5IsEyk(B`7O1NwX9M1QnW-haF0mRuum zO)i#~#-CfQ z=`pYq<^*E>$+_9v3vb?B`C;Ye3U0;pGd`k?rs!uEelWNA?)(B@I_T%rrImgL0c=!T z_R|!_>SqvygE2Rq>t{hOD%$U7qZ?oPc(n^-hVO25y&|^JyMPZpP5am%r98af&qki+ zR({#Di8aQ+GX6yjP9zT`CsTAUB83|fDSS_+a>+yZOD9ubr$_T>;b+{Q$n*b+lLP9XpOF_W>Ymk-Sz0|{80AV4rN9f)Q{etMC z*&wzcXo3vHOs<#2=U9;aJi%fs3!!)peaFPuqonumV$4a297JFW@2UUT)=^^Yi!q3= z#Jzk^Vv@pLO59HUDDiRP;dmsjv%$pA67?kASp6v1gV_5J;(8<3%i;dz)d06xK?mGq z{RCrsIS9q08)InQ*Bc{2I(YYK^8S<{p|^keD-bgg?%i^dz9RAG(cKo01j=;M%g{6T zP3806tN67|^!yRx(wio*FVRPdK1gWE50XRPIec8fq*4GqNin1EDg#Mm`7`){1e_uX z7$nw`9@JZqr0*d0aiW(bmFA=E4(=k|hb#w0lFQR0-esEnb^4$?JW5g~oiCH#8`R2Z zvZNflOjki3w7o0e2cAAr3A~+r`s;8jwyIy~Zqeb8B#6vJ;XmBBi-NPKhSmBhWUz7V z0e2;+ijYMQBoYa$ft1}vtX`tKV5Ev5O|1^XHy4~T51xXkUwAN(iys;^>qe4y!X(%pQ}`8e5EqI zub-_{K>bPu%DPjHf~wvk^~w_BwpEq1nn06B0*RT!yGc*C=z|%CS?4wSNi>aTAuFI# z8RG8&3*|23LPEzjuh35T^H@syD9Oc%6mT8M?ZIazH=Y7l@&A$dNwo}rfAibT5O(Uj zQ`-Rk&Tm!g*6+c%;V0E=GcJQ3gZDc<4f+k9WWS##{xkEF#D&}wx#x4w + +## Scripts in this directory + +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. + +### Cheat sheet & help + +| Command | Description | +|---|---| +| `cheatsheet` | Show this cheat sheet in a terminal (glow + less, full detail) | +| `cheatsheet_overlay` | Show this cheat sheet as a floating overlay panel (bound to Super+F1) | +| `cheatsheet_to_shortcuts_json` | Regenerate the overlay's JSON from this file's headings/tables | +| `check_kerberos_renewal` | Diagnose why the Kerberos ticket renewer might be renewing/prompting too often | +| `useful_commands` | Show the older, plain-text useful-commands reference | + +### EPICS / eco environment + +| Command | Description | +|---|---| +| `eco` | Start eco, the beamline's Python control environment | +| `eco-dev` | Start eco from the development checkout (symlink into the eco repo) | +| `eco-in` | Start eco inside a chosen environment (arg, default bpy-default) | +| `eco-notlazy` | Start eco with lazy-loading disabled (default env bernina37) | +| `eco-status-server` | Start/stop/check the eco status server (namespace mode) | +| `eco-status-server-install-user-service` | Install the eco status server as a systemd --user service | +| `eco-test` | Start eco against the sfb312 test micromamba environment | +| `eco_bams_bpms` | Start eco configured for BAM/BPM work | +| `eco_historic` | Start eco against historic/legacy config | +| `eco_mm_env` | Start eco in a chosen micromamba environment | +| `eco_new` | Start eco using bpy-default.sh directly | +| `eco_noenv` | Start eco's startup.py directly, no environment activation step | +| `eco_old` | Start eco against the older sfb312 micromamba environment | +| `eco_roman` | Start eco configured for the bernina_robot setup | +| `ecotest` | Start eco in the jungfrau_client conda environment | + +### Python, conda & pixi environments + +| Command | Description | +|---|---| +| `anaconda_bernina_env` | Conda init hook for the bernina anaconda env (source this) | +| `anaconda_env` | Activate a named anaconda environment | +| `anaconda_manual` | Show the anaconda environment manual (rendered markdown) | +| `anaconda_testenv` | Activate the anaconda test environment | +| `bpy` | Activate a named bpy (pixi) environment | +| `bpy-default.sh` | Set PATH for the default shared bpy312 pixi environment (source this) | +| `bpy-dev.sh` | Set PATH for the bpy312-dev pixi environment (source this) | +| `bpy-env` | Activate the Bernina pixi Python env -- local per-host mirror if in sync, else shared (source this) | +| `bpy-select` | Interactively pick/switch the active bpy pixi environment | +| `micromamba` | micromamba package manager | +| `mm_bernina` | Micromamba shell-init hook for bernina (source this) | +| `mm_bernina_env` | Activate a named micromamba environment | +| `pixi` | pixi package manager | +| `pixi_local_sync.sh` | Keep the local per-host pixi env mirror in sync (runs in background at login) | +| `pixi_rebuild_local.sh` | Rebuild the bernina pixi env in an isolated local sandbox, for testing | + +### Jupyter & notebooks + +| Command | Description | +|---|---| +| `bernina-jupyter` | Start JupyterLab in the bernina-scripts config dir (bpy env) | +| `ipylab` | Start an ipylab session in a chosen anaconda environment | +| `jupyter-notebook-on-ra` | Open a Jupyter Notebook on a remote-access host, for a given user | +| `jupyterlab-on-ra` | Open JupyterLab on a remote-access host, for a given user | + +### Browsers + +| Command | Description | +|---|---| +| `chrome` | Open Chrome with a per-host profile | +| `chromeAs` | Open another user's Chrome profile from your own desktop (older variant) | +| `chrome_as` | Open another user's Chrome profile from your own desktop | +| `firefox` | Open Firefox with a per-host profile | +| `firefox-create-hostprofile` | Create a fresh per-host Firefox profile | +| `firefox-hostprofile` | Open Firefox with the per-host profile (alt entrypoint) | +| `firefox_as` | Open another user's default Firefox profile from your own desktop | +| `firefox_default` | Default Firefox profile launcher (symlink to firefox_shared_profile) | +| `firefox_shared_profile` | Open Firefox using a shared/synced profile | +| `google-chrome` | Open Chrome with a per-host profile (alt entrypoint) | + +### Displays & control panels + +| Command | Description | +|---|---| +| `Aramis` | Open the Aramis overview display (mydm/medm) | +| `alarms_caqtdm` | Open the EPICS alarms caQtDM panel | +| `aramis-home` | Open the Aramis photonics overview caQtDM panel | +| `bernina-home` | Open the esbMaster caQtDM panel | +| `cams_sf` | Open the SwissFEL cameras caQtDM panel | +| `diffractometer-config` | Open the diffractometer motor IOC configuration tool | +| `motion-home` | Open the ZamofingMaster motion caQtDM panel | +| `screen_panel` | Open the SF controls screen panel | +| `screenpanel` | Open the screen panel (uses screenpanel-sf if available) | +| `sf_archiver_panel` | Open the SwissFEL archiver viewer panel | +| `strip_chart` | Open the PShell strip chart viewer | +| `swissfelStatus` | Open the SwissFEL OP overview caQtDM panel | +| `thc_angles` | Open the THC angles bokeh app in Firefox | +| `thc_stripchart` | Open the THC temperature strip chart | +| `timetool_online_plot` | Open the timetool online plot (voila notebook) | + +### Cameras + +| Command | Description | +|---|---| +| `bernina-cam-mobile1` | View the mobile1 camera feed (VLC) | +| `bernina-cam-n` | View the north camera feed (VLC) | +| `bernina-cam-s` | View the south camera feed (VLC) | +| `bernina-cam-w` | View the west camera feed (VLC) | +| `bernina-cam-wideangle1` | View wide-angle camera 1 feed (VLC) | +| `bernina-cam-wideangle2` | View wide-angle camera 2 feed (VLC) | +| `bernina-cam-wideangle3` | View wide-angle camera 3 feed (VLC) | +| `bernina-cam-wideangle4` | View wide-angle camera 4 feed (VLC) | +| `bernina-cams-fix` | Launch the fixed multi-camera mosaic view (VLC) | +| `monitoring_cameras` | Open the monitoring cameras panel (pshell-based) | +| `record_camera` | Record a camera feed to file | + +### FEL & laser control + +| Command | Description | +|---|---| +| `feloff` | Stop/block the FEL beam and announce it | +| `felon` | Open/enable the FEL beam and announce it | +| `laseroff` | Block the laser shutter and announce it | +| `laseron` | Open the laser shutter and announce it | + +### pshell & robot control + +| Command | Description | +|---|---| +| `pshell_be` | Start the PShell Workbench for the main Bernina beamline environment | +| `pshell_correlation` | Open the correlation panel | +| `pshell_default` | Default pshell entrypoint (symlink to pshell_be) | +| `robot_server` | 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 + +| Command | Description | +|---|---| +| `code` | Launch VS Code | +| `darkscreens` | Blank/turn off the screens (xset dpms) | +| `flameshot` | Take a screenshot | +| `gnome-terminal_as` | Open a GNOME Terminal as another user | +| `launcher` | Main Bernina application launcher | +| `launcher_dev` | Application launcher (dev/legacy python 3.5 environment) | +| `launcher_lca` | Open the launcher on the machine/LCA network, for a given user | +| `launcher_legacy` | Legacy application launcher (python 3.5 environment) | +| `rdpWater` | RDP into the WaterLab Windows machine | +| `remoteDesktop` | RDP into an arbitrary PSI Windows host | +| `sf-launcher` | Open the launcher on the sf network, for a given user | +| `termAs` | Open a terminal as another user | +| `terminalAs` | Open a terminal as another user (alt entrypoint) | +| `window_arrange.py` | Arrange/tile windows helper script | +| `winterm` | Open a Windows-side terminal session | + +### Applications & viewers + +| Command | Description | +|---|---| +| `hdfview` | Open HDFView (HDF5 file viewer) | +| `jungfrau_console` | Open the Jungfrau detector console | +| `ncdu` | Disk usage analyzer | +| `paraview` | Open ParaView | +| `pycharm` | Open PyCharm Community | +| `pylote` | Open Pylote | +| `vesta` | Open VESTA (crystal structure viewer) | + +### Login session & keybinding setup + +| Command | Description | +|---|---| +| `create-links-in-home` | Force-apply PSI's Partially Shared Home symlinks now | +| `gogh` | Gogh terminal color-scheme installer (legacy -- upstream download is broken, see install_terminal_profiles) | +| `install_gtile` | Install/update and enable the gTile gnome-shell extension (runs at login) | +| `install_terminal_profiles` | Install Solarized/Spacedust GNOME Terminal profiles, set Spacedust default (runs at login) | +| `kerberos_ticket_renew.sh` | Keep the Kerberos ticket cache alive for the login session (runs in background) | +| `migrate_to_network_drive.sh` | Move a local dotfile onto PSI's Partially Shared Home and register it for sync | +| `set_gnome_keyboard_shortcut` | Register one GNOME custom keyboard shortcut (used by startup_bernina_terminal) | +| `startup_bernina_terminal` | Full per-login setup for a Bernina console -- runs automatically at login | +| `sync_personal_config.sh` | Seed .ipython/.condarc onto PSI's Partially Shared Home for cross-host sync | + +### gopass secrets & git + +| Command | Description | +|---|---| +| `git-push` | git push, forcing the origin remote to SSH first | +| `gopass` | Bernina gopass secrets store CLI | +| `gopass.py` | Minimal Python interface to the Bernina gopass store | +| `gopass_bernina_service_setup.sh` | One-shot setup: give a service account (gac-bernina) access to the gopass store | +| `gopass_bernina_setup.sh` | One-shot onboarding for a human PSI account joining the gopass store | + +### Personal dirs & data + +| Command | Description | +|---|---| +| `create_data_symlinks` | (Re)create the ~/bernina_data convenience symlink | +| `create_personal_dir.sh` | Create/verify a personal working directory under /sf/bernina/config/personal/ (your username) | +| `name2pgroups` | Look up p-group(s) for a given user name | +| `pgroup2name` | Look up the user name for a given p-group | +| `quotas-bernina` | Show Bernina's storage quota usage | + +### Beamline status & monitoring + +| Command | Description | +|---|---| +| `chiller_flowrate` | Query the JF16 chiller flowrate over telnet | +| `feedbacks_bernina` | Open the undulator/feedbacks control tool, for a given user | +| `get_bernina_PVs_alive` | Check whether a set of Bernina PVs are alive | +| `go2daq` | SSH to the SwissFEL DAQ host (sf-daq-3) | +| `ioc-control-bernina` | Open the IOC process monitor (PBMonitor) | +| `ioc-finder` | Find/locate an EPICS IOC | +| `is_control` | Check whether a given hostname is a primary controls terminal | +| `phonebook` | Show the Bernina phone list | +| `remaining-slurm-job-time` | Show remaining time for a SLURM job | +| `restart_service` | Restart a systemd service as the dbe account (sudo) | +| `run-and-position.py` | Run-and-position helper script | +| `saythis` | Speak given text aloud | +| `set_bs_pvs` | Configure the SLAAR21 beam-synchronous PVs | +| `slurm_resources` | Show SLURM partition resource usage | +| `undulator-pointing` | Open the undulator pointing control tool, for a given user | + +### Desktop entries (.desktop files) + +| Command | Description | +|---|---| +| `Bernina.desktop` | Bernina launcher desktop icon (runs launcher) | +| `bernifox.desktop` | Bernina-branded Firefox launcher (per-host profile), set as default browser | +| `kerberos-ticket-renew.desktop` | Autostart entry for the Kerberos ticket renewer | + + diff --git a/cheatsheet_to_shortcuts_json b/cheatsheet_to_shortcuts_json index 451d700..f4dff96 100755 --- a/cheatsheet_to_shortcuts_json +++ b/cheatsheet_to_shortcuts_json @@ -95,8 +95,9 @@ def build_shortcuts(header, rows): if row[0] ] - # Shortcut | Action [| Runs] (everything else: app shortcuts, gTile, primary-controls) - if ncols >= 2 and header[0].lower().startswith("shortcut"): + # Shortcut | Action [| Runs] (app shortcuts, gTile, primary-controls) + # Command | Description (the bin/ script index) + if ncols >= 2 and header[0].lower().startswith(("shortcut", "command")): return [ {"key": row[0], "description": row[1]} for row in rows diff --git a/create_personal_dir.md b/create_personal_dir.md index 34138a7..bb3c0ba 100644 --- a/create_personal_dir.md +++ b/create_personal_dir.md @@ -1,11 +1,11 @@ # Personal directories on `/sf/bernina` -`/sf/bernina/config/personal/` is the Bernina beamline -convention for a per-user directory on real, git-friendly NFS storage — -as opposed to `~/network_drives`, which is SMB and can't hold a git -repository at all (see `migrate_to_network_drive.md`). It's the location -the gopass guide (`gopass.md`) uses for a personal gopass clone that's -automatically the same on every Bernina host. +`/sf/bernina/config/personal/` is the Bernina convention for a +per-user directory on real, git-friendly NFS storage — unlike +`~/network_drives` (SMB), which can't hold a git repo (see +`migrate_to_network_drive.md`). The gopass guide (`gopass.md`) uses it for +a personal store clone that's automatically the same on every Bernina +host. ## Usage @@ -14,29 +14,16 @@ create_personal_dir.sh # create/verify your own directory create_personal_dir.sh someuser # provision on someone else's behalf (needs root, or ask them to self-serve) ``` -Run it again any time — it's safe to re-run, and only changes anything -that doesn't already match. +Safe to re-run — only changes what doesn't already match. ## What it does -1. **Checks the shared parent directory (`/sf/bernina/config/personal/`) - allows self-service.** It needs to be group-writable (so anyone in - `unx-sf_bernina_bs` can create their own subdirectory) with the sticky - bit set (so nobody can rename or delete someone *else's* directory — - the same principle `/tmp` uses). If it isn't set up that way yet and - you own it, the script offers to fix it (`chmod 3775`, - `chgrp unx-sf_bernina_bs`) — this only needs doing once, ever, since +1. Makes sure the shared parent (`/sf/bernina/config/personal/`) is + group-writable with the sticky bit set — like `/tmp`: anyone in + `unx-sf_bernina_bs` can add their own subdirectory, but nobody can + touch anyone else's. Offers to fix it (`chmod 3775`) if you own it and + it isn't set up that way yet; only ever needs doing once, since `/sf/bernina` is one NFS export shared by every Bernina host. -2. **Creates (or verifies) ``'s own subdirectory**, locked to - `2700` — owner-only, nobody else (not even other team members) can - list or read into it, despite the parent being browsable. - -## Why this layout - -- Parent `rwxrwxr-x` + sticky: anyone can see who has a personal - directory and add their own; nobody can touch anyone else's entry. -- Each user's own directory `rwx------`: totally private once inside. - -This is the standard "shared drop-in directory, private subdirectories" -pattern — structurally the same idea as `/tmp`, scoped to the beamline -group instead of the whole system. +2. Creates (or verifies) ``'s own subdirectory, locked to + `2700` — owner-only, nobody else can list or read into it even though + the parent is browsable. diff --git a/delme_x b/delme_x new file mode 100644 index 0000000..7ec9b51 --- /dev/null +++ b/delme_x @@ -0,0 +1,3 @@ +{ + "value": 0 +} \ No newline at end of file diff --git a/delme_y b/delme_y new file mode 100644 index 0000000..7ec9b51 --- /dev/null +++ b/delme_y @@ -0,0 +1,3 @@ +{ + "value": 0 +} \ No newline at end of file diff --git a/delme_z b/delme_z new file mode 100644 index 0000000..7ec9b51 --- /dev/null +++ b/delme_z @@ -0,0 +1,3 @@ +{ + "value": 0 +} \ No newline at end of file diff --git a/eco-status-server b/eco-status-server new file mode 100755 index 0000000..c91e5ac --- /dev/null +++ b/eco-status-server @@ -0,0 +1,183 @@ +#!/bin/bash +# eco status server (namespace mode) - start/stop/status wrapper. +# +# Canonical copy lives in the eco repo at eco/status_server/bin/; the copy in +# /sf/bernina/bin is installed from there (see install-user-service). +# +# Runs in the FOREGROUND by default, which is what systemd's Type=simple +# wants and what the accompanying user unit calls. Use `-b` to detach for +# interactive use. +# +# Everything is overridable by environment, so the same script serves a +# personal checkout, the shared checkout and the systemd unit unchanged: +# +# ECO_STATUS_SERVER_CHECKOUT eco checkout to run (prepended to PYTHONPATH) +# ECO_STATUS_SERVER_CONFIG server config JSON +# ECO_STATUS_SERVER_PYTHON interpreter (needs eco's full dependency set) +# ECO_STATUS_SERVER_URL what `status`/`stop` talk to +# ECO_STATUS_SERVER_LOG log file used by -b +# +# A file at /sf/bernina/config/eco_status_server/env (or +# $ECO_STATUS_SERVER_ENV) is sourced first if it exists, so a site can set +# those in one place without editing this script. +set -euo pipefail + +ENV_FILE="${ECO_STATUS_SERVER_ENV:-/sf/bernina/config/eco_status_server/env}" +# shellcheck source=/dev/null +[ -r "$ENV_FILE" ] && . "$ENV_FILE" + +CHECKOUT="${ECO_STATUS_SERVER_CHECKOUT:-/sf/bernina/code/gac-bernina/eco}" +CONFIG="${ECO_STATUS_SERVER_CONFIG:-/sf/bernina/config/eco_status_server/bernina_namespace.json}" +PYTHON="${ECO_STATUS_SERVER_PYTHON:-/sf/bernina/applications/python/.pixi/envs/bpy312/bin/python}" +URL="${ECO_STATUS_SERVER_URL:-http://$(hostname -s):8091}" +LOG="${ECO_STATUS_SERVER_LOG:-$HOME/.eco/status_server_$(hostname -s).log}" + +# Matches only the running server, never this script or an ssh command line +# that mentions it (the bracket keeps the pattern from matching itself). +PGREP_PATTERN='[e]co\.status_server --mode' + +usage() { + cat < [options] + + start [-b] [-- ] run the server (foreground; -b to detach) + stop stop the running server on this host + restart stop, then start detached + status query $URL/health + wait [timeout] block until the server reports ready + logs [-f] show the detached-mode log + config print the resolved configuration + +Environment (current values): + ECO_STATUS_SERVER_CHECKOUT=$CHECKOUT + ECO_STATUS_SERVER_CONFIG=$CONFIG + ECO_STATUS_SERVER_PYTHON=$PYTHON + ECO_STATUS_SERVER_URL=$URL + ECO_STATUS_SERVER_LOG=$LOG +EOF +} + +die() { echo "$(basename "$0"): $*" >&2; exit 1; } + +check_prereqs() { + [ -d "$CHECKOUT/eco" ] || die "no eco checkout at $CHECKOUT (set ECO_STATUS_SERVER_CHECKOUT)" + # Not fatal, but almost always the explanation when half the namespace + # fails to initialize: without the gateway list, Channel Access falls + # back to the local broadcast domain. A systemd unit inherits nothing, + # so this is the first thing to check there. + if [ -z "${EPICS_CA_ADDR_LIST:-}" ]; then + echo "$(basename "$0"): warning: EPICS_CA_ADDR_LIST is not set;" >&2 + echo " most components will fail to connect. See the EnvironmentFile" >&2 + echo " written by eco-status-server-install-user-service." >&2 + fi + [ -r "$CONFIG" ] || die "no server config at $CONFIG (set ECO_STATUS_SERVER_CONFIG)" + [ -x "$PYTHON" ] || die "no interpreter at $PYTHON (set ECO_STATUS_SERVER_PYTHON)" +} + +server_pid() { pgrep -f "$PGREP_PATTERN" | head -1; } + +cmd_start() { + local background=0 + if [ "${1:-}" = "-b" ] || [ "${1:-}" = "--background" ]; then background=1; shift; fi + [ "${1:-}" = "--" ] && shift + check_prereqs + local pid; pid="$(server_pid || true)" + [ -n "$pid" ] && die "already running (pid $pid); use 'restart' or 'stop' first" + + export PYTHONPATH="$CHECKOUT${PYTHONPATH:+:$PYTHONPATH}" + if [ "$background" -eq 1 ]; then + mkdir -p "$(dirname "$LOG")" + # >"$LOG" 2>&1 & + echo "started pid $! -> $LOG" + echo "the namespace init takes minutes; watch it with: $(basename "$0") wait" + else + exec "$PYTHON" -u -m eco.status_server --mode namespace \ + --config "$CONFIG" "$@" /dev/null || { echo "stopped (was pid $pid)"; return 0; } + done + die "pid $pid did not exit after 30 s" +} + +cmd_status() { + local pid; pid="$(server_pid || true)" + if [ -n "$pid" ]; then echo "process: running, pid $pid"; else echo "process: not running on $(hostname -s)"; fi + # urllib rather than curl|python: the health formatting is a real + # program, and quoting it through a shell pipeline is how it silently + # turns into "no answer" the first time someone edits it. + # `|| true`: a server that is not up yet is a normal state for this + # command, and under `set -e` a non-zero exit here would abort the + # `wait` loop that calls it. + "$PYTHON" - "$URL" <<'PYEOF' || true +import json, sys, urllib.request + +url = sys.argv[1].rstrip("/") + "/health" +try: + with urllib.request.urlopen(url, timeout=15) as r: + h = json.load(r) +except Exception as exc: + print("health: no answer from %s (%s)" % (url, exc)) + raise SystemExit(1) + +print("state: {state} (generation {generation}, up {up:.1f} min)".format( + state=h["state"], generation=h["generation"], up=h["uptime_s"] / 60)) +print("init: {i}/{t} components, {f} failed".format( + i=h["n_initialized"], t=h["n_target_names"], f=h["n_failed"])) +print("serving: {d} status detectors, {m} monitorable".format( + d=h["n_direct_read"], m=h.get("n_monitorable"))) +print("process: {r:.0f} MB, {t} threads, {c:.0f} s cpu".format( + r=h.get("rss_mb") or 0, t=h.get("n_threads"), c=h.get("cpu_seconds") or 0)) +if h.get("failed_names"): + print("failed: " + ", ".join(h["failed_names"])) +for rec in h.get("recordings", []): + if rec.get("running"): + print("recording {id}: {u} updates, {s} stored".format( + id=rec["recording_id"], u=rec["n_updates"], s=rec["n_stored"])) +PYEOF +} + +is_ready() { + "$PYTHON" - "$URL" <<'PYEOF' >/dev/null 2>&1 +import json, sys, urllib.request +with urllib.request.urlopen(sys.argv[1].rstrip("/") + "/health", timeout=15) as r: + raise SystemExit(0 if json.load(r).get("ready") else 1) +PYEOF +} + +cmd_wait() { + # split, not `local a=.. b=$((a))`: under `set -u` bash evaluates the + # arithmetic before the first assignment is visible, and the command + # fails with "timeout: unbound variable". + local timeout="${1:-1800}" + local end=$((SECONDS + timeout)) + while [ $SECONDS -lt $end ]; do + if is_ready; then cmd_status; return 0; fi + cmd_status | sed -n '2p' + sleep 15 + done + die "not ready after ${timeout}s" +} + +case "${1:-}" in + start) shift; cmd_start "$@" ;; + stop) cmd_stop ;; + restart) cmd_stop; shift; cmd_start -b "$@" ;; + status) cmd_status ;; + wait) shift; cmd_wait "$@" ;; + logs) shift; [ "${1:-}" = "-f" ] && tail -f "$LOG" || tail -n 100 "$LOG" ;; + config) usage ;; + ""|-h|--help|help) usage ;; + *) usage; exit 2 ;; +esac diff --git a/eco-status-server-install-user-service b/eco-status-server-install-user-service new file mode 100755 index 0000000..f338cc3 --- /dev/null +++ b/eco-status-server-install-user-service @@ -0,0 +1,122 @@ +#!/bin/bash +# Install the eco status server as a *user* systemd service on this host. +# +# User-level (`systemctl --user`) rather than system-level on purpose: it +# needs no root, and the service then runs as the invoking account - which +# matters twice for this server. It writes status.json into the pgroup's +# res/ tree, so it needs that account's pgroup membership; and several +# bernina components authenticate to auxiliary machines over SSH during +# __init__, so it needs that account's SSH credentials. +# +# The unit just calls `eco-status-server start` in the foreground, so the +# interactive command and the service run exactly the same thing. +set -euo pipefail + +SELF_DIR="$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")" && pwd)" +LAUNCHER="${ECO_STATUS_SERVER_LAUNCHER:-$SELF_DIR/eco-status-server}" +UNIT_DIR="$HOME/.config/systemd/user" +UNIT="$UNIT_DIR/eco-status-server.service" +ENV_FILE="${ECO_STATUS_SERVER_ENV:-$HOME/.config/eco-status-server.env}" + +usage() { + cat <&2; usage; exit 2 ;; + esac +done + +[ -x "$LAUNCHER" ] || { echo "launcher not executable: $LAUNCHER" >&2; exit 1; } + +mkdir -p "$UNIT_DIR" "$(dirname "$ENV_FILE")" + +if [ ! -e "$ENV_FILE" ]; then + { + echo "# Environment for the eco status server (systemd EnvironmentFile" + echo "# syntax: KEY=value, no export, no shell expansion). Edit to taste." + echo "ECO_STATUS_SERVER_CHECKOUT=${ECO_STATUS_SERVER_CHECKOUT:-/sf/bernina/code/gac-bernina/eco}" + echo "ECO_STATUS_SERVER_CONFIG=${ECO_STATUS_SERVER_CONFIG:-/sf/bernina/config/eco_status_server/bernina_namespace.json}" + echo "ECO_STATUS_SERVER_PYTHON=${ECO_STATUS_SERVER_PYTHON:-/sf/bernina/applications/python/.pixi/envs/bpy312/bin/python}" + echo + echo "# Channel Access settings, captured from the shell that ran the" + echo "# installer. A systemd user service inherits almost nothing, and" + echo "# without EPICS_CA_ADDR_LIST the server talks to the local" + echo "# broadcast domain instead of the beamline gateway: measured on" + echo "# saresb-cons-04, that is the difference between 77 of 87" + echo "# components initializing and 49 of 87." + env | grep '^EPICS_' | sort || true + } > "$ENV_FILE" + echo "wrote template $ENV_FILE - check it before starting the service" + if ! grep -q '^EPICS_CA_ADDR_LIST=' "$ENV_FILE"; then + echo + echo "WARNING: EPICS_CA_ADDR_LIST was not set in this shell, so it is" >&2 + echo " not in $ENV_FILE either. Re-run this from a shell where" >&2 + echo " Channel Access works, or add it by hand - otherwise the" >&2 + echo " service will start but most components will not connect." >&2 + fi +else + echo "keeping existing $ENV_FILE" +fi + +if [ -e "$UNIT" ] && [ "$FORCE" -eq 0 ]; then + echo "$UNIT already exists; re-run with --force to overwrite" >&2 + exit 1 +fi + +cat > "$UNIT" < dumps every thread's stack into the journal - + # the way to see which component a stuck initialization is sitting in. +EOF diff --git a/flameshot b/flameshot index bbd61cd..43b24bd 100755 --- a/flameshot +++ b/flameshot @@ -11,4 +11,4 @@ FLAMESHOT_DIR="/sf/bernina/applications/Flameshot/Flameshot-12.1.0-extracted" LC_ALL=C \ QT_QPA_PLATFORM_PLUGIN_PATH=/usr/lib64/qt5/plugins/platforms \ LD_LIBRARY_PATH=/usr/lib64 \ -"$FLAMESHOT_DIR/usr/bin/flameshot" "$@" +"$FLAMESHOT_DIR/usr/bin/flameshot" "$@" "&" diff --git a/gopass.md b/gopass.md index 727677b..867b4a2 100644 --- a/gopass.md +++ b/gopass.md @@ -1,830 +1,317 @@ -# Team Secrets with gopass + gitea.psi.ch - -A beginner's guide to sharing passwords and secrets across a team using -[gopass](https://www.gopass.pw/) with a private git repository on -[gitea.psi.ch](https://gitea.psi.ch) as the sync backend. - -Written for the SwissFEL Bernina beamline team. Replace ``, -`` and email addresses below with your actual PSI account names. - ---- - -## 1. How this works (read this first) - -gopass is a command-line password manager. Understanding these four ideas -makes everything below make sense: - -1. **The store is just a folder of encrypted files.** Every secret is a - small file, encrypted with [GPG](https://gnupg.org/), e.g. - `bernina/laser/status-page.gpg`. There is no server, no database — the - "backend" is a directory tree. -2. **git is only for syncing and history**, not for security. gitea.psi.ch - hosts that folder as a git repo so everyone's local copy can push/pull - changes, and you get a full audit trail (who changed what secret, when). - git itself never sees plaintext — it only ever stores `.gpg` files. -3. **GPG is what actually controls who can read a secret.** Each store has - a `.gpg-id` file listing the GPG key IDs ("recipients") a secret is - encrypted for. When you `insert` a secret, gopass encrypts it once per - recipient. Only someone holding the matching *private* key can decrypt - it — access to the git repo alone is not enough. -4. **Repo access and decrypt access are two separate permissions.** - Gitea's collaborator list controls who can clone/push the repository - (i.e. see ciphertext and history). The `.gpg-id` recipient list controls - who can turn that ciphertext back into plaintext. For a small trusted - team these are usually kept in sync (same people in both), but it's - worth knowing they're independent — see [§8 Removing someone](#8-removing-someone--rotating-secrets). - -``` - you gitea.psi.ch teammate - ┌────────────┐ git push ┌──────────────┐ git pull ┌────────────┐ - │ gopass show│──────────────▶│ *.gpg files │──────────────▶│ gopass show│ - │ (needs your│ (ciphertext │ in git repo, │ (ciphertext │ (needs their - │ GPG private│ only) │ full history │ only) │ GPG private - │ key) │◀──────────────│ │◀──────────────│ key) │ - └────────────┘ git pull └──────────────┘ git push └────────────┘ -``` - ---- - -## 2. Prerequisites - -- A PSI account and access to . -- `gopass` and `gpg` installed. On this machine both are already available: - - ```bash - gopass version # gopass 1.16.1 - gpg --version # gpg (GnuPG) 2.3.3 - ``` - - If they're missing elsewhere: `gpg` comes with most Linux distros, and - gopass releases are at - (or use whatever is symlinked in `/sf/bernina/bin/gopass` on PSI Linux). - ---- - -## 3. One-time setup — the first person (admin) - -This is done **once** by whoever creates the shared store. Everyone after -that follows [§5](#5-onboarding-additional-users). - -### 3.1 Create the gitea repository - -1. Log in to . -2. Create a new **private** repository, e.g. under your group/org: - `/bernina-secrets`. - - Leave it empty — do **not** initialize with a README/license. gopass - needs to push the first commit itself. -3. Add your teammates as **Collaborators** (or add the team to the repo) - with at least **Write** access, under Settings → Collaborators. - Repo access lets them pull/push the encrypted files; it does **not** - by itself let them read any secret (see §1). - -### 3.2 Set up SSH access to gitea - -Every remote URL in this guide uses SSH -(`git@gitea.psi.ch:/bernina-secrets.git`) rather than HTTPS. This -matters more here than for typical git use: `core.autosync` (§3.4) makes -gopass run `git push`/`git pull` on nearly every command, invisibly. Over -HTTPS that means a personal access token and usually a credential helper -to avoid being prompted (or failing silently) on every sync; over SSH, an -agent handles it with no prompts at all. - -If you don't already have an SSH key: - -```bash -ssh-keygen -t ed25519 -C "jane.doe@psi.ch" -``` - -Add the public key (`~/.ssh/id_ed25519.pub`) to gitea under -**Settings → SSH/GPG Keys → Add Key**, then verify: - -```bash -ssh -T git@gitea.psi.ch -# Hi there, ! You've successfully authenticated with the key ... -``` - -(If you're setting this up on a headless/service account with no SSH key -available, use the HTTPS form plus a personal access token instead — -everywhere below, swap `git@gitea.psi.ch:/bernina-secrets.git` for -`https://gitea.psi.ch//bernina-secrets.git`.) - -### 3.3 Generate your GPG key (if you don't have one yet) - -```bash -gpg --full-generate-key -``` - -Recommended answers when prompted: -- Key type: `RSA and RSA` (default) -- Key size: `4096` -- Expiry: `2y` (two years — you can extend it later; forces periodic key hygiene) -- Real name / email: use your real name and PSI address, e.g. - `Jane Doe ` — gopass matches recipients by this. -- Set a passphrase you'll remember; `gpg-agent` caches it so you're not - typing it on every command. - -Find your key ID afterwards: - -```bash -gpg --list-secret-keys --keyid-format=long -# sec rsa4096/ABCD1234EF567890 2026-08-15 [SC] [expires: 2028-08-15] -# uid Jane Doe -``` - -The `ABCD1234EF567890` part (or simply your email) is what you'll use as -the gopass recipient. - -### 3.4 Create the store and connect it to gitea - -**First, make sure your default (root) store exists**, even though it -will stay empty and unused. This is a real gopass quirk, not optional: -a named `--store` substore only fully initializes — and most gopass -commands only work at all — once a root store exists too. - -```bash -gopass init jane.doe@psi.ch -``` - -You should see `git initialized at ~/.local/share/gopass/stores/root` in -the output. If you skip this and go straight to creating `bernina` below, -you'll get a store with a `.gpg-id` but no `.git` inside it, and later -commands (`git remote add`, `gopass ls`, `gopass mounts`) will fail with -`fatal: not a git repository` or `password-store is not initialized`. If -that already happened to you, fix it in this order: run the root `init` -above first, then `gopass mounts remove bernina`, delete the broken -`bernina` directory, and redo the `init --store bernina` command below — -it will fully succeed once root exists. - -Now create the actual shared store. Use `--store` to give it a name -(`bernina`) rather than using the root store you just created — this -keeps the shared team store cleanly separate from any personal secrets -you might keep in root later. - -gopass's default location for a named store is -`~/.local/share/gopass/stores/bernina`. **Where it lives locally doesn't -matter for keeping multiple hosts in sync** — git talking to gitea is what -does that (§1), not the local path. So there's no need to chase a -particular directory for sync purposes; pick whichever path you find -tidier, or just use the default: - -```bash -gopass init --store bernina jane.doe@psi.ch -# or, to use a custom path instead of the default: -gopass init --store bernina --path ~/.gopass/bernina jane.doe@psi.ch -``` - -(Earlier drafts of this guide recommended `--path ~/.gopass/bernina` -specifically to get the store picked up by PSI's dotfile-sync tool -[Partially Shared Home](https://linux.psi.ch/documentation/user-guide/personal_drive/) — -**that turned out not to work and isn't recommended**: that mechanism's -network drive can't host a git repository at all, tested by hand. §10 has -the full picture, including one genuinely useful path-based shortcut if -you only ever work from Bernina beamline hosts: `--path -/sf/bernina/config/personal//gopass-bernina` (§10.2, using the -per-user directory `create_personal_dir.sh` sets up — private, `0700`, -nobody else can even see the ciphertext) puts the store on a real NFS -mount shared identically across those hosts, so you don't even need to -repeat `init`/`clone` per host. For anything more portable than that — a -laptop, a non-Bernina host — stick with independent per-host clones -(§10.1, the default this guide assumes throughout).) - -This writes a `.gpg-id` file containing your key into `~/.gopass/bernina` -and mounts it as `bernina`. Now point it at the empty gitea repo and push: - -```bash -cd ~/.gopass/bernina -git remote add origin git@gitea.psi.ch:/bernina-secrets.git -git push -u origin master -``` - -(gopass initializes git internally on `init`, so this is a normal `git -remote add` + `push`, not a gopass-specific command.) From now on gopass -auto-syncs (`core.autosync`/`core.autopush` default to `true`), so you -generally don't need to touch git directly again — `gopass sync` is -available if you ever want to force it. - ---- - -## 4. Adding and reading secrets - -All examples assume the `bernina` store from above; paths look like -`bernina//`. Organize however makes sense for the team, -e.g. `bernina/instrument/eiger-web-ui`, `bernina/network/switch-admin`. - -### Add a secret interactively - -```bash -gopass insert bernina/instrument/eiger-web-ui -# Enter password for bernina/instrument/eiger-web-ui: -# Retype password for bernina/instrument/eiger-web-ui: -``` - -### Add a secret with extra notes (multi-line) - -The first line is always treated as "the password"; anything after is -free-form (URLs, usernames, notes): - -```bash -gopass insert -m bernina/instrument/eiger-web-ui -``` -``` -s3cr3t-Passw0rd! -url: https://eiger-daq.psi.ch -user: admin -note: shared account, ask Jane before rotating -``` - -### Let gopass generate a strong password for you - -```bash -gopass generate bernina/network/switch-admin 24 -``` - -### Read a secret - -```bash -gopass show bernina/instrument/eiger-web-ui # shows everything -gopass show -o bernina/instrument/eiger-web-ui # password only -gopass show -c bernina/instrument/eiger-web-ui # copies password to clipboard (clears after 45s) -``` - -### List / search - -```bash -gopass ls bernina # tree view -gopass find eiger # search by name -gopass grep "eiger-daq" # search inside decrypted contents -``` - -### Edit or delete - -```bash -gopass edit bernina/instrument/eiger-web-ui -gopass rm bernina/instrument/eiger-web-ui -``` - -### Sync manually - -Usually automatic, but if you want to force a pull/push: - -```bash -gopass sync -``` - -Every insert/edit/delete creates a git commit and (with autosync on) -pushes it — so `git log` in `~/.gopass/bernina` (or wherever you pointed -`--path` at) is a real audit trail of who changed which secret and when. - ---- - -## 5. Onboarding additional users - -Each new team member does this **once**. It's the fastest path — one -command handles GPG key creation/selection, cloning, and mounting. - -**Fast path for anyone in the `unx-sf_bernina_bs` group:** -[`gopass_bernina_setup.sh`](gopass_bernina_setup.sh) (same directory as this -guide) automates everything in §5.1–§5.4 below in one idempotent script — -personal directory, SSH key check, GPG key, root store, cloning `bernina` -onto the shared `/sf/bernina` NFS mount (§10.2), and exporting your public -key. Run it, then send the printed pubkey file to the admin: +# Team Secrets: gopass + gitea.psi.ch + +Sharing passwords/secrets across the Bernina team using +[gopass](https://www.gopass.pw/) with a private repo on +[gitea.psi.ch](https://gitea.psi.ch). Replace ``/emails below with +real values. `gopass`/`gpg` are already available on Bernina hosts +(`/sf/bernina/bin/gopass`). + +## How it works + +- A store is just a folder of GPG-encrypted files + (`bernina/laser/status-page.gpg`) — no server, no database. +- **git** (via gitea) only syncs and versions the ciphertext; it never + sees plaintext. +- **GPG** controls who can decrypt: each store has a `.gpg-id` listing + authorized recipients. Only someone holding the matching *private* key + can read a secret. +- **Repo access ≠ decrypt access.** Being a gitea collaborator lets you + pull/push ciphertext; being a GPG recipient lets you read it. Usually + the same people have both, but they're separate permissions (see + "Removing someone" below). + +## Which of the 3 roles are you, and which script do you run? + +| Role | Who | Repo access | Decrypt access | Script | +|---|---|---|---|---| +| **Admin** | currently lemke_h | gitea repo **owner**/admin, manages Collaborators | yes — also the one who runs `gopass recipients add`/`remove` for everyone else | `gopass_bernina_setup.sh` | +| **Normal group user** | any human on the team | gitea **Collaborator** (Write) via group access to `Bernina/bernina-secrets` | yes, once the admin authorizes their GPG key | `gopass_bernina_setup.sh` (**same script, same procedure** as the admin — "admin" is not a different technical setup, just the human who also manages recipients) | +| **Service account** | e.g. `gac-bernina` | **none directly** — no gitea account of its own; git access is a repo-scoped **Deploy Key**, and should be added **read-only** since it never pushes | yes (read-only) — its GPG key is authorized as a recipient like anyone else, but it has no reason to ever `insert`/`edit`/`rm` | `gopass_bernina_service_setup.sh` — see "Service/automation accounts" below | + +Run `