Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
FROM python:3.9-bullseye

ENV DEBIAN_FRONTEND=noninteractive
ARG GECKODRIVER_VERSION=v0.31.0
ARG CHROME_VERSION=107.0.5304.110-1
ARG CHROMEDRIVER_VERSION=107.0.5304.62
ARG NODE_MAJOR=16

RUN apt-get update \
&& apt-get install -y --no-install-recommends \
bzip2 \
ca-certificates \
curl \
firefox-esr \
git \
gnupg2 \
iputils-ping \
libdbus-glib-1-2 \
libsane \
libsane-common \
libxtst6 \
net-tools \
nmap \
sane-utils \
software-properties-common \
tzdata \
unzip \
usbutils \
wget \
xauth \
xvfb \
&& rm -rf /var/lib/apt/lists/*

RUN curl -fsSL https://deb.nodesource.com/setup_${NODE_MAJOR}.x | bash - \
&& apt-get update \
&& apt-get install -y --no-install-recommends nodejs \
&& node --version | grep -E "^v${NODE_MAJOR}\\." \
&& rm -rf /var/lib/apt/lists/*

RUN python3.9 -m pip install --no-cache-dir --upgrade pip \
&& python3.9 -m pip install --no-cache-dir tox==3.27.1

# Use distro Firefox ESR to ensure runtime library compatibility with Debian Bullseye.
RUN ln -sf /usr/bin/firefox-esr /usr/local/bin/firefox

RUN wget -q -O /tmp/geckodriver.tar.gz \
"https://github.com/mozilla/geckodriver/releases/download/${GECKODRIVER_VERSION}/geckodriver-${GECKODRIVER_VERSION}-linux64.tar.gz" \
&& tar -xzf /tmp/geckodriver.tar.gz -C /usr/local/bin \
&& chmod +x /usr/local/bin/geckodriver \
&& rm -f /tmp/geckodriver.tar.gz

RUN wget -q -O /tmp/google-chrome.deb \
"https://mirror.cs.uchicago.edu/google-chrome/pool/main/g/google-chrome-stable/google-chrome-stable_${CHROME_VERSION}_amd64.deb" \
&& apt-get update \
&& apt-get install -y --no-install-recommends /tmp/google-chrome.deb \
&& rm -f /etc/apt/sources.list.d/google-chrome.list \
&& rm -f /tmp/google-chrome.deb \
&& rm -rf /var/lib/apt/lists/*

RUN wget -q -O /tmp/chromedriver.zip \
"https://chromedriver.storage.googleapis.com/${CHROMEDRIVER_VERSION}/chromedriver_linux64.zip" \
&& unzip -q /tmp/chromedriver.zip -d /tmp \
&& mv /tmp/chromedriver /usr/local/bin/chromedriver \
&& chmod +x /usr/local/bin/chromedriver \
&& rm -f /tmp/chromedriver.zip

# Add scanner IDs in case automatic discovery fails.
RUN echo "usb 0x4b8 0x12c" >> /etc/sane.d/epson2.conf \
&& echo "usb 0x4b8 0x151" >> /etc/sane.d/epson2.conf

WORKDIR /workspaces/scanomatic-standalone
41 changes: 41 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"name": "scanomatic-standalone",
"build": {
"dockerfile": "Dockerfile",
"context": ".."
},
"remoteUser": "root",
"runArgs": [
"--privileged",
"--device=/dev/bus/usb:/dev/bus/usb"
],
"containerEnv": {
"LOGGING_LEVEL": "20",
"SOM_PROJECTS_ROOT": "/somprojects",
"SOM_SETTINGS": "/root/.scan-o-matic"
},
"mounts": [
"source=scanomatic-settings,target=/root/.scan-o-matic,type=volume",
"source=scanomatic-projects,target=/somprojects,type=volume"
],
"forwardPorts": [
5000
],
"portsAttributes": {
"5000": {
"label": "Scan-o-Matic",
"onAutoForward": "openBrowser"
}
},
"postCreateCommand": "bash .devcontainer/postCreate.sh",
"postStartCommand": "bash .devcontainer/postStart.sh",
"customizations": {
"vscode": {
"extensions": [
"ms-python.python",
"ms-python.vscode-pylance",
"ms-azuretools.vscode-docker"
]
}
}
}
10 changes: 10 additions & 0 deletions .devcontainer/postCreate.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/usr/bin/env bash
set -euo pipefail

cd /workspaces/scanomatic-standalone

python3.9 -m pip install --upgrade pip
python3.9 -m pip install -r requirements.txt

npm ci
npm run build
9 changes: 9 additions & 0 deletions .devcontainer/postStart.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/usr/bin/env bash
set -euo pipefail

CONFIG_SRC="/workspaces/scanomatic-standalone/data/config"
CONFIG_DST="${SOM_SETTINGS:-/root/.scan-o-matic}/config"

mkdir -p "$CONFIG_DST"
cp -n "$CONFIG_SRC"/* "$CONFIG_DST"/ 2>/dev/null || true
mkdir -p "${SOM_PROJECTS_ROOT:-/somprojects}"
12 changes: 12 additions & 0 deletions .devcontainer/start-scanomatic.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/usr/bin/env bash
set -euo pipefail

cd /workspaces/scanomatic-standalone

export PYTHONPATH="/workspaces/scanomatic-standalone${PYTHONPATH:+:$PYTHONPATH}"
export PATH="/workspaces/scanomatic-standalone/scripts${PATH:+:$PATH}"

exec python3.9 scripts/scan-o-matic \
--host 0.0.0.0 \
--port 5000 \
--no-browser
28 changes: 28 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Copilot Instructions for This Repository

## Environment Overview
- Repository: `scanomatic-standalone`
- Primary runtime: Python (backend) with JavaScript tooling for frontend tests/build.
- Typical development environment: Linux dev container.
- Docker Compose is used for local app startup in normal workflows.

## Project Layout (High Level)
- `scanomatic/`: Main Python package.
- `tests/`: Unit, integration, and system tests.
- `.github/workflows/`: CI definitions.
- `scripts/`: Entrypoints and helper scripts.

## Validation Commands
- Backend test and quality environments are run through `tox`.
- Common environments: `tox -e lint`, `tox -e mypy`, `tox -e unit`, `tox -e integration`, `tox -e system`.
- Frontend tests use Karma (`karma.conf.js`), lint uses ESLint.
- For incremental type checks on modified files: `./typecheck-changed.sh`.

## Runtime Notes
- App startup is commonly done with Docker Compose (`docker-compose up -d`).
- Some system/integration flows may run local services inside the container when Docker daemon access is unavailable.

## Coding Guidance
- Prefer minimal, targeted changes that match existing style.
- Keep edits scoped; avoid unrelated refactors.
- When changing behavior, update or add tests close to the affected area.
28 changes: 14 additions & 14 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on: [push]

jobs:
frontend-lint:
runs-on: ubuntu-20.04
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v2

Expand All @@ -17,7 +17,7 @@ jobs:
npm run lint

frontend-test:
runs-on: ubuntu-20.04
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v2
- uses: browser-actions/setup-chrome@latest
Expand All @@ -38,7 +38,7 @@ jobs:
npm test

frontend-build:
runs-on: ubuntu-20.04
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v2

Expand All @@ -51,7 +51,7 @@ jobs:
npm run build

backend-lint:
runs-on: ubuntu-20.04
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v2

Expand All @@ -63,14 +63,14 @@ jobs:
- name: Install General Dependencies
run: |
python3.9 -m pip install --upgrade pip
pip install tox
pip install tox==3.27.1

- name: Lint and Types
run: |
tox -e lint

backend-mypy:
runs-on: ubuntu-20.04
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v2

Expand All @@ -82,14 +82,14 @@ jobs:
- name: Install General Dependencies
run: |
python3.9 -m pip install --upgrade pip
pip install tox
pip install tox==3.27.1

- name: Lint and Types
run: |
tox -e mypy

backend-test-unit:
runs-on: ubuntu-20.04
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v2

Expand All @@ -101,14 +101,14 @@ jobs:
- name: Install General Dependencies
run: |
python3.9 -m pip install --upgrade pip
pip install tox
pip install tox==3.27.1

- name: Run unit tests
run: |
tox -e unit

backend-test-integration:
runs-on: ubuntu-20.04
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v2

Expand All @@ -120,14 +120,14 @@ jobs:
- name: Install General Dependencies
run: |
python3.9 -m pip install --upgrade pip
pip install tox
pip install tox==3.27.1

- name: Run integration tests
run: |
tox -e integration

test-system:
runs-on: ubuntu-20.04
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v2
- uses: browser-actions/setup-chrome@latest
Expand All @@ -143,15 +143,15 @@ jobs:
- name: Install General Dependencies
run: |
python3.9 -m pip install --upgrade pip
pip install tox
pip install tox==3.27.1

- name: Run headless system test
uses: GabrielBB/xvfb-action@v1
with:
run: tox -e system

docker-build:
runs-on: ubuntu-20.04
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v2

Expand Down
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
16
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ WORKDIR /src
RUN npm ci
RUN npm run build

FROM python:3.9
FROM python:3.9-bullseye
RUN apt-get update
RUN export DEBIAN_FRONTEND=noninteractive \
&& ln -fs /usr/share/zoneinfo/Etc/UTC /etc/localtime \
Expand Down
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,17 @@ Currently this is not required as the code is still riddled with type errors, ho
### System tests

System tests require `firefox`, `geckodriver`, `google-chrome` and `chromedriver` to be installed on host system.
After that it run with `tox -e system`.
After that they can be run with `tox -e system`.

By default the test fixture uses Docker Compose when a working Docker daemon is available, and automatically falls back to a local in-container server when Docker is unavailable (for example inside a dev container without nested Docker).

You can force mode selection with `SOM_SYSTEM_TEST_MODE`:
- `SOM_SYSTEM_TEST_MODE=docker tox -e system`
- `SOM_SYSTEM_TEST_MODE=local tox -e system`

The devcontainer installs browser dependencies for system tests, including `firefox-esr`, `geckodriver`, `google-chrome` and `chromedriver`.
To verify installed versions inside the container:
- `firefox --version`
- `geckodriver --version`
- `google-chrome --version`
- `chromedriver --version`
9 changes: 8 additions & 1 deletion scanomatic/io/rpc_client.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import enum
import shutil
import socket
import sys
import xmlrpc.client
from collections.abc import Callable
from http.client import CannotSendRequest, ResponseNotReady
Expand Down Expand Up @@ -80,7 +82,12 @@ def __init__(self, host: str, port: int, user_id: str):
def launch_local(self) -> None:
if self.online is False and self.local:
self._logger.info("Launching new local server")
Popen(["scan-o-matic_server"])
launcher = shutil.which("scan-o-matic_server")
if launcher is not None:
# Use the active interpreter to avoid shebang/path issues.
Popen([sys.executable, launcher])
else:
Popen(["scan-o-matic_server"])
else:
self._logger.warning(
"Can't launch because server is {0}".format(
Expand Down
Loading
Loading