diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml index 16538820d..029eae464 100644 --- a/.github/release-drafter.yml +++ b/.github/release-drafter.yml @@ -1,3 +1,14 @@ +autolabeler: + - label: "bug" + branch: + - '/^fix/' + title: + - "/^fix/i" + - label: "enhancement" + branch: + - '/^feature/' + title: + - "/^feature/i" categories: - title: 'Breaking Changes' labels: @@ -15,9 +26,15 @@ categories: - 'chore' - 'deployment' - 'translation' + - 'ci-cd' - title: 'Dependencies' collapse-after: 3 label: 'dependencies' + - title: 'All App Changes' + labels: + - 'frontend' + - 'backend' + collapse-after: 0 include-labels: - 'enhancement' - 'bug' @@ -25,11 +42,12 @@ include-labels: - 'deployment' - 'translation' - 'dependencies' -replacers: # Changes "Feature: Update checker" to "Update checker" - - search: '/Feature:|Feat:|\[feature\]/gi' - replace: '' + - 'documentation' + - 'frontend' + - 'backend' + - 'ci-cd' category-template: '### $TITLE' -change-template: '- $TITLE [@$AUTHOR](https://github.com/$AUTHOR) ([#$NUMBER]($URL))' +change-template: '- $TITLE @$AUTHOR ([#$NUMBER]($URL))' change-title-escapes: '\<*_&#@' template: | ## paperless-ngx $RESOLVED_VERSION diff --git a/.github/scripts/cleanup-tags.py b/.github/scripts/cleanup-tags.py index 023030b5d..904f44346 100644 --- a/.github/scripts/cleanup-tags.py +++ b/.github/scripts/cleanup-tags.py @@ -1,167 +1,41 @@ #!/usr/bin/env python3 -import functools import json import logging import os -import re import shutil import subprocess from argparse import ArgumentParser from typing import Dict from typing import Final from typing import List -from urllib.parse import quote -import requests from common import get_log_level +from github import ContainerPackage +from github import GithubBranchApi +from github import GithubContainerRegistryApi logger = logging.getLogger("cleanup-tags") -class ContainerPackage: - def __init__(self, data: Dict): +class DockerManifest2: + """ + Data class wrapping the Docker Image Manifest Version 2. + + See https://docs.docker.com/registry/spec/manifest-v2-2/ + """ + + def __init__(self, data: Dict) -> None: self._data = data - self.name = self._data["name"] - self.id = self._data["id"] - self.url = self._data["url"] - self.tags = self._data["metadata"]["container"]["tags"] - - @functools.cached_property - def untagged(self) -> bool: - return len(self.tags) == 0 - - @functools.cache - def tag_matches(self, pattern: str) -> bool: - for tag in self.tags: - if re.match(pattern, tag) is not None: - return True - return False - - def __repr__(self): - return f"Package {self.name}" - - -class GithubContainerRegistry: - def __init__( - self, - session: requests.Session, - token: str, - owner_or_org: str, - ): - self._session: requests.Session = session - self._token = token - self._owner_or_org = owner_or_org - # https://docs.github.com/en/rest/branches/branches - self._BRANCHES_ENDPOINT = "https://api.github.com/repos/{OWNER}/{REPO}/branches" - if self._owner_or_org == "paperless-ngx": - # https://docs.github.com/en/rest/packages#get-all-package-versions-for-a-package-owned-by-an-organization - self._PACKAGES_VERSIONS_ENDPOINT = "https://api.github.com/orgs/{ORG}/packages/{PACKAGE_TYPE}/{PACKAGE_NAME}/versions" - # https://docs.github.com/en/rest/packages#delete-package-version-for-an-organization - self._PACKAGE_VERSION_DELETE_ENDPOINT = "https://api.github.com/orgs/{ORG}/packages/{PACKAGE_TYPE}/{PACKAGE_NAME}/versions/{PACKAGE_VERSION_ID}" - else: - # https://docs.github.com/en/rest/packages#get-all-package-versions-for-a-package-owned-by-the-authenticated-user - self._PACKAGES_VERSIONS_ENDPOINT = "https://api.github.com/user/packages/{PACKAGE_TYPE}/{PACKAGE_NAME}/versions" - # https://docs.github.com/en/rest/packages#delete-a-package-version-for-the-authenticated-user - self._PACKAGE_VERSION_DELETE_ENDPOINT = "https://api.github.com/user/packages/{PACKAGE_TYPE}/{PACKAGE_NAME}/versions/{PACKAGE_VERSION_ID}" - - def __enter__(self): - """ - Sets up the required headers for auth and response - type from the API - """ - self._session.headers.update( - { - "Accept": "application/vnd.github.v3+json", - "Authorization": f"token {self._token}", - }, + # This is the sha256: digest string. Corresponds to Github API name + # if the package is an untagged package + self.digest = self._data["digest"] + platform_data_os = self._data["platform"]["os"] + platform_arch = self._data["platform"]["architecture"] + platform_variant = self._data["platform"].get( + "variant", + "", ) - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - """ - Ensures the authorization token is cleaned up no matter - the reason for the exit - """ - if "Accept" in self._session.headers: - del self._session.headers["Accept"] - if "Authorization" in self._session.headers: - del self._session.headers["Authorization"] - - def _read_all_pages(self, endpoint): - """ - Internal function to read all pages of an endpoint, utilizing the - next.url until exhausted - """ - internal_data = [] - - while True: - resp = self._session.get(endpoint) - if resp.status_code == 200: - internal_data += resp.json() - if "next" in resp.links: - endpoint = resp.links["next"]["url"] - else: - logger.debug("Exiting pagination loop") - break - else: - logger.warning(f"Request to {endpoint} return HTTP {resp.status_code}") - break - - return internal_data - - def get_branches(self, repo: str): - """ - Returns all current branches of the given repository - """ - endpoint = self._BRANCHES_ENDPOINT.format(OWNER=self._owner_or_org, REPO=repo) - internal_data = self._read_all_pages(endpoint) - return internal_data - - def filter_branches_by_name_pattern(self, branch_data, pattern: str): - """ - Filters the given list of branches to those which start with the given - pattern. Future enhancement could use regex patterns instead. - """ - matches = {} - - for branch in branch_data: - if branch["name"].startswith(pattern): - matches[branch["name"]] = branch - - return matches - - def get_package_versions( - self, - package_name: str, - package_type: str = "container", - ) -> List[ContainerPackage]: - """ - Returns all the versions of a given package (container images) from - the API - """ - package_name = quote(package_name, safe="") - endpoint = self._PACKAGES_VERSIONS_ENDPOINT.format( - ORG=self._owner_or_org, - PACKAGE_TYPE=package_type, - PACKAGE_NAME=package_name, - ) - - pkgs = [] - - for data in self._read_all_pages(endpoint): - pkgs.append(ContainerPackage(data)) - - return pkgs - - def delete_package_version(self, package_data: ContainerPackage): - """ - Deletes the given package version from the GHCR - """ - resp = self._session.delete(package_data.url) - if resp.status_code != 204: - logger.warning( - f"Request to delete {package_data.url} returned HTTP {resp.status_code}", - ) + self.platform = f"{platform_data_os}/{platform_arch}{platform_variant}" def _main(): @@ -187,6 +61,15 @@ def _main(): help="If provided, delete untagged containers as well", ) + # If given, the package is assumed to be a multi-arch manifest. Cache packages are + # not multi-arch, all other types are + parser.add_argument( + "--is-manifest", + action="store_true", + default=False, + help="If provided, the package is assumed to be a multi-arch manifest following schema v2", + ) + # Allows configuration of log level for debugging parser.add_argument( "--loglevel", @@ -194,6 +77,12 @@ def _main(): help="Configures the logging level", ) + # Get the name of the package being processed this round + parser.add_argument( + "package", + help="The package to process", + ) + args = parser.parse_args() logging.basicConfig( @@ -207,181 +96,194 @@ def _main(): repo: Final[str] = os.environ["GITHUB_REPOSITORY"] gh_token: Final[str] = os.environ["TOKEN"] - with requests.session() as sess: - with GithubContainerRegistry(sess, gh_token, repo_owner) as gh_api: + # Find all branches named feature-* + # Note: Only relevant to the main application, but simpler to + # leave in for all packages + with GithubBranchApi(gh_token) as branch_api: + feature_branches = {} + for branch in branch_api.get_branches( + repo=repo, + ): + if branch.name.startswith("feature-"): + logger.debug(f"Found feature branch {branch.name}") + feature_branches[branch.name] = branch - # Step 1 - Get branch information + logger.info(f"Located {len(feature_branches)} feature branches") - # Step 1.1 - Locate all branches of the repo - all_branches = gh_api.get_branches("paperless-ngx") - logger.info(f"Located {len(all_branches)} branches of {repo_owner}/{repo} ") + with GithubContainerRegistryApi(gh_token, repo_owner) as container_api: + # Get the information about all versions of the given package + all_package_versions: List[ + ContainerPackage + ] = container_api.get_package_versions(args.package) - # Step 1.2 - Filter branches to those starting with "feature-" - feature_branches = gh_api.filter_branches_by_name_pattern( - all_branches, - "feature-", - ) - logger.info(f"Located {len(feature_branches)} feature branches") + all_pkgs_tags_to_version: Dict[str, ContainerPackage] = {} + for pkg in all_package_versions: + for tag in pkg.tags: + all_pkgs_tags_to_version[tag] = pkg + logger.info( + f"Located {len(all_package_versions)} versions of package {args.package}", + ) - # Step 2 - Deal with package information - for package_name in ["paperless-ngx", "paperless-ngx/builder/cache/app"]: + # Filter to packages which are tagged with feature-* + packages_tagged_feature: List[ContainerPackage] = [] + for package in all_package_versions: + if package.tag_matches("feature-"): + packages_tagged_feature.append(package) - # Step 2.1 - Location all versions of the given package - all_package_versions = gh_api.get_package_versions(package_name) + feature_pkgs_tags_to_versions: Dict[str, ContainerPackage] = {} + for pkg in packages_tagged_feature: + for tag in pkg.tags: + feature_pkgs_tags_to_versions[tag] = pkg - # Faster lookup, map the tag to their container - all_pkgs_tags_to_version = {} - for pkg in all_package_versions: - for tag in pkg.tags: - all_pkgs_tags_to_version[tag] = pkg + logger.info( + f'Located {len(feature_pkgs_tags_to_versions)} versions of package {args.package} tagged "feature-"', + ) + + # All the feature tags minus all the feature branches leaves us feature tags + # with no corresponding branch + tags_to_delete = list( + set(feature_pkgs_tags_to_versions.keys()) - set(feature_branches.keys()), + ) + + # All the tags minus the set of going to be deleted tags leaves us the + # tags which will be kept around + tags_to_keep = list( + set(all_pkgs_tags_to_version.keys()) - set(tags_to_delete), + ) + logger.info( + f"Located {len(tags_to_delete)} versions of package {args.package} to delete", + ) + + # Delete certain package versions for which no branch existed + for tag_to_delete in tags_to_delete: + package_version_info = feature_pkgs_tags_to_versions[tag_to_delete] + + if args.delete: logger.info( - f"Located {len(all_package_versions)} versions of package {package_name}", + f"Deleting {tag_to_delete} (id {package_version_info.id})", + ) + container_api.delete_package_version( + package_version_info, ) - # Step 2.2 - Location package versions which have a tag of "feature-" - packages_tagged_feature = [] + else: + logger.info( + f"Would delete {tag_to_delete} (id {package_version_info.id})", + ) + + # Deal with untagged package versions + if args.untagged: + + logger.info("Handling untagged image packages") + + if not args.is_manifest: + # If the package is not a multi-arch manifest, images without tags are safe to delete. + # They are not referred to by anything. This will leave all with at least 1 tag + for package in all_package_versions: - if package.tag_matches("feature-"): - packages_tagged_feature.append(package) - - logger.info( - f'Located {len(packages_tagged_feature)} versions of package {package_name} tagged "feature-"', - ) - - # Faster lookup, map feature- tags to their container - feature_pkgs_tags_to_versions = {} - for pkg in packages_tagged_feature: - for tag in pkg.tags: - feature_pkgs_tags_to_versions[tag] = pkg - - # Step 2.3 - Determine which package versions have no matching branch and which tags we're keeping - tags_to_delete = list( - set(feature_pkgs_tags_to_versions.keys()) - - set(feature_branches.keys()), - ) - tags_to_keep = list( - set(all_pkgs_tags_to_version.keys()) - set(tags_to_delete), - ) - logger.info( - f"Located {len(tags_to_delete)} versions of package {package_name} to delete", - ) - - # Step 2.4 - Delete certain package versions - for tag_to_delete in tags_to_delete: - package_version_info = feature_pkgs_tags_to_versions[tag_to_delete] - - if args.delete: - logger.info( - f"Deleting {tag_to_delete} (id {package_version_info.id})", - ) - gh_api.delete_package_version( - package_version_info, - ) - - else: - logger.info( - f"Would delete {tag_to_delete} (id {package_version_info.id})", - ) - - # Step 3 - Deal with untagged and dangling packages - if args.untagged: - - """ - Ok, bear with me, these are annoying. - - Our images are multi-arch, so the manifest is more like a pointer to a sha256 digest. - These images are untagged, but pointed to, and so should not be removed (or every pull fails). - - So for each image getting kept, parse the manifest to find the digest(s) it points to. Then - remove those from the list of untagged images. The final result is the untagged, not pointed to - version which should be safe to remove. - - Example: - Tag: ghcr.io/paperless-ngx/paperless-ngx:1.7.1 refers to - amd64: sha256:b9ed4f8753bbf5146547671052d7e91f68cdfc9ef049d06690b2bc866fec2690 - armv7: sha256:81605222df4ba4605a2ba4893276e5d08c511231ead1d5da061410e1bbec05c3 - arm64: sha256:374cd68db40734b844705bfc38faae84cc4182371de4bebd533a9a365d5e8f3b - each of which appears as untagged image - - """ - - # Step 3.1 - Simplify the untagged data, mapping name (which is a digest) to the version - untagged_versions = {} - for x in all_package_versions: - if x.untagged: - untagged_versions[x.name] = x - - skips = 0 - # Extra security to not delete on an unexpected error - actually_delete = True - - logger.info( - f"Located {len(tags_to_keep)} tags of package {package_name} to keep", - ) - - # Step 3.2 - Parse manifests to locate digests pointed to - for tag in tags_to_keep: - full_name = f"ghcr.io/{repo_owner}/{package_name}:{tag}" - logger.info(f"Checking manifest for {full_name}") - try: - proc = subprocess.run( - [ - shutil.which("docker"), - "manifest", - "inspect", - full_name, - ], - capture_output=True, - ) - - manifest_list = json.loads(proc.stdout) - for manifest in manifest_list["manifests"]: - digest = manifest["digest"] - platform_data_os = manifest["platform"]["os"] - platform_arch = manifest["platform"]["architecture"] - platform_variant = manifest["platform"].get( - "variant", - "", - ) - platform = f"{platform_data_os}/{platform_arch}{platform_variant}" - - if digest in untagged_versions: - logger.debug( - f"Skipping deletion of {digest}, referred to by {full_name} for {platform}", - ) - del untagged_versions[digest] - skips += 1 - - except json.decoder.JSONDecodeError as err: - # This is probably for a cache image, which isn't a multi-arch digest - # These are ok to delete all on - logger.debug(f"{err} on {full_name}") - continue - except Exception as err: - actually_delete = False - logger.exception(err) - continue - - logger.info(f"Skipping deletion of {skips} packages") - - # Step 3.3 - Delete the untagged and not pointed at packages - logger.info(f"Deleting untagged packages of {package_name}") - for to_delete_name in untagged_versions: - to_delete_version = untagged_versions[to_delete_name] - - if args.delete and actually_delete: + if package.untagged: + if args.delete: logger.info( - f"Deleting id {to_delete_version.id} named {to_delete_version.name}", + f"Deleting id {package.id} named {package.name}", ) - gh_api.delete_package_version( - to_delete_version, + container_api.delete_package_version( + package, ) else: logger.info( - f"Would delete {to_delete_name} (id {to_delete_version.id})", + f"Would delete {package.name} (id {package.id})", ) - else: - logger.info("Leaving untagged images untouched") + else: + logger.info( + f"Not deleting tag {package.tags[0]} of package {args.package}", + ) + else: + + """ + Ok, bear with me, these are annoying. + + Our images are multi-arch, so the manifest is more like a pointer to a sha256 digest. + These images are untagged, but pointed to, and so should not be removed (or every pull fails). + + So for each image getting kept, parse the manifest to find the digest(s) it points to. Then + remove those from the list of untagged images. The final result is the untagged, not pointed to + version which should be safe to remove. + + Example: + Tag: ghcr.io/paperless-ngx/paperless-ngx:1.7.1 refers to + amd64: sha256:b9ed4f8753bbf5146547671052d7e91f68cdfc9ef049d06690b2bc866fec2690 + armv7: sha256:81605222df4ba4605a2ba4893276e5d08c511231ead1d5da061410e1bbec05c3 + arm64: sha256:374cd68db40734b844705bfc38faae84cc4182371de4bebd533a9a365d5e8f3b + each of which appears as untagged image, but isn't really. + + So from the list of untagged packages, remove those digests. Once all tags which + are being kept are checked, the remaining untagged packages are actually untagged + with no referrals in a manifest to them. + + """ + + # Simplify the untagged data, mapping name (which is a digest) to the version + untagged_versions = {} + for x in all_package_versions: + if x.untagged: + untagged_versions[x.name] = x + + skips = 0 + # Extra security to not delete on an unexpected error + actually_delete = True + + # Parse manifests to locate digests pointed to + for tag in sorted(tags_to_keep): + full_name = f"ghcr.io/{repo_owner}/{args.package}:{tag}" + logger.info(f"Checking manifest for {full_name}") + try: + proc = subprocess.run( + [ + shutil.which("docker"), + "manifest", + "inspect", + full_name, + ], + capture_output=True, + ) + + manifest_list = json.loads(proc.stdout) + for manifest_data in manifest_list["manifests"]: + manifest = DockerManifest2(manifest_data) + + if manifest.digest in untagged_versions: + logger.debug( + f"Skipping deletion of {manifest.digest}, referred to by {full_name} for {manifest.platform}", + ) + del untagged_versions[manifest.digest] + skips += 1 + + except Exception as err: + actually_delete = False + logger.exception(err) + + logger.info( + f"Skipping deletion of {skips} packages referred to by a manifest", + ) + + # Step 3.3 - Delete the untagged and not pointed at packages + logger.info(f"Deleting untagged packages of {args.package}") + for to_delete_name in untagged_versions: + to_delete_version = untagged_versions[to_delete_name] + + if args.delete and actually_delete: + logger.info( + f"Deleting id {to_delete_version.id} named {to_delete_version.name}", + ) + container_api.delete_package_version( + to_delete_version, + ) + else: + logger.info( + f"Would delete {to_delete_name} (id {to_delete_version.id})", + ) + else: + logger.info("Leaving untagged images untouched") if __name__ == "__main__": diff --git a/.github/scripts/common.py b/.github/scripts/common.py index 62f58aa1c..1e130eae0 100644 --- a/.github/scripts/common.py +++ b/.github/scripts/common.py @@ -1,6 +1,5 @@ #!/usr/bin/env python3 import logging -from argparse import ArgumentError def get_image_tag( diff --git a/.github/scripts/github.py b/.github/scripts/github.py new file mode 100644 index 000000000..4059f89d0 --- /dev/null +++ b/.github/scripts/github.py @@ -0,0 +1,227 @@ +#!/usr/bin/env python3 +""" +This module contains some useful classes for interacting with the Github API. +The full documentation for the API can be found here: https://docs.github.com/en/rest + +Mostly, this focusses on two areas, repo branches and repo packages, as the use case +is cleaning up container images which are no longer referred to. + +""" +import functools +import logging +import re +import urllib.parse +from typing import Dict +from typing import List +from typing import Optional + +import requests + +logger = logging.getLogger("github-api") + + +class _GithubApiBase: + """ + A base class for interacting with the Github API. It + will handle the session and setting authorization headers. + """ + + def __init__(self, token: str) -> None: + self._token = token + self._session: Optional[requests.Session] = None + + def __enter__(self) -> "_GithubApiBase": + """ + Sets up the required headers for auth and response + type from the API + """ + self._session = requests.Session() + self._session.headers.update( + { + "Accept": "application/vnd.github.v3+json", + "Authorization": f"token {self._token}", + }, + ) + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + """ + Ensures the authorization token is cleaned up no matter + the reason for the exit + """ + if "Accept" in self._session.headers: + del self._session.headers["Accept"] + if "Authorization" in self._session.headers: + del self._session.headers["Authorization"] + + # Close the session as well + self._session.close() + self._session = None + + def _read_all_pages(self, endpoint): + """ + Helper function to read all pages of an endpoint, utilizing the + next.url until exhausted. Assumes the endpoint returns a list + """ + internal_data = [] + + while True: + resp = self._session.get(endpoint) + if resp.status_code == 200: + internal_data += resp.json() + if "next" in resp.links: + endpoint = resp.links["next"]["url"] + else: + logger.debug("Exiting pagination loop") + break + else: + logger.warning(f"Request to {endpoint} return HTTP {resp.status_code}") + break + + return internal_data + + +class _EndpointResponse: + """ + For all endpoint JSON responses, store the full + response data, for ease of extending later, if need be. + """ + + def __init__(self, data: Dict) -> None: + self._data = data + + +class GithubBranch(_EndpointResponse): + """ + Simple wrapper for a repository branch, only extracts name information + for now. + """ + + def __init__(self, data: Dict) -> None: + super().__init__(data) + self.name = self._data["name"] + + +class GithubBranchApi(_GithubApiBase): + """ + Wrapper around branch API. + + See https://docs.github.com/en/rest/branches/branches + + """ + + def __init__(self, token: str) -> None: + super().__init__(token) + + self._ENDPOINT = "https://api.github.com/repos/{REPO}/branches" + + def get_branches(self, repo: str) -> List[GithubBranch]: + """ + Returns all current branches of the given repository owned by the given + owner or organization. + """ + endpoint = self._ENDPOINT.format(REPO=repo) + internal_data = self._read_all_pages(endpoint) + return [GithubBranch(branch) for branch in internal_data] + + +class ContainerPackage(_EndpointResponse): + """ + Data class wrapping the JSON response from the package related + endpoints + """ + + def __init__(self, data: Dict): + super().__init__(data) + # This is a numerical ID, required for interactions with this + # specific package, including deletion of it or restoration + self.id: int = self._data["id"] + + # A string name. This might be an actual name or it could be a + # digest string like "sha256:" + self.name: str = self._data["name"] + + # URL to the package, including its ID, can be used for deletion + # or restoration without needing to build up a URL ourselves + self.url: str = self._data["url"] + + # The list of tags applied to this image. Maybe an empty list + self.tags: List[str] = self._data["metadata"]["container"]["tags"] + + @functools.cached_property + def untagged(self) -> bool: + """ + Returns True if the image has no tags applied to it, False otherwise + """ + return len(self.tags) == 0 + + @functools.cache + def tag_matches(self, pattern: str) -> bool: + """ + Returns True if the image has at least one tag which matches the given regex, + False otherwise + """ + for tag in self.tags: + if re.match(pattern, tag) is not None: + return True + return False + + def __repr__(self): + return f"Package {self.name}" + + +class GithubContainerRegistryApi(_GithubApiBase): + """ + Class wrapper to deal with the Github packages API. This class only deals with + container type packages, the only type published by paperless-ngx. + """ + + def __init__(self, token: str, owner_or_org: str) -> None: + super().__init__(token) + self._owner_or_org = owner_or_org + if self._owner_or_org == "paperless-ngx": + # https://docs.github.com/en/rest/packages#get-all-package-versions-for-a-package-owned-by-an-organization + self._PACKAGES_VERSIONS_ENDPOINT = "https://api.github.com/orgs/{ORG}/packages/{PACKAGE_TYPE}/{PACKAGE_NAME}/versions" + # https://docs.github.com/en/rest/packages#delete-package-version-for-an-organization + self._PACKAGE_VERSION_DELETE_ENDPOINT = "https://api.github.com/orgs/{ORG}/packages/{PACKAGE_TYPE}/{PACKAGE_NAME}/versions/{PACKAGE_VERSION_ID}" + else: + # https://docs.github.com/en/rest/packages#get-all-package-versions-for-a-package-owned-by-the-authenticated-user + self._PACKAGES_VERSIONS_ENDPOINT = "https://api.github.com/user/packages/{PACKAGE_TYPE}/{PACKAGE_NAME}/versions" + # https://docs.github.com/en/rest/packages#delete-a-package-version-for-the-authenticated-user + self._PACKAGE_VERSION_DELETE_ENDPOINT = "https://api.github.com/user/packages/{PACKAGE_TYPE}/{PACKAGE_NAME}/versions/{PACKAGE_VERSION_ID}" + + def get_package_versions( + self, + package_name: str, + ) -> List[ContainerPackage]: + """ + Returns all the versions of a given package (container images) from + the API + """ + + package_type: str = "container" + # Need to quote this for slashes in the name + package_name = urllib.parse.quote(package_name, safe="") + + endpoint = self._PACKAGES_VERSIONS_ENDPOINT.format( + ORG=self._owner_or_org, + PACKAGE_TYPE=package_type, + PACKAGE_NAME=package_name, + ) + + pkgs = [] + + for data in self._read_all_pages(endpoint): + pkgs.append(ContainerPackage(data)) + + return pkgs + + def delete_package_version(self, package_data: ContainerPackage): + """ + Deletes the given package version from the GHCR + """ + resp = self._session.delete(package_data.url) + if resp.status_code != 204: + logger.warning( + f"Request to delete {package_data.url} returned HTTP {resp.status_code}", + ) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bdb98741d..ddf23e253 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -452,7 +452,7 @@ jobs: - name: Create Release and Changelog id: create-release - uses: release-drafter/release-drafter@v5 + uses: paperless-ngx/release-drafter@master with: name: Paperless-ngx ${{ steps.get_version.outputs.version }} tag: ${{ steps.get_version.outputs.version }} @@ -492,6 +492,8 @@ jobs: git branch ${{ needs.publish-release.outputs.version }}-changelog git checkout ${{ needs.publish-release.outputs.version }}-changelog echo -e "# Changelog\n\n${{ needs.publish-release.outputs.changelog }}\n" > changelog-new.md + echo "Manually linking usernames" + sed -i -r 's|@(.+?) \(\[#|[@\1](https://github.com/\1) ([#|ig' changelog-new.md CURRENT_CHANGELOG=`tail --lines +2 changelog.md` echo -e "$CURRENT_CHANGELOG" >> changelog-new.md mv changelog-new.md changelog.md diff --git a/.github/workflows/cleanup-tags.yml b/.github/workflows/cleanup-tags.yml index 097badaaa..55841e7f0 100644 --- a/.github/workflows/cleanup-tags.yml +++ b/.github/workflows/cleanup-tags.yml @@ -16,6 +16,7 @@ on: paths: - ".github/workflows/cleanup-tags.yml" - ".github/scripts/cleanup-tags.py" + - ".github/scripts/github.py" - ".github/scripts/common.py" jobs: @@ -45,14 +46,63 @@ jobs: name: Install requests run: | python -m pip install requests + # Clean up primary packages - - name: Cleanup feature tags - # Only run if the token is not empty + name: Cleanup for package "paperless-ngx" if: "${{ env.TOKEN != '' }}" run: | - python ${GITHUB_WORKSPACE}/.github/scripts/cleanup-tags.py --loglevel info --untagged --delete + python ${GITHUB_WORKSPACE}/.github/scripts/cleanup-tags.py --loglevel info --untagged --is-manifest --delete "paperless-ngx" + - + name: Cleanup for package "qpdf" + if: "${{ env.TOKEN != '' }}" + run: | + python ${GITHUB_WORKSPACE}/.github/scripts/cleanup-tags.py --loglevel info --untagged --is-manifest --delete "paperless-ngx/builder/qpdf" + - + name: Cleanup for package "pikepdf" + if: "${{ env.TOKEN != '' }}" + run: | + python ${GITHUB_WORKSPACE}/.github/scripts/cleanup-tags.py --loglevel info --untagged --is-manifest --delete "paperless-ngx/builder/pikepdf" + - + name: Cleanup for package "jbig2enc" + if: "${{ env.TOKEN != '' }}" + run: | + python ${GITHUB_WORKSPACE}/.github/scripts/cleanup-tags.py --loglevel info --untagged --is-manifest --delete "paperless-ngx/builder/jbig2enc" + - + name: Cleanup for package "psycopg2" + if: "${{ env.TOKEN != '' }}" + run: | + python ${GITHUB_WORKSPACE}/.github/scripts/cleanup-tags.py --loglevel info --untagged --is-manifest --delete "paperless-ngx/builder/psycopg2" + # + # Clean up registry cache packages + # + - + name: Cleanup for package "builder/cache/app" + if: "${{ env.TOKEN != '' }}" + run: | + python ${GITHUB_WORKSPACE}/.github/scripts/cleanup-tags.py --loglevel info --untagged --delete "paperless-ngx/builder/cache/app" + - + name: Cleanup for package "builder/cache/qpdf" + if: "${{ env.TOKEN != '' }}" + run: | + python ${GITHUB_WORKSPACE}/.github/scripts/cleanup-tags.py --loglevel info --untagged --delete "paperless-ngx/builder/cache/qpdf" + - + name: Cleanup for package "builder/cache/psycopg2" + if: "${{ env.TOKEN != '' }}" + run: | + python ${GITHUB_WORKSPACE}/.github/scripts/cleanup-tags.py --loglevel info --untagged --delete "paperless-ngx/builder/cache/psycopg2" + - + name: Cleanup for package "builder/cache/jbig2enc" + if: "${{ env.TOKEN != '' }}" + run: | + python ${GITHUB_WORKSPACE}/.github/scripts/cleanup-tags.py --loglevel info --untagged --delete "paperless-ngx/builder/cache/jbig2enc" + - + name: Cleanup for package "builder/cache/pikepdf" + if: "${{ env.TOKEN != '' }}" + run: | + python ${GITHUB_WORKSPACE}/.github/scripts/cleanup-tags.py --loglevel info --untagged --delete "paperless-ngx/builder/cache/pikepdf" - name: Check all tags still pull run: | ghcr_name=$(echo "${GITHUB_REPOSITORY}" | awk '{ print tolower($0) }') + echo "Pulling all tags of ghcr.io/${ghcr_name}" docker pull --quiet --all-tags ghcr.io/${ghcr_name} diff --git a/.github/workflows/project-actions.yml b/.github/workflows/project-actions.yml index d52f6d06a..0ec168a7a 100644 --- a/.github/workflows/project-actions.yml +++ b/.github/workflows/project-actions.yml @@ -13,6 +13,9 @@ on: - main - dev +permissions: + contents: read + env: todo: Todo done: Done @@ -24,7 +27,7 @@ jobs: runs-on: ubuntu-latest if: github.event_name == 'issues' && (github.event.action == 'opened' || github.event.action == 'reopened') steps: - - name: Set issue status to ${{ env.todo }} + - name: Add issue to project and set status to ${{ env.todo }} uses: leonsteinhaeuser/project-beta-automations@v1.3.0 with: gh_token: ${{ secrets.GH_TOKEN }} @@ -35,13 +38,20 @@ jobs: pr_opened_or_reopened: name: pr_opened_or_reopened runs-on: ubuntu-latest - if: github.event_name == 'pull_request_target' && (github.event.action == 'opened' || github.event.action == 'reopened') + permissions: + # write permission is required for autolabeler + pull-requests: write + if: github.event_name == 'pull_request_target' && (github.event.action == 'opened' || github.event.action == 'reopened') && github.event.pull_request.user.login != 'dependabot' steps: - - name: Set PR status to ${{ env.in_progress }} + - name: Add PR to project and set status to "Needs Review" uses: leonsteinhaeuser/project-beta-automations@v1.3.0 with: gh_token: ${{ secrets.GH_TOKEN }} organization: paperless-ngx project_id: 2 resource_node_id: ${{ github.event.pull_request.node_id }} - status_value: ${{ env.in_progress }} # Target status + status_value: "Needs Review" # Target status + - name: Label PR with release-drafter + uses: release-drafter/release-drafter@v5 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/Pipfile b/Pipfile index 48b855f1a..ef5212f50 100644 --- a/Pipfile +++ b/Pipfile @@ -33,6 +33,8 @@ redis = "*" scikit-learn = "~=1.1" # Pin this until piwheels is building 1.9 (see https://www.piwheels.org/project/scipy/) scipy = "==1.8.1" +# https://github.com/paperless-ngx/paperless-ngx/issues/1364 +numpy = "==1.22.3" whitenoise = "~=6.2" watchdog = "~=2.1" whoosh="~=2.7" @@ -51,8 +53,8 @@ concurrent-log-handler = "*" "importlib-resources" = {version = "*", markers = "python_version < '3.9'"} zipp = {version = "*", markers = "python_version < '3.9'"} pyzbar = "*" -pdf2image = "*" mysqlclient = "*" +setproctitle = "*" [dev-packages] coveralls = "*" diff --git a/Pipfile.lock b/Pipfile.lock index 651ae4825..98499df9e 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "af0a0a5b996c11ad95266e98200640fd77648ec0f337ac3eb4e4b5ca1c714a0e" + "sha256": "896665b8ff6d8a99af44b729c581033add1ba5cbd927723ef275649491c92a4f" }, "pipfile-spec": 6, "requires": {}, @@ -39,7 +39,7 @@ "sha256:3934b30ca1b9f292376d9db15b19446088d12ec58629bc3f0da28fd55fb633a1", "sha256:5a49ab92e3b7b71d96cd6bfcc4df14efefc9dfa96ea19045815914a6ab6b1fe2" ], - "markers": "python_full_version >= '3.6.0'", + "markers": "python_version >= '3.6'", "version": "==1.2.3" }, "asgiref": { @@ -55,7 +55,7 @@ "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15", "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c" ], - "markers": "python_full_version >= '3.6.0'", + "markers": "python_version >= '3.6'", "version": "==4.0.2" }, "attrs": { @@ -208,7 +208,7 @@ "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845", "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f" ], - "markers": "python_full_version >= '3.6.0'", + "markers": "python_version >= '3.6'", "version": "==2.1.1" }, "click": { @@ -271,7 +271,7 @@ "sha256:d4ef6cc305394ed669d4d9eebf10d3a101059bdcf2669c366ec1d14e4fb227bd", "sha256:d9e69ae01f99abe6ad646947bba8941e896cb3aa805be2597a0400e0764b5818" ], - "markers": "python_full_version >= '3.6.0'", + "markers": "python_version >= '3.6'", "version": "==38.0.1" }, "daphne": { @@ -279,7 +279,7 @@ "sha256:76ffae916ba3aa66b46996c14fa713e46004788167a4873d647544e750e0e99f", "sha256:a9af943c79717bc52fe64a3c236ae5d3adccc8b5be19c881b442d2c3db233393" ], - "markers": "python_full_version >= '3.6.0'", + "markers": "python_version >= '3.6'", "version": "==3.0.2" }, "dateparser": { @@ -348,7 +348,7 @@ "django-q": { "editable": true, "git": "https://github.com/paperless-ngx/django-q.git", - "ref": "21ef9116f6386cf985ccd812dde4111efcfd1ba9" + "ref": "8b5289d8caf36f67fb99448e76ead20d5b498c1b" }, "djangorestframework": { "hashes": [ @@ -390,7 +390,7 @@ "sha256:70813c1135087a248a4d38cc0e1a0181ffab2188141a93eaf567940c3957ff06", "sha256:8ddd78563b633ca55346c8cd41ec0af27d3c79931828beffb46ce70a379e7442" ], - "markers": "python_full_version >= '3.6.0'", + "markers": "python_version >= '3.6'", "version": "==0.13.0" }, "hiredis": { @@ -437,7 +437,7 @@ "sha256:f52010e0a44e3d8530437e7da38d11fb822acfb0d5b12e9cd5ba655509937ca0", "sha256:f8196f739092a78e4f6b1b2172679ed3343c39c61a3e9d722ce6fcf1dac2824a" ], - "markers": "python_full_version >= '3.6.0'", + "markers": "python_version >= '3.6'", "version": "==2.0.0" }, "httptools": { @@ -553,7 +553,7 @@ "sha256:4158fcecd13733f8be669be0683b96ebdbbd38d23559f54dca7205aea1bf1e35", "sha256:f21f109b3c7ff9d95f8387f752d0d9c34a02aa2f7060c2135f465da0e5160ff6" ], - "markers": "python_full_version >= '3.6.0'", + "markers": "python_version >= '3.6'", "version": "==1.1.0" }, "langdetect": { @@ -712,37 +712,57 @@ }, "numpy": { "hashes": [ + "sha256:07a8c89a04997625236c5ecb7afe35a02af3896c8aa01890a849913a2309c676", + "sha256:08d9b008d0156c70dc392bb3ab3abb6e7a711383c3247b410b39962263576cd4", "sha256:17e5226674f6ea79e14e3b91bfbc153fdf3ac13f5cc54ee7bc8fdbe820a32da0", + "sha256:201b4d0552831f7250a08d3b38de0d989d6f6e4658b709a02a73c524ccc6ffce", "sha256:2bd879d3ca4b6f39b7770829f73278b7c5e248c91d538aab1e506c628353e47f", + "sha256:2c10a93606e0b4b95c9b04b77dc349b398fdfbda382d2a39ba5a822f669a0123", + "sha256:3ca688e1b9b95d80250bca34b11a05e389b1420d00e87a0d12dc45f131f704a1", + "sha256:48a3aecd3b997bf452a2dedb11f4e79bc5bfd21a1d4cc760e703c31d57c84b3e", "sha256:4f41f5bf20d9a521f8cab3a34557cd77b6f205ab2116651f12959714494268b0", "sha256:5593f67e66dea4e237f5af998d31a43e447786b2154ba1ad833676c788f37cde", + "sha256:568dfd16224abddafb1cbcce2ff14f522abe037268514dd7e42c6776a1c3f8e5", + "sha256:5bfb1bb598e8229c2d5d48db1860bcf4311337864ea3efdbe1171fb0c5da515d", "sha256:5e28cd64624dc2354a349152599e55308eb6ca95a13ce6a7d5679ebff2962913", "sha256:633679a472934b1c20a12ed0c9a6c9eb167fbb4cb89031939bfd03dd9dbc62b8", + "sha256:639b54cdf6aa4f82fe37ebf70401bbb74b8508fddcf4797f9fe59615b8c5813a", "sha256:806970e69106556d1dd200e26647e9bee5e2b3f1814f9da104a943e8d548ca38", "sha256:806cc25d5c43e240db709875e947076b2826f47c2c340a5a2f36da5bb10c58d6", "sha256:8247f01c4721479e482cc2f9f7d973f3f47810cbc8c65e38fd1bbd3141cc9842", + "sha256:8251ed96f38b47b4295b1ae51631de7ffa8260b5b087808ef09a39a9d66c97ab", "sha256:8ebf7e194b89bc66b78475bd3624d92980fca4e5bb86dda08d677d786fefc414", "sha256:8ecb818231afe5f0f568c81f12ce50f2b828ff2b27487520d85eb44c71313b9e", "sha256:8f9d84a24889ebb4c641a9b99e54adb8cab50972f0166a3abc14c3b93163f074", "sha256:909c56c4d4341ec8315291a105169d8aae732cfb4c250fbc375a1efb7a844f8f", + "sha256:92bfa69cfbdf7dfc3040978ad09a48091143cffb778ec3b03fa170c494118d75", + "sha256:97098b95aa4e418529099c26558eeb8486e66bd1e53a6b606d684d0c3616b168", "sha256:9b83d48e464f393d46e8dd8171687394d39bc5abfe2978896b77dc2604e8635d", + "sha256:a3bae1a2ed00e90b3ba5f7bd0a7c7999b55d609e0c54ceb2b076a25e345fa9f4", "sha256:ac987b35df8c2a2eab495ee206658117e9ce867acf3ccb376a19e83070e69418", "sha256:b78d00e48261fbbd04aa0d7427cf78d18401ee0abd89c7559bbf422e5b1c7d01", "sha256:b8b97a8a87cadcd3f94659b4ef6ec056261fa1e1c3317f4193ac231d4df70215", "sha256:bd5b7ccae24e3d8501ee5563e82febc1771e73bd268eef82a1e8d2b4d556ae66", "sha256:bdc02c0235b261925102b1bd586579b7158e9d0d07ecb61148a1799214a4afd5", "sha256:be6b350dfbc7f708d9d853663772a9310783ea58f6035eec649fb9c4371b5389", + "sha256:c34ea7e9d13a70bf2ab64a2532fe149a9aced424cd05a2c4ba662fd989e3e45f", "sha256:c403c81bb8ffb1c993d0165a11493fd4bf1353d258f6997b3ee288b0a48fce77", "sha256:cf8c6aed12a935abf2e290860af8e77b26a042eb7f2582ff83dc7ed5f963340c", "sha256:d98addfd3c8728ee8b2c49126f3c44c703e2b005d4a95998e2167af176a9e722", + "sha256:dbc7601a3b7472d559dc7b933b18b4b66f9aa7452c120e87dfb33d02008c8a18", "sha256:dc76bca1ca98f4b122114435f83f1fcf3c0fe48e4e6f660e07996abf2f53903c", "sha256:dec198619b7dbd6db58603cd256e092bcadef22a796f778bf87f8592b468441d", "sha256:df28dda02c9328e122661f399f7655cdcbcf22ea42daa3650a26bce08a187450", "sha256:e603ca1fb47b913942f3e660a15e55a9ebca906857edfea476ae5f0fe9b457d5", - "sha256:ecfdd68d334a6b97472ed032b5b37a30d8217c097acfff15e8452c710e775524" + "sha256:e7927a589df200c5e23c57970bafbd0cd322459aa7b1ff73b7c2e84d6e3eae62", + "sha256:ecfdd68d334a6b97472ed032b5b37a30d8217c097acfff15e8452c710e775524", + "sha256:f8c1f39caad2c896bc0018f699882b345b2a63708008be29b1f355ebf6f933fe", + "sha256:f950f8845b480cffe522913d35567e29dd381b0dc7e4ce6a4a9f9156417d2430", + "sha256:fade0d4f4d292b6f39951b6836d7a3c7ef5b2347f3c420cd9820a1d90d794802", + "sha256:fdf3c08bce27132395d3c3ba1503cac12e17282358cb4bddc25cc46b0aca07aa" ], - "markers": "python_version >= '3.8'", - "version": "==1.23.2" + "index": "pypi", + "version": "==1.22.3" }, "ocrmypdf": { "hashes": [ @@ -757,7 +777,7 @@ "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb", "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522" ], - "markers": "python_full_version >= '3.6.0'", + "markers": "python_version >= '3.6'", "version": "==21.3" }, "pathvalidate": { @@ -768,14 +788,6 @@ "index": "pypi", "version": "==2.5.2" }, - "pdf2image": { - "hashes": [ - "sha256:84f79f2b8fad943e36323ea4e937fcb05f26ded0caa0a01181df66049e42fb65", - "sha256:d58ed94d978a70c73c2bb7fdf8acbaf2a7089c29ff8141be5f45433c0c4293bb" - ], - "index": "pypi", - "version": "==1.16.0" - }, "pdfminer.six": { "hashes": [ "sha256:5a64c924410ac48501d6060b21638bf401db69f5b1bd57207df7fbc070ac8ae2", @@ -894,7 +906,7 @@ "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159", "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3" ], - "markers": "python_full_version >= '3.6.0'", + "markers": "python_version >= '3.6'", "version": "==1.0.0" }, "portalocker": { @@ -1035,6 +1047,7 @@ }, "pyyaml": { "hashes": [ + "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf", "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293", "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b", "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57", @@ -1046,26 +1059,32 @@ "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287", "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513", "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0", + "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782", "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0", "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92", "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f", "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2", "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc", + "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1", "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c", "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86", "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4", "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c", "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34", "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b", + "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d", "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c", "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb", + "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7", "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737", "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3", "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d", + "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358", "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53", "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78", "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803", "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a", + "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f", "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174", "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5" ], @@ -1165,7 +1184,7 @@ "sha256:fbc88d3ba402b5d041d204ec2449c4078898f89c4a6e6f0ed1c1a510ef1e221d", "sha256:fbd3fe37353c62fd0eb19fb76f78aa693716262bcd5f9c14bb9e5aca4b3f0dc4" ], - "markers": "python_full_version >= '3.6.0'", + "markers": "python_version >= '3.6'", "version": "==2022.3.2" }, "reportlab": { @@ -1278,6 +1297,72 @@ ], "version": "==21.1.0" }, + "setproctitle": { + "hashes": [ + "sha256:1c5d5dad7c28bdd1ec4187d818e43796f58a845aa892bb4481587010dc4d362b", + "sha256:1c8d9650154afaa86a44ff195b7b10d683c73509d085339d174e394a22cccbb9", + "sha256:1f0cde41857a644b7353a0060b5f94f7ba7cf593ebde5a1094da1be581ac9a31", + "sha256:1f29b75e86260b0ab59adb12661ef9f113d2f93a59951373eb6d68a852b13e83", + "sha256:1fa1a0fbee72b47dc339c87c890d3c03a72ea65c061ade3204f285582f2da30f", + "sha256:1ff863a20d1ff6ba2c24e22436a3daa3cd80be1dfb26891aae73f61b54b04aca", + "sha256:265ecbe2c6eafe82e104f994ddd7c811520acdd0647b73f65c24f51374cf9494", + "sha256:288943dec88e178bb2fd868adf491197cc0fc8b6810416b1c6775e686bab87fe", + "sha256:2e3ac25bfc4a0f29d2409650c7532d5ddfdbf29f16f8a256fc31c47d0dc05172", + "sha256:2fbd8187948284293f43533c150cd69a0e4192c83c377da837dbcd29f6b83084", + "sha256:4058564195b975ddc3f0462375c533cce310ccdd41b80ac9aed641c296c3eff4", + "sha256:4749a2b0c9ac52f864d13cee94546606f92b981b50e46226f7f830a56a9dc8e1", + "sha256:4d8938249a7cea45ab7e1e48b77685d0f2bab1ebfa9dde23e94ab97968996a7c", + "sha256:5194b4969f82ea842a4f6af2f82cd16ebdc3f1771fb2771796e6add9835c1973", + "sha256:55ce1e9925ce1765865442ede9dca0ba9bde10593fcd570b1f0fa25d3ec6b31c", + "sha256:589be87172b238f839e19f146b9ea47c71e413e951ef0dc6db4218ddacf3c202", + "sha256:5b932c3041aa924163f4aab970c2f0e6b4d9d773f4d50326e0ea1cd69240e5c5", + "sha256:5fb4f769c02f63fac90989711a3fee83919f47ae9afd4758ced5d86596318c65", + "sha256:630f6fe5e24a619ccf970c78e084319ee8be5be253ecc9b5b216b0f474f5ef18", + "sha256:65d884e22037b23fa25b2baf1a3316602ed5c5971eb3e9d771a38c3a69ce6e13", + "sha256:6c877691b90026670e5a70adfbcc735460a9f4c274d35ec5e8a43ce3f8443005", + "sha256:710e16fa3bade3b026907e4a5e841124983620046166f355bbb84be364bf2a02", + "sha256:7a55fe05f15c10e8c705038777656fe45e3bd676d49ad9ac8370b75c66dd7cd7", + "sha256:7aa0aac1711fadffc1d51e9d00a3bea61f68443d6ac0241a224e4d622489d665", + "sha256:7f0bed90a216ef28b9d227d8d73e28a8c9b88c0f48a082d13ab3fa83c581488f", + "sha256:7f2719a398e1a2c01c2a63bf30377a34d0b6ef61946ab9cf4d550733af8f1ef1", + "sha256:7fe9df7aeb8c64db6c34fc3b13271a363475d77bc157d3f00275a53910cb1989", + "sha256:8ff3c8cb26afaed25e8bca7b9dd0c1e36de71f35a3a0706b5c0d5172587a3827", + "sha256:9124bedd8006b0e04d4e8a71a0945da9b67e7a4ab88fdad7b1440dc5b6122c42", + "sha256:92c626edc66169a1b09e9541b9c0c9f10488447d8a2b1d87c8f0672e771bc927", + "sha256:a149a5f7f2c5a065d4e63cb0d7a4b6d3b66e6e80f12e3f8827c4f63974cbf122", + "sha256:a47d97a75fd2d10c37410b180f67a5835cb1d8fdea2648fd7f359d4277f180b9", + "sha256:a499fff50387c1520c085a07578a000123f519e5f3eee61dd68e1d301659651f", + "sha256:ab45146c71ca6592c9cc8b354a2cc9cc4843c33efcbe1d245d7d37ce9696552d", + "sha256:b2c9cb2705fc84cb8798f1ba74194f4c080aaef19d9dae843591c09b97678e98", + "sha256:b34baef93bfb20a8ecb930e395ccd2ae3268050d8cf4fe187de5e2bd806fd796", + "sha256:b617f12c9be61e8f4b2857be4a4319754756845dbbbd9c3718f468bbb1e17bcb", + "sha256:b9fb97907c830d260fa0658ed58afd48a86b2b88aac521135c352ff7fd3477fd", + "sha256:bae283e85fc084b18ffeb92e061ff7ac5af9e183c9d1345c93e178c3e5069cbe", + "sha256:c2c46200656280a064073447ebd363937562debef329482fd7e570c8d498f806", + "sha256:c8a09d570b39517de10ee5b718730e171251ce63bbb890c430c725c8c53d4484", + "sha256:c91b9bc8985d00239f7dc08a49927a7ca1ca8a6af2c3890feec3ed9665b6f91e", + "sha256:dad42e676c5261eb50fdb16bdf3e2771cf8f99a79ef69ba88729aeb3472d8575", + "sha256:de3a540cd1817ede31f530d20e6a4935bbc1b145fd8f8cf393903b1e02f1ae76", + "sha256:e00c9d5c541a2713ba0e657e0303bf96ddddc412ef4761676adc35df35d7c246", + "sha256:e1aafc91cbdacc9e5fe712c52077369168e6b6c346f3a9d51bf600b53eae56bb", + "sha256:e425be62524dc0c593985da794ee73eb8a17abb10fe692ee43bb39e201d7a099", + "sha256:e43f315c68aa61cbdef522a2272c5a5b9b8fd03c301d3167b5e1343ef50c676c", + "sha256:e49ae693306d7624015f31cb3e82708916759d592c2e5f72a35c8f4cc8aef258", + "sha256:e5c50e164cd2459bc5137c15288a9ef57160fd5cbf293265ea3c45efe7870865", + "sha256:e8579a43eafd246e285eb3a5b939e7158073d5087aacdd2308f23200eac2458b", + "sha256:e85e50b9c67854f89635a86247412f3ad66b132a4d8534ac017547197c88f27d", + "sha256:f0452282258dfcc01697026a8841258dd2057c4438b43914b611bccbcd048f10", + "sha256:f4bfc89bd33ebb8e4c0e9846a09b1f5a4a86f5cb7a317e75cc42fee1131b4f4f", + "sha256:fa2f50678f04fda7a75d0fe5dd02bbdd3b13cbe6ed4cf626e4472a7ccf47ae94", + "sha256:faec934cfe5fd6ac1151c02e67156c3f526e82f96b24d550b5d51efa4a5527c6", + "sha256:fcd3cf4286a60fdc95451d8d14e0389a6b4f5cebe02c7f2609325eb016535963", + "sha256:fe8a988c7220c002c45347430993830666e55bc350179d91fcee0feafe64e1d4", + "sha256:fed18e44711c5af4b681c2b3b18f85e6f0f1b2370a28854c645d636d5305ccd8", + "sha256:ffc61a388a5834a97953d6444a2888c24a05f2e333f9ed49f977a87bb1ad4761" + ], + "index": "pypi", + "version": "==1.3.2" + }, "setuptools": { "hashes": [ "sha256:2e24e0bec025f035a2e72cdd1961119f557d78ad331bb00ff82efb2ab8da8e82", @@ -1315,7 +1400,7 @@ "sha256:8b99adda265feb6773280df41eece7b2e6561b772d21ffd52e372f999024907b", "sha256:a335baacfaa4400ae1f0d8e3a58d6674d2f8828e3716bb2802c44955ad391380" ], - "markers": "python_full_version >= '3.6.0'", + "markers": "python_version >= '3.6'", "version": "==3.1.0" }, "tika": { @@ -1349,7 +1434,7 @@ "sha256:2e4582b70f04b2345908254684a984206c0d9b50e3074a24a4c55aba21d24d01", "sha256:41223af4a9d5726e645a8ee82480f413e5e300dd257db94bc38ae12ea48fb2e5" ], - "markers": "python_full_version >= '3.6.0'", + "markers": "python_version >= '3.6'", "version": "==22.2.1" }, "typing-extensions": { @@ -1365,7 +1450,7 @@ "sha256:21f4f0d7241572efa7f7a4fdabb052e61b55dc48274e6842697ccdf5253e5451", "sha256:c3119520447d68ef3eb8187a55a4f44fa455f30eb1b4238fa5691ba094f2b05b" ], - "markers": "python_full_version >= '3.6.0'", + "markers": "python_version >= '3.6'", "version": "==2022.2" }, "tzlocal": { @@ -1373,7 +1458,7 @@ "sha256:89885494684c929d9191c57aa27502afc87a579be5cdd3225c77c463ea043745", "sha256:ee5842fa3a795f023514ac2d801c4a81d1743bbe642e3940143326b3a00addd7" ], - "markers": "python_full_version >= '3.6.0'", + "markers": "python_version >= '3.6'", "version": "==4.2" }, "urllib3": { @@ -1757,7 +1842,7 @@ "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845", "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f" ], - "markers": "python_full_version >= '3.6.0'", + "markers": "python_version >= '3.6'", "version": "==2.1.1" }, "click": { @@ -2036,7 +2121,7 @@ "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb", "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522" ], - "markers": "python_full_version >= '3.6.0'", + "markers": "python_version >= '3.6'", "version": "==21.3" }, "pathspec": { @@ -2060,7 +2145,7 @@ "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159", "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3" ], - "markers": "python_full_version >= '3.6.0'", + "markers": "python_version >= '3.6'", "version": "==1.0.0" }, "pre-commit": { @@ -2175,6 +2260,7 @@ }, "pyyaml": { "hashes": [ + "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf", "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293", "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b", "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57", @@ -2186,26 +2272,32 @@ "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287", "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513", "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0", + "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782", "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0", "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92", "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f", "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2", "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc", + "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1", "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c", "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86", "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4", "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c", "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34", "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b", + "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d", "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c", "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb", + "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7", "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737", "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3", "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d", + "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358", "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53", "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78", "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803", "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a", + "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f", "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174", "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5" ], diff --git a/docker/compose/docker-compose.env b/docker/compose/docker-compose.env index 34e694b92..c4dbb4cce 100644 --- a/docker/compose/docker-compose.env +++ b/docker/compose/docker-compose.env @@ -36,3 +36,7 @@ # The default language to use for OCR. Set this to the language most of your # documents are written in. #PAPERLESS_OCR_LANGUAGE=eng + +# Set if accessing paperless via a domain subpath e.g. https://domain.com/PATHPREFIX and using a reverse-proxy like traefik or nginx +#PAPERLESS_FORCE_SCRIPT_NAME=/PATHPREFIX +#PAPERLESS_STATIC_URL=/PATHPREFIX/static/ # trailing slash required diff --git a/docker/docker-entrypoint.sh b/docker/docker-entrypoint.sh index cc91fadb6..14a0650f0 100755 --- a/docker/docker-entrypoint.sh +++ b/docker/docker-entrypoint.sh @@ -50,6 +50,7 @@ map_folders() { # Export these so they can be used in docker-prepare.sh export DATA_DIR="${PAPERLESS_DATA_DIR:-/usr/src/paperless/data}" export MEDIA_ROOT_DIR="${PAPERLESS_MEDIA_ROOT:-/usr/src/paperless/media}" + export CONSUME_DIR="${PAPERLESS_CONSUMPTION_DIR:-/usr/src/paperless/consume}" } initialize() { @@ -77,7 +78,11 @@ initialize() { local export_dir="/usr/src/paperless/export" - for dir in "${export_dir}" "${DATA_DIR}" "${DATA_DIR}/index" "${MEDIA_ROOT_DIR}" "${MEDIA_ROOT_DIR}/documents" "${MEDIA_ROOT_DIR}/documents/originals" "${MEDIA_ROOT_DIR}/documents/thumbnails"; do + for dir in \ + "${export_dir}" \ + "${DATA_DIR}" "${DATA_DIR}/index" \ + "${MEDIA_ROOT_DIR}" "${MEDIA_ROOT_DIR}/documents" "${MEDIA_ROOT_DIR}/documents/originals" "${MEDIA_ROOT_DIR}/documents/thumbnails" \ + "${CONSUME_DIR}"; do if [[ ! -d "${dir}" ]]; then echo "Creating directory ${dir}" mkdir "${dir}" @@ -91,7 +96,11 @@ initialize() { set +e echo "Adjusting permissions of paperless files. This may take a while." chown -R paperless:paperless ${tmp_dir} - for dir in "${export_dir}" "${DATA_DIR}" "${MEDIA_ROOT_DIR}"; do + for dir in \ + "${export_dir}" \ + "${DATA_DIR}" \ + "${MEDIA_ROOT_DIR}" \ + "${CONSUME_DIR}"; do find "${dir}" -not \( -user paperless -and -group paperless \) -exec chown paperless:paperless {} + done set -e diff --git a/docker/paperless_cmd.sh b/docker/paperless_cmd.sh index cdeaf68c4..81a7c6334 100755 --- a/docker/paperless_cmd.sh +++ b/docker/paperless_cmd.sh @@ -12,4 +12,4 @@ if [ "$(id -u)" == "$(id -u paperless)" ]; then ) fi -/usr/local/bin/supervisord -c /etc/supervisord.conf "${rootless_args[@]}" +exec /usr/local/bin/supervisord -c /etc/supervisord.conf "${rootless_args[@]}" diff --git a/docs/configuration.rst b/docs/configuration.rst index 5b71bad99..99c037ec8 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -27,6 +27,12 @@ PAPERLESS_REDIS= This is required for processing scheduled tasks such as email fetching, index optimization and for training the automatic document matcher. + * If your Redis server needs login credentials PAPERLESS_REDIS = ``redis://:@:`` + + * With the requirepass option PAPERLESS_REDIS = ``redis://:@:`` + + `More information on securing your Redis Instance `_. + Defaults to redis://localhost:6379. PAPERLESS_DBENGINE= @@ -215,9 +221,16 @@ PAPERLESS_FORCE_SCRIPT_NAME= PAPERLESS_STATIC_URL= Override the STATIC_URL here. Unless you're hosting Paperless off a subdomain like /paperless/, you probably don't need to change this. + If you do change it, be sure to include the trailing slash. Defaults to "/static/". + .. note:: + + When hosting paperless behind a reverse proxy like Traefik or Nginx at a subpath e.g. + example.com/paperlessngx you will also need to set ``PAPERLESS_FORCE_SCRIPT_NAME`` + (see above). + PAPERLESS_AUTO_LOGIN_USERNAME= Specify a username here so that paperless will automatically perform login with the selected user. @@ -805,10 +818,10 @@ the program doesn't automatically execute it (ie. the program isn't in your $PATH), then you'll need to specify the literal path for that program. PAPERLESS_CONVERT_BINARY= - Defaults to "/usr/bin/convert". + Defaults to "convert". PAPERLESS_GS_BINARY= - Defaults to "/usr/bin/gs". + Defaults to "gs". .. _configuration-docker: diff --git a/docs/scanners.rst b/docs/scanners.rst index 1100516a9..68b0b335f 100644 --- a/docs/scanners.rst +++ b/docs/scanners.rst @@ -1,142 +1,8 @@ .. _scanners: -*********************** -Scanner recommendations -*********************** +******************* +Scanners & Software +******************* -As Paperless operates by watching a folder for new files, doesn't care what -scanner you use, but sometimes finding a scanner that will write to an FTP, -NFS, or SMB server can be difficult. This page is here to help you find one -that works right for you based on recommendations from other Paperless users. - -Physical scanners -================= - -+---------+-------------------+-----+------+-----+----------+------+----------+----------------+ -| Brand | Model | Supports | Recommended By | -+---------+-------------------+-----+------+-----+----------+------+----------+----------------+ -| | | FTP | SFTP | NFS | SMB | SMTP | API [1]_ | | -+=========+===================+=====+======+=====+==========+======+==========+================+ -| Brother | `ADS-1700W`_ | yes | yes | | yes | yes | |`holzhannes`_ | -+---------+-------------------+-----+------+-----+----------+------+----------+----------------+ -| Brother | `ADS-1600W`_ | yes | | | yes | yes | |`holzhannes`_ | -+---------+-------------------+-----+------+-----+----------+------+----------+----------------+ -| Brother | `ADS-1500W`_ | yes | | | yes | yes | |`danielquinn`_ | -+---------+-------------------+-----+------+-----+----------+------+----------+----------------+ -| Brother | `ADS-1100W`_ | yes | | | | | |`ytzelf`_ | -+---------+-------------------+-----+------+-----+----------+------+----------+----------------+ -| Brother | `ADS-2800W`_ | yes | yes | | yes | yes | |`philpagel`_ | -+---------+-------------------+-----+------+-----+----------+------+----------+----------------+ -| Brother | `MFC-J6930DW`_ | yes | | | | | |`ayounggun`_ | -+---------+-------------------+-----+------+-----+----------+------+----------+----------------+ -| Brother | `MFC-L5850DW`_ | yes | | | | yes | |`holzhannes`_ | -+---------+-------------------+-----+------+-----+----------+------+----------+----------------+ -| Brother | `MFC-L2750DW`_ | yes | | | yes | yes | |`muued`_ | -+---------+-------------------+-----+------+-----+----------+------+----------+----------------+ -| Brother | `MFC-J5910DW`_ | yes | | | | | |`bmsleight`_ | -+---------+-------------------+-----+------+-----+----------+------+----------+----------------+ -| Brother | `MFC-8950DW`_ | yes | | | yes | yes | |`philpagel`_ | -+---------+-------------------+-----+------+-----+----------+------+----------+----------------+ -| Brother | `MFC-9142CDN`_ | yes | | | yes | | |`REOLDEV`_ | -+---------+-------------------+-----+------+-----+----------+------+----------+----------------+ -| Canon | `Maxify MB 5350`_ | | | | yes [2]_ | yes | |`eingemaischt`_ | -+---------+-------------------+-----+------+-----+----------+------+----------+----------------+ -| Fujitsu | `ix500`_ | yes | | | yes | | |`eonist`_ | -+---------+-------------------+-----+------+-----+----------+------+----------+----------------+ -| Epson | `ES-580W`_ | yes | | | yes | yes | |`fignew`_ | -+---------+-------------------+-----+------+-----+----------+------+----------+----------------+ -| Epson | `WF-7710DWF`_ | yes | | | yes | | |`Skylinar`_ | -+---------+-------------------+-----+------+-----+----------+------+----------+----------------+ -| Fujitsu | `S1300i`_ | yes | | | yes | | |`jonaswinkler`_ | -+---------+-------------------+-----+------+-----+----------+------+----------+----------------+ -| Doxie | `Q2`_ | | | | | | yes |`Unkn0wnCat`_ | -+---------+-------------------+-----+------+-----+----------+------+----------+----------------+ - -.. _MFC-L5850DW: https://www.brother-usa.com/products/mfcl5850dw -.. _MFC-L2750DW: https://www.brother.de/drucker/laserdrucker/mfc-l2750dw -.. _ADS-1700W: https://www.brother-usa.com/products/ads1700w -.. _ADS-1600W: https://www.brother-usa.com/products/ads1600w -.. _ADS-1500W: https://www.brother.ca/en/p/ads1500w -.. _ADS-1100W: https://support.brother.com/g/b/downloadtop.aspx?c=fr&lang=fr&prod=ads1100w_eu_as_cn -.. _ADS-2800W: https://www.brother-usa.com/products/ads2800w -.. _Maxify MB 5350: https://www.canon.de/printers/inkjet/maxify/maxify_mb5350/specification.html -.. _MFC-J6930DW: https://www.brother.ca/en/p/MFCJ6930DW -.. _MFC-J5910DW: https://www.brother.co.uk/printers/inkjet-printers/mfcj5910dw -.. _MFC-8950DW: https://www.brother-usa.com/products/mfc8950dw -.. _MFC-9142CDN: https://www.brother.co.uk/printers/laser-printers/mfc9140cdn -.. _ES-580W: https://epson.com/Support/Scanners/ES-Series/Epson-WorkForce-ES-580W/s/SPT_B11B258201 -.. _WF-7710DWF: https://www.epson.de/en/products/printers/inkjet-printers/for-home/workforce-wf-7710dwf -.. _ix500: http://www.fujitsu.com/us/products/computing/peripheral/scanners/scansnap/ix500/ -.. _S1300i: https://www.fujitsu.com/global/products/computing/peripheral/scanners/soho/s1300i/ -.. _Q2: https://www.getdoxie.com/product/doxie-q/ - -.. _ayounggun: https://github.com/ayounggun -.. _bmsleight: https://github.com/bmsleight -.. _danielquinn: https://github.com/danielquinn -.. _eonist: https://github.com/eonist -.. _fignew: https://github.com/fignew -.. _holzhannes: https://github.com/holzhannes -.. _jonaswinkler: https://github.com/jonaswinkler -.. _REOLDEV: https://github.com/REOLDEV -.. _Skylinar: https://github.com/Skylinar -.. _ytzelf: https://github.com/ytzelf -.. _Unkn0wnCat: https://github.com/Unkn0wnCat -.. _muued: https://github.com/muued -.. _philpagel: https://github.com/philpagel -.. _eingemaischt: https://github.com/eingemaischt - -.. [1] Scanners with API Integration allow to push scanned documents directly to :ref:`Paperless API `, sometimes referred to as Webhook or Document POST. -.. [2] Canon Multi Function Printers show strange behavior over SMB. They close and reopen the file after every page. It's recommended to tune the - :ref:`polling ` and :ref:`inotify ` configuration values for your scanner. The scanner timeout is 3 minutes, so ``180`` is a good starting point. - -Mobile phone software -===================== - -You can use your phone to "scan" documents. The regular camera app will work, but may have too low contrast for OCR to work well. Apps specifically for scanning are recommended. - -+-----------------------------+----------------+-----+-----+-----+-------+--------+------------------+ -| Name | OS | Supports | Recommended By | -+-----------------------------+----------------+-----+-----+-----+-------+--------+------------------+ -| | | FTP | NFS | SMB | Email | WebDAV | | -+=============================+================+=====+=====+=====+=======+========+==================+ -| `Office Lens`_ | Android | ? | ? | ? | ? | ? | `jonaswinkler`_ | -+-----------------------------+----------------+-----+-----+-----+-------+--------+------------------+ -| `Genius Scan`_ | Android | yes | no | yes | yes | yes | `hannahswain`_ | -+-----------------------------+----------------+-----+-----+-----+-------+--------+------------------+ -| `OpenScan`_ | Android | no | no | no | no | no | `benjaminfrank`_ | -+-----------------------------+----------------+-----+-----+-----+-------+--------+------------------+ -| `OCR Scanner - QuickScan`_ | iOS | no | no | no | no | yes | `holzhannes`_ | -+-----------------------------+----------------+-----+-----+-----+-------+--------+------------------+ - -On Android, you can use these applications in combination with one of the :ref:`Paperless-ngx compatible apps ` to "Share" the documents produced by these scanner apps with paperless. On iOS, you can share the scanned documents via iOS-Sharing to other mail, WebDav or FTP apps. - -There is also an iOS Shortcut that allows you to directly upload text, PDF and image documents available here: https://www.icloud.com/shortcuts/d234abc0885040129d9d75fa45fe1154 -Please note this only works for documents downloaded to iCloud / the device, in other words not directly from a URL. - -.. _Office Lens: https://play.google.com/store/apps/details?id=com.microsoft.office.officelens -.. _Genius Scan: https://play.google.com/store/apps/details?id=com.thegrizzlylabs.geniusscan.free -.. _OCR Scanner - QuickScan: https://apps.apple.com/us/app/quickscan-scanner-text-ocr/id1513790291 -.. _OpenScan: https://github.com/Ethereal-Developers-Inc/OpenScan - -.. _hannahswain: https://github.com/hannahswain -.. _benjaminfrank: https://github.com/benjaminfrank - -API Scanning Setup -================== - -This sections contains information on how to set up scanners to post directly to :ref:`Paperless API `. - -Doxie Q2 --------- - -This part assumes your Doxie is connected to WiFi and you know its IP. - -1. Open your Doxie web UI by navigating to its IP address -2. Navigate to Options -> Webhook -3. Set the *URL* to ``https://[your-paperless-ngx-instance]/api/documents/post_document/`` -4. Set the *File Parameter Name* to ``document`` -5. Add the username and password to the respective fields (Consider creating a user just for your Doxie) -6. Click *Submit* at the bottom of the page - -Congrats, you can now scan directly from your Doxie to your Paperless-ngx instance! +Paperless-ngx is compatible with many different scanners and scanning tools. A user-maintained list of scanners and other software is available on `the wiki `_. diff --git a/docs/setup.rst b/docs/setup.rst index 9312e70d4..ca07c1032 100644 --- a/docs/setup.rst +++ b/docs/setup.rst @@ -224,6 +224,7 @@ Install Paperless from Docker Hub You can utilize Docker secrets for some configuration settings by appending `_FILE` to some configuration values. This is supported currently only by: + * PAPERLESS_DBUSER * PAPERLESS_DBPASS * PAPERLESS_SECRET_KEY diff --git a/src-ui/messages.xlf b/src-ui/messages.xlf index 3080e8b84..9827bda41 100644 --- a/src-ui/messages.xlf +++ b/src-ui/messages.xlf @@ -816,7 +816,7 @@ src/app/components/document-detail/document-detail.component.html - 184 + 185 src/app/components/document-list/save-view-config-dialog/save-view-config-dialog.component.html @@ -1015,16 +1015,12 @@ 13 - - Add item - - src/app/components/common/input/select/select.component.html - 11 - - Used for both types, correspondents, storage paths - Suggestions: + + src/app/components/common/input/date/date.component.html + 16 + src/app/components/common/input/select/select.component.html 30 @@ -1034,6 +1030,14 @@ 42 + + Add item + + src/app/components/common/input/select/select.component.html + 11 + + Used for both types, correspondents, storage paths + Add tag @@ -1498,7 +1502,7 @@ Correspondent src/app/components/document-detail/document-detail.component.html - 78 + 79 src/app/components/document-list/bulk-editor/bulk-editor.component.html @@ -1521,7 +1525,7 @@ Document type src/app/components/document-detail/document-detail.component.html - 80 + 81 src/app/components/document-list/bulk-editor/bulk-editor.component.html @@ -1544,7 +1548,7 @@ Storage path src/app/components/document-detail/document-detail.component.html - 82 + 83 src/app/components/document-list/bulk-editor/bulk-editor.component.html @@ -1563,21 +1567,21 @@ Default src/app/components/document-detail/document-detail.component.html - 83 + 84 Content src/app/components/document-detail/document-detail.component.html - 90 + 91 Metadata src/app/components/document-detail/document-detail.component.html - 99 + 100 src/app/components/document-detail/metadata-collapse/metadata-collapse.component.ts @@ -1588,95 +1592,95 @@ Date modified src/app/components/document-detail/document-detail.component.html - 105 + 106 Date added src/app/components/document-detail/document-detail.component.html - 109 + 110 Media filename src/app/components/document-detail/document-detail.component.html - 113 + 114 Original filename src/app/components/document-detail/document-detail.component.html - 117 + 118 Original MD5 checksum src/app/components/document-detail/document-detail.component.html - 121 + 122 Original file size src/app/components/document-detail/document-detail.component.html - 125 + 126 Original mime type src/app/components/document-detail/document-detail.component.html - 129 + 130 Archive MD5 checksum src/app/components/document-detail/document-detail.component.html - 133 + 134 Archive file size src/app/components/document-detail/document-detail.component.html - 137 + 138 Original document metadata src/app/components/document-detail/document-detail.component.html - 143 + 144 Archived document metadata src/app/components/document-detail/document-detail.component.html - 144 + 145 Enter Password src/app/components/document-detail/document-detail.component.html - 166 + 167 src/app/components/document-detail/document-detail.component.html - 202 + 203 Comments src/app/components/document-detail/document-detail.component.html - 173 + 174 src/app/components/manage/settings/settings.component.html @@ -1687,14 +1691,14 @@ Discard src/app/components/document-detail/document-detail.component.html - 182 + 183 Save & next src/app/components/document-detail/document-detail.component.html - 183 + 184 diff --git a/src-ui/src/app/components/app-frame/app-frame.component.html b/src-ui/src/app/components/app-frame/app-frame.component.html index 4db6825b3..5a68a3cda 100644 --- a/src-ui/src/app/components/app-frame/app-frame.component.html +++ b/src-ui/src/app/components/app-frame/app-frame.component.html @@ -21,7 +21,7 @@