From b421e330f4423297a58c9be51a7f246610f97473 Mon Sep 17 00:00:00 2001 From: Prunebutt Date: Sat, 27 Sep 2025 15:51:35 +0200 Subject: [PATCH 1/4] cleanup scanbuddy script --- scanbuddy.bash | 51 +++++++++++++++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/scanbuddy.bash b/scanbuddy.bash index 0fd8e4c..7e28472 100755 --- a/scanbuddy.bash +++ b/scanbuddy.bash @@ -13,7 +13,6 @@ sides=$simplex_source mode='c' resolution='300' - current_page=$(find "$PWD" -name "out*.pdf" | sort | tail -n 1); read -r current_pageno <<<"${current_page//[^0-9]/ }" current_pageno=${current_pageno:-0} @@ -77,6 +76,33 @@ scan() { -x 210 -y 297 # A4 } +clean() { + echo "Cleaning up..." + rm -rfv "$PWD/out*.pdf" +} + +dispatch() { + readarray -d '' out_files < <(find . -regextype posix-awk -regex "\./out[0-9]+\.pdf" -print0 | sort -Vz) + combined_file=$(mktemp --suff="_scanbuddy.pdf") + gs -q -dNOPAUSE -sDEVICE=pdfwrite -sOUTPUTFILE="$combined_file" -dBATCH "${out_files[@]}" + echo "Merged pdf written to $combined_file" + + # send to paperless + PAPERLESS_STATUS=$(curl \ + -H "Authorization: Token $PAPERLESS_TOKEN" \ + -F document=@"$combined_file" \ + "$([ -v PAPERLESS_TAG ] && echo "-F tags=$PAPERLESS_TAG")" \ + "${PAPERLESS_URL}/api/documents/post_document/") || FAILED=1 + + if [ -v FAILED ]; then + printf "Paperless failed with message:\n%s" "$PAPERLESS_STATUS" + else + printf "Paperless consumption job: %s\n" "$PAPERLESS_STATUS" + fi + + clean +} + if [ "$#" -lt 1 ]; then echo "You need an argument!" exit 1 @@ -90,25 +116,12 @@ case "$1" in rm "$last_out_file" ;; dispatch) - readarray -d '' out_files < <(find . -regextype posix-awk -regex "\./out[0-9]+\.pdf" -print0 | sort -Vz) - combined_file=$(mktemp --suff="_scanbuddy.pdf") - gs -q -dNOPAUSE -sDEVICE=pdfwrite -sOUTPUTFILE="$combined_file" -dBATCH "${out_files[@]}" - echo "Merged pdf written to $combined_file" - - # send to paperless - PAPERLESS_STATUS=$(curl \ - -H "Authorization: Token $PAPERLESS_TOKEN" \ - -F document=@"$combined_file" \ - "$([ -v PAPERLESS_TAG ] && echo "-F tags=$PAPERLESS_TAG")" \ - "${PAPERLESS_URL}/api/documents/post_document/") || FAILED=1 - - if [ -v FAILED ]; then - printf "Paperless failed with message:\n%s" "$PAPERLESS_STATUS" - else - printf "Paperless consumption job: %s\n" "$PAPERLESS_STATUS" - fi + dispatch "${@:2}" + ;; + clean) + clean ;; *) - echo "Usage: scanbuddy" + echo "Usage: scanbuddy scan|unscan|dispatch|clean" ;; esac From 465a5574b885885275e52c068421bef6ed3bb364 Mon Sep 17 00:00:00 2001 From: Prunebutt Date: Sat, 27 Sep 2025 15:51:47 +0200 Subject: [PATCH 2/4] use pyproject.toml --- flake.lock | 23 +++++++++++- flake.nix | 65 ++++++++++++++++---------------- pyproject.toml | 14 +++++++ server.py | 52 -------------------------- server/server.py | 97 ++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 166 insertions(+), 85 deletions(-) create mode 100644 pyproject.toml delete mode 100755 server.py create mode 100755 server/server.py diff --git a/flake.lock b/flake.lock index 2155089..0ed96fb 100644 --- a/flake.lock +++ b/flake.lock @@ -16,9 +16,30 @@ "type": "github" } }, + "pyproject-nix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1758265079, + "narHash": "sha256-amLaLNwKSZPShQHzfgmc/9o76dU8xzN0743dWgvYlr8=", + "owner": "nix-community", + "repo": "pyproject.nix", + "rev": "02e9418fd4af638447dca4b17b1280da95527fc9", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "pyproject.nix", + "type": "github" + } + }, "root": { "inputs": { - "nixpkgs": "nixpkgs" + "nixpkgs": "nixpkgs", + "pyproject-nix": "pyproject-nix" } } }, diff --git a/flake.nix b/flake.nix index ef747b6..559540a 100644 --- a/flake.nix +++ b/flake.nix @@ -1,59 +1,60 @@ { inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + pyproject-nix = { + url = "github:nix-community/pyproject.nix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; }; outputs = { self, nixpkgs, + ... } @ inputs: let system = "x86_64-linux"; pkgs = import nixpkgs { inherit system; config.allowUnfree = true; }; - in { - devShells.${system}.default = pkgs.mkShell { - packages = with pkgs; [ - python313 - python313Packages.flask - python313Packages.waitress - ]; - - shellHook = '' - python --version - exec zsh - ''; + python = pkgs.python3; + serverProject = inputs.pyproject-nix.lib.project.loadPyproject { + projectRoot = ./.; }; + in { + # devShells.${system}.default = pkgs.mkShell { + # packages = with pkgs; [ + # python313 + # python313Packages.flask + # python313Packages.waitress + # ]; + # + # shellHook = '' + # python --version + # exec zsh + # ''; + # }; packages.${system} = let scanbuddy = pkgs.writeShellApplication { - name = "scanbuddy-script"; + name = "scanbuddy"; runtimeInputs = with pkgs; [sane-backends brscan5 ghostscript]; text = builtins.readFile ./scanbuddy.bash; }; - wrapper = pkgs.writeShellApplication { - name = "scanbuddy-wrapper"; - text = - /* - bash - */ - '' - #!/usr/bin/env bash - readarray -d '_' args < <(printf "%s" "$1") - - scanbuddy "''${args[@]}" - ''; - }; - package = pkgs.symlinkJoin { - name = "scanbuddy"; - paths = [scanbuddy wrapper]; - }; + # Returns an attribute set that can be passed to `buildPythonPackage`. + attrs = serverProject.renderers.buildPythonPackage {inherit python;}; + server = python.pkgs.buildPythonPackage (attrs + // { + env.CUSTOM_ENVVAR = "foobar"; + }); in { - default = package; + default = pkgs.symlinkJoin { + name = "scanbuddy"; + paths = [scanbuddy server]; + }; inherit scanbuddy; - inherit wrapper; + inherit server; }; }; } diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..49823ce --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,14 @@ +[project] +name = "scanbuddy-server" +version = "0.1.0" +description = "A REST server for the scanbuddy shell-script" + +# define any Python dependencies +dependencies = [ + "flask>3", +] + +# define the CLI executable +# Here, we define the entry point to be the 'main()' function in the module 'app/main.py' +[project.scripts] +scanbuddy-server = "server.server:main" diff --git a/server.py b/server.py deleted file mode 100755 index ba2632b..0000000 --- a/server.py +++ /dev/null @@ -1,52 +0,0 @@ -#!/usr/bin/env python3 - -# Run this stuff with `waitress-serve --port=5000 --call server:create_app` - -import os -import subprocess -from flask import Flask, request - -app = Flask(__name__) - -@app.route("/") -def hello_world(): - return "Hello, World! Yoyoyo" - -@app.route("/scan") -def scan(): - command = ["bash", f"{os.getcwd()}/scanbuddy.bash", "scan"] - - command += ["-d"] if (request.args.get("duplex", "n") == 'y') else [] - - result = subprocess.run(command, stdout=subprocess.PIPE, text=True) - output = result.stdout.strip() - - print("Scanbuddy output: ") - print(output) - - return output - # return f"Scanning {'duplex' if duplex == 'y' else ''}..." - -@app.route("/dispatch") -def dispatch(): - command = ["bash", f"{os.getcwd()}/scanbuddy.bash", "dispatch"] - user = request.args.get("user", "") - - result = subprocess.run(command, stdout=subprocess.PIPE, text=True) - output = result.stdout.strip() - - print("Scanbuddy output: ") - print(output) - - return "Dispatching..." - -@app.route("/unscan") -def unscan(): - return "Unscanning..." - -def create_app(): - return app - -if __name__=='__main__': - from waitress import serve - serve(app, host="0.0.0.0", port=8080) diff --git a/server/server.py b/server/server.py new file mode 100755 index 0000000..cfa49c2 --- /dev/null +++ b/server/server.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 + +# Run this stuff with `waitress-serve --port=5000 --call server:create_app` + +import os +import subprocess +from flask import Flask, request + +app = Flask(__name__) + +SCANBUDDY_COMMAND = ["bash", f"{os.getcwd()}/scanbuddy.bash"] + + +@app.route("/") +def hello_world(): + return "Hello, World! Yoyoyo" + + +@app.route("/scan") +def scan(): + print("Scanning...") + command = SCANBUDDY_COMMAND + ["scan"] + + duplex = request.args.get("duplex", "n") == 'y' + resolution = request.args.get("resolution", None) + + if duplex: + print("Duplex: on") + command += ["-d"] + if resolution: + print(f"Res.: {int(resolution)}") + # turning the string to an int and back to a string should clean up the input + command += ["-r", f"{int(resolution)}"] + + result = subprocess.run(command, stdout=subprocess.PIPE, text=True) + output = result.stdout.strip() + + print("Scanbuddy output: ") + print(output) + + return output + # return f"Scanning {'duplex' if duplex == 'y' else ''}..." + + +@app.route("/dispatch") +def dispatch(): + print("Dispatching...") + command = SCANBUDDY_COMMAND + ["dispatch"] + + # TODO: implement user + user = request.args.get("user", "") + + result = subprocess.run(command, stdout=subprocess.PIPE, text=True) + output = result.stdout.strip() + + print("Scanbuddy output: ") + print(output) + + return output + + +@app.route("/unscan") +def unscan(): + print("Unscanning...") + command = SCANBUDDY_COMMAND + ["unscan"] + + result = subprocess.run(command, stdout=subprocess.PIPE, text=True) + output = result.stdout.strip() + + print("Scanbuddy output: ") + print(output) + return output + + +@app.route("/clean") +def clean(): + print("Cleaning...") + command = SCANBUDDY_COMMAND + ["clean"] + + result = subprocess.run(command, stdout=subprocess.PIPE, text=True) + output = result.stdout.strip() + + print("Scanbuddy output: ") + print(output) + return output + + +def create_app(): + return app + + +def main(): + from waitress import serve + serve(app, host="0.0.0.0", port=5000) + +if __name__=='__main__': + main() From d6e6bd77686031afa26ac11bb2d302be757d6ea2 Mon Sep 17 00:00:00 2001 From: Prunebutt Date: Sat, 27 Sep 2025 18:54:09 +0200 Subject: [PATCH 3/4] making scanbuddy server more verbose --- server/server.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/server/server.py b/server/server.py index cfa49c2..cb3b9b4 100755 --- a/server/server.py +++ b/server/server.py @@ -13,7 +13,12 @@ SCANBUDDY_COMMAND = ["bash", f"{os.getcwd()}/scanbuddy.bash"] @app.route("/") def hello_world(): - return "Hello, World! Yoyoyo" + return ("Scanbuddy server. Go to ") @app.route("/scan") @@ -91,6 +96,7 @@ def create_app(): def main(): from waitress import serve + print("Serving scanbuddy on port 5000") serve(app, host="0.0.0.0", port=5000) if __name__=='__main__': From c8ce4db2a2234beecadd8f6ce2f32e6f74deb697 Mon Sep 17 00:00:00 2001 From: Prunebutt Date: Sat, 27 Sep 2025 18:54:24 +0200 Subject: [PATCH 4/4] split up the flake better --- flake.nix | 67 ++++++++++++++++++++++++++++++++++---------------- pyproject.toml | 1 + 2 files changed, 47 insertions(+), 21 deletions(-) diff --git a/flake.nix b/flake.nix index 559540a..e10f4c4 100644 --- a/flake.nix +++ b/flake.nix @@ -20,6 +20,27 @@ serverProject = inputs.pyproject-nix.lib.project.loadPyproject { projectRoot = ./.; }; + + # Packages + script = pkgs.writeShellApplication { + name = "scanbuddy"; + + runtimeInputs = with pkgs; [sane-backends brscan5 ghostscript]; + + text = builtins.readFile ./scanbuddy.bash; + }; + + # Returns an attribute set that can be passed to `buildPythonPackage`. + attrs = serverProject.renderers.buildPythonPackage {inherit python;}; + server = python.pkgs.buildPythonPackage (attrs + // { + env.CUSTOM_ENVVAR = "foobar"; + }); + + scanbuddy-pkg = pkgs.symlinkJoin { + name = "scanbuddy"; + paths = [script server]; + }; in { # devShells.${system}.default = pkgs.mkShell { # packages = with pkgs; [ @@ -33,28 +54,32 @@ # exec zsh # ''; # }; - packages.${system} = let - scanbuddy = pkgs.writeShellApplication { - name = "scanbuddy"; - - runtimeInputs = with pkgs; [sane-backends brscan5 ghostscript]; - - text = builtins.readFile ./scanbuddy.bash; - }; - - # Returns an attribute set that can be passed to `buildPythonPackage`. - attrs = serverProject.renderers.buildPythonPackage {inherit python;}; - server = python.pkgs.buildPythonPackage (attrs - // { - env.CUSTOM_ENVVAR = "foobar"; - }); - in { - default = pkgs.symlinkJoin { - name = "scanbuddy"; - paths = [scanbuddy server]; - }; - inherit scanbuddy; + packages.${system} = { + default = scanbuddy-pkg; + inherit script; inherit server; }; + + nixosModules.default = { + config, + pkgs, + lib, + ... + }: { + options = { + service.scanbuddy = lib.mkEnableOption "Enable the scanbuddy server"; + }; + config = { + systemd.services.scanbuddy = lib.mkIf config.service.scanbuddy { + description = "The scanbuddy webservice"; + wantedBy = ["multi-user.target"]; + serviceConfig = { + ExecStart = "${scanbuddy-pkg}/bin/scanbuddy-server"; + Path = ["${scanbuddy-pkg}/bin"]; + WorkingDirectory = "/var/lib/scanbuddy"; + }; + }; + }; + }; }; } diff --git a/pyproject.toml b/pyproject.toml index 49823ce..61e7598 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,6 +6,7 @@ description = "A REST server for the scanbuddy shell-script" # define any Python dependencies dependencies = [ "flask>3", + "waitress" ] # define the CLI executable