{"id":16924818,"url":"https://github.com/stapelberg/scan2drive","last_synced_at":"2026-03-05T03:31:12.072Z","repository":{"id":57516542,"uuid":"58916425","full_name":"stapelberg/scan2drive","owner":"stapelberg","description":"scan paper documents 📄 from a scanner 🖨️ as PDFs to Google Drive for full-text search","archived":false,"fork":false,"pushed_at":"2025-02-18T20:51:24.000Z","size":12368,"stargazers_count":168,"open_issues_count":7,"forks_count":7,"subscribers_count":10,"default_branch":"main","last_synced_at":"2026-02-02T07:37:20.504Z","etag":null,"topics":["golang","google-drive","scan","scanner"],"latest_commit_sha":null,"homepage":"","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/stapelberg.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}},"created_at":"2016-05-16T08:32:24.000Z","updated_at":"2026-02-01T20:57:16.000Z","dependencies_parsed_at":"2024-06-05T21:05:37.009Z","dependency_job_id":null,"html_url":"https://github.com/stapelberg/scan2drive","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/stapelberg/scan2drive","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stapelberg%2Fscan2drive","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stapelberg%2Fscan2drive/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stapelberg%2Fscan2drive/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stapelberg%2Fscan2drive/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/stapelberg","download_url":"https://codeload.github.com/stapelberg/scan2drive/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stapelberg%2Fscan2drive/sbom","scorecard":{"id":846199,"data":{"date":"2025-08-11","repo":{"name":"github.com/stapelberg/scan2drive","commit":"e6222fa4c91831878013bcb5510126909da9efaa"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":2.9,"checks":[{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","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":"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":"Code-Review","score":0,"reason":"Found 2/27 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":"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":"Binary-Artifacts","score":8,"reason":"binaries present in source code","details":["Warn: binary detected: _bundled_turbojpeg/libjpeg.a:1","Warn: binary detected: _bundled_turbojpeg/libturbojpeg.a:1"],"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":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/gokrazy.yml:1","Warn: no topLevel permission defined: .github/workflows/main.yml:1","Info: no jobLevel write permissions found"],"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":"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":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: Apache License 2.0: 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":"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":"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 'main'"],"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"}},{"name":"Pinned-Dependencies","score":1,"reason":"dependency not pinned by hash detected -- score normalized to 1","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/gokrazy.yml:12: update your workflow using https://app.stepsecurity.io/secureworkflow/stapelberg/scan2drive/gokrazy.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/gokrazy.yml:14: update your workflow using https://app.stepsecurity.io/secureworkflow/stapelberg/scan2drive/gokrazy.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/main.yml:14: update your workflow using https://app.stepsecurity.io/secureworkflow/stapelberg/scan2drive/main.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/main.yml:17: update your workflow using https://app.stepsecurity.io/secureworkflow/stapelberg/scan2drive/main.yml/main?enable=pin","Warn: containerImage not pinned by hash: .github/workflows/Dockerfile:2: pin your Docker image by updating debian:sid to debian:sid@sha256:8fcabbc0112b91ed4faabae11ccb91dba23263563bc07a9489a0c7ab3a8494ae","Warn: containerImage not pinned by hash: _bundled_turbojpeg/Dockerfile:1: pin your Docker image by updating debian:bookworm to debian:bookworm@sha256:731dd1380d6a8d170a695dbeb17fe0eade0e1c29f654cf0a3a07f372191c3f4b","Warn: goCommand not pinned by hash: .github/workflows/gokrazy.yml:21","Warn: goCommand not pinned by hash: .github/workflows/main.yml:32","Info:   0 out of   4 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   2 containerImage dependencies pinned","Info:   1 out of   3 goCommand dependencies pinned"],"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":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 5 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":6,"reason":"4 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GO-2025-3487 / GHSA-hcg3-q754-cr77","Warn: Project is vulnerable to: GO-2025-3503 / GHSA-qxp5-gwg8-xv66","Warn: Project is vulnerable to: GO-2025-3595 / GHSA-vvgc-356p-c3xw","Warn: Project is vulnerable to: GO-2025-3488 / GHSA-6v2p-p543-phr9"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-23T21:29:42.732Z","repository_id":57516542,"created_at":"2025-08-23T21:29:42.732Z","updated_at":"2025-08-23T21:29:42.732Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30108585,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-05T01:39:18.192Z","status":"online","status_checked_at":"2026-03-05T02:00:06.710Z","response_time":93,"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":["golang","google-drive","scan","scanner"],"created_at":"2024-10-13T20:07:08.325Z","updated_at":"2026-03-05T03:31:12.052Z","avatar_url":"https://github.com/stapelberg.png","language":"C","funding_links":[],"categories":[],"sub_categories":[],"readme":"# scan2drive\n\n\u003cimg src=\"https://github.com/stapelberg/scan2drive/raw/main/scan2drive.png\"\nwidth=\"266\" align=\"right\" alt=\"scan2drive screenshot\"\u003e\n\nscan2drive is a Go program (with a web interface) for scanning, converting and\nuploading physical documents to Google Drive. The author runs scan2drive as a\n[gokrazy](https://gokrazy.org/) appliance on a Raspberry Pi 4.\n\nDuring the conversion step, scan2drive skips empty pages and converts the rest\nfrom multi-megabyte JPEGs into a kilobyte-sized PDF. This allows you to use\nGoogle Drive’s\n[OCR](https://en.wikipedia.org/wiki/Optical_character_recognition)-based full\ntext search.\n\nBoth the originals and the converted PDF are uploaded to Google Drive, so that\nyou can enjoy full text search but still have the full-quality originals just\nin case.\n\nIn comparison to the native Google Drive connectivity which some document\nscanner vendors provide, scan2drive has these main advantages:\n\n 1. scan2drive integrates with the scan button of your document scanner. You\n    press one button and your documents will end up on Google Drive. Other\n    solutions require you to use a mobile app or software on your PC.\n 1. scan2drive is self-hosted and depends only on Google Drive being available,\n    not the scanner vendor’s cloud integration service. Many vendors send\n    documents into their own clouds and then to Google Drive. You are welcome\n    to archive the scan directory of scan2drive to other places you see fit, in\n    case there are any issues with Google Drive.\n 1. scan2drive converts the scanned documents into a PDF which is small enough\n    to be full text indexed by Google Drive, but it also retains the original\n    JPEGs in case you need them.\n\n## Project status and vision\n\nCurrently, there are a number of open issues and not all functionality might\nwork well. Use at your own risk!\n\nThe project vision is described above. Notably, scan2drive is already feature\ncomplete. We don’t want to add any more features to it than it currently has.\n\nscan2drive was published in the hope that it could be useful to others, but the\nmain author has no time to create an active community around it or accept\ncontributions in a timely manner. All support, development and bug fixes are\nstrictly best effort.\n\n## Supported scanners {#supported}\n\n* scan2drive can scan from **any AirScan-compatible scanner**. This means any\n  scanner that is marketed as compatible with Apple iPhones should work. You can\n  find a list of tested devices at\n  https://github.com/stapelberg/airscan#tested-devices\n* Fujitsu ScanSnap iX500 connected via USB\n\n## Architecture\n\n![](/img/2021-11-14-scan2drive-architecture.svg)\n\n## Directory structure\n\nThe scans directory (`-scans_dir` flag) contains the following files:\n\n * `\u003csub\u003e/` is the per-user directory under which scans are placed\n  * `2016-05-09-21:05:02+0200/` is a directory for an individual scan\n    * `page*.jpg` are the raw pages obtained by calling `scanimage`\n    * `scan.pdf` is the converted PDF\n    * `thumb.png` is the first page of the converted PDF for display in the UI\n    * `COMPLETE.*` are empty files recording which individual processing steps\n      are done\n\nAny file in the scans directory can be deleted at will, with the caveat that\ndeleting scans before the `COMPLETE.uploadoriginals` file is present will\nresult in that scan being irrevocably lost.\n\nThe state directory (`-state_dir` flag) contains the following files:\n\n * `cookies.key` is a secret key with which cookies are encrypted\n * `sessions/` contains session contents\n * `users/` is a directory containing per-user data\n  * `users/\u003csub\u003e/` is a directory for an individual user\n    * `drive_folder.json` contains information about the selected destination\n      Google Drive folder. In case this file is deleted, the user will need to\n      re-select the destination folder and scans cannot be uploaded until a new\n      destination folder has been selected.\n    * `token.json` contains the offline OAuth token for accessing Google Drive\n      on behalf of the user. In case this file is deleted, the user will need\n      to re-login. In case this file is leaked, the user should [revoke the\n      token](https://security.google.com/settings/u/0/security/permissions)\n\n## Installation\n\nFirst, [follow the gokrazy quickstart instructions](https://gokrazy.org/quickstart/).\n\nThen, add `github.com/stapelberg/scan2drive/cmd/scan2drive` package to your\ngokrazy instance:\n\n```\ngok -i scanner add github.com/stapelberg/scan2drive/cmd/scan2drive\n```\n\nDeploy your gokrazy instance to your Raspberry Pi and connect [a supported\nscanner](#supported).\n\nYou should be able to access the gokrazy web interface at the URL which the\n`gok` tool printed. To access the scan2drive web interface, switch to port 7120.\n\nFor setting up Google OAuth, you’ll need to access scan2drive via a domain name\nwith a valid TLS certificate. scan2drive has builtin support to obtain free\ncertificates from [Let’s\nEncrypt](https://en.wikipedia.org/wiki/Let%27s_Encrypt), but you do need to make\nyour scan2drive installation reachable over the internet for this to work:\n\n1. If your provider offers IPv6, set your domain name’s AAAA record to point to\n   your Raspberry Pi’s internet-reachable IPv6 address.\n1. If you don’t have IPv6 available, set up a port forwarding on your router and\n   use Dynamic DNS to make your domain name point to your current IP address.\n\n## Building with libjpeg-turbo\n\n[libjpeg-turbo](https://libjpeg-turbo.org/) is a JPEG image codec that uses SIMD\ninstructions (Arm Neon in case of the Raspberry Pi) to accelerate baseline JPEG\ncompression.\n\nscan2drive can optionally make use of libjpeg-turbo (via the `turbojpeg` build\ntag), but doesn’t include it by default because of the cumbersome setup.\n\nUsing libjpeg-turbo on gokrazy requires a few extra setup steps. Because gokrazy\ndoes not include a C runtime environment (neither libc nor a dynamic linker), we\nneed to link scan2drive statically.\n\n1. Install the gcc cross compiler, for example on Debian:\n    ```\n   apt install crossbuild-essential-arm64\n   ```\n\n1. Enable cgo for your gokrazy instance. This means setting the following\n   environment variables when calling `gok` (for example in your “gokline”, see\n   [gokrazy → Automation](https://gokrazy.org/userguide/automation/)):\n\n    ```\n    export CC=aarch64-linux-gnu-gcc\n    export CGO_ENABLED=1\n    ```\n\n1. Enable static linking and the `turbojpeg` build tag for scan2drive in your\n   instance config (use `gok edit`):\n\n```json\n{\n    \"Hostname\": \"scanner\",\n    \"Packages\": [\n        \"github.com/gokrazy/fbstatus\",\n        \"github.com/gokrazy/hello\",\n        \"github.com/gokrazy/serial-busybox\",\n        \"github.com/gokrazy/breakglass\",\n        \"github.com/stapelberg/scan2drive/cmd/scan2drive\"\n    ],\n    \"PackageConfig\": {\n        \"github.com/stapelberg/scan2drive/cmd/scan2drive\": {\n            \"GoBuildFlags\": [\n                \"-ldflags=-linkmode external -extldflags -static\"\n            ],\n            \"GoBuildTags\": [\n                \"turbojpeg\"\n            ]\n        }\n    }\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstapelberg%2Fscan2drive","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstapelberg%2Fscan2drive","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstapelberg%2Fscan2drive/lists"}