{"id":22196678,"url":"https://github.com/parmaster/zoomrs","last_synced_at":"2025-10-14T20:32:56.722Z","repository":{"id":177719757,"uuid":"634421817","full_name":"parMaster/zoomrs","owner":"parMaster","description":"Save thousands of dollars on Zoom Cloud Recording Storage! Download records automatically and store locally. Provide simple but effective web frontend to watch and share meeting recordings","archived":false,"fork":false,"pushed_at":"2025-10-02T10:29:32.000Z","size":12526,"stargazers_count":7,"open_issues_count":5,"forks_count":2,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-10-06T18:44:56.002Z","etag":null,"topics":["storage-service","zoom","zoom-api","zoom-cloud-recording","zoom-meetings","zoom-recorder"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/parMaster.png","metadata":{"files":{"readme":"README.md","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,"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":"2023-04-30T04:05:51.000Z","updated_at":"2025-10-02T10:29:36.000Z","dependencies_parsed_at":"2024-06-07T20:27:23.811Z","dependency_job_id":"45f04b60-caf5-4f07-8b7b-53d10869572f","html_url":"https://github.com/parMaster/zoomrs","commit_stats":null,"previous_names":["parmaster/zoomrs"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/parMaster/zoomrs","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/parMaster%2Fzoomrs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/parMaster%2Fzoomrs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/parMaster%2Fzoomrs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/parMaster%2Fzoomrs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/parMaster","download_url":"https://codeload.github.com/parMaster/zoomrs/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/parMaster%2Fzoomrs/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279020905,"owners_count":26086948,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-14T02:00:06.444Z","response_time":60,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":["storage-service","zoom","zoom-api","zoom-cloud-recording","zoom-meetings","zoom-recorder"],"created_at":"2024-12-02T14:16:03.208Z","updated_at":"2025-10-14T20:32:56.702Z","avatar_url":"https://github.com/parMaster.png","language":"Go","readme":"# Zoomrs - Zoom meetings recordings download service\n\n[![Go Report Card](https://goreportcard.com/badge/github.com/parMaster/zoomrs)](https://goreportcard.com/report/github.com/parMaster/zoomrs)\n[![Go](https://github.com/parMaster/zoomrs/actions/workflows/go.yml/badge.svg)](https://github.com/parMaster/zoomrs/actions/workflows/go.yml)\n[![License](https://img.shields.io/github/license/parMaster/zoomrs)](https://github.com/parMaster/zoomrs/blob/main/LICENSE)\n![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/parMaster/zoomrs?filename=go.mod)\n\nSave thousands of dollars on Zoom Cloud Recording Storage! Download records automatically and store locally. Provide simple but effective web frontend to watch and share meeting recordings.\n\n## Features\n\n- Download Zoom Cloud Recordings automatically\n- Delete/Trash downloaded recordings from Zoom Cloud after download\n- Specify which types of recordings to download (shared screen, gallery view, active speaker) and which to ignore (audio only, chat, etc.)\n- Host a simple web frontend to watch and share recordings\n- Run multiple instances of the service for redundancy\n\n## Installation\nZoomrs can be installed as a systemd service or run from the console as a persistent process or a set of CLI tools. It can be run as a Docker container as well.\n\n## Prerequisites\n### Zoom API credentials\nZoom API credentials are required to download recordings. You can get them at https://marketplace.zoom.us/develop/create. You need to create JWT app and copy API key and secret to the configuration file.\n\nAdd the following scopes to the App:\n\n- `/recording:master`\n- `/recording:read:admin`\n- `/recording:write:admin`\n- `/report:read:admin`\n\n### Google OAuth credentials *(only if you want to host web frontend)*\nGoogle OAuth credentials are required to authenticate users. You can get them at https://console.cloud.google.com/apis/credentials. You need to create OAuth client ID and copy client ID and secret to the configuration file. Mind authorized redirect URIs - local domains are not allowed, so you need to use a public domain name or IP address.\n\n### Google OAuth authorized users *(only if you want to host web frontend)*\nYou need to specify the list of users that are allowed to access the web frontend. Their email addresses should be specified in the configuration file.\n\n## Configuration\nSee `config/config_example.yml` for example configuration file, available options and their descriptions. Copy it to `config/config.yml` and edit it to your needs.\n\n## Running the service\n- To run a binary distribution, please refer to the [README](https://github.com/parMaster/zoomrs/dist/README.md) in `dist` directory.\n\n- To build from source, proceed with this manual.\n\n### Foreground mode\n\u003e [!NOTE]\n\u003e this is not recommended for production use, use systemd service instead or run it in a Docker container\n\n1. Clone the repository from GitHub\n\n\t```sh\n\tgit clone https://github.com/parMaster/zoomrs.git\n\t```\n\n2. Make sure `config/config.yml` exists and is configured properly\n3. Run `make run` to build the binary and run it in foreground mode\n\n\t```sh\n\tmake run\n\t```\n4. To stop the service press `Ctrl+C` (or send `SIGINT`, `SIGTERM` signal to the process)\n\n### Systemd service\n1. Repeat steps 1 and 2 from the previous section\n2. Run `make deploy` to build the binary and copy everything where it belongs (see `Makefile` for details), enable and run the service\n\t```sh\n\tmake deploy\n\t```\n3. Run `make status` to check the status of the service\n\n\t```sh\n\tmake status\n\t```\n\nLog files are located at `/var/log/zoomrs.log` and `/var/log/zoomrs.err` by default.\n\n### Docker container\n1. Clone the repository from GitHub\n\n\t```sh\n\tgit clone https://github.com/parMaster/zoomrs.git\n\t```\n\n2. Make sure `config/config.yml` exists and is configured properly\n3. Check configuration parameters in Dockerfile and docker-compose.yml\n4. Build and run container\n\n\t```sh\n\tdocker compose up -d\n\t```\n\n## Usage\n### Web frontend\nWeb frontend is available at `http://localhost:8099` by default. You can change the port in the configuration file (`server.listen` parameter).\n\n### Web frontend Pages\n\n```http\nGET `/`\n```\nDisplays the list of recordings. Each recording has a link to share (view) it. Recordings are sorted by date in descending order. Login is required to view the list. Google OAuth is used for authentication. Access is restricted to users with email addresses from the list specified in the configuration file (see `server.managers`).\n\nShare button is available for each recording, it generates a link to view the recording. Share link looks like:\n\n```http\nGET `/watch/834d0992ad0d632cf6c3174b975cb5e5?uuid=kzbiTyvQQp2fW6biu8Vy%2BQ%3D%3D`\n```\nDisplays the page with the meeting title and player to watch the recording. Simple controls besides the embeded player is providing are available.\n\n## API\n\n#### GET `/status`\nReturns the status of the service and Zoom cloud storage usage stats. If the service is running, returns `200 OK` and the following JSON. Example response:\n```json\n{\n  \"cloud\": {\n    \"date\": \"2023-07-09\",\n    \"free_usage\": \"495 GB\",\n    \"plan_usage\": \"0\",\n    \"usage\": \"27.98 GB\",\n    \"usage_percent\": 5\n  },\n  \"stats\": {\n    \"downloaded\": {\n      \"count\": 6529,\n      \"size_gb\": 2148,\n      \"size_mb\": 2200412\n    }\n  },\n  \"status\": \"OK\",\n  \"storage\": {\n    \"free\": \"1.2 TB\",\n    \"total\": \"3.6 TB\",\n    \"usage_percent\": 63,\n    \"used\": \"2.2 TB\"\n  }\n}\n```\nstatus can be:\n- `OK` when everything is downloaded and nothing has failed\n- `LOADING` when there are `queued` or `downloading` recordings present\n- `FAILED` when there are only `downloaded` and `failed` recordings in the database\n\n`stats` section contains number of recordings and their total size in GB and MB grouped by status\n\n`cloud` section contains Zoom cloud storage usage stats. `date` is the last time the stats were updated (it is updated every 24 hours, so if you see the date is not today, it means the stats dodn't change since then), `free_usage` is the amount of free storage, `plan_usage` is the amount of storage available for the current plan, `usage` is the amount of storage used by recordings, `usage_percent` is the percentage of used storage.\n\n`storage` section contains the stats of the local storage. `free` is the amount of free storage, `total` is the total amount of storage, `usage_percent` is the percentage of used storage, `used` is the amount of used storage.\n\nThis API is useful for monitoring the service status and triggering alerts when something goes wrong.\n\nAnother example response, when there are recordings in `queued` and `downloading` status (only relevant fields are shown):\n```json \n{\n  \"stats\": {\n    \"downloaded\": {\n      \"count\": 5292,\n      \"size_gb\": 1765,\n      \"size_mb\": 1808161\n    },\n    \"downloading\": {\n      \"count\": 1,\n      \"size_gb\": 0,\n      \"size_mb\": 666\n    },\n    \"queued\": {\n      \"count\": 88,\n      \"size_gb\": 27,\n      \"size_mb\": 28044\n    }\n  },\n  \"status\": \"LOADING\"\n}\n```\n\n#### GET `/check`\nAuth required. Runs a consistency check of the repository (see `check` cli tool cmd, it's the same). Example response:\n```json\n{\n  \"checked\": 5278,\n  \"error\": null\n}\n```\n\n#### GET `/stats[/\u003cK|M|G\u003e]`\nAuth required. Returns the total size of the recordings grouped by date. Optional parameter `K`, `M` or `G` can be used to specify the size in KB, MB or GB respectively. If no parameter is specified, the size is returned in bytes. Example response:\n```json\n{\n\t\"2023-03-20\":31,\n\t\"2023-03-21\":13,\n\t\"2023-03-22\":36,\n\t\"2023-03-23\":19,\n\t\"2023-03-24\":41\n}\n```\n\n#### GET `/meetingsLoaded/{accessKey}`\n`accessKey` is checked against server.access_key_salt config option. This api is called to ask if every meeting from the list is loaded, list is passed as a JSON array of UUIDs in the request body.\nRequest example:\n```json\n{\n\t\"meetings\":{\n\t\t\"in7MDVrTS5adXWFwsCwoYg==\",\n\t\t\"0ao3hvbxQvqU2wkpXjbwhw==\",\n\t\t\"pEbVqZ5jQP6+NY0ewvZ+wg==\",\n\t\t\"uOoMA3wcSF65PtwTDw/k1w==\"\n\t}\n}\n``` \n\nResponse when all meetings are loaded:\n```json\n{\n\t\"result\":\"ok\"\n}\n```\nResponse when some meetings are not loaded:\n```json\n{\n\t\"result\":\"pending\"\n}\n```\n\n## CLI tool\nZoomrs comes with a CLI tool to trash/delete recordings from Zoom Cloud. It is useful when running miltiple servers and you want to delete recordings from Zoom Cloud only after all servers have downloaded them. CLI tool is located at `cmd/cli/main.go`. Run `make` to build it and put to `dist/zoomrs-cli`.\nIt can be run like this:\n\n```sh\ngo run ./cmd/cli --cmd check\n```\n\nor like this:\n\n```sh\n./dist/zoomrs-cli --cmd check\n```\n\nAvailable commands:\n- `check` - checks the consistency of the repository: if all recordings are downloaded and if all downloaded recordings are present on the disk, also the size of each recording file is checked. Run this command periodically to make sure everything is OK. \nRun it like this:\n\n```sh\n./dist/zoomrs-cli --cmd check\n```\n\tExample output:\n\t```\n\t2023/06/19 17:15:01 [INFO]  starting CheckConsistency\n\t2023/06/19 17:15:01 [INFO]  Checked files: 5278\n\t2023/06/19 17:15:01 [INFO]  CheckConsistency: OK, 5278\n\t```\n- `trash` - trashes recordings from Zoom Cloud. Run it like this:\n\n```sh\n./zoomrs-cli --dbg --cmd trash --trash 2\n```\n\n\twhere `2` is 2 days before today, so all the recordings from the bay before yesterday will be trashed. This is designed this way to run it as a cron job every day. Cron job line example:\n```sh\n00 10 * * * cd $HOME/go/src/zoomrs/dist \u0026\u0026 ./zoomrs-cli --cmd trash --trash 2 --config ../config/config_cli.yml \u003e\u003e /var/log/cron.log 2\u003e\u00261\n```\n\n\twill trash all recordings from the day before yesterday every day at 10:00 AM. `--config` option is used to specify the path to the configuration file. `--dbg` option can be used to enable debug logging. Logs are written to stdout, and redirected to `/var/log/cron.log` in the example above.\n\n- `cloudcap` - trims recordings from Zoom Cloud to avoid exceeding the storage limit. Leaves `Client.CloudCapacityHardLimit` bytes of the most recent recordings (review the value in config before running!), trashes the rest. Cron job line to run it every day at 5:30 AM (don't mind the paths, they are specific to my setup, use your own):\n```sh\n30 05 * * * cd $HOME/go/src/zoomrs/dist \u0026\u0026 ./zoomrs-cli --dbg --cmd cloudcap --config ../config/config_cli.yml \u003e\u003e /var/log/cron.log 2\u003e\u00261\n```\n- `sync` - syncs recordings from Zoom Cloud. Run it like this:\n```sh\n./zoomrs-cli --dbg --cmd sync --days 1\n```\n\n\t`--days` parameter used with the value of `1` to sync all the yesterday recordings (1 day before today). This is designed this way to run it as a cron job. Cron job line example:\n```sh\n00 03 * * * cd $HOME/go/src/zoomrs/dist \u0026\u0026 ./zoomrs-cli --cmd sync --days 1 --config ../config/config_cli.yml \u003e\u003e /var/log/zoomrs.cron.log 2\u003e\u00261\n```\n\nwill sync all recordings from the yesterday every day at 3:00 AM. `--config` option is used to specify the path to the configuration file. `--dbg` option can be used to enable debug logging. Logs are written to stdout, and redirected to `/var/log/cron.log` in the example above.\n\n\n\u003e [!NOTE] \n\u003e CLI tool uses different configuration file then the server with different Zoom API credentials to avoid spoiling services's auth token when running CLI. Also, running multi-server setup you want to sync recordings only after all servers have downloaded them, so you need to run CLI tool on one of the servers, allow syncing records in CLI config and deny it in servers configs.\n\n## Running multiple instances\nYou can run multiple instances of the service to increase reliability, duplicate downloaded data for redundancy. Each instance should have its own configuration file and its own database file. Each instance should have its own Zoom API credentials. Consider following setup as an example:\n1. One main instance that downloads recordings and hosts web frontend (see `config/config_example.yml` for example configuration file). Enable sync and download for this instance: `server.sync_job: true` and `server.download_job: true` in the configuration file, set oauth credentials and authorized users.\n2. One or many secondary instances that download recordings but don't host web frontend. Two options are available here:\n\t- Run the service with `server.sync_job: true` and `server.download_job: true` in the configuration file. This way download job will run somewhere from 00:00 to 01:00 am.\n\t- Run the service with `server.sync_job: false` and `server.download_job: false` so it will just host the API. Run downloader with cron job (see `sync` cmd crontab line example in the previous section). This way you can set the time to run the download job\n3. Run cleanup job on one of the instances (see `trash` cmd crontab line example in the previous section). Use configuration file that enumerates all the instances in `server.instances` section. This way cleanup job will check all the instances for consistency and trash/delete recordings from Zoom Cloud only if all the instances have downloaded them. Disable deleting and trashing downloaded recordings (`client.trash_downloaded: false` and `client.delete_downloaded: false` in the configuration file) on every other instance but this one.\n\n\u003e [!NOTE]\n\u003e Copy yesterday's recordings from \"Main\" instance to \"Secondary\" instance\n\u003e Secondary instance can run something like this to copy yesterday's recordings from \"Main\" instance:\n\n```sh\nsleep 1s \u0026\u0026 date \u0026\u0026 scp -r server.local:/data/`date --date=\"yesterday\" +%Y-%m-%d` /data/ \u0026\u0026 date\n```\n\n\u003e [!NOTE]\n\u003e Database backup\n\u003e Backup database file regularly to prevent data loss. See example shell script at `dist/backup_db.sh`. It can be run as a cron job like this:\n\n```sh\n0 10 * * * sh $HOME/go/src/zoomrs/backup_db.sh\n```\n\n## Contributing\nPull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. Check the existing issues to see if your problem is already being discussed or if you're willing to help with one of them. Tests are highly appreciated.\n\n## License\n[GNU GPLv3](https://choosealicense.com/licenses/gpl-3.0/) © [Dmytro Borshchanenko](https://github.com/parMaster) 2023\n\n## Responsible disclosure\nIf you have any security issue to report, contact project owner directly at [master@parMaster.com.ua](mailto:master@parMaster.com.ua) or use Issues section of this repository.\n\n## Responsibility\nThe author of this project is not responsible for any damage caused by the use of this software. Use it at your own risk. However, the software is being used in production at least since May 2023 on a number of devices, processing hundreds of GB of data every day and is considered stable.\n\n## Credits\n- [lgr](github.com/go-pkgz/lgr) - simple but effective logging package\n- [go-sqlite3](github.com/mattn/go-sqlite3) as a database driver\n- [go-pkgz/auth](github.com/go-pkgz/auth) - powerful authentication middleware\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fparmaster%2Fzoomrs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fparmaster%2Fzoomrs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fparmaster%2Fzoomrs/lists"}