{"id":38686041,"url":"https://github.com/opensafely-core/sysadmin","last_synced_at":"2026-01-17T10:23:46.030Z","repository":{"id":37413287,"uuid":"314219460","full_name":"opensafely-core/sysadmin","owner":"opensafely-core","description":"Various scripts and tools for administering OpenSAFELY organisation and infrastructure","archived":false,"fork":false,"pushed_at":"2025-10-06T07:07:18.000Z","size":1148,"stargazers_count":0,"open_issues_count":31,"forks_count":0,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-10-06T09:25:37.150Z","etag":null,"topics":["foo"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/opensafely-core.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2020-11-19T10:55:39.000Z","updated_at":"2025-10-06T07:07:21.000Z","dependencies_parsed_at":"2023-10-11T16:55:30.731Z","dependency_job_id":"d1624389-baec-498c-984f-fe54b4595d82","html_url":"https://github.com/opensafely-core/sysadmin","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/opensafely-core/sysadmin","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/opensafely-core%2Fsysadmin","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/opensafely-core%2Fsysadmin/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/opensafely-core%2Fsysadmin/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/opensafely-core%2Fsysadmin/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/opensafely-core","download_url":"https://codeload.github.com/opensafely-core/sysadmin/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/opensafely-core%2Fsysadmin/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28506389,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-17T06:57:29.758Z","status":"ssl_error","status_checked_at":"2026-01-17T06:56:03.931Z","response_time":85,"last_error":"SSL_read: 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":["foo"],"created_at":"2026-01-17T10:23:45.933Z","updated_at":"2026-01-17T10:23:46.011Z","avatar_url":"https://github.com/opensafely-core.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# OpenSAFELY Sysadmin Tools\n\nThis repository contains the documentation and scripts used to manage\nthe OpenSAFELY Github organisation's users, teams, repos and\npermissions.\n\nGithub's organisation features are somewhat limited. Repositories are\nflat, no grouping, so each repo needs explicitly adding to a team, at an\nexplicit permissions level. This makes managing this via the UI\nlaborious and and error prone.\n\nThis repo include config and scripts to manage the teams and repos via\nthe Github API.\n\nThe high level goal to protect against injection of code via github into\nany part of the OpenSAFELY systems. To reduce risk, we to separate the\nsensitive infrastructure repos out from the ever-growing list of\nstudy repos, and restrict write access to the senstive repos to a\nsmaller technical team.  As there is no repository grouping, this is\ndone via explicit config stored in this repo.\n\nThere are two teams. Researchers have admin access to all study repos.\nDevelopers have admin access to all protected infrastructure repos, and\nare also in Researchers team.\n\nAll master/main branches are protected, even for admins. This disables\nforce-pushes from anywhere.\n\nAdditionally, protected repos require code review, and signing. This\nprevents pushes to master/main without a review.\n\n# Readonly Classic PATs\n\nAt the time this system was implemented, Github only had classic PATs. And\nwhilst these supported a readonly scope for public repos, if you wanted private\nrepo access, you *had* to have write access too. However, it was not acceptable\nfor job-server or job-runner to have write access.\n\nSo, in order to acheive a readonly opensafely org PAT, we:\n\na) lowered the base permissions for the opensafely org to read (they were admin!)\nb) added the machinery described above to elevate approved users permissions to be able to *write*.\nc) created an opensafely-readonly bot account, that was *not* included in the machinery above\nc) use this bot use to create PATs for job-server and job-runner.\n\nOver time, this readonly user has also been used to create issues in various\nprivate repo, so is also a collaborator on specific repos in ebmdatalab org as well.\n\n# Run\n\nEnsure you have a GH PAT with org admin permissions in `./org-token`\n\n`make manage` will run the command in dryrun mode, printing changes it would have made\n\n`make manage ARGS=--exec` will actually apply the changes.\n\n\n# Cron Job\n\nThe management script is designed to run periodically. However, it uses a very\nprivileged secret, so it currently runs from Simon's home machine.\n\nRun set up a cronjob yourself, you can use `cronjob.sh`. First, edit the `tokenfile` variable to\npoint a file with a GH PAT that has admin org permissions.\n\nThen, set it to run every hour at n minutes past the hour via `crontab -e` or similar.\ne.g. to run at 17m past each hour:\n\n`17 * * * * /path/to/cronjob.sh \u003e\u003e /path/to/logfile.log 2\u003e\u00261`\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fopensafely-core%2Fsysadmin","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fopensafely-core%2Fsysadmin","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fopensafely-core%2Fsysadmin/lists"}