Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
9895028
fix: add Raspberry Pi 3 support to device manifests and OS sublists
esto-openscan May 26, 2026
d06e849
Merge pull request #115 from esto-openscan/temp-raspi3
esto-openscan May 26, 2026
f81434e
Create FUNDING.yml
OpenScanEu May 27, 2026
fecfdc5
fix(camera): improve Raspberry Pi 5 camera detection and diagnostics
esto-openscan Jun 1, 2026
4dd1f02
Merge pull request #117 from esto-openscan/develop
esto-openscan Jun 1, 2026
199c4a3
refactor(example_tasks): rename and refactor `hello_world_async_task`…
esto-openscan Jun 17, 2026
84c3784
Merge pull request #122 from esto-openscan/develop
esto-openscan Jun 17, 2026
4eb7ddb
fix(scan): await photo save so motor moves don't overlap encode/save …
bgrupczy Jul 6, 2026
fe56712
refactor(scan_task): migrate photo capture to async method
esto-openscan Jul 22, 2026
f9218fe
refactor(scan_task): migrate photo capture to async method (#132)
esto-openscan Jul 23, 2026
2d1785b
refactor(tasks): replace static core task list with a centralized reg…
esto-openscan Jul 23, 2026
7c944d1
Merge branch 'OpenScan-org:develop' into develop
esto-openscan Jul 23, 2026
1ed9124
Feature/deb packaging and prepare stable release (#133)
esto-openscan Jul 23, 2026
31b4c4b
Merge remote-tracking branch 'upstream/develop' into sync-upstream-an…
esto-openscan Jul 24, 2026
8688881
chore(openapi): regenerate specs after upstream merge
esto-openscan Jul 24, 2026
9350d57
Merge pull request #134 from esto-openscan/sync-upstream-and-task-reg…
esto-openscan Jul 24, 2026
220d104
refactor(update_api): simplify endpoints and improve response structu…
esto-openscan Jul 24, 2026
c8a9a93
fix(router): update OpenAPI schema to use correct response types
esto-openscan Jul 27, 2026
841d045
fix(update): schedule updates outside firmware service cgroup
esto-openscan Jul 27, 2026
b65baaa
fix(router): define and use typed response models for `/configuration…
esto-openscan Jul 28, 2026
27105c3
fix(router): add binary ZIP response schema for `/logs/archive`
esto-openscan Jul 28, 2026
5469650
fix(router): add JSON request schemas for project and scan creation i…
esto-openscan Jul 28, 2026
6d0f4d6
Merge pull request #136 from esto-openscan/fix/api-inconsistencies
esto-openscan Jul 28, 2026
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
15 changes: 15 additions & 0 deletions .github/FUNDING.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# These are supported funding model platforms

github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: openscan
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
polar: # Replace with a single Polar username
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
thanks_dev: # Replace with a single thanks.dev username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
10 changes: 10 additions & 0 deletions debian/changelog
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
openscan3-firmware (0.12.0) unstable; urgency=medium

* Add Debian package installation and service integration for the firmware.
* Add API support for checking and installing OpenScan system updates.
* Add system health checks and repair actions.
* Preserve existing device settings during installation and upgrades.
* Schedule update installation outside the firmware service cgroup.
* Preserve the pre-upgrade service state across interrupted dpkg retries.

-- OpenScan3 Contributors <info@openscan.eu> Thu, 23 Jul 2026 00:00:00 +0200
4 changes: 4 additions & 0 deletions debian/clean
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
build/
dist/
*.egg-info/
debian/build-wheelhouse/
27 changes: 27 additions & 0 deletions debian/control
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
Source: openscan3-firmware
Section: misc
Priority: optional
Maintainer: OpenScan3 Contributors <info@openscan.eu>
Build-Depends:
debhelper-compat (= 13),
libcap-dev,
python3,
python3-pip,
python3-venv
Standards-Version: 4.7.0
Homepage: https://github.com/OpenScan-org/OpenScan3

Package: openscan3-firmware
Architecture: any
Depends:
${misc:Depends},
adduser,
python3,
python3-pip,
python3-venv
Recommends:
openscan3-system-config
Description: OpenScan3 firmware runtime
FastAPI-based firmware runtime for OpenScan3 Raspberry Pi photogrammetry
scanners. The Debian package installs a bundled Python wheelhouse and creates
a runtime virtual environment under /opt/openscan3.
1 change: 1 addition & 0 deletions debian/install
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
debian/openscan3.service lib/systemd/system/
21 changes: 21 additions & 0 deletions debian/openscan3.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[Unit]
Description=OpenScan3 firmware API
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=openscan
Group=openscan
WorkingDirectory=/var/openscan3
Environment=OPENSCAN_SETTINGS_DIR=/etc/openscan3
Environment=OPENSCAN_RUNTIME_DIR=/var/openscan3
Environment=OPENSCAN_PROJECT_DIR=/var/openscan3/projects
Environment=OPENSCAN_COMMUNITY_TASKS_DIR=/var/openscan3/community-tasks
Environment=OPENSCAN_LOG_DIR=/var/log/openscan3
ExecStart=/opt/openscan3/current/venv/bin/openscan-firmware serve --root-path /api
Restart=on-failure
RestartSec=5s

[Install]
WantedBy=multi-user.target
193 changes: 193 additions & 0 deletions debian/postinst
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
#!/bin/sh
set -e

DEB_PACKAGE_NAME="openscan3-firmware"
PYTHON_PACKAGE_NAME="openscan-firmware"
RUNTIME_USER="openscan"
RUNTIME_GROUP="openscan"
RELEASES_DIR="/opt/openscan3/releases"
CURRENT_LINK="/opt/openscan3/current"
SERVICE_STATE_DIR="/run/openscan3-firmware"
SERVICE_WAS_ACTIVE_FILE="$SERVICE_STATE_DIR/openscan3.service-was-active"
RELEASE_METADATA="/usr/share/openscan3-firmware/release.json"

package_version() {
dpkg-query -W -f='${Version}' "$DEB_PACKAGE_NAME"
}

bundled_python_version() {
debian_version="$1"
python3 - "$RELEASE_METADATA" "$debian_version" <<'PY'
import json
import pathlib
import sys

path = pathlib.Path(sys.argv[1])
installed_debian_version = sys.argv[2]
try:
metadata = json.loads(path.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError) as error:
raise SystemExit(f"Invalid or missing firmware release metadata {path}: {error}")

if metadata.get("schema") != 1:
raise SystemExit(f"Unsupported firmware release metadata schema in {path}")
if metadata.get("debian_version") != installed_debian_version:
raise SystemExit(
"Firmware release metadata does not match installed Debian version: "
f"{metadata.get('debian_version')!r} != {installed_debian_version!r}"
)
python_version = metadata.get("python_version")
if not isinstance(python_version, str) or not python_version:
raise SystemExit(f"Missing Python version in firmware release metadata {path}")
print(python_version)
PY
}

ensure_runtime_user() {
if ! getent group "$RUNTIME_GROUP" >/dev/null; then
addgroup --system "$RUNTIME_GROUP" >/dev/null
fi

if ! getent passwd "$RUNTIME_USER" >/dev/null; then
adduser \
--system \
--ingroup "$RUNTIME_GROUP" \
--home /var/openscan3 \
--no-create-home \
--disabled-login \
"$RUNTIME_USER" >/dev/null
fi
}

set_default_acl_if_available() {
dir="$1"
if command -v setfacl >/dev/null 2>&1; then
setfacl -m "g:${RUNTIME_GROUP}:rwx" -m "d:g:${RUNTIME_GROUP}:rwx" "$dir" || true
else
# TODO: Consider depending on acl once the image/package baseline is settled.
:
fi
}

create_runtime_dir() {
dir="$1"
install -d -o "$RUNTIME_USER" -g "$RUNTIME_GROUP" -m 2775 "$dir"
chmod 2775 "$dir"
chown "$RUNTIME_USER:$RUNTIME_GROUP" "$dir"
set_default_acl_if_available "$dir"
}

seed_settings_file() {
source_file="$1"
target_file="$2"

if [ ! -f "$source_file" ]; then
return 0
fi

if [ -e "$target_file" ]; then
return 0
fi

install -o "$RUNTIME_USER" -g "$RUNTIME_GROUP" -m 0664 "$source_file" "$target_file"
}

seed_default_settings() {
version="$1"
defaults_dir="$RELEASES_DIR/$version/default-settings"

if [ ! -d "$defaults_dir" ]; then
echo "Missing bundled default settings: $defaults_dir" >&2
return 0
fi

create_runtime_dir /etc/openscan3/device
create_runtime_dir /etc/openscan3/firmware
create_runtime_dir /etc/openscan3/logging

for source_file in "$defaults_dir"/device/*.json; do
seed_settings_file "$source_file" "/etc/openscan3/device/$(basename "$source_file")"
done
for source_file in "$defaults_dir"/firmware/*.json; do
seed_settings_file "$source_file" "/etc/openscan3/firmware/$(basename "$source_file")"
done
for source_file in "$defaults_dir"/logging/*.json; do
seed_settings_file "$source_file" "/etc/openscan3/logging/$(basename "$source_file")"
done
}

install_release_venv() {
version="$1"
release_dir="$RELEASES_DIR/$version"
wheelhouse="$release_dir/wheels"
venv="$release_dir/venv"

if [ ! -d "$wheelhouse" ]; then
echo "Missing bundled wheelhouse: $wheelhouse" >&2
exit 1
fi

rm -rf "$venv"
python3 -m venv --system-site-packages "$venv"
"$venv/bin/python" -m pip install \
--no-index \
--find-links="$wheelhouse" \
"$PYTHON_PACKAGE_NAME==$version"

chown -R root:root "$release_dir"
find "$release_dir" -type d -exec chmod 0755 {} +
}

update_current_link() {
version="$1"
release_dir="$RELEASES_DIR/$version"

if [ -L "$CURRENT_LINK" ] || [ ! -e "$CURRENT_LINK" ]; then
ln -sfn "$release_dir" "$CURRENT_LINK"
else
echo "$CURRENT_LINK exists and is not a symlink; leaving it unchanged" >&2
fi
}

reload_and_restart_service() {
previous_version="${1:-}"

if command -v systemctl >/dev/null 2>&1 && [ -d /run/systemd/system ]; then
systemctl daemon-reload

if [ -f "$SERVICE_WAS_ACTIVE_FILE" ]; then
systemctl restart openscan3.service
rm -f "$SERVICE_WAS_ACTIVE_FILE"
elif [ -z "$previous_version" ]; then
systemctl start openscan3.service || true
else
systemctl try-restart openscan3.service || true
fi
fi
}

case "$1" in
configure)
version="$(package_version)"
python_version="$(bundled_python_version "$version")"

ensure_runtime_user

install -d -o root -g root -m 0755 /opt/openscan3 "$RELEASES_DIR"
create_runtime_dir /etc/openscan3
create_runtime_dir /var/openscan3
create_runtime_dir /var/openscan3/projects
create_runtime_dir /var/openscan3/community-tasks
create_runtime_dir /var/log/openscan3

seed_default_settings "$python_version"
install_release_venv "$python_version"
update_current_link "$python_version"
# TODO: Prune older release directories after release-retention policy is defined.
reload_and_restart_service "${2:-}"
;;
esac

#DEBHELPER#

exit 0
24 changes: 24 additions & 0 deletions debian/preinst
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/bin/sh
set -e

SERVICE_STATE_DIR="/run/openscan3-firmware"
SERVICE_WAS_ACTIVE_FILE="$SERVICE_STATE_DIR/openscan3.service-was-active"

record_service_state() {
if command -v systemctl >/dev/null 2>&1 \
&& [ -d /run/systemd/system ] \
&& systemctl is-active --quiet openscan3.service; then
install -d -o root -g root -m 0755 "$SERVICE_STATE_DIR"
: > "$SERVICE_WAS_ACTIVE_FILE"
fi
}

case "$1" in
install|upgrade)
record_service_state
;;
esac

#DEBHELPER#

exit 0
47 changes: 47 additions & 0 deletions debian/rules
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#!/usr/bin/make -f

export DH_VERBOSE=1

PYPROJECT_VERSION := $(shell python3 -c 'import pathlib, tomllib; print(tomllib.loads(pathlib.Path("pyproject.toml").read_text())["project"]["version"])')
DEBIAN_VERSION := $(shell dpkg-parsechangelog -SVersion)
WHEELHOUSE := debian/build-wheelhouse
RELEASE_DIR := debian/openscan3-firmware/opt/openscan3/releases/$(PYPROJECT_VERSION)
RELEASE_METADATA := debian/openscan3-firmware/usr/share/openscan3-firmware/release.json
OPENSCAN_RELEASE_CHANNEL ?= stable

%:
dh $@

override_dh_auto_clean:
dh_auto_clean
rm -rf build dist *.egg-info $(WHEELHOUSE)

override_dh_auto_build:
mkdir -p $(WHEELHOUSE)
python3 -m pip wheel --wheel-dir $(WHEELHOUSE) .

override_dh_auto_install:
mkdir -p $(RELEASE_DIR)/wheels
cp -a $(WHEELHOUSE)/*.whl $(RELEASE_DIR)/wheels/
mkdir -p $(RELEASE_DIR)/default-settings/device
mkdir -p $(RELEASE_DIR)/default-settings/firmware
mkdir -p $(RELEASE_DIR)/default-settings/logging
cp -a settings/device/default_*.json $(RELEASE_DIR)/default-settings/device/
cp -a settings/device/example_custom.json $(RELEASE_DIR)/default-settings/device/
cp -a settings/firmware/*.json $(RELEASE_DIR)/default-settings/firmware/
cp -a settings/logging/*.json $(RELEASE_DIR)/default-settings/logging/
python3 scripts/write-release-metadata.py \
--output $(RELEASE_METADATA) \
--channel "$(OPENSCAN_RELEASE_CHANNEL)" \
--debian-version "$(DEBIAN_VERSION)" \
--python-version "$(PYPROJECT_VERSION)" \
--build-timestamp "$(OPENSCAN_RELEASE_TIMESTAMP)" \
--source-revision "$(OPENSCAN_SOURCE_REVISION)" \
--expected-debian-version "$(OPENSCAN_DEBIAN_VERSION)" \
--expected-python-version "$(OPENSCAN_PYTHON_VERSION)"

override_dh_installsystemd:
# Let debhelper enable the unit using deb-systemd-helper. That works while
# pi-gen installs the package in a chroot, where systemd itself is not
# running. Do not start the service during package installation.
dh_installsystemd --no-start
1 change: 1 addition & 0 deletions debian/source/format
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.0 (native)
4 changes: 4 additions & 0 deletions dist/local_json.rpi-imager-manifest
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"pi5",
"pi4",
"pi400",
"pi3",
"all"
],
"capabilities": [
Expand All @@ -81,6 +82,7 @@
"pi5",
"pi4",
"pi400",
"pi3",
"all"
],
"capabilities": [
Expand Down Expand Up @@ -128,6 +130,7 @@
"pi5",
"pi4",
"pi400",
"pi3",
"all"
],
"capabilities": [
Expand All @@ -151,6 +154,7 @@
"pi5",
"pi4",
"pi400",
"pi3",
"all"
],
"capabilities": [
Expand Down
Loading