{"id":37078712,"url":"https://github.com/jkglasbrenner/sshcustodian","last_synced_at":"2026-01-14T09:12:51.925Z","repository":{"id":57470820,"uuid":"59689454","full_name":"jkglasbrenner/sshcustodian","owner":"jkglasbrenner","description":"A modification to the Custodian class in custodian (github.com/materialsproject/custodian) to allow for copying the temp_dir to other compute nodes via ssh.","archived":false,"fork":false,"pushed_at":"2016-05-26T20:40:21.000Z","size":52,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-09-29T17:14:01.957Z","etag":null,"topics":["computational-science","condensed-matter","custodian","materials-science","python"],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jkglasbrenner.png","metadata":{"files":{"readme":"README.rst","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-05-25T18:52:14.000Z","updated_at":"2017-02-16T23:02:47.000Z","dependencies_parsed_at":"2022-09-26T17:40:22.613Z","dependency_job_id":null,"html_url":"https://github.com/jkglasbrenner/sshcustodian","commit_stats":null,"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"purl":"pkg:github/jkglasbrenner/sshcustodian","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jkglasbrenner%2Fsshcustodian","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jkglasbrenner%2Fsshcustodian/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jkglasbrenner%2Fsshcustodian/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jkglasbrenner%2Fsshcustodian/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jkglasbrenner","download_url":"https://codeload.github.com/jkglasbrenner/sshcustodian/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jkglasbrenner%2Fsshcustodian/sbom","scorecard":{"id":522464,"data":{"date":"2025-08-11","repo":{"name":"github.com/jkglasbrenner/sshcustodian","commit":"870d1088f27e1528e27f94f55f2efad7dad32d5d"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3,"checks":[{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Code-Review","score":0,"reason":"Found 0/30 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}}]},"last_synced_at":"2025-08-20T03:24:28.193Z","repository_id":57470820,"created_at":"2025-08-20T03:24:28.193Z","updated_at":"2025-08-20T03:24:28.193Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28414937,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T08:38:59.149Z","status":"ssl_error","status_checked_at":"2026-01-14T08:38:43.588Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["computational-science","condensed-matter","custodian","materials-science","python"],"created_at":"2026-01-14T09:12:51.203Z","updated_at":"2026-01-14T09:12:51.920Z","avatar_url":"https://github.com/jkglasbrenner.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"=============\nSSH Custodian\n=============\n\nThis module depends on the Custodian class in the `custodian project\n\u003chttps://github.com/materialsproject/custodian\u003e`_, which is a wrapper that\nmanages jobs running on computing clusters. The custodian module is part of\n`The Materials Project \u003chttp://materialsproject.org/\u003e`_.\n\nThis module extends the Custodian class by creating the subclass SSHCustodian,\nwhich adds the functionality to copy the temporary directory created via monty\nto the scratch partitions on slave compute nodes, provided that the cluster's\nfile-system is configured in this way. The implementation invokes a sub-process\nto utilize the ssh executable installed on the cluster, so it is not\nparticularly elegant or platform independent, nor is this solution likely to be\ngeneral to all clusters. This is why this modification has not been submitted\nas a pull request to the main Custodian project.\n\nYou use SSHCustodian in the same way as the Custodian class, and it should\nintegrate in with your existing scripts. The SSHCustodian class takes two\nadditional arguments when creating a new instance, ``scratch_dir_node_only``\nand ``pbs_nodefile``::\n  \n  scratch_dir_node_only (bool): If set to True, custodian will grab the list\n      of nodes in the file path provided to pbs_nodefile and use copy the\n      temp_dir to the scratch_dir on each node over ssh. This is necessary on\n      cluster setups where each node has its own independent scratch\n      partition.\n      \n  pbs_nodefile (str): The filepath to the list of nodes to be used in a\n      calculation. If this path does not point to a valid file, then\n      scratch_dir_node_only will be automatically set to False.\n\nThe subclass SSHVaspJob was also created, which overrides the setup method to\nalso check the environment variable ``PBS_NUM_PPN`` when ``auto_npar =\nTrue``. This is necessary to implement for using compute node scratch\npartitions on PBS-based queueing systems, as the generic method of using\n``multiprocessing.cpu_count()`` to count the number of cores will include\nhyperthreads, which will overestimate the number of physical cores and lead to\nNPAR being set too large. One consequence of setting NPAR too high is that a\nVASP job will hang, consuming resources but not doing anything useful. If you\nare using this kind of scratch directory, be careful about setting NPAR.\n\nOn many clusters, the filepath for the list of compute nodes is in the\nenvironment variable ``PBS_NODEFILE``, which can be accessed in bash as\n``$PBS_NODEFILE`` and in python using the ``os`` module. An example of\nhow SSHCustodian can be used in a script is the following::\n\n  import logging\n  import os\n  from sshcustodian.sshcustodian import SSHCustodian\n  from custodian.vasp.handlers import (VaspErrorHandler,\n                                       UnconvergedErrorHandler,\n                                       MeshSymmetryErrorHandler,\n                                       NonConvergingErrorHandler,\n                                       PotimErrorHandler)\n  from custodian.vasp.validators import VasprunXMLValidator\n  from sshcustodian.vasp.sshjobs import SSHVaspJob\n  from pymatgen.io.vasp import VaspInput\n\n  FORMAT = '%(asctime)s %(message)s'\n  logging.basicConfig(format=FORMAT, level=logging.INFO, filename=\"run.log\")\n\n  class VaspInputArgs:\n      def __init__(self):\n          \"\"\"\n          Set the default values for running a VASP job.\n          \"\"\"\n          self.static_kpoint = 1\n      \n      def import_dict(self, in_dict):\n          \"\"\"\n          Create and update self variables using dictionary.\n          \"\"\"\n          for (key, value) in iteritems(in_dict):\n              if key == \"command\":\n                  self.command = value\n              if key == \"static_kpoint\":\n                  self.static_kpoint = value\n              if key == \"jobs\":\n                  self.jobs = value\n           \n\n  def get_runs(args):\n      vasp_command = args.command.split()\n      njobs = len(args.jobs)\n      for i, job in enumerate(args.jobs):\n          final = False if i != njobs - 1 else True\n          if any(c.isdigit() for c in job):\n              suffix = \".\" + job\n          else:\n              suffix = \".{}{}\".format(job, i + 1)\n          settings = []\n          backup = True if i == 0 else False\n          copy_magmom = False\n          vinput = VaspInput.from_directory(\".\")\n          if i \u003e 0:\n              settings.append(\n                  {\"file\": \"CONTCAR\",\n                   \"action\": {\"_file_copy\": {\"dest\": \"POSCAR\"}}})\n          job_type = job.lower()\n          auto_npar = True\n          if job_type.startswith(\"static\"):\n              m = [i * args.static_kpoint for i in vinput[\"KPOINTS\"].kpts[0]]\n              settings.extend([\n                  {\"dict\": \"INCAR\",\n                   \"action\": {\"_set\": {\"NSW\": 0}}},\n                  {'dict': 'KPOINTS',\n                   'action': {'_set': {'kpoints': [m]}}}])\n      \n          yield SSHVaspJob(vasp_command, final=final, suffix=suffix,\n                           backup=backup, settings_override=settings,\n                           copy_magmom=copy_magmom, auto_npar=auto_npar)\n\n\n  scratch_root = os.path.abspath(\"/scratch\")\n  pbs_nodefile = os.environ.get(\"PBS_NODEFILE\")\n  job_args = VaspInputArgs()\n  job_dict = {\"command\": \"pvasp\",\n              \"jobs\": [\"static\"]}\n  job_args.import_dict(job_dict)\n  handlers = [VaspErrorHandler(), MeshSymmetryErrorHandler(),\n              UnconvergedErrorHandler(), NonConvergingErrorHandler(),\n              PotimErrorHandler()]\n  validators = [VasprunXMLValidator()]\n  logging.info(\"Handlers used are {0}\".format(handlers))\n  c = SSHCustodian(handlers, get_runs(job_args), validators,\n                   checkpoint=True,\n                   scratch_dir=scratch_root,\n                   scratch_dir_node_only=True,\n                   pbs_nodefile=pbs_nodefile)\n  c.run()\n\nNote that depending on how your cluster is configured, the ``\"command\":\n\"pvasp\"`` will need to be changed to however you invoke a parallel job.\n\nFor further information on how to use custodian, consult the `custodian project\ndocumentation \u003chttps://pythonhosted.org/custodian/\u003e`_.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjkglasbrenner%2Fsshcustodian","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjkglasbrenner%2Fsshcustodian","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjkglasbrenner%2Fsshcustodian/lists"}