Created
December 25, 2025 16:49
-
-
Save Jwink3101/9845ff48917e71a637740cfd35443cd7 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python | |
| """ | |
| This is my generic script to mount[1] an rclone[2] crypt[3] locally[4], with some | |
| tuning for that specific use case. | |
| It is designed for macOS and FUSE-T[5] but should work on all. | |
| This assumes 'config.cfg' is in the same directory of the script. | |
| NOTE: The config is encrypted with rclone's native config encryption[6] | |
| and it will let rclone prompt for the password. | |
| [1]: https://rclone.org/commands/rclone_mount/ | |
| [2]: https://rclone.org/ | |
| [3]: https://rclone.org/crypt/ | |
| [4]: https://rclone.org/local/ | |
| [5]: https://www.fuse-t.org/ | |
| [6]: https://rclone.org/docs/#configuration-encryption | |
| Built in conjunction with ChatGPT 5.2 but *not* "vibe coded" | |
| """ | |
| import os | |
| import sys | |
| import subprocess | |
| import tempfile | |
| import shutil | |
| import signal | |
| import atexit | |
| from pathlib import Path | |
| ################################ Make sure in GNU Screen ################################ | |
| if 'STY' not in os.environ: | |
| print("Not running in a GNU Screen session. Relaunching in Screen...") | |
| # Relaunch the script in a new Screen session with all arguments | |
| name = f"enc.{os.getpid()}" # Make sure it is unique (enough) | |
| subprocess.run(['screen', '-S', name, sys.executable] + sys.argv) | |
| sys.exit() | |
| ######################################################################################### | |
| # Make sure you are executing in the current directory of the file | |
| os.chdir(os.path.dirname(os.path.abspath(__file__))) | |
| MNT= Path("~/Desktop/_priv").expanduser() | |
| MNT.mkdir(parents=True,exist_ok=False) # Will error if it exists | |
| # This uses the OS's tempdir. But if the script is killed hard (e.g. -9), it will | |
| # not get cleaned. You could instead use the Desktop so you can confirm it's cleared | |
| # or use any other path. | |
| TMP = Path(tempfile.mkdtemp(prefix='PRIV.',suffix='.PRIV')) | |
| #TMP = Path("~/Desktop/__TEMP_DELETE_ME__").expanduser() | |
| temp = TMP / 'temp' | |
| cache = TMP / 'cache' | |
| temp.mkdir(parents=True,exist_ok=False) | |
| cache.mkdir(parents=True,exist_ok=False) | |
| env = os.environ.copy() | |
| env['RCLONE_TEMP_DIR'] = str(temp) | |
| env['RCLONE_CACHE_DIR'] = str(cache) | |
| env['RCLONE_CONFIG'] = 'config.cfg' # Update for your config path | |
| env.pop('RCLONE_PASSWORD_COMMAND',0) | |
| # Track rclone explicitly so we can terminate it during cleanup | |
| rclone_proc = None | |
| cleaned = False | |
| def cleanup(signum=None, frame=None): | |
| """ | |
| Best-effort cleanup. | |
| Safe to call multiple times. | |
| """ | |
| global cleaned, rclone_proc | |
| if cleaned: | |
| return | |
| cleaned = True | |
| print("\nCleaning up…") | |
| # Terminate rclone if it is still running | |
| if rclone_proc and rclone_proc.poll() is None: | |
| try: | |
| rclone_proc.terminate() | |
| rclone_proc.wait(timeout=5) | |
| except Exception: | |
| pass | |
| # This is macOS specific. Need to update for Linux | |
| subprocess.run(["umount", "-f", str(MNT)], check=False, env=env) | |
| shutil.rmtree(TMP, ignore_errors=True) | |
| print(f"Fully removed (rm -rf) {str(TMP)!r}") | |
| try: | |
| os.rmdir(MNT) | |
| print(f"Removed Mount (rmdir) {str(MNT)!r}") | |
| except OSError: | |
| pass | |
| if signum is not None: | |
| sys.exit(128 + signum) | |
| # Explicit signal handling (covers far more cases than KeyboardInterrupt alone) | |
| for sig in (signal.SIGINT, signal.SIGTERM, signal.SIGHUP, signal.SIGQUIT): | |
| signal.signal(sig, cleanup) | |
| # Fallback only — not relied upon | |
| atexit.register(cleanup) | |
| command = [ | |
| 'rclone', | |
| # Mounting method | |
| #'nfsmount', | |
| 'mount', | |
| #'-o','backend=smb', # https://forum.rclone.org/t/macos-rclone-mount-new-fuse-t-released-old-issues-fixed/39502/13?u=jwink3101 | |
| 'crypt:', MNT, | |
| # Options | |
| '-v', | |
| '--volname', 'PRIV', | |
| '--vfs-links', # translate to/from .rclonelinks | |
| ## Cache Settings tuned for local | |
| # Reading from local is FAST so just cache writes. If this were a non-local remote | |
| # I would use 'full' | |
| '--vfs-cache-mode', 'writes', | |
| '--vfs-cache-max-age', '15m', | |
| '--vfs-cache-max-size', '2G', | |
| '--vfs-fast-fingerprint', | |
| '--vfs-write-back', '5s', | |
| '--vfs-cache-poll-interval', '20s', | |
| '--vfs-case-insensitive', | |
| # '--file-perms', '0777', # Uncomment if needed | |
| # Stats printing | |
| '--stats-one-line', '--stats', '20s', | |
| # Apple cruft | |
| '--noappledouble', '--noapplexattr', | |
| '--filter', '- ._*', # Make sure resource forks aren't written | |
| # This ignores symlinks in the encrypted dir. Uncomment if you want them followed | |
| '--skip-links', | |
| ] | |
| try: | |
| rclone_proc = subprocess.Popen( | |
| [str(c) for c in command], # make sure all strings, not ints or Paths | |
| env=env, | |
| start_new_session=True, # This makes sure that CTRL-C is not sent to rclone. | |
| # Cleanup handles termination. | |
| ) | |
| rclone_proc.wait() | |
| finally: | |
| cleanup() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment