Initializare dupa curatare folder

This commit is contained in:
2026-10-05 11:46:46 +03:00
commit 48e800adc1
39 changed files with 6303 additions and 0 deletions
+28
View File
@@ -0,0 +1,28 @@
# Give the RFID reader a stable name: /dev/rdm-reader
#
# Without this, the adapter is /dev/ttyUSB0 or /dev/ttyUSB1 depending on the
# order the kernel enumerated USB devices, which changes between reboots and
# whenever anything else is plugged in. Authenticating against the wrong reader
# is a real failure, so pin the identity here instead of hoping.
#
# Match on the USB-serial bridge chip. Add serial numbers once you know them -
# see README "Identifying the right ttyUSB" for how to read them off:
#
# SUBSYSTEM=="tty", ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="7523", \
# ATTRS{serial}=="REPLACE_WITH_YOUR_SERIAL", SYMLINK+="rdm-reader", MODE="0660", GROUP="dialout"
#
# Current rule: match by vendor:product (CH340 is 1a86:7523).
# Commented-out alternatives for other common bridge chips are listed below.
# Only ONE rule should be active - remove or comment the others.
# CH340 / CH341 (most common on cheap USB-TTL adapters)
SUBSYSTEM=="tty", ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="7523", SYMLINK+="rdm-reader", MODE="0660", GROUP="dialout"
# FTDI FT232R
# SUBSYSTEM=="tty", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", ATTRS{serial}=="REPLACE_ME", SYMLINK+="rdm-reader", MODE="0660", GROUP="dialout"
# Silicon Labs CP2102
# SUBSYSTEM=="tty", ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="ea60", ATTRS{serial}=="REPLACE_ME", SYMLINK+="rdm-reader", MODE="0660", GROUP="dialout"
# Prolific PL2303
# SUBSYSTEM=="tty", ATTRS{idVendor}=="067b", ATTRS{idProduct}=="2303", ATTRS{serial}=="REPLACE_ME", SYMLINK+="rdm-reader", MODE="0660", GROUP="dialout"
+261
View File
@@ -0,0 +1,261 @@
#!/usr/bin/env bash
#
# Install the RDM card reader as a service that starts at boot and keeps the
# card on the reader available over HTTP for the web page to claim.
#
# Run as root on the Raspberry Pi:
#
# sudo ./deploy/install.sh --origin https://your.web.app
#
# Options:
# --origin URL web app origin allowed to read the API (repeatable)
# --port PORT HTTP port for the bridge (default 8765)
# --dir PATH install location (default /opt/rdm-card-reader)
# --user NAME service account (default rdmcard)
# --no-service install and configure, but do not enable the service
#
set -euo pipefail
ORIGINS=()
BRIDGE_PORT=8765
INSTALL_DIR=/opt/rdm-card-reader
SERVICE_USER=rdmcard
INSTALL_SERVICE=1
SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
while [[ $# -gt 0 ]]; do
case "$1" in
--origin) ORIGINS+=("$2"); shift 2 ;;
--port) BRIDGE_PORT="$2"; shift 2 ;;
--dir) INSTALL_DIR="$2"; shift 2 ;;
--user) SERVICE_USER="$2"; shift 2 ;;
--no-service) INSTALL_SERVICE=0; shift ;;
-h|--help) sed -n '2,20p' "$0"; exit 0 ;;
*) echo "unknown option: $1" >&2; exit 2 ;;
esac
done
log() { printf '\n\033[1m==> %s\033[0m\n' "$*"; }
warn() { printf '\033[33mwarning: %s\033[0m\n' "$*" >&2; }
if [[ $EUID -ne 0 ]]; then
echo "Please run as root: sudo $0 $*" >&2
exit 1
fi
# ---------------------------------------------------------------------------
# 1. System packages
# ---------------------------------------------------------------------------
log "Checking system packages"
# Both of these need the network, so neither may abort the install: a device
# with no internet usually already has python3, and we can work without them.
apt-get update -qq 2>/dev/null || warn "apt-get update failed (no network?) - continuing"
apt-get install -y --no-install-recommends python3 python3-venv python3-pip \
|| warn "could not install system packages - continuing with what is present"
# ---------------------------------------------------------------------------
# 2. Install the application
# ---------------------------------------------------------------------------
log "Installing to $INSTALL_DIR"
mkdir -p "$INSTALL_DIR"
# Copy the source tree (excluding local build/test artifacts).
for item in src pyproject.toml README.md LAUNCH.md LICENSE deploy; do
if [[ -e "$SOURCE_DIR/$item" ]]; then
cp -a "$SOURCE_DIR/$item" "$INSTALL_DIR/"
fi
done
log "Building the virtual environment"
if ! python3 -m venv "$INSTALL_DIR/.venv"; then
warn "could not create the virtualenv."
warn "On Debian/Raspberry Pi OS this usually means python3-venv is missing:"
warn " sudo apt-get install python3-venv"
exit 1
fi
VENV_PY="$INSTALL_DIR/.venv/bin/python"
WHEEL_DIR="$INSTALL_DIR/src/rdm_card_reader/wheels"
# Upgrading pip is optional and needs the network.
"$INSTALL_DIR/.venv/bin/pip" install --quiet --upgrade pip 2>/dev/null \
|| echo " (kept the bundled pip - no network)"
# Install the runtime dependencies from the wheels shipped in the source tree.
#
# Deliberately NOT 'pip install .': that builds the project, which requires
# downloading setuptools into an isolated build environment. With no internet
# it fails with
# Could not find a version that satisfies the requirement setuptools>=68
# even though everything needed to *run* is sitting in the wheel directory.
# Installing the runtime dependencies directly, and putting 'src' on the
# path, achieves the same thing and works offline.
INSTALLED_PACKAGE=0
if [[ -d "$WHEEL_DIR" ]] && "$VENV_PY" -m pip install --quiet --no-index \
--find-links "$WHEEL_DIR" rdm6300 pyserial 2>/dev/null; then
echo " installed rdm6300 and pyserial from the bundled wheels (offline)"
else
echo " bundled wheels unavailable - falling back to PyPI"
if "$VENV_PY" -m pip install --quiet rdm6300 pyserial 2>/dev/null; then
echo " installed rdm6300 and pyserial from PyPI"
else
warn "could not install rdm6300 / pyserial."
warn "The app will try again on first start (it can install from its own"
warn "bundled wheels), but check network access if that also fails."
fi
fi
# Status LED support. Best-effort: the reader works fine without a LED, so a
# failure here must not stop the install.
#
# --no-deps is deliberate: gpiozero declares colorzero, which in turn declares
# setuptools. setuptools is only a packaging requirement and is never imported
# at runtime, so it is not vendored (it would add 818 KB for nothing).
if [[ -d "$WHEEL_DIR" ]] && "$VENV_PY" -m pip install --quiet --no-index \
--no-deps --find-links "$WHEEL_DIR" gpiozero colorzero 2>/dev/null; then
echo " installed gpiozero + colorzero for the status LED (offline)"
else
if "$VENV_PY" -m pip install --quiet gpiozero colorzero 2>/dev/null; then
echo " installed gpiozero + colorzero from PyPI"
else
warn "could not install LED support - the reader still works, the LED"
warn "will be inactive. The app can install it later on first use."
fi
fi
# Make the package importable. The .pth file adds src/ to sys.path, so
# 'python -m rdm_card_reader' resolves from any working directory.
SITE_PACKAGES="$("$VENV_PY" -c 'import sysconfig; print(sysconfig.get_paths()["purelib"])')"
echo "$INSTALL_DIR/src" > "$SITE_PACKAGES/rdm_card_reader_src.pth"
# Console-script equivalent, so the unit and the docs work as written.
# The shebang must be the venv's interpreter by absolute path: '#!/usr/bin/env
# python3' would pick up the *system* python when the venv is not activated,
# and the package would not be importable.
cat > "$INSTALL_DIR/.venv/bin/rdm-card-reader" <<EOF
#!$VENV_PY
import sys
from rdm_card_reader.cli import main
if __name__ == "__main__":
sys.exit(main())
EOF
chmod +x "$INSTALL_DIR/.venv/bin/rdm-card-reader"
# Standalone launcher for running without activating anything.
cat > "$INSTALL_DIR/run.sh" <<EOF
#!/bin/sh
# Convenience launcher: ./run.sh [args...]
exec "$INSTALL_DIR/.venv/bin/rdm-card-reader" "\$@"
EOF
chmod +x "$INSTALL_DIR/run.sh"
# Sanity check: prove it imports and can run before we enable a service.
if "$INSTALL_DIR/.venv/bin/rdm-card-reader" --version >/dev/null 2>&1; then
echo " verified: $("$INSTALL_DIR/.venv/bin/rdm-card-reader" --version)"
else
warn "the reader does not run yet. Check with:"
warn " $INSTALL_DIR/.venv/bin/python -m rdm_card_reader --list-ports"
fi
# ---------------------------------------------------------------------------
# 3. Service account
# ---------------------------------------------------------------------------
log "Creating service account '$SERVICE_USER'"
if ! id -u "$SERVICE_USER" >/dev/null 2>&1; then
# No login shell, no home directory: this account only reads a serial port.
useradd --system --no-create-home --shell /usr/sbin/nologin "$SERVICE_USER"
fi
# Serial access requires the dialout group.
usermod -aG dialout "$SERVICE_USER"
echo " $SERVICE_USER is in group dialout"
# ---------------------------------------------------------------------------
# 4. Stable device name
# ---------------------------------------------------------------------------
log "Installing the udev rule (/dev/rdm-reader)"
install -m 0644 "$SOURCE_DIR/deploy/99-rdm-reader.rules" \
/etc/udev/rules.d/99-rdm-reader.rules
udevadm control --reload-rules
udevadm trigger --subsystem-match=tty || true
sleep 1
if [[ -e /dev/rdm-reader ]]; then
echo " /dev/rdm-reader -> $(readlink -f /dev/rdm-reader)"
else
warn "/dev/rdm-reader does not exist yet."
warn "The reader may not be plugged in, or it uses a different chip."
warn "Run '$INSTALL_DIR/.venv/bin/rdm-card-reader --list-ports --verbose-ports'"
warn "then edit /etc/udev/rules.d/99-rdm-reader.rules to match your adapter."
fi
# ---------------------------------------------------------------------------
# 5. Service
# ---------------------------------------------------------------------------
log "Installing the systemd unit"
UNIT=/etc/systemd/system/rdm-card-reader.service
install -m 0644 "$SOURCE_DIR/deploy/rdm-card-reader.service" "$UNIT"
# Rewrite the ExecStart line with the real install dir, port and origins.
ORIGIN_ARGS=""
for origin in "${ORIGINS[@]:-}"; do
[[ -n "$origin" ]] && ORIGIN_ARGS+=" --allow-origin $origin"
done
python3 - "$UNIT" "$INSTALL_DIR" "$BRIDGE_PORT" "$ORIGIN_ARGS" "$SERVICE_USER" <<'PY'
import os, re, sys
unit, install_dir, port, origin_args, user = sys.argv[1:6]
text = open(unit).read()
# The console script is created by the installer, but the .pth file is what
# makes the package importable, so fall back to 'python -m' if it is ever
# absent. systemd's ExecStart has no shell, so no pipes or conditionals.
wrapper = f"{install_dir}/.venv/bin/rdm-card-reader"
if not os.path.exists(wrapper):
wrapper = f"{install_dir}/.venv/bin/python -m rdm_card_reader"
command = (
f"{wrapper} --serve "
f"--port /dev/rdm-reader --bridge-port {port} --mode login{origin_args}"
)
# Replace the whole (possibly line-continued) ExecStart definition.
text = re.sub(r"ExecStart=[\s\S]*?(?=\n\n|\nRestart=)", f"ExecStart={command}", text, count=1)
text = re.sub(r"^User=.*$", f"User={user}", text, count=1, flags=re.M)
open(unit, "w").write(text)
PY
# Make the venv readable by the service account.
chown -R "$SERVICE_USER:$SERVICE_USER" "$INSTALL_DIR"
if [[ "$INSTALL_SERVICE" -eq 1 ]]; then
log "Enabling and starting the service"
systemctl daemon-reload
systemctl enable rdm-card-reader.service
systemctl restart rdm-card-reader.service
sleep 2
systemctl --no-pager --lines=15 status rdm-card-reader.service || true
else
log "Skipping service start (--no-service)"
fi
# ---------------------------------------------------------------------------
# Done
# ---------------------------------------------------------------------------
cat <<EOF
$(log "Installed")
Bridge : http://127.0.0.1:$BRIDGE_PORT
Card state : curl http://127.0.0.1:$BRIDGE_PORT/api/card
Health : curl http://127.0.0.1:$BRIDGE_PORT/api/health
Logs : journalctl -u rdm-card-reader -f
Test a tap : place a card and run the curl above
The service starts automatically on boot. The card stays readable at
/api/card while it is on the reader; POST /api/card/claim hands it over
once, so a page reload cannot replay the same tap into a second login.
EOF
+56
View File
@@ -0,0 +1,56 @@
[Unit]
Description=RDM card reader HTTP bridge (serves card taps for web login)
Documentation=file:/opt/rdm-card-reader/README.md
# The serial device must exist before we start, otherwise the reader opens the
# wrong tty or none at all.
After=dev-serial.device network-online.target
Wants=network-online.target
# Do not restart forever if the reader is genuinely absent. These are [Unit]
# options, not [Service] - systemd ignores them in the wrong section.
StartLimitIntervalSec=60
StartLimitBurst=5
[Service]
Type=simple
User=rdmcard
Group=dialout
WorkingDirectory=/opt/rdm-card-reader
# --serve exposes the card on http://127.0.0.1:8765/api/card
# --port points at the udev alias, so the reader is the same device every boot.
# Autodetect refuses to guess if more than one USB adapter is present, which is
# the safe behaviour: a wrong guess means authenticating the wrong person.
# The wheels for rdm6300 and pyserial are bundled inside the package, so no
# --wheel-dir is needed here: the install works with no internet.
ExecStart=/opt/rdm-card-reader/.venv/bin/rdm-card-reader \
--serve \
--port /dev/rdm-reader \
--mode login \
--allow-origin https://your.web.app
Restart=always
RestartSec=3
# The bridge only needs to read the serial port and listen on loopback.
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
RestrictNamespaces=true
LockPersonality=true
MemoryDenyWriteExecute=true
DeviceAllow=/dev/rdm-reader rw
DevicePolicy=closed
# stdout/stderr go here; watch with: journalctl -u rdm-card-reader -f
StandardOutput=journal
StandardError=journal
SyslogIdentifier=rdm-card-reader
[Install]
WantedBy=multi-user.target