diff --git a/scanservjs/.gitignore b/scanservjs/.gitignore new file mode 100644 index 0000000..9a4a52e --- /dev/null +++ b/scanservjs/.gitignore @@ -0,0 +1 @@ +paperless.token diff --git a/scanservjs/README.md b/scanservjs/README.md new file mode 100644 index 0000000..faf6726 --- /dev/null +++ b/scanservjs/README.md @@ -0,0 +1,36 @@ +# scanserv-js + +[scanservjs][0] is an open source, web-based front-end for the [SANE][1] +software suite. It provides a simple graphical user interface for scanning +photos and documents using any local or network-connected SANE-compatible +scanner. + +## Installation + +```sh +kubectl apply -k scanservjs +``` + +Environment variables used by the [container image][2] can be set in the +`scanservjs.env` file. + + +## Integration with *paperless-ngx* + +[paperless-ngx][3] is an open-source, web-based document management platform. +Integrating *scanservjs* with *paperless-ngx* allows scanned documents to be +fed automatically into the document management system for OCR, analysis, and +classification. This integration is performed using the `afterScan` hook in +the *scanservjs* `config.local.js` file and the *paperless-ngx* REST API to +upload documents scanned as PDF files. + +To enable the integration, specify the URL to the *paperless-ngx* service in +the `PAPERLESS_URL` environment variable. Additionally, create an +[authentication token][4] for *paperless-ngx* user, store it in a file, and set +the `PAPERLESS_TOKEN_FILE` environment variable to the file path. + +[0]: https://github.com/sbs20/scanservjs +[1]: http://sane-project.org/ +[2]: https://github.com/sbs20/scanservjs/blob/master/docs/docker.md +[3]: https://docs.paperless-ngx.com/ +[4]: https://docs.paperless-ngx.com/api/#authorization diff --git a/scanservjs/config.local.js b/scanservjs/config.local.js new file mode 100644 index 0000000..808cbfb --- /dev/null +++ b/scanservjs/config.local.js @@ -0,0 +1,77 @@ +// vim: set sw=2 ts=2 sts=2 et : +const fs = require('node:fs/promises'); +const http = require('node:http'); +const https = require('node:https'); +const path = require('node:path'); + +const HANDLERS = { + 'http': http, + 'https': https, +} + +async function get_token() { + const buf = await fs.readFile( + process.env['PAPERLESS_TOKEN_FILE'], + 'utf-8', + ); + return buf.trimEnd(); +} + +async function paperless_send(filepath) { + if (!'PAPERLESS_URL' in process.env) { + return + } + + const filename = path.basename(filepath); + const boundary = `------------------------${Math.random() * 1e16}`; + const token = await get_token(); + const options = { + method: 'POST', + headers: { + 'Content-Type': `multipart/form-data; boundary=${boundary}`, + 'Authorization': `Token ${token}`, + }, + }; + const url = new URL( + process.env['PAPERLESS_URL'] + '/api/documents/post_document/' + ); + console.log('Uploading', filename, 'to', url.toString()); + const handler = HANDLERS[url.protocol.slice(0, -1)]; + const req = handler.request(url, options, (res) => { + console.log('Received HTTP status code', res.statusCode, 'from Paperless'); + }); + + req.on('error', (e) => { + console.error(e); + }); + + const header = `--${boundary}\r\n` + + `Content-Disposition: form-data; name="document"; filename="${filename}"\r\n` + + `\r\n` + const trailer = `\r\n--${boundary}--\r\n` + const f = await fs.open(filepath); + const st = await f.stat(); + const length = header.length + st.size + trailer.length; + req.setHeader('Content-Length', length.toString()); + req.write(header) + while (1) { + const d = await f.read(); + if (!d.bytesRead) { + break; + } + req.write(d.buffer.slice(0, d.bytesRead)); + }; + await f.close(); + + req.write(trailer); + req.end(); + console.log('Upload complete'); +} + +module.exports = { + async afterScan(fileInfo) { + if (fileInfo.name.endsWith('.pdf')) { + await paperless_send(fileInfo.fullname); + } + } +} diff --git a/scanservjs/ingress.yaml b/scanservjs/ingress.yaml new file mode 100644 index 0000000..351d9e7 --- /dev/null +++ b/scanservjs/ingress.yaml @@ -0,0 +1,36 @@ +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + labels: + app.kubernetes.io/name: scanservjs + app.kubernetes.io/component: scanservjs + app.kubernetes.io/instance: scanservjs + app.kubernetes.io/part-of: scanservjs + annotations: + nginx.ingress.kubernetes.io/proxy-body-size: '0' + nginx.ingress.kubernetes.io/auth-method: GET + nginx.ingress.kubernetes.io/auth-url: http://authelia.authelia.svc.cluster.local:9091/api/verify + nginx.ingress.kubernetes.io/auth-signin: https://auth.pyrocufflink.blue/?rm=$request_method + nginx.ingress.kubernetes.io/auth-response-headers: Remote-User,Remote-Name,Remote-Groups,Remote-Email + nginx.ingress.kubernetes.io/auth-snippet: | + proxy_set_header X-Forwarded-Method $request_method; + name: scanservjs + namespace: scanservjs +spec: + ingressClassName: nginx + tls: + - hosts: + - scan.pyrocufflink.blue + rules: + - host: scan.pyrocufflink.blue + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: scanservjs + port: + name: http + diff --git a/scanservjs/kustomization.yml b/scanservjs/kustomization.yml new file mode 100644 index 0000000..ada68a5 --- /dev/null +++ b/scanservjs/kustomization.yml @@ -0,0 +1,29 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +resources: +- scanservjs.yaml +- ingress.yaml + +configMapGenerator: +- name: scanservjs + namespace: scanservjs + envs: + - scanservjs.env + options: + disableNameSuffixHash: true +- name: scanservjs-config + namespace: scanservjs + files: + - config.local.js + options: + disableNameSuffixHash: true + +secretGenerator: +- name: paperless-token + namespace: scanservjs + files: + - paperless.token + options: + disableNameSuffixHash: true + diff --git a/scanservjs/scanservjs.env b/scanservjs/scanservjs.env new file mode 100644 index 0000000..842f5ca --- /dev/null +++ b/scanservjs/scanservjs.env @@ -0,0 +1,3 @@ +SCANIMAGE_LIST_IGNORE=true +DEVICES=escl:https://172.30.0.234:443 +PAPERLESS_URL=http://paperless-ngx.paperless-ngx.svc.cluster.local:8000 diff --git a/scanservjs/scanservjs.yaml b/scanservjs/scanservjs.yaml new file mode 100644 index 0000000..14fc4d0 --- /dev/null +++ b/scanservjs/scanservjs.yaml @@ -0,0 +1,118 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: scanservjs + +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/name: scanservjs + app.kubernetes.io/component: scanservjs + app.kubernetes.io/instance: scanservjs + app.kubernetes.io/part-of: scanservjs + name: scanservjs + namespace: scanservjs +spec: + ports: + - name: http + port: 8080 + selector: + app.kubernetes.io/name: scanservjs + app.kubernetes.io/component: scanservjs + app.kubernetes.io/instance: scanservjs + type: ClusterIP + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: scanservjs + namespace: scanservjs + labels: + app.kubernetes.io/name: scanservjs + app.kubernetes.io/component: scanservjs + app.kubernetes.io/instance: scanservjs + app.kubernetes.io/part-of: scanservjs +spec: + selector: + matchLabels: + app.kubernetes.io/name: scanservjs + app.kubernetes.io/component: scanservjs + app.kubernetes.io/instance: scanservjs + template: + metadata: + labels: + app.kubernetes.io/name: scanservjs + app.kubernetes.io/component: scanservjs + app.kubernetes.io/instance: scanservjs + spec: + initContainers: + - name: init-data + image: docker.io/sbs20/scanservjs:v2.26.1 + imagePullPolicy: IfNotPresent + command: + - cp + - -r + - /app/data/. + - /tmp/data + securityContext: + runAsNonRoot: true + readOnlyRootFilesystem: true + runAsUser: 1000 + runAsGroup: 1000 + volumeMounts: + - name: scanservjs-datadir + mountPath: /tmp/data + containers: + - name: scanservjs + image: docker.io/sbs20/scanservjs:v2.26.1 + imagePullPolicy: IfNotPresent + env: + - name: PAPERLESS_TOKEN_FILE + value: /run/secrets/paperless.token + envFrom: + - configMapRef: + name: scanservjs + optional: true + ports: + - name: scanservjs + containerPort: 8080 + securityContext: + runAsNonRoot: true + readOnlyRootFilesystem: true + runAsUser: 1000 + runAsGroup: 1000 + volumeMounts: + - name: scanservjs-datadir + mountPath: /app/data + - name: scanservjs-configdir + mountPath: /app/config + - name: scanservjs-config + mountPath: /app/config/config.local.js + subPath: config.local.js + - name: scanservjs-tmp + mountPath: /tmp + - name: paperless-token + mountPath: /run/secrets/paperless.token + subPath: paperless.token + readOnly: true + securityContext: + fsGroup: 1000 + volumes: + - name: scanservjs-datadir + emptyDir: + - name: scanservjs-configdir + emptyDir: + medium: Memory + - name: scanservjs-tmp + emptyDir: + medium: Memory + - name: scanservjs-config + configMap: + name: scanservjs-config + - name: paperless-token + secret: + secretName: paperless-token + optional: true